package integration import ( "context" "testing" "orchestra/internal/domain" "orchestra/internal/human" "orchestra/internal/operations" "orchestra/internal/orchestrator" "orchestra/internal/registry" "orchestra/internal/router" "orchestra/internal/workphase" ) // F23. Human steering must work throughout the task, not only after a pull // request exists. Pull-request feedback is a separate concept with a narrower // window; conflating them would leave every pre-submission phase unsteerable, // which is the state run 3 was diagnosed in. // // The task here never submits anything. It sits in its first phase, leased and // running, and a comment on its own issue still becomes a standing decision // that the live session is handed at its next verified turn boundary. func TestPreSubmissionCommentSteersALiveSession(t *testing.T) { s, reg, _ := setup(t) task := ingest(t, s, "381") src := &tracingSource{tr: &trace{}} 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{occupancy: .1}}, StatePath: t.TempDir() + "/sessions.json", Hard: .8} c.ReconcileHumanInput = rec.Reconcile 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) } leasedTask, _ := s.Task(task.ID) if leasedTask.Lease == nil { t.Fatal("task is not leased") } // Still in the first phase, and nothing has been submitted. if leasedTask.WorkPhase != "" && leasedTask.WorkPhase != domain.WorkPhaseFrame { t.Fatalf("phase = %q", leasedTask.WorkPhase) } epoch := leasedTask.Lease.Epoch // No human input yet: the boundary answers with nothing to deliver. _, decisions, err := c.RemoteTurn(context.Background(), task.ID, epoch, orchestrator.TurnContinue, nil) if err != nil { t.Fatal(err) } if len(decisions) != 0 { t.Fatalf("decisions before any comment = %+v", decisions) } // The human comments on the issue while the session runs. src.next = "918" src.inputs = []human.Input{{Provider: "gitea", ExternalID: "918", Author: "kami", Body: "no, use b"}} _, decisions, err = c.RemoteTurn(context.Background(), task.ID, epoch, orchestrator.TurnContinue, nil) if err != nil { t.Fatal(err) } if len(decisions) != 1 || decisions[0].Value != "no, use b" { t.Fatalf("decisions = %+v", decisions) } // Reported as delivered, so the same correction is not re-sent every turn. _, decisions, err = c.RemoteTurn(context.Background(), task.ID, epoch, orchestrator.TurnContinue, []string{decisions[0].ID}) if err != nil { t.Fatal(err) } if len(decisions) != 0 { t.Fatalf("a delivered decision repeated: %+v", decisions) } } // F21 and F22 at the coordinator boundary: an accepted request moves the phase // and seals what the phase produced, and a stale session's request is refused. func TestPhaseRequestPathSealsAndFences(t *testing.T) { s, reg, _ := setup(t) task := ingest(t, s, "381") c := &orchestrator.Coordinator{Store: s, Worktrees: worktrees{}, Adapters: adapters{&harness{occupancy: .1}}, StatePath: t.TempDir() + "/sessions.json", Hard: .8} rt := router.Router{Store: s, Registry: reg, Reachability: alwaysReachable{}, OnLease: func(e domain.Event) error { return c.Start(context.Background(), e) }} if _, err := rt.AssignPending(); err != nil { t.Fatal(err) } leased, _ := s.Task(task.ID) epoch := leased.Lease.Epoch project := registry.Project{ID: "p", Verification: registry.VerificationPolicy{Allowed: [][]string{{"go", "test", "*"}}}} if _, err := operations.RequestWorkPhase(s, project, task.ID, epoch, "op-1", domain.WorkPhaseFrame, domain.WorkPhaseResearch, nil); err != nil { t.Fatal(err) } got, _ := s.Task(task.ID) if got.WorkPhase != domain.WorkPhaseResearch { t.Fatalf("phase = %q", got.WorkPhase) } // A request carrying a lease epoch that no longer owns the task is a stale // opinion from a session that has been superseded. if _, err := operations.RequestWorkPhase(s, project, task.ID, "stale-epoch", "op-2", domain.WorkPhaseResearch, domain.WorkPhasePlan, sealed(t, researchArtifact)); err == nil { t.Fatal("a stale epoch advanced the phase") } if got, _ := s.Task(task.ID); got.WorkPhase != domain.WorkPhaseResearch { t.Fatalf("phase moved on a stale request: %q", got.WorkPhase) } // The real owner's request seals the research the next phase will read. if _, err := operations.RequestWorkPhase(s, project, task.ID, epoch, "op-3", domain.WorkPhaseResearch, domain.WorkPhasePlan, sealed(t, researchArtifact)); err != nil { t.Fatal(err) } got, _ = s.Task(task.ID) if got.WorkPhase != domain.WorkPhasePlan || got.ResearchRef == "" { t.Fatalf("task = %+v", got) } } var researchArtifact = workphase.Research{Findings: []workphase.Finding{{ID: "r1", Confidence: workphase.Fact, Claim: "runs per figure", Evidence: "attr.go:88"}}} func sealed(t *testing.T, v interface{ Validate() error }) []byte { t.Helper() b, err := workphase.Encode(v) if err != nil { t.Fatal(err) } return b }