Add federation worker and canonical handoffs

This commit is contained in:
kami
2026-07-28 16:17:18 +04:00
parent 58793a5aa3
commit 2cecbc4015
22 changed files with 1429 additions and 108 deletions
+123 -41
View File
@@ -3,8 +3,6 @@ package herdr
import (
"bufio"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"net"
"orchestra/internal/continuity"
@@ -61,15 +59,20 @@ func fakeHerdr(t *testing.T) *Client {
func validHandoff(anchorSHA string) continuity.Handoff {
return continuity.Handoff{
Meta: continuity.Meta{ID: "t1", Reason: "threshold", RotationIndex: 1},
Anchor: continuity.Anchor{GitSHA: anchorSHA, Branch: "main"},
Goal: "finish the thing",
DoneWhen: []string{"tests pass"},
Action: "continue",
Command: "go test ./...",
Meta: continuity.Meta{ID: "t1", Reason: "threshold", RotationIndex: 1},
Anchor: continuity.Anchor{GitSHA: anchorSHA, Branch: "main"},
Action: "run the focused tests",
Command: "go test ./...",
}
}
const validAnswer = `NEXT: run the focused tests
WHY: confirm the current implementation before changing it
REMAINING: NONE
DEAD ENDS: NONE
OPEN Q: NONE
LEARNED: NONE`
func TestReleaseUploadsHandoffAndReleasesAgent(t *testing.T) {
repo := t.TempDir()
runGit(t, repo, "init")
@@ -81,11 +84,7 @@ func TestReleaseUploadsHandoffAndReleasesAgent(t *testing.T) {
t.Fatal(err)
}
b, err := continuity.Encode(validHandoff(head))
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(repo, HandoffReportFile), b, 0644); err != nil {
if err := os.WriteFile(filepath.Join(repo, HandoffReportFile), []byte(validAnswer), 0644); err != nil {
t.Fatal(err)
}
@@ -105,8 +104,77 @@ func TestReleaseUploadsHandoffAndReleasesAgent(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if got.Anchor.GitSHA == head {
t.Fatal("semantic report should be snapshotted before publication")
if got.Anchor.GitSHA != head {
t.Fatal("semantic report must not be included in the successor scratch anchor")
}
if _, err := os.Stat(filepath.Join(repo, HandoffReportFile)); !os.IsNotExist(err) {
t.Fatalf("transferred semantic report still present: %v", err)
}
}
func TestCanonicalHandoffCarriesCoordinatorReason(t *testing.T) {
repo := t.TempDir()
runGit(t, repo, "init")
runGit(t, repo, "config", "user.email", "t@t")
runGit(t, repo, "config", "user.name", "t")
runGit(t, repo, "commit", "--allow-empty", "-m", "init")
if err := os.WriteFile(filepath.Join(repo, HandoffReportFile), []byte(validAnswer), 0644); err != nil {
t.Fatal(err)
}
cas := &memCAS{}
a := CLIAdapter{Client: fakeHerdr(t), Harness: "claude", CAS: cas}
ref, err := a.Release(context.Background(), Session{PaneID: "p1", Worktree: repo, HandoffReason: "thrash"})
if err != nil {
t.Fatal(err)
}
got, err := continuity.Load(ref, cas)
if err != nil {
t.Fatal(err)
}
if got.Meta.Reason != "thrash" {
t.Fatalf("canonical reason = %q, want thrash", got.Meta.Reason)
}
}
func TestReleaseUsesHarnessBindingNotDisplayName(t *testing.T) {
repo := t.TempDir()
runGit(t, repo, "init")
runGit(t, repo, "config", "user.email", "t@t")
runGit(t, repo, "config", "user.name", "t")
runGit(t, repo, "commit", "--allow-empty", "-m", "init")
if err := os.WriteFile(filepath.Join(repo, HandoffReportFile), []byte(validAnswer), 0644); err != nil {
t.Fatal(err)
}
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = ln.Close() })
request := make(chan Request, 1)
go func() {
conn, err := ln.Accept()
if err != nil {
return
}
defer conn.Close()
var req Request
if json.NewDecoder(bufio.NewReader(conn)).Decode(&req) == nil {
request <- req
_ = json.NewEncoder(conn).Encode(Response{ID: req.ID, Result: json.RawMessage(`{}`)})
}
}()
a := CLIAdapter{Client: &Client{Path: ln.Addr().String(), dial: func() (net.Conn, error) { return net.Dial("tcp", ln.Addr().String()) }}, Harness: "opencode", CAS: &memCAS{}}
if _, err := a.Release(context.Background(), Session{PaneID: "p1", Worktree: repo, AgentName: "oc-task-specific"}); err != nil {
t.Fatal(err)
}
req := <-request
p, _ := json.Marshal(req.Params)
var got struct {
Agent string `json:"agent"`
}
_ = json.Unmarshal(p, &got)
if got.Agent != "opencode" {
t.Fatalf("release agent = %q, want harness binding opencode", got.Agent)
}
}
@@ -119,6 +187,43 @@ func TestReleaseRefusesWithoutHandoffFile(t *testing.T) {
}
}
func TestReleaseRefusesNarrativeOrCircularHandoffAnswer(t *testing.T) {
repo := t.TempDir()
runGit(t, repo, "init")
runGit(t, repo, "config", "user.email", "t@t")
runGit(t, repo, "config", "user.name", "t")
runGit(t, repo, "commit", "--allow-empty", "-m", "init")
bad := `NEXT: Continue the task from the handoff
WHY: the predecessor asked for it
REMAINING: what changed\n# a markdown report
DEAD ENDS: NONE
OPEN Q: NONE
LEARNED: NONE`
if err := os.WriteFile(filepath.Join(repo, HandoffReportFile), []byte(bad), 0o644); err != nil {
t.Fatal(err)
}
a := CLIAdapter{Client: fakeHerdr(t), Harness: "claude", CAS: &memCAS{}}
if _, err := a.Release(context.Background(), Session{PaneID: "p1", Worktree: repo}); err == nil {
t.Fatal("expected invalid agent answer to refuse release")
}
}
func TestParseHandoffAnswerPreservesOnlyAuthoredFields(t *testing.T) {
a, err := parseHandoffAnswer(`NEXT: inspect the failing integration test
WHY: isolate the regression before changing production code
REMAINING: fix the assertion after identifying the cause
DEAD ENDS: tried rerunning the whole suite → failed because it obscures the relevant failure
OPEN Q: whether the remote worker has the updated fixture
LEARNED: the fixture requires a committed scratch branch
`)
if err != nil {
t.Fatal(err)
}
if len(a.Remaining) != 1 || len(a.DeadEnds) != 1 || len(a.OpenQuestions) != 1 || len(a.Learned) != 1 {
t.Fatalf("parsed answer lost authored fields: %#v", a)
}
}
func TestReleaseDoesNotTrustAgentSuppliedAnchor(t *testing.T) {
repo := t.TempDir()
runGit(t, repo, "init")
@@ -126,11 +231,7 @@ func TestReleaseDoesNotTrustAgentSuppliedAnchor(t *testing.T) {
runGit(t, repo, "config", "user.name", "t")
runGit(t, repo, "commit", "--allow-empty", "-m", "init")
b, err := continuity.Encode(validHandoff("deaddeaddeaddeaddeaddeaddeaddeaddeaddead"))
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(repo, HandoffReportFile), b, 0644); err != nil {
if err := os.WriteFile(filepath.Join(repo, HandoffReportFile), []byte(validAnswer), 0644); err != nil {
t.Fatal(err)
}
a := CLIAdapter{Client: fakeHerdr(t), Harness: "claude", CAS: &memCAS{}}
@@ -154,14 +255,7 @@ func TestReleaseScratchCommitsDirtyFilesBeforeUpload(t *testing.T) {
if err := os.WriteFile(wipPath, []byte("in progress"), 0644); err != nil {
t.Fatal(err)
}
sum := sha256.Sum256([]byte("in progress"))
h := validHandoff(head)
h.Anchor.Dirty = []continuity.Dirty{{Path: "wip.txt", SHA256: hex.EncodeToString(sum[:])}}
b, err := continuity.Encode(h)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(repo, HandoffReportFile), b, 0644); err != nil {
if err := os.WriteFile(filepath.Join(repo, HandoffReportFile), []byte(validAnswer), 0644); err != nil {
t.Fatal(err)
}
@@ -203,22 +297,10 @@ func TestReleaseDoesNotTrustAgentSuppliedDirtyFile(t *testing.T) {
runGit(t, repo, "config", "user.email", "t@t")
runGit(t, repo, "config", "user.name", "t")
runGit(t, repo, "commit", "--allow-empty", "-m", "init")
head, err := HeadSHA(repo)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(repo, "wip.txt"), []byte("changed after handoff written"), 0644); err != nil {
t.Fatal(err)
}
stale := sha256.Sum256([]byte("original content"))
h := validHandoff(head)
h.Anchor.Dirty = []continuity.Dirty{{Path: "wip.txt", SHA256: hex.EncodeToString(stale[:])}}
b, err := continuity.Encode(h)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(repo, HandoffReportFile), b, 0644); err != nil {
if err := os.WriteFile(filepath.Join(repo, HandoffReportFile), []byte(validAnswer), 0644); err != nil {
t.Fatal(err)
}
a := CLIAdapter{Client: fakeHerdr(t), Harness: "claude", CAS: &memCAS{}}