HOMELAB-1674: feat(harness): HarnessDriver seam + claudecode renderer/driver (zero dispatcher changes) #1

Merged
aaron merged 2 commits from plane/HOMELAB-1674-harness into main 2026-09-30 22:52:01 +00:00
Owner

What this is

The code half of the LD1.6 seam proof (design §4.3 HarnessDriver, §4.4 renderer table, §5.1's onboarding test; scout homelab-1674/scout-findings-2026-09-30.md). It is the additive third of the three-artifact seam statement — one driver impl, one renderer, one image variant (image = Track 2, infra-core/agent-sandbox/claude-code-image/):

# artifact where status
a one HarnessDriver impl internal/harness/claudecode/driver.go this PR
b one renderer internal/harness/claudecode/render.go this PR
c one image variant infra-core/agent-sandbox/claude-code-image/ Track 2 (not this PR)

Nothing calls this yet. The dispatcher that selects a driver is LD2.1 (design §5.3 step 1/5; ticket HOMELAB-1674's own Verify line: "the full claim path lands in Epic 2"). There are zero edits to the dispatcher, bus, meters or cockpit — they do not exist in this repo yet — and zero edits to any existing file: the diff is additive (go.mod + internal/harness/**).

What follows the design exactly

  • §4.3 method set, verbatim: Kind(), Prepare(pkg ContextPackage, sb SandboxEnvSpec, fs SandboxEnvFS) error, SpawnCmd(sb) (argv, env), ParseEvents(r io.Reader, emit func(SandboxEnvEvent)) error, Status(s SandboxEnvState) DerivedState, Artifacts(sb) ([]Artifact, error), Capabilities() Caps — with var _ harness.HarnessDriver = (*Driver)(nil) as a compile-time proof.
  • §4.2 types: SandboxEnvSpec{RoleProfile, HarnessKind, ContextPackageRef, RepoSliceSpec, SandboxEnvLimits, EvidenceContract}.
  • §4.4 Claude Code column: S1 → --append-system-prompt-file preamble; S2 → CLAUDE.md + first user message; S3 → verbatim first user message; S4 → CLAUDE.md rules + pinned files + hash-check step; S5 → CLAUDE.md lore block (by address); S6 → harness-agnostic sparse-checkout list; S7 → output/evidence contract + out/ convention; S8 → settings.json env block.
  • contract §9: 8 hash-pinned sections, Validate() recomputes every pin and the content address and returns *StaleError{Section, Pinned, Recomputed} naming the section that moved (fail-loud on stale). A test pins the count at 8 (a ninth section is a contract amendment, not a refactor).
  • §5.1 boundary: the neutral vocabulary (SandboxEnvEvent, Caps, DerivedState, ContextPackage) carries no harness name; only claudecode/* does.

Adaptations and provisional choices (please review these)

  1. Repo/path deviation (needs a ruling). The dispatch said /home/aaron/workspace/lab-director as an "active app checkout, remote aaron/lab-director". That path is a clone of the dormant LDv1 app (Homelab/lab-director, private, live, last commit 2026-05-02) whose origin URL still reads aaron/lab-director — a stale-redirect relic now resolving to the contract repo (already documented as a hazard in .rpiv/artifacts/landing/ld0-1-repo-setup.md §6). The ticket and the scout both name aaron/lab-director as the home for internal/harness/, so this branch is based on aaron/lab-director@main (f2e16ae) and the PR targets main. If the intent was instead "code goes into the LDv1 app under backend/internal/harness", say so and this lands as a 5-minute re-target — the package is importable as-is.
  2. New Go module at the repo root. aaron/lab-director had no go.mod; this PR adds module forgejo.aaron.reynoza.org/aaron/lab-director, go 1.24, stdlib only (matches the convention visible in the app's forgejo.aaron.reynoza.org/aaron/lab-director/backend). LD2.1's dispatcher extends this module.
  3. Seal() (compiler-side pinning) lives here so the compiler, fixtures and tests share one hashing implementation; LD2.1 owns the compiler itself. Validate() is the consumer-side check.
  4. Content address carries no sandbox-env identity. The design's example id is ctx-homelab-1654-builder-1-a91f3c, but §2.4 requires cross-worker memoization ("a second worker on the same ticket reuses the package unless inputs moved") — a per-sandbox-env address can never hit that cache. Address = ctx-<sha256 of sorted section hashes>, a pure function of section content (provenance like compiled_at is not rendered and does not move it).
  5. SpawnCmd cannot carry the brief (the §4.3 signature takes no package), so argv emits PromptPlaceholder = "{{first_user_message}}" and SubstitutePrompt(argv, brief) is the documented one-liner sandbox-init calls; the verbatim brief is rendered at .claude/context/S3-brief.md.
  6. --verbose is mandatory for -p --output-format stream-json (verified: Error: When using --print, --output-format=stream-json requires --verbose) — the driver adds it, and only for stream-json.
  7. --bare fixes the context channel: claude --help says --bare skips "…CLAUDE.md auto-discovery" and names the explicit channels, so the spawn passes --append-system-prompt-file <S1> and --add-dir <state root> (the documented CLAUDE.md mechanism) — S1 in CLAUDE.md would otherwise never load.
  8. Renderer fail-loud rules (each design-sourced, tested): refuse a stale/crippled package; refuse a loopback/localhost OmniRoute endpoint (design §2.2 S8 + risk #10: never the mgmt-VM hot standby); refuse S8 without api_key_helper (the token arrives as a file — provisioning contracts §1); refuse any credential-looking env key/value (contract §11).
  9. capabilities are deliberately less rosy than §4.1's table for claude 2.1.284: Steer=false (design §4.3: steer is never harness-native), ACP=false (no ACP surface), Hooks=true with a note that --bare skips settings/plugin hooks — i.e. advertised but inactive in our spawn mode.
  10. SandboxEnvEvent/DriverEvent/Caps/DerivedState are marked LD1: provisional in code (// LD1: provisional — …), with the owner named: meters owns the canonical event schema and the compiler owns the package shape (design §5.1).

Test evidence

$ go test ./internal/harness/...
ok  	forgejo.aaron.reynoza.org/aaron/lab-director/internal/harness	0.005s
ok  	forgejo.aaron.reynoza.org/aaron/lab-director/internal/harness/claudecode	0.012s
$ go test -v ./internal/harness/... | grep -c PASS   # 42 passing cases
42
$ go vet ./... && go build ./...    # clean

Coverage of the brief's requirements: golden tree from a fixture package (testdata/golden/**, 9 files); a stale-hash package → Validate error naming intent (testdata/stale-package.json); purity (render twice byte-identical; a recompiled package at a different time renders identically and the tree contains no provenance); Prepare byte-equality through MemFS; spawn argv pinned as a golden list; ParseEvents over verbatim claude 2.1.284 stdout captured on the mgmt VM (testdata/stream-json-2.1.284.jsonl) plus a tool_use/result fixture; loud failure on dialect drift, quiet forward-compatibility for unknown event types; derived-state table; artifact hashing; capability honesty assertions.

Fixtures are regenerated with go test ./internal/harness/claudecode -update — never hand-edited.

Open items (explicitly not done here)

  • R2/§4.1 mechanism: execute_command reaches only the runtime sidecar, so the proof run (Track 4) needs the single-container combine — an image decision, not a code one.
  • R1: Claude Code against OmniRoute's Anthropic /v1/messages remains unverified (Track 1); this PR deliberately does not claim it works.
  • Hooks-based evidence checks (a Kiro/Claude capability the design's table advertises) are unusable under --bare; if we want them, the spawn mode is the thing to revisit (recorded in Caps.Notes).
  • No mcp.json is rendered (the MCP config is the dispatcher/image's, design §5.1 sandbox-init) — SpawnCmd references --mcp-config only when configured.
  • CI: aaron/lab-director has no workflow yet, so there is no CI gate on this PR; the test commands above are the evidence. A tiny go-test workflow is a candidate follow-up (out of scope here).
## What this is The **code half of the LD1.6 seam proof** (design §4.3 `HarnessDriver`, §4.4 renderer table, §5.1's onboarding test; scout `homelab-1674/scout-findings-2026-09-30.md`). It is the **additive third** of the three-artifact seam statement — one driver impl, one renderer, one image variant (image = Track 2, `infra-core/agent-sandbox/claude-code-image/`): | # | artifact | where | status | |---|---|---|---| | a | one `HarnessDriver` impl | `internal/harness/claudecode/driver.go` | **this PR** | | b | one renderer | `internal/harness/claudecode/render.go` | **this PR** | | c | one image variant | `infra-core/agent-sandbox/claude-code-image/` | Track 2 (not this PR) | **Nothing calls this yet.** The dispatcher that selects a driver is LD2.1 (design §5.3 step 1/5; ticket HOMELAB-1674's own Verify line: "the full claim path lands in Epic 2"). There are zero edits to the dispatcher, bus, meters or cockpit — they do not exist in this repo yet — and zero edits to any existing file: the diff is additive (`go.mod` + `internal/harness/**`). ## What follows the design exactly * **§4.3 method set, verbatim**: `Kind()`, `Prepare(pkg ContextPackage, sb SandboxEnvSpec, fs SandboxEnvFS) error`, `SpawnCmd(sb) (argv, env)`, `ParseEvents(r io.Reader, emit func(SandboxEnvEvent)) error`, `Status(s SandboxEnvState) DerivedState`, `Artifacts(sb) ([]Artifact, error)`, `Capabilities() Caps` — with `var _ harness.HarnessDriver = (*Driver)(nil)` as a compile-time proof. * **§4.2 types**: `SandboxEnvSpec{RoleProfile, HarnessKind, ContextPackageRef, RepoSliceSpec, SandboxEnvLimits, EvidenceContract}`. * **§4.4 Claude Code column**: S1 → `--append-system-prompt-file` preamble; S2 → `CLAUDE.md` + first user message; S3 → verbatim first user message; S4 → `CLAUDE.md` rules + pinned files + hash-check step; S5 → `CLAUDE.md` lore block (by address); S6 → harness-agnostic sparse-checkout list; S7 → output/evidence contract + `out/` convention; S8 → `settings.json` `env` block. * **contract §9**: 8 hash-pinned sections, `Validate()` recomputes every pin and the content address and returns `*StaleError{Section, Pinned, Recomputed}` naming the section that moved (fail-loud on stale). A test pins the count at 8 (a ninth section is a contract amendment, not a refactor). * **§5.1 boundary**: the neutral vocabulary (`SandboxEnvEvent`, `Caps`, `DerivedState`, `ContextPackage`) carries no harness name; only `claudecode/*` does. ## Adaptations and provisional choices (please review these) 1. **Repo/path deviation (needs a ruling).** The dispatch said `/home/aaron/workspace/lab-director` as an "active app checkout, remote aaron/lab-director". That path is a clone of the **dormant LDv1 app** (`Homelab/lab-director`, private, `live`, last commit 2026-05-02) whose `origin` URL still reads `aaron/lab-director` — a stale-redirect relic now resolving to the *contract* repo (already documented as a hazard in `.rpiv/artifacts/landing/ld0-1-repo-setup.md` §6). The ticket and the scout both name **`aaron/lab-director`** as the home for `internal/harness/`, so this branch is based on `aaron/lab-director@main` (`f2e16ae`) and the PR targets `main`. If the intent was instead "code goes into the LDv1 app under `backend/internal/harness`", say so and this lands as a 5-minute re-target — the package is importable as-is. 2. **New Go module at the repo root.** `aaron/lab-director` had no `go.mod`; this PR adds `module forgejo.aaron.reynoza.org/aaron/lab-director`, `go 1.24`, **stdlib only** (matches the convention visible in the app's `forgejo.aaron.reynoza.org/aaron/lab-director/backend`). LD2.1's dispatcher extends this module. 3. **`Seal()`** (compiler-side pinning) lives here so the compiler, fixtures and tests share one hashing implementation; LD2.1 owns the compiler itself. `Validate()` is the consumer-side check. 4. **Content address carries no sandbox-env identity.** The design's example id is `ctx-homelab-1654-builder-1-a91f3c`, but §2.4 requires cross-worker memoization ("a second worker on the same ticket reuses the package unless inputs moved") — a per-sandbox-env address can never hit that cache. Address = `ctx-<sha256 of sorted section hashes>`, a pure function of section content (provenance like `compiled_at` is not rendered and does not move it). 5. **`SpawnCmd` cannot carry the brief** (the §4.3 signature takes no package), so argv emits `PromptPlaceholder = "{{first_user_message}}"` and `SubstitutePrompt(argv, brief)` is the documented one-liner sandbox-init calls; the verbatim brief is rendered at `.claude/context/S3-brief.md`. 6. **`--verbose` is mandatory** for `-p --output-format stream-json` (verified: `Error: When using --print, --output-format=stream-json requires --verbose`) — the driver adds it, and only for stream-json. 7. **`--bare` fixes the context channel**: `claude --help` says `--bare` skips "…CLAUDE.md auto-discovery" and names the explicit channels, so the spawn passes `--append-system-prompt-file <S1>` **and** `--add-dir <state root>` (the documented CLAUDE.md mechanism) — S1 in `CLAUDE.md` would otherwise never load. 8. **Renderer fail-loud rules** (each design-sourced, tested): refuse a stale/crippled package; refuse a loopback/`localhost` OmniRoute endpoint (design §2.2 S8 + risk #10: never the mgmt-VM hot standby); refuse S8 without `api_key_helper` (the token arrives as a file — provisioning contracts §1); refuse any credential-looking `env` key/value (contract §11). 9. **capabilities are deliberately less rosy than §4.1's table** for claude 2.1.284: `Steer=false` (design §4.3: steer is never harness-native), `ACP=false` (no ACP surface), `Hooks=true` **with a note that `--bare` skips settings/plugin hooks** — i.e. advertised but inactive in our spawn mode. 10. **`SandboxEnvEvent`/`DriverEvent`/`Caps`/`DerivedState` are marked `LD1: provisional`** in code (`// LD1: provisional — …`), with the owner named: meters owns the canonical event schema and the compiler owns the package shape (design §5.1). ## Test evidence ``` $ go test ./internal/harness/... ok forgejo.aaron.reynoza.org/aaron/lab-director/internal/harness 0.005s ok forgejo.aaron.reynoza.org/aaron/lab-director/internal/harness/claudecode 0.012s $ go test -v ./internal/harness/... | grep -c PASS # 42 passing cases 42 $ go vet ./... && go build ./... # clean ``` Coverage of the brief's requirements: golden tree from a fixture package (`testdata/golden/**`, 9 files); a stale-hash package → `Validate` error **naming `intent`** (`testdata/stale-package.json`); purity (render twice byte-identical; a recompiled package at a different time renders identically and the tree contains no provenance); `Prepare` byte-equality through `MemFS`; spawn argv pinned as a golden list; `ParseEvents` over **verbatim `claude 2.1.284` stdout** captured on the mgmt VM (`testdata/stream-json-2.1.284.jsonl`) plus a tool_use/result fixture; loud failure on dialect drift, quiet forward-compatibility for unknown event types; derived-state table; artifact hashing; capability honesty assertions. Fixtures are regenerated with `go test ./internal/harness/claudecode -update` — never hand-edited. ## Open items (explicitly not done here) * **R2/§4.1 mechanism**: `execute_command` reaches only the runtime sidecar, so the *proof run* (Track 4) needs the single-container combine — an image decision, not a code one. * **R1**: Claude Code against OmniRoute's Anthropic `/v1/messages` remains unverified (Track 1); this PR deliberately does not claim it works. * Hooks-based evidence checks (a Kiro/Claude capability the design's table advertises) are unusable under `--bare`; if we want them, the spawn mode is the thing to revisit (recorded in `Caps.Notes`). * No `mcp.json` is rendered (the MCP config is the dispatcher/image's, design §5.1 sandbox-init) — `SpawnCmd` references `--mcp-config` only when configured. * CI: `aaron/lab-director` has no workflow yet, so there is no CI gate on this PR; the test commands above are the evidence. A tiny go-test workflow is a candidate follow-up (out of scope here).
Design §4.3/§4.4/§5.1's seam, landed as additive code only:

- internal/harness: the HarnessDriver interface (exact §4.3 method set) plus the
  harness-neutral vocabulary it needs (HarnessKind, RoleProfile, SandboxEnvSpec,
  EvidenceContract, SandboxEnvFS/DirFS/MemFS, SandboxEnvEvent/DriverEvent,
  DerivedState/SandboxEnvState, Artifact, Caps).
- internal/harness/context.go: versioned ContextPackage carrying contract §9's
  eight hash-pinned sections, with Seal()/Validate() implementing fail-loud on
  stale (a *StaleError names the section that moved).
- internal/harness/claudecode: the pure renderer (S1 preamble, S2/S4/S5 CLAUDE.md
  + contract files, S3 verbatim first user message, S6 harness-agnostic
  slice.yaml, S7 output/out contract, S8 settings.json env block) and the
  HarnessDriver impl (spawn argv verified against claude 2.1.284, stream-json
  parsing, derived state, evidence artifacts, honest capabilities).
- go.mod: module forgejo.aaron.reynoza.org/aaron/lab-director (stdlib only).

Nothing imports the package yet: the dispatcher that selects a driver is LD2.1.
Tests: go test ./internal/harness/... (golden tree, purity, stale-package
refusal, real 2.1.284 stdout dialect fixtures).
Author
Owner

Landing review — LD1.6 renderer + driver (HOMELAB-1674, 2180fbc)

Reviewer: fresh independent context (pi -p --no-session, reviewer ≠ author) + landing worker mechanical checks. Tests run by the reviewer, not taken from the PR body: go test ./... -count=1 ok; go vet ./... clean; 42 cases; coverage 79.0% / 91.0%. Diff --name-status = all added, 22 files +4410, no edits to existing files. Interface fidelity to design §4.3 = verbatim method set + var _ harness.HarnessDriver = (*Driver)(nil). Golden fixtures are byte-compared bidirectionally against the rendered tree; the stale fixture fails with *StaleError{Section:"intent"}; purity asserted across renders and recompiles. Spawn argv pinned; --verbose only for stream-json. Review quality: high.

Verdict: FIX-FIRST (not merged). Three confirmed defects in the fail-loud guards, all in render.go/context.go, all cheap. Each was reproduced by probe (module copy under /tmp, worktree left clean).

F1 — Validate trusts the package's own required flags (context.go Validate, vs Seal/requiredByDefault)

Probe: take the valid fixture, set section_pins.brief.required=false, empty the S3 payload and its pin, re-derive package_id via ContentAddress() → Validate returns nil and Render emits a 9-file tree whose brief is "# Assignment — HOMELAB-1674 (builder-core) …## Goal\n\n\n\n". The presence gate therefore lives only in Seal (producer side); the consumer-side check the dispatcher will call accepts a context-crippled package. Fix (3 lines, cannot reject a legitimate package): re-derive requiredness from requiredByDefault in Validate instead of reading Pin.Required.

F2 — guard/renderer precedence mismatch, loopback endpoint reachable (render.go checkEnvironment vs renderSettings)

checkEnvironment validates Endpoints["omniroute"] first; renderSettings renders Env["ANTHROPIC_BASE_URL"] if present. Probe: in-cluster endpoint + env.ANTHROPIC_BASE_URL=http://127.0.0.1:20128 → guard passes, settings.json renders the mgmt-VM hot standby URL (design §2.2 S8 / risk #10 — the rule the guard exists for). Fix: validate the value that is actually rendered.

F3 — de-risk rules unenforced (render.go checkEnvironment; driver.go SpawnCmd)

/v1-suffixed base URL renders verbatim (→ /v1/v1/messages); de-risk §7/Recommended config says no /v1 suffix. An omniroute/…-prefixed --model passes through unvalidated (de-risk §6: that form 401s with a misleading invalid_api_key). Both are dispatch-time traps the guard function is the right place to refuse.

Non-blocking notes

  • identity.model = omniroute/deepseek-flash in fixture-package.json while --model/ANTHROPIC_MODEL = claude-opus-5-5; nothing cross-checks them (fixture-only, but it illustrates F3's trap).
  • staleness.ttl / hard_invalidators are parsed but never enforced (no ValidateAt); fine if LD2.1 owns TTL enforcement — worth a comment.
  • --add-dir re-enabling CLAUDE.md ingestion under --bare is doc-derived (claude --help), not yet run-proven; the PR already lists the end-to-end proof as Track 1/4, so this is a note, not a defect.
  • Repo placement (internal/harness/** + new go.mod in the contract repo) accepted: the alternative checkout is the dormant LDv1 clone; no CI workflow exists to break. Re-target to the app repo remains a 5-minute option for LD2.1.
## Landing review — LD1.6 renderer + driver (HOMELAB-1674, `2180fbc`) **Reviewer:** fresh independent context (`pi -p --no-session`, reviewer ≠ author) + landing worker mechanical checks. **Tests run by the reviewer, not taken from the PR body:** `go test ./... -count=1` ok; `go vet ./...` clean; 42 cases; coverage 79.0% / 91.0%. Diff `--name-status` = **all added**, 22 files +4410, no edits to existing files. Interface fidelity to design §4.3 = verbatim method set + `var _ harness.HarnessDriver = (*Driver)(nil)`. Golden fixtures are byte-compared bidirectionally against the rendered tree; the stale fixture fails with `*StaleError{Section:"intent"}`; purity asserted across renders and recompiles. Spawn argv pinned; `--verbose` only for `stream-json`. Review quality: high. **Verdict: FIX-FIRST (not merged).** Three confirmed defects in the fail-loud guards, all in `render.go`/`context.go`, all cheap. Each was reproduced by probe (module copy under `/tmp`, worktree left clean). ### F1 — `Validate` trusts the package's own `required` flags (`context.go` `Validate`, vs `Seal`/`requiredByDefault`) Probe: take the valid fixture, set `section_pins.brief.required=false`, empty the S3 payload and its pin, re-derive `package_id` via `ContentAddress()` → `Validate` returns **nil** and `Render` emits a 9-file tree whose brief is `"# Assignment — HOMELAB-1674 (builder-core) …## Goal\n\n\n\n"`. The presence gate therefore lives only in `Seal` (producer side); the consumer-side check the dispatcher will call accepts a context-crippled package. Fix (3 lines, cannot reject a legitimate package): re-derive requiredness from `requiredByDefault` in `Validate` instead of reading `Pin.Required`. ### F2 — guard/renderer precedence mismatch, loopback endpoint reachable (`render.go` `checkEnvironment` vs `renderSettings`) `checkEnvironment` validates `Endpoints["omniroute"]` first; `renderSettings` renders `Env["ANTHROPIC_BASE_URL"]` if present. Probe: in-cluster endpoint + `env.ANTHROPIC_BASE_URL=http://127.0.0.1:20128` → guard passes, `settings.json` renders **the mgmt-VM hot standby URL** (design §2.2 S8 / risk #10 — the rule the guard exists for). Fix: validate the value that is actually rendered. ### F3 — de-risk rules unenforced (`render.go` `checkEnvironment`; `driver.go` `SpawnCmd`) `/v1`-suffixed base URL renders verbatim (→ `/v1/v1/messages`); de-risk §7/Recommended config says **no `/v1` suffix**. An `omniroute/…`-prefixed `--model` passes through unvalidated (de-risk §6: that form 401s with a misleading `invalid_api_key`). Both are dispatch-time traps the guard function is the right place to refuse. ### Non-blocking notes - `identity.model` = `omniroute/deepseek-flash` in `fixture-package.json` while `--model`/`ANTHROPIC_MODEL` = `claude-opus-5-5`; nothing cross-checks them (fixture-only, but it illustrates F3's trap). - `staleness.ttl` / `hard_invalidators` are parsed but never enforced (no `ValidateAt`); fine if LD2.1 owns TTL enforcement — worth a comment. - `--add-dir` re-enabling CLAUDE.md ingestion under `--bare` is doc-derived (`claude --help`), not yet run-proven; the PR already lists the end-to-end proof as Track 1/4, so this is a note, not a defect. - Repo placement (`internal/harness/**` + new `go.mod` in the contract repo) accepted: the alternative checkout is the dormant LDv1 clone; no CI workflow exists to break. Re-target to the app repo remains a 5-minute option for LD2.1.
Landing review of PR #1 reproduced three guard defects; each now fails loud
with a regression probe.

F1 — Validate() trusted the package's own `required` flags, so a demoted S3
rendered a tree with an empty Goal. Presence is now judged against
requiredByDefault ∪ the package's pins: a pin may add requiredness, never
remove it (context.go).

F2 — checkEnvironment validated endpoints.omniroute while renderSettings
renders env.ANTHROPIC_BASE_URL, so an in-cluster endpoint admitted a package
whose settings.json pointed at the mgmt-VM hot standby. The guard now
validates the value that is actually rendered (blank override = absent), and
when both are declared they must agree on the destination. Rule: the rendered
base URL must be cluster-reachable from the sandbox — `*.svc.cluster.local`
(contract §1 shape), never loopback/RFC1918/the mgmt addresses; any other
host needs an explicit entry in endpointHostAllowlist, which ships empty.

F3 — the two de-risk traps (base URL with a trailing /v1 → /v1/v1/messages;
`omniroute/`-prefixed wire model id → 401) are now refused with a message
naming the correct form. The id is checked everywhere it can enter: S8
ANTHROPIC_*MODEL, S1 identity.model, Config.Model (--model) and
Config.ExtraEnv. Fixture identity.model corrected to the bare served id.

Evidence: pre-fix probes (HEAD 2180fbc, 42 cases) returned Validate()=nil /
Render()=nil for all three; post-fix they name the defect. go test ./...
-count=1 -race green: 53 cases (39 tests + 14 subtests), go vet clean,
coverage 79.1% / 90.6%. Repo has no CI workflow: tests are the gate.
Author
Owner

Fix worker — all three blockers addressed (5d04c02, same branch, not merged)

Pre-fix replication used a detached worktree at 2180fbc (/tmp/prefix-1674, removed after use) plus the new probes; both runs are reproducible with the probes in the tree.

F1 — Validate required-set (internal/harness/context.go). Presence is now judged against requiredByDefault ∪ Pin.Required (a pin may add requiredness, never remove it).

  • before: Validate(): <nil>, Render(): err=<nil> files=9 (empty Goal)
  • after: harness: required context package section "brief" is empty (Validate and Render)
  • probes: TestValidateEnforcesTheDefaultRequiredSet, TestRenderRefusesDemotedRequiredSection

F2 — endpoint/env agreement (checkEnvironment). The guard now validates the value that is actually rendered (blank override = absent, matching renderSettings), and when both are declared they must agree on the destination. Rule: the rendered base URL must be cluster-reachable from the sandbox — *.svc.cluster.local (contract §1 shape); loopback/RFC1918/mgmt addresses are refused; any other host needs an explicit entry in endpointHostAllowlist, which ships empty. Same rule applied to Config.ExtraEnv.

  • before: Render(): <nil> (hot standby in settings.json); after: S8 ANTHROPIC_BASE_URL "http://127.0.0.1:20128" is a loopback address (unreachable from inside the cluster); …use the in-cluster Service DNS name (design §2.2 S8, risk #10)
  • probes: TestRenderRefusesUnreachableModelEndpoint (6 cases), TestModelEndpointRule

F3 — de-risk traps. Trailing /v1 and omniroute/-prefixed ids are refused with a message naming the correct form, everywhere the id can enter: S8 ANTHROPIC_*MODEL, S1 identity.model, Config.Model, Config.ExtraEnv. Fixture identity.model corrected to the bare served id (goldens regenerated via -update; diff = model id + package id only).

  • before: Render(): <nil> (verbatim /v1), New(): driver=<built> err=<nil>; after: messages point at ANTHROPIC_BASE_URL=…:20128 (no /v1 suffix) and the bare served id.
  • probes: TestRenderRefusesDeriskTraps, TestNewRejectsBadConfig (prefixed model, loopback ExtraEnv base url, prefixed ExtraEnv model)

Gate: go test ./... -count=1 -race green — 42 → 53 cases (39 tests + 14 subtests), go vet ./... clean, gofmt clean, coverage 79.1% / 90.6%. This repo has no CI workflow (no .forgejo/.github, no workflow files): tests are the gate.

Open (not in scope): staleness.ttl/hard_invalidators still unenforced (LD2.1 owns TTL); fixture identity.model vs Config.Model still not cross-checked at spawn.

## Fix worker — all three blockers addressed (`5d04c02`, same branch, not merged) Pre-fix replication used a detached worktree at `2180fbc` (`/tmp/prefix-1674`, removed after use) plus the new probes; both runs are reproducible with the probes in the tree. **F1 — `Validate` required-set (`internal/harness/context.go`).** Presence is now judged against `requiredByDefault ∪ Pin.Required` (a pin may add requiredness, never remove it). - before: `Validate(): <nil>`, `Render(): err=<nil> files=9` (empty Goal) - after: `harness: required context package section "brief" is empty` (Validate and Render) - probes: `TestValidateEnforcesTheDefaultRequiredSet`, `TestRenderRefusesDemotedRequiredSection` **F2 — endpoint/env agreement (`checkEnvironment`).** The guard now validates the value that is actually rendered (blank override = absent, matching `renderSettings`), and when both are declared they must agree on the destination. Rule: the rendered base URL must be cluster-reachable from the sandbox — `*.svc.cluster.local` (contract §1 shape); loopback/RFC1918/mgmt addresses are refused; any other host needs an explicit entry in `endpointHostAllowlist`, which ships empty. Same rule applied to `Config.ExtraEnv`. - before: `Render(): <nil>` (hot standby in `settings.json`); after: `S8 ANTHROPIC_BASE_URL "http://127.0.0.1:20128" is a loopback address (unreachable from inside the cluster); …use the in-cluster Service DNS name (design §2.2 S8, risk #10)` - probes: `TestRenderRefusesUnreachableModelEndpoint` (6 cases), `TestModelEndpointRule` **F3 — de-risk traps.** Trailing `/v1` and `omniroute/`-prefixed ids are refused with a message naming the correct form, everywhere the id can enter: S8 `ANTHROPIC_*MODEL`, S1 `identity.model`, `Config.Model`, `Config.ExtraEnv`. Fixture `identity.model` corrected to the bare served id (goldens regenerated via `-update`; diff = model id + package id only). - before: `Render(): <nil>` (verbatim `/v1`), `New(): driver=<built> err=<nil>`; after: messages point at `ANTHROPIC_BASE_URL=…:20128` (no `/v1` suffix) and the bare served id. - probes: `TestRenderRefusesDeriskTraps`, `TestNewRejectsBadConfig` (`prefixed model`, `loopback ExtraEnv base url`, `prefixed ExtraEnv model`) **Gate:** `go test ./... -count=1 -race` green — 42 → 53 cases (39 tests + 14 subtests), `go vet ./...` clean, `gofmt` clean, coverage 79.1% / 90.6%. This repo has **no CI workflow** (no `.forgejo`/`.github`, no workflow files): tests are the gate. Open (not in scope): `staleness.ttl`/`hard_invalidators` still unenforced (LD2.1 owns TTL); `fixture identity.model` vs `Config.Model` still not cross-checked at spawn.
aaron merged commit e42ccf3682 into main 2026-09-30 22:52:01 +00:00
Author
Owner

Landing re-review — fix delta (2180fbc..5d04c02) — CLEAN, MERGED

Merge: e42ccf3 on main (contains 5d04c02; head confirmed = origin/plane/HOMELAB-1674-harness). Diff is confined to internal/harness/**, additive, nothing calls it yet → auto-merge class. No CI exists in this repo, so the local gate is the gate.

Independent reproduction (module copied to /tmp, each SHA checked out separately; worktree left clean):

  • F1 demoted+emptied S3 (required:false, pin hash omitted, package_id re-derived): pre-fix Validate()=<nil> and Render emitted the empty-Goal tree → post-fix both refuse with *MissingSectionError{Section:"brief"}. Note: omitting the pin hash is the escape hatch; keeping a matching hash was already caught pre-fix by the "empty but pinned" branch, so that spelling is not evidence of the fix.
  • F2 loopback/localhost/10.10.10.5/192.168.1.10 base URL vs. in-cluster endpoints.omniroute: pre-fix 4/5 probes passed with the hot standby rendered into settings.json → post-fix all refuse naming the value and the remedy. Bonus: a blank override rendered ANTHROPIC_BASE_URL:"" pre-fix; now falls back to the declared endpoint.
  • F3 /v1, /v1/, /v1/messages and omniroute/-prefixed ids (S8 ANTHROPIC_*MODEL, S1 identity.model, Config.Model, Config.ExtraEnv): pre-fix all allowed → post-fix all refuse with the correct form named.

Legit shapes unaffected: fixture as-is, explicit in-cluster base URL, bare model id, New() with legal config — all still pass (I re-ran these as controls; the new tests assert the same).

Gate (re-run by me, not taken from the PR): go test ./... -count=1 and -race green (39 tests + 14 subtests = 53 cases, reconciling the earlier "42"); go vet ./... clean; gofmt -l clean; coverage 79.1% / 90.6%. Goldens byte-compare; the only golden deltas are the corrected identity.model and the content address it propagates. Merged tree re-tested green from a fresh extract of main.

Non-blocking notes for the operator:

  1. env.ANTHROPIC_BASE_URL must now be a *.svc.cluster.local host — shorter cluster spellings (omniroute:20128) and bare ClusterIPs are refused, and public https://omniroute.aaron.reynoza.org moved from allowed to refused. Per design §2.2 S8/contract §1 this is the intended tightening, with endpointHostAllowlist (ships empty) as the reviewable hatch — flagged so LD2.1's compiler builds the FQDN.
  2. Two cosmetic message warts: the RFC1918 branch repeats "the mgmt-VM hot standby" twice; the /v1/messages case reports the doubled path as …/v1/messages/v1/messages. Both accurate, neither misleading.
  3. Still open as stated: staleness.ttl/hard_invalidators unenforced (LD2.1), fixture identity.model vs Config.Model not cross-checked at spawn, --add-dir CLAUDE.md ingestion doc-derived not run-proven.
## Landing re-review — fix delta (`2180fbc..5d04c02`) — **CLEAN, MERGED** **Merge:** `e42ccf3` on `main` (contains `5d04c02`; head confirmed `= origin/plane/HOMELAB-1674-harness`). Diff is confined to `internal/harness/**`, additive, nothing calls it yet → auto-merge class. No CI exists in this repo, so the local gate is the gate. **Independent reproduction** (module copied to `/tmp`, each SHA checked out separately; worktree left clean): - **F1** demoted+emptied S3 (`required:false`, pin hash omitted, `package_id` re-derived): pre-fix `Validate()=<nil>` **and** `Render` emitted the empty-Goal tree → post-fix both refuse with `*MissingSectionError{Section:"brief"}`. Note: omitting the pin hash is the escape hatch; keeping a matching hash was already caught pre-fix by the "empty but pinned" branch, so that spelling is not evidence of the fix. - **F2** loopback/`localhost`/`10.10.10.5`/`192.168.1.10` base URL vs. in-cluster `endpoints.omniroute`: pre-fix 4/5 probes passed with the hot standby rendered into `settings.json` → post-fix all refuse naming the value and the remedy. Bonus: a blank override rendered `ANTHROPIC_BASE_URL:""` pre-fix; now falls back to the declared endpoint. - **F3** `/v1`, `/v1/`, `/v1/messages` and `omniroute/`-prefixed ids (S8 `ANTHROPIC_*MODEL`, S1 `identity.model`, `Config.Model`, `Config.ExtraEnv`): pre-fix all allowed → post-fix all refuse with the correct form named. **Legit shapes unaffected:** fixture as-is, explicit in-cluster base URL, bare model id, `New()` with legal config — all still pass (I re-ran these as controls; the new tests assert the same). **Gate (re-run by me, not taken from the PR):** `go test ./... -count=1` and `-race` green (39 tests + 14 subtests = 53 cases, reconciling the earlier "42"); `go vet ./...` clean; `gofmt -l` clean; coverage 79.1% / 90.6%. Goldens byte-compare; the only golden deltas are the corrected `identity.model` and the content address it propagates. Merged tree re-tested green from a fresh extract of `main`. **Non-blocking notes for the operator:** 1. `env.ANTHROPIC_BASE_URL` must now be a `*.svc.cluster.local` host — shorter cluster spellings (`omniroute:20128`) and bare ClusterIPs are refused, and public `https://omniroute.aaron.reynoza.org` moved from allowed to refused. Per design §2.2 S8/contract §1 this is the intended tightening, with `endpointHostAllowlist` (ships empty) as the reviewable hatch — flagged so LD2.1's compiler builds the FQDN. 2. Two cosmetic message warts: the RFC1918 branch repeats "the mgmt-VM hot standby" twice; the `/v1/messages` case reports the doubled path as `…/v1/messages/v1/messages`. Both accurate, neither misleading. 3. Still open as stated: `staleness.ttl`/`hard_invalidators` unenforced (LD2.1), `fixture identity.model` vs `Config.Model` not cross-checked at spawn, `--add-dir` CLAUDE.md ingestion doc-derived not run-proven.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aaron/lab-director!1
No description provided.