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>
This commit is contained in:
2026-08-26 18:31:20 +04:00
parent 97a9c65302
commit 7f12c7fc37
78 changed files with 16417 additions and 352 deletions
+90
View File
@@ -186,3 +186,93 @@ func TestSessionRevoke(t *testing.T) {
t.Fatal("revoked session must not be valid")
}
}
// The agent boundary: an agent may perform work and request lifecycle changes,
// never perform one. Both halves are proven here — the bus refuses the event
// types Orchestra owns, and the middleware refuses their endpoints — because
// an agent that reaches a handler with a valid token would otherwise be
// indistinguishable from the browser operator.
func TestAgentSurfaceCannotMutateLifecycle(t *testing.T) {
for _, typ := range []string{
"TaskLeased", "TaskReleased", "TaskCompleted", "TaskBlocked",
"WorkPhaseChanged", "ReviewRecorded", "TaskSubmitted",
"TaskChangesRequested", "HumanDecisionRecorded", "ApprovalGranted",
} {
if Agent.CanEmit(typ) {
t.Errorf("agent surface emitted %s", typ)
}
if err := AuthorizeEvent(Agent, typ); err == nil {
t.Errorf("AuthorizeEvent(agent, %s) allowed", typ)
}
}
if !Agent.CanEmit("ApprovalRequested") {
t.Fatal("agent surface cannot ask")
}
if !Agent.CanRead() {
t.Fatal("agent surface cannot read")
}
}
func TestAgentSurfaceReachesOnlyRequestEndpoints(t *testing.T) {
tokens := map[Surface]string{Agent: "agent-secret"}
h := HTTPWithSessions(tokens, &Sessions{}, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
do := func(method, path, auth string) int {
r := httptest.NewRequest(method, path, nil)
r.Header.Set("X-Orchestra-Surface", "agent")
if auth != "" {
r.Header.Set("Authorization", auth)
}
w := httptest.NewRecorder()
h.ServeHTTP(w, r)
return w.Code
}
const bearer = "Bearer agent-secret"
for _, tc := range []struct {
path string
want int
}{
// May ask.
{"/v1/tasks/t1/decision-request", http.StatusNoContent},
{"/v1/tasks/t1/deferred", http.StatusNoContent},
{"/v1/tasks/t1/approval", http.StatusNoContent},
// May not act.
{"/v1/tasks/t1/phase", http.StatusForbidden},
{"/v1/tasks/t1/review", http.StatusForbidden},
{"/v1/tasks/t1/submission", http.StatusForbidden},
{"/v1/tasks/t1/complete", http.StatusForbidden},
{"/v1/tasks/t1/lease", http.StatusForbidden},
{"/v1/tasks/t1/release", http.StatusForbidden},
{"/v1/tasks/t1/approval/grant", http.StatusForbidden},
{"/v1/standup/apply", http.StatusForbidden},
{"/v1/artifacts", http.StatusForbidden},
} {
if got := do(http.MethodPost, tc.path, bearer); got != tc.want {
t.Errorf("POST %s = %d, want %d", tc.path, got, tc.want)
}
}
// The token still gates the surface: no credential, no request endpoint.
if got := do(http.MethodPost, "/v1/tasks/t1/decision-request", ""); got != http.StatusUnauthorized {
t.Errorf("uncredentialed agent = %d, want 401", got)
}
// A session cookie must not authenticate an agent, and an agent token must
// not authenticate the browser surface.
if got := do(http.MethodGet, "/v1/tasks", "Bearer wrong"); got != http.StatusUnauthorized {
t.Errorf("wrong agent token = %d, want 401", got)
}
}
// The harness turn endpoint authenticates its own bearer token in the handler.
// Before this exemption it defaulted to the session-gated Web surface, so every
// harness call returned 401 in any deployment with web credentials configured.
func TestHarnessTurnBypassesSurfaceGate(t *testing.T) {
h := HTTPWithSessions(map[Surface]string{}, &Sessions{}, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
w := httptest.NewRecorder()
h.ServeHTTP(w, httptest.NewRequest(http.MethodPost, HarnessTurnPath, nil))
if w.Code != http.StatusNoContent {
t.Fatalf("harness turn = %d, want 204", w.Code)
}
}