Files
orchestra/internal/operations/review_test.go
T
kami fb7135e1d9 Refuse a plan command outside project policy when the plan seals
The brief tells the planner "a command outside its policy is refused when you
seal, not later". It was not. The only caller of VerificationPolicy.Allows was
PlanPhaseCommands, which runs when the implementer asks to verify: one phase,
one session and one rotation after the planner could have fixed it.

Run 9 sealed ["bash", "scripts/test_healthcheck.sh"] against a policy that
allows neither shape, and the phase request was accepted.

The check now runs beside citation resolution, on the coordinator, where the
project is already in scope. A project with no verification policy can still
seal a plan; it cannot seal one that declares run: lines, which matches what
an absent policy already meant at verification time.

Test fixtures gained a policy for the same reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1
2026-08-28 15:27:39 +04:00

219 lines
8.9 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, planDoc); 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 ./...", Verification: registry.VerificationPolicy{Allowed: [][]string{{"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 ./...", Verification: registry.VerificationPolicy{Allowed: [][]string{{"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 ./...", Verification: registry.VerificationPolicy{Allowed: [][]string{{"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 ./...", Verification: registry.VerificationPolicy{Allowed: [][]string{{"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 ./...", Verification: registry.VerificationPolicy{Allowed: [][]string{{"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)
}
}