Skip to content

feat(config): C3a — healthcheck/dependsOn/hooks schema structs (spec 10/11) - #3

Merged
gustavobertoi merged 1 commit into
mainfrom
nightly/C3a
Jun 29, 2026
Merged

feat(config): C3a — healthcheck/dependsOn/hooks schema structs (spec 10/11)#3
gustavobertoi merged 1 commit into
mainfrom
nightly/C3a

Conversation

@gustavobertoi

Copy link
Copy Markdown
Contributor

Chunk C3a (M2-remainder)

Adds the declarative config structs that C3b (health), C3c (generate emits healthcheck:), and C4 (hooks runner) build on — structs + structural validation only, per specs 10 & 11.

What

  • Service.Healthcheck (*Healthcheck, nil = none): kind (oneof tcp/http/https/exec/pg_isready/redis) + per-kind params + Compose-style duration strings (interval/timeout/startPeriod) validated as parseable Go durations.
  • Service.DependsOn ([]DependsOn): service + condition (oneof healthy/started).
  • Workspace.Hooks / Project.Hooks (Hooks): preUp/firstRun/postUp/postPull/preDown lists of Hook (name/run/service/command-argv/workdir/env/timeout/retries/onFailure/once), with Hooks.IsZero().

Deliberately deferred (not this chunk)

Semantic rules live in their owning packages, not in config: dependsOn target existence + "condition: healthy needs a healthcheck" → internal/health (C3b/X2); run: exec requires a service + the firstRun ledger → internal/hooks (C4).

Tests

  • TestLoadValid now asserts the new fields parse (the valid testdata exercises every one — seeds the config-conformance golden).
  • New negative tests: bad healthcheck kind, bad duration, bad dependsOn condition, bad hook transport, missing hook command.
  • A new duration validator + oneof/duration/min error rendering.

Gate

make ci + make determinism + make smoke green. generate ignores the new fields until C3c, so output stays byte-deterministic.

🤖 Generated with Claude Code

…10/11)

Add the declarative shape the saga/health/hooks chunks build on:

- Service.Healthcheck (*Healthcheck): kind (oneof tcp/http/https/exec/
  pg_isready/redis) + per-kind params + Compose-style duration strings
  (interval/timeout/startPeriod), validated as parseable Go durations.
- Service.DependsOn ([]DependsOn): service + condition (oneof healthy/started).
- Workspace.Hooks / Project.Hooks (Hooks): preUp/firstRun/postUp/postPull/
  preDown lists of Hook (name/run/service/command argv/workdir/env/timeout/
  retries/onFailure/once), with Hooks.IsZero() for cheap skipping.

Structural validation only (validator/v10): a new `duration` validator plus
oneof/duration/min render messages. Semantic rules — dependsOn target
existence, "condition:healthy needs a healthcheck", run:exec requires a
service, the firstRun ledger — belong to internal/health (C3b) and
internal/hooks (C4), not here.

The valid testdata workspace now exercises every new field (seeds the
config-conformance golden); generate ignores them until C3c, so determinism
and smoke stay green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gustavobertoi
gustavobertoi merged commit 901aaad into main Jun 29, 2026
9 checks passed
@gustavobertoi
gustavobertoi deleted the nightly/C3a branch June 29, 2026 12:35
gustavobertoi added a commit that referenced this pull request Jun 29, 2026
#20)

internal/dns owns the devstack block in /etc/hosts for resolving
<service>.<project>.localhost on OS-resolver clients (the only consistently
reliable mechanism cross-platform — spec 05 gotchas). All ops are idempotent and
operate on an injectable path (fully temp-file-testable):

- Block/Apply/Remove/Present/Missing: Apply replaces (or appends, or on absent
  creates) ONLY the fenced block — content outside the fence is never touched;
  Apply(nil) removes it; Remove restores the original byte-for-byte; the file's
  mode is preserved.
- CLI `dns setup|status|remove` derives hostnames from the proxy route table
  (network.proxy.engine: caddy); writing /etc/hosts needs root, so a permission
  failure maps to a `sudo` remediation (per locked decision #3: build the logic,
  test with temp files, flag the human/sudo step).

Unit tests (temp file): insert preserving original content, idempotent re-apply,
replace-not-append, remove-strips-block-only + exact restore, Apply(nil) removes,
Present/Missing diff, create-when-absent. CLI tests: registration + status
reports the route host.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gustavobertoi added a commit that referenced this pull request Jun 29, 2026
internal/trust shells out to mkcert (NOT smallstep/truststore) to install/remove
the local root CA and diagnose readiness, behind an injectable Runner so it is
fully unit-testable without mkcert present:

