Repository navigation
feat(t12): durable coding lanes with task-bound belts (off by default) - #1520
Conversation
The orchestration pieces (leases, handoff gate, admission) had no caller that composed them into claim, make, check, integrate, refill. This adds that, on existing trinity_tasks columns, so it needs no DDL. - Fenced transitions: each is one transaction that appends a hash-chained decision receipt. metadata.t12.epoch is the fencing token. - Leases use expires_at and the existing trinity_reap_expired_claims cron. - Exact-file path locks are decided under one advisory lock. - A different lane re-runs the test in a fresh checkout, then removes the deliverable and requires the test to fail. - Lane rows avoid every status the RepID bridge polls, so this path cannot move a score. Publishing stops at a draft PR. Nothing arms it: T12_LANES_ENABLED, T12_LANES_ALLOW_EXEC and T12_LANES_PUBLISH are each off unless exactly true, and nothing schedules it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The negative control deleted the test, so it proved only that a file went missing and approved an empty test. Controls now revert the code change and apply breaks the task names to the code under test. - spec.context was unvalidated, so a task row could send any local file to a model. It now has the same fence as a write. - A dead host looped on one row and exhausted it. Blameless releases refund the claim, infra pauses the run, and an exhausted task gets a terminal state. - Checks no longer block the event loop and starve other lanes' heartbeats. - One dropped connection no longer kills the sibling lanes. - A commit this host cannot see is NOT_CHECKED, not a failed check. - dependsOn is refused: a maker bases on main, which lacks an unmerged parent. - Removed an epoch bump in recovery that mutation testing showed did nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A live run showed a free model overwrite two existing test files with its own shorter versions. The new tests passed, so the check would have approved deleting real coverage. The check now rejects any changed or deleted file unless the spec lists it in `overwrites`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found when a free-tier rate limit stopped a live run: each attempt left an empty t12/ branch on the clone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Groq 429 paused every lane for the whole run, and the wave's Cerebras step used an archived model (404), so there was no real second host. - T12_ROUTES is now the only set of cloud routes a lane can reach: nvidia, openrouter, cerebras, groq. A key being present never makes a host eligible. OpenRouter uses a free id and a zero price cap, so a paid model is refused. - A failed host gets a durable cooldown in agent_resource_claims, no DDL. A 429 still stops that wave; the task is released at no cost and its next claim routes to a host that is not cooling. - Every route attempt is receipted with provider, model, reason, status, cooldown and task id. - All routes cooling is a durable WAITING_FOR_CAPACITY state. It costs the task nothing and it is not offered again until a route returns. Checks continue. - together is removed from FREE_PROVIDERS, which gates spend in dev-codegen. Lane routing has its own order, so the default wave and B21 are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reasoning models spent their whole budget thinking and returned nothing, and those tokens came out of a limit the whole Groq organization shares. Each route now caps output, and the two gpt-oss routes use low reasoning effort. Measured on real calls: valid file JSON in about a second, in 702 and 544 tokens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mutation testing found this unpinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The registry invariant that every lane can reason rejected the HARNESS entry, and it was right: a re-run does no reasoning and takes no assignments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A filesystem-pinned test requires each tick loop to gate on the halt, and the lane runner did not. Lanes now check it before every step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…igin A lane worker runs model-written code, so it now refuses to start with any signing, finance, trading or admin credential, or on a database role that can bypass row security. A draft role confines it to the lane pool. Removing the import chain to the engine config means it no longer needs a service key. A second wake of a running lane leaves without doing anything, and a crashed lane's slot lapses by timer. Each receipt now says whether a person, a schedule, the system or recovery caused the decision. Proved on the real path: the CLI as separate OS processes under the restricted role, with real git and a real SIGKILL mid-task, recovering from durable state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The migration only created the role IF NOT EXISTS, so a pre-existing t12_lane_worker with BYPASSRLS kept it. Found by a mutation that survived. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efused All five maker failures in the three-lane run were raw newlines inside JSON strings, and the parser discarded the reason. The lease check is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…path A lane needs a live belt for this task and this claim before it can submit. The grant lives in principal_grants, the worker role holds no privilege on it, and a policy function decides. Denials are recorded where the worker cannot edit them, and a denied belt never reaches a model. Draft; the owner applies it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… document belts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Strix Security Review✅ No open security findings. All previously reported findings have been resolved. PR summary: Adds durable T12 coding lanes (claim, make, independently check, integrate, refill) on the existing `trinity_tasks` table, with leases and fencing tokens, exact-file path locks, hash-chained decision receipts, free-only model routing with per-provider cooldowns, and a draft migration that creates a restricted `t12_lane_worker` Postgres role whose row policy requires a task-bound belt before a submission can be written. Everything is off by default and unscheduled, and the most a lane can publish is a draft PR. Review summaryI reviewed the full PR — the lane store, git adapters, runner, router, worker profile, the draft worker-role migration, and the free-provider change — and traced the worker-controlled values into every SQL, git and filesystem sink. The one prior finding, the migration failing open because its sanitising guard aborted only its own DO block, is fixed: the file is now a single BEGIN/COMMIT transaction, so a refusal stops the whole apply, and the new test runs it statement-by-statement as 13 resolved findings
Ruled out (5)
Review history (7)
Review 7 on this PR · Reviewed by Strix |
prod-fixture-guard refused it: a comment said its definitions were copied from production, and a made-up row used a real agent's name. It holds table definitions and invented rows only, and now says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A compromised worker could set a lane row to a status the RepID bridge scores, so the row policy now refuses those and the role can write only lane columns. Re-applying the migration now strips memberships and object grants, not just attributes. Publishing and running model-written code cannot share a process. The boot refusal matches credential name families instead of fixed spellings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The worker could call SECURITY DEFINER functions open to PUBLIC, so the migration closes PUBLIC on exactly those and re-names every other role, and the CLI refuses to start if a worker can still reach one. Checkout no longer locks or logs tasks outside the pool, repeated denials write one row, and a belt can only name a lane. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An owner skips row policies unless they are forced, so a non-superuser owner passed the check and escaped every lane policy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ified Publish pushed a branch and commit read from a row the worker can write. Postgres now accepts only this task's branch and full object ids, and the checker and publisher check them again. A refused publish ends the task. The verdict column no longer says verified_done for a same-controller re-run, and a restricted worker may not hold a key whose account has paid credit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g role REPLICATION can decode the write-ahead log past row security, and a default privilege would grant the role every object created later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run with psql -f, the ownership refusal stopped only its own block and the rest of the file still applied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
I reviewed the full PR — the lane store, git adapters, runner, router, worker profile, the draft worker-role migration, and the free-provider change — and traced the worker-controlled values into every SQL, git and filesystem sink. The one prior finding, the migration failing open because its sanitising guard aborted only its own DO block, is fixed: the file is now a single BEGIN/COMMIT transaction, so a refusal stops the whole apply, and the new test runs it statement-by-statement as psql -f would. No new security issues were confirmed. The remaining observations are hardening gaps rather than vulnerabilities: check() trusts spec.verify.controls[].file without re-validating it, but the only actor who can author that spec is the lane worker role, which already has the same filesystem write on the same host and already executes arbitrary code there under T12_LANES_ALLOW_EXEC; and the free-only guarantee for the paid OpenRouter route is enforced by the CLI's credential refusal rather than by the routing table itself.
Reviewed by Strix
Configure security review settings
Durable coding lanes for T12: claim, make, independently check, integrate, refill. They run on
trinity_taskswith no new table. Off by default and not scheduled. The most a lane can publish is a draft PR, and a human merges.What this adds
src/orchestration/t12-lane-*.ts,scripts/t12/lanes.ts): leases with a fencing token, exact-file path locks, hash-chained decision receipts, automatic refill, duplicate-wake refusal, and the emergency halt.Needs the owner before it can run against production
migrations/2026-11-08_t12_lane_worker_role_DRAFT.sqlis a draft and has not been applied. Set the role's login password separately.Review it as a change to more than the lanes. It closes PUBLIC EXECUTE on existing SECURITY DEFINER functions in
publicthat are still open to PUBLIC, because PUBLIC cannot exclude one role. Every other existing role is granted EXECUTE by name first, so none of them gains or loses access. A role created later would not inherit access to those functions through PUBLIC.Limits
same_controller_rerun. Controls are only as strong as the breaks a task names.Evidence
1d952b86: 12,958 passed, 150 failed. By test name against main at1869bd40, the only new failures are two 5-second timeouts intests/hal-crag-corroboration. That suite already fails on main and varied between 1, 2 and 4 failures across runs today. This branch changes no HAL file.8855a180: 437 passed. The full-suite figure above predates the review fixes.The Postgres test blocks need a throwaway database and report as skipped without one. The variable is named at the top of each block.
🤖 Generated with Claude Code