Files
orchestra/internal/operations/reflect_test.go
T
kami 7f12c7fc37 v3 workflow: intent, phases, review, submission, enforcement, burn-in
The v3 stack, previously an uncommitted working tree, plus this session's two
units and the burn-in instrument. This commit is the burn-in build identity:
coordinator and worker must both report this revision before a task is created.

Workflow (earlier sessions, uncommitted until now): human decision events and
reduction, source cursors and reconcile-before-launch, turn-boundary
reconciliation, internal/agentctx as the single renderer, ace-fca phases with
sealed artifacts, the trajectory gate, bounded grilling, independent review,
task pr enforcement, and human review reflection.

Capability restrictions at the agent boundary: an authz.Agent surface at
GatedWrite may ask and may not act. It also fixes two bugs the unit exposed --
gated surfaces could not reach the two endpoints written for them, and
RequestHumanDecision would block an unowned task while rejecting a question
from the session that did own it.

Turn-boundary reconcile-failure escalation: a streak of consecutive failures
asks the session to hand off, fenced on the lease epoch, with reconcile_failure
as a real handoff reason. The worker was dropping the coordinator's verdict on
the floor; it now acts on it.

Burn-in: herdr.WriteLaunchContext dumps the exact agentctx.Build result to
<worktree>/.orchestra/launch.md at every launch, local and federated. BURNIN.md
is the runbook. deploy/build.sh stamps both binaries from one commit.

go build, go vet and go test ./... pass, 20 packages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 18:31:20 +04:00

295 lines
11 KiB
Go