- Available / CARoot / Install (`mkcert -install`) / Uninstall (`mkcert
  -uninstall`) / Status. Status probes mkcert-on-PATH, the CAROOT rootCA.pem,
  certutil (Firefox/NSS), and WSL2, emitting the exact one-line remediation for
  whatever is missing (clean-Ubuntu certutil hint, WSL2 Windows-store import).
- CLI `trust install|uninstall|status` replaces the stub. install/uninstall need
  sudo (mkcert writes system/NSS stores); status is read-only. Per locked
  decision #3: logic built + fake-runner tested, sudo flagged, Status is the
  self-verify probe (also feeds the doctor matrix in X6).

Unit tests (fake runner + temp CAROOT): available detection, status across
missing-mkcert / CA-not-installed / fully-ready / missing-certutil, install +
uninstall call-through, missing-mkcert error, install error propagation. CLI:
registration + read-only status.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gustavobertoi added a commit that referenced this pull request Jun 29, 2026
…(spec 05/13) (#33)

Add a `trust (mkcert)` probe to `devstack doctor`: it reports local-CA readiness
(mkcert on PATH, CAROOT rootCA.pem, certutil for Firefox/NSS) with the exact
remediation, as a WARNING (local HTTPS is opt-in, so a missing CA never fails
doctor). This is the decision-#3 self-verification for the sudo-gated trust
feature (N2) and a step toward the full X6 doctor matrix + safe --fix.

Test: doctor --json lists the trust probe.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gustavobertoi added a commit that referenced this pull request Jun 29, 2026
…I (spec 04) (#64)

Adds the `aws-sm` and `aws-ssm` provider kinds. It shells out to the `aws` CLI
(NOT aws-sdk-go) — the same anti-bloat reasoning that keeps SOPS on its binary
(DECISIONS): the SDK would pull a large cloud tree into the CGO_ENABLED=0 static
binary. Shelling also inherits the user's AWS config / SSO / IAM-role creds the
way git inherits SSH, so no AWS credential passes through devstack.

- `aws-sm`: `get-secret-value` per distinct secret id (batched across refs that
  share an id); a keyless ref takes the raw SecretString, a `#dot.key` ref parses
  it as JSON and walks the path.
- `aws-ssm`: ONE batched `get-parameters --with-decryption` for all referenced
  names; `--names` kept last (variadic) so `--region` isn't swallowed; any
  InvalidParameters → a clear error; optional `#key` walks a JSON-valued param.
- `--region` from cfg.Region/Opts["region"]; empty lets the CLI resolve it.
- Registered in RegisterBuiltins alongside sops; config passes the kind through
  (no allowlist), so it's declarable in workspace.yaml exactly like sops.

Stdlib-only (no new deps). Unit-tested with a fake `aws` runner: SM keyless +
JSON-key, per-secret batching (one call), SSM batched + region passthrough,
invalid-parameter error, missing-CLI error, builtin registration.

Account-gated (decision #3): logic + fakes here; real creds/localstack are a
flagged human/integration step, not run in the nightly.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gustavobertoi added a commit that referenced this pull request Jun 29, 2026
…) — M4 secrets COMPLETE (#67)

Adds the `infisical` provider kind, mirroring S2/S3: shells to the `infisical`
CLI (not the Go SDK — anti-bloat, DECISIONS) and inherits the user's auth
(`infisical login` / INFISICAL_TOKEN), so no credential passes through devstack.
The environment is exported ONCE (`infisical export --format=json`, batched) with
optional --projectId/--env/--path, then each ref's key is extracted; a #key walks
a JSON-valued secret.

Export-format drift across Infisical versions is handled by tolerant parsing:
accepts both a flat {KEY:VALUE} object and a list of {key/secretKey,
value/secretValue} records. Fake-runner tested for both shapes + JSON #key +
missing-secret + missing-CLI + builtin registration.

Account-gated (decision #3): logic + fakes here; the exact CLI version/contract is
a flagged owner-verify step, not run in the nightly. Stdlib-only (no new deps).

This completes M4: S1 (core) · S2 (SOPS+age) · S3 (AWS SM/SSM) · S4 (Infisical) ·
S5 (keyring login) · S6 (post-render inject + leak test).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant