ORB-170: Gate root files with a closed allowlist - #680
Conversation
|
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: 4e6e487125
ℹ️ 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".
| pre-commit: | ||
| commands: | ||
| root-allowlist: | ||
| run: node tools/check-root-allowlist.mjs |
There was a problem hiding this comment.
Exempt local env files from the pre-commit gate
When a developer has a legitimate local root .env/.env.local file (patterns this repo still ignores in .gitignore), this new pre-commit command runs check-root-allowlist, whose readdirSync scan includes ignored and untracked root files, so every commit is blocked even when nothing root-related is staged. Because contributors are also told not to bypass git hooks, this breaks the local secret/env workflow; either skip known local-only ignored files in the hook or run the closed allowlist against tracked/CI contents only.
AGENTS.md reference: AGENTS.md:L78-L80
Useful? React with 👍 / 👎.
…iles Two gaps in the file-only gate, both measured rather than guessed. A local root .env blocked every commit. The scan sees ignored and untracked files by design, and .gitignore blesses .env and .env.local at the root, so the first developer to create one could not commit at all and was also told never to pass --no-verify. Both names are declared now. Gating files alone left the directory half of the root fully open, so the same throwaway simply lands one level down: .artifacts/transcript.mjs walked past a gate written to stop exactly that. The closed set now covers directories too, seeded from the real root, including the generated node_modules and .turbo that a checkout genuinely carries after an install. Closes ORB-176. 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>
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>
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>
* 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>



Summary
.gitpointer.Root AllowlistCI context.Scope
orbit-ui-mobileonly. Theorbit-apiandorbit-landing-pagemirrors require their own tickets and PRs under the one-ticket-per-repo rule.Verification
node tools/check-root-allowlist.mjsnode tools/test-tools.mjsnode .claude/hooks/test-hooks.mjsnpm run lintnpm run type-checknpm run testThe harness covers
.agent-scratch-extract.mjs,.tmp-extract.mjs, and the load-bearing plain filenameextract-tmp.mjs. It also proves a.tmp-extract.mjsfixture is both gitignored and untracked before the gate rejects it.Human follow-up
After this lands, Thomas must add
Root Allowlistas a required status context onmain. This PR does not change branch protection.Linear: ORB-170