package integration import ( "context" "errors" "os" "path/filepath" "sync" "testing" "time" "strings" "orchestra/internal/domain" "orchestra/internal/herdr" "orchestra/internal/human" "orchestra/internal/orchestrator" "orchestra/internal/router" "orchestra/internal/store" ) // trace records the real order of operations across the launch path, which is // the property under test: reconciliation must be upstream of the agent, not // merely present somewhere in the process. type trace struct { mu sync.Mutex steps []string } func (tr *trace) add(step string) { tr.mu.Lock() tr.steps = append(tr.steps, step) tr.mu.Unlock() } func (tr *trace) snapshot() []string { tr.mu.Lock() defer tr.mu.Unlock() return append([]string(nil), tr.steps...) } type tracingSource struct { tr *trace inputs []human.Input next string err error } func (s *tracingSource) FetchAfter(_ context.Context, task domain.Task, cursor store.SourceCursor) ([]human.Input, store.SourceCursor, error) { s.tr.add("fetch") if s.err != nil { return nil, store.SourceCursor{}, s.err } return s.inputs, store.SourceCursor{TaskID: task.ID, Provider: "gitea", Cursor: s.next}, nil } // The milestone test: a comment written while the task was queued is durable // and standing before the agent that will act on it is started. func TestHumanInputReconciledBeforeAgentStarts(t *testing.T) { s, reg, _ := setup(t) task := ingest(t, s, "381") tr := &trace{} src := &tracingSource{tr: tr, next: "918", inputs: []human.Input{ {Provider: "gitea", ExternalID: "918", Author: "kami", Body: "no, use b"}, }} rec := &human.Reconciler{Store: s, Sources: map[string]human.Source{"gitea": src}} s.PreLease = func(id string) error { return rec.Reconcile(context.Background(), id) } c := &orchestrator.Coordinator{Store: s, Worktrees: worktrees{}, Adapters: adapters{&harness{}}, StatePath: t.TempDir() + "/sessions.json"} rt := router.Router{Store: s, Registry: reg, Reachability: alwaysReachable{}, OnLease: func(e domain.Event) error { tr.add("agent.start") return c.Start(context.Background(), e) }} leased, err := rt.AssignPending() if err != nil || len(leased) != 1 { t.Fatalf("leased=%d err=%v", len(leased), err) } if got := tr.snapshot(); len(got) != 2 || got[0] != "fetch" || got[1] != "agent.start" { t.Fatalf("order = %v, want fetch before agent.start", got) } intent, err := s.EffectiveIntent(task.ID) if err != nil { t.Fatal(err) } if len(intent.Decisions) != 1 || intent.Decisions[0].Value != "no, use b" { t.Fatalf("standing set = %+v", intent.Decisions) } if c, ok := s.SourceCursor(task.ID, "gitea"); !ok || c.Cursor != "918" { t.Fatalf("cursor = %+v ok=%v", c, ok) } } // Fail closed. If Orchestra cannot establish whether newer human input // exists, no lease is minted, so nothing downstream can start an agent from // the older intent. func TestUnreachableSourceRefusesTheLease(t *testing.T) { s, reg, _ := setup(t) task := ingest(t, s, "381") tr := &trace{} src := &tracingSource{tr: tr, err: errors.New("gitea unreachable")} rec := &human.Reconciler{Store: s, Sources: map[string]human.Source{"gitea": src}} s.PreLease = func(id string) error { return rec.Reconcile(context.Background(), id) } h := &harness{} c := &orchestrator.Coordinator{Store: s, Worktrees: worktrees{}, Adapters: adapters{h}, StatePath: t.TempDir() + "/sessions.json"} rt := router.Router{Store: s, Registry: reg, Reachability: alwaysReachable{}, OnLease: func(e domain.Event) error { tr.add("agent.start") return c.Start(context.Background(), e) }} leased, err := rt.AssignPending() if len(leased) != 0 { t.Fatalf("a lease was minted despite unreachable human input: %v (err=%v)", leased, err) } for _, step := range tr.snapshot() { if step == "agent.start" { t.Fatal("an agent was started without reconciliation") } } got, _ := s.Task(task.ID) if got.State != domain.StateQueued { t.Fatalf("task state = %s, want queued so a later attempt retries", got.State) } if got.Lease != nil { t.Fatal("task must not hold a lease") } } // The successor case: a comment arriving after session 1 released must be // standing before session 2 is leased, not merged in later. func TestSuccessorLeaseReconcilesBeforeResume(t *testing.T) { s, reg, _ := setup(t) task := ingest(t, s, "381") tr := &trace{} src := &tracingSource{tr: tr} rec := &human.Reconciler{Store: s, Sources: map[string]human.Source{"gitea": src}} s.PreLease = func(id string) error { return rec.Reconcile(context.Background(), id) } if _, err := s.Lease(task.ID, "h1", 30*time.Minute); err != nil { t.Fatal(err) } ref, err := s.PutArtifact([]byte("handoff: next, implement a")) if err != nil { t.Fatal(err) } leasedTask, _ := s.Task(task.ID) if err := s.Append(domain.Event{ID: domain.NewID(), Type: "TaskReleased", TaskID: task.ID, Version: leasedTask.Version + 1, Surface: "system", Payload: mustJSON(map[string]any{ "handoff_ref": ref, "anchor_sha": "0123456789012345678901234567890123456789", "harness_id": leasedTask.Lease.HarnessID, "lease_epoch": leasedTask.Lease.Epoch, "expected_version": leasedTask.Version, })}); err != nil { t.Fatal(err) } // The human comments while the task sits queued between sessions. src.inputs = []human.Input{{Provider: "gitea", ExternalID: "918", Author: "kami", Body: "no, use b"}} src.next = "918" rt := router.Router{Store: s, Registry: reg, Reachability: alwaysReachable{}, OnLease: func(e domain.Event) error { intent, err := s.EffectiveIntent(e.TaskID) if err != nil { return err } // Read at the moment ownership begins: the successor's authority must // already contain the correction, before it reads any handoff. if len(intent.Decisions) != 1 || intent.Decisions[0].Value != "no, use b" { t.Errorf("successor launched with standing set %+v", intent.Decisions) } if intent.Task.HandoffRef != ref { t.Errorf("handoff ref = %q, want %q", intent.Task.HandoffRef, ref) } return nil }} leased, err := rt.AssignPending() if err != nil || len(leased) != 1 { t.Fatalf("leased=%d err=%v", len(leased), err) } } // capturingAdapter records the exact launch instruction the agent receives. type capturingAdapter struct { *harness mu sync.Mutex prompt string } func (a *capturingAdapter) LeasePrompt(_ context.Context, _, worktree, prompt string) (herdr.Session, error) { a.mu.Lock() a.prompt = prompt a.mu.Unlock() return herdr.Session{Harness: "h1", PaneID: "pane-1", Worktree: worktree}, nil } type capturingAdapters struct{ a herdr.Adapter } func (c capturingAdapters) Adapter(string) (herdr.Adapter, error) { return c.a, nil } // The live proof: the contract says implement a, the human says use b, and the // agent's launch instruction presents b as authority. func TestAgentLaunchInstructionCarriesTheCorrection(t *testing.T) { s, reg, _ := setup(t) task := ingest(t, s, "381") amend, _ := s.Task(task.ID) if err := s.Append(domain.Event{ID: domain.NewID(), Type: "TaskAmended", TaskID: task.ID, Version: amend.Version + 1, Surface: "system", Payload: mustJSON(map[string]any{ "description": "implement a", })}); err != nil { t.Fatal(err) } src := &tracingSource{tr: &trace{}, next: "918", inputs: []human.Input{ {Provider: "gitea", ExternalID: "918", Author: "kami", Body: "no, use b"}, }} rec := &human.Reconciler{Store: s, Sources: map[string]human.Source{"gitea": src}} s.PreLease = func(id string) error { return rec.Reconcile(context.Background(), id) } a := &capturingAdapter{harness: &harness{}} c := &orchestrator.Coordinator{Store: s, Worktrees: worktrees{}, Adapters: capturingAdapters{a}, StatePath: t.TempDir() + "/sessions.json"} rt := router.Router{Store: s, Registry: reg, Reachability: alwaysReachable{}, OnLease: func(e domain.Event) error { return c.Start(context.Background(), e) }} if leased, err := rt.AssignPending(); err != nil || len(leased) != 1 { t.Fatalf("leased=%d err=%v", len(leased), err) } a.mu.Lock() prompt := a.prompt a.mu.Unlock() if prompt == "" { t.Fatal("no launch instruction was sent") } decision := strings.Index(prompt, "no, use b") goal := strings.Index(prompt, "implement a") if decision < 0 || goal < 0 { t.Fatalf("prompt missing goal or decision:\n%s", prompt) } if !strings.Contains(prompt, "## Current human decisions") { t.Fatalf("prompt has no decisions section:\n%s", prompt) } if !strings.Contains(prompt, "Authority order") { t.Fatalf("prompt does not state the authority order:\n%s", prompt) } if goal > decision { t.Fatal("goal must precede the decisions section") } } // tempWorktrees gives one test its own worktree, so what a launch writes into // it can be read back. type tempWorktrees struct{ path string } func (w tempWorktrees) Create(context.Context, domain.Task) (string, error) { return w.path, nil } // Burn-in depends on this: the exact instruction a session was launched with is // on disk, not only in pane scrollback the harness has reflowed. func TestLaunchWritesTheContextItSent(t *testing.T) { s, reg, _ := setup(t) task := ingest(t, s, "381") src := &tracingSource{tr: &trace{}, next: "918", inputs: []human.Input{ {Provider: "gitea", ExternalID: "918", Author: "kami", Body: "no, use b"}, }} rec := &human.Reconciler{Store: s, Sources: map[string]human.Source{"gitea": src}} s.PreLease = func(id string) error { return rec.Reconcile(context.Background(), id) } worktree := t.TempDir() a := &capturingAdapter{harness: &harness{}} c := &orchestrator.Coordinator{Store: s, Worktrees: tempWorktrees{path: worktree}, Adapters: capturingAdapters{a}, StatePath: t.TempDir() + "/sessions.json"} rt := router.Router{Store: s, Registry: reg, Reachability: alwaysReachable{}, OnLease: func(e domain.Event) error { return c.Start(context.Background(), e) }} if leased, err := rt.AssignPending(); err != nil || len(leased) != 1 { t.Fatalf("leased=%d err=%v", len(leased), err) } b, err := os.ReadFile(filepath.Join(worktree, herdr.LaunchContextFile)) if err != nil { t.Fatalf("no launch context recorded for task %s: %v", task.ID, err) } a.mu.Lock() sent := a.prompt a.mu.Unlock() if string(b) != sent { t.Fatalf("recorded context differs from what was sent:\nrecorded:\n%s\nsent:\n%s", b, sent) } if !strings.Contains(string(b), "no, use b") { t.Fatalf("recorded context missing the standing decision:\n%s", b) } }