package operations
import (
"context"
"errors"
"strings"
"testing"
"time"
"orchestra/internal/domain"
"orchestra/internal/human"
"orchestra/internal/registry"
"orchestra/internal/review"
"orchestra/internal/store"
)
var operatorTrust = human.Trust{Accepted: []string{"kami"}, Ignored: []string{"orchestra-bot", "gitea-actions"}}
// submitted walks a task all the way to in_review at shaA.
func submitted(t *testing.T) (*store.Store, string, registry.Project) {
t.Helper()
s, id, project := reviewed(t)
plan, err := PrepareSubmission(s, project, id, shaA, gate(shaA), Notes{})
if err != nil {
t.Fatal(err)
}
if _, err := ExecuteSubmission(context.Background(), s, plan, &fakePublisher{}, head(shaA)); err != nil {
t.Fatal(err)
}
if got, _ := s.Task(id); got.State != domain.StateInReview {
t.Fatalf("state = %s", got.State)
}
return s, id, project
}
func submittedAt(t *testing.T, s *store.Store, id string) time.Time {
t.Helper()
got, _ := s.Task(id)
at, _, ok := submissionRecord(s, got)
if !ok {
t.Fatal("no submission event")
}
return at
}
func prState(id, headSHA, state string, comments ...human.Input) human.PullRequestState {
return human.PullRequestState{ID: id, HeadSHA: headSHA, State: state, Comments: comments}
}
// Trusted feedback after submission reopens the task without any lease being
// involved, and the old submission stays as history.
func TestTrustedFeedbackReopensTheTask(t *testing.T) {
s, id, project := submitted(t)
after := submittedAt(t, s, id).Add(time.Minute)
events, err := ReflectSubmission(s, project, id, prState("142", shaA, "open",
human.Input{Provider: "gitea:p", ExternalID: "c9", Author: "kami", At: after, Body: "change x to y"},
), operatorTrust)
if err != nil {
t.Fatal(err)
}
if len(events) != 2 {
t.Fatalf("events = %d, want a decision and a changes-requested", len(events))
}
got, _ := s.Task(id)
if got.State != domain.StateQueued {
t.Fatalf("state = %s, want queued so the router can lease it", got.State)
}
if got.WorkPhase != domain.WorkPhaseImplement {
t.Fatalf("phase = %q, want implement", got.WorkPhase)
}
if got.Submission == nil || got.Submission.ResultSHA != shaA {
t.Fatalf("the submission must remain as history: %+v", got.Submission)
}
// The feedback is standing authority.
intent, err := s.EffectiveIntent(id)
if err != nil {
t.Fatal(err)
}
if len(intent.Decisions) != 1 || intent.Decisions[0].Value != "change x to y" {
t.Fatalf("decisions = %+v", intent.Decisions)
}
// The old review and submission satisfy nothing at a new commit.
if domain.CheckSubmission(got, shaB, gate(shaB)).Eligible {
t.Fatal("a new commit inherited the old review")
}
// Polling again with the same comment changes nothing.
before := len(s.Events(0))
if events, err := ReflectSubmission(s, project, id, prState("142", shaA, "open",
human.Input{Provider: "gitea:p", ExternalID: "c9", Author: "kami", At: after, Body: "change x to y"},
), operatorTrust); err != nil || len(events) != 0 {
t.Fatalf("duplicate poll: events=%d err=%v", len(events), err)
}
if len(s.Events(0)) != before {
t.Fatal("a duplicate comment appended events")
}
}
// Bots, Orchestra itself, and comments from before the submission cannot
// reopen finished work.
func TestUntrustedAndStaleCommentsDoNotReopen(t *testing.T) {
s, id, project := submitted(t)
at := submittedAt(t, s, id)
cases := map[string]human.Input{
"bot": {Provider: "gitea:p", ExternalID: "b1", Author: "gitea-actions", At: at.Add(time.Minute), Body: "build passed"},
"orchestra itself": {Provider: "gitea:p", ExternalID: "b2", Author: "orchestra-bot", At: at.Add(time.Minute), Body: "submitted"},
"unknown actor": {Provider: "gitea:p", ExternalID: "b3", Author: "passer-by", At: at.Add(time.Minute), Body: "nice"},
"before submission": {Provider: "gitea:p", ExternalID: "b4", Author: "kami", At: at.Add(-time.Hour), Body: "looks good so far"},
"at submission": {Provider: "gitea:p", ExternalID: "b5", Author: "kami", At: at, Body: "same instant"},
"empty": {Provider: "gitea:p", ExternalID: "b6", Author: "kami", At: at.Add(time.Minute), Body: " "},
}
for name, in := range cases {
events, err := ReflectSubmission(s, project, id, prState("142", shaA, "open", in), operatorTrust)
if err != nil || len(events) != 0 {
t.Fatalf("%s: events=%d err=%v", name, len(events), err)
}
if got, _ := s.Task(id); got.State != domain.StateInReview {
t.Fatalf("%s: reopened the task", name)
}
}
}
// A merged pull request completes the task, with the receipt bound to the exact
// submission. Merge strategy is not assumed.
func TestMergedPullRequestCompletes(t *testing.T) {
s, id, project := submitted(t)
state := human.PullRequestState{
ID: "142", HeadSHA: shaA, State: "merged",
MergeSHA: "9999999999999999999999999999999999999999", MergedAt: time.Unix(1700000000, 0).UTC(),
}
events, err := ReflectSubmission(s, project, id, state, operatorTrust)
if err != nil {
t.Fatal(err)
}
if len(events) != 1 || events[0].Type != "TaskCompleted" {
t.Fatalf("events = %+v", events)
}
got, _ := s.Task(id)
if got.State != domain.StateCompleted {
t.Fatalf("state = %s", got.State)
}
// The receipt names the submission, the pull request, both commits, and
// when it merged.
var payload struct {
ReportRef string `json:"report_ref"`
Receipt domain.CompletionReceipt `json:"receipt"`
}
if err := unmarshal(events[0].Payload, &payload); err != nil {
t.Fatal(err)
}
r := payload.Receipt
if r.SubmittedSHA != shaA || r.MergeSHA != "9999999999999999999999999999999999999999" {
t.Fatalf("receipt = %+v", r)
}
if r.PR.ID != "142" || r.SubmissionRef == "" || r.MergedAt.IsZero() {
t.Fatalf("receipt = %+v", r)
}
if _, err := s.Artifact(payload.ReportRef); err != nil {
t.Fatalf("receipt artifact missing: %v", err)
}
// Reflecting again is idempotent.
if events, err := ReflectSubmission(s, project, id, state, operatorTrust); err != nil || len(events) != 0 {
t.Fatalf("second merge reflection: events=%d err=%v", len(events), err)
}
}
// A stale observation cannot complete a task, and neither can another task's
// pull request.
func TestForeignOrStaleObservationCannotComplete(t *testing.T) {
s, id, project := submitted(t)
// Another pull request entirely.
if _, err := ReflectSubmission(s, project, id, human.PullRequestState{ID: "999", HeadSHA: shaA, State: "merged"}, operatorTrust); !errors.Is(err, ErrForeignPullRequest) {
t.Fatalf("want ErrForeignPullRequest, got %v", err)
}
// The right pull request, but carrying a commit that was never submitted.
if _, err := ReflectSubmission(s, project, id, human.PullRequestState{ID: "142", HeadSHA: shaB, State: "merged"}, operatorTrust); !errors.Is(err, ErrForeignPullRequest) {
t.Fatalf("want ErrForeignPullRequest, got %v", err)
}
if got, _ := s.Task(id); got.State != domain.StateInReview {
t.Fatalf("state = %s, a rejected observation must change nothing", got.State)
}
}
// Closed without merging is an operator question, not a failure.
func TestClosedWithoutMergeAsksTheOperator(t *testing.T) {
s, id, project := submitted(t)
events, err := ReflectSubmission(s, project, id, human.PullRequestState{ID: "142", HeadSHA: shaA, State: "closed"}, operatorTrust)
if err != nil {
t.Fatal(err)
}
if len(events) != 1 {
t.Fatalf("events = %+v", events)
}
got, _ := s.Task(id)
if got.State == domain.StateFailed || got.State == domain.StateCompleted {
t.Fatalf("state = %s, a closed pull request must not decide the task", got.State)
}
if got.BlockReason != domain.BlockReasonOperator {
t.Fatalf("block reason = %q", got.BlockReason)
}
if !strings.Contains(got.Blocker, "closed without merging") {
t.Fatalf("blocker = %q", got.Blocker)
}
// And it does not repeat on the next poll.
if events, err := ReflectSubmission(s, project, id, human.PullRequestState{ID: "142", HeadSHA: shaA, State: "closed"}, operatorTrust); err != nil || len(events) != 0 {
t.Fatalf("repeated: events=%d err=%v", len(events), err)
}
}
// A review with changes_requested and no body still reopens the task.
func TestChangesRequestedReviewWithNoBodyReopens(t *testing.T) {
s, id, project := submitted(t)
after := submittedAt(t, s, id).Add(time.Minute)
state := human.PullRequestState{ID: "142", HeadSHA: shaA, State: "open", Reviews: []human.ReviewObservation{
{Actor: "kami", State: "changes_requested", At: after},
}}
events, err := ReflectSubmission(s, project, id, state, operatorTrust)
if err != nil {
t.Fatal(err)
}
if len(events) != 2 {
t.Fatalf("events = %d", len(events))
}
if got, _ := s.Task(id); got.State != domain.StateQueued {
t.Fatalf("state = %s", got.State)
}
}
// A reflector outage leaves the task exactly as it was.
func TestReflectorOutageChangesNothing(t *testing.T) {
s, id, project := submitted(t)
before, _ := s.Task(id)
beforeEvents := len(s.Events(0))
// A poll that never happened is simply a poll with no observation. The
// caller records its own error; the task must not move.
if _, err := ReflectSubmission(s, project, id, prState("142", shaA, "open"), operatorTrust); err != nil {
t.Fatal(err)
}
after, _ := s.Task(id)
if after.State != before.State || after.Version != before.Version || len(s.Events(0)) != beforeEvents {
t.Fatalf("an empty observation changed state: %s -> %s", before.State, after.State)
}
}
// The full loop: rejected at A, fixed at B, reviewed again, resubmitted to the
// same pull request, then merged.
func TestFullHumanLoopFromRejectionToMerge(t *testing.T) {
s, id, project := submitted(t)
after := submittedAt(t, s, id).Add(time.Minute)
if _, err := ReflectSubmission(s, project, id, prState("142", shaA, "open",
human.Input{Provider: "gitea:p", ExternalID: "c9", Author: "kami", At: after, Body: "rename the variable"},
), operatorTrust); err != nil {
t.Fatal(err)
}
// A fresh gate and a fresh review at the new commit.
if _, err := EnterReview(s, project, id, evidence(shaB)); err != nil {
t.Fatal(err)
}
if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaB, Findings: []review.Finding{finding("f1", review.Minor)}}); err != nil {
t.Fatal(err)
}
plan, err := PrepareSubmission(s, project, id, shaB, gate(shaB), Notes{})
if err != nil {
t.Fatal(err)
}
pub := &fakePublisher{pr: domain.ExternalRef{Provider: "gitea:p", ID: "142", URL: "https://git/pulls/142"}}
if _, err := ExecuteSubmission(context.Background(), s, plan, pub, head(shaB)); err != nil {
t.Fatal(err)
}
resubmitted, _ := s.Task(id)
if resubmitted.Submission.ResultSHA != shaB || resubmitted.Submission.PR.ID != "142" {
t.Fatalf("submission = %+v, the same pull request must be refreshed", resubmitted.Submission)
}
// Feedback on the old submission cannot reopen the new one.
if _, err := ReflectSubmission(s, project, id, prState("142", shaA, "merged"), operatorTrust); !errors.Is(err, ErrForeignPullRequest) {
t.Fatalf("a stale observation completed a newer submission: %v", err)
}
// The human merges what they reviewed.
if _, err := ReflectSubmission(s, project, id, human.PullRequestState{
ID: "142", HeadSHA: shaB, State: "merged", MergeSHA: "8888888888888888888888888888888888888888", MergedAt: time.Unix(1700009999, 0).UTC(),
}, operatorTrust); err != nil {
t.Fatal(err)
}
final, _ := s.Task(id)
if final.State != domain.StateCompleted {
t.Fatalf("state = %s", final.State)
}
}