A task in retry backoff was filtered out before the candidate loop, so it
recorded no rejection at all: queued, apparently assignable, and silent. That is
the exact shape that made F5 take a live session to diagnose. It now reports
"retry backoff until <time>".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
F7, security. An unset surface token makes the middleware skip its check, so a
full-control surface with no credential is an open control plane rather than a
closed one. With ORCHESTRA_TUI_TOKEN unset, any LAN caller could lease, release,
complete or block any task by declaring one header, which is how this session's
manual leases were issued. authz.RequireCredentials now refuses startup instead
of logging. Web is exempt: Sessions makes its login mandatory.
F5, lifecycle. router.go's silent `continue` was the first bug, not the
predicate behind it. Every eligibility gate now records a router.Rejection with
task, herdr and reason, exposed at GET /v1/router/health, reset per pass. No
gate was weakened: a direct Store.Lease succeeding proves the lease path, not
that eligibility should have selected that worker.
F8, correctness. Reconcile iterated every configured source for every task, so a
task's external id was looked up in whatever repository each source pointed at.
Once two repositories share an issue number, an unrelated human comment becomes
an authoritative decision for the wrong task. Reconciliation is now bound to
task.Source, the provider:project identity the ingest stamped, and a source that
cannot prove it owns the task is skipped. A task with no matching source
reconciles to nothing and still launches, because nothing to import is not a
failure to read.
The integration fixture ingested from "jsonl" while reconciling from "gitea",
which is exactly the shape F8 makes impossible; it now ingests from the source
it reconciles.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found live on the first burn-in task. Every federated launch failed with
`effective intent: federation: 401 Unauthorized: unauthorized surface`, and the
task nacked back to queued.
GET /v1/tasks/<id>/intent was added so a worker renders its own launch
instruction from the reduced intent, but authz.HTTPWithSessions never got the
matching worker-path exemption. An unlabelled request defaults to the Web
surface, which is session-gated, so the one caller the endpoint exists for could
never reach it.
Twenty test packages passed throughout, because the authz tests covered the
surface capability table and not the worker path list. Only the live owner path
established this.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
The UI is a full control plane: it can create tasks, release or complete
them, and inject approval keystrokes into live panes. authz.HTTP did cover
it (an absent surface header defaults to Web), but the gate is opt-in and
the deployed env sets no tokens while binding all interfaces, so in
practice it was reachable unauthenticated from the LAN. Setting the token
alone did not work either: a browser cannot put a bearer token on a
document load, so the UI would 401 on index.html.
- ORCHESTRA_WEB_TOKEN is now mandatory; startup fails rather than silently
serving an open control plane.
- authz.Sessions issues random values stored SHA-256-hashed, with a TTL,
so a leaked snapshot yields nothing usable.
- POST /v1/ui/session verifies the token in constant time and returns it
as an HttpOnly, SameSite=Strict, Secure cookie. This is a presentable
form of the same credential, not a new authority.
- HTTPWithSessions accepts that cookie in place of the bearer token, and
only for the Web surface. The login endpoint and non-/v1/ GETs (the SPA
shell) are exempt by necessity; every /v1/ control path stays gated.
Note this is a breaking config change: .orchestra-config/orchestra.env
sets no tokens, so the service will not start until it does, and setting a
Web token newly gates the other /v1/ surfaces that default to Web.
AUDIT.md is reconciled against the code rather than against itself. B14,
B15 and B16 are closed with their evidence; B17 is closed on the worker
path only; the stale claim that B13 was open is corrected. Adds the
previously undocumented command channel and web UI, and files what that
implementation pass surfaced: federated approvals emit no event (B19), the
local capture revision is a timestamp rather than a change counter and can
silently defeat approvals (B20), the command queue never prunes (B21), the
ntfy token serves two unrelated purposes (S12), and a dead copy of the
authorization policy sits in main.go (S13).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01535A3Y8RtkAi8wYuWhtkEd
System means "the plane itself, in-process" (router, coordinator, adapters,
lease-expiry reclaim) and is unconditionally FullControl with no token
gate. But it was reachable straight from the X-Orchestra-Surface HTTP
header, both in authz.HTTP's token check and in main.go's own `surface`
closure (which every handler actually calls to authorize an event — it
re-parses the header independently of what the HTTP middleware resolved).
Since no deployment configures ORCHESTRA_SYSTEM_TOKEN (no legitimate HTTP
caller should ever need one), tokens[System] is always "", so the token
check was skipped entirely: any LAN request with
"X-Orchestra-Surface: system" got unauthenticated full control to emit any
event on any task.
Both the authz.HTTP middleware and main.go's `surface` closure now
downgrade System to Web before doing anything else with it, so the header
can never resolve to System over HTTP regardless of token config.
AUDIT.md B8.
Pre-existing uncommitted work found at session start: rotation now emits
anchor_sha on TaskReleased (previously silently dropped by store.Append
validation), multi-repo Gitea provider support, per-project git worktree
roots, and associated test coverage. Committing as a checkpoint before
starting remediation work tracked in AUDIT.md.