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:
@@ -136,6 +136,67 @@ func TestRotationEmitsValidReleaseWithAnchorSHA(t *testing.T) {
|
||||
|
||||
func mustJSON(v any) []byte { b, _ := json.Marshal(v); return b }
|
||||
|
||||
// TestStartBlocksOnInvalidPickup guards AUDIT.md's B6/Phase 4 item 4:
|
||||
// Coordinator.Start must run §6.2 pickup validation against the real
|
||||
// worktree before bootstrapping a successor onto a handoff_ref, and refuse
|
||||
// (TaskBlocked) rather than bootstrap on a mismatched anchor.
|
||||
func TestStartBlocksOnInvalidPickup(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
run(t, repo, "init")
|
||||
run(t, repo, "config", "user.email", "t@t")
|
||||
run(t, repo, "config", "user.name", "t")
|
||||
run(t, repo, "commit", "--allow-empty", "-m", "init")
|
||||
|
||||
s, err := store.Open(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.Append(domain.Event{ID: domain.NewID(), Type: "TaskCreated", TaskID: "t1", Surface: string(authz.System), Payload: mustJSON(map[string]any{
|
||||
"source": "jsonl", "external_id": "1", "project": "p",
|
||||
})}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
task := s.Tasks()[0]
|
||||
|
||||
// A handoff whose anchor doesn't match anything in this fresh repo.
|
||||
badHandoff := map[string]any{
|
||||
"meta": map[string]any{"id": "h1", "reason": "manual", "rotation_index": 0},
|
||||
"anchor": map[string]any{"git_sha": strings0(40, 'a'), "branch": "orchestra/t1"},
|
||||
"goal": "g", "done_when": []string{"x"}, "action": "prompt", "command": "go test ./...",
|
||||
}
|
||||
ref, err := s.PutArtifact(mustJSON(badHandoff))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
a := &fakeAdapter{occupancy: 0}
|
||||
wt := orchestrator.GitWorktrees{Root: t.TempDir(), Repo: repo}
|
||||
c := &orchestrator.Coordinator{Store: s, Worktrees: wt, Adapters: adapters{a}, StatePath: t.TempDir() + "/sessions.json"}
|
||||
|
||||
leaseEvt, err := s.Lease(task.ID, "h1", time.Minute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, _ := json.Marshal(map[string]string{"harness_id": "h1", "handoff_ref": ref})
|
||||
leaseEvt.Payload = b
|
||||
if err := c.Start(context.Background(), leaseEvt); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, ok := s.Task(task.ID)
|
||||
if !ok || got.State != domain.StateBlocked {
|
||||
t.Fatalf("expected TaskBlocked on invalid pickup, got state=%v ok=%v", got.State, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func strings0(n int, c byte) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = c
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// keyedHarnessAdapter reports Session.Harness as the harness kind ("claude"),
|
||||
// distinct from the herdr instance id ("homesrv-claude") under which it is
|
||||
// registered in AdapterFactory.Herdrs — reproducing production's real key
|
||||
|
||||
Reference in New Issue
Block a user