Files
orchestra/internal/workphase/workphase_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

102 lines
3.0 KiB
Go

package workphase
import (
"strings"
"testing"
)
func research() Research {
return Research{
Findings: []Finding{{Claim: "attribution runs per figure", Evidence: "internal/attr/attr.go:88"}},
Code: []CodePath{{Path: "internal/attr/attr.go", Why: "aggregation happens here"}},
Invariants: []string{"identity semantics must not change"},
DeadEnds: []DeadEnd{{Tried: "figure plurality", WhyFailed: "no measured gain"}},
}
}
func plan() Plan {
return Plan{
Changes: []Change{{Target: "internal/attr/attr.go", Intent: "aggregate per person"}},
Verification: []string{"go test ./internal/attr/"},
}
}
func TestRoundTrip(t *testing.T) {
b, err := Encode(research())
if err != nil {
t.Fatal(err)
}
got, err := DecodeResearch(b)
if err != nil {
t.Fatal(err)
}
if got.Findings[0].Claim != "attribution runs per figure" || got.DeadEnds[0].Tried != "figure plurality" {
t.Fatalf("round trip lost content: %+v", got)
}
pb, err := Encode(plan())
if err != nil {
t.Fatal(err)
}
gotPlan, err := DecodePlan(pb)
if err != nil {
t.Fatal(err)
}
if gotPlan.Changes[0].Target != "internal/attr/attr.go" {
t.Fatalf("round trip lost content: %+v", gotPlan)
}
}
// The bound is the point. An artifact that can hold a transcript is a
// transcript, and the next phase pays for reading it.
func TestBoundsRejectUnboundedArtifacts(t *testing.T) {
cases := map[string]func() error{
"no findings": func() error { return Research{}.Validate() },
"no evidence": func() error { return Research{Findings: []Finding{{Claim: "x"}}}.Validate() },
"multiline claim": func() error { return Research{Findings: []Finding{{Claim: "a\nb", Evidence: "e"}}}.Validate() },
"long claim": func() error {
return Research{Findings: []Finding{{Claim: strings.Repeat("x", 501), Evidence: "e"}}}.Validate()
},
"too many findings": func() error {
r := Research{}
for i := 0; i < 65; i++ {
r.Findings = append(r.Findings, Finding{Claim: "c", Evidence: "e"})
}
return r.Validate()
},
"absolute path": func() error {
r := research()
r.Code = []CodePath{{Path: "/etc/passwd", Why: "no"}}
return r.Validate()
},
"blank invariant": func() error {
r := research()
r.Invariants = []string{" "}
return r.Validate()
},
"no changes": func() error { return Plan{}.Validate() },
"no change intent": func() error { return Plan{Changes: []Change{{Target: "x"}}}.Validate() },
"multiline risk": func() error {
p := plan()
p.Risks = []string{"a\nb"}
return p.Validate()
},
}
for name, fn := range cases {
if err := fn(); err == nil {
t.Fatalf("%s: expected rejection", name)
}
}
}
func TestEncodeRejectsInvalid(t *testing.T) {
if _, err := Encode(Research{}); err == nil {
t.Fatal("Encode must validate before sealing")
}
if _, err := DecodeResearch([]byte(`{"findings":[]}`)); err == nil {
t.Fatal("Decode must validate")
}
if _, err := DecodePlan([]byte(`not json`)); err == nil {
t.Fatal("Decode must reject non-JSON")
}
}