chore: delete and rebuild the harness - #676
Conversation
… /ticket) Replaces the orchestration core with the design approved on 2026-08-04. - launch-worker.mjs 1477 -> 263: headless only, stdin closed, foreground watchdog owning a 45 min hard ceiling and a 10 min no-progress timer. The Windows block (resolveOnPath, NPM_SHIM_SCRIPT, headlessInvocation) is a byte-identical port: it encodes CVE-2024-27980, the ORB-88 mangled prompt class, and openai/codex#20919. - verify-delivery.mjs, new: the sole authority for "delivered", derived only from git and gh. A worker exit code proves nothing. - orchestrate 770 -> 358: one ticket, no --sleep, no waves. Adds the D32 always-loaded byte report and the D33 skill-collision assertion. - pr-review 766 -> ~380: findings frozen after round 1, hard cap of two rounds, blocking/non-blocking severity floor, five review subagents absorbed into the rubric plus a new test-quality dimension. - ticket: /feature merged in. Cardinality is now an output of the analysis, so one message listing several problems yields N tickets. - orchestrator-guardrails: quote-aware segmentation. It no longer blocks a grep whose pattern contains "codex", nor "codex --version". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rubric.md 430 -> 257 while carrying strictly more coverage: the security, contract-alignment, parity, i18n and design subagents (327 lines) fold in as dimensions, plus a new test-quality dimension built from the vendored tdd skill's tautological-test definition. Drops the dimensions that Lint and Type Check already fail on. Re-flagging a gate's own finding by hand is noise, and core.md calls that the most-broken rule in the repo. Scoped around what three gates ACTUALLY do, read from the workflow YAML rather than inferred from their names: Cross-Platform Parity compares only per-platform file counts, Contract Drift regenerates from orbit-api main and is blind to a paired in-flight API PR, and Design Token Guard greps three patterns. The judgement each one does not make stays reviewer-owned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Step 0 asserted "clean, on main, up to date" before step 1 had resolved which repository the ticket targets, so the placeholder could only mean the orchestrator's own checkout. That is the wrong tree. D17 pins the session to orbit-ui-mobile no matter where the work lands, and the worktree branches from the TARGET repo's main. Checking this checkout would refuse a legitimate run (a repo:api ticket driven from any non-main branch here) and pass a dangerous one (a repo:api ticket whose own main is dirty or behind). Split into 0a, environment only, which needs no ticket, and 0b, the target repo, which runs after step 1 and before the worktree is created. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two of the three 0b conditions are one safe idempotent command away, and refusing a run because a repo is one commit behind is friction with no safety in it. That pattern is what froze the product. behind origin/main -> fetch + merge --ff-only not on main -> git switch main dirty tree -> STOP, unchanged The dirty tree stays a hard stop and is never auto-repaired. Uncommitted work is somebody's unsaved thinking, and stashing hides it somewhere they will not look. Carve-out: never switch the branch of the repository the session is running from. Doing so swaps this skill file, orchestrator.json and the tools out mid-run, and the orchestrator would finish reading a different harness than it started with. Every repair is printed with its command. A silent repair starts a run from a state nobody chose. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CLAUDE.md and core.md load on every turn, so a stale instruction there is
not a docs nit, it is a wrong order given to every session. All three were
false as of the harness rebuild:
"after /next, run /orchestrate ORB-N --only" -> /next and --only are gone
"guards.yml + merge-sweep gate merges" -> merge-sweep is deleted,
and only Thomas merges
core.md naming /feature -> merged into /ticket
Not the full CLAUDE.md trim, which is a later step. These three would have
sent the next session after tooling that no longer exists.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removes the layer the rebuild replaces. Every deletion stands on its own
evidence: it serves a deleted feature, is provably inert, was shadowed, or
checks the harness rather than the product.
29 tools + 25 test files + 7 fixtures unattended merge, wave planning,
the automation budget, the strike
ledger, TUI worker driving, and
nine harness self-checks
10 skills /next /watch /rollup /quota
/make-tool /android-generate
/dep-sweep /profile /commit-sweep
/llm-council
4 review subagents absorbed into pr-review/rubric.md
1 hook forbid-raw-repo-tool-surfacing
6 CI workflows plus 7 jobs cut from guards.yml,
397 -> 139 lines, leaving exactly
the five surviving required checks
Three things the deletion lists got wrong, kept instead:
- forbid-raw-linear-mutation and forbid-ef-migration-raw-index were lumped
in with the harness self-checks. They guard Linear data integrity and EF
migration idempotency, which is product safety, not harness bookkeeping.
- design-reviewer was counted among the subagents absorbed into the rubric,
but visual.yml invokes it as a CI job. Deleting it breaks that job.
- calibration.json and two fixture directories lost their only readers, so
they go as orphans rather than being patched.
Closes a real hole found while rewriting the hook suite: httpie takes its
method positionally, so `http PUT .../pulls/{n}/merge` was ALLOWED while the
byte-identical curl call was blocked. The admin-merge ban is the prohibition
everything else rests on, and it had a documented bypass.
Also stops rules-linear.mjs handing out `node tools/new-ticket.mjs` in its
block message, a command that no longer exists.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two suites now prove the surviving harness rather than the deleted one. test-tools.mjs 261 -> 201, _harness.mjs 1158 -> 318, and three new test files for the tools that changed today. 158 assertions, ~53s, hermetic: no network, no Linear, no model invocation. The coverage contract now fails in BOTH directions, so a row naming a tool that no longer exists is a failure. That gap is how 29 rows outlived their tools. test-hooks.mjs 1538 -> 300, 118 assertions. It asserts settings.json and the hooks directory agree in both directions, which is the check that would have caught this morning's breakage: the suite imported a hook that had just been deleted and could not even parse. Both suites are proven able to go RED, not just green. Always-loaded context, 25,229 -> 14,509 bytes: CLAUDE.md 10,449 -> 6,654 core.md 3,015 -> 2,197 AGENTS.md 11,765 -> 5,658 hot.md 8,668 -> 5,968 (brain vault) AGENTS.md cannot reach its 4,000-byte target: the "Never assume an external interface" section it must keep intact is itself exactly 4,000 bytes, so that is the floor. It survives byte-identical, both measured failure cases included. What went instead was the worker contract, now injected into every prompt by compose-prompt.mjs, and the Code Review Rules, whose five rules all live in rubric.md dimensions 7 and 8, core.md rule 4, and standard 8. Adds three code standards Thomas approved: simplest implementation, layered growth, and lean on existing dependencies. Not the fourth, which would have inverted the append-only DTO contract that exists because mobile lags behind the Play store. launch-worker.mjs now exports ORCA_CLI_COMMAND to the worker. Measured on ORB-87: the worker resolved bare `orca`, which is not on PATH, and correctly refused rather than falling through. It delivered nothing in 54 seconds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1fad3b514a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| try { | ||
| process.kill(-pid) | ||
| } catch { | ||
| /* a tree that is already gone still ends this launch; the exit handler reports what happened */ |
There was a problem hiding this comment.
Start a process group before signaling it
On POSIX, spawn() does not create a new process group unless detached: true is set, so process.kill(-pid) targets a nonexistent group and the catch silently ignores ESRCH. Running node tools/test-tools.mjs confirmed that the configured 0.02-minute timeout did not terminate the 60-second sleeper; it only reported success after the sleeper exited naturally. A genuinely hung worker therefore survives both watchdog clocks indefinitely.
AGENTS.md reference: AGENTS.md:L15-L24
Useful? React with 👍 / 👎.
| checks.pushed = { pass: localOnly === 0, observed: localOnly } | ||
| if (!checks.pushed.pass) emit("UNPUSHED") | ||
|
|
||
| const listed = run(GH, ["pr", "list", "--head", branch, "--json", "number,url,headRefOid,additions,deletions"], githubCwd) |
There was a problem hiding this comment.
Verify that the pull request links the ticket
When a worker opens one current, in-cap PR without mentioning ORB-N, this query still finds it and the script emits DELIVERED, even though the composed work order requires a PR that links the issue. gh pr list --help confirms that --head filters only the head branch and that the default state is open, so nothing in this invocation ties the PR to issue; the sole delivery authority must independently verify that association.
Useful? React with 👍 / 👎.
| const size = pullRequest.additions + pullRequest.deletions | ||
| checks.diffSize = { pass: size <= DIFF_CAP, observed: size, cap: DIFF_CAP } | ||
| if (!checks.diffSize.pass) emit("OVERSIZE") |
There was a problem hiding this comment.
Enforce the affected-file cap before declaring delivery
A worker can change 9 or more files while staying under 400 added/deleted lines, and this verifier will still return DELIVERED. The new configuration and composed prompt call caps.affectedFiles a hard cap, but the sole delivery authority checks only total line count, so the post-work scope gate does not enforce one of its two promised limits.
Useful? React with 👍 / 👎.
| ```bash | ||
| bash tools/merge-sweep-cov.sh \ | ||
| --issue <pr-number>=ORB-N \ | ||
| --reviewed-through <pr-number>=<ISO-8601-timestamp> \ | ||
| <owner/repo> <pr-number> | ||
| node tools/teardown-worktree.mjs --issue ORB-N --worktree <p> | ||
| ``` |
There was a problem hiding this comment.
Pass exactly one selector to teardown
After a PR is merged, the documented step invokes teardown with both --issue and --worktree, but teardown-worktree.mjs explicitly requires exactly one selector and exits 2 when both are present. Following /orchestrate verbatim therefore makes every completed run fail at cleanup and leaves its Orca worktree and local branch behind.
Useful? React with 👍 / 👎.
| @@ -1,477 +0,0 @@ | |||
| #!/usr/bin/env node | |||
There was a problem hiding this comment.
Remove stale audit calls before deleting the validator
Deleting this executable breaks the ticket-emission phase of five retained workflows: a commit-tree-wide git grep still finds mandatory node tools/check-ticket.mjs --file calls in audit-code-quality, audit-performance, audit-security, audit-tests, and prod-readiness. Each workflow now reaches that command and stops with MODULE_NOT_FOUND; update those callers to the rebuilt validation flow as part of the deletion.
Useful? React with 👍 / 👎.
| @@ -1,208 +0,0 @@ | |||
| #!/usr/bin/env node | |||
There was a problem hiding this comment.
Preserve the registered redesign coverage command
The root package.json still registers redesign:coverage as node tools/redesign-coverage.mjs, and a repo-wide search finds no replacement implementation. After this deletion, npm run redesign:coverage fails immediately with MODULE_NOT_FOUND, removing the documented check for unclaimed redesign surfaces and orphaned UI files; either retain the tool or update the package command to its replacement.
Useful? React with 👍 / 👎.
Five were broken on first use. Verified each against the tree before fixing. - teardown was documented with BOTH --issue and --worktree, and the tool exits 2 on both. Following /orchestrate verbatim made every completed run fail at cleanup and leave its worktree behind. My error, in two places. - package.json still ran `npm run redesign:coverage` against a deleted tool. - Five skills still called `tools/check-ticket.mjs`: audit-code-quality, audit-performance, audit-security, audit-tests, prod-readiness. Each would have stopped with MODULE_NOT_FOUND at its ticket-emission phase. They now re-read the draft against the 6.2 template instead. - verify-delivery enforced only ONE of the two promised caps. A worker could touch 20 files while staying under 400 lines and still read DELIVERED. - verify-delivery never checked that the pull request names the ticket: `--head` filters on the branch alone, so a PR that never mentions ORB-N passed the sole delivery authority. New UNLINKED_PR verdict. - process.kill(-pid) signalled a process group that spawn never created, so the POSIX watchdog was a silent no-op. Fixed with detached on POSIX only; Windows keeps taskkill /T, where detached would spawn a console window. The seventh finding, that the watchdog does not kill a hung worker, is NOT reproducible: the suite runs a 60-second sleeper twice and finishes in 49 seconds asserting KILLED_NO_PROGRESS and KILLED_HARD_CEILING. A sleeper that survived could not exit inside that window. The POSIX branch was genuinely dead, which is the defect underneath it, and is fixed above. The gh fields title, body and files were read off a real response before being used. `files` truncates at 100 entries, so a length of 100 is reported as "at least 100" rather than as a count that would be a lie. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
* chore: ignore agent scratch files by shape, not by name Seven session-transcript parsers accumulated at the repo root on 2026-08-04 (.orca-extract, .sess-now, .session-durable and four more), and eleven of the same class in iqpay-api. They are throwaway scripts an agent wrote while analysing a session, and tools/README.md already says a throwaway belongs in the scratchpad and never in a repo. That rule was prose, so it decayed. The tell is the line this replaces: .tmp_session_text.txt was added by exact filename after an earlier instance, which fixed one file and left the shape open. Ignores the shape instead: .tmp-*, .tmp_*, .sess-*, .session-*, .orca-*. Verified no tracked file is newly ignored: the count of tracked-but-ignored paths is 5 both before and after, all pre-existing ORB-116 screenshot artifacts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: delete stale harness artifacts the rebuild left behind Every deletion below is a file whose reader was removed by the harness rebuild (#676) or a file no reader ever had. Each was grepped across all three Orbit repos and the brain vault before it went. Five baselines and snapshots whose reader no longer exists: tools/lockstep-declarations.json read by tools/check-lockstep.mjs tools/context-budget.json read by tools/check-context-budget.mjs tools/on-demand-budget.json read by tools/check-context-budget.mjs tools/archaeology-baseline.json read by tools/check-archaeology.mjs tools/required-gates.json read by tools/check-required-gates.mjs .claude/linear-team-labels.json read by the tier-labels guards job None of those five tools is on disk. required-gates.json names its own missing reader in its "note" field. Two of them had also drifted into nonsense: context-budget.json claims CLAUDE.md is 10,504 bytes when it is 6,654, and on-demand-budget.json claims orchestrate/SKILL.md is 53,533 bytes when it is 19,024. Both numbers survived only because nothing reads them. skills-lock.json, whose entire content was {"version":1,"skills":{}}. The calibration job that wrote it is gone. Two mocks that are literally `export {}` and are absent from every alias map in apps/mobile/vitest.config.ts: apps/mobile/test-mocks/shared-empty.ts apps/mobile/test-mocks/shared-types.ts Five ORB-116 artifacts untracked, not deleted from disk. .gitignore:72 already ignores .artifacts/, which capture-surfaces.mjs regenerates; these five were committed once in #636 and have been both tracked and ignored ever since. The ignore rule is right, so the tracking was the error. Dead references in two files. guards.yml's header listed thirteen jobs when the file defines five, naming context-budget, archaeology, frontmatter, harness, lockstep, calibration and tier-labels, all deleted, plus three escape-hatch labels with no job left to honour them. second-opinion/SKILL.md still described an unattended run as a `--sleep` run; that flag no longer exists. Also carries the unpushed .gitignore commit that ignores agent scratch by shape, since it gates the same class of file this sweep found. Kept, having tried to prove them dead and failed: mutation.yml (696 runs), perf.yml (82), visual.yml (96), promote-prod.yml (5, all green, dispatch-only), .claude/mcp.json.example (a template to copy), .agents/skills/* (Codex-side pointers), api.generated.ts (a Contract Drift diff target, documented as never imported), and mono-toggle.tsx on both platforms, which has no importer but is the settings toggle the Figma handoff specifies. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: delete MonoToggle, close the scratch ignore gap, unbreak the mobile lint hook MonoToggle was implemented on both platforms and wired to nothing. No file in either app imports it. Its only appearances anywhere were three coverage exclusion lists, which record that it was never tested because it was never used, not that anything uses it. Thomas confirmed it is not the intended settings toggle, so it goes with everything its deletion orphaned: apps/web/components/ui/mono-toggle.tsx apps/mobile/components/ui/mono-toggle.tsx its entry in apps/web/vitest.config.ts and apps/mobile/vitest.config.ts both entries in sonar.coverage.exclusions, plus the prose naming it common.toggleOn and common.toggleOff in en.json and pt-BR.json Those two locale keys had no other consumer. They land in both files in the same edit, as the parity rule requires. The Figma handoff still defines MonoToggle, so the design source is unchanged; only the unused code goes. .gitignore gains .agent-scratch-*. The commit that added the scratch patterns covers .tmp-*, .tmp_*, .sess-*, .session-* and .orca-*, and the untracked file that motivated it was .agent-scratch-extract.mjs, which matches none of them. The gate did not cover its own trigger case. lefthook.yml loses --fix from mobile-eslint, because that hook could not pass. Measured: `expo lint` exits 0, `expo lint --fix` exits 2. `expo lint` ignores its file arguments and walks the whole app, so --fix repaired all 718 suppressed local/spacing-scale violations across 65 unstaged files, which orphaned those same 718 suppressions and tripped eslint's stale-suppression error. A hook that mass-edits files nobody staged is a hazard even when it exits 0. Pruning the suppressions does not help: nothing is stale until --fix creates the staleness. stage_fixed went with it, since nothing is fixed now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: regenerate the architecture map after the toggle key removal Architecture map drift went red on PR #679 (run 30943557070). architecture.json and architecture.html are generated and committed, and arch-map.yml regenerates then fails on any diff, so deleting common.toggleOn and common.toggleOff had to land in the map in the same PR. Regenerated with the workflow's own command, node tools/arch-map.mjs. The diff is exactly those two i18n keys and nothing else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wires the three tools from the parent PR into the contract. /orchestrate now takes one ticket, several, a project, or --auto, and grows --sleep, --parallel and --auto. One ticket with no flags behaves exactly as it did. --sleep opens pull requests and never merges one. Every piece of signing, provenance, ledger and merge-sweep machinery the OLD --sleep needed stays deleted, because nothing acts on a receipt. What makes an unattended run trustworthy now is verify-delivery.mjs, which reads only artifacts and never a worker's self-report. That is what was missing the first time. Stacking, because nothing merges overnight: a ticket whose blocker is in the same queue branches from its blocker's branch and targets it, since main will not contain the blocker's work until morning. --base is passed to verify-delivery too, or the size caps read the parent's commits as this ticket's. A stack lives in one repository, so cross-repo blockers stay separate pull requests and the api one deploys first. New step 12 clears the Codex reviewer's threads, which nothing has ever read: 8 threads opened across #676, #680 and #681, 8 still unresolved, all merged. The verdict comes from the presence of a review, never the thread count, and NO_REVIEW is never reported as clean. Every resolve replies first. One fix round, then re-verify and post @codex review explicitly, because the bot reviews on open and on request but NEVER on a push: #676's only review landed at 17:23:33Z while commits ran to 17:35:14Z. Two jump references changed MEANING, not just digits. "0 blocking -> step 12" and "all CLOSED -> step 12" used to mean "hand over" and now mean "go to the bot pass"; a mechanical renumber would have skipped the new step on the cleanest and most common path. The OPEN branch reaches it too, report-only. Preflight 0a now asserts the ACTIVE gh account owns the target remote, not merely that some account is logged in. This run hit the exact failure it guards: a push refused for thomas-luizon_iqpay after the work was done. Preflight 0b runs once per distinct repo rather than once per ticket, so concurrent fetch and merge --ff-only cannot race on .git/index. The visual layer is deliberately absent. A visible-effect ticket runs, opens its pull request, and is NOT moved to In Review: the run prints "visual check owed" and Thomas looks with /dev-server. The screenshots existed to prove correctness before a machine merged, and no machine merges. Amends the §5.3 round cap to scope "round" to the frozen cross-vendor list, or the bot fix round reads as a forbidden third round. Amends the merge prohibition to say what it means: never merges UNASKED. Thomas asking for a merge in a later turn is the intended path; --admin, the raw REST and GraphQL paths, force pushes and pushes to main stay blocked in every case. caps.parallelTickets is 3, not 8: each worktree is a full install, build and test run plus its own model session. node tools/test-tools.mjs ORBIT TOOLS GATE OK node .claude/hooks/test-hooks.mjs ORBIT HOOKS OK node tools/check-dashes.mjs clean Refs ORB-179, ORB-180. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…m it never happens #676 reviewed once and never again while commits kept landing. #682 reviewed the old head, took a push, and reviewed the new head seven minutes later. The blanket 'never on a push' claim was written from #676 alone and #682 falsified it the same day. Nothing about the code changes: comparing review.commit.oid to headRefOid is what makes the verdict correct under either behaviour, which is the point. Only the claim is corrected, because a run that trusts 'never' would skip the freshness check as redundant. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wires the three tools from the parent PR into the contract. /orchestrate now takes one ticket, several, a project, or --auto, and grows --sleep, --parallel and --auto. One ticket with no flags behaves exactly as it did. --sleep opens pull requests and never merges one. Every piece of signing, provenance, ledger and merge-sweep machinery the OLD --sleep needed stays deleted, because nothing acts on a receipt. What makes an unattended run trustworthy now is verify-delivery.mjs, which reads only artifacts and never a worker's self-report. That is what was missing the first time. Stacking, because nothing merges overnight: a ticket whose blocker is in the same queue branches from its blocker's branch and targets it, since main will not contain the blocker's work until morning. --base is passed to verify-delivery too, or the size caps read the parent's commits as this ticket's. A stack lives in one repository, so cross-repo blockers stay separate pull requests and the api one deploys first. New step 12 clears the Codex reviewer's threads, which nothing has ever read: 8 threads opened across #676, #680 and #681, 8 still unresolved, all merged. The verdict comes from the presence of a review, never the thread count, and NO_REVIEW is never reported as clean. Every resolve replies first. One fix round, then re-verify and post @codex review explicitly, because the bot reviews on open and on request but NEVER on a push: #676's only review landed at 17:23:33Z while commits ran to 17:35:14Z. Two jump references changed MEANING, not just digits. "0 blocking -> step 12" and "all CLOSED -> step 12" used to mean "hand over" and now mean "go to the bot pass"; a mechanical renumber would have skipped the new step on the cleanest and most common path. The OPEN branch reaches it too, report-only. Preflight 0a now asserts the ACTIVE gh account owns the target remote, not merely that some account is logged in. This run hit the exact failure it guards: a push refused for thomas-luizon_iqpay after the work was done. Preflight 0b runs once per distinct repo rather than once per ticket, so concurrent fetch and merge --ff-only cannot race on .git/index. The visual layer is deliberately absent. A visible-effect ticket runs, opens its pull request, and is NOT moved to In Review: the run prints "visual check owed" and Thomas looks with /dev-server. The screenshots existed to prove correctness before a machine merged, and no machine merges. Amends the §5.3 round cap to scope "round" to the frozen cross-vendor list, or the bot fix round reads as a forbidden third round. Amends the merge prohibition to say what it means: never merges UNASKED. Thomas asking for a merge in a later turn is the intended path; --admin, the raw REST and GraphQL paths, force pushes and pushes to main stay blocked in every case. caps.parallelTickets is 3, not 8: each worktree is a full install, build and test run plus its own model session. node tools/test-tools.mjs ORBIT TOOLS GATE OK node .claude/hooks/test-hooks.mjs ORBIT HOOKS OK node tools/check-dashes.mjs clean Refs ORB-179, ORB-180. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The step 12 text claimed the bot never reviews on a push, written from #676 alone. #682 falsified it the same day: it reviewed the old head, took a push, and reviewed the new head seven minutes later. Both shapes are now named, and the conclusion is stronger for it. A re-review is luck, never the mechanism, so the explicit @codex review request stays and list-bot-threads comparing review.commit.oid to the head is what makes the verdict correct under either behaviour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wires the three tools from the parent PR into the contract. /orchestrate now takes one ticket, several, a project, or --auto, and grows --sleep, --parallel and --auto. One ticket with no flags behaves exactly as it did. --sleep opens pull requests and never merges one. Every piece of signing, provenance, ledger and merge-sweep machinery the OLD --sleep needed stays deleted, because nothing acts on a receipt. What makes an unattended run trustworthy now is verify-delivery.mjs, which reads only artifacts and never a worker's self-report. That is what was missing the first time. Stacking, because nothing merges overnight: a ticket whose blocker is in the same queue branches from its blocker's branch and targets it, since main will not contain the blocker's work until morning. --base is passed to verify-delivery too, or the size caps read the parent's commits as this ticket's. A stack lives in one repository, so cross-repo blockers stay separate pull requests and the api one deploys first. New step 12 clears the Codex reviewer's threads, which nothing has ever read: 8 threads opened across #676, #680 and #681, 8 still unresolved, all merged. The verdict comes from the presence of a review, never the thread count, and NO_REVIEW is never reported as clean. Every resolve replies first. One fix round, then re-verify and post @codex review explicitly, because the bot reviews on open and on request but NEVER on a push: #676's only review landed at 17:23:33Z while commits ran to 17:35:14Z. Two jump references changed MEANING, not just digits. "0 blocking -> step 12" and "all CLOSED -> step 12" used to mean "hand over" and now mean "go to the bot pass"; a mechanical renumber would have skipped the new step on the cleanest and most common path. The OPEN branch reaches it too, report-only. Preflight 0a now asserts the ACTIVE gh account owns the target remote, not merely that some account is logged in. This run hit the exact failure it guards: a push refused for thomas-luizon_iqpay after the work was done. Preflight 0b runs once per distinct repo rather than once per ticket, so concurrent fetch and merge --ff-only cannot race on .git/index. The visual layer is deliberately absent. A visible-effect ticket runs, opens its pull request, and is NOT moved to In Review: the run prints "visual check owed" and Thomas looks with /dev-server. The screenshots existed to prove correctness before a machine merged, and no machine merges. Amends the §5.3 round cap to scope "round" to the frozen cross-vendor list, or the bot fix round reads as a forbidden third round. Amends the merge prohibition to say what it means: never merges UNASKED. Thomas asking for a merge in a later turn is the intended path; --admin, the raw REST and GraphQL paths, force pushes and pushes to main stay blocked in every case. caps.parallelTickets is 3, not 8: each worktree is a full install, build and test run plus its own model session. node tools/test-tools.mjs ORBIT TOOLS GATE OK node .claude/hooks/test-hooks.mjs ORBIT HOOKS OK node tools/check-dashes.mjs clean Refs ORB-179, ORB-180. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The step 12 text claimed the bot never reviews on a push, written from #676 alone. #682 falsified it the same day: it reviewed the old head, took a push, and reviewed the new head seven minutes later. Both shapes are now named, and the conclusion is stronger for it. A re-review is luck, never the mechanism, so the explicit @codex review request stays and list-bot-threads comparing review.commit.oid to the head is what makes the verdict correct under either behaviour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(tools): plan a queue, read the Codex reviewer, close its threads Three tools the overnight /orchestrate queue needs. All new files, nothing wired yet: the SKILL.md contract change is the PR stacked on this one. plan-queue.mjs resolves a scope (explicit tickets, a Linear project, or the whole board) into one ordered execution plan. The rule that matters: a blocker INSIDE the queue is a stack edge, because the branch can carry it, while an open blocker OUTSIDE it defers the ticket, because its branch would have to contain work that does not exist. Admission runs to a fixed point, since dropping one ticket can strand another behind it. Cross-repo blockers order the waves but never stack, because GitHub requires every branch in a stack to live in one repository. list-bot-threads.mjs makes "the second reviewer never ran" a verdict instead of a silence read as approval. Measured on 2026-08-05 across PRs #676, #680 and #681: the Codex bot opened eight inline threads, eight are still unresolved, and all three are merged. Two shapes make a naive reading wrong, and both are handled rather than documented: an empty thread list is ambiguous between "clean" and "not yet", so the verdict comes from the presence of a bot review; and a body-level CHANGES_REQUESTED opens no thread at all, so zero unresolved threads is not proof of a clean PR. A draft attracts no review ever, so draft is checked before the wait clock starts. resolve-bot-thread.mjs replies first and resolves only once the reply is confirmed. A thread closed with no reason is indistinguishable from one nobody read, so the ordering is a gate rather than a convention: a failed or empty reply exits non-zero having mutated nothing. A failed resolve after a landed reply says so, so a retry cannot double-post. Verified against live data, not fixtures alone: list-bot-threads reads PR #681 and reports its real unresolved P2 on tools/launch-worker.mjs, and plan-queue orders ORB-179 before ORB-180 and stacks the second on the first. Both GraphQL mutations were checked against the live schema; pullRequestReviewId is optional and names a pending review, so it is omitted. node tools/test-tools.mjs ORBIT TOOLS GATE OK (21/20/12 new assertions) node .claude/hooks/test-hooks.mjs ORBIT HOOKS OK Refs ORB-179, ORB-180. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(tools): reject a bot review pinned to an older head, refuse forked stacks Both defects were raised by the Codex reviewer on this pull request and both were right. The cross-vendor review earned its keep on the very PR that adds the tool for reading it. P1, list-bot-threads: a review is evidence about the commit it was given on and nothing else. The tool's own usage says the bot never reviews on a push, then it accepted the newest bot review regardless of which commit that review saw. After any fixup push it would report REVIEWED for code the bot never looked at, which is precisely the defect the tool exists to remove. It now compares review.commit.oid against headRefOid and keeps waiting otherwise, and NO_REVIEW distinguishes "never reviewed" from "reviewed a dead head" by naming the stale commit so the caller knows to post @codex review. P2, plan-queue: a branch has ONE parent, so a ticket blocked by two INDEPENDENT same-repo tickets cannot stack on both. It picked the lexicographically last and said nothing, planning a branch that would not contain the other blocker's work while the plan claimed both were satisfied. It now picks the deepest blocker in topological order and asserts every other same-repo blocker is one of its ancestors; a shape that cannot be expressed is refused by name rather than emitted wrong. Blockers that do form a chain are unaffected, and a cross-repo blocker alongside a same-repo one is not a fork. Seven regression cases added, including the fork refusal, the chained pair, the mixed cross-repo parent, a stale review, a fresh review following a stale one, and a review carrying no commit at all. node tools/test-tools.mjs ORBIT TOOLS GATE OK (list-bot-threads 24, plan-queue 24) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(tools): a re-review after a push is not reliable, so do not claim it never happens #676 reviewed once and never again while commits kept landing. #682 reviewed the old head, took a push, and reviewed the new head seven minutes later. The blanket 'never on a push' claim was written from #676 alone and #682 falsified it the same day. Nothing about the code changes: comparing review.commit.oid to headRefOid is what makes the verdict correct under either behaviour, which is the point. Only the claim is corrected, because a run that trusts 'never' would skip the freshness check as redundant. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(tools): a resolve-only retry, and the body of a review-level block Two P2s from the Codex re-review of this pull request, both real. resolve-bot-thread: the failed-resolve branch told the caller to retry the resolve alone and the tool offered no way to do it, so the advice could not be followed. --resolve-only closes that WITHOUT weakening the no-bare-resolve rule: it asks GitHub whether a reply is actually on the thread and refuses when there is none, so the invariant is enforced against the live thread rather than trusted from a flag. An already-resolved thread is a no-op exit 0. list-bot-threads: a body-level CHANGES_REQUESTED carries its whole complaint in the review body and opens no thread, so the caller learned it was blocked and nothing about why. The body now rides along for that verdict only; on a COMMENTED review it is boilerplate and the threads hold the findings. The third P2, splitting sibling blockers into separate stack chains, is answered on the thread rather than implemented. Refusing is one of the two remedies the reviewer itself proposed in the previous round, and chaining two tickets Linear never ordered would invent a dependency. node tools/test-tools.mjs ORBIT TOOLS GATE OK (list-bot-threads 26, resolve-bot-thread 16) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(orchestrate): a queue of tickets, a stack of reviewed pull requests Wires the three tools from the parent PR into the contract. /orchestrate now takes one ticket, several, a project, or --auto, and grows --sleep, --parallel and --auto. One ticket with no flags behaves exactly as it did. --sleep opens pull requests and never merges one. Every piece of signing, provenance, ledger and merge-sweep machinery the OLD --sleep needed stays deleted, because nothing acts on a receipt. What makes an unattended run trustworthy now is verify-delivery.mjs, which reads only artifacts and never a worker's self-report. That is what was missing the first time. Stacking, because nothing merges overnight: a ticket whose blocker is in the same queue branches from its blocker's branch and targets it, since main will not contain the blocker's work until morning. --base is passed to verify-delivery too, or the size caps read the parent's commits as this ticket's. A stack lives in one repository, so cross-repo blockers stay separate pull requests and the api one deploys first. New step 12 clears the Codex reviewer's threads, which nothing has ever read: 8 threads opened across #676, #680 and #681, 8 still unresolved, all merged. The verdict comes from the presence of a review, never the thread count, and NO_REVIEW is never reported as clean. Every resolve replies first. One fix round, then re-verify and post @codex review explicitly, because the bot reviews on open and on request but NEVER on a push: #676's only review landed at 17:23:33Z while commits ran to 17:35:14Z. Two jump references changed MEANING, not just digits. "0 blocking -> step 12" and "all CLOSED -> step 12" used to mean "hand over" and now mean "go to the bot pass"; a mechanical renumber would have skipped the new step on the cleanest and most common path. The OPEN branch reaches it too, report-only. Preflight 0a now asserts the ACTIVE gh account owns the target remote, not merely that some account is logged in. This run hit the exact failure it guards: a push refused for thomas-luizon_iqpay after the work was done. Preflight 0b runs once per distinct repo rather than once per ticket, so concurrent fetch and merge --ff-only cannot race on .git/index. The visual layer is deliberately absent. A visible-effect ticket runs, opens its pull request, and is NOT moved to In Review: the run prints "visual check owed" and Thomas looks with /dev-server. The screenshots existed to prove correctness before a machine merged, and no machine merges. Amends the §5.3 round cap to scope "round" to the frozen cross-vendor list, or the bot fix round reads as a forbidden third round. Amends the merge prohibition to say what it means: never merges UNASKED. Thomas asking for a merge in a later turn is the intended path; --admin, the raw REST and GraphQL paths, force pushes and pushes to main stay blocked in every case. caps.parallelTickets is 3, not 8: each worktree is a full install, build and test run plus its own model session. node tools/test-tools.mjs ORBIT TOOLS GATE OK node .claude/hooks/test-hooks.mjs ORBIT HOOKS OK node tools/check-dashes.mjs clean Refs ORB-179, ORB-180. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(orchestrate): state both measured shapes of the bot re-review The step 12 text claimed the bot never reviews on a push, written from #676 alone. #682 falsified it the same day: it reviewed the old head, took a push, and reviewed the new head seven minutes later. Both shapes are now named, and the conclusion is stronger for it. A re-review is luck, never the mechanism, so the explicit @codex review request stays and list-bot-threads comparing review.commit.oid to the head is what makes the verdict correct under either behaviour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: bump the three patchable vulnerable transitive deps Dependabot has five open high alerts, all npm transitives reached only through package-lock.json. Three have a published patch and are fixed here with the overrides block the repo already uses for exactly this (brace-expansion@1, and js-yaml@4 which this raises rather than adds). js-yaml@4 4.3.0 -> 4.3.1 GHSA-5p4m-2wfm-xmqj (eslintrc, xcpretty, orval) js-yaml@3 3.15.0 -> 3.15.1 GHSA-5p4m-2wfm-xmqj (@lhci/utils, dev) nanoid@3 3.3.16 -> 3.3.17 GHSA-2v37-7h3g-55p8 (expo-router, postcss) Every dependent's declared range already admits the patched version, so no dependent needed relaxing. npm reported the installed copies "invalid" against the new overrides but would not re-resolve them from the existing lock, so the lockfile was regenerated with `npm update js-yaml nanoid`. The remaining two alerts are image-size (GHSA-w3rx-r6r6-pgpr, GHSA-5p2g-fcmc-qvqq) and are NOT fixed, because no fixed release exists: the advisory range is <= 2.0.2 and 2.0.2 is the latest published version. npm's only proposed remedy is a major downgrade to expo 53 / react-native 0.72, which is a worse outcome than the bug. It is unreachable in production regardless: image-size is pulled by metro and runs in the bundler at build time, never shipped into the app. Validation: type-check 3/3, and 670 test files pass across all four workspaces. The full suite must be run with --concurrency=1; at turbo's default concurrency the shared barrel-import test at types.test.ts:1943 exceeds its 15s timeout purely from machine contention, unrelated to this change (shared imports neither package). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(deps): sweep in-range dependency drift for August 2026 Monthly non-security freshness sweep. /dep-sweep and the reminder workflow that files these tickets were both deleted by #676, so this follows the contract stated in the ticket body instead: bump web and shared minor/patch, defer mobile to expo install --check, report the orbit-api NuGet drift without changing it. package-lock.json only. Every bump was already inside an existing semver range, so no package.json edit was needed. 263 packages moved, including next 16.2.11 -> 16.3.0, @sentry/* 10.68.0 -> 10.70.0, @supabase/supabase-js 2.110.8 -> 2.112.3, react-hook-form 7.82 -> 7.85, next-intl 4.13.4 -> 4.13.6, axios 1.18.1 -> 1.19.0, and 23 @radix-ui patch bumps. #599 recorded that regenerating this lockfile refloats transitives, so the pins that had to hold were checked against the base commit rather than by eye. Every exactly-pinned root override is identical: the Expo SDK 57 native module set did not move, orval and @orval/zod held at 8.20.0 so the committed Zod snapshot stays comparable, and react, react-dom and react-test-renderer held. The only override-pinned package that moved is postcss 8.5.23 -> 8.5.26, whose override is the caret range ^8.5.18, so that is the pin working as designed. Two things npm did that the invocation did not ask for. A hoisted lockfile ignores --workspace scoping, so Expo build tooling moved with the sweep (@expo/cli 57.0.10 -> 57.0.14, @react-native/* 0.86.0 -> 0.86.2); all in-range, all still SDK 57, none in the pinned native set. And brace-expansion moved 1.1.18 -> 1.1.16, a downgrade, because the lockfile had drifted above its own exact security pin and re-resolution pulled it back. Validation: lint 3/3 with 0 errors, type-check 3/3, and 670 test files pass across all four workspaces. The suite needs --concurrency=1; at turbo's default the shared barrel-import test at types.test.ts:1943 exceeds its 15s timeout from machine contention alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd de-flake the suite (#715) * fix: bump the three patchable vulnerable transitive deps Dependabot has five open high alerts, all npm transitives reached only through package-lock.json. Three have a published patch and are fixed here with the overrides block the repo already uses for exactly this (brace-expansion@1, and js-yaml@4 which this raises rather than adds). js-yaml@4 4.3.0 -> 4.3.1 GHSA-5p4m-2wfm-xmqj (eslintrc, xcpretty, orval) js-yaml@3 3.15.0 -> 3.15.1 GHSA-5p4m-2wfm-xmqj (@lhci/utils, dev) nanoid@3 3.3.16 -> 3.3.17 GHSA-2v37-7h3g-55p8 (expo-router, postcss) Every dependent's declared range already admits the patched version, so no dependent needed relaxing. npm reported the installed copies "invalid" against the new overrides but would not re-resolve them from the existing lock, so the lockfile was regenerated with `npm update js-yaml nanoid`. The remaining two alerts are image-size (GHSA-w3rx-r6r6-pgpr, GHSA-5p2g-fcmc-qvqq) and are NOT fixed, because no fixed release exists: the advisory range is <= 2.0.2 and 2.0.2 is the latest published version. npm's only proposed remedy is a major downgrade to expo 53 / react-native 0.72, which is a worse outcome than the bug. It is unreachable in production regardless: image-size is pulled by metro and runs in the bundler at build time, never shipped into the app. Validation: type-check 3/3, and 670 test files pass across all four workspaces. The full suite must be run with --concurrency=1; at turbo's default concurrency the shared barrel-import test at types.test.ts:1943 exceeds its 15s timeout purely from machine contention, unrelated to this change (shared imports neither package). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(deps): sweep in-range dependency drift for August 2026 Monthly non-security freshness sweep. /dep-sweep and the reminder workflow that files these tickets were both deleted by #676, so this follows the contract stated in the ticket body instead: bump web and shared minor/patch, defer mobile to expo install --check, report the orbit-api NuGet drift without changing it. package-lock.json only. Every bump was already inside an existing semver range, so no package.json edit was needed. 263 packages moved, including next 16.2.11 -> 16.3.0, @sentry/* 10.68.0 -> 10.70.0, @supabase/supabase-js 2.110.8 -> 2.112.3, react-hook-form 7.82 -> 7.85, next-intl 4.13.4 -> 4.13.6, axios 1.18.1 -> 1.19.0, and 23 @radix-ui patch bumps. #599 recorded that regenerating this lockfile refloats transitives, so the pins that had to hold were checked against the base commit rather than by eye. Every exactly-pinned root override is identical: the Expo SDK 57 native module set did not move, orval and @orval/zod held at 8.20.0 so the committed Zod snapshot stays comparable, and react, react-dom and react-test-renderer held. The only override-pinned package that moved is postcss 8.5.23 -> 8.5.26, whose override is the caret range ^8.5.18, so that is the pin working as designed. Two things npm did that the invocation did not ask for. A hoisted lockfile ignores --workspace scoping, so Expo build tooling moved with the sweep (@expo/cli 57.0.10 -> 57.0.14, @react-native/* 0.86.0 -> 0.86.2); all in-range, all still SDK 57, none in the pinned native set. And brace-expansion moved 1.1.18 -> 1.1.16, a downgrade, because the lockfile had drifted above its own exact security pin and re-resolution pulled it back. Validation: lint 3/3 with 0 errors, type-check 3/3, and 670 test files pass across all four workspaces. The suite needs --concurrency=1; at turbo's default the shared barrel-import test at types.test.ts:1943 exceeds its 15s timeout from machine contention alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: align mobile to Expo SDK 57 and repair the rotting override pins Closes the residual risk flagged on the August sweep, plus two override defects found while fixing it. expo install --check now reports "Dependencies are up to date". It previously wanted 26 packages moved, including react-native 0.86.0 -> 0.86.2 while six of its own tooling packages had already floated to 0.86.2, leaving core and tooling on different versions across the Android build. The 30 exact Expo pins in the root overrides block are deleted rather than refreshed. They existed to stop transitives refloating, but they are hand-maintained and silently rot, which is what produced the skew above. Removing them and measuring instead shows the tree dedupes on its own: duplicate expo/react-native module copies went from 1 to 0. The single remaining duplicate was react-native-device-info, hoisted 10.3.0 against react-native-siren's nested 8.7.1. Two copies of one native module cannot both autolink, so siren was running 8.x JS against 10.x native. It is now the one Expo-adjacent override that survives, because it is the only one carrying its weight. overrides: 45 entries -> 16 brace-expansion@1 was pinned to exactly 1.1.16, which is no longer a patched version. Two advisories now cover it, GHSA-mh99-v99m-4gvg (<1.1.17) and GHSA-rgw5-rvv9-x895 (<1.1.18, which bypasses the first mitigation). The tree had drifted to the safe 1.1.18 and the exact pin was dragging it back into both. Every security override is now a caret range so a pin cannot rot into the vulnerability it was added to fix. apps/mobile/package.json carried a 21-entry overrides block that npm never applied, since overrides are honoured only from the root workspace. #599 hoisted 7 of these; the block is now deleted outright. Not taken, each verified rather than assumed: eslint 10 eslint-config-next advertises eslint >=9.0.0, but its transitive eslint-plugin-react is at its last release (7.37.5), peers capped at ^9.7, and it throws "contextOrFilename.getFilename is not a function" on eslint 10. The brace-expansion override is the real fix regardless; eslint 10 was only npm's route to it. typescript 7 typescript-eslint@8.67.0 peers typescript <6.1.0. react 19.2.8 expo install --check rejects it and wants 19.2.3. Validation: lint 3/3 with 0 errors, type-check 3/3, 670 test files pass, and npm ci accepts the lockfile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: stop the suite flaking when turbo runs four vitest instances at once `turbo run test` runs the four workspace suites concurrently and each vitest sizes its worker pool to the whole machine, so they oversubscribe the CPU. Tests that re-import a module graph then blow their timeout for no reason connected to the code under test. Reproduced before fixing: five consecutive default-concurrency runs, red every time, and the failure moved between workspaces rather than sitting on one test. shared types.test.ts barrel import, 15087ms against a 15s limit while its sibling barrels finish in 224ms to 490ms mobile use-push-notifications-state beforeEach, "Hook timed out in 10000ms", on a vi.resetModules() that rebuilds the graph per test web i18n/request and lib/server-fetch, the latter being the intermittent failure already filed as orbit-tickets #287 All four workspaces pass standalone, which is what identifies contention rather than a product defect as the cause. testTimeout and hookTimeout go to 30s in all three vitest configs. The inline per-test timeouts are deleted, because that is what made the first attempt at this fix fail: `it(..., 10000)` silently overrides the config, so raising the config alone left web red. Those magic numbers were an earlier pass at this same flake and each one resets the same clock as the suite grows. Verified with two further full default-concurrency runs, 4/4 tasks and 670 test files green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: cap vitest worker pools instead of leaning on long timeouts Addresses the Codex review on #715, which is right about the cause: 30s timeouts left four machine-sized worker pools oversubscribing the CPU and merely waited longer, while tripling how long a genuine hang takes to surface. maxWorkers is now '25%' in all three configs, so four concurrent workspace suites add up to about one machine's worth of workers instead of four. Confirmed against the installed vitest types rather than assumed: maxWorkers?: number | string, "Maximum number or percentage of workers". The cap alone is not sufficient, which is why the timeouts do not go all the way back to the defaults. Measured at default turbo concurrency: no fix 5 of 5 runs red 30s timeouts, no cap 2 of 2 green cap, default 5s timeouts 1 of 3 red, at 5000ms cap plus 15s margin 4 of 4 green 15s rather than the previous 30s, so a real hang reports in half the time while the residual cold-import cost still fits. One failure in the cap-only run was an AssertionError, not a timeout: server-fetch expecting forceRefresh: true on the second call. That is the pre-existing intermittent failure filed as orbit-tickets #287. It appears to be mock state cascading from the timed-out test above it in the same file, and it has not recurred in the four green runs, but this change does not claim to fix #287 because the cascade is unproven. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: cap the fourth vitest suite too Addresses the second Codex review on #715. The cap landed on mobile, web and shared but not eslint-rules, which kept vitest's default machine-sized pool and added workers on top of the three capped ones. That also made the comment wrong where it claimed four suites were capped. Its two test files make the practical pool small, but the config is what sets the ceiling, and on a 4-core CI runner the uncapped fourth is exactly where the aggregate goes back over one machine's worth. Verified with two more full default-concurrency runs, 4/4 tasks and 670 test files green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>



Executes the plan approved on 2026-08-04. The harness measured 67,809 lines across three layers and had stopped being able to certify its own repair.
Why, in one number
PR #672 ran 9 local
/pr-reviewrounds over 38 hours. Verdict every time:NEEDS_WORK. 19 findings, 19 unique, zero repeats. The fixer fixed everything and the reviewer found brand-new issues every round, on a 7,078-line diff against a stated 400-line cap. Termination required "the reviewer finds nothing", which on that diff has probability near zero. Correctly implemented and mathematically unable to stop.Nearly every finding concerned the trust model of unattended merge: forged approval evidence, receipts trusting attacker-selected keys, replayable approvals. That adversary exists only under
--sleep, where a machine acts on a receipt with no human present.--sleepis deleted. All 19 die by deletion.What changed
~/.claude/skills/)tools/,.claude/,AGENTS.md,CLAUDE.md)guards.ymlguards.ymljobs./featuremerged into/ticket. Cardinality is now an output of the analysis, so one message listing several problems yields N tickets. It no longer tells you to quit and retype a different command.launch-worker.mjs1,477 to 263, a port not a rewrite. The 54-line Windows block is byte-identical: it encodes CVE-2024-27980, the ORB-88 mangled-prompt class, and codex exec "<prompt>" hangs indefinitely when stdin is a non-TTY pipe with no writer openai/codex#20919.verify-delivery.mjsis new and is the sole authority for the word "delivered", derived only from git and gh.What this fixes, with evidence
The product-merge freeze. 9 of 44 required checks were harness self-checks. A red
Harness Executionblocked every product merge. Branch protection is now 18 / 13 / 4.A live hole in the admin-merge ban. httpie takes its method positionally, so
http PUT .../pulls/{n}/mergewas allowed while the byte-identical curl call was blocked. Found while rewriting the hook suite, which refused to write a passing assertion for something it had not verified.A guardrail that blocked real work.
grep -rnE 'claude|codex'was refused, because the splitter broke on the|inside the quoted pattern and readcodexas an invocation. So wascodex --version. Both are red-before, green-after in the suite.A self-test that could not load.
test-hooks.mjsimported a hook deleted minutes earlier. It now assertssettings.jsonand the hooks directory agree in both directions, which is exactly the check that would have caught it.Where the plan was wrong, and what I did instead
orbit-api/mutation.yml: the plan said "0 runs. Ever." It has 284, ran four times today, all green.benchmark.ymlis 20/20 green nightly. Neither was deleted. Verified against the live API.forbid-raw-linear-mutationandforbid-ef-migration-raw-indexwere lumped in as harness self-checks. They guard Linear data integrity and EF migration idempotency. Kept.design-reviewerwas counted among the subagents absorbed into the rubric, butvisual.ymlinvokes it as a CI job. Kept.AGENTS.mdcannot reach its 4,000-byte target. The "Never assume an external interface" section it must keep intact is itself exactly 4,000 bytes. It survives byte-identical, both measured failure cases included.Verification
node tools/test-tools.mjs: 158 assertions, exit 0, hermeticnode .claude/hooks/test-hooks.mjs: 118 assertions, exit 0Not proven yet
The new harness has not completed a full end-to-end run. One attempt on ORB-87 reached delivery verification and correctly returned
NO_COMMITwhencodex execexited 0 having written nothing, which is openai/codex#19945's exact shape. That is the invariant working, but steps 8 through 13 are unexercised. Re-prove on a real ticket after this merges.Paired PRs
npm auditfailure blocking every PR there)🤖 Generated with Claude Code