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:
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"orchestra/internal/domain"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -14,6 +15,41 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// RenderTaskFile produces the immutable §6.2 TASK.md content for a task.
|
||||
// Deterministic in every field that comes from the task itself, so the same
|
||||
// task always hashes to the same content.
|
||||
func RenderTaskFile(t domain.Task) []byte {
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "# Task %s\n\n", t.ID)
|
||||
if t.Title != "" {
|
||||
fmt.Fprintf(&b, "%s\n\n", t.Title)
|
||||
}
|
||||
fmt.Fprintf(&b, "- Project: %s\n", t.Project)
|
||||
fmt.Fprintf(&b, "- Source: %s/%s\n", t.Source, t.ExternalID)
|
||||
fmt.Fprintf(&b, "- Priority: %d\n", t.InherentPriority)
|
||||
if len(t.Capability) > 0 {
|
||||
fmt.Fprintf(&b, "- Capability: %s\n", strings.Join(t.Capability, ", "))
|
||||
}
|
||||
if t.Due != nil {
|
||||
fmt.Fprintf(&b, "- Due: %s\n", t.Due.UTC().Format(time.RFC3339))
|
||||
}
|
||||
if t.Parent != "" {
|
||||
fmt.Fprintf(&b, "- Parent: %s\n", t.Parent)
|
||||
}
|
||||
b.WriteString("\nThis file is immutable for the lifetime of the task (§6.2) — its hash is\ncarried in every handoff and re-verified on every pickup. Do not edit it.\n")
|
||||
return []byte(b.String())
|
||||
}
|
||||
|
||||
// TaskFileHash returns the sha256 of the TASK.md at the root of a worktree.
|
||||
func TaskFileHash(root string) (string, error) {
|
||||
b, err := os.ReadFile(filepath.Join(root, "TASK.md"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sum := sha256.Sum256(b)
|
||||
return hex.EncodeToString(sum[:]), nil
|
||||
}
|
||||
|
||||
type Dirty struct {
|
||||
Path string `json:"path"`
|
||||
SHA256 string `json:"sha256"`
|
||||
|
||||
Reference in New Issue
Block a user