7f12c7fc37
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>
219 lines
8.5 KiB
Go
219 lines
8.5 KiB
Go
package operations
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"orchestra/internal/domain"
|
|
"orchestra/internal/registry"
|
|
"orchestra/internal/review"
|
|
"orchestra/internal/store"
|
|
)
|
|
|
|
const shaA = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
const shaB = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
|
const shaBase = "0000000000000000000000000000000000000000"
|
|
|
|
func evidence(result string) review.Evidence {
|
|
return review.Evidence{
|
|
BaseSHA: shaBase, ResultSHA: result,
|
|
Diff: "--- a/internal/attr/attr.go\n+++ b/internal/attr/attr.go\n+index lookup\n",
|
|
GateCommand: "go test ./...", GateExit: 0,
|
|
}
|
|
}
|
|
|
|
// atImplement walks a task to the implementation phase with both artifacts
|
|
// sealed, which is where review becomes possible.
|
|
func atImplement(t *testing.T, project registry.Project) (*store.Store, string) {
|
|
t.Helper()
|
|
s, id := phaseStore(t)
|
|
if _, err := AdvanceWorkPhase(s, project, id, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := AdvanceWorkPhase(s, project, id, sealed(t, research)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := AdvanceWorkPhase(s, project, id, sealed(t, plan)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return s, id
|
|
}
|
|
|
|
func finding(id string, sev review.Severity) review.Finding {
|
|
return review.Finding{
|
|
ID: id, Severity: sev, File: "internal/attr/attr.go", Line: 81,
|
|
Claim: "retry path acknowledges success before the durable append", Evidence: "line 81 returns before Append",
|
|
}
|
|
}
|
|
|
|
// Minor findings are reported and the task stays eligible. The review is bound
|
|
// to the commit it examined.
|
|
func TestMinorOnlyReviewIsAccepted(t *testing.T) {
|
|
project := registry.Project{ID: "p", QualityGate: "go test ./..."}
|
|
s, id := atImplement(t, project)
|
|
if _, err := EnterReview(s, project, id, evidence(shaA)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, _ := s.Task(id); got.WorkPhase != domain.WorkPhaseReview {
|
|
t.Fatalf("phase = %q", got.WorkPhase)
|
|
}
|
|
if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaA, Findings: []review.Finding{finding("f1", review.Minor)}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, _ := s.Task(id)
|
|
if got.WorkPhase != domain.WorkPhaseReview {
|
|
t.Fatalf("a minor-only review must not send work back: %q", got.WorkPhase)
|
|
}
|
|
if !got.ReviewSatisfied(shaA) {
|
|
t.Fatalf("review not satisfied for its own commit: %+v", got.Review)
|
|
}
|
|
// The same review says nothing about a different tree.
|
|
if got.ReviewSatisfied(shaB) {
|
|
t.Fatal("a review of one commit must not satisfy another")
|
|
}
|
|
}
|
|
|
|
// A blocking finding returns the task to implementation, and the old review
|
|
// cannot satisfy the new commit.
|
|
func TestBlockingReviewReturnsWorkAndGoesStale(t *testing.T) {
|
|
project := registry.Project{ID: "p", QualityGate: "go test ./..."}
|
|
s, id := atImplement(t, project)
|
|
if _, err := EnterReview(s, project, id, evidence(shaA)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaA, Findings: []review.Finding{
|
|
finding("f1", review.Important), finding("f2", review.Minor),
|
|
}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, _ := s.Task(id)
|
|
if got.WorkPhase != domain.WorkPhaseImplement {
|
|
t.Fatalf("phase = %q, want implement", got.WorkPhase)
|
|
}
|
|
if got.ReviewSatisfied(shaA) {
|
|
t.Fatal("a review with an important finding must not satisfy completion")
|
|
}
|
|
// The findings are readable for the implementation context.
|
|
r, err := TaskReview(s, got)
|
|
if err != nil || r == nil || len(r.Findings) != 2 {
|
|
t.Fatalf("findings = %+v err=%v", r, err)
|
|
}
|
|
|
|
// Fixed at a new commit: a fresh review passes, and it is bound to B.
|
|
if _, err := EnterReview(s, project, id, evidence(shaB)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaB}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, _ = s.Task(id)
|
|
if !got.ReviewSatisfied(shaB) {
|
|
t.Fatalf("fresh review not satisfied: %+v", got.Review)
|
|
}
|
|
if got.ReviewSatisfied(shaA) {
|
|
t.Fatal("the superseded commit must not look reviewed")
|
|
}
|
|
}
|
|
|
|
// A review sealed against a commit other than the one under review is
|
|
// rejected while the reviewing session still exists to redo it.
|
|
func TestReviewForTheWrongCommitIsRejected(t *testing.T) {
|
|
project := registry.Project{ID: "p", QualityGate: "go test ./..."}
|
|
s, id := atImplement(t, project)
|
|
if _, err := EnterReview(s, project, id, evidence(shaA)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, _ := s.Task(id); got.ReviewTargetSHA != shaA {
|
|
t.Fatalf("review target = %q", got.ReviewTargetSHA)
|
|
}
|
|
if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaB}); !errors.Is(err, domain.ErrInvalid) {
|
|
t.Fatalf("want ErrInvalid, got %v", err)
|
|
}
|
|
if got, _ := s.Task(id); got.Review != nil {
|
|
t.Fatalf("a mismatched review was recorded: %+v", got.Review)
|
|
}
|
|
if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaA}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestReviewEntryConditions(t *testing.T) {
|
|
project := registry.Project{ID: "p", QualityGate: "go test ./..."}
|
|
|
|
// Wrong phase.
|
|
s, id := phaseStore(t)
|
|
if _, err := EnterReview(s, project, id, evidence(shaA)); !errors.Is(err, ErrReviewNotEligible) {
|
|
t.Fatalf("frame phase: want ErrReviewNotEligible, got %v", err)
|
|
}
|
|
|
|
// Failing gate, unanchored commits, empty diff, and a gate that never ran.
|
|
s, id = atImplement(t, project)
|
|
bad := map[string]review.Evidence{
|
|
"failing gate": func() review.Evidence { e := evidence(shaA); e.GateExit = 1; return e }(),
|
|
"no result": func() review.Evidence { e := evidence(shaA); e.ResultSHA = "short"; return e }(),
|
|
"no base": func() review.Evidence { e := evidence(shaA); e.BaseSHA = ""; return e }(),
|
|
"no diff": func() review.Evidence { e := evidence(shaA); e.Diff = ""; return e }(),
|
|
"gate skipped": func() review.Evidence { e := evidence(shaA); e.GateCommand = ""; return e }(),
|
|
}
|
|
for name, ev := range bad {
|
|
if _, err := EnterReview(s, project, id, ev); !errors.Is(err, ErrReviewNotEligible) {
|
|
t.Fatalf("%s: want ErrReviewNotEligible, got %v", name, err)
|
|
}
|
|
}
|
|
|
|
// An unresolved question blocks entry.
|
|
lease(t, s, id)
|
|
if _, err := RequestHumanDecision(s, project, id, request("which behaviour is intended?")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := EnterReview(s, project, id, evidence(shaA)); !errors.Is(err, ErrReviewNotEligible) {
|
|
t.Fatalf("blocked task: want ErrReviewNotEligible, got %v", err)
|
|
}
|
|
}
|
|
|
|
// A review can only be sealed by a reviewing session, and only in a shape that
|
|
// is actually reviewable.
|
|
func TestReviewResultRejections(t *testing.T) {
|
|
project := registry.Project{ID: "p", QualityGate: "go test ./..."}
|
|
s, id := atImplement(t, project)
|
|
|
|
// Not in the review phase yet.
|
|
if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaA}); !errors.Is(err, domain.ErrInvalid) {
|
|
t.Fatalf("want ErrInvalid, got %v", err)
|
|
}
|
|
if _, err := EnterReview(s, project, id, evidence(shaA)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
long := strings.Repeat("x", 501)
|
|
bad := map[string]review.Result{
|
|
"no sha": {Findings: []review.Finding{finding("f1", review.Minor)}},
|
|
"short sha": {ResultSHA: "abc"},
|
|
"no id": {ResultSHA: shaA, Findings: []review.Finding{{Severity: review.Minor, File: "a.go", Claim: "c", Evidence: "e"}}},
|
|
"duplicate id": {ResultSHA: shaA, Findings: []review.Finding{finding("f1", review.Minor), finding("f1", review.Blocker)}},
|
|
"bad severity": {ResultSHA: shaA, Findings: []review.Finding{{ID: "f", Severity: "invalid", File: "a.go", Claim: "c", Evidence: "e"}}},
|
|
"absolute path": {ResultSHA: shaA, Findings: []review.Finding{{ID: "f", Severity: review.Minor, File: "/etc/passwd", Claim: "c", Evidence: "e"}}},
|
|
"no evidence": {ResultSHA: shaA, Findings: []review.Finding{{ID: "f", Severity: review.Minor, File: "a.go", Claim: "c"}}},
|
|
"essay": {ResultSHA: shaA, Findings: []review.Finding{{ID: "f", Severity: review.Minor, File: "a.go", Claim: long, Evidence: "e"}}},
|
|
"multiline": {ResultSHA: shaA, Findings: []review.Finding{{ID: "f", Severity: review.Minor, File: "a.go", Claim: "one\ntwo", Evidence: "e"}}},
|
|
}
|
|
for name, result := range bad {
|
|
if _, err := RecordReview(s, project, id, result); !errors.Is(err, domain.ErrInvalid) {
|
|
t.Fatalf("%s: want ErrInvalid, got %v", name, err)
|
|
}
|
|
}
|
|
// A rejected review left no trace.
|
|
if got, _ := s.Task(id); got.Review != nil {
|
|
t.Fatalf("a rejected review was recorded: %+v", got.Review)
|
|
}
|
|
// Too many findings is also a rejection.
|
|
flood := review.Result{ResultSHA: shaA}
|
|
for i := 0; i < 41; i++ {
|
|
flood.Findings = append(flood.Findings, finding(string(rune('a'+i%26))+strings.Repeat("z", i), review.Minor))
|
|
}
|
|
if _, err := RecordReview(s, project, id, flood); !errors.Is(err, domain.ErrInvalid) {
|
|
t.Fatalf("want ErrInvalid, got %v", err)
|
|
}
|
|
}
|