fix(orchestrator): wire TASK.md writing and §6.2 pickup validation (B6, partial)
Fixes AUDIT.md's B6: nothing wrote a TASK.md into a worktree, so continuity.ValidatePickup had no caller and no file to check. - continuity.RenderTaskFile/TaskFileHash: render and hash the immutable §6.2 TASK.md from a domain.Task. - GitWorktrees.Create writes and commits TASK.md into every freshly created worktree (must be committed, not dirty, for ScratchCommit's immutability check and for a stable hash). - Coordinator.Start now runs continuity.ValidatePickup (anchor SHA, dirty-file hashes, TASK.md hash) against the real worktree before bootstrapping a successor onto a handoff_ref, and blocks the task instead of bootstrapping on a validation failure. Still open from Phase 4: handoff production (agent writing the real handoff; Release still refuses per B5), ScratchCommit wiring before release, and the §6.2 bootstrap-prompt rewrite — see AUDIT.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1rkJ2hBMybnJctPbcy4tT
This commit is contained in:
@@ -558,13 +558,70 @@ handler test coverage of any kind (everything is inline in `main()`), so this
|
||||
follows the existing gap rather than introducing an isolated test harness for
|
||||
one handler.
|
||||
|
||||
**Fixed 2026-07-27 (later same day):** `CLIAdapter.Lease`'s bootstrap prompt now
|
||||
passes `time.Minute` instead of `wait=0`, matching `Bootstrap`'s inline-wait
|
||||
pattern — closes the race B5 named (send-into-a-half-rendered-prompt).
|
||||
`internal/herdr/adapter.go`. `go build`/`vet`/`test` all still pass; no
|
||||
existing test asserted the old `wait=0` value.
|
||||
|
||||
**Still open from B5** (not attempted this pass — larger, needs design, not
|
||||
just a method-name swap):
|
||||
- `agent.prompt` inline `wait` on `CLIAdapter.Lease` (still `wait=0`, per B5's
|
||||
note that only `Bootstrap` passes a real wait).
|
||||
- `Release`'s real implementation, which depends on Phase 4 (§6) handoff
|
||||
production existing at all.
|
||||
- The stuck live task (`06FT6CKD9Y98AZRX6X8K3QXFZG`) was deliberately **not**
|
||||
manipulated directly (no `pane.close`/`pane.release_agent` call against it)
|
||||
— killing or releasing a real running agent from an audit session without
|
||||
the user present is exactly the kind of action that warrants asking first.
|
||||
|
||||
## B6 — partial fix, 2026-07-27 (Phase 4 items 1 and 4)
|
||||
|
||||
Two of Phase 4's six items landed; the rest are unchanged (still open, listed
|
||||
below).
|
||||
|
||||
1. **TASK.md is now actually written.** `continuity.RenderTaskFile(t)`
|
||||
produces the §6.2 immutable spec content from `domain.Task`;
|
||||
`GitWorktrees.Create` (`internal/orchestrator/orchestrator.go`) writes it
|
||||
into every freshly created worktree and commits it immediately — it must
|
||||
be committed, not left dirty, both so `ScratchCommit`'s "TASK.md is
|
||||
immutable" check (which inspects `git status`) sees it as clean, and so
|
||||
its hash is stable across whatever the agent does afterward. A worktree
|
||||
that already has a `TASK.md` (recreation on restart) is left untouched.
|
||||
New `continuity.TaskFileHash(root)` reads it back and hashes it — this is
|
||||
what makes `w.TaskFileSHA`/`VerifyTaskFile`, previously dead because
|
||||
nothing ever set `TaskFileSHA`, actually reachable.
|
||||
2. **Pickup validation now gates bootstrap.** `Coordinator.Start`
|
||||
(`internal/orchestrator/orchestrator.go`) computes the new worktree's
|
||||
`TaskFileHash`, and — whenever the lease carries a `handoff_ref` (i.e.
|
||||
this is a rotation continuation, not a fresh lease) — loads the handoff
|
||||
from CAS and runs `continuity.ValidatePickup(worktree, handoff,
|
||||
taskFileSHA)` **before** calling `Adapter.Bootstrap`. A validation failure
|
||||
kills the just-started session and emits `TaskBlocked` instead of handing
|
||||
the successor an unverified anchor. This is exactly the gap B6 named:
|
||||
`ValidatePickup` had no caller outside its own tests.
|
||||
`TestStartBlocksOnInvalidPickup` (`rotation_test.go`) drives this against
|
||||
a handoff whose anchor SHA doesn't exist in the repo and asserts the task
|
||||
ends `Blocked`, never bootstrapped.
|
||||
`TestGitWorktreesCommitsTaskFile` (`worktrees_test.go`) asserts the
|
||||
written/committed content matches `RenderTaskFile` and survives
|
||||
recreation.
|
||||
|
||||
Caveat recorded in code: TASK.md hashing is best-effort — if a worktree came
|
||||
from the `WorktreeCreator` (herdr-hosted, potentially remote) path rather
|
||||
than `GitWorktrees`, `TaskFileHash` fails silently and pickup validation runs
|
||||
with an empty `taskFileSHA` (so it still checks anchor SHA and dirty-file
|
||||
hashes, just not TASK.md). No current adapter or test exercises
|
||||
`WorktreeCreator` with a real `handoff_ref`, so this is unverified, not
|
||||
proven safe — same cross-host caveat as the federation-fork section.
|
||||
|
||||
**Still open from B6/Phase 4** (unchanged, larger and needs live-agent
|
||||
cooperation):
|
||||
- Item 2: handoff *production* — the rotating agent writing the §6.1
|
||||
handoff and a stop-hook path uploading it via `POST /v1/artifacts` before
|
||||
`Coordinator.rotate` calls `Adapter.Release`. `Release` still just refuses
|
||||
(see B5 above) — there is nothing yet to validate-and-mint a ref from.
|
||||
- Item 3: `ScratchCommit` before release — not wired into `rotate` at all.
|
||||
- Item 5: `CLIAdapter.Bootstrap`'s prompt is still ad hoc prose, not the
|
||||
§6.2 ~200-token procedure (read handoff → validate-handoff → re-read
|
||||
TASK.md → proceed).
|
||||
- Item 6: `MarkdownChanges` (§6.3 adjacent-task notice) still uncalled from
|
||||
anything but its own test.
|
||||
|
||||
Reference in New Issue
Block a user