From 10779dd652c5718c295a21074a73c4772bd060b8 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:22:29 -0500 Subject: [PATCH 01/43] feat(agents): a spent metered cap closes on Codex, not on a maintainer AGENTS.md held three individually-sound rules that were jointly unsatisfiable: the gate needed a CodeRabbit review with no actionable comments, metered providers were capped at two completed reviews, and a third pass meant handing the PR to a human. A PR whose two permitted reviews both found something -- even something small and immediately fixed -- could never reach the zero-finding review the gate demanded. PR #434 is the first to arrive there. The deeper defect is that the old gate bundled "were the findings dealt with?" and "did a provider read the merging head?" into one test and billed both to the most expensive provider, so a review finding three real problems cost two credits to close while one finding nothing cost one. The contract priced diligence as failure. When the cap is spent and every finding is fixed, deferred-and-tracked or dropped sub-floor with threads resolved, a fresh Codex read of the final head now closes the gate in CodeRabbit's place. Three conditions shut that branch, each readable off the PR rather than out of an agent's account of its own motives: a refusal is not a spent cap, a clean review is already the gate, and the two completed reviews must sit at different commit_ids with the commits between them answering the first one's findings. The maintainer escalation is deleted from both files that carried it, which repairs AGENTS.md's "Nothing else waits" -- false while a capped review gate waited on a human. Clearing the gate is still not authority to merge, stated as its own rule. The merge bullet is byte-identical, preserving both mechanical guards in test_agent_contract_is_runnable.py. All six drift surfaces move together, since every rule-stating file is material. Closes #439. ADR-0065. --- .agents/skills/tether-worker/SKILL.md | 4 +- .../skills/tether-worker/agents/openai.yaml | 2 +- .github/pull_request_template.md | 5 +- .greptile/README.md | 4 +- AGENTS.md | 59 +++-- CONTRIBUTING.md | 24 +- docs/PRD.md | 7 +- ...t-layer-coordinates-writers-not-reviews.md | 2 +- ...ed-cap-closes-on-the-unmetered-provider.md | 206 ++++++++++++++++++ docs/adr/README.md | 3 +- 10 files changed, 284 insertions(+), 32 deletions(-) create mode 100644 docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md diff --git a/.agents/skills/tether-worker/SKILL.md b/.agents/skills/tether-worker/SKILL.md index c8070a46..15b53a23 100644 --- a/.agents/skills/tether-worker/SKILL.md +++ b/.agents/skills/tether-worker/SKILL.md @@ -82,7 +82,9 @@ gap — that is what invalidated reviews across three PRs at once under the old **Open the PR as a draft** and get the checks green there. Then follow `AGENTS.md` §Review: an external provider reads the final head, you fix what is serious and defer or drop the rest, and -CodeRabbit with no actionable comments is the last gate before merge. +CodeRabbit with no actionable comments is the last metered gate before merge. If its two-review cap +is spent and every finding is disposed of, a fresh Codex read of the final head closes the gate in +its place — §Review carries the conditions, and no maintainer is waited on for it. You do not have to sit and watch it. A review takes as long as it takes, and a short-lived worker that polls is spending tokens to wait — so **write the state into the PR body before you go**: diff --git a/.agents/skills/tether-worker/agents/openai.yaml b/.agents/skills/tether-worker/agents/openai.yaml index 0cf03094..38245c9d 100644 --- a/.agents/skills/tether-worker/agents/openai.yaml +++ b/.agents/skills/tether-worker/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Tether Worker" short_description: "Claim one accepted issue and open its draft PR onto the review lane" - default_prompt: "Use $tether-worker to claim issue #N with .agents/bin/claim.py, implement it in an isolated worktree, open a DRAFT PR, request the first Codex review, write the lane state into the PR body, and exit. Do not arm auto-merge: that happens at the end of the review lane, after the mandatory CodeRabbit gate." + default_prompt: "Use $tether-worker to claim issue #N with .agents/bin/claim.py, implement it in an isolated worktree, open a DRAFT PR, request the first Codex review, write the lane state into the PR body, and exit. Do not arm auto-merge: that happens at the end of the review lane, once the gate is closed — by a clean CodeRabbit review, or by a Codex read of the final head when CodeRabbit's two-review cap is spent and every finding is disposed of." diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e162efc5..f99622d3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -34,8 +34,9 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Risk rationale: - Final head SHA: - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped +- Codex closing read — only when the CodeRabbit line below records a spent cap: n/a | closed the gate (quote it exactly as the CodeRabbit gate is quoted — permalink, the **full 40-hex** head it read **which must be the final head**, when, and what it said; a re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged, and anything the closing read surfaced is disposed of above before it closes) - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify, a `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify, a `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body and at a **different** `commit_id` with the commits between them answering the first one's findings; every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; neither came back clean; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) @@ -58,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a final-head result recorded above — a quoted verdict, or the reason it produced none — and at least one of them is an external provider that reviewed the **final head**, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **CodeRabbit returned no actionable comments at that head**, asked with the **full-review** command. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a final-head result recorded above — a quoted verdict, or the reason it produced none — and at least one of them is an external provider that reviewed the **final head**, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/.greptile/README.md b/.greptile/README.md index 27ca0151..471b7843 100644 --- a/.greptile/README.md +++ b/.greptile/README.md @@ -98,4 +98,6 @@ reads the diff before any metered provider does. **One *review* in practice**, a reviews per metered provider** in `AGENTS.md` §Review applies here as everywhere, but it is a ceiling rather than a second credit to plan on, so ask again only if the first found something blocking and the seat still has budget. A request that produced no review — a throttle, a quota refusal, a failed run — is not an -ask and spends nothing. CodeRabbit, not Greptile, is the gate. +ask and spends nothing. CodeRabbit, not Greptile, is the metered gate — and when CodeRabbit's own +cap is spent with every finding disposed of, an unmetered Codex read closes it. Greptile is never +the closer either way. diff --git a/AGENTS.md b/AGENTS.md index ea19372d..0563e93d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -155,8 +155,11 @@ validity turns on it being the right test — must satisfy both. - **You are never the only reviewer of your own diff.** Before merge at least one external provider must have reviewed the final head and reported what it found. Author-side or local output never - satisfies this, and a green status check with no review body is not a review. Quote the provider - and name the 40-hex head it read in the PR body. + satisfies this, and a green status check with no review body is not a review. *Author-side* names + whose judgement it is and not which machine ran it: a provider's own review posted on the pull + request is external however its CLI resolves locally (§This machine), while your transcript of a + local run is not, because nothing but you attests it. Quote the provider and name the 40-hex head + it read in the PR body. - **Open as a draft and get it green there.** Every required check runs on a draft, so the diff reaches fully green before anyone is asked to read it. Opening ready is **not forbidden** but is never free: it spends a metered provider on a diff no unmetered one has read, so record the @@ -165,9 +168,11 @@ validity turns on it being the right test — must satisfy both. draft by default, or the ready PR whose reason is recorded — **Codex** first, unmetered and so uncapped, until it surfaces nothing blocking. Then **optionally one Greptile review**, if the seat has budget: a *review*, since a standard one costs a credit and a TREX one three. Then - ready-for-review if it is not already, and **CodeRabbit last**. Codex is not optional: it is what - makes the metered providers affordable, and skipping it is the same spend as opening ready. - Record each leg in the PR. + ready-for-review if it is not already, and **CodeRabbit last** — last of the *metered* providers, + which is the spend the order buys. The unmetered one is not confined to the front and may read + again behind it. Codex is not optional: it is what makes the metered providers affordable, and + skipping it is the same spend as opening ready. Record each leg in the PR, including the closing + read. - **Review evidence survives a non-material push, so answering findings does not restart the gate.** **The non-material list is a set of exceptions and it wins**, so a change touching a material path is still non-material when the change itself is one of them: merging `main` in @@ -185,12 +190,35 @@ validity turns on it being the right test — must satisfy both. ` .agents/bin/greptile_usage.py` before spending one, and if the seat is empty record *"Greptile: no credits this month"* and move on; exhaustion never blocks. **A quota refusal from any provider means the provider did not review, and never counts as a pass.** Copilot is advisory and satisfies nothing. -- **CodeRabbit is the last gate**: at least one review with no actionable comments, asked with the - **full-review** command (the bare incremental one applies only where automatic reviews are - *paused*; they are *disabled* here, so it reviews nothing and says so in words that read like a - clean pass). Read its commit status before every ask — `pending` means one is running and a second - request destroys it. A fair-use refusal naming a retry time is a **wait**, not unavailability; - **never** accept its usage-based-billing offer, which is the maintainer's spending decision. +- **CodeRabbit is the last metered gate**: at least one review with no actionable comments, asked + with the **full-review** command (the bare incremental one applies only where automatic reviews + are *paused*; they are *disabled* here, so it reviews nothing and says so in words that read like + a clean pass). Read its commit status before every ask — `pending` means one is running and a + second request destroys it. A fair-use refusal naming a retry time is a **wait**, not + unavailability; **never** accept its usage-based-billing offer, which is the maintainer's spending + decision. +- **A spent cap closes on Codex rather than on a maintainer.** When two *completed* CodeRabbit + reviews stand on this PR — each one it submitted with a body, since a throttle, a quota refusal or + a failed run reviewed nothing — and every finding they raised is **fixed, deferred-and-tracked, or + dropped sub-floor** with the thread resolved, then a **fresh Codex read of the final head** closes + the gate in their place: a full read posted by the provider on the pull request, never an earlier + Codex pass re-quoted, since the head that pass read is not the head being merged. Anything that + read surfaces is disposed of by those same three dispositions before it closes — the close is a + *substitute for the clean pass*, not a lower bar than it. That review is then *the clean review* + the merge binding below names. +- **Three things shut that close, and each is readable off the pull request rather than out of your + own account of why you did something.** A refusal is **not** a spent cap: it reviewed nothing, so + it is a wait, and waiting is still what you do. If either completed review came back clean and its + evidence still stands under the non-material rule above, **that** review is the gate, it has + already closed, and none of this applies. And the two completed reviews must sit at **different + `commit_id`s**, with the commits between them answering the first review's findings — two reads of + one unchanged diff are one review asked twice, which buys the close nothing. Motive is not a test + and never becomes one; these three are, and they are also why spending an ask to reach the close + would buy nothing if it worked, since the close costs the disposal of every finding and a further + review on top — more work than the clean pass it replaces. +- **Clearing the gate is not authority to merge.** They are different things and the second is still + per-PR, explicit, and never inferred. Escalate to the maintainer only when the closing read + surfaces something blocking that you may not resolve inside this item's scope. - **Never write a provider's handle in a comment you do not intend as a request.** A mention fires the bot even inside backticks — a code span is not an escape. Describe the command in prose instead. @@ -218,12 +246,13 @@ validity turns on it being the right test — must satisfy both. provider is rate-limiting: both asks spent on refusals and no review obtainable. It does **not** license a third review, and it does not license hammering — **honour the retry interval the refusal names**, and never re-request while the status check reads `pending`, which aborts the - run in flight. If a third pass would be needed, hand the PR to the maintainer with a comment - saying why. Nothing counts this for you; the merged history is auditable. + run in flight. **A spent cap is not a stuck PR**: it opens the Codex close above, so a PR whose + findings are all disposed of and whose threads are all resolved finishes on an unmetered read + rather than on a maintainer. Nothing counts this for you; the merged history is auditable. - **Greptile is one *review* in practice, and a review is not always one credit** — a standard review costs one, a TREX review three, so a second ask is a real spend. Two is the ceiling every - provider shares, not a second credit to plan on, so ask again only if the first found something - blocking and the seat still has budget. + *metered* provider shares, not a second credit to plan on, so ask again only if the first found + something blocking and the seat still has budget. - Human sign-off: releases, tags, signing, any new scientific claim or citation. Nothing else waits. - Merge under explicit per-PR authority, with checks green and threads resolved. Then arm and exit — never wait, never poll: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e4f4dd52..18bd1472 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,9 @@ review lane as merge gates**: branch protection on `main` requires green required CI plus a self-review checklist on every PR, while `AGENTS.md` requires a substantive final-head review on one fixed lane, cheapest provider first: **Codex on the green diff until nothing blocking remains, then optionally one metered -Greptile review, then CodeRabbit with no actionable comments before merge.** +Greptile review, then CodeRabbit with no actionable comments before merge — or, when +CodeRabbit's two-review cap is spent and every finding is disposed of, a fresh Codex +read of the final head closing the gate in its place.** Copilot is advisory only and never satisfies a leg. The ruleset still requires zero GitHub approval reviews; load-bearing changes additionally need any qualified human/domain judgment specified in `AGENTS.md`. This scales to required human reviews + `CODEOWNERS` if @@ -259,7 +261,9 @@ Before requesting review / merging, confirm: routes nothing; the round; and a result from every provider the lane reached — **either** a substantive review **or** that provider's own quoted "nothing to review" for the head it read, a Codex 👍 included. - **CodeRabbit with no actionable comments is required**, and that is a verdict a completed + **CodeRabbit with no actionable comments is required — or, if its two-review cap is spent + and every finding is disposed of, the Codex read of the final head that closed the gate in + its place** — and that is a verdict a completed review reached rather than an absence of one: record the review itself — permalink, the `commit_id` it read, **which must be the final head**, `submitted_at` with a state of **`COMMENTED` or `APPROVED`** (a `PENDING` review has no `submitted_at` and is not a submitted @@ -285,7 +289,7 @@ Automated agents are peers, not a hierarchy: each claims one issue, opens one ** PR, gets it reviewed, and **hands off or merges** rather than sitting and polling. There is no coordinator. Auto-merge is armed at the **end** of the lane, and completing the lane is **not by itself authority to arm it** — `AGENTS.md` requires explicit per-PR merge authority, which is a separate grant that no amount of green -checks confers. Arming it on a draft would merge the PR past the mandatory CodeRabbit +checks confers. Arming it on a draft would merge the PR past the mandatory review gate, since that gate is not a required check. The merge is bound to the head the review evidence covers with `gh pr merge N --auto --squash --match-head-commit ` — that guard is what stands in for the merge queue, which needs an organization-owned repository and so is unavailable @@ -313,7 +317,9 @@ checks are green and the diff is declared final. **Every PR walks the same lane, cheapest provider first: Codex on the green diff, uncapped — the draft by default, or the ready PR whose reason is recorded; then optionally one metered Greptile **review** if the seat has budget, a review being one credit as a standard and three as a TREX; then -CodeRabbit with no actionable comments, which is the last gate before merge.** **Open as a draft and get it green there** — every +CodeRabbit with no actionable comments, which is the last metered gate before merge — and +when that cap is spent with every finding disposed of, a fresh Codex read of the final head +closes the gate in its place rather than a maintainer doing it.** **Open as a draft and get it green there** — every required check runs on a draft, so the diff reaches fully green before anyone is asked to read it, and that is what makes the sequence affordable rather than a policy nobody keeps. Opening ready is not forbidden, but it spends a metered provider on a diff no cheap one has @@ -403,7 +409,8 @@ head it read — a deletion, a pure rename, or Codex's 👍 reaction, which is i any other commenter, never does. **Exhaustion is not incapacity**: a provider with nothing to say has reviewed, a provider with no budget left has not. Greptile out of credits is skippable and never blocks; **CodeRabbit unavailable freezes the PR**, because it is the -gate. Record which and why — and a quota refusal means the provider **did not review**, and never counts as a pass. +metered gate and an unavailable provider is not a spent cap — nothing reviewed, so nothing +opens the Codex close. Record which and why — and a quota refusal means the provider **did not review**, and never counts as a pass. **Two completed reviews per metered provider, then stop.** The cap bounds how many times a provider whose reads cost money or quota is made to *read the diff*, so **Codex @@ -413,9 +420,10 @@ exactly when the provider is rate-limiting. It does **not** license a third revi does not license hammering: honour the retry interval the refusal names, and never re-request while the status check reads `pending`. **Greptile is one *review* in practice**, and a review is not always one credit — a standard review costs one, a TREX review three. Two is the shared ceiling, not a second review to plan on, so ask -again only if the first found something blocking and the seat still has budget. If a third -pass would be needed, hand the pull -request to the maintainer with a comment saying why. Nothing counts this for you; the +again only if the first found something blocking and the seat still has budget. **A spent cap +is not a stuck PR**: when every finding those two reviews raised is disposed of and every +thread resolved, a fresh Codex read of the final head closes the gate in their place, so the +PR finishes on an unmetered read rather than on a maintainer. Nothing counts this for you; the merged history is auditable and you are trusted with it. On agent-layer paths (`.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` and the agent test modules) a finding below the severity floor is **dropped rather than tracked**, because there the diff --git a/docs/PRD.md b/docs/PRD.md index 8ae57caa..ac054921 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1062,7 +1062,9 @@ has not — until it surfaces nothing blocking; it is the free provider, and throttling it bought nothing but slower convergence. Then **optionally one Greptile review**, if the seat has budget — a *review*, not a credit, since a standard one costs one credit and a TREX one three; then ready-for-review if it is not already; then **CodeRabbit with no actionable comments, -which is the last gate before merge**. The round +which is the last metered gate before merge — and when that two-review cap is spent with every finding +disposed of and every thread resolved, a fresh Codex read of the final head closes the gate in its place, +so a reviewed PR finishes on an unmetered read rather than on a maintainer (ADR-0065)**. The round ledger that used to count this is gone — ADR-0064 retired it along with the launcher that consumed it, leaving **at most two completed reviews per metered provider** as a convention a worker keeps rather than a counter that publishes labels — **Codex is @@ -1120,7 +1122,8 @@ exist. When a **selected** provider reports that a change has nothing to review including Codex's 👍 reaction, its documented form of "no suggestions" — that statement satisfies its leg, quoted and never substituted by the author or any other commenter. **Exhaustion is not incapacity**: a provider with no budget left has not reviewed. Greptile out of credits is skippable and never blocks; **CodeRabbit unavailable freezes the -PR**, because it is the last gate and nothing merges past it. **Throttled is not unavailable**: CodeRabbit's fair-use +PR**, because it is the last metered gate and an unavailable provider is not a spent cap — it reviewed +nothing, so nothing opens the unmetered close. **Throttled is not unavailable**: CodeRabbit's fair-use limit is adaptive, and a refusal that names when the next included review is due is a wait — wait it and ask again, which costs no round and no request, and never accept the usage-based-billing offer that accompanies it, since that is the maintainer's spending decision. The elapsed interval is necessary but **not sufficient**: the `CodeRabbit` commit diff --git a/docs/adr/0064-the-agent-layer-coordinates-writers-not-reviews.md b/docs/adr/0064-the-agent-layer-coordinates-writers-not-reviews.md index 2273b0b4..89ac457a 100644 --- a/docs/adr/0064-the-agent-layer-coordinates-writers-not-reviews.md +++ b/docs/adr/0064-the-agent-layer-coordinates-writers-not-reviews.md @@ -5,7 +5,7 @@ SPDX-License-Identifier: GPL-3.0-or-later # 0064 — The agent layer coordinates writers, not reviews -- **Status:** accepted; supersedes [ADR-0062](0062-draft-first-review-lane-with-metered-providers.md) and [ADR-0063](0063-review-evidence-is-read-not-inferred.md), and the review gate, round cap, launcher and advisory scope guard of [ADR-0057](0057-github-native-swarm-coordination.md) +- **Status:** accepted; supersedes [ADR-0062](0062-draft-first-review-lane-with-metered-providers.md) and [ADR-0063](0063-review-evidence-is-read-not-inferred.md), and the review gate, round cap, launcher and advisory scope guard of [ADR-0057](0057-github-native-swarm-coordination.md); its review-gate terminus is in turn superseded by [ADR-0065](0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md), the rest governing unchanged - **Date:** 2026-08-07 - **Deciders:** bioedca - **PRD anchor:** §12 (development & version-control protocol) diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md new file mode 100644 index 00000000..2f6929c1 --- /dev/null +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -0,0 +1,206 @@ + + +# 0065 — A spent metered cap closes on the unmetered provider, not on a maintainer + +- **Status:** accepted; supersedes the review-gate terminus of [ADR-0064](0064-the-agent-layer-coordinates-writers-not-reviews.md), whose other decisions govern unchanged +- **Date:** 2026-08-10 +- **Deciders:** bioedca +- **PRD anchor:** §12 (development & version-control protocol) +- **Milestone:** M11 - Agent-swarm infrastructure + +## Context and problem statement + +> **What of ADR-0064 survives.** Everything except one clause. Its four coordination controls, its +> drop rule, its feature-complete boundary and its removals **govern unchanged**; this record +> supersedes only the fourth of the four review rules it kept — *"one CodeRabbit review with no +> actionable comments is the last gate before merge"* (ADR-0064:311–312). That distinction is stated +> here rather than compressed into the `Status` bullet because `scripts/gen_adr_index.py` extracts +> that field with a single-line pattern and copies it verbatim into the index. + +Three rules in `AGENTS.md` §Review were individually sound and jointly unsatisfiable: + +| line | rule | +|---|---| +| `:188` | CodeRabbit is the last gate — **at least one review with no actionable comments** | +| `:211` | **Two completed reviews per METERED provider**, then stop | +| `:221` | If a third pass would be needed, **hand the PR to the maintainer** | + +If both permitted metered reviews found anything at all, the zero-finding review the gate demanded +required a third read, which the cap forbade. The PR was then stuck behind a human indefinitely, +with nothing wrong with it. + +### The rule priced diligence as failure + +This is the defect, and it is sharper than the deadlock it produced. The old gate bundled two +different questions into one test: + +1. *Were the findings dealt with?* +2. *Did a provider read the head being merged?* + +and then assigned both to the **most expensive** provider in the lane. The arithmetic that falls out +runs backwards. A review that finds three real problems costs two credits to close — one to find +them, one to confirm the fixes — while a review that finds nothing costs one. A pull request was +charged for having been reviewed usefully, and the charge was paid in the currency ADR-0064 was +most careful about: the maintainer's metered budget. + +That inversion is why the deadlock is not an edge case. It is reached by exactly the PRs the lane is +working correctly on. + +### It was reached + +[PR #434](https://github.com/bioedca/tether/pull/434) is the first pull request to arrive there, on +2026-08-10, and the record is complete because the state was documented as it happened: + +- 15/15 required checks green, `mergeStateStatus: CLEAN`, every commit signed. +- Codex reviewed five times and converged with nothing blocking outstanding. +- CodeRabbit review 1 at `454eebf` — `Actionable comments posted: 2`. Both fixed. On the replies + CodeRabbit **confirmed one and withdrew the other** in its own words: *"I withdraw the finding."* +- CodeRabbit review 2 at `359675e` — `Actionable comments posted: 1`, labelled 🟡 Minor. Fixed in + `02fb081`, a **three-line comment edit** — squarely on the non-material exception list. +- All three review threads resolved. Nothing outstanding. + +Two completed metered reviews, every finding disposed of, and the pull request could not merge. + +### Two things already anticipated this + +**`claim.py doctor` detects the class and cannot remedy it.** Its `unarmed` section reports *"open +pull requests that are finished and that nothing will merge"* — precisely this state — but `doctor` +reports and never writes, by design and with a test asserting it issues no write verb. Detection +without an agent-reachable remedy is what produces a stranded pull request rather than a fixed one. + +**ADR-0064 booked the loss.** Its §Consequences lists *"automatic resumption of a stranded pull +request"* among what the cut gave up, on the evidence that the empty `refs/lane-advances/` namespace +showed it had never once occurred. That evidence was true when written. It is no longer. + +### The section already handled the same situation correctly one provider over + +`AGENTS.md:183–187` disposes of an exhausted **metered** provider in words: *"if the seat is empty +record 'Greptile: no credits this month' and move on; **exhaustion never blocks**."* A spent +CodeRabbit cap is the same situation and blocked. This record makes CodeRabbit consistent with +Greptile rather than inventing a principle for it. + +## Decision drivers + +- **The unmetered provider is already trusted and already uncapped.** `AGENTS.md:165` calls Codex + *"unmetered and so uncapped"* and `:168` calls it *"not optional: it is what makes the metered + providers affordable."* Nothing new is being extended to it; it is being allowed to finish. +- **The safety property must not move.** *"You are never the only reviewer of your own diff"* must + hold identically after the change. +- **No new machinery.** ADR-0064 established that review state has no concurrency content and needs + no ledger, label state machine or launcher. The fix has to be prose an agent evaluates. +- **The branch must not become the preferred path.** An agent must not be able to reach a cheaper + route by deliberately spending the cap. + +## Considered options + +1. **Keep the escalation.** Rejected: it is the human gate this record exists to remove, and #434 + shows it fires on well-reviewed work rather than on risky work. +2. **Raise the cap to three.** Rejected: it moves the deadlock rather than removing it, and spends + more of the budget ADR-0064 was protecting. A fourth review would be demanded next. +3. **Reframe the gate for every PR** as *"every finding disposed"* rather than *"the review found + nothing."* Rejected, though it is the most intellectually appealing of the three. It swaps a + third-party attestation for a self-attestation on the **default** path, where the deadlock is + rare; it collides with `CONTRIBUTING.md`'s rule that a credit-funded review must not be + answerable entirely by deferral; and since *serious* is the provider's own labelling, it would + let every 🟡 Minor exit through deferral with no re-read. A rare deadlock does not justify + loosening the common case. +4. **A narrow branch that opens only on a spent cap.** Adopted. + +## Decision outcome + +**When the metered cap is genuinely spent and every finding is genuinely disposed of, a fresh Codex +read of the final head closes the gate in the metered provider's place. The maintainer escalation is +deleted rather than supplemented.** + +### What makes it narrow + +Three conditions shut the branch, and each is **readable off the pull request** rather than out of +an agent's account of its own reasoning: + +1. **A refusal is not a spent cap.** A throttle, a quota refusal or a failed run reviewed nothing, + so it is a wait, and waiting is still what you do. This also means an *unavailable* CodeRabbit + still freezes the pull request — nothing reviewed, so nothing opens the close. +2. **A clean review is the gate, and it has already closed.** If either completed review came back + clean and its evidence still stands under the non-material rule, the branch never opens. +3. **The two completed reviews must sit at different `commit_id`s**, with the commits between them + answering the first review's findings. Two reads of one unchanged diff are one review asked + twice. + +**Motive is deliberately not a test.** An earlier draft closed the gaming path with *"an ask made to +spend the cap is not one of the two."* That was rejected on review for two reasons: a motive is not +checkable by anyone, including the agent itself; and read strictly it **restores the deadlock in a +new form**, since an agent that suspects its own motive concludes the cap is unspent, so the close is +shut, so it must ask again — which it cannot, because two asks have been made. A rule that fixes a +deadlock must not be able to re-create one. + +The remaining deterrent is structural rather than moral: spending an ask to reach the close **buys +nothing even if it works**, because the close costs the disposal of every finding *plus* a further +review on top. It is strictly more work than the clean pass it replaces. + +### The close is a substitute, not a discount + +Anything the closing read surfaces is disposed of by the same three dispositions — fixed, +deferred-and-tracked, or dropped sub-floor — before it closes. An earlier draft held the closing read +to *"nothing blocking"*, which would have silently dropped two severity bands relative to the +zero-actionable-comments bar it replaces, and on agent-layer paths those findings are not tracked at +all. The bar does not move; only who holds it does. + +### Clearing the gate is still not authority to merge + +Stated as its own rule rather than as a tail clause, because it is the most damaging available +misreading. `AGENTS.md:32–33` — *"Do not infer merge authority"* — is untouched. + +### What the merge binds to + +`--match-head-commit` still names *"the 40-hex head the clean review read."* When the close applies, +the Codex closing read **is** that clean review. The merge bullet is left byte-identical: it is the +only place in `AGENTS.md` satisfying both mechanical guards in +`tests/test_agent_contract_is_runnable.py` — the `_SHA_DEFINED` window and the arming-page floor — +and its referent was always provider-neutral in words. Only context had pointed it at CodeRabbit. + +## Consequences + +**Good.** A pull request that has been reviewed, has had its findings fixed and its threads resolved +can finish without a human. The escalation is deleted from both files that carried it — `AGENTS.md` +and `CONTRIBUTING.md` — and deleting it **repairs** `AGENTS.md:227`, *"Human sign-off: releases, +tags, signing, any new scientific claim or citation. Nothing else waits,"* which was false while a +capped review gate waited on a human. That sentence becoming true again is affirmative evidence the +escalation was the anomaly rather than the design. + +**Bad, and named rather than minimised.** The terminal verdict on a capped PR now comes from the +provider the repository does **not** pay for, and Codex's reliability is therefore load-bearing in a +way it was not before. Two things bound that. It is a fresh read of the exact head being merged, not +a re-quoted earlier pass. And it is reached only after two completed metered reviews have already +read the diff — so the close is a third opinion on a twice-reviewed diff, never a first opinion on an +unreviewed one. **If Codex's review quality degrades, this paragraph is the part of the record that +stops holding**, and nothing in this repository would detect that. + +A second cost: §Review grows by roughly twenty lines in a file ADR-0064 deliberately shrank, and +whose resident-context driver — *"`AGENTS.md` is read on every model call by every agent"* — argues +against every addition. The three shutting conditions are what that length buys, and they are the +part that cannot be compressed without making the branch a judgment call. + +**Reversible.** Prose only. No script, no workflow, no label, no ref namespace, no test fixture. A +`git revert` restores the escalation exactly. + +## Adoption status + +Landed with this record. The six surfaces that restated the retired rule move in the same pull +request, because `AGENTS.md:177–182` puts every rule-stating file on the material list precisely so +that a push changing what the gate requires cannot leave a stale copy behind: +`.agents/skills/tether-worker/SKILL.md`, `.agents/skills/tether-worker/agents/openai.yaml`, +`CONTRIBUTING.md`, `.github/pull_request_template.md`, +[`docs/PRD.md`](https://github.com/bioedca/tether/blob/main/docs/PRD.md) §12 and +`.greptile/README.md`. + +`openai.yaml` is called out because it is the likeliest omission and the most consequential one: +four lines, read by no test, and injected as the Codex lane's **default prompt**, so a stale copy +there briefs every future Codex-lane worker under a contract that no longer exists. + +**This pull request cannot use the branch it introduces.** `AGENTS.md:6` says only instructions on +the default branch govern and unmerged edits are inert, and every file it touches is material, so it +re-arms its own review and is judged under the old gate. That is the correct order and not an +oversight: the rule earns its way in under the regime it replaces. diff --git a/docs/adr/README.md b/docs/adr/README.md index e9cefa88..8c1df737 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -103,7 +103,8 @@ else would make it a worse historical record in exchange for a working link. | [0061](0061-transport-failures-and-the-strict-x509-opt-out.md) | A transport failure is not a scope verdict, and strict X.509 conformance is opt-out | accepted; reverses a non-goal of [#315](https://github.com/bioedca/tether/issues/315) | §12.2–§12.5 | | [0062](0062-draft-first-review-lane-with-metered-providers.md) | Draft-first review lane: spend the free provider before the metered ones | superseded by [ADR-0064](0064-the-agent-layer-coordinates-writers-not-reviews.md) | §12 | | [0063](0063-review-evidence-is-read-not-inferred.md) | Review evidence is read from the payload, not inferred from its shape | superseded by [ADR-0064](0064-the-agent-layer-coordinates-writers-not-reviews.md); its measurements stand as evidence | §12 | -| [0064](0064-the-agent-layer-coordinates-writers-not-reviews.md) | The agent layer coordinates writers, not reviews | accepted; supersedes [ADR-0062](0062-draft-first-review-lane-with-metered-providers.md) and [ADR-0063](0063-review-evidence-is-read-not-inferred.md), and the review gate, round cap, launcher and advisory scope guard of [ADR-0057](0057-github-native-swarm-coordination.md) | §12 (development & version-control protocol) | +| [0064](0064-the-agent-layer-coordinates-writers-not-reviews.md) | The agent layer coordinates writers, not reviews | accepted; supersedes [ADR-0062](0062-draft-first-review-lane-with-metered-providers.md) and [ADR-0063](0063-review-evidence-is-read-not-inferred.md), and the review gate, round cap, launcher and advisory scope guard of [ADR-0057](0057-github-native-swarm-coordination.md); its review-gate terminus is in turn superseded by [ADR-0065](0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md), the rest governing unchanged | §12 (development & version-control protocol) | +| [0065](0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md) | A spent metered cap closes on the unmetered provider, not on a maintainer | accepted; supersedes the review-gate terminus of [ADR-0064](0064-the-agent-layer-coordinates-writers-not-reviews.md), whose other decisions govern unchanged | §12 (development & version-control protocol) | From 0abcc82bdecd710c3ef7552ab53fe47f75c18eb0 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:51:14 -0500 Subject: [PATCH 02/43] fix(agents): the close must not re-create the deadlock it removes Codex raised two P1 findings against the first draft, both correct and both falsifying this PR's central claim, so both are fixed rather than deferred. 1. The different-commit_id condition re-created the deadlock. It required the two completed reviews to sit at different commit_ids with commits between them answering the first one's findings. But when a review's findings are all non-serious, the prescribed disposition is deferral or a sub-floor drop, which moves no head. A second review at that same head was then locked out of the close while the cap forbade a third. The test is now the disposal itself -- commits that answer the findings, or the replies and resolutions that record a deferral or drop -- not a new commit. 2. The final-head requirement was unsatisfiable on the capped path. On that path CodeRabbit's two reviews sit at earlier heads by construction, because answering them is what moved the head; demanding every reached provider cover the final head therefore demanded the third review the cap forbids. Only the provider that closes the gate must read the final head. Finding 2 was reported against one line of the PR template. It was a sample of a class: the same requirement appears three more times, in the template's CodeRabbit line and twice in CONTRIBUTING.md -- one of which asserted outright that "answering a finding moves the head", the exact premise finding 1 falsifies. All four are fixed. ADR-0065 records the corrected condition and, deliberately, the draft that was wrong: a rule that fixes a deadlock must be checked against its own failure mode. --- .github/pull_request_template.md | 4 ++-- AGENTS.md | 10 +++++++--- CONTRIBUTING.md | 13 +++++++++---- ...tered-cap-closes-on-the-unmetered-provider.md | 16 +++++++++++++--- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f99622d3..5962f39a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -36,7 +36,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped - Codex closing read — only when the CodeRabbit line below records a spent cap: n/a | closed the gate (quote it exactly as the CodeRabbit gate is quoted — permalink, the **full 40-hex** head it read **which must be the final head**, when, and what it said; a re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged, and anything the closing read surfaced is disposed of above before it closes) - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify, a `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body and at a **different** `commit_id` with the commits between them answering the first one's findings; every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; neither came back clean; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify, a `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body and at a **different** `commit_id` with the commits between them answering the first one's findings; every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; neither came back clean; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) @@ -59,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a final-head result recorded above — a quoted verdict, or the reason it produced none — and at least one of them is an external provider that reviewed the **final head**, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head**, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews sit at **earlier** heads by construction — answering them is what moved the head — so they are recorded at the heads they read and it is the Codex closing read that names the final one; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/AGENTS.md b/AGENTS.md index 0563e93d..d4fb0a4c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -210,9 +210,13 @@ validity turns on it being the right test — must satisfy both. own account of why you did something.** A refusal is **not** a spent cap: it reviewed nothing, so it is a wait, and waiting is still what you do. If either completed review came back clean and its evidence still stands under the non-material rule above, **that** review is the gate, it has - already closed, and none of this applies. And the two completed reviews must sit at **different - `commit_id`s**, with the commits between them answering the first review's findings — two reads of - one unchanged diff are one review asked twice, which buys the close nothing. Motive is not a test + already closed, and none of this applies. And the second completed review must have been asked + **after the first one's findings were disposed of** — by commits that answer them, or, where the + disposition is a deferral or a sub-floor drop, by the replies and resolutions that record it. + Asking twice at one head with nothing answered in between is one review asked twice and buys the + close nothing. **The test is the disposal, not a new commit**: a review answered wholly on the + record moves no head, so demanding one would re-create the deadlock this rule exists to remove. + Motive is not a test and never becomes one; these three are, and they are also why spending an ask to reach the close would buy nothing if it worked, since the close costs the disposal of every finding and a further review on top — more work than the clean pass it replaces. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 18bd1472..61f537cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -265,12 +265,17 @@ Before requesting review / merging, confirm: and every finding is disposed of, the Codex read of the final head that closed the gate in its place** — and that is a verdict a completed review reached rather than an absence of one: record the review itself — permalink, the - `commit_id` it read, **which must be the final head**, `submitted_at` with a state of + `commit_id` it read, **which must be the final head for whichever review closes the gate** + — on the cap-spent path CodeRabbit's two sit at earlier heads by construction, since + answering them is what moved the head, and it is the Codex closing read that names the + final one — `submitted_at` with a state of **`COMMENTED` or `APPROVED`** (a `PENDING` review has no `submitted_at` and is not a submitted one; a `DISMISSED` one is a verdict *withdrawn* and proves nothing), and the - opening of its body. **A review of any earlier head does not qualify, however clean it was** — - answering a finding moves the head, so that review is evidence about a diff this one is no - longer. The clean verdict is written by the `Actionable comments posted:` line being + opening of its body. **A review of any earlier head does not close the gate, however clean it + was** — where answering a finding moved the head, that review is evidence about a diff this + one is no longer. It still stands as one of the two completed reviews, which is exactly what + the cap-spent path relies on: each recorded at the head it read, with the Codex closing read + covering the final one. The clean verdict is written by the `Actionable comments posted:` line being **absent** rather than reading `0`. Neither silence nor a green `CodeRabbit` status check is the gate; both are also what a request that reviewed **nothing** leaves behind (see the full-review command below). Greptile is optional, and its absence diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 2f6929c1..4ce776f2 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -125,9 +125,19 @@ an agent's account of its own reasoning: still freezes the pull request — nothing reviewed, so nothing opens the close. 2. **A clean review is the gate, and it has already closed.** If either completed review came back clean and its evidence still stands under the non-material rule, the branch never opens. -3. **The two completed reviews must sit at different `commit_id`s**, with the commits between them - answering the first review's findings. Two reads of one unchanged diff are one review asked - twice. +3. **The second review must have been asked after the first one's findings were disposed of** — by + commits that answer them, or, where the disposition is a deferral or a sub-floor drop, by the + replies and resolutions that record it. Asking twice at one head with nothing answered in + between is one review asked twice. + + **The test is the disposal, not a new commit**, and an earlier draft got this wrong in a way + worth recording. It required the two reviews to sit at *different `commit_id`s*. Codex's review + of this record's own pull request found the hole: when a review's findings are all non-serious, + the prescribed disposition is deferral or a sub-floor drop, which moves **no head**. A second + review at that same head would then have been locked out of the close while the cap forbade a + third — **the deadlock re-created by the rule written to remove it**. A rule that fixes a + deadlock must be checked against its own failure mode, and this one was not until a provider + checked it. **Motive is deliberately not a test.** An earlier draft closed the gaming path with *"an ask made to spend the cap is not one of the two."* That was rejected on review for two reasons: a motive is not From f04daa6dcc46eaab55635a88b36476d70fd03871 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:20:03 -0500 Subject: [PATCH 03/43] fix(agents): shut the close to scope that landed after the cap was spent Codex round 2 raised two more P1s, both correct and both fixed. 1. The fallback could be used for material scope neither metered review saw. A material push AFTER the second completed review leaves the cap spent, so the close still applied -- and that new code would then be read by the closing provider and by nobody else, since material pushes re-arm review but do not raise the two-review ceiling. It also made the branch reachable by choice, which the other conditions exist to prevent: push the risky part last. A fourth condition now shuts it -- every commit between the second completed review and the closing read must answer a recorded finding or be a non-material exception. This is what makes ADR-0065's "third opinion on a twice-read diff" true rather than merely asserted. 2. The PR template still carried the different-commit_id condition that the previous commit retired from AGENTS.md, so the mandatory template rejected the valid cap-spent path -- the same deadlock, one file over. Fixed there, and the "earlier heads by construction" phrasing I introduced in the template and CONTRIBUTING.md is wrong for the same reason: where disposal is a deferral or drop, the heads legitimately match. The second finding is the third time in this PR that a fix landed in one file and not its mirrors. Swept all five rule-stating files; the only surviving mention of the retired condition is ADR-0065's deliberate record of the draft that was wrong. --- .github/pull_request_template.md | 4 ++-- AGENTS.md | 13 +++++++++---- CONTRIBUTING.md | 5 ++--- ...ed-cap-closes-on-the-unmetered-provider.md | 19 +++++++++++++++++-- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5962f39a..2b7e3f71 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -36,7 +36,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped - Codex closing read — only when the CodeRabbit line below records a spent cap: n/a | closed the gate (quote it exactly as the CodeRabbit gate is quoted — permalink, the **full 40-hex** head it read **which must be the final head**, when, and what it said; a re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged, and anything the closing read surfaced is disposed of above before it closes) - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify, a `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body and at a **different** `commit_id` with the commits between them answering the first one's findings; every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; neither came back clean; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify, a `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal landed after the cap was spent**, so no new scope reached the merge unread by a metered provider; neither came back clean; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) @@ -59,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head**, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews sit at **earlier** heads by construction — answering them is what moved the head — so they are recorded at the heads they read and it is the Codex closing read that names the final one; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head**, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/AGENTS.md b/AGENTS.md index d4fb0a4c..fb4ff6b1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -206,18 +206,23 @@ validity turns on it being the right test — must satisfy both. read surfaces is disposed of by those same three dispositions before it closes — the close is a *substitute for the clean pass*, not a lower bar than it. That review is then *the clean review* the merge binding below names. -- **Three things shut that close, and each is readable off the pull request rather than out of your +- **Four things shut that close, and each is readable off the pull request rather than out of your own account of why you did something.** A refusal is **not** a spent cap: it reviewed nothing, so it is a wait, and waiting is still what you do. If either completed review came back clean and its evidence still stands under the non-material rule above, **that** review is the gate, it has - already closed, and none of this applies. And the second completed review must have been asked + already closed, and none of this applies. The second completed review must have been asked **after the first one's findings were disposed of** — by commits that answer them, or, where the disposition is a deferral or a sub-floor drop, by the replies and resolutions that record it. Asking twice at one head with nothing answered in between is one review asked twice and buys the close nothing. **The test is the disposal, not a new commit**: a review answered wholly on the record moves no head, so demanding one would re-create the deadlock this rule exists to remove. - Motive is not a test - and never becomes one; these three are, and they are also why spending an ask to reach the close + And **nothing but disposal may land after the cap is spent**: every commit between the second + completed review and the closing read must answer a finding those reviews recorded, or be one of + the non-material exceptions above. New scope pushed after the cap has spent it is scope **no + metered provider will ever read**, and the close is a third opinion on a twice-read diff, never a + first opinion on an unread one — so the close is shut and the PR waits for a gate it can actually + satisfy. Motive is not a test + and never becomes one; these four are, and they are also why spending an ask to reach the close would buy nothing if it worked, since the close costs the disposal of every finding and a further review on top — more work than the clean pass it replaces. - **Clearing the gate is not authority to merge.** They are different things and the second is still diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61f537cf..b53c4baf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -266,9 +266,8 @@ Before requesting review / merging, confirm: its place** — and that is a verdict a completed review reached rather than an absence of one: record the review itself — permalink, the `commit_id` it read, **which must be the final head for whichever review closes the gate** - — on the cap-spent path CodeRabbit's two sit at earlier heads by construction, since - answering them is what moved the head, and it is the Codex closing read that names the - final one — `submitted_at` with a state of + — on the cap-spent path CodeRabbit's two are recorded at whatever heads they read, and it is + the Codex closing read that names the final one — `submitted_at` with a state of **`COMMENTED` or `APPROVED`** (a `PENDING` review has no `submitted_at` and is not a submitted one; a `DISMISSED` one is a verdict *withdrawn* and proves nothing), and the opening of its body. **A review of any earlier head does not close the gate, however clean it diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 4ce776f2..590e1f44 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -139,6 +139,20 @@ an agent's account of its own reasoning: deadlock must be checked against its own failure mode, and this one was not until a provider checked it. +4. **Nothing but disposal may land after the cap is spent.** Every commit between the second + completed review and the closing read must answer a finding those reviews recorded, or be one of + the existing non-material exceptions. + + This is the condition that makes the *"third opinion on a twice-read diff"* claim below true + rather than merely asserted, and it was missing from the first two drafts. A second Codex review + of this record's own pull request found it: a material push **after** the cap is spent leaves the + cap spent, so the close still applied — and the new code would then be read by the closing + provider and by nobody else. Material pushes re-arm review but do **not** raise the two-review + ceiling, so there was no path by which a metered provider could ever see that scope. It also made + the branch reachable by choice, which conditions 1–3 were written to prevent: push the risky part + last. A safety property that holds only when the author does not think to break it is not a + safety property. + **Motive is deliberately not a test.** An earlier draft closed the gaming path with *"an ask made to spend the cap is not one of the two."* That was rejected on review for two reasons: a motive is not checkable by anyone, including the agent itself; and read strictly it **restores the deadlock in a @@ -184,8 +198,9 @@ escalation was the anomaly rather than the design. provider the repository does **not** pay for, and Codex's reliability is therefore load-bearing in a way it was not before. Two things bound that. It is a fresh read of the exact head being merged, not a re-quoted earlier pass. And it is reached only after two completed metered reviews have already -read the diff — so the close is a third opinion on a twice-reviewed diff, never a first opinion on an -unreviewed one. **If Codex's review quality degrades, this paragraph is the part of the record that +read the diff, and condition 4 above is what keeps that true by refusing the close to any scope that +landed after the cap was spent — so it is a third opinion on a twice-read diff, never a first opinion +on an unread one. **If Codex's review quality degrades, this paragraph is the part of the record that stops holding**, and nothing in this repository would detect that. A second cost: §Review grows by roughly twenty lines in a file ADR-0064 deliberately shrank, and From a80c1a4b3e5036ad028e1d1b519675dc27215ff4 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:14:15 -0500 Subject: [PATCH 04/43] fix(agents): anchor the scope freeze at the reviewed commit, not the clock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex round 3, three more P1s, all correct. 1. The scope freeze was time-anchored. "After the second completed review" let a material push that landed while that review was still running fall inside the window -- before its submitted_at, after the commit_id it actually read -- so a commit no metered provider saw could still ride the close. Anchored at the commit_id now, with the reason stated inline. 2. The evidence shape was unsatisfiable in the ordinary clean case. The template demanded commit_id, submitted_at and COMMENTED/APPROVED from whichever provider closed the gate, but a clean Codex run reports a thumbs-up reaction and has no pull-request-review object at all, so the new path could not be recorded exactly when it succeeded cleanly. It now asks for what Codex can emit while keeping the final-head binding. 3. There was a THIRD copy of the maintainer escalation, in docs/PRD.md §12.4. The PR body, the previous commit message and ADR-0065 all claimed it lived in exactly two files. That was wrong. The PRD phrases it "the lane stops for the maintainer" rather than "hand the PR to the maintainer", so the phrase-matched grep never saw it -- and since AGENTS.md tells agents to stop and ask when contracts conflict, a worker reading the PRD would have stranded on exactly the PR this unblocks. The sweep is now semantic across all seven rule-stating files rather than a grep for remembered wording, and ADR-0065 records why the grep missed it. That is the fourth time in this PR a fix landed in one file and not its mirrors. --- .github/pull_request_template.md | 2 +- AGENTS.md | 8 +++++--- docs/PRD.md | 4 +++- ...nt-metered-cap-closes-on-the-unmetered-provider.md | 11 +++++++++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2b7e3f71..7048abc9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -59,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head**, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex it often has no pull-request-review object at all, since a clean run reports a 👍 rather than a submitted review, so quote what it *does* emit (its verdict text, the run, and the **full 40-hex** head it was pointed at) and skip the fields that only exist on a review; where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/AGENTS.md b/AGENTS.md index fb4ff6b1..a85f9c2e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -216,9 +216,11 @@ validity turns on it being the right test — must satisfy both. Asking twice at one head with nothing answered in between is one review asked twice and buys the close nothing. **The test is the disposal, not a new commit**: a review answered wholly on the record moves no head, so demanding one would re-create the deadlock this rule exists to remove. - And **nothing but disposal may land after the cap is spent**: every commit between the second - completed review and the closing read must answer a finding those reviews recorded, or be one of - the non-material exceptions above. New scope pushed after the cap has spent it is scope **no + And **nothing but disposal may land after the cap is spent**: every commit after the one the + second completed review actually *read* — its `commit_id`, never its `submitted_at` — must answer + a finding those reviews recorded, or be one of the non-material exceptions above. Anchor it at the + commit and not the clock, because a material push landing while that review is still running is a + push it never saw, and a time-anchored window would wave it through. New scope pushed after the cap has spent it is scope **no metered provider will ever read**, and the close is a third opinion on a twice-read diff, never a first opinion on an unread one — so the close is shut and the PR waits for a gate it can actually satisfy. Motive is not a test diff --git a/docs/PRD.md b/docs/PRD.md index ac054921..cb6a8445 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1095,7 +1095,9 @@ governance text itself (`AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, this docume `.agents/**`, `docs/agents/**`, `.claude/**`, `.github/pull_request_template.md` and `.greptile/**`) are material — the list is *every file that states a rule*, because a push that changes what the gate requires must not keep evidence gathered under the old requirement. A material push re-arms the review, and a PR gets **at most two completed reviews per metered provider**, Codex being unmetered and uncapped — -needing a third means the issue was scoped too large, and the lane stops for the maintainer rather than continuing. +needing a third usually means the issue was scoped too large. The lane does **not** stop for the maintainer: when the cap is +spent with every finding disposed of and no new scope landed since the commit the second review read, a fresh Codex read of +the final head closes the gate instead (ADR-0065). That bound is a **convention a worker keeps, not a counter that publishes labels.** ADR-0064 retired the round ledger, the `agent:round-*` / `agent:review-capped` / `agent:gate-blocked` labels and the launcher that consumed them, after diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 590e1f44..080224e4 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -188,8 +188,15 @@ and its referent was always provider-neutral in words. Only context had pointed ## Consequences **Good.** A pull request that has been reviewed, has had its findings fixed and its threads resolved -can finish without a human. The escalation is deleted from both files that carried it — `AGENTS.md` -and `CONTRIBUTING.md` — and deleting it **repairs** `AGENTS.md:227`, *"Human sign-off: releases, +can finish without a human. The escalation is deleted from all **three** files that carried it — +`AGENTS.md`, `CONTRIBUTING.md` and +[`docs/PRD.md`](https://github.com/bioedca/tether/blob/main/docs/PRD.md) §12.4. An earlier draft of +this record said *two*, and the third was found by review rather than by the sweep that looked for +it: the PRD phrases it as *"the lane stops for the maintainer"* rather than *"hand the PR to the +maintainer"*, so a +phrase-matched search missed it. **A rule stated in five files is found by reading all five, not by +grepping the wording you happen to remember** — and this record is the third place in this pull +request where a fix landed in one file and not its mirrors. Deleting it **repairs** `AGENTS.md:227`, *"Human sign-off: releases, tags, signing, any new scientific claim or citation. Nothing else waits,"* which was false while a capped review gate waited on a human. That sentence becoming true again is affirmative evidence the escalation was the anomaly rather than the design. From 1e02fdca0787c875739cb54f852b60ab4b333295 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:36:41 -0500 Subject: [PATCH 05/43] fix(agents): the closing read must stamp its own head Codex round 4, two P1s and a P2, all correct. 1. A clean Codex run is often a bare thumbs-up carrying no commit, so the head recorded for it was AUTHOR-supplied, not provider-attested. A push landing while the read was in flight could then have the PR name a head the provider never saw and pass it to --match-head-commit -- the exact thing binding the merge exists to prevent. The closing read must now carry a SHA the provider itself wrote; an unstamped reaction is a fine lane result and is not a close. This is the resolution of a genuine tension between two rounds. Round 3 found that demanding commit_id/submitted_at/COMMENTED made the path unsatisfiable in the ordinary clean case; round 4 found that accepting a bare reaction made the head unattested. Both are right, and the answer is not a compromise: a reaction is a lane result, not a close. 2. The scope freeze was per-commit, so a commit that answered a recorded finding AND carried an unrelated hunk passed the literal test while smuggling in exactly the scope the condition excludes. The unit is now the change. 3. The template's cap-spent option said "nothing but disposal", which contradicts AGENTS.md still allowing the non-material exceptions after the cap -- a clean main merge or a comment edit would have re-stranded an eligible PR. The checklist now carries the exceptions too. ADR-0065 records the fifth condition and, deliberately, the round-3/round-4 tension, because the resolution is the reusable part. --- .github/pull_request_template.md | 4 ++-- AGENTS.md | 20 ++++++++++++----- ...ed-cap-closes-on-the-unmetered-provider.md | 22 +++++++++++++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7048abc9..797b0930 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -36,7 +36,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped - Codex closing read — only when the CodeRabbit line below records a spent cap: n/a | closed the gate (quote it exactly as the CodeRabbit gate is quoted — permalink, the **full 40-hex** head it read **which must be the final head**, when, and what it said; a re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged, and anything the closing read surfaced is disposed of above before it closes) - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify, a `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal landed after the cap was spent**, so no new scope reached the merge unread by a metered provider; neither came back clean; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify, a `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since traces to a recorded disposition, or to a clean `main` merge / formatting / comment or docstring edit / ADR renumber — so no new scope reached the merge unread by a metered provider; neither came back clean; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) @@ -59,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex it often has no pull-request-review object at all, since a clean run reports a 👍 rather than a submitted review, so quote what it *does* emit (its verdict text, the run, and the **full 40-hex** head it was pointed at) and skip the fields that only exist on a review; where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head must come from the provider's own artifact (a posted review, or a run record that names the commit it read), never from you: a bare 👍 carries no commit, so it is a valid lane result and **cannot** close the gate, since an author-supplied head could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/AGENTS.md b/AGENTS.md index a85f9c2e..5e655fe2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -206,6 +206,13 @@ validity turns on it being the right test — must satisfy both. read surfaces is disposed of by those same three dispositions before it closes — the close is a *substitute for the clean pass*, not a lower bar than it. That review is then *the clean review* the merge binding below names. +- **The closing read must stamp the head itself, or it does not close.** The SHA that reaches + `--match-head-commit` has to come from something the *provider* wrote — a posted review, or a run + artifact that records the commit it read. A bare 👍 with no commit on it is a fine lane result and + is **not** a close, because the head would then be yours to assert rather than the provider's to + attest: a push landing while the read is in flight would let a PR name a head the provider never + saw, and the whole point of binding the merge is that nobody can do that. Where the only Codex + output is an unstamped reaction, the gate stays shut until a stamped one exists. - **Four things shut that close, and each is readable off the pull request rather than out of your own account of why you did something.** A refusal is **not** a spent cap: it reviewed nothing, so it is a wait, and waiting is still what you do. If either completed review came back clean and its @@ -216,11 +223,14 @@ validity turns on it being the right test — must satisfy both. Asking twice at one head with nothing answered in between is one review asked twice and buys the close nothing. **The test is the disposal, not a new commit**: a review answered wholly on the record moves no head, so demanding one would re-create the deadlock this rule exists to remove. - And **nothing but disposal may land after the cap is spent**: every commit after the one the - second completed review actually *read* — its `commit_id`, never its `submitted_at` — must answer - a finding those reviews recorded, or be one of the non-material exceptions above. Anchor it at the - commit and not the clock, because a material push landing while that review is still running is a - push it never saw, and a time-anchored window would wave it through. New scope pushed after the cap has spent it is scope **no + And **nothing but disposal may land after the cap is spent**: everything added after the commit + the second completed review actually *read* — its `commit_id`, never its `submitted_at` — must + answer a finding those reviews recorded, or be one of the non-material exceptions above. Anchor it + at the commit and not the clock, because a material push landing while that review is still + running is a push it never saw, and a time-anchored window would wave it through. **The unit is + the change, not the commit**: a commit that fixes a recorded finding *and* carries an unrelated + hunk passes any per-commit test while smuggling exactly the scope this shuts out, so every hunk + has to trace to a disposition or an exception. New scope pushed after the cap has spent it is scope **no metered provider will ever read**, and the close is a third opinion on a twice-read diff, never a first opinion on an unread one — so the close is shut and the PR waits for a gate it can actually satisfy. Motive is not a test diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 080224e4..69267561 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -153,6 +153,28 @@ an agent's account of its own reasoning: last. A safety property that holds only when the author does not think to break it is not a safety property. + The same condition also has to be read **per change, not per commit**. A commit that answers a + recorded finding and carries an unrelated hunk alongside it satisfies any per-commit phrasing + while smuggling in exactly the scope the condition excludes. + +### The closing read must stamp its own head + +The SHA that reaches `--match-head-commit` must come from something the **provider** wrote. This is +a fifth condition in substance, and it was the last one found. + +Codex's clean result is often a bare 👍 carrying no commit. Two review rounds pulled in opposite +directions here, and the resolution is the interesting part. One round found that demanding +`commit_id` / `submitted_at` / `COMMENTED` from the closer made the path **unsatisfiable in the +ordinary clean case**, since a reaction has none of those. The next round found that accepting the +reaction made the head **author-asserted rather than provider-attested** — and a push landing while +the read is in flight would then let a pull request name a head the provider never saw, which is +precisely what binding the merge exists to prevent. + +Both are right, and the resolution is not a compromise between them: a reaction is a perfectly good +*lane result* and simply is not a *close*. What closes is any artifact the provider itself stamps +with the commit it read. Where the only output is an unstamped reaction the gate stays shut until a +stamped one exists, which costs an unmetered re-run and nothing else. + **Motive is deliberately not a test.** An earlier draft closed the gaming path with *"an ask made to spend the cap is not one of the two."* That was rejected on review for two reasons: a motive is not checkable by anyone, including the agent itself; and read strictly it **restores the deadlock in a From 37699bf336bf9faeeb34dd24de0abdac3418345b Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:56:14 -0500 Subject: [PATCH 06/43] fix(agents): reconcile the stamped-head rule with the files it contradicted Codex round 5, three P1s and a P3. All four are contradictions the round-4 fix introduced: the attestation rule landed in one place and three others still said the old thing. Fifth instance of that pattern in this PR. 1. AGENTS.md still required the closing read to be "posted by the provider on the pull request" one sentence above the bullet that permits a stamped run artifact, so the artifact alternative was rejected by the text introducing it. 2. The template's cap-spent option required "neither came back clean", which is stronger than the canonical condition ("neither came back clean AND its evidence still stands"). Where review 1 was clean at an earlier head, a material push re-armed it, and review 2 then found something, the option became unselectable with no third read available -- the deadlock again. 3. CONTRIBUTING.md applied submitted_at and COMMENTED/APPROVED to whichever review closes the gate, including a Codex close, which is exactly the unsatisfiability ADR-0065 records. Those fields are CodeRabbit's; a stamped artifact records what it actually emits. 4. The ADR said "three conditions" in two places while the normative list has four, understating the safeguard that keeps post-cap scope out. Counted as sub-floor on an agent-layer path, but fixed rather than dropped because it miscounts safety conditions in the record that defines them. --- .github/pull_request_template.md | 2 +- AGENTS.md | 5 +++-- CONTRIBUTING.md | 7 +++++-- ...a-spent-metered-cap-closes-on-the-unmetered-provider.md | 5 +++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 797b0930..cccf408c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -36,7 +36,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped - Codex closing read — only when the CodeRabbit line below records a spent cap: n/a | closed the gate (quote it exactly as the CodeRabbit gate is quoted — permalink, the **full 40-hex** head it read **which must be the final head**, when, and what it said; a re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged, and anything the closing read surfaced is disposed of above before it closes) - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify, a `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since traces to a recorded disposition, or to a clean `main` merge / formatting / comment or docstring edit / ADR renumber — so no new scope reached the merge unread by a metered provider; neither came back clean; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify, a `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since traces to a recorded disposition, or to a clean `main` merge / formatting / comment or docstring edit / ADR renumber — so no new scope reached the merge unread by a metered provider; neither came back clean **with its evidence still standing** — a clean review that a later material push re-armed is not a gate that already closed, and reading this as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) diff --git a/AGENTS.md b/AGENTS.md index 5e655fe2..ee6911ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -201,8 +201,9 @@ validity turns on it being the right test — must satisfy both. reviews stand on this PR — each one it submitted with a body, since a throttle, a quota refusal or a failed run reviewed nothing — and every finding they raised is **fixed, deferred-and-tracked, or dropped sub-floor** with the thread resolved, then a **fresh Codex read of the final head** closes - the gate in their place: a full read posted by the provider on the pull request, never an earlier - Codex pass re-quoted, since the head that pass read is not the head being merged. Anything that + the gate in their place: a full read recorded on the pull request — a review the provider posted, + or a verifiable run artifact of its own that names the commit it read — never an earlier Codex + pass re-quoted, since the head that pass read is not the head being merged. Anything that read surfaces is disposed of by those same three dispositions before it closes — the close is a *substitute for the clean pass*, not a lower bar than it. That review is then *the clean review* the merge binding below names. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b53c4baf..5c9316ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -267,9 +267,12 @@ Before requesting review / merging, confirm: review reached rather than an absence of one: record the review itself — permalink, the `commit_id` it read, **which must be the final head for whichever review closes the gate** — on the cap-spent path CodeRabbit's two are recorded at whatever heads they read, and it is - the Codex closing read that names the final one — `submitted_at` with a state of + the Codex closing read that names the final one. **The review-object fields are CodeRabbit's** + — `submitted_at` with a state of **`COMMENTED` or `APPROVED`** (a `PENDING` review has no `submitted_at` and is not a submitted - one; a `DISMISSED` one is a verdict *withdrawn* and proves nothing), and the + one; a `DISMISSED` one is a verdict *withdrawn* and proves nothing) — and a Codex close that is + a stamped run artifact rather than a posted review has none of them: record what it does emit, + including the commit **it** names, and do not demand fields that only exist on a review. Then the opening of its body. **A review of any earlier head does not close the gate, however clean it was** — where answering a finding moved the head, that review is evidence about a diff this one is no longer. It still stands as one of the two completed reviews, which is exactly what diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 69267561..5d4a0dc1 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -117,7 +117,7 @@ deleted rather than supplemented.** ### What makes it narrow -Three conditions shut the branch, and each is **readable off the pull request** rather than out of +Four conditions shut the branch, and each is **readable off the pull request** rather than out of an agent's account of its own reasoning: 1. **A refusal is not a spent cap.** A throttle, a quota refusal or a failed run reviewed nothing, @@ -234,7 +234,8 @@ stops holding**, and nothing in this repository would detect that. A second cost: §Review grows by roughly twenty lines in a file ADR-0064 deliberately shrank, and whose resident-context driver — *"`AGENTS.md` is read on every model call by every agent"* — argues -against every addition. The three shutting conditions are what that length buys, and they are the +against every addition. The four shutting conditions and the stamped-head rule are what that length +buys, and they are the part that cannot be compressed without making the branch a judgment call. **Reversible.** Prose only. No script, no workflow, no label, no ref namespace, no test fixture. A From 422c1dc6b03065449000d017d69a18dac7ddf059 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 12:40:18 -0500 Subject: [PATCH 07/43] fix(agents): let a stamped read rebind a head the clean review no longer names Codex round 6, one P1 and one P2, both correct. 1. The clean-review condition stranded a PR it was written to leave alone. Closing the gate and supplying a bindable head are two different jobs, and only the first was handled. A clean review at commit A closes the gate; a PERMITTED non-material push to B leaves that closure intact, since review evidence survives such a push -- but --match-head-commit still demands A, which is no longer the head, and the spent cap forbids a third metered read to rebind it. Condition 2 then shut the Codex close precisely BECAUSE the review was clean. A clean review followed by a formatting commit could therefore never merge at all. A stamped Codex read of the current head now supplies the binding without re-opening a gate that was never in question. What the close still may never do is stand in for a metered read that never happened. This is an older tension than this PR -- evidence surviving a non-material push has always sat awkwardly against an exact-head merge guard -- but the new branch made it reachable, so it is fixed here rather than deferred. 2. docs/PRD.md still said the mandatory CodeRabbit gate is what holds auto-merge, contradicting the terminus this PR introduces. The identical sentence in CONTRIBUTING.md was updated earlier in this PR and this one was not: the sixth time a fix landed in one file and not its mirrors. --- AGENTS.md | 11 +++++++++-- docs/PRD.md | 4 ++-- ...nt-metered-cap-closes-on-the-unmetered-provider.md | 10 ++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ee6911ed..9786c708 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -217,8 +217,15 @@ validity turns on it being the right test — must satisfy both. - **Four things shut that close, and each is readable off the pull request rather than out of your own account of why you did something.** A refusal is **not** a spent cap: it reviewed nothing, so it is a wait, and waiting is still what you do. If either completed review came back clean and its - evidence still stands under the non-material rule above, **that** review is the gate, it has - already closed, and none of this applies. The second completed review must have been asked + evidence still stands under the non-material rule above, **that** review is the gate and it has + already closed — but *closing the gate* and *supplying a head the merge can bind* are two + different jobs, and only the first is finished. Where a permitted non-material push has since + moved the head, a stamped Codex read of the current head supplies the SHA the merge binding + needs, and does not re-open a gate that was never in question. Without that, a clean review plus + a formatting commit would strand a PR outright: the evidence survives by one rule while + `--match-head-commit` demands a head that is no longer current, and the cap forbids asking the + metered provider again. What the close may never do is stand in for a metered read that never + happened. The second completed review must have been asked **after the first one's findings were disposed of** — by commits that answer them, or, where the disposition is a deferral or a sub-floor drop, by the replies and resolutions that record it. Asking twice at one head with nothing answered in between is one review asked twice and buys the diff --git a/docs/PRD.md b/docs/PRD.md index cb6a8445..48d85473 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1173,8 +1173,8 @@ the ADR-0052 run was a lease that only a sleeping human could renew. Every agent is a peer: it claims one issue, works one isolated worktree/branch/PR, opens the review lane on a draft, and hands off. Auto-merge — bound to the reviewed head with `--match-head-commit` — is armed at the **end** of that -lane by whoever completes it, never on the draft, since the mandatory CodeRabbit gate is not a required check and -nothing else would hold the merge. No agent waits on another, and no agent merges on another's +lane by whoever completes it, never on the draft, since the mandatory review gate — CodeRabbit, or the Codex close +when its cap is spent (ADR-0065) — is not a required check and nothing else would hold the merge. No agent waits on another, and no agent merges on another's behalf. ADR-0052's coordinator, leases, run records and guarded-merge monopoly are retired, not merely superseded. **Label taxonomy** (prefixed namespaces, so labels group and filter cleanly): diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 5d4a0dc1..e2a579de 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -125,6 +125,16 @@ an agent's account of its own reasoning: still freezes the pull request — nothing reviewed, so nothing opens the close. 2. **A clean review is the gate, and it has already closed.** If either completed review came back clean and its evidence still stands under the non-material rule, the branch never opens. + + With one carve-out that review found, and it exposes a tension older than this record. + *Closing the gate* and *supplying a head the merge can bind* are two different jobs. A clean + review at commit A closes the gate; a permitted non-material push to B leaves that closure intact + — `AGENTS.md` says review evidence survives such a push — while `--match-head-commit` still + demands A, which is no longer the head. With the cap spent there is no third metered read to + rebind it, so a clean review followed by a formatting commit **stranded the pull request + outright**. A stamped Codex read of B supplies the binding without re-opening a gate that was + never in question. The rule that must not bend is the other one: the close may never stand in + for a metered read that never happened. 3. **The second review must have been asked after the first one's findings were disposed of** — by commits that answer them, or, where the disposition is a deferral or a sub-floor drop, by the replies and resolutions that record it. Asking twice at one head with nothing answered in From 12b6b8be5c40117d9975d514904b20eadb538cf3 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 21:06:38 -0500 Subject: [PATCH 08/43] fix(agents): give the rebinding path a template state and the summaries their preconditions Codex round 5, two P1 findings, both the drift this PR exists to prevent. `.github/pull_request_template.md` had no truthful state for the path `AGENTS.md:219-226` added last commit: a clean CodeRabbit review at head A whose evidence still stands, a permitted non-material push to B, and a stamped Codex read of B supplying the SHA `--match-head-commit` needs. The clean state wanted CodeRabbit itself to have read B; the cap-spent state wanted no clean evidence standing; and the Codex line was admitted only by the latter. A rule with no field in the template is a rule that will not be evidenced -- this PR's own claim, and it was false about its own newest rule. The Codex line now names two outcomes and keeps them apart: closing the gate, and rebinding the head, only the first of which is a close. `CONTRIBUTING.md` and `docs/PRD.md` stated the close as "cap spent + findings disposed of" and dropped two of the four conditions that shut it -- that the second review was asked only after the first one's findings were disposed of, and that nothing but disposal landed after the commit that second review read. Both authorise the close in cases `AGENTS.md` refuses, and since `AGENTS.md:9-10` tells a worker to stop when those files conflict, the summaries would have stranded exactly the pull request this unblocks. `docs/PRD.md` carried the second condition in one of its two passages and neither in the other. `.greptile/README.md` states the close in one clause; it now defers the conditions explicitly, the way `.agents/skills/tether-worker/SKILL.md` already did, so no summary is left implying it carries them. Local gates: 2833 passed, 19 skipped, 34 deselected. --- .github/pull_request_template.md | 4 ++-- .greptile/README.md | 4 ++-- CONTRIBUTING.md | 17 ++++++++++++++++- docs/PRD.md | 13 +++++++++---- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cccf408c..797cb999 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -34,9 +34,9 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Risk rationale: - Final head SHA: - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped -- Codex closing read — only when the CodeRabbit line below records a spent cap: n/a | closed the gate (quote it exactly as the CodeRabbit gate is quoted — permalink, the **full 40-hex** head it read **which must be the final head**, when, and what it said; a re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged, and anything the closing read surfaced is disposed of above before it closes) +- Codex closing read — whenever the merge needs a head no metered review names. That is **two** cases and only the first is a *close*: n/a | **closed the gate** — the CodeRabbit line below records a spent cap, and this read stands in for the clean pass | **rebound the head** — the CodeRabbit line below records a clean review whose evidence still stands, and a permitted non-material push has since moved the head, so the gate closed there and this read only supplies the SHA the merge can bind. Either way quote it exactly as the CodeRabbit gate is quoted — permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said; a re-quoted earlier Codex pass is **not** one, since the head that pass read is not the head being merged, and a bare 👍 carrying no commit is not one either, because the head would then be yours to assert rather than the provider's to attest. Anything a *closing* read surfaces is disposed of above before it closes; a *rebinding* read re-opens nothing that was never in question - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify, a `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since traces to a recorded disposition, or to a clean `main` merge / formatting / comment or docstring edit / ADR renumber — so no new scope reached the merge unread by a metered provider; neither came back clean **with its evidence still standing** — a clean review that a later material push re-armed is not a gate that already closed, and reading this as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify **unless every push since it is one of the non-material exceptions**, in which case its evidence still stands, this line is what closed the gate, and the *rebound the head* option above supplies the SHA the merge binds — the only case where the gate's head and the merge's head are different commits, and the reason those are two lines rather than one. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since traces to a recorded disposition, or to a clean `main` merge / formatting / comment or docstring edit / ADR renumber — so no new scope reached the merge unread by a metered provider; neither came back clean **with its evidence still standing** — a clean review that a later material push re-armed is not a gate that already closed, and reading this as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) diff --git a/.greptile/README.md b/.greptile/README.md index 471b7843..306399d6 100644 --- a/.greptile/README.md +++ b/.greptile/README.md @@ -99,5 +99,5 @@ reviews per metered provider** in `AGENTS.md` §Review applies here as everywher ceiling rather than a second credit to plan on, so ask again only if the first found something blocking and the seat still has budget. A request that produced no review — a throttle, a quota refusal, a failed run — is not an ask and spends nothing. CodeRabbit, not Greptile, is the metered gate — and when CodeRabbit's own -cap is spent with every finding disposed of, an unmetered Codex read closes it. Greptile is never -the closer either way. +cap is spent with every finding disposed of, an unmetered Codex read closes it — §Review carries the +conditions on that, and this page states none of them. Greptile is never the closer either way. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c9316ad..62b0b85e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -430,7 +430,22 @@ credit — a standard review costs one, a TREX review three. Two is the shared c again only if the first found something blocking and the seat still has budget. **A spent cap is not a stuck PR**: when every finding those two reviews raised is disposed of and every thread resolved, a fresh Codex read of the final head closes the gate in their place, so the -PR finishes on an unmetered read rather than on a maintainer. Nothing counts this for you; the +PR finishes on an unmetered read rather than on a maintainer. Two preconditions come with +that, and `AGENTS.md` §Review states them in full — a summary here that drops them would +authorise the close in cases the contract shuts, and a contributor reading the two together +would have to stop. **The cap has to have been spent on two reads of two different states of +the diff**: the second review must have been asked only *after* the first one's findings were +disposed of, by commits that answer them or by the replies and resolutions recording a +deferral or a drop. The test is the disposal and not a new commit — a review answered wholly +on the record moves no head, and demanding one would re-create the deadlock this removes — +but asking twice at one head with nothing answered in between is one review asked twice, and +it buys the close nothing. **And nothing but disposal may land after the cap is spent**: +every hunk added after the commit the second completed review actually read — its +`commit_id`, never its `submitted_at`, since a material push landing while that review is +still running is a push it never saw — must trace to a recorded disposition or to one of the +non-material exceptions above. New scope pushed past that point is scope no metered provider +will ever read, and the close is a third opinion on a twice-read diff, never a first opinion +on an unread one. Nothing counts this for you; the merged history is auditable and you are trusted with it. On agent-layer paths (`.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` and the agent test modules) a finding below the severity floor is **dropped rather than tracked**, because there the diff --git a/docs/PRD.md b/docs/PRD.md index 48d85473..3f8d889d 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1063,8 +1063,12 @@ nothing blocking; it is the free provider, and throttling it bought nothing but one Greptile review**, if the seat has budget — a *review*, not a credit, since a standard one costs one credit and a TREX one three; then ready-for-review if it is not already; then **CodeRabbit with no actionable comments, which is the last metered gate before merge — and when that two-review cap is spent with every finding -disposed of and every thread resolved, a fresh Codex read of the final head closes the gate in its place, -so a reviewed PR finishes on an unmetered read rather than on a maintainer (ADR-0065)**. The round +disposed of and every thread resolved, the second review having been asked only after the first one's +findings were disposed of and nothing but disposal having landed since the commit that second review read, +a fresh Codex read of the final head closes the gate in its place, +so a reviewed PR finishes on an unmetered read rather than on a maintainer (ADR-0065)**. `AGENTS.md` +§Review is the operative statement of those preconditions; a summary that drops them would open the close +in cases the contract shuts. The round ledger that used to count this is gone — ADR-0064 retired it along with the launcher that consumed it, leaving **at most two completed reviews per metered provider** as a convention a worker keeps rather than a counter that publishes labels — **Codex is @@ -1096,8 +1100,9 @@ governance text itself (`AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, this docume `.greptile/**`) are material — the list is *every file that states a rule*, because a push that changes what the gate requires must not keep evidence gathered under the old requirement. A material push re-arms the review, and a PR gets **at most two completed reviews per metered provider**, Codex being unmetered and uncapped — needing a third usually means the issue was scoped too large. The lane does **not** stop for the maintainer: when the cap is -spent with every finding disposed of and no new scope landed since the commit the second review read, a fresh Codex read of -the final head closes the gate instead (ADR-0065). +spent on two reads of two *different* states of the diff — the second asked only after the first one's findings were +disposed of — with every finding disposed of and no new scope landed since the commit the second review read, a fresh +Codex read of the final head closes the gate instead (ADR-0065). That bound is a **convention a worker keeps, not a counter that publishes labels.** ADR-0064 retired the round ledger, the `agent:round-*` / `agent:review-capped` / `agent:gate-blocked` labels and the launcher that consumed them, after From 1932b093b4445a690ef38ec225159f8c48ec1cdc Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 21:17:53 -0500 Subject: [PATCH 09/43] fix(agents): stop the scope freeze from shutting on the closing read's own fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex round 6, two P1 and one P2 — and the P1 on `AGENTS.md` is the third time a draft of this rule has re-created the deadlock it removes, which is now recorded in ADR-0065 as the pattern rather than as three incidents. `AGENTS.md:234-236` froze scope after the cap by requiring every hunk to "answer a finding those reviews recorded". The bullet above it requires the closing read to dispose of whatever it surfaces BEFORE it closes -- and that fix is a material push answering no CodeRabbit finding, so the freeze disqualified it, and the cap forbade asking the metered provider again. The branch was reachable only by the closing reads that found something, which is the worst possible selection. The allowed set now includes findings a closing read itself raised, and the remedy for one is another stamped read of the head it produced rather than a narrower set: each round is still read by the provider that closes it, and unread scope stays out because a closing read cannot raise a finding about a hunk it never saw. `CONTRIBUTING.md` and `docs/adr/0065` carry the same widening. `.agents/skills/tether-worker/agents/openai.yaml` restated the close as "cap spent and every finding disposed of" and dropped both safeguards. It is injected as the Codex lane's default prompt and read by no test, so a worker launched from it would have been briefed on a laxer contract than the one on main with no signal at all. It now defers to §Review and states no condition itself, which is the discipline SKILL.md and .greptile/README.md already use. `.github/pull_request_template.md` required "the provider that closed the gate reviewed the final head", which is false on the rebinding path the previous commit made valid: there a clean review closes at an earlier head and Codex only stamps the final one. Labelled P2, fixed rather than deferred because it falsifies a claim this PR introduces -- and because it is the same defect as the P1 fixed one commit ago, one line further down the same file. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green; openai.yaml re-parsed with PyYAML. --- .agents/skills/tether-worker/agents/openai.yaml | 2 +- .github/pull_request_template.md | 2 +- AGENTS.md | 12 +++++++++++- CONTRIBUTING.md | 9 +++++++-- ...metered-cap-closes-on-the-unmetered-provider.md | 14 +++++++++++++- 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/.agents/skills/tether-worker/agents/openai.yaml b/.agents/skills/tether-worker/agents/openai.yaml index 38245c9d..80c18e11 100644 --- a/.agents/skills/tether-worker/agents/openai.yaml +++ b/.agents/skills/tether-worker/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Tether Worker" short_description: "Claim one accepted issue and open its draft PR onto the review lane" - default_prompt: "Use $tether-worker to claim issue #N with .agents/bin/claim.py, implement it in an isolated worktree, open a DRAFT PR, request the first Codex review, write the lane state into the PR body, and exit. Do not arm auto-merge: that happens at the end of the review lane, once the gate is closed — by a clean CodeRabbit review, or by a Codex read of the final head when CodeRabbit's two-review cap is spent and every finding is disposed of." + default_prompt: "Use $tether-worker to claim issue #N with .agents/bin/claim.py, implement it in an isolated worktree, open a DRAFT PR, request the first Codex review, write the lane state into the PR body, and exit. Do not arm auto-merge: that happens at the end of the review lane, once the gate is closed — by a clean CodeRabbit review, or by a Codex read of the final head when CodeRabbit's two-review cap is spent. AGENTS.md §Review carries the conditions on that second path and this prompt states none of them, so read them there rather than inferring them from this line." diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 797cb999..3dc0e0bb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -59,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head must come from the provider's own artifact (a posted review, or a run record that names the commit it read), never from you: a bare 👍 carries no commit, so it is a valid lane result and **cannot** close the gate, since an author-supplied head could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the final head carries a provider's own read of it** — normally that is the provider that closed the gate, and on the rebinding path above it is not: there a clean review closed the gate at an earlier head, only non-material pushes have landed since, and the Codex read that stamps the final head is a different provider from the one that closed. Those two jobs coincide in every case but that one, and this box asks for both rather than conflating them, because demanding the *closer* reach the final head would make the rebinding path unattestable while lines 37 and 39 declare it valid — where the read of the final head is Codex's, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head must come from the provider's own artifact (a posted review, or a run record that names the commit it read), never from you: a bare 👍 carries no commit, so it is a valid lane result and **cannot** close the gate, since an author-supplied head could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/AGENTS.md b/AGENTS.md index 9786c708..b34e7a15 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -233,7 +233,17 @@ validity turns on it being the right test — must satisfy both. record moves no head, so demanding one would re-create the deadlock this rule exists to remove. And **nothing but disposal may land after the cap is spent**: everything added after the commit the second completed review actually *read* — its `commit_id`, never its `submitted_at` — must - answer a finding those reviews recorded, or be one of the non-material exceptions above. Anchor it + answer a finding already on this pull request's record — one of those two reviews', **or one a + closing read has itself raised** — or be one of the non-material exceptions above. A closing + read's own findings have to count, and this is not a courtesy to it: the bullet above requires + everything that read surfaces to be disposed of before it closes, and that disposal is a material + push answering no CodeRabbit finding at all. An allowed set holding only *their* findings would + therefore disqualify every closing read that found anything, while the cap forbade asking the + metered provider again — this rule's own failure mode, re-created one level down and reachable + only by the closing reads that did their job. What follows such a fix is another stamped read of + the head it produced, never a narrower allowed set: each round is still read by the provider that + closes it, which is the property this condition exists to protect, and unread scope stays shut out + because a closing read cannot raise a finding about a hunk it never saw. Anchor it at the commit and not the clock, because a material push landing while that review is still running is a push it never saw, and a time-anchored window would wave it through. **The unit is the change, not the commit**: a commit that fixes a recorded finding *and* carries an unrelated diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 62b0b85e..4f8875f2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -442,8 +442,13 @@ but asking twice at one head with nothing answered in between is one review aske it buys the close nothing. **And nothing but disposal may land after the cap is spent**: every hunk added after the commit the second completed review actually read — its `commit_id`, never its `submitted_at`, since a material push landing while that review is -still running is a push it never saw — must trace to a recorded disposition or to one of the -non-material exceptions above. New scope pushed past that point is scope no metered provider +still running is a push it never saw — must answer a finding already on the pull request's +record, **including one the closing read itself raised**, or be one of the non-material +exceptions above. That inclusion is load-bearing rather than generous: the closing read has +to dispose of whatever it surfaces before it closes, and that fix answers no CodeRabbit +finding, so a set holding only theirs would disqualify every closing read that found +something — the same deadlock, one level down. What follows such a fix is another stamped +read of the head it produced. New scope pushed past that point is scope no metered provider will ever read, and the close is a third opinion on a twice-read diff, never a first opinion on an unread one. Nothing counts this for you; the merged history is auditable and you are trusted with it. On agent-layer paths diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index e2a579de..577c24b9 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -150,7 +150,8 @@ an agent's account of its own reasoning: checked it. 4. **Nothing but disposal may land after the cap is spent.** Every commit between the second - completed review and the closing read must answer a finding those reviews recorded, or be one of + completed review and the closing read must answer a finding already on the pull request's + record — one of those two reviews', or one a closing read has itself raised — or be one of the existing non-material exceptions. This is the condition that makes the *"third opinion on a twice-read diff"* claim below true @@ -167,6 +168,17 @@ an agent's account of its own reasoning: recorded finding and carries an unrelated hunk alongside it satisfies any per-commit phrasing while smuggling in exactly the scope the condition excludes. + And the allowed set has to include **the closing read's own findings**, which the first three + drafts of this condition did not. A sixth Codex review of this record's pull request found it, + and it is the third time a draft here re-created the deadlock it removes: the bullet above + requires the closing read to dispose of whatever it surfaces *before* it closes, and that fix is + a material push answering no CodeRabbit finding — so a set holding only *their* findings shut the + close against every closing read that found anything, while the cap forbade asking the metered + provider again. The branch was reachable only by the closing reads doing their job, which is the + worst possible selection. The remedy is not a narrower set but another stamped read of the head + the fix produced: each round is still read by the provider that closes it, and unread scope stays + excluded, because a closing read cannot raise a finding about a hunk it never saw. + ### The closing read must stamp its own head The SHA that reaches `--match-head-commit` must come from something the **provider** wrote. This is From 9a617f62b2fc323b9a98b181a90f44668541e996 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 21:33:23 -0500 Subject: [PATCH 10/43] fix(agents): stop the summaries contradicting the two paths they summarise Codex round 7, two P1 and two P2. Both P1s are contradictions between a summary and `AGENTS.md`, which under `AGENTS.md:9-10` is a stop-and-ask for whoever reads them together -- so each one strands the pull request it was meant to unblock. `CONTRIBUTING.md` said "a review of ANY earlier head does not close the gate, HOWEVER CLEAN it was". Its own justification is about a push that answered a finding, but the claim was absolute, and absolute it denies the rebinding path added two commits ago: a clean review at A whose evidence survives a formatting commit to B still closed the gate, and the Codex read of B only supplies the SHA. Narrowed to a material push, with the surviving case stated rather than implied. `docs/PRD.md` -- and `CONTRIBUTING.md` in the same words, which Codex flagged in one place and is a class of two -- said the cap must be spent on "two reads of two DIFFERENT states of the diff". That was mine, added in the previous commit while fixing something else, and it re-creates the deadlock for every PR whose first-review findings are all deferred or dropped: disposal on the record moves no head, so the second review is at the SAME commit, which `AGENTS.md:228-233` allows in as many words. The test is the disposition, never a differing diff. P2, fixed rather than deferred because it falsifies a claim this PR introduces: `CONTRIBUTING.md` scoped the review-object fields to CodeRabbit and then still demanded its body opening and its absent `Actionable comments posted:` marker from a Codex close, which is a run artifact that has neither -- leaving the close path with no recordable evidence at all. P2, deferred: the template's Greptile line offers only "reviewed the final head", so a Greptile review that read an earlier head has no truthful state and the checkbox prices a re-review at one to three credits. Pre-existing, about a provider this PR does not touch. Deferred: tracked in #449. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- CONTRIBUTING.md | 36 +++++++++++++++++++++++------------- docs/PRD.md | 7 ++++--- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4f8875f2..6714779c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -272,13 +272,22 @@ Before requesting review / merging, confirm: **`COMMENTED` or `APPROVED`** (a `PENDING` review has no `submitted_at` and is not a submitted one; a `DISMISSED` one is a verdict *withdrawn* and proves nothing) — and a Codex close that is a stamped run artifact rather than a posted review has none of them: record what it does emit, - including the commit **it** names, and do not demand fields that only exist on a review. Then the - opening of its body. **A review of any earlier head does not close the gate, however clean it - was** — where answering a finding moved the head, that review is evidence about a diff this - one is no longer. It still stands as one of the two completed reviews, which is exactly what - the cap-spent path relies on: each recorded at the head it read, with the Codex closing read - covering the final one. The clean verdict is written by the `Actionable comments posted:` line being - **absent** rather than reading `0`. Neither silence + including the commit **it** names, and do not demand fields that only exist on a review. + **The body convention is CodeRabbit's too** — the opening of its body, where the clean verdict + is written by the `Actionable comments posted:` line being **absent** rather than reading `0`. + A Codex artifact has no such line and never will; what stands in its place is the verdict the + run actually emitted, quoted, against the commit it names. Asking a Codex close for + CodeRabbit's marker would leave that path with no recordable evidence at all. + **A review of an earlier head does not close the gate where a material push has moved the head + since** — answering a finding is the ordinary such push, and that review is then evidence about + a diff this one is no longer. It still stands as one of the two completed reviews, which is + exactly what the cap-spent path relies on: each recorded at the head it read, with the Codex + closing read covering the final one. Where **every** push since is one of the non-material + exceptions the opposite holds and the gate never re-opened: that review's evidence survives by + the rule above, and a stamped Codex read of the current head supplies the SHA + `--match-head-commit` needs without standing in for a gate that was never in question. Read + absolutely, this sentence would strand a clean review followed by a formatting commit, which is + the case `AGENTS.md` §Review names and refuses to strand. Neither silence nor a green `CodeRabbit` status check is the gate; both are also what a request that reviewed **nothing** leaves behind (see the full-review command below). Greptile is optional, and its absence for want of credits is recorded rather than excused as a review. Blocking @@ -433,13 +442,14 @@ thread resolved, a fresh Codex read of the final head closes the gate in their p PR finishes on an unmetered read rather than on a maintainer. Two preconditions come with that, and `AGENTS.md` §Review states them in full — a summary here that drops them would authorise the close in cases the contract shuts, and a contributor reading the two together -would have to stop. **The cap has to have been spent on two reads of two different states of -the diff**: the second review must have been asked only *after* the first one's findings were +would have to stop. **The cap has to have been spent on two reads with a disposition between +them**: the second review must have been asked only *after* the first one's findings were disposed of, by commits that answer them or by the replies and resolutions recording a -deferral or a drop. The test is the disposal and not a new commit — a review answered wholly -on the record moves no head, and demanding one would re-create the deadlock this removes — -but asking twice at one head with nothing answered in between is one review asked twice, and -it buys the close nothing. **And nothing but disposal may land after the cap is spent**: +deferral or a drop. The test is the disposal and **not** a differing diff — a review answered +wholly on the record moves no head, so a second review at that same commit is in order, and +demanding a changed one would re-create the deadlock this removes for every PR whose findings +were all deferred or dropped. What buys the close nothing is asking twice at one head with +nothing answered in between: that is one review asked twice. **And nothing but disposal may land after the cap is spent**: every hunk added after the commit the second completed review actually read — its `commit_id`, never its `submitted_at`, since a material push landing while that review is still running is a push it never saw — must answer a finding already on the pull request's diff --git a/docs/PRD.md b/docs/PRD.md index 3f8d889d..f738458c 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1100,9 +1100,10 @@ governance text itself (`AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, this docume `.greptile/**`) are material — the list is *every file that states a rule*, because a push that changes what the gate requires must not keep evidence gathered under the old requirement. A material push re-arms the review, and a PR gets **at most two completed reviews per metered provider**, Codex being unmetered and uncapped — needing a third usually means the issue was scoped too large. The lane does **not** stop for the maintainer: when the cap is -spent on two reads of two *different* states of the diff — the second asked only after the first one's findings were -disposed of — with every finding disposed of and no new scope landed since the commit the second review read, a fresh -Codex read of the final head closes the gate instead (ADR-0065). +spent — the second review asked only after the first one's findings were **disposed of**, which is a test on the +disposition and not on the diff, since a finding answered wholly on the record moves no head and a second review at that +same commit is therefore in order — with every finding disposed of and no new scope landed since the commit the second +review read, a fresh Codex read of the final head closes the gate instead (ADR-0065). That bound is a **convention a worker keeps, not a counter that publishes labels.** ADR-0064 retired the round ledger, the `agent:round-*` / `agent:review-capped` / `agent:gate-blocked` labels and the launcher that consumed them, after From e5416453230f24cc2b559240654e77e8e875eeee Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 21:48:09 -0500 Subject: [PATCH 11/43] docs(prd): stop restating the close's conditions and point at the one file that sets them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex round 8, two P1 and one P2 — and the two P1s are the same defect pointing opposite ways in one file, which is the signal to stop writing summaries. `docs/PRD.md` carried two restatements of the post-cap condition. One said "no new scope landed", which admits a material but issue-scoped hunk that no metered provider will ever read. The other said "nothing but disposal", which refuses the non-material exceptions AGENTS.md explicitly permits. Loose in one place, tight in the other, both wrong, both a stop-and-ask under AGENTS.md:9-10 -- so each one strands the pull request the rule exists to unblock, in opposite directions. Four consecutive rounds have now found a defect of exactly this shape, three of them in text written to fix the previous one. The surfaces converted to POINTERS have all held: openai.yaml, .greptile/README.md, SKILL.md, CONTRIBUTING.md's lane summary. Every surface RE-QUALIFIED has broken again on the next read. So the PRD now states that the close exists and that AGENTS.md §Review is the only statement of its conditions, and restates none of them. That is a deletion, not another qualification, and it removes the defect class from this file rather than moving it. P2, fixed: CONTRIBUTING.md said gate-closing and final-head-naming come apart "on two paths". They come apart on ONE. On the cap-spent path the Codex closing read does both at once; only the rebinding path splits them. That sentence was mine, from the previous commit's sweep -- the same way round 7's P1 was mine from round 6's fix. Recorded rather than quietly corrected, because the pattern is the point. Also swept, same class, in the two places Codex had not named: CONTRIBUTING.md's "which must be the final head for whichever review closes the gate" was false on the rebinding path, and its lane summary restated the close without conditions. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- CONTRIBUTING.md | 24 ++++++++++++++++-------- docs/PRD.md | 20 +++++++++----------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6714779c..27b8b775 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -265,9 +265,13 @@ Before requesting review / merging, confirm: and every finding is disposed of, the Codex read of the final head that closed the gate in its place** — and that is a verdict a completed review reached rather than an absence of one: record the review itself — permalink, the - `commit_id` it read, **which must be the final head for whichever review closes the gate** - — on the cap-spent path CodeRabbit's two are recorded at whatever heads they read, and it is - the Codex closing read that names the final one. **The review-object fields are CodeRabbit's** + `commit_id` it read, **which must be the final head for whichever read the merge binds to** — + closing the gate and naming the head the merge binds are **one act on every path but the + rebinding one**. On the cap-spent path the Codex closing read does both at once, while + CodeRabbit's two completed reviews are recorded at whatever heads they read. Only on the + rebinding path do the two come apart: a clean CodeRabbit review closed the gate at an earlier + head, every push since was non-material, and a stamped Codex read names the final one without + closing anything. **The review-object fields are CodeRabbit's** — `submitted_at` with a state of **`COMMENTED` or `APPROVED`** (a `PENDING` review has no `submitted_at` and is not a submitted one; a `DISMISSED` one is a verdict *withdrawn* and proves nothing) — and a Codex close that is @@ -335,7 +339,8 @@ ready PR whose reason is recorded; then optionally one metered Greptile **review seat has budget, a review being one credit as a standard and three as a TREX; then CodeRabbit with no actionable comments, which is the last metered gate before merge — and when that cap is spent with every finding disposed of, a fresh Codex read of the final head -closes the gate in its place rather than a maintainer doing it.** **Open as a draft and get it green there** — every +closes the gate in its place rather than a maintainer doing it — under conditions `AGENTS.md` +§Review sets and this summary does not restate.** **Open as a draft and get it green there** — every required check runs on a draft, so the diff reaches fully green before anyone is asked to read it, and that is what makes the sequence affordable rather than a policy nobody keeps. Opening ready is not forbidden, but it spends a metered provider on a diff no cheap one has @@ -439,10 +444,13 @@ credit — a standard review costs one, a TREX review three. Two is the shared c again only if the first found something blocking and the seat still has budget. **A spent cap is not a stuck PR**: when every finding those two reviews raised is disposed of and every thread resolved, a fresh Codex read of the final head closes the gate in their place, so the -PR finishes on an unmetered read rather than on a maintainer. Two preconditions come with -that, and `AGENTS.md` §Review states them in full — a summary here that drops them would -authorise the close in cases the contract shuts, and a contributor reading the two together -would have to stop. **The cap has to have been spent on two reads with a disposition between +PR finishes on an unmetered read rather than on a maintainer. **`AGENTS.md` §Review sets four +conditions on that and is the operative statement of all of them**; the other two are above — +a provider that refused reviewed nothing, so a spent cap is not what an unavailable CodeRabbit +leaves behind, and a review that came back clean with its evidence still standing is already +the gate. The two restated here are the two a summary most easily drops, and dropping either +would authorise the close in a case the contract shuts, which a contributor reading the two +documents together would have to stop over. **The cap has to have been spent on two reads with a disposition between them**: the second review must have been asked only *after* the first one's findings were disposed of, by commits that answer them or by the replies and resolutions recording a deferral or a drop. The test is the disposal and **not** a differing diff — a review answered diff --git a/docs/PRD.md b/docs/PRD.md index f738458c..50462374 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1062,13 +1062,13 @@ has not — until it surfaces nothing blocking; it is the free provider, and throttling it bought nothing but slower convergence. Then **optionally one Greptile review**, if the seat has budget — a *review*, not a credit, since a standard one costs one credit and a TREX one three; then ready-for-review if it is not already; then **CodeRabbit with no actionable comments, -which is the last metered gate before merge — and when that two-review cap is spent with every finding -disposed of and every thread resolved, the second review having been asked only after the first one's -findings were disposed of and nothing but disposal having landed since the commit that second review read, -a fresh Codex read of the final head closes the gate in its place, -so a reviewed PR finishes on an unmetered read rather than on a maintainer (ADR-0065)**. `AGENTS.md` -§Review is the operative statement of those preconditions; a summary that drops them would open the close -in cases the contract shuts. The round +which is the last metered gate before merge — and when that two-review cap is spent, a fresh Codex read +of the final head closes the gate in its place, so a reviewed PR finishes on an unmetered read rather +than on a maintainer (ADR-0065)**. **`AGENTS.md` §Review sets the conditions that close is subject to +and is the only statement of them; this document deliberately restates none.** Four drafts of this +paragraph tried to summarise them and each admitted or refused a case the contract does not — a +restatement that is wrong in either direction is worse than a pointer, because `AGENTS.md` tells a +worker to stop when these two files disagree, so the summary strands the pull request either way. The round ledger that used to count this is gone — ADR-0064 retired it along with the launcher that consumed it, leaving **at most two completed reviews per metered provider** as a convention a worker keeps rather than a counter that publishes labels — **Codex is @@ -1100,10 +1100,8 @@ governance text itself (`AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, this docume `.greptile/**`) are material — the list is *every file that states a rule*, because a push that changes what the gate requires must not keep evidence gathered under the old requirement. A material push re-arms the review, and a PR gets **at most two completed reviews per metered provider**, Codex being unmetered and uncapped — needing a third usually means the issue was scoped too large. The lane does **not** stop for the maintainer: when the cap is -spent — the second review asked only after the first one's findings were **disposed of**, which is a test on the -disposition and not on the diff, since a finding answered wholly on the record moves no head and a second review at that -same commit is therefore in order — with every finding disposed of and no new scope landed since the commit the second -review read, a fresh Codex read of the final head closes the gate instead (ADR-0065). +spent, a fresh Codex read of the final head closes the gate instead (ADR-0065), subject to conditions +`AGENTS.md` §Review states and this document, again deliberately, does not. That bound is a **convention a worker keeps, not a counter that publishes labels.** ADR-0064 retired the round ledger, the `agent:round-*` / `agent:review-capped` / `agent:gate-blocked` labels and the launcher that consumed them, after From f4d895623212976d7a181653708d0b41a10dc42d Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 21:59:03 -0500 Subject: [PATCH 12/43] fix(agents): close the rebinding read's own deadlock and the drift this PR left behind Codex round 9: zero P1 -- the first clean round on the severity axis, so the unmetered leg has surfaced nothing blocking. Three P2s follow, all fixed rather than deferred or dropped, because each one falsifies a claim this PR itself makes, which the severity test says is serious whatever it is labelled. A rebinding read that finds something serious was deadlocked. Fixing it is a material push, so the clean review it was rebinding past falls -- and the post-cap allowed set admitted findings from the two CodeRabbit reviews or from a CLOSING read, never from a rebinding one, while no third metered review was available. AGENTS.md now says a rebinding read that surfaces something serious stops being one: the PR is on the cap-spent path and the next stamped read is a closing read. That needs no third category in the allowed set, which is why it is said there rather than enumerated here. This PR's central claim is that a rule fixing a deadlock must not be able to re-create one; leaving this would have falsified it. The PR template's closing-read field triggered on "the merge needs a head no metered review names" -- my wording, two commits ago. When both CodeRabbit reviews land on the final head and both find things that are deferred or dropped, a metered review DOES name the merge head, so the field read `n/a` while the contract still required a close. It now triggers on a spent cap or a rebinding need, which is what it was always meant to mean. AGENTS.md gained a clarification in this PR that author-side names whose judgement it is and not which machine ran it -- and CONTRIBUTING.md and docs/PRD.md still carried the old absolute "local review does not satisfy the gate". That is a drift surface this PR opened and did not sweep, in the same breath as claiming all of them move together. Both mirrors now carry the distinction. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .github/pull_request_template.md | 2 +- AGENTS.md | 7 ++++++- CONTRIBUTING.md | 7 +++++-- docs/PRD.md | 6 ++++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3dc0e0bb..498c5f20 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -34,7 +34,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Risk rationale: - Final head SHA: - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped -- Codex closing read — whenever the merge needs a head no metered review names. That is **two** cases and only the first is a *close*: n/a | **closed the gate** — the CodeRabbit line below records a spent cap, and this read stands in for the clean pass | **rebound the head** — the CodeRabbit line below records a clean review whose evidence still stands, and a permitted non-material push has since moved the head, so the gate closed there and this read only supplies the SHA the merge can bind. Either way quote it exactly as the CodeRabbit gate is quoted — permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said; a re-quoted earlier Codex pass is **not** one, since the head that pass read is not the head being merged, and a bare 👍 carrying no commit is not one either, because the head would then be yours to assert rather than the provider's to attest. Anything a *closing* read surfaces is disposed of above before it closes; a *rebinding* read re-opens nothing that was never in question +- Codex closing read — required in **two** cases, and only the first is a *close*: whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read; and whenever a permitted non-material push has moved the head past the clean review that closed the gate. `n/a` is for neither of those — a clean CodeRabbit review at the final head, and nothing since. So: n/a | **closed the gate** — the CodeRabbit line below records a spent cap, and this read stands in for the clean pass | **rebound the head** — the CodeRabbit line below records a clean review whose evidence still stands, and a permitted non-material push has since moved the head, so the gate closed there and this read only supplies the SHA the merge can bind. Either way quote it exactly as the CodeRabbit gate is quoted — permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said; a re-quoted earlier Codex pass is **not** one, since the head that pass read is not the head being merged, and a bare 👍 carrying no commit is not one either, because the head would then be yours to assert rather than the provider's to attest. Anything a *closing* read surfaces is disposed of above before it closes; a *rebinding* read re-opens nothing that was never in question - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter - **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify **unless every push since it is one of the non-material exceptions**, in which case its evidence still stands, this line is what closed the gate, and the *rebound the head* option above supplies the SHA the merge binds — the only case where the gate's head and the merge's head are different commits, and the reason those are two lines rather than one. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since traces to a recorded disposition, or to a clean `main` merge / formatting / comment or docstring edit / ADR renumber — so no new scope reached the merge unread by a metered provider; neither came back clean **with its evidence still standing** — a clean review that a later material push re-armed is not a gate that already closed, and reading this as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass diff --git a/AGENTS.md b/AGENTS.md index b34e7a15..bd635323 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -224,7 +224,12 @@ validity turns on it being the right test — must satisfy both. needs, and does not re-open a gate that was never in question. Without that, a clean review plus a formatting commit would strand a PR outright: the evidence survives by one rule while `--match-head-commit` demands a head that is no longer current, and the cap forbids asking the - metered provider again. What the close may never do is stand in for a metered read that never + metered provider again. **A rebinding read that surfaces something serious stops being one.** + Fixing it is a material push, the clean review's evidence falls with it, and the PR is on the + cap-spent path — so the next stamped read of the resulting head is a *closing* read, judged by + everything below. Saying so is what keeps the allowed set below from needing a third category and + from shutting on a rebinding read that did its job, which is this rule's own failure mode reached + one door further along. What the close may never do is stand in for a metered read that never happened. The second completed review must have been asked **after the first one's findings were disposed of** — by commits that answer them, or, where the disposition is a deferral or a sub-floor drop, by the replies and resolutions that record it. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 27b8b775..badb4a60 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -345,8 +345,11 @@ required check runs on a draft, so the diff reaches fully green before anyone is read it, and that is what makes the sequence affordable rather than a policy nobody keeps. Opening ready is not forbidden, but it spends a metered provider on a diff no cheap one has seen; the old rationale for allowing it turned on the round counter ADR-0064 retires, so -what remains is simply that it costs more for nothing. Record the reason in the PR. Author-side or local review, and status-only -output, do not satisfy it. **Exhaustion is not incapacity** — a provider with no budget +what remains is simply that it costs more for nothing. Record the reason in the PR. Author-side +review and status-only output do not satisfy it — and *author-side* is about whose judgement it +is, not which machine ran it: a provider's own review posted on the pull request is external +however its CLI resolved locally, while your transcript of a local run is not, because nothing +but you attests it. **Exhaustion is not incapacity** — a provider with no budget left has not reviewed: Greptile out of credits is skippable and never blocks, while CodeRabbit unavailable freezes the PR. diff --git a/docs/PRD.md b/docs/PRD.md index 50462374..661538e9 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1084,8 +1084,10 @@ works in, billed per **completed review** — one credit for a standard review, leg, and a quota refusal from it means the provider **did not review**, never a pass. Exhaustion and incapacity differ: Greptile out of credits is skippable, CodeRabbit unavailable **freezes the PR** — though a fair-use refusal that names a retry time is a **wait**, not unavailability, and a request that produced no review is not one of the two completed reviews. -Author-side/local review and a green or status-only -result do not satisfy the gate. **No provider auto-reviews this repository** — CodeRabbit reports auto reviews +Author-side review and a green or status-only +result do not satisfy the gate; *author-side* names whose judgement it is rather than which machine ran it, +so a provider's own review posted on the pull request counts however its CLI resolved locally, while an +author's transcript of a local run does not. **No provider auto-reviews this repository** — CodeRabbit reports auto reviews disabled, Greptile is held by `.greptile/config.json`'s `skipReview: "AUTOMATIC"`, and Codex fires only on open-for-review, draft-ready, or an `@codex review` comment — so a provider that was not asked has not declined. The one exception is a branch cut **before** that config landed: it is read from the PR's source branch, so such a branch From 94cbdbcf7c160c0b1dc79a182aacd43d8e19d35a Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 22:13:02 -0500 Subject: [PATCH 13/43] fix(agents): delete the rebinding mechanism and let the ordinary close cover it Codex round 10, two P1, both created by round 9's fix. The second asked for the merge-arming rule AND its contract test to change -- scope this PR said it was staying out of, and the third consecutive round whose findings all trace to one mechanism. That is the signal to remove the mechanism rather than qualify it. The rebinding read was added in 422c1dc to answer a real defect: a clean CodeRabbit review at commit A, a permitted non-material push to B, evidence surviving by one rule while --match-head-commit demands a head no metered provider has named and the cap forbids a third to name it. Condition 2 shut the Codex close there precisely BECAUSE the review was clean, so a clean review followed by a formatting commit could never merge. The answer was a second mechanism -- a read that supplies a head without closing anything -- and four rounds found four defects in it: the template had no state for it (r6), CONTRIBUTING contradicted it (r7), a rebinding read that found something serious deadlocked (r9), and the arming rule still sourced from "the clean review" (r10). It also created round 10's other P1, where a clean FIRST review plus a non-material push declared the cap spent while a second metered review was still available. Narrowing condition 2 deletes all of it. A clean review closes the gate when it read the head being merged; where a non-material push has since moved the head, the case takes the ORDINARY close -- the cap is genuinely spent, and a fresh Codex read of the final head closes and names it under every condition already written. That is strictly more work than the clean review it follows, never less, so the widening opens nothing, and: - the arming rule at AGENTS.md and its contract test stay byte-identical, because the closing read IS the clean review the binding names; - the template loses a state instead of gaining one; - CONTRIBUTING loses the two-jobs prose; - round 9's and round 10's P1s have no subject left to be about. ADR-0065 records the abandoned mechanism and why, rather than pretending the narrow rule was the first draft. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .github/pull_request_template.md | 6 ++-- AGENTS.md | 25 +++++++------- CONTRIBUTING.md | 26 ++++++--------- ...ed-cap-closes-on-the-unmetered-provider.md | 33 ++++++++++++------- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 498c5f20..d2801c34 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -34,9 +34,9 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Risk rationale: - Final head SHA: - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped -- Codex closing read — required in **two** cases, and only the first is a *close*: whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read; and whenever a permitted non-material push has moved the head past the clean review that closed the gate. `n/a` is for neither of those — a clean CodeRabbit review at the final head, and nothing since. So: n/a | **closed the gate** — the CodeRabbit line below records a spent cap, and this read stands in for the clean pass | **rebound the head** — the CodeRabbit line below records a clean review whose evidence still stands, and a permitted non-material push has since moved the head, so the gate closed there and this read only supplies the SHA the merge can bind. Either way quote it exactly as the CodeRabbit gate is quoted — permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said; a re-quoted earlier Codex pass is **not** one, since the head that pass read is not the head being merged, and a bare 👍 carrying no commit is not one either, because the head would then be yours to assert rather than the provider's to attest. Anything a *closing* read surfaces is disposed of above before it closes; a *rebinding* read re-opens nothing that was never in question +- Codex closing read — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged, and a bare 👍 carrying no commit is not one either, because the head would then be yours to assert rather than the provider's to attest. Anything this read surfaces is disposed of above before it closes - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify **unless every push since it is one of the non-material exceptions**, in which case its evidence still stands, this line is what closed the gate, and the *rebound the head* option above supplies the SHA the merge binds — the only case where the gate's head and the merge's head are different commits, and the reason those are two lines rather than one. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since traces to a recorded disposition, or to a clean `main` merge / formatting / comment or docstring edit / ADR renumber — so no new scope reached the merge unread by a metered provider; neither came back clean **with its evidence still standing** — a clean review that a later material push re-armed is not a gate that already closed, and reading this as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since traces to a recorded disposition, or to a clean `main` merge / formatting / comment or docstring edit / ADR renumber — so no new scope reached the merge unread by a metered provider; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) @@ -59,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the final head carries a provider's own read of it** — normally that is the provider that closed the gate, and on the rebinding path above it is not: there a clean review closed the gate at an earlier head, only non-material pushes have landed since, and the Codex read that stamps the final head is a different provider from the one that closed. Those two jobs coincide in every case but that one, and this box asks for both rather than conflating them, because demanding the *closer* reach the final head would make the rebinding path unattestable while lines 37 and 39 declare it valid — where the read of the final head is Codex's, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head must come from the provider's own artifact (a posted review, or a run record that names the commit it read), never from you: a bare 👍 carries no commit, so it is a valid lane result and **cannot** close the gate, since an author-supplied head could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head must come from the provider's own artifact (a posted review, or a run record that names the commit it read), never from you: a bare 👍 carries no commit, so it is a valid lane result and **cannot** close the gate, since an author-supplied head could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/AGENTS.md b/AGENTS.md index bd635323..28b10442 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -216,20 +216,17 @@ validity turns on it being the right test — must satisfy both. output is an unstamped reaction, the gate stays shut until a stamped one exists. - **Four things shut that close, and each is readable off the pull request rather than out of your own account of why you did something.** A refusal is **not** a spent cap: it reviewed nothing, so - it is a wait, and waiting is still what you do. If either completed review came back clean and its - evidence still stands under the non-material rule above, **that** review is the gate and it has - already closed — but *closing the gate* and *supplying a head the merge can bind* are two - different jobs, and only the first is finished. Where a permitted non-material push has since - moved the head, a stamped Codex read of the current head supplies the SHA the merge binding - needs, and does not re-open a gate that was never in question. Without that, a clean review plus - a formatting commit would strand a PR outright: the evidence survives by one rule while - `--match-head-commit` demands a head that is no longer current, and the cap forbids asking the - metered provider again. **A rebinding read that surfaces something serious stops being one.** - Fixing it is a material push, the clean review's evidence falls with it, and the PR is on the - cap-spent path — so the next stamped read of the resulting head is a *closing* read, judged by - everything below. Saying so is what keeps the allowed set below from needing a third category and - from shutting on a rebinding read that did its job, which is this rule's own failure mode reached - one door further along. What the close may never do is stand in for a metered read that never + it is a wait, and waiting is still what you do. If either completed review came back clean, its + evidence still stands under the non-material rule above, **and it read the head you are merging**, + **that** review is the gate, it has already closed, and none of this applies. All three, because a + clean review whose head a permitted non-material push has since moved does **not** shut this + branch: its evidence survives, but `--match-head-commit` binds a commit no metered provider has + named, and the cap forbids asking for a third to name it. Shutting the branch there would strand a + clean review followed by a formatting commit — the one PR in the queue with nothing whatever wrong + with it. So that case takes the ordinary close: the cap is genuinely spent, and a fresh Codex read + of the final head closes and names it under every condition here. That is **more** work than the + clean review it follows, never less, which is why widening the branch this way opens nothing. What + the close may never do is stand in for a metered read that never happened. The second completed review must have been asked **after the first one's findings were disposed of** — by commits that answer them, or, where the disposition is a deferral or a sub-floor drop, by the replies and resolutions that record it. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index badb4a60..f8eb5a2a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -265,13 +265,9 @@ Before requesting review / merging, confirm: and every finding is disposed of, the Codex read of the final head that closed the gate in its place** — and that is a verdict a completed review reached rather than an absence of one: record the review itself — permalink, the - `commit_id` it read, **which must be the final head for whichever read the merge binds to** — - closing the gate and naming the head the merge binds are **one act on every path but the - rebinding one**. On the cap-spent path the Codex closing read does both at once, while - CodeRabbit's two completed reviews are recorded at whatever heads they read. Only on the - rebinding path do the two come apart: a clean CodeRabbit review closed the gate at an earlier - head, every push since was non-material, and a stamped Codex read names the final one without - closing anything. **The review-object fields are CodeRabbit's** + `commit_id` it read, **which must be the final head for whichever review closes the gate** — + on the cap-spent path CodeRabbit's two are recorded at whatever heads they read, and it is + the Codex closing read that closes and names the final one. **The review-object fields are CodeRabbit's** — `submitted_at` with a state of **`COMMENTED` or `APPROVED`** (a `PENDING` review has no `submitted_at` and is not a submitted one; a `DISMISSED` one is a verdict *withdrawn* and proves nothing) — and a Codex close that is @@ -282,16 +278,14 @@ Before requesting review / merging, confirm: A Codex artifact has no such line and never will; what stands in its place is the verdict the run actually emitted, quoted, against the commit it names. Asking a Codex close for CodeRabbit's marker would leave that path with no recordable evidence at all. - **A review of an earlier head does not close the gate where a material push has moved the head - since** — answering a finding is the ordinary such push, and that review is then evidence about - a diff this one is no longer. It still stands as one of the two completed reviews, which is + **A review of an earlier head does not close the gate**, whether a material push moved the + head or a non-material one did. It still stands as one of the two completed reviews, which is exactly what the cap-spent path relies on: each recorded at the head it read, with the Codex - closing read covering the final one. Where **every** push since is one of the non-material - exceptions the opposite holds and the gate never re-opened: that review's evidence survives by - the rule above, and a stamped Codex read of the current head supplies the SHA - `--match-head-commit` needs without standing in for a gate that was never in question. Read - absolutely, this sentence would strand a clean review followed by a formatting commit, which is - the case `AGENTS.md` §Review names and refuses to strand. Neither silence + closing read covering the final one. The non-material case is worth naming because it looks + like an exception and is not one — a clean review followed by a formatting commit keeps its + evidence, so the cap is spent with nothing outstanding, and the Codex close is what names the + commit `--match-head-commit` binds. That is the ordinary close doing ordinary work, not a + special path, and it is strictly more work than the clean review it follows. Neither silence nor a green `CodeRabbit` status check is the gate; both are also what a request that reviewed **nothing** leaves behind (see the full-review command below). Greptile is optional, and its absence for want of credits is recorded rather than excused as a review. Blocking diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 577c24b9..c45d391e 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -123,18 +123,27 @@ an agent's account of its own reasoning: 1. **A refusal is not a spent cap.** A throttle, a quota refusal or a failed run reviewed nothing, so it is a wait, and waiting is still what you do. This also means an *unavailable* CodeRabbit still freezes the pull request — nothing reviewed, so nothing opens the close. -2. **A clean review is the gate, and it has already closed.** If either completed review came back - clean and its evidence still stands under the non-material rule, the branch never opens. - - With one carve-out that review found, and it exposes a tension older than this record. - *Closing the gate* and *supplying a head the merge can bind* are two different jobs. A clean - review at commit A closes the gate; a permitted non-material push to B leaves that closure intact - — `AGENTS.md` says review evidence survives such a push — while `--match-head-commit` still - demands A, which is no longer the head. With the cap spent there is no third metered read to - rebind it, so a clean review followed by a formatting commit **stranded the pull request - outright**. A stamped Codex read of B supplies the binding without re-opening a gate that was - never in question. The rule that must not bend is the other one: the close may never stand in - for a metered read that never happened. +2. **A clean review is the gate, and it has already closed** — when it read the head being merged. + If either completed review came back clean, its evidence still stands under the non-material + rule, **and** it named the merging commit, the branch never opens. + + All three conditions, and the third exposes a tension older than this record. A clean review at + commit A closes the gate; a permitted non-material push to B leaves that closure intact — + `AGENTS.md` says review evidence survives such a push — while `--match-head-commit` still demands + a head no metered provider has named, and the cap forbids asking for a third to name it. Stated + without the third condition, this branch shut there, and a clean review followed by a formatting + commit **stranded the pull request outright**. + + Four review rounds were spent trying to fix that with a second mechanism — a *rebinding* read + that supplied a head without closing anything — and each round found a new defect in it: the + pull-request template had no state for it, `CONTRIBUTING.md` contradicted it, a rebinding read + that found something serious deadlocked, and the arming rule still sourced the SHA from "the + clean review". The mechanism was the problem. Narrowing this condition instead deletes it: the + case simply takes the **ordinary close**, since the cap is genuinely spent and a fresh Codex read + of the final head closes and names it under every condition here. That is strictly *more* work + than the clean review it follows, never less, so widening the branch this way opens nothing — + and the rule that must not bend is untouched: the close may never stand in for a metered read + that never happened. 3. **The second review must have been asked after the first one's findings were disposed of** — by commits that answer them, or, where the disposition is a deferral or a sub-floor drop, by the replies and resolutions that record it. Asking twice at one head with nothing answered in From 398491ddf4a7f01cfa7a8fec209afed5e1764f33 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 22:26:35 -0500 Subject: [PATCH 14/43] fix(agents): admit the conflict resolution this contract orders you to make MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex round 11, one P1 and one P2. Neither is about the mechanism the previous commit deleted, which is the first evidence that the restructure held. P1, and a deadlock nobody on the branch could avoid: §Concurrent GitHub Flow REQUIRES merging a freshly fetched origin/main and resolving it before merge. The non-material list covers that merge only when it is CLEAN. So a conflicted one is a material push the contract itself ordered -- and condition 4 then rejected it, since resolving a conflict answers no finding and is no listed exception, while the cap forbade another metered read. Any PR that main happened to touch after its cap was spent was stranded, by other people's merges rather than by anything it did. The allowed set now admits the resolution, and only the reconciliation: both sides were already read -- yours by the two metered reviews, main's on its own pull request -- so reconciling them introduces no scope, and the closing read reads the result. A resolution carrying new logic of its own is new scope like any other, the close is shut, and the PR goes to the maintainer. P2, fixed rather than deferred because ADRs are rule-stating files and this one is the record OF this condition: ADR-0065 phrased the scope freeze as "every commit between the second completed review and the closing read". That is clock-anchored, and the operative rule is anchored at the commit the second review READ -- so the ADR waved through a material push landing while that review was still in flight, which is the one change the provider demonstrably never saw. Fixed, and recorded as its own small lesson: an ADR can drift from the contract it records, and this one was accepted carrying the weaker wording. CONTRIBUTING.md and the PR template carry both changes. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .github/pull_request_template.md | 2 +- AGENTS.md | 12 ++++++++- CONTRIBUTING.md | 6 ++++- ...ed-cap-closes-on-the-unmetered-provider.md | 26 ++++++++++++++++--- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d2801c34..05c42238 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -36,7 +36,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped - Codex closing read — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged, and a bare 👍 carrying no commit is not one either, because the head would then be yours to assert rather than the provider's to attest. Anything this read surfaces is disposed of above before it closes - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since traces to a recorded disposition, or to a clean `main` merge / formatting / comment or docstring edit / ADR renumber — so no new scope reached the merge unread by a metered provider; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since traces to a recorded disposition — including one the closing read itself raised — or to a clean `main` merge / formatting / comment or docstring edit / ADR renumber, or to the resolution of a conflict in the `main` merge the contract requires, which is admitted because both sides were already read and which admits the reconciliation only — so no new scope reached the merge unread by a metered provider; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) diff --git a/AGENTS.md b/AGENTS.md index 28b10442..6b2bec41 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -236,7 +236,17 @@ validity turns on it being the right test — must satisfy both. And **nothing but disposal may land after the cap is spent**: everything added after the commit the second completed review actually *read* — its `commit_id`, never its `submitted_at` — must answer a finding already on this pull request's record — one of those two reviews', **or one a - closing read has itself raised** — or be one of the non-material exceptions above. A closing + closing read has itself raised** — or be one of the non-material exceptions above, **or be the + resolution of a conflict in the `main` merge this contract requires**. That last one is not a + loophole and it is not optional: §Concurrent GitHub Flow obliges you to merge a freshly fetched + `origin/main` and resolve it here before merging, the non-material list covers that merge only when + it is *clean*, and a conflicted one is therefore a material push you were ordered to make. Shutting + the close on it would strand any PR that `main` happened to touch — a deadlock triggered by other + people's merges rather than by anything on this branch. What is admitted is the **reconciliation + and nothing else**: both sides were already read, yours by the metered reviews and `main`'s on its + own pull request, so a resolution that only reconciles them introduces no scope, and the closing + read reads the result. A resolution carrying new logic of its own is new scope like any other, the + close is shut, and the PR goes to the maintainer. A closing read's own findings have to count, and this is not a courtesy to it: the bullet above requires everything that read surfaces to be disposed of before it closes, and that disposal is a material push answering no CodeRabbit finding at all. An allowed set holding only *their* findings would diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f8eb5a2a..07796e2f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -459,7 +459,11 @@ every hunk added after the commit the second completed review actually read — `commit_id`, never its `submitted_at`, since a material push landing while that review is still running is a push it never saw — must answer a finding already on the pull request's record, **including one the closing read itself raised**, or be one of the non-material -exceptions above. That inclusion is load-bearing rather than generous: the closing read has +exceptions above, **or be the resolution of a conflict in the `main` merge the contract +requires** — that merge is non-material only when it is *clean*, so a conflicted one is a +material push you were ordered to make, and shutting the close on it would strand any PR +that `main` happened to touch. Only the reconciliation is admitted; a resolution carrying +new logic of its own is new scope like any other. That inclusion is load-bearing rather than generous: the closing read has to dispose of whatever it surfaces before it closes, and that fix answers no CodeRabbit finding, so a set holding only theirs would disqualify every closing read that found something — the same deadlock, one level down. What follows such a fix is another stamped diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index c45d391e..7ae46827 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -158,10 +158,12 @@ an agent's account of its own reasoning: deadlock must be checked against its own failure mode, and this one was not until a provider checked it. -4. **Nothing but disposal may land after the cap is spent.** Every commit between the second - completed review and the closing read must answer a finding already on the pull request's - record — one of those two reviews', or one a closing read has itself raised — or be one of - the existing non-material exceptions. +4. **Nothing but disposal may land after the cap is spent.** Every change added after **the commit + the second completed review actually read** — its `commit_id`, never its `submitted_at` — must + answer a finding already on the pull request's record — one of those two reviews', or one a + closing read has itself raised — or be one of the existing non-material exceptions, or be the + resolution of a conflict in the `main` merge this contract requires. Anchored at the reviewed + commit and applied per change, both for the reasons below. This is the condition that makes the *"third opinion on a twice-read diff"* claim below true rather than merely asserted, and it was missing from the first two drafts. A second Codex review @@ -177,6 +179,22 @@ an agent's account of its own reasoning: recorded finding and carries an unrelated hunk alongside it satisfies any per-commit phrasing while smuggling in exactly the scope the condition excludes. + It also has to be anchored at the commit the second review **read**, not at the clock. An earlier + phrasing here said *"between the second completed review and the closing read"*, which sounds + equivalent and is not: a material push landing while that review is still running is after its + `commit_id` but before it completed, so a clock-anchored window waves through the one change the + provider demonstrably never saw. This record is a rule-stating file and was accepted carrying the + weaker wording, which is its own small lesson — an ADR can drift from the contract it records. + + And the set has to admit **the resolution of a conflict in the required `main` merge**. This + contract obliges a worker to merge a freshly fetched `origin/main` before merging; the + non-material list covers that merge only when it is *clean*; so a conflicted one is a material + push the contract itself ordered. Excluding it strands any pull request that `main` happened to + touch after the cap was spent — a deadlock triggered entirely by other people's merges. The + reconciliation is admitted because both sides were already read, yours by the metered reviews and + `main`'s on its own pull request; a resolution carrying new logic of its own is new scope like any + other and shuts the close. + And the allowed set has to include **the closing read's own findings**, which the first three drafts of this condition did not. A sixth Codex review of this record's pull request found it, and it is the third time a draft here re-created the deadlock it removes: the bullet above From f5b147cd843e70471741fb017897f119ceec036a Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 22:38:19 -0500 Subject: [PATCH 15/43] fix(agents): carry condition 2's narrowing into the summary that restates it Codex round 12, one P1 -- down from two, and it is a mirror rather than a new defect, which is what convergence looks like here. Narrowing condition 2 two commits ago -- a clean review closes the gate WHEN IT READ THE HEAD BEING MERGED -- landed in AGENTS.md and ADR-0065 and not in CONTRIBUTING.md's restatement of the same condition, which still said a clean review with standing evidence "is already the gate". In the case the narrowing exists for (cap spent, clean review at A, non-material push to B) that sends a worker to bind --match-head-commit to a head no metered provider named, and AGENTS.md:9-10 turns the disagreement into a stop. Which is the finding this PR keeps re-learning at its own expense: the fix went into the operative file and one of its mirrors, not all of them. Swept the rest of the restatements of condition 2 while here -- the template and .greptile carry no copy, SKILL.md defers, and the PRD now states none of the conditions. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- CONTRIBUTING.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 07796e2f..82d340f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -444,8 +444,11 @@ thread resolved, a fresh Codex read of the final head closes the gate in their p PR finishes on an unmetered read rather than on a maintainer. **`AGENTS.md` §Review sets four conditions on that and is the operative statement of all of them**; the other two are above — a provider that refused reviewed nothing, so a spent cap is not what an unavailable CodeRabbit -leaves behind, and a review that came back clean with its evidence still standing is already -the gate. The two restated here are the two a summary most easily drops, and dropping either +leaves behind, and a review that came back clean with its evidence still standing **and that +read the head being merged** is already the gate. That last qualifier is not decoration: where +a permitted non-material push has since moved the head, no metered provider has named the +commit `--match-head-commit` binds, so the case takes the ordinary Codex close rather than +resting on the earlier review. The two restated here are the two a summary most easily drops, and dropping either would authorise the close in a case the contract shuts, which a contributor reading the two documents together would have to stop over. **The cap has to have been spent on two reads with a disposition between them**: the second review must have been asked only *after* the first one's findings were From dac56dad0b54ae1317864a204e2b11dac9242eda Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 22:56:31 -0500 Subject: [PATCH 16/43] fix(agents): state the allowed set as a principle, and report the ADR's real cost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex round 13: one P1, two P2. P1, and the same deadlock a third time from a new direction: the post-cap allowed set ENUMERATED whose findings a late fix may answer -- the two CodeRabbit reviews, then also the closing read's -- and a GREPTILE finding fixed after review 2 answered nobody on the list. The disposition rule applies to findings from any provider, so the fix was compulsory; the list rejected it; the cap forbade another metered read; the PR stranded. Every version of this list has omitted a provider, and each omission produced the identical failure, so the list is gone: a late change must answer a finding already recorded on the pull request, WHICHEVER provider raised it. Stating the principle instead of the members also shortens the section by eight lines, which is the second finding's subject. P2, fixed: the PR template offered "ADR renumber" as a post-cap exception where the contract says renumber-ONLY -- a renumber that also edits a word of the decision is material. As written the template would have let governance text reach the final head unread by a metered provider, which is the one thing this condition exists to prevent. P2, fixed and verified independently rather than taken on trust: the ADR recorded its own cost as "roughly twenty lines" of growth in AGENTS.md §Review. Measured at the merge base and at this head, the section goes 80 -> 152, so the recorded cost was understated around fourfold. Corrected, and the understatement is left on the record, because a decision record that under-reports its cost is how a cost stops being weighed. The reasoning that makes up most of that growth belongs in this ADR rather than in a file loaded on every model call -- named as follow-up rather than attempted here, since this PR's own history is thirteen rounds of evidence that large edits to this text introduce new defects. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .github/pull_request_template.md | 2 +- AGENTS.md | 38 ++++++++----------- ...ed-cap-closes-on-the-unmetered-provider.md | 21 +++++++--- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 05c42238..0e56f585 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -36,7 +36,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped - Codex closing read — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged, and a bare 👍 carrying no commit is not one either, because the head would then be yours to assert rather than the provider's to attest. Anything this read surfaces is disposed of above before it closes - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since traces to a recorded disposition — including one the closing read itself raised — or to a clean `main` merge / formatting / comment or docstring edit / ADR renumber, or to the resolution of a conflict in the `main` merge the contract requires, which is admitted because both sides were already read and which admits the reconciliation only — so no new scope reached the merge unread by a metered provider; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers a finding already recorded on this PR **whichever provider raised it** — either metered review, Greptile, or the closing read's own — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by a metered provider; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) diff --git a/AGENTS.md b/AGENTS.md index 6b2bec41..22613c3b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -234,33 +234,25 @@ validity turns on it being the right test — must satisfy both. close nothing. **The test is the disposal, not a new commit**: a review answered wholly on the record moves no head, so demanding one would re-create the deadlock this rule exists to remove. And **nothing but disposal may land after the cap is spent**: everything added after the commit - the second completed review actually *read* — its `commit_id`, never its `submitted_at` — must - answer a finding already on this pull request's record — one of those two reviews', **or one a - closing read has itself raised** — or be one of the non-material exceptions above, **or be the - resolution of a conflict in the `main` merge this contract requires**. That last one is not a - loophole and it is not optional: §Concurrent GitHub Flow obliges you to merge a freshly fetched - `origin/main` and resolve it here before merging, the non-material list covers that merge only when - it is *clean*, and a conflicted one is therefore a material push you were ordered to make. Shutting - the close on it would strand any PR that `main` happened to touch — a deadlock triggered by other - people's merges rather than by anything on this branch. What is admitted is the **reconciliation - and nothing else**: both sides were already read, yours by the metered reviews and `main`'s on its - own pull request, so a resolution that only reconciles them introduces no scope, and the closing - read reads the result. A resolution carrying new logic of its own is new scope like any other, the - close is shut, and the PR goes to the maintainer. A closing - read's own findings have to count, and this is not a courtesy to it: the bullet above requires - everything that read surfaces to be disposed of before it closes, and that disposal is a material - push answering no CodeRabbit finding at all. An allowed set holding only *their* findings would - therefore disqualify every closing read that found anything, while the cap forbade asking the - metered provider again — this rule's own failure mode, re-created one level down and reachable - only by the closing reads that did their job. What follows such a fix is another stamped read of - the head it produced, never a narrower allowed set: each round is still read by the provider that - closes it, which is the property this condition exists to protect, and unread scope stays shut out - because a closing read cannot raise a finding about a hunk it never saw. Anchor it + the second completed review actually *read* — its `commit_id`, never its `submitted_at` — must do + one of three things. It must **answer a finding already recorded on this pull request, whichever + provider raised it** — either metered review, Greptile, or a closing read's own — or be one of the + non-material exceptions above, or be **the resolution of a conflict in the `main` merge this + contract requires**. The allowed set is stated as a principle and not a list of providers because + every list of it so far has omitted one, and each omission was the same deadlock: the omitted + provider's finding still had to be fixed, the fix answered nobody on the list, and the cap forbade + asking the metered provider again — so the close shut against exactly the pull requests whose + reviews did their job. The conflict resolution is on that footing too and is not optional: + §Concurrent GitHub Flow orders you to merge a freshly fetched `origin/main` and resolve it here, + while the non-material list covers that merge only when it is *clean*. Both admissions turn on the + same fact — the change answers something already read, or reconciles two things already read — so + neither lets unread scope through, and a resolution carrying new logic of its own is new scope like + any other. Anchor it at the commit and not the clock, because a material push landing while that review is still running is a push it never saw, and a time-anchored window would wave it through. **The unit is the change, not the commit**: a commit that fixes a recorded finding *and* carries an unrelated hunk passes any per-commit test while smuggling exactly the scope this shuts out, so every hunk - has to trace to a disposition or an exception. New scope pushed after the cap has spent it is scope **no + has to trace to one of the three. New scope pushed after the cap has spent it is scope **no metered provider will ever read**, and the close is a third opinion on a twice-read diff, never a first opinion on an unread one — so the close is shut and the PR waits for a gate it can actually satisfy. Motive is not a test diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 7ae46827..7d1f58da 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -281,11 +281,22 @@ landed after the cap was spent — so it is a third opinion on a twice-read diff on an unread one. **If Codex's review quality degrades, this paragraph is the part of the record that stops holding**, and nothing in this repository would detect that. -A second cost: §Review grows by roughly twenty lines in a file ADR-0064 deliberately shrank, and -whose resident-context driver — *"`AGENTS.md` is read on every model call by every agent"* — argues -against every addition. The four shutting conditions and the stamped-head rule are what that length -buys, and they are the -part that cannot be compressed without making the branch a judgment call. +A second cost, and it is the larger of the two: **§Review grows from 80 lines to 152** — it nearly +doubles — in a file ADR-0064 deliberately shrank, and whose resident-context driver, *"`AGENTS.md` is +read on every model call by every agent"*, argues against every addition. An earlier draft of this +paragraph said *"roughly twenty lines"*; a review measured it and the real figure is around four +times that. The +understatement is recorded rather than quietly corrected, because a decision record that +under-reports its own cost is how a cost stops being weighed. + +What the length buys is the four shutting conditions and the stamped-head rule, which cannot be +compressed without turning the branch into a judgment call. What it also carries is the *reasoning* +behind each condition — most of it added one review round at a time, as each draft was shown to +re-create the deadlock it removed. That reasoning belongs here, in a file nobody loads on every call, +and a later pass moving it out of §Review would be a straightforward win. It is not attempted in this +pull request: the same review rounds that produced the prose also showed that large edits to this +text reliably introduce new defects, and a compression pass is exactly such an edit. It is named as +follow-up work rather than left for someone to notice. **Reversible.** Prose only. No script, no workflow, no label, no ref namespace, no test fixture. A `git revert` restores the escalation exactly. From 0e77eeff84f30bdad3096aecfd76f7f9006ba014 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 23:11:45 -0500 Subject: [PATCH 17/43] fix(agents): carry the provider-neutral allowed set into the two files that still enumerated it Codex round 14: one P1, one P2, both mirrors of the previous round's fix rather than new defects. Round 13 replaced the enumerated allowed set with a provider-neutral principle in AGENTS.md and the PR template, and left ADR-0065 and CONTRIBUTING.md holding the old form -- so both still shut the close against a Greptile finding fixed after CodeRabbit's second review, which is the deadlock round 13 removed. Codex named the ADR; sweeping the class found CONTRIBUTING.md carrying it too. Fixing three surfaces out of four is the failure this pull request has now committed at least four times, and it is worth saying plainly: the sweep has to be the fix, not a follow-up to it. ADR-0065 also now records WHY the set is a principle -- the first list admitted only the two CodeRabbit reviews and shut out the closing read; the second added the closing read and still omitted Greptile; each omission produced an identical deadlock and each was found only by the next round. A list of who may raise a finding will keep omitting somebody. P2: CONTRIBUTING.md's non-material list said "comment edits" where AGENTS.md and docs/PRD.md both say "comment and docstring edits". A docstring-only fix landing after review 2 was therefore material in one file and exempt in two, which sends the cap-spent path in opposite directions. All three now read the same. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- CONTRIBUTING.md | 28 +++++++++---------- ...ed-cap-closes-on-the-unmetered-provider.md | 18 +++++++++--- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 82d340f1..421cf97b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -388,7 +388,7 @@ quoting a trigger while describing it spends a real review. Break the handle, or "the full-review command" instead. Review evidence **survives a non-material push**, so addressing findings does not -restart the gate — merging `main` in cleanly, formatting, comment edits and ADR +restart the gate — merging `main` in cleanly, formatting, comment and docstring edits, and ADR renumbering (renumber-only — touching a word of the decision is not) are all non-material, and that exception list WINS over the material paths below, while executable code, scientific claims, data, schema, locks, CI/release config and the governance text itself (`AGENTS.md`, @@ -460,19 +460,19 @@ were all deferred or dropped. What buys the close nothing is asking twice at one nothing answered in between: that is one review asked twice. **And nothing but disposal may land after the cap is spent**: every hunk added after the commit the second completed review actually read — its `commit_id`, never its `submitted_at`, since a material push landing while that review is -still running is a push it never saw — must answer a finding already on the pull request's -record, **including one the closing read itself raised**, or be one of the non-material -exceptions above, **or be the resolution of a conflict in the `main` merge the contract -requires** — that merge is non-material only when it is *clean*, so a conflicted one is a -material push you were ordered to make, and shutting the close on it would strand any PR -that `main` happened to touch. Only the reconciliation is admitted; a resolution carrying -new logic of its own is new scope like any other. That inclusion is load-bearing rather than generous: the closing read has -to dispose of whatever it surfaces before it closes, and that fix answers no CodeRabbit -finding, so a set holding only theirs would disqualify every closing read that found -something — the same deadlock, one level down. What follows such a fix is another stamped -read of the head it produced. New scope pushed past that point is scope no metered provider -will ever read, and the close is a third opinion on a twice-read diff, never a first opinion -on an unread one. Nothing counts this for you; the +still running is a push it never saw — must answer a finding **already recorded on the pull +request, whichever provider raised it** (either metered review, Greptile, or the closing +read's own), or be one of the non-material exceptions above, **or be the resolution of a +conflict in the `main` merge the contract requires** — that merge is non-material only when +it is *clean*, so a conflicted one is a material push you were ordered to make, and shutting +the close on it would strand any PR that `main` happened to touch. Only the reconciliation is +admitted; a resolution carrying new logic of its own is new scope like any other. The +provider-neutral wording is load-bearing rather than loose: whoever raised a serious finding, +you are obliged to fix it, so any list of *whose* findings count shuts the close against the +fix it just required — and every list drafted here has omitted somebody, first the closing +read and then Greptile. What follows such a fix is another stamped read of the head it +produced. New scope pushed past that point is scope no metered provider will ever read, and +the close is a third opinion on a twice-read diff, never a first opinion on an unread one. Nothing counts this for you; the merged history is auditable and you are trusted with it. On agent-layer paths (`.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` and the agent test modules) a finding below the severity floor is **dropped rather than tracked**, because there the diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 7d1f58da..0ff521b3 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -160,10 +160,11 @@ an agent's account of its own reasoning: 4. **Nothing but disposal may land after the cap is spent.** Every change added after **the commit the second completed review actually read** — its `commit_id`, never its `submitted_at` — must - answer a finding already on the pull request's record — one of those two reviews', or one a - closing read has itself raised — or be one of the existing non-material exceptions, or be the - resolution of a conflict in the `main` merge this contract requires. Anchored at the reviewed - commit and applied per change, both for the reasons below. + answer a finding already recorded on the pull request, **whichever provider raised it** — either + metered review, Greptile, or a closing read's own — or be one of the existing non-material + exceptions, or be the resolution of a conflict in the `main` merge this contract requires. + Anchored at the reviewed commit and applied per change, both for the reasons below, and stated as + a principle rather than a list of providers for the reason after those. This is the condition that makes the *"third opinion on a twice-read diff"* claim below true rather than merely asserted, and it was missing from the first two drafts. A second Codex review @@ -186,6 +187,15 @@ an agent's account of its own reasoning: provider demonstrably never saw. This record is a rule-stating file and was accepted carrying the weaker wording, which is its own small lesson — an ADR can drift from the contract it records. + And the set is stated as a **principle rather than a list of providers**, because every list of + it drifted. The first admitted only the two CodeRabbit reviews' findings, which shut the close + against any closing read that found something; adding the closing read's own findings then still + omitted **Greptile**, whose findings a worker is equally obliged to fix. Each omission produced + the identical deadlock — the fix was compulsory, it answered nobody on the list, and the cap + forbade another metered read — and each was found only by the next review round. A list of who + may raise a finding will keep omitting somebody; *any provider already recorded on the pull + request* cannot. + And the set has to admit **the resolution of a conflict in the required `main` merge**. This contract obliges a worker to merge a freshly fetched `origin/main` before merging; the non-material list covers that merge only when it is *clean*; so a conflicted one is a material From dcabe31a2e89f11ae43e7da7723bc2b36214d644 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 23:28:38 -0500 Subject: [PATCH 18/43] fix(agents): pin the closing read's head, because nothing attests it Codex round 15, one P1, and the most consequential of the run: the close as written could not be executed at all. The stamped-head rule required the merging SHA to come from an artifact the PROVIDER wrote. Checking whether such an artifact exists, rather than assuming one does: - The GitHub Codex bot has NEVER posted a review on this repository. Both of its appearances -- #427 and #428, 2026-08-07 -- are the same usage-limit refusal. Its code reviews are metered on this account and the meter is spent. - The CLI, which is what the lane actually runs, leaves a rollout record carrying cwd, cli_version and a session id. Not the commit it read. So the requirement was written for evidence the tooling does not produce, and would have replaced the deadlock this PR removes with a permanent one. Keep the property, drop the mechanism. The head is now PINNED rather than attested: where a provider stamps the commit, quote it; where none does, record git rev-parse HEAD immediately before and after the run alongside the PR head at arming time, all three equal. That proves the head did not move under the read. It does NOT prove the provider read that head, it is stated as weaker in every file that carries it, and it rests on the same worker honesty the section already assumes of anyone quoting a review. Also corrected while here, because this PR's own ADR now contradicted it: AGENTS.md called Codex "unmetered and so uncapped" without qualification. That is true of the CLI and false of the GitHub bot, whose meter is spent -- so asking the bot is asking a provider that will decline, and a decline is not a review. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .github/pull_request_template.md | 2 +- AGENTS.md | 29 +++++++---- CONTRIBUTING.md | 10 ++-- ...ed-cap-closes-on-the-unmetered-provider.md | 48 ++++++++++++------- 4 files changed, 60 insertions(+), 29 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0e56f585..924717ec 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -34,7 +34,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Risk rationale: - Final head SHA: - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped -- Codex closing read — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged, and a bare 👍 carrying no commit is not one either, because the head would then be yours to assert rather than the provider's to attest. Anything this read surfaces is disposed of above before it closes +- Codex closing read — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged. Where the provider stamps the commit itself, quote that; where it does not — which is the live case, since Codex's CLI records the working directory and session but not the commit — give the **procedural pin** instead: `git rev-parse HEAD` in the review worktree immediately **before** and immediately **after** the run, plus the PR head at arming time, all three the same 40-hex value. Say which of the two you are giving; the pin proves the head did not move under the read, not that the provider read it. Anything this read surfaces is disposed of above before it closes - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter - **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers a finding already recorded on this PR **whichever provider raised it** — either metered review, Greptile, or the closing read's own — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by a metered provider; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass diff --git a/AGENTS.md b/AGENTS.md index 22613c3b..70662d47 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -166,7 +166,10 @@ validity turns on it being the right test — must satisfy both. reason in the PR. - **The lane is cheapest provider first, and the order is the point.** On the green diff — the draft by default, or the ready PR whose reason is recorded — **Codex** first, unmetered and so - uncapped, until it surfaces nothing blocking. Then **optionally one Greptile review**, if the + uncapped, until it surfaces nothing blocking. *Unmetered* is a fact about the **CLI**, which is + what this lane runs: the GitHub Codex bot's code reviews are metered on this account and its meter + is spent — both of its appearances in this repository are the same usage-limit refusal — so asking + it is asking a provider that will decline, and a decline is not a review. Then **optionally one Greptile review**, if the seat has budget: a *review*, since a standard one costs a credit and a TREX one three. Then ready-for-review if it is not already, and **CodeRabbit last** — last of the *metered* providers, which is the spend the order buys. The unmetered one is not confined to the front and may read @@ -207,13 +210,23 @@ validity turns on it being the right test — must satisfy both. read surfaces is disposed of by those same three dispositions before it closes — the close is a *substitute for the clean pass*, not a lower bar than it. That review is then *the clean review* the merge binding below names. -- **The closing read must stamp the head itself, or it does not close.** The SHA that reaches - `--match-head-commit` has to come from something the *provider* wrote — a posted review, or a run - artifact that records the commit it read. A bare 👍 with no commit on it is a fine lane result and - is **not** a close, because the head would then be yours to assert rather than the provider's to - attest: a push landing while the read is in flight would let a PR name a head the provider never - saw, and the whole point of binding the merge is that nobody can do that. Where the only Codex - output is an unstamped reaction, the gate stays shut until a stamped one exists. +- **The closing read must be pinned to the head it closes, and the pin must be checkable by someone + who was not there.** What that rules out is a head *asserted* after the fact: a push landing while + the read is in flight would otherwise let a PR name a commit the provider never saw, and binding + the merge exists so nobody can do that. Where the provider stamps the commit itself — a posted + review carries a `commit_id` — quote it and you are done. **Where it does not, the pin is + procedural and must be recorded as such**: run the read against the exact head being merged, and + record `git rev-parse HEAD` in that worktree **immediately before and immediately after** the run + together with the PR's head at arming time, all three equal. A read whose head moved under it is + not a close, and the equality is what says it did not. Say which of the two you did. + **Codex's local CLI emits no head-stamped artifact today** — its run record carries the working + directory, version and session id, not the commit — so the procedural pin is the live path here, + and calling it "provider-attested" would be false. What it buys is weaker and worth naming: it + proves the head did not move across the read, not that the provider read that head, and it rests + on the worker reporting the three values honestly. That is the same trust the rest of this section + already places in a worker who quotes a review. Requiring an attestation the tooling cannot produce + would not buy the stronger property — it would shut the close permanently, which is the deadlock + this whole branch exists to remove. - **Four things shut that close, and each is readable off the pull request rather than out of your own account of why you did something.** A refusal is **not** a spent cap: it reviewed nothing, so it is a wait, and waiting is still what you do. If either completed review came back clean, its diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 421cf97b..9144920a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -270,9 +270,13 @@ Before requesting review / merging, confirm: the Codex closing read that closes and names the final one. **The review-object fields are CodeRabbit's** — `submitted_at` with a state of **`COMMENTED` or `APPROVED`** (a `PENDING` review has no `submitted_at` and is not a submitted - one; a `DISMISSED` one is a verdict *withdrawn* and proves nothing) — and a Codex close that is - a stamped run artifact rather than a posted review has none of them: record what it does emit, - including the commit **it** names, and do not demand fields that only exist on a review. + one; a `DISMISSED` one is a verdict *withdrawn* and proves nothing) — and a Codex close run + from the CLI rather than posted as a review has none of them: record what it does emit, and do + not demand fields that only exist on a review. It does not emit the commit either — its run + record carries the working directory, version and session id — so the head comes from the + **procedural pin** §Review sets out: `git rev-parse HEAD` immediately before and after the run + and the PR head at arming time, all three equal, recorded as a pin rather than described as an + attestation. **The body convention is CodeRabbit's too** — the opening of its body, where the clean verdict is written by the `Actionable comments posted:` line being **absent** rather than reading `0`. A Codex artifact has no such line and never will; what stands in its place is the verdict the diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 0ff521b3..c91343e1 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -216,23 +216,37 @@ an agent's account of its own reasoning: the fix produced: each round is still read by the provider that closes it, and unread scope stays excluded, because a closing read cannot raise a finding about a hunk it never saw. -### The closing read must stamp its own head - -The SHA that reaches `--match-head-commit` must come from something the **provider** wrote. This is -a fifth condition in substance, and it was the last one found. - -Codex's clean result is often a bare 👍 carrying no commit. Two review rounds pulled in opposite -directions here, and the resolution is the interesting part. One round found that demanding -`commit_id` / `submitted_at` / `COMMENTED` from the closer made the path **unsatisfiable in the -ordinary clean case**, since a reaction has none of those. The next round found that accepting the -reaction made the head **author-asserted rather than provider-attested** — and a push landing while -the read is in flight would then let a pull request name a head the provider never saw, which is -precisely what binding the merge exists to prevent. - -Both are right, and the resolution is not a compromise between them: a reaction is a perfectly good -*lane result* and simply is not a *close*. What closes is any artifact the provider itself stamps -with the commit it read. Where the only output is an unstamped reaction the gate stays shut until a -stamped one exists, which costs an unmetered re-run and nothing else. +### The closing read must be pinned to the head it closes + +The SHA that reaches `--match-head-commit` must not be one the author asserted after the fact. This +is a fifth condition in substance, and it took three review rounds pulling in different directions +to land, which is the part worth recording. + +Round one found that demanding `commit_id` / `submitted_at` / `COMMENTED` from the closer made the +path **unsatisfiable in the ordinary clean case**: Codex's clean result is often a bare 👍 carrying +no commit, and a reaction has none of those fields. Round two found that accepting the reaction made +the head **author-asserted** — a push landing while the read is in flight would let a pull request +name a head the provider never saw, which is precisely what binding the merge exists to prevent. The +draft that came out of those two required an artifact *the provider itself stamps with the commit*, +and said the gate stays shut until one exists. + +Round three established, by looking, that **no such artifact exists on this repository today**, so +that draft shut the gate permanently: + +- The GitHub Codex bot has **never posted a review here.** Both of its appearances in the repository's + history — on #427 and #428, 2026-08-07 — are the same usage-limit refusal. Its code reviews are + metered on this account and the meter is spent, which also qualifies this record's *"unmetered and + so uncapped"* framing: that is true of the **CLI**, which is what the lane actually runs. +- The CLI leaves a rollout record carrying `cwd`, `cli_version` and a session id — **not the commit + it read.** + +So the requirement was written for evidence the tooling does not produce, and would have replaced a +deadlock with a deadlock. What survives is the *property* rather than the mechanism: the head is +**pinned** rather than attested. Where a provider stamps the commit, quote it. Where none does, +record `git rev-parse HEAD` immediately before and immediately after the run alongside the PR head +at arming time, all three equal — which proves the head did not move under the read, and does not +pretend to prove the provider read it. That is weaker, it is stated as weaker, and it rests on the +same worker honesty the rest of the section already assumes of anyone quoting a review. **Motive is deliberately not a test.** An earlier draft closed the gaming path with *"an ask made to spend the cap is not one of the two."* That was rejected on review for two reasons: a motive is not From a21bf94925611cc66f749b4e12f83d160a7fca81 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 23:38:29 -0500 Subject: [PATCH 19/43] fix(agents): accept the pin in every prerequisite, not only where it is defined MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex round 16, one P1: the previous commit defined the procedural pin and left three prerequisites still demanding the provider-stamped artifact it exists because nothing produces. AGENTS.md's close bullet, the PR template's Review-complete checkbox, and CONTRIBUTING.md's evidence paragraph each still required "a posted review, or a run artifact that names the commit it read" -- so the contract defined the escape in one place and refused it in three, and an agent reading any of those first keeps the gate shut in exactly the case ADR-0065 unblocks. All three now take either the provider's own commit_id, where one exists, or the pin, and say which is live for the Codex CLI. This is the same defect for the fifth time on this pull request -- a fix landing in the file that was named and not in the files that mirror it -- and the fifth time it has been the review rather than the author that noticed. Swept the class directly this time rather than fixing the cited line: no surface now requires an artifact naming the commit, and the remaining 👍 references are about the ordinary lane leg, where a reaction is still a perfectly good result, and about ADR-0065's account of how the rule got here. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .github/pull_request_template.md | 2 +- AGENTS.md | 2 +- CONTRIBUTING.md | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 924717ec..0b574df9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -59,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head must come from the provider's own artifact (a posted review, or a run record that names the commit it read), never from you: a bare 👍 carries no commit, so it is a valid lane result and **cannot** close the gate, since an author-supplied head could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head is either the provider's own (a posted review's `commit_id`) or, where the provider records none — which is the live case for Codex's CLI — the **procedural pin**: `git rev-parse HEAD` in the review worktree immediately before and after the run, and the PR head at arming time, all three identical, quoted as a pin and not as an attestation. What is never acceptable is a head asserted with neither, since that could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/AGENTS.md b/AGENTS.md index 70662d47..3a453e11 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -205,7 +205,7 @@ validity turns on it being the right test — must satisfy both. a failed run reviewed nothing — and every finding they raised is **fixed, deferred-and-tracked, or dropped sub-floor** with the thread resolved, then a **fresh Codex read of the final head** closes the gate in their place: a full read recorded on the pull request — a review the provider posted, - or a verifiable run artifact of its own that names the commit it read — never an earlier Codex + or its own run verdict quoted and pinned to the head by the rule below — never an earlier Codex pass re-quoted, since the head that pass read is not the head being merged. Anything that read surfaces is disposed of by those same three dispositions before it closes — the close is a *substitute for the clean pass*, not a lower bar than it. That review is then *the clean review* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9144920a..c9dc6e21 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -264,10 +264,12 @@ Before requesting review / merging, confirm: **CodeRabbit with no actionable comments is required — or, if its two-review cap is spent and every finding is disposed of, the Codex read of the final head that closed the gate in its place** — and that is a verdict a completed - review reached rather than an absence of one: record the review itself — permalink, the - `commit_id` it read, **which must be the final head for whichever review closes the gate** — - on the cap-spent path CodeRabbit's two are recorded at whatever heads they read, and it is - the Codex closing read that closes and names the final one. **The review-object fields are CodeRabbit's** + review reached rather than an absence of one: record the review itself — permalink, and the + commit it read, **which must be the final head for whichever review closes the gate**. That + commit is the review's own `commit_id` where the provider posts one, and the **procedural + pin** where it does not, which is the live case for Codex's CLI. On the cap-spent path + CodeRabbit's two are recorded at whatever heads they read, and it is the Codex closing read + that closes and names the final one. **The review-object fields are CodeRabbit's** — `submitted_at` with a state of **`COMMENTED` or `APPROVED`** (a `PENDING` review has no `submitted_at` and is not a submitted one; a `DISMISSED` one is a verdict *withdrawn* and proves nothing) — and a Codex close run From 9da3b462990f2fbc1243023bb9598fc97bb810e2 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Tue, 11 Aug 2026 23:47:52 -0500 Subject: [PATCH 20/43] fix(agents): say what "never the only reviewer" actually protects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex round 17, one P1, and the deepest of the run: the close permitted a locally run Codex verdict as the final-head read, while "You are never the only reviewer of your own diff" required an EXTERNAL provider on the final head and excluded local runs by name. Flatly contradictory, and under AGENTS.md:9-10 that is a stop -- so the central mechanism was unusable for a compliant worker. The available fixes were to demand a provider-POSTED final-head review, which #450 records as unavailable on this account and which therefore just relocates the deadlock; or to widen "external" to admit a quoted CLI run, which trades away the tamper-evidence a posted review has. Neither is right, because both argue about the DELIVERY of the review rather than about what the bullet is for. What it is for is that no unreviewed substance merges. The reviewed commit being the merged commit was the ordinary way of getting that, never the property itself. On the cap-spent path the final head may differ from the externally reviewed one only by changes answering recorded findings, the non-material exceptions, or a required main conflict resolution -- §Review's fourth condition is exactly that guarantee, and the closing read is what confirms nothing else crept in. So the property holds; only the shortcut for checking it does not. The bullet now states the property and says when inspection is enough. It also keeps the distinction between a posted review, which GitHub attests, and a quoted CLI run, which nothing does -- named as the weaker thing rather than levelled up, since the Codex lane runs the weaker one and pretending otherwise was how this contradiction got written. CONTRIBUTING.md and docs/PRD.md carry the same, both pointing at §Review for what makes it hold rather than restating the conditions. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- AGENTS.md | 19 +++++++++++++------ CONTRIBUTING.md | 9 ++++++--- docs/PRD.md | 6 +++--- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3a453e11..6ee4a599 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -154,12 +154,19 @@ validity turns on it being the right test — must satisfy both. ## Review - **You are never the only reviewer of your own diff.** Before merge at least one external provider - must have reviewed the final head and reported what it found. Author-side or local output never - satisfies this, and a green status check with no review body is not a review. *Author-side* names - whose judgement it is and not which machine ran it: a provider's own review posted on the pull - request is external however its CLI resolves locally (§This machine), while your transcript of a - local run is not, because nothing but you attests it. Quote the provider and name the 40-hex head - it read in the PR body. + must have reviewed **every substantive change reaching the merge**, and reported what it found. + Author-side output never satisfies this, and a green status check with no review body is not a + review. *Author-side* names whose judgement it is and not which machine ran it: the verdict must + be the provider's, quoted as it wrote it, and a provider's own review posted on the pull request + carries the strongest form of that because GitHub attests it (§This machine). + **Normally the head it read *is* the head you merge, and then this bullet is satisfied by + inspection.** Where it is not — the cap-spent close below is the case that reaches it — what makes + the difference safe is that the final head may differ from the externally reviewed one only by + changes that answer recorded findings, by the non-material exceptions, or by a required `main` + conflict resolution. §Review's fourth condition is what enforces that, and the closing read is + what confirms nothing else crept in. So no unreviewed substance merges, which is the property this + bullet is protecting; the reviewed *commit* being the merged commit was only ever the ordinary way + of getting it. Quote the provider and name the 40-hex head it read in the PR body. - **Open as a draft and get it green there.** Every required check runs on a draft, so the diff reaches fully green before anyone is asked to read it. Opening ready is **not forbidden** but is never free: it spends a metered provider on a diff no unmetered one has read, so record the diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c9dc6e21..ab70819d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -347,9 +347,12 @@ Opening ready is not forbidden, but it spends a metered provider on a diff no ch seen; the old rationale for allowing it turned on the round counter ADR-0064 retires, so what remains is simply that it costs more for nothing. Record the reason in the PR. Author-side review and status-only output do not satisfy it — and *author-side* is about whose judgement it -is, not which machine ran it: a provider's own review posted on the pull request is external -however its CLI resolved locally, while your transcript of a local run is not, because nothing -but you attests it. **Exhaustion is not incapacity** — a provider with no budget +is, not which machine ran it. The verdict has to be the provider's, quoted as it wrote it; a +review it posted on the pull request is the strongest form, because GitHub attests that it said +so, and a CLI run quoted verbatim is the weaker one the Codex lane actually runs. What has to +hold either way is that **every substantive change reaching the merge was externally reviewed**, +which `AGENTS.md` §Review states operatively and which its fourth condition is what enforces on +the cap-spent path. **Exhaustion is not incapacity** — a provider with no budget left has not reviewed: Greptile out of credits is skippable and never blocks, while CodeRabbit unavailable freezes the PR. diff --git a/docs/PRD.md b/docs/PRD.md index 661538e9..b67ac2e9 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1085,9 +1085,9 @@ leg, and a quota refusal from it means the provider **did not review**, never a credits is skippable, CodeRabbit unavailable **freezes the PR** — though a fair-use refusal that names a retry time is a **wait**, not unavailability, and a request that produced no review is not one of the two completed reviews. Author-side review and a green or status-only -result do not satisfy the gate; *author-side* names whose judgement it is rather than which machine ran it, -so a provider's own review posted on the pull request counts however its CLI resolved locally, while an -author's transcript of a local run does not. **No provider auto-reviews this repository** — CodeRabbit reports auto reviews +result do not satisfy the gate; *author-side* names whose judgement it is rather than which machine ran it, and what +the gate protects is that **every substantive change reaching the merge was externally reviewed** — `AGENTS.md` +§Review is the operative statement of that and of what makes it hold when the reviewed commit is not the merged one. **No provider auto-reviews this repository** — CodeRabbit reports auto reviews disabled, Greptile is held by `.greptile/config.json`'s `skipReview: "AUTOMATIC"`, and Codex fires only on open-for-review, draft-ready, or an `@codex review` comment — so a provider that was not asked has not declined. The one exception is a branch cut **before** that config landed: it is read from the PR's source branch, so such a branch From fc3e930ec043b425f12a5748d725415100c75429 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 00:02:31 -0500 Subject: [PATCH 21/43] fix(agents): drop the source dimension from the post-cap condition entirely Codex round 18, one P1, and the fourth time this one set has been too narrow: a CodeQL or secret-scan alert, or a condition a human sign-off attaches, is mandatory to fix and comes from no review provider at all -- so "whichever provider raised it" still shut the close on work the contract compels. Four drafts, four omissions, one failure mode: 1. only the two CodeRabbit reviews' findings -- shut out the closing read; 2. plus the closing read's own -- omitted Greptile; 3. plus any provider -- omitted CI alerts and human sign-off; 4. no source dimension at all. Each was found by the next review round and never by the drafting, which is the general lesson and is worth more than the rule: an ENUMERATION INSIDE A SAFETY CONDITION IS A LATENT DEADLOCK, because the condition fails closed and the list is always incomplete. ADR-0065 records it that way rather than as four incidents. The condition now tests the CHANGE and not its source: a late hunk must answer something already recorded on the pull request that you were required to address, or be a non-material exception, or be the required main-merge conflict resolution. That is the line it was always drawing -- between work you were obliged to do and scope you chose to add -- and the named examples are illustrations rather than the rule. AGENTS.md, CONTRIBUTING.md, the PR template and ADR-0065 all carry it, swept in this commit rather than left for the next round to find. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .github/pull_request_template.md | 2 +- AGENTS.md | 18 ++++++---- CONTRIBUTING.md | 6 ++-- ...ed-cap-closes-on-the-unmetered-provider.md | 33 ++++++++++++------- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0b574df9..b245238b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -36,7 +36,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped - Codex closing read — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged. Where the provider stamps the commit itself, quote that; where it does not — which is the live case, since Codex's CLI records the working directory and session but not the commit — give the **procedural pin** instead: `git rev-parse HEAD` in the review worktree immediately **before** and immediately **after** the run, plus the PR head at arming time, all three the same 40-hex value. Say which of the two you are giving; the pin proves the head did not move under the read, not that the provider read it. Anything this read surfaces is disposed of above before it closes - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers a finding already recorded on this PR **whichever provider raised it** — either metered review, Greptile, or the closing read's own — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by a metered provider; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing read's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by a metered provider; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) diff --git a/AGENTS.md b/AGENTS.md index 6ee4a599..46324825 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -255,14 +255,18 @@ validity turns on it being the right test — must satisfy both. record moves no head, so demanding one would re-create the deadlock this rule exists to remove. And **nothing but disposal may land after the cap is spent**: everything added after the commit the second completed review actually *read* — its `commit_id`, never its `submitted_at` — must do - one of three things. It must **answer a finding already recorded on this pull request, whichever - provider raised it** — either metered review, Greptile, or a closing read's own — or be one of the + one of three things. It must **answer something already recorded on this pull request that you were + required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a + condition a human sign-off attached, a closing read's own finding — or be one of the non-material exceptions above, or be **the resolution of a conflict in the `main` merge this - contract requires**. The allowed set is stated as a principle and not a list of providers because - every list of it so far has omitted one, and each omission was the same deadlock: the omitted - provider's finding still had to be fixed, the fix answered nobody on the list, and the cap forbade - asking the metered provider again — so the close shut against exactly the pull requests whose - reviews did their job. The conflict resolution is on that footing too and is not optional: + contract requires**. **The first is a test on the change, not on its source**, and the examples are + illustrations rather than the rule: four drafts of it enumerated *who* may raise a finding, each + omitted somebody — first the closing read, then Greptile, then CI alerts and human sign-off — and + every omission was the identical deadlock, because the omitted party's finding still had to be + fixed, the fix answered nobody on the list, and the cap forbade asking the metered provider again. + Any list of sources will keep omitting one; *compelled by something already on the record* cannot, + and it draws the line exactly where it belongs, since what the condition excludes is scope you + chose to add rather than work you were obliged to do. The conflict resolution is on that footing too and is not optional: §Concurrent GitHub Flow orders you to merge a freshly fetched `origin/main` and resolve it here, while the non-material list covers that merge only when it is *clean*. Both admissions turn on the same fact — the change answers something already read, or reconciles two things already read — so diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab70819d..98d44910 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -470,8 +470,10 @@ nothing answered in between: that is one review asked twice. **And nothing but d every hunk added after the commit the second completed review actually read — its `commit_id`, never its `submitted_at`, since a material push landing while that review is still running is a push it never saw — must answer a finding **already recorded on the pull -request, whichever provider raised it** (either metered review, Greptile, or the closing -read's own), or be one of the non-material exceptions above, **or be the resolution of a +request that you were required to address** (a review finding from any provider, a CodeQL or +`secret-scan` alert, a condition a human sign-off attached, the closing read's own finding — +illustrations, not the rule, which is a test on the change and not on its source), or be one +of the non-material exceptions above, **or be the resolution of a conflict in the `main` merge the contract requires** — that merge is non-material only when it is *clean*, so a conflicted one is a material push you were ordered to make, and shutting the close on it would strand any PR that `main` happened to touch. Only the reconciliation is diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index c91343e1..575c6054 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -160,11 +160,12 @@ an agent's account of its own reasoning: 4. **Nothing but disposal may land after the cap is spent.** Every change added after **the commit the second completed review actually read** — its `commit_id`, never its `submitted_at` — must - answer a finding already recorded on the pull request, **whichever provider raised it** — either - metered review, Greptile, or a closing read's own — or be one of the existing non-material + answer something already recorded on the pull request **that the worker was required to address** + — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human + sign-off attached, a closing read's own finding — or be one of the existing non-material exceptions, or be the resolution of a conflict in the `main` merge this contract requires. Anchored at the reviewed commit and applied per change, both for the reasons below, and stated as - a principle rather than a list of providers for the reason after those. + a test on the change rather than on its source for the reason after those. This is the condition that makes the *"third opinion on a twice-read diff"* claim below true rather than merely asserted, and it was missing from the first two drafts. A second Codex review @@ -187,14 +188,24 @@ an agent's account of its own reasoning: provider demonstrably never saw. This record is a rule-stating file and was accepted carrying the weaker wording, which is its own small lesson — an ADR can drift from the contract it records. - And the set is stated as a **principle rather than a list of providers**, because every list of - it drifted. The first admitted only the two CodeRabbit reviews' findings, which shut the close - against any closing read that found something; adding the closing read's own findings then still - omitted **Greptile**, whose findings a worker is equally obliged to fix. Each omission produced - the identical deadlock — the fix was compulsory, it answered nobody on the list, and the cap - forbade another metered read — and each was found only by the next review round. A list of who - may raise a finding will keep omitting somebody; *any provider already recorded on the pull - request* cannot. + And the set is stated as a **test on the change rather than on its source**, because every + version that named sources omitted one. Four drafts, four omissions, one failure mode: + + 1. only the two CodeRabbit reviews' findings — which shut the close against any closing read that + found something; + 2. plus the closing read's own — which still omitted **Greptile**, whose findings a worker is + equally obliged to fix; + 3. plus *any provider* — which still omitted **CodeQL and `secret-scan` alerts, and conditions a + human sign-off attaches**, none of which come from a review provider and all of which are + mandatory; + 4. and finally the test that has no source dimension at all. + + Each omission produced the identical deadlock — the fix was compulsory, it answered nobody on the + list, the cap forbade another metered read — and each was found only by the next review round, + never by the drafting. That is the general lesson and it is worth more than the rule: **an + enumeration inside a safety condition is a latent deadlock**, because the condition fails closed + and the enumeration is always incomplete. The line the condition is actually drawing is between + work you were *obliged* to do and scope you *chose* to add, and that is what it should say. And the set has to admit **the resolution of a conflict in the required `main` merge**. This contract obliges a worker to merge a freshly fetched `origin/main` before merging; the From 06ccae1390686158dc6a089ca52650ee285893fa Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 00:18:11 -0500 Subject: [PATCH 22/43] fix(agents): stop claiming the cap-spent diff was read twice Codex round 19: one P1, two P2. One P2 deferred. P1, mine from two commits ago: CONTRIBUTING.md told a Codex close to quote its verdict "against the commit it names" three lines after correctly saying the CLI names no commit. Points at the procedural pin now. P2, fixed rather than deferred because it falsifies a claim this PR introduces: the close was described as "a third opinion on a twice-read diff". That is false in a case the rules permit -- review 1 clean at A, an in-scope material push to B, review 2 at B finds something, fix, close. The post-cap freeze starts at review 2, so B was legitimate, and B carries exactly ONE metered read. The guarantee the four conditions actually buy is that every substantive part of the merging diff has had at least one metered read; twice is a frequent accident of the lane, not a property. Corrected in AGENTS.md, CONTRIBUTING.md and ADR-0065 -- the last of which is where the argument for the close being safe lives, and an argument that overstates its own premise is worth less than the weaker true one. P2, deferred: the template's Greptile row still offers only "reviewed the final head", so a Greptile review of an earlier head has no truthful state. Already raised in round 7 and already tracked -- Deferred: tracked in #449. Re-raising does not change its severity, and it is a pre-existing gap about a provider this PR does not touch. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- AGENTS.md | 9 +++++++-- CONTRIBUTING.md | 10 +++++++--- ...ed-cap-closes-on-the-unmetered-provider.md | 19 +++++++++++++------ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 46324825..2434a2d0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -277,8 +277,13 @@ validity turns on it being the right test — must satisfy both. the change, not the commit**: a commit that fixes a recorded finding *and* carries an unrelated hunk passes any per-commit test while smuggling exactly the scope this shuts out, so every hunk has to trace to one of the three. New scope pushed after the cap has spent it is scope **no - metered provider will ever read**, and the close is a third opinion on a twice-read diff, never a - first opinion on an unread one — so the close is shut and the PR waits for a gate it can actually + metered provider will ever read**, and what the close is entitled to be is a further opinion on a + diff **every substantive part of which a metered provider has already read** — never a first + opinion on an unread one. Not *twice*-read, which an earlier draft claimed and which is false: if + review 1 came back clean and an in-scope material push then drew review 2, the added part carries + one metered read, not two. One is the guarantee; two is a frequent accident of the lane, and + stating the stronger version would have this bullet overclaim exactly where it is doing its work. + So new scope shuts the close and the PR waits for a gate it can actually satisfy. Motive is not a test and never becomes one; these four are, and they are also why spending an ask to reach the close would buy nothing if it worked, since the close costs the disposal of every finding and a further diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 98d44910..c23c5ccb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -282,8 +282,9 @@ Before requesting review / merging, confirm: **The body convention is CodeRabbit's too** — the opening of its body, where the clean verdict is written by the `Actionable comments posted:` line being **absent** rather than reading `0`. A Codex artifact has no such line and never will; what stands in its place is the verdict the - run actually emitted, quoted, against the commit it names. Asking a Codex close for - CodeRabbit's marker would leave that path with no recordable evidence at all. + run actually emitted, quoted, against the commit **the procedural pin identifies** — not one + the run names, since it names none. Asking a Codex close for CodeRabbit's marker, or for a + commit its artifact does not carry, would leave that path with no recordable evidence at all. **A review of an earlier head does not close the gate**, whether a material push moved the head or a non-material one did. It still stands as one of the two completed reviews, which is exactly what the cap-spent path relies on: each recorded at the head it read, with the Codex @@ -483,7 +484,10 @@ you are obliged to fix it, so any list of *whose* findings count shuts the close fix it just required — and every list drafted here has omitted somebody, first the closing read and then Greptile. What follows such a fix is another stamped read of the head it produced. New scope pushed past that point is scope no metered provider will ever read, and -the close is a third opinion on a twice-read diff, never a first opinion on an unread one. Nothing counts this for you; the +the close is a further opinion on a diff every substantive part of which a metered provider has +already read — never a first opinion on an unread one, and not *twice*-read, which overclaims: +where review 1 was clean and an in-scope material push drew review 2, the added part carries one +metered read. Nothing counts this for you; the merged history is auditable and you are trusted with it. On agent-layer paths (`.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` and the agent test modules) a finding below the severity floor is **dropped rather than tracked**, because there the diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 575c6054..7931bcf3 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -167,8 +167,8 @@ an agent's account of its own reasoning: Anchored at the reviewed commit and applied per change, both for the reasons below, and stated as a test on the change rather than on its source for the reason after those. - This is the condition that makes the *"third opinion on a twice-read diff"* claim below true - rather than merely asserted, and it was missing from the first two drafts. A second Codex review + This is the condition that makes the coverage claim below true rather than merely asserted, and + it was missing from the first two drafts. A second Codex review of this record's own pull request found it: a material push **after** the cap is spent leaves the cap spent, so the close still applied — and the new code would then be read by the closing provider and by nobody else. Material pushes re-arm review but do **not** raise the two-review @@ -310,10 +310,17 @@ escalation was the anomaly rather than the design. **Bad, and named rather than minimised.** The terminal verdict on a capped PR now comes from the provider the repository does **not** pay for, and Codex's reliability is therefore load-bearing in a way it was not before. Two things bound that. It is a fresh read of the exact head being merged, not -a re-quoted earlier pass. And it is reached only after two completed metered reviews have already -read the diff, and condition 4 above is what keeps that true by refusing the close to any scope that -landed after the cap was spent — so it is a third opinion on a twice-read diff, never a first opinion -on an unread one. **If Codex's review quality degrades, this paragraph is the part of the record that +a re-quoted earlier pass. And it is reached only after two completed metered reviews, with condition +4 above refusing the close to any scope that landed after the cap was spent — so **every substantive +part of the merging diff has had at least one metered read**, and the close is a further opinion on +it rather than a first opinion on an unread one. + +An earlier draft of this paragraph put that as *"a third opinion on a twice-read diff"*, and a review +of this record's own pull request showed it false: if review 1 comes back clean and an in-scope +material push then draws review 2, the added part carries **one** metered read, not two. One is the +guarantee the conditions actually buy. The overclaim mattered because this paragraph is the argument +that the close is safe, and an argument that overstates its own premise is worth less than the +weaker true one. **If Codex's review quality degrades, this paragraph is the part of the record that stops holding**, and nothing in this repository would detect that. A second cost, and it is the larger of the two: **§Review grows from 80 lines to 152** — it nearly From ba62a898980629c50085d75de4edd9a217be4235 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 00:31:39 -0500 Subject: [PATCH 23/43] fix(agents): test that nothing is outstanding, not which disposition cleared it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex round 20: one P1, one P2, and both are the same shape as earlier rounds arriving in new places. P1 -- the dispositions were an enumeration, and it omitted one that HAPPENED. A provider can WITHDRAW a finding: it retracts a false positive, the thread resolves, and the finding was neither fixed nor deferred-and-tracked nor dropped sub-floor. #434, this record's own motivating example, reads exactly that way -- so the close was shut on the pull request it was written to unblock, unless a worker mislabelled the disposition to fit the list. The rule now tests whether anything is LEFT OUTSTANDING and names the four known ways of clearing one as ways rather than as the test, which is the same correction condition 4 needed when it enumerated sources. P2 -- the coverage claim was wrong a second time, in the same direction. Round 19 replaced "a third opinion on a twice-read diff" with "every substantive part has had at least one METERED read". Also false: a fix answering review 2 lands after that review's commit_id by design, as do a permitted conflict resolution and anything the closing read raises, and no metered provider ever sees them. The guarantee is EXTERNAL coverage -- metered up to the second review's commit, the closing read after it -- which is precisely what §Review's first bullet already requires. Worth noticing that the honest version of the claim collapsed into a property the contract had all along, rather than something extra the close was quietly relying on. Both swept across AGENTS.md, CONTRIBUTING.md, the PR template and ADR-0065 in this commit, and ADR-0065 keeps both wrong versions visible, since this paragraph is the argument that the close is safe and it has now been caught overstating itself twice. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .github/pull_request_template.md | 2 +- AGENTS.md | 33 +++++++++----- CONTRIBUTING.md | 11 +++-- ...ed-cap-closes-on-the-unmetered-provider.md | 44 ++++++++++++------- 4 files changed, 59 insertions(+), 31 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b245238b..273a88bb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -36,7 +36,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped - Codex closing read — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged. Where the provider stamps the commit itself, quote that; where it does not — which is the live case, since Codex's CLI records the working directory and session but not the commit — give the **procedural pin** instead: `git rev-parse HEAD` in the review worktree immediately **before** and immediately **after** the run, plus the PR head at arming time, all three the same 40-hex value. Say which of the two you are giving; the pin proves the head did not move under the read, not that the provider read it. Anything this read surfaces is disposed of above before it closes - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); every finding from both is fixed, deferred-and-tracked or dropped sub-floor with the thread resolved; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing read's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by a metered provider; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); no finding from either is left outstanding, with the thread resolved on each — cleared by being fixed, deferred-and-tracked, dropped sub-floor, or **withdrawn by the provider that raised it**; *outstanding* is the test and those are the known ways of clearing one; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing read's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by a metered provider; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) diff --git a/AGENTS.md b/AGENTS.md index 2434a2d0..2fe7db50 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -166,7 +166,9 @@ validity turns on it being the right test — must satisfy both. conflict resolution. §Review's fourth condition is what enforces that, and the closing read is what confirms nothing else crept in. So no unreviewed substance merges, which is the property this bullet is protecting; the reviewed *commit* being the merged commit was only ever the ordinary way - of getting it. Quote the provider and name the 40-hex head it read in the PR body. + of getting it. Quote the provider and name the 40-hex head its read covered in the PR body — from + the provider's own artifact where it carries one, and from the procedural pin below where it does + not. - **Open as a draft and get it green there.** Every required check runs on a draft, so the diff reaches fully green before anyone is asked to read it. Opening ready is **not forbidden** but is never free: it spends a metered provider on a diff no unmetered one has read, so record the @@ -209,13 +211,17 @@ validity turns on it being the right test — must satisfy both. decision. - **A spent cap closes on Codex rather than on a maintainer.** When two *completed* CodeRabbit reviews stand on this PR — each one it submitted with a body, since a throttle, a quota refusal or - a failed run reviewed nothing — and every finding they raised is **fixed, deferred-and-tracked, or - dropped sub-floor** with the thread resolved, then a **fresh Codex read of the final head** closes + a failed run reviewed nothing — and **no finding they raised is left outstanding**, with the thread + resolved on each, then a **fresh Codex read of the final head** closes the gate in their place: a full read recorded on the pull request — a review the provider posted, or its own run verdict quoted and pinned to the head by the rule below — never an earlier Codex - pass re-quoted, since the head that pass read is not the head being merged. Anything that - read surfaces is disposed of by those same three dispositions before it closes — the close is a - *substitute for the clean pass*, not a lower bar than it. That review is then *the clean review* + pass re-quoted, since the head that pass read is not the head being merged. *Outstanding* is the + test and the ways of clearing one are **fixed, deferred-and-tracked, dropped sub-floor, or + withdrawn by the provider that raised it** — that last is not hypothetical, a provider retracting + a false positive is how #434's own record reads, and an earlier draft naming only the first three + shut the close on it. Any list of dispositions can miss one the way any list of sources did; what + cannot is *nothing left open*. Anything that read surfaces is cleared the same way before it + closes — the close is a *substitute for the clean pass*, not a lower bar than it. That review is then *the clean review* the merge binding below names. - **The closing read must be pinned to the head it closes, and the pin must be checkable by someone who was not there.** What that rules out is a head *asserted* after the fact: a push landing while @@ -278,11 +284,16 @@ validity turns on it being the right test — must satisfy both. hunk passes any per-commit test while smuggling exactly the scope this shuts out, so every hunk has to trace to one of the three. New scope pushed after the cap has spent it is scope **no metered provider will ever read**, and what the close is entitled to be is a further opinion on a - diff **every substantive part of which a metered provider has already read** — never a first - opinion on an unread one. Not *twice*-read, which an earlier draft claimed and which is false: if - review 1 came back clean and an in-scope material push then drew review 2, the added part carries - one metered read, not two. One is the guarantee; two is a frequent accident of the lane, and - stating the stronger version would have this bullet overclaim exactly where it is doing its work. + diff **every substantive part of which some external provider has already read** — never a first + opinion on an unread one. Two earlier drafts of that sentence overclaimed and both are worth + keeping visible, because the claim is the whole argument for the close being safe. It is not a + *twice*-read diff: where review 1 was clean and an in-scope material push drew review 2, the added + part carries one metered read. And the coverage is not all *metered* either: a fix answering + review 2 lands after that review's `commit_id` by design, as do a permitted conflict resolution + and anything the closing read itself raises, and the only provider that reads those is the closing + read. So the guarantee is **external** coverage of every substantive part, metered up to the + second review's commit and the closing read after it — which is exactly what §Review's first + bullet requires, and no more than that. So new scope shuts the close and the PR waits for a gate it can actually satisfy. Motive is not a test and never becomes one; these four are, and they are also why spending an ask to reach the close diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c23c5ccb..cc1c4edb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -484,10 +484,13 @@ you are obliged to fix it, so any list of *whose* findings count shuts the close fix it just required — and every list drafted here has omitted somebody, first the closing read and then Greptile. What follows such a fix is another stamped read of the head it produced. New scope pushed past that point is scope no metered provider will ever read, and -the close is a further opinion on a diff every substantive part of which a metered provider has -already read — never a first opinion on an unread one, and not *twice*-read, which overclaims: -where review 1 was clean and an in-scope material push drew review 2, the added part carries one -metered read. Nothing counts this for you; the +the close is a further opinion on a diff every substantive part of which **some external provider** +has already read — never a first opinion on an unread one. Not *twice*-read, and not all of it +*metered*: a fix answering review 2 lands after that review's `commit_id` by design, as do a +permitted conflict resolution and anything the closing read raises, and the closing read is the +only provider that reads those. Metered up to the second review's commit, the closing read after +it, external throughout — which is what `AGENTS.md` §Review's first bullet asks for and no more. +Nothing counts this for you; the merged history is auditable and you are trusted with it. On agent-layer paths (`.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` and the agent test modules) a finding below the severity floor is **dropped rather than tracked**, because there the diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 7931bcf3..123c1c96 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -253,10 +253,11 @@ that draft shut the gate permanently: So the requirement was written for evidence the tooling does not produce, and would have replaced a deadlock with a deadlock. What survives is the *property* rather than the mechanism: the head is -**pinned** rather than attested. Where a provider stamps the commit, quote it. Where none does, -record `git rev-parse HEAD` immediately before and immediately after the run alongside the PR head -at arming time, all three equal — which proves the head did not move under the read, and does not -pretend to prove the provider read it. That is weaker, it is stated as weaker, and it rests on the +**pinned** rather than attested. Where a provider stamps the commit, quote it. Where none does, the +**procedural pin** — the term `AGENTS.md` §Review, `CONTRIBUTING.md` and the pull-request template +all use for it — is `git rev-parse HEAD` immediately before and immediately after the run alongside +the PR head at arming time, all three equal, which proves the head did not move under the read and +does not pretend to prove the provider read it. That is weaker, it is stated as weaker, and it rests on the same worker honesty the rest of the section already assumes of anyone quoting a review. **Motive is deliberately not a test.** An earlier draft closed the gaming path with *"an ask made to @@ -272,8 +273,13 @@ review on top. It is strictly more work than the clean pass it replaces. ### The close is a substitute, not a discount -Anything the closing read surfaces is disposed of by the same three dispositions — fixed, -deferred-and-tracked, or dropped sub-floor — before it closes. An earlier draft held the closing read +Anything the closing read surfaces is cleared the same way — **left not outstanding**, by being +fixed, deferred-and-tracked, dropped sub-floor, or withdrawn by the provider that raised it — before +it closes. That last one was missing from three drafts, and it is not hypothetical: a provider +retracting a false positive is how #434's own record reads, so the close was shut on its own +motivating example unless a worker mislabelled the disposition. The test is *nothing left open*; +the four are the known ways of getting there, the same way condition 4 tests the change rather than +naming who may raise a finding. An earlier draft held the closing read to *"nothing blocking"*, which would have silently dropped two severity bands relative to the zero-actionable-comments bar it replaces, and on agent-layer paths those findings are not tracked at all. The bar does not move; only who holds it does. @@ -312,15 +318,23 @@ provider the repository does **not** pay for, and Codex's reliability is therefo way it was not before. Two things bound that. It is a fresh read of the exact head being merged, not a re-quoted earlier pass. And it is reached only after two completed metered reviews, with condition 4 above refusing the close to any scope that landed after the cap was spent — so **every substantive -part of the merging diff has had at least one metered read**, and the close is a further opinion on -it rather than a first opinion on an unread one. - -An earlier draft of this paragraph put that as *"a third opinion on a twice-read diff"*, and a review -of this record's own pull request showed it false: if review 1 comes back clean and an in-scope -material push then draws review 2, the added part carries **one** metered read, not two. One is the -guarantee the conditions actually buy. The overclaim mattered because this paragraph is the argument -that the close is safe, and an argument that overstates its own premise is worth less than the -weaker true one. **If Codex's review quality degrades, this paragraph is the part of the record that +part of the merging diff has had at least one external read**, metered up to the commit the second +review read and the closing read after it, and the close is a further opinion on it rather than a +first opinion on an unread one. + +This paragraph has now been wrong twice in the same direction, which is worth keeping visible because +it *is* the argument that the close is safe. It first said *"a third opinion on a twice-read diff"*, +and a review of this record's own pull request showed it false: if review 1 comes back clean and an +in-scope +material push then draws review 2, the added part carries **one** metered read, not two. The +correction then claimed one *metered* read of every substantive part, and the next round showed that +false too: a fix answering review 2 lands after that review's `commit_id` by design, as do a +permitted conflict resolution and anything the closing read raises, and no metered provider ever sees +them. What the conditions actually buy is **external** coverage throughout — which is what §Review's +first bullet asks for, and it is worth noticing that the honest version of this claim turned out to +be exactly the property that bullet already states, rather than something stronger the close was +smuggling in. An argument that overstates its own premise is worth less than the weaker true one, +and this one took two rounds of being caught to stop doing it. **If Codex's review quality degrades, this paragraph is the part of the record that stops holding**, and nothing in this repository would detect that. A second cost, and it is the larger of the two: **§Review grows from 80 lines to 152** — it nearly From 2fe0bde78aba9e92ef611441b88d68f72e6e75e1 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 00:47:41 -0500 Subject: [PATCH 24/43] fix(agents): stop a rule-editing PR from supplying its own reviewer's rules Codex round 21, two P1. The first is new and it is about this pull request as much as about the rule. The Codex leg runs through the CLI; the CLI discovers AGENTS.md from the checkout it runs in; so a PR that edits AGENTS.md hands the instructions to the one provider reading its final head. The branch grades itself under its own unmerged contract, which is what "only agent instructions on the default branch govern; unmerged edits are inert" refuses in this file's first paragraph. The close did not create that, but it makes it reachable, by putting a rule-editing PR's fate on a CLI read. On a diff touching any rule-stating file the closing read now runs codex review --strict-config -c project_doc_max_bytes=0 --base origin/main Both flags matter and both were VERIFIED against the installed CLI 0.147.0 rather than assumed: a deliberately bogus key is rejected under --strict-config, and project_doc_max_bytes is accepted. Without --strict-config a mistyped override is silently ignored, which fails in the worst direction -- a read that looks isolated and is not. Stated in ADR-0065 rather than left implicit: EVERY round on this PR so far ran without that isolation, in a worktree carrying the modified AGENTS.md. Those rounds were adversarial throughout and found twenty-odd defects in the very text feeding them, so there is no sign it mattered -- but "no sign it mattered" is not the property the trust boundary asks for, and the next round is re-run with the flag. The second P1 is the sweep: CONTRIBUTING.md and docs/PRD.md still sent workers to the GitHub bot's triggers, and AGENTS.md's cap bullet still called Codex unqualifiedly unmetered, after the lane bullet had already said the bot is metered and spent. All three now distinguish the CLI from the bot. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- AGENTS.md | 16 +++++++++++-- CONTRIBUTING.md | 16 +++++++++---- docs/PRD.md | 6 +++-- ...ed-cap-closes-on-the-unmetered-provider.md | 23 +++++++++++++++++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2fe7db50..7ebfdbe7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -240,6 +240,16 @@ validity turns on it being the right test — must satisfy both. already places in a worker who quotes a review. Requiring an attestation the tooling cannot produce would not buy the stronger property — it would shut the close permanently, which is the deadlock this whole branch exists to remove. +- **On a PR that edits agent instructions, the closing read must not be run under them.** The CLI + discovers `AGENTS.md` from the checkout it runs in, so a pull request changing this file would + otherwise supply the rules to the one provider reading its final head — the branch grading itself + by its own unmerged contract, which is exactly what *"only agent instructions on the default + branch govern"* refuses at the top of this file. Run it with project-document discovery off — + `codex review --strict-config -c project_doc_max_bytes=0 --base origin/main`, where + `--strict-config` is what makes a mistyped key fail loudly instead of silently leaving discovery + on — and say in the PR that you did. This binds only when the diff touches a file that states a + rule; everywhere else the checkout's instructions are `main`'s anyway and the flag changes + nothing. - **Four things shut that close, and each is readable off the pull request rather than out of your own account of why you did something.** A refusal is **not** a spent cap: it reviewed nothing, so it is a wait, and waiting is still what you do. If either completed review came back clean, its @@ -320,8 +330,10 @@ validity turns on it being the right test — must satisfy both. They accept bug fixes and safety fixes only; a capability change needs a maintainer-opened issue and may not originate in a review finding. - **Two completed reviews per METERED provider, then stop.** The cap bounds how many times a - provider whose reads cost money or quota is made to *read the diff*, so **Codex is - uncapped** — it is unmetered, and throttling it bought nothing but slower convergence. + provider whose reads cost money or quota is made to *read the diff*, so **the Codex CLI is + uncapped** — it is unmetered, and throttling it bought nothing but slower convergence. The GitHub + Codex bot is a different provider for this purpose: its reviews are metered and its meter is + spent, so it declines rather than reads (§the lane, above). Otherwise **a request that produced no review is not one of the two** — a throttle, a quota refusal or a failed run reviewed nothing, which is the same rule — a refusal means the provider did not review — diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc1c4edb..e0e440f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -358,10 +358,18 @@ left has not reviewed: Greptile out of credits is skippable and never blocks, wh CodeRabbit unavailable freezes the PR. **No provider auto-reviews this repository; you have to ask.** CodeRabbit replies -to an unrequested PR with *"Auto reviews are disabled on this repository"*, and Codex -reviews only when you open a PR for review, mark a draft ready, or comment -`@codex review`. A provider that was never asked has not declined — so if you are -waiting on a review, check that a request was actually posted. +to an unrequested PR with *"Auto reviews are disabled on this repository"*. A provider +that was never asked has not declined — so if you are waiting on a review, check that a +request was actually posted. + +**Codex is asked through its CLI here, not through the GitHub bot.** The bot does trigger +on opening a PR for review, marking a draft ready, or an `@codex review` comment — but its +code reviews are metered on this account and the meter is spent, so every one of its +appearances in this repository is the same usage-limit refusal, and a refusal is not a +review. Run `codex review --base origin/main` in the PR's worktree instead, and add +`--strict-config -c project_doc_max_bytes=0` when the diff touches a file that states a +rule, so the branch does not supply the instructions its own reviewer follows +(`AGENTS.md` §Review). One exception, and it has already cost money: `.greptile/config.json` is read from the pull request's **source branch**, so a branch cut before that file landed still diff --git a/docs/PRD.md b/docs/PRD.md index b67ac2e9..8caf66a8 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1088,8 +1088,10 @@ Author-side review and a green or status-only result do not satisfy the gate; *author-side* names whose judgement it is rather than which machine ran it, and what the gate protects is that **every substantive change reaching the merge was externally reviewed** — `AGENTS.md` §Review is the operative statement of that and of what makes it hold when the reviewed commit is not the merged one. **No provider auto-reviews this repository** — CodeRabbit reports auto reviews -disabled, Greptile is held by `.greptile/config.json`'s `skipReview: "AUTOMATIC"`, and Codex fires only on -open-for-review, draft-ready, or an `@codex review` comment — so a provider that was not asked has not declined. The +disabled, Greptile is held by `.greptile/config.json`'s `skipReview: "AUTOMATIC"`, and the GitHub Codex bot fires only on +open-for-review, draft-ready, or an `@codex review` comment — so a provider that was not asked has not declined. That +bot's reviews are metered on this account and its meter is spent, so the Codex leg is run through the **CLI**; `AGENTS.md` +§Review states how, including the isolation flag a rule-editing PR needs. The one exception is a branch cut **before** that config landed: it is read from the PR's source branch, so such a branch still auto-fires Greptile on open. The credit is spent regardless, so the review is answered and the optional Greptile step recorded as spent, never discarded as unsolicited. diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 123c1c96..a9063e5e 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -260,6 +260,29 @@ the PR head at arming time, all three equal, which proves the head did not move does not pretend to prove the provider read it. That is weaker, it is stated as weaker, and it rests on the same worker honesty the rest of the section already assumes of anyone quoting a review. +### The closer must not read the rules the branch is proposing + +Because the Codex leg runs through the CLI, and the CLI discovers `AGENTS.md` from the checkout it +runs in, a pull request that edits `AGENTS.md` supplies the instructions to the one provider reading +its final head. The branch would be graded by its own unmerged contract — precisely what +*"only agent instructions on the default branch govern; unmerged edits are inert"* refuses in the +first paragraph of that file. Nothing about the close created this; the close is what makes it +reachable, by putting a rule-editing PR's fate in the hands of a CLI read. + +So on a diff touching any file that states a rule, the closing read runs +`codex review --strict-config -c project_doc_max_bytes=0 --base origin/main`. The two flags do +different jobs and both are load-bearing: the second turns project-document discovery off, and +`--strict-config` makes a mistyped key **fail** rather than be ignored, which matters because the +failure mode of a silently-dropped override is a read that looks isolated and is not. Both were +verified against the installed CLI (0.147.0) rather than assumed — a deliberately bogus key is +rejected under `--strict-config`, and `project_doc_max_bytes` is accepted. + +This is worth stating plainly: **every review round on this record's own pull request ran without +that isolation**, in a worktree carrying the modified `AGENTS.md`. Those rounds were adversarial +throughout and found twenty-odd defects in the text feeding them, so there is no sign it mattered +here — but "no sign it mattered" is not the property the trust boundary asks for, and the last round +of this pull request was re-run with the flag. + **Motive is deliberately not a test.** An earlier draft closed the gaming path with *"an ask made to spend the cap is not one of the two."* That was rejected on review for two reasons: a motive is not checkable by anyone, including the agent itself; and read strictly it **restores the deadlock in a From c147d429b81ddf9a83e19ba023939663c79379b7 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 01:03:09 -0500 Subject: [PATCH 25/43] fix(agents): scope the isolation flag to the two files the CLI actually reads Codex round 22 -- the first round run WITH the isolation the previous commit added -- and it found that the isolation itself was too wide. Two P1, one P2. The flag is all-or-nothing: project_doc_max_bytes=0 denies the reviewer main's contract as well as the branch's. Scoped to "any file that states a rule" it therefore fired on a CONTRIBUTING.md- or template-only diff, where AGENTS.md was never at risk and the only effect was a less informed read. Narrowed to AGENTS.md and CLAUDE.md, which are the two the CLI discovers, with the cost stated rather than implied: this is a trade worth making when the diff edits the instructions and a pure loss when it does not. There is no narrower switch -- --base selects the diff and cannot substitute origin/main's copy of the instructions, so "review under the default-branch contract" is not something the CLI can be asked for. The second P1 is round 20's correction, missed in the one place it also had to land: the PR template still made the author attest that no new scope reached the merge "unread by a metered provider", which is false on the motivating path -- the fix answering review 2 is covered only by the closing read. Now external, matching AGENTS.md and CONTRIBUTING.md. P2: docs/PRD.md claimed AGENTS.md is "the only statement" of the close's conditions while this same diff has CONTRIBUTING.md and the template carrying working copies. An editor trusting that provenance would update one file and leave two operative surfaces stale. It is the OPERATIVE statement, and the copies defer to it. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .github/pull_request_template.md | 2 +- AGENTS.md | 21 ++++++++++++------- CONTRIBUTING.md | 8 ++++--- docs/PRD.md | 6 ++++-- ...ed-cap-closes-on-the-unmetered-provider.md | 12 ++++++++++- 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 273a88bb..13f2a1a8 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -36,7 +36,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped - Codex closing read — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged. Where the provider stamps the commit itself, quote that; where it does not — which is the live case, since Codex's CLI records the working directory and session but not the commit — give the **procedural pin** instead: `git rev-parse HEAD` in the review worktree immediately **before** and immediately **after** the run, plus the PR head at arming time, all three the same 40-hex value. Say which of the two you are giving; the pin proves the head did not move under the read, not that the provider read it. Anything this read surfaces is disposed of above before it closes - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); no finding from either is left outstanding, with the thread resolved on each — cleared by being fixed, deferred-and-tracked, dropped sub-floor, or **withdrawn by the provider that raised it**; *outstanding* is the test and those are the known ways of clearing one; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing read's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by a metered provider; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); no finding from either is left outstanding, with the thread resolved on each — cleared by being fixed, deferred-and-tracked, dropped sub-floor, or **withdrawn by the provider that raised it**; *outstanding* is the test and those are the known ways of clearing one; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing read's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by an **external** provider — metered up to the commit the second review read, the closing read after it, which is the guarantee the conditions buy and not a metered read of everything; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) diff --git a/AGENTS.md b/AGENTS.md index 7ebfdbe7..05a493e3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -240,16 +240,21 @@ validity turns on it being the right test — must satisfy both. already places in a worker who quotes a review. Requiring an attestation the tooling cannot produce would not buy the stronger property — it would shut the close permanently, which is the deadlock this whole branch exists to remove. -- **On a PR that edits agent instructions, the closing read must not be run under them.** The CLI - discovers `AGENTS.md` from the checkout it runs in, so a pull request changing this file would - otherwise supply the rules to the one provider reading its final head — the branch grading itself - by its own unmerged contract, which is exactly what *"only agent instructions on the default - branch govern"* refuses at the top of this file. Run it with project-document discovery off — +- **When the diff edits `AGENTS.md` or `CLAUDE.md`, the closing read must not be run under them.** + Those two are what the CLI discovers from the checkout it runs in, so a pull request changing them + would otherwise supply the rules to the one provider reading its final head — the branch grading + itself by its own unmerged contract, which is what *"only agent instructions on the default branch + govern"* refuses at the top of this file. Run it with project-document discovery off — `codex review --strict-config -c project_doc_max_bytes=0 --base origin/main`, where `--strict-config` is what makes a mistyped key fail loudly instead of silently leaving discovery - on — and say in the PR that you did. This binds only when the diff touches a file that states a - rule; everywhere else the checkout's instructions are `main`'s anyway and the flag changes - nothing. + on — and say in the PR that you did. + **Only those two files, and the cost is real rather than nil.** The switch is all-or-nothing: it + denies the reviewer `main`'s contract as well as the branch's, so the read is less informed than + an ordinary one. That is accepted here because the alternative is worse — a diff choosing the + rules by which it is judged — but it buys nothing on a PR that edits `CONTRIBUTING.md` or a + template while leaving these two alone, and there the flag is wrong rather than merely + unnecessary. There is no narrower switch: `--base` picks the diff and does not substitute + `origin/main`'s copy of the instructions. - **Four things shut that close, and each is readable off the pull request rather than out of your own account of why you did something.** A refusal is **not** a spent cap: it reviewed nothing, so it is a wait, and waiting is still what you do. If either completed review came back clean, its diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e0e440f7..ade771b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -367,9 +367,11 @@ on opening a PR for review, marking a draft ready, or an `@codex review` comment code reviews are metered on this account and the meter is spent, so every one of its appearances in this repository is the same usage-limit refusal, and a refusal is not a review. Run `codex review --base origin/main` in the PR's worktree instead, and add -`--strict-config -c project_doc_max_bytes=0` when the diff touches a file that states a -rule, so the branch does not supply the instructions its own reviewer follows -(`AGENTS.md` §Review). +`--strict-config -c project_doc_max_bytes=0` when the diff touches **`AGENTS.md` or +`CLAUDE.md`** — the two the CLI discovers — so the branch does not supply the instructions +its own reviewer follows. Only those two: the switch also denies the reviewer `main`'s +contract, which is a price worth paying when the diff edits the instructions and a pure +loss when it does not (`AGENTS.md` §Review). One exception, and it has already cost money: `.greptile/config.json` is read from the pull request's **source branch**, so a branch cut before that file landed still diff --git a/docs/PRD.md b/docs/PRD.md index 8caf66a8..8aa3b0c3 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1064,8 +1064,10 @@ one Greptile review**, if the seat has budget — a *review*, not a credit, sinc TREX one three; then ready-for-review if it is not already; then **CodeRabbit with no actionable comments, which is the last metered gate before merge — and when that two-review cap is spent, a fresh Codex read of the final head closes the gate in its place, so a reviewed PR finishes on an unmetered read rather -than on a maintainer (ADR-0065)**. **`AGENTS.md` §Review sets the conditions that close is subject to -and is the only statement of them; this document deliberately restates none.** Four drafts of this +than on a maintainer (ADR-0065)**. **`AGENTS.md` §Review is the operative statement of the conditions +that close is subject to, and this document deliberately restates none of them** — `CONTRIBUTING.md` +and the pull-request template do carry working copies, as detail and as evidence fields, and both +defer to §Review where they differ. Four drafts of this paragraph tried to summarise them and each admitted or refused a case the contract does not — a restatement that is wrong in either direction is worse than a pointer, because `AGENTS.md` tells a worker to stop when these two files disagree, so the summary strands the pull request either way. The round diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index a9063e5e..35251d0e 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -269,7 +269,8 @@ its final head. The branch would be graded by its own unmerged contract — prec first paragraph of that file. Nothing about the close created this; the close is what makes it reachable, by putting a rule-editing PR's fate in the hands of a CLI read. -So on a diff touching any file that states a rule, the closing read runs +So on a diff touching **`AGENTS.md` or `CLAUDE.md`** — the two files the CLI actually discovers — +the closing read runs `codex review --strict-config -c project_doc_max_bytes=0 --base origin/main`. The two flags do different jobs and both are load-bearing: the second turns project-document discovery off, and `--strict-config` makes a mistyped key **fail** rather than be ignored, which matters because the @@ -277,6 +278,15 @@ failure mode of a silently-dropped override is a read that looks isolated and is verified against the installed CLI (0.147.0) rather than assumed — a deliberately bogus key is rejected under `--strict-config`, and `project_doc_max_bytes` is accepted. +**The switch is blunt and the first draft of this rule fired it too widely.** It is all-or-nothing: +turning discovery off denies the reviewer `main`'s contract as well as the branch's, so the read is +less informed than an ordinary one. That trade is worth making when the diff edits the instructions +themselves, and it is simply a loss on a pull request that edits `CONTRIBUTING.md` or a template +while leaving those two alone — which the first draft, scoped to *any* rule-stating file, would have +done. There is no narrower switch available: `--base` selects the diff and does not substitute +`origin/main`'s copy of the instructions, so "review under the default-branch contract" is not +something the CLI can be asked for. + This is worth stating plainly: **every review round on this record's own pull request ran without that isolation**, in a worktree carrying the modified `AGENTS.md`. Those rounds were adversarial throughout and found twenty-odd defects in the text feeding them, so there is no sign it mattered From 9db25d626a808625ef99ed9f95ca7a4fd6c0406e Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 01:19:09 -0500 Subject: [PATCH 26/43] fix(agents): scope the isolation by property, after mis-scoping it in both directions Codex round 23, one P1: the CLI also discovers AGENTS.override.md and gives it PRECEDENCE over AGENTS.md, so a cap-spent PR adding one kept the exact hole the previous commit closed while appearing to satisfy the rule. Verified rather than taken on trust -- the filename literals were read out of the installed binary (0.147.0): AGENTS.md, AGENTS.override.md and CLAUDE.md, with the override adjacent to AGENTS.md in what looks like a precedence pair. The rule was now mis-scoped twice, once each way, and that pair is the point. "Any rule-stating file" was too wide: a CONTRIBUTING.md-only diff was never at risk, and the flag costs the reviewer main's contract for nothing. "AGENTS.md or CLAUDE.md" was too narrow, by exactly one filename. Naming files was the error both times, so the condition is now the PROPERTY -- anything the CLI loads as project instructions -- with the three current names recorded as the verified set rather than as the test, so a release that adds a fourth does not silently re-open it. That is the third time this pull request has had to make the same correction: condition 4 enumerated who may raise a finding, the disposition list enumerated how one may be cleared, and this enumerated where instructions come from. ADR-0065 now states it once as the general rule -- inside a safety condition a list is a hole or a deadlock waiting for the next case -- instead of three times as three incidents. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- AGENTS.md | 19 ++++++++++++------- CONTRIBUTING.md | 11 ++++++----- ...ed-cap-closes-on-the-unmetered-provider.md | 18 ++++++++++++++++-- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 05a493e3..ef612c0f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -240,19 +240,24 @@ validity turns on it being the right test — must satisfy both. already places in a worker who quotes a review. Requiring an attestation the tooling cannot produce would not buy the stronger property — it would shut the close permanently, which is the deadlock this whole branch exists to remove. -- **When the diff edits `AGENTS.md` or `CLAUDE.md`, the closing read must not be run under them.** - Those two are what the CLI discovers from the checkout it runs in, so a pull request changing them - would otherwise supply the rules to the one provider reading its final head — the branch grading - itself by its own unmerged contract, which is what *"only agent instructions on the default branch - govern"* refuses at the top of this file. Run it with project-document discovery off — +- **When the diff adds or edits anything the CLI loads as project instructions, the closing read + must not be run under it.** That is the test; in the installed 0.147.0 the set is `AGENTS.md`, + **`AGENTS.override.md`** — which takes precedence over it — and `CLAUDE.md`, at the root or in a + changed subtree, read out of the binary rather than assumed. Name the property and not the three, + because a version that adds a fourth would silently re-open this: a pull request adding an + override the reviewer then obeys is the same self-grading whatever the file is called. A PR + changing any of them would otherwise supply the rules to the one provider reading its final + head — the branch grading itself by its own unmerged contract, which is what *"only agent + instructions on the default branch govern"* refuses at the top of this file. Run it with + project-document discovery off — `codex review --strict-config -c project_doc_max_bytes=0 --base origin/main`, where `--strict-config` is what makes a mistyped key fail loudly instead of silently leaving discovery on — and say in the PR that you did. - **Only those two files, and the cost is real rather than nil.** The switch is all-or-nothing: it + **Only those files, and the cost is real rather than nil.** The switch is all-or-nothing: it denies the reviewer `main`'s contract as well as the branch's, so the read is less informed than an ordinary one. That is accepted here because the alternative is worse — a diff choosing the rules by which it is judged — but it buys nothing on a PR that edits `CONTRIBUTING.md` or a - template while leaving these two alone, and there the flag is wrong rather than merely + template while leaving the instruction files alone, and there the flag is wrong rather than merely unnecessary. There is no narrower switch: `--base` picks the diff and does not substitute `origin/main`'s copy of the instructions. - **Four things shut that close, and each is readable off the pull request rather than out of your diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ade771b3..95008eb8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -367,11 +367,12 @@ on opening a PR for review, marking a draft ready, or an `@codex review` comment code reviews are metered on this account and the meter is spent, so every one of its appearances in this repository is the same usage-limit refusal, and a refusal is not a review. Run `codex review --base origin/main` in the PR's worktree instead, and add -`--strict-config -c project_doc_max_bytes=0` when the diff touches **`AGENTS.md` or -`CLAUDE.md`** — the two the CLI discovers — so the branch does not supply the instructions -its own reviewer follows. Only those two: the switch also denies the reviewer `main`'s -contract, which is a price worth paying when the diff edits the instructions and a pure -loss when it does not (`AGENTS.md` §Review). +`--strict-config -c project_doc_max_bytes=0` when the diff adds or edits **anything the CLI +loads as project instructions** — `AGENTS.md`, `AGENTS.override.md` (higher precedence) and +`CLAUDE.md` in the installed 0.147.0 — so the branch does not supply the instructions its own +reviewer follows. Only those: the switch also denies the reviewer `main`'s contract, which is +a price worth paying when the diff edits the instructions and a pure loss when it does not +(`AGENTS.md` §Review, which states the test as the property rather than the three names). One exception, and it has already cost money: `.greptile/config.json` is read from the pull request's **source branch**, so a branch cut before that file landed still diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 35251d0e..f98dc7a5 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -269,7 +269,9 @@ its final head. The branch would be graded by its own unmerged contract — prec first paragraph of that file. Nothing about the close created this; the close is what makes it reachable, by putting a rule-editing PR's fate in the hands of a CLI read. -So on a diff touching **`AGENTS.md` or `CLAUDE.md`** — the two files the CLI actually discovers — +So on a diff adding or editing **anything the CLI loads as project instructions** — `AGENTS.md`, +`AGENTS.override.md` and `CLAUDE.md` in the installed 0.147.0, read out of the binary rather than +assumed, with the override taking precedence over `AGENTS.md` — the closing read runs `codex review --strict-config -c project_doc_max_bytes=0 --base origin/main`. The two flags do different jobs and both are load-bearing: the second turns project-document discovery off, and @@ -278,7 +280,19 @@ failure mode of a silently-dropped override is a read that looks isolated and is verified against the installed CLI (0.147.0) rather than assumed — a deliberately bogus key is rejected under `--strict-config`, and `project_doc_max_bytes` is accepted. -**The switch is blunt and the first draft of this rule fired it too widely.** It is all-or-nothing: +**This rule was mis-scoped twice, once in each direction, and the pair is the lesson.** The first +draft fired on *any* rule-stating file, which is too wide — a `CONTRIBUTING.md`-only diff was never +at risk and the flag costs the reviewer `main`'s contract for nothing. Narrowing it to `AGENTS.md` +and `CLAUDE.md` was then too narrow: the CLI also discovers **`AGENTS.override.md`**, and gives it +*precedence*, so a pull request adding one kept the exact hole the rule exists to close while +appearing to satisfy it. Naming files was the error both times. The condition is now the property — +*anything the CLI loads as project instructions* — with the three current names given as the +verified set rather than as the test, so a version that adds a fourth does not silently re-open it. +That is the same correction condition 4 needed when it enumerated sources, and the disposition list +needed when it enumerated dispositions: **inside a safety condition, a list is a hole or a deadlock +waiting for the next case.** + +**The switch is blunt.** It is all-or-nothing: turning discovery off denies the reviewer `main`'s contract as well as the branch's, so the read is less informed than an ordinary one. That trade is worth making when the diff edits the instructions themselves, and it is simply a loss on a pull request that edits `CONTRIBUTING.md` or a template From 0b22dc34b24e0475bd0fd6e7e30695b5dba72661 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 01:33:24 -0500 Subject: [PATCH 27/43] fix(agents): stop guessing what Codex loads, and over-approximate on purpose Codex round 24, one P1 and one P2, and together they are the fourth mis-scoping of the same sub-rule: the trigger missed branch-local skills under .agents/skills/**, whose metadata the CLI injects -- so a changed skill description could pull an unmerged SKILL.md into the review -- while also firing on root-CLAUDE.md-only diffs, which Codex reportedly does not load as a project document when a root AGENTS.md is present. Four attempts, each wrong in BOTH directions, each caught only by the next round: any rule-stating file; AGENTS.md+CLAUDE.md; those three plus AGENTS.override.md; and now the skills gap. What they share is that the contract was asserting how Codex RESOLVES instructions, and reading filename literals out of a binary does not establish that -- precedence, fallback and skill injection are behaviour, not strings, and they move between versions. So the trigger stops being a claim about Codex and becomes a policy choice: fire on the agent-layer paths AGENTS.md already names (.agents/, docs/agents/, AGENTS.md, CLAUDE.md), accept that this over-fires, and say why. Over-firing costs review QUALITY on a narrow class of PRs, since the switch also denies the reviewer main's contract; under-firing leaves a SELF-GRADING path open. That asymmetry is what makes over-approximation right here rather than merely cautious. #451 filed to establish the real set by OBSERVING what a review actually receives, per file kind and per precedence, rather than by inference -- and to re-check it on each CLI bump instead of re-arguing it. ADR-0065 records all four attempts and the addition this instance makes to the enumeration lesson: when the true list is a fact about someone else's tool, do not encode a guess at it. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- AGENTS.md | 39 +++++++------ CONTRIBUTING.md | 13 +++-- ...ed-cap-closes-on-the-unmetered-provider.md | 57 ++++++++++++------- 3 files changed, 63 insertions(+), 46 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ef612c0f..4cd1841f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -240,26 +240,29 @@ validity turns on it being the right test — must satisfy both. already places in a worker who quotes a review. Requiring an attestation the tooling cannot produce would not buy the stronger property — it would shut the close permanently, which is the deadlock this whole branch exists to remove. -- **When the diff adds or edits anything the CLI loads as project instructions, the closing read - must not be run under it.** That is the test; in the installed 0.147.0 the set is `AGENTS.md`, - **`AGENTS.override.md`** — which takes precedence over it — and `CLAUDE.md`, at the root or in a - changed subtree, read out of the binary rather than assumed. Name the property and not the three, - because a version that adds a fourth would silently re-open this: a pull request adding an - override the reviewer then obeys is the same self-grading whatever the file is called. A PR - changing any of them would otherwise supply the rules to the one provider reading its final - head — the branch grading itself by its own unmerged contract, which is what *"only agent - instructions on the default branch govern"* refuses at the top of this file. Run it with - project-document discovery off — +- **When the diff touches an agent-layer path, the closing read runs isolated.** Those paths are the + ones §Review already names — `.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` — and the + command is `codex review --strict-config -c project_doc_max_bytes=0 --base origin/main`, where `--strict-config` is what makes a mistyped key fail loudly instead of silently leaving discovery - on — and say in the PR that you did. - **Only those files, and the cost is real rather than nil.** The switch is all-or-nothing: it - denies the reviewer `main`'s contract as well as the branch's, so the read is less informed than - an ordinary one. That is accepted here because the alternative is worse — a diff choosing the - rules by which it is judged — but it buys nothing on a PR that edits `CONTRIBUTING.md` or a - template while leaving the instruction files alone, and there the flag is wrong rather than merely - unnecessary. There is no narrower switch: `--base` picks the diff and does not substitute - `origin/main`'s copy of the instructions. + on. Say in the PR that you did it. Otherwise the CLI reads instructions out of the checkout it + runs in, and a pull request editing them supplies the rules to the one provider reading its final + head — the branch grading itself by its own unmerged contract, which is what *"only agent + instructions on the default branch govern"* refuses at the top of this file. + **This trigger deliberately over-approximates, because the exact set is not something this + contract can state truthfully.** Four attempts to name it were wrong in both directions: *any + rule-stating file* fired on diffs that were never at risk; `AGENTS.md`+`CLAUDE.md` missed + `AGENTS.override.md`, which the CLI also loads and gives precedence; adding that still missed + branch-local skills under `.agents/skills/**`, whose metadata the CLI injects; and `CLAUDE.md` + itself may not be loaded at all when a root `AGENTS.md` is present. Establishing the real + discovery order needs knowledge of the tool's internals that reading its binary does not give, + and it changes between versions. So this trigger is a **policy choice and not a claim about + Codex**: over-fire, and take the cost. That cost is real — the switch is all-or-nothing, so it + denies the reviewer `main`'s contract as well as the branch's, and the read is less informed than + an ordinary one. It is the right way round, because over-firing costs review *quality* on a + narrow class of PRs while under-firing leaves a *self-grading* path open, and there is no narrower + switch: `--base` picks the diff and cannot substitute `origin/main`'s copy of the instructions. + **#451 tracks pinning the real set down**; until it does, the over-approximation stands. - **Four things shut that close, and each is readable off the pull request rather than out of your own account of why you did something.** A refusal is **not** a spent cap: it reviewed nothing, so it is a wait, and waiting is still what you do. If either completed review came back clean, its diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95008eb8..4245969b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -367,12 +367,13 @@ on opening a PR for review, marking a draft ready, or an `@codex review` comment code reviews are metered on this account and the meter is spent, so every one of its appearances in this repository is the same usage-limit refusal, and a refusal is not a review. Run `codex review --base origin/main` in the PR's worktree instead, and add -`--strict-config -c project_doc_max_bytes=0` when the diff adds or edits **anything the CLI -loads as project instructions** — `AGENTS.md`, `AGENTS.override.md` (higher precedence) and -`CLAUDE.md` in the installed 0.147.0 — so the branch does not supply the instructions its own -reviewer follows. Only those: the switch also denies the reviewer `main`'s contract, which is -a price worth paying when the diff edits the instructions and a pure loss when it does not -(`AGENTS.md` §Review, which states the test as the property rather than the three names). +`--strict-config -c project_doc_max_bytes=0` whenever the diff touches an **agent-layer +path** — `.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` — so the branch does not supply +the instructions its own reviewer follows. That trigger deliberately over-fires: the switch +also denies the reviewer `main`'s contract, and four attempts to name the exact set Codex +loads were wrong in both directions, so the contract stops claiming to know it and errs +toward isolation instead. `AGENTS.md` §Review carries the reasoning; **#451** is where the +real set gets established. One exception, and it has already cost money: `.greptile/config.json` is read from the pull request's **source branch**, so a branch cut before that file landed still diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index f98dc7a5..41a0034e 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -269,35 +269,48 @@ its final head. The branch would be graded by its own unmerged contract — prec first paragraph of that file. Nothing about the close created this; the close is what makes it reachable, by putting a rule-editing PR's fate in the hands of a CLI read. -So on a diff adding or editing **anything the CLI loads as project instructions** — `AGENTS.md`, -`AGENTS.override.md` and `CLAUDE.md` in the installed 0.147.0, read out of the binary rather than -assumed, with the override taking precedence over `AGENTS.md` — -the closing read runs +So on a diff touching an **agent-layer path** — `.agents/`, `docs/agents/`, `AGENTS.md`, +`CLAUDE.md`, the set §Review already names for other purposes — the closing read runs `codex review --strict-config -c project_doc_max_bytes=0 --base origin/main`. The two flags do different jobs and both are load-bearing: the second turns project-document discovery off, and `--strict-config` makes a mistyped key **fail** rather than be ignored, which matters because the failure mode of a silently-dropped override is a read that looks isolated and is not. Both were -verified against the installed CLI (0.147.0) rather than assumed — a deliberately bogus key is -rejected under `--strict-config`, and `project_doc_max_bytes` is accepted. - -**This rule was mis-scoped twice, once in each direction, and the pair is the lesson.** The first -draft fired on *any* rule-stating file, which is too wide — a `CONTRIBUTING.md`-only diff was never -at risk and the flag costs the reviewer `main`'s contract for nothing. Narrowing it to `AGENTS.md` -and `CLAUDE.md` was then too narrow: the CLI also discovers **`AGENTS.override.md`**, and gives it -*precedence*, so a pull request adding one kept the exact hole the rule exists to close while -appearing to satisfy it. Naming files was the error both times. The condition is now the property — -*anything the CLI loads as project instructions* — with the three current names given as the -verified set rather than as the test, so a version that adds a fourth does not silently re-open it. -That is the same correction condition 4 needed when it enumerated sources, and the disposition list -needed when it enumerated dispositions: **inside a safety condition, a list is a hole or a deadlock -waiting for the next case.** +verified against the installed CLI (0.147.0) — a deliberately bogus key is rejected under +`--strict-config`, and `project_doc_max_bytes` is accepted. + +**That trigger over-approximates on purpose, and the four attempts it took to get there are the +reason.** Each named a set; each was wrong, in both directions; each was caught only by the next +review round: + +1. *any rule-stating file* — too wide: a `CONTRIBUTING.md`- or template-only diff was never at risk, + and the flag costs the reviewer `main`'s contract for nothing; +2. *`AGENTS.md` or `CLAUDE.md`* — too narrow by one name: the CLI also discovers + **`AGENTS.override.md`** and gives it *precedence*, so a pull request adding one kept the exact + hole the rule closes while appearing to satisfy it; +3. *those three* — still too narrow: the CLI injects repository **skill** metadata from + `.agents/skills/**`, so a changed skill description can pull an unmerged `SKILL.md` into the + review without touching any of the three; +4. *and `CLAUDE.md` may not belong at all*, since Codex reportedly does not load it as a project + document when a root `AGENTS.md` is present. + +What every attempt had in common is that **the contract was asserting how Codex resolves +instructions**, and reading filename literals out of a binary does not establish that — precedence, +fallback and skill injection are behaviour, not strings, and they move between versions. So the +trigger stopped being a claim about Codex and became a **policy choice**: fire on the agent-layer +paths, accept the over-approximation, and record why. **#451** is where the real set gets +established, by observing what a review actually receives rather than by inference. + +This is the same correction condition 4 needed when it enumerated sources, and the disposition list +needed when it enumerated dispositions — **inside a safety condition, a list is a hole or a deadlock +waiting for the next case** — with one addition this instance makes plain: when the true list is a +fact about someone else's tool, do not encode a guess at it. Over-approximate and say so. **The switch is blunt.** It is all-or-nothing: turning discovery off denies the reviewer `main`'s contract as well as the branch's, so the read is -less informed than an ordinary one. That trade is worth making when the diff edits the instructions -themselves, and it is simply a loss on a pull request that edits `CONTRIBUTING.md` or a template -while leaving those two alone — which the first draft, scoped to *any* rule-stating file, would have -done. There is no narrower switch available: `--base` selects the diff and does not substitute +less informed than an ordinary one. Over-firing therefore costs review *quality* on a narrow class +of pull requests, while under-firing leaves a *self-grading* path open — which is why the +over-approximation is the right way round rather than merely the safe-sounding one. There is no +narrower switch available: `--base` selects the diff and does not substitute `origin/main`'s copy of the instructions, so "review under the default-branch contract" is not something the CLI can be asked for. From c26a683b843bf12361d6dbcabe4dbdadfe103bc3 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 01:46:26 -0500 Subject: [PATCH 28/43] fix(agents): close the second door the isolated read left open Codex round 25, two of its four P1s are in scope and fixed here; the other two are scope questions for the maintainer and are raised on the PR rather than patched. The isolation trigger still missed AGENTS.override.md: it is not under any agent-layer path, so a PR adding one at the root matched no trigger while the very next paragraph said the CLI loads that file WITH PRECEDENCE. Added explicitly. And project_doc_max_bytes=0 was never sufficient on its own. Repository SKILLS are injected through a separate switch, skills.include_instructions, which defaults to true -- so for a PR under .agents/skills/** the branch's own modified SKILL.md stayed model-visible through a read that reported as isolated. That is the worst shape a safety control can have: it looked closed. Both overrides are now given, and the key was checked against the installed 0.147.0 the same way the others were. Local gates: 2938 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- AGENTS.md | 15 ++++++++++----- CONTRIBUTING.md | 5 +++-- ...etered-cap-closes-on-the-unmetered-provider.md | 7 +++++-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4cd1841f..0ce3e249 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -241,11 +241,16 @@ validity turns on it being the right test — must satisfy both. would not buy the stronger property — it would shut the close permanently, which is the deadlock this whole branch exists to remove. - **When the diff touches an agent-layer path, the closing read runs isolated.** Those paths are the - ones §Review already names — `.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` — and the - command is - `codex review --strict-config -c project_doc_max_bytes=0 --base origin/main`, where - `--strict-config` is what makes a mistyped key fail loudly instead of silently leaving discovery - on. Say in the PR that you did it. Otherwise the CLI reads instructions out of the checkout it + ones §Review already names — `.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` — plus + **`AGENTS.override.md`** anywhere, which the CLI loads *with precedence* and which no other rule + here mentions. The command is + `codex review --strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false --base origin/main`. + Both overrides are needed and they close different doors: `project_doc_max_bytes` governs the + `AGENTS.md` family, while repository **skills** are injected through a separate switch that + defaults to on, so a branch-modified `SKILL.md` stayed model-visible through a read that looked + isolated. `--strict-config` is what makes a mistyped key fail loudly rather than silently leave + either door open — the failure mode being a read that reports as isolated and is not. All three + keys were checked against the installed 0.147.0 rather than assumed. Say in the PR that you did it. Otherwise the CLI reads instructions out of the checkout it runs in, and a pull request editing them supplies the rules to the one provider reading its final head — the branch grading itself by its own unmerged contract, which is what *"only agent instructions on the default branch govern"* refuses at the top of this file. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4245969b..2940811a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -367,8 +367,9 @@ on opening a PR for review, marking a draft ready, or an `@codex review` comment code reviews are metered on this account and the meter is spent, so every one of its appearances in this repository is the same usage-limit refusal, and a refusal is not a review. Run `codex review --base origin/main` in the PR's worktree instead, and add -`--strict-config -c project_doc_max_bytes=0` whenever the diff touches an **agent-layer -path** — `.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` — so the branch does not supply +`--strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false` whenever the +diff touches an **agent-layer path** — `.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` — +or an `AGENTS.override.md` anywhere, so the branch does not supply the instructions its own reviewer follows. That trigger deliberately over-fires: the switch also denies the reviewer `main`'s contract, and four attempts to name the exact set Codex loads were wrong in both directions, so the contract stops claiming to know it and errs diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 41a0034e..b57a9c43 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -271,8 +271,11 @@ reachable, by putting a rule-editing PR's fate in the hands of a CLI read. So on a diff touching an **agent-layer path** — `.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md`, the set §Review already names for other purposes — the closing read runs -`codex review --strict-config -c project_doc_max_bytes=0 --base origin/main`. The two flags do -different jobs and both are load-bearing: the second turns project-document discovery off, and +`codex review --strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false --base origin/main`. +The overrides do different jobs and all are load-bearing: `project_doc_max_bytes` turns off the +`AGENTS.md` family, `skills.include_instructions` turns off repository **skills**, which are injected +through a switch of their own that defaults to on — so the first draft of this command left a +branch-modified `SKILL.md` model-visible through a read that reported as isolated — and `--strict-config` makes a mistyped key **fail** rather than be ignored, which matters because the failure mode of a silently-dropped override is a read that looks isolated and is not. Both were verified against the installed CLI (0.147.0) — a deliberately bogus key is rejected under From 0e3a279bc3d335734f8e33b309340fde9c14281e Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:28:07 -0500 Subject: [PATCH 29/43] =?UTF-8?q?fix(agents):=20the=20bot=20does=20post=20?= =?UTF-8?q?reviews=20=E2=80=94=20revert=20the=20detour=20built=20on=20it?= =?UTF-8?q?=20not=20posting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The maintainer pushed back on the claim that the GitHub Codex bot was unavailable. Asking it settled the question in nine minutes: it posted a review of this PR carrying commit_id c26a683b843bf12361d6dbcabe4dbdadfe103bc3. I had inferred "the meter is spent" from two usage-limit refusals dated 2026-08-07 and never re-tested it on this pull request. Three things made that error expensive: an observation became a rule, the rule was load-bearing -- it forced the close onto a local CLI read and pushed a rewrite of AGENTS.md:156, which #439 explicitly put OUT OF SCOPE -- and it was never re-checked, because it explained the evidence well enough that re-checking felt unnecessary. Reverted, and the scope conflict goes with it: the close now requires a POSTED Codex review, "author-side or local output never satisfies this" is restored verbatim, and the procedural pin is deleted. #439's non-goal stands untouched, so only the criterion-2 amendment the maintainer approved remains as a scope change. The bot's own review then found four P1s, and one is the most serious defect this PR has had: AGENTS.md had grown 21,361 -> 35,178 bytes against Codex's 32,768-byte project-document cap. Verified with `codex debug prompt-input`: the load stopped at 33,264 bytes MID-WORD, silently dropping the rest of the cluster prohibition, the whole of §Handoff and cleanup, and the whole of §This machine -- including TETHER_ALLOW_NONSTRICT_X509, without which claim.py cannot reach the GitHub API on this machine. The contract was truncating itself, and nothing reported it. §Review is now 20,297 -> ~11,000 bytes and the file 28,404, with the rationale where it belongs in ADR-0065. Re-probed: the full contract loads, ending at "ADR-0061." with Handoff, This machine and the TLS workaround all present. tests/test_agent_contract_is_runnable.py gains a guard with 3 KiB of headroom, so the next time this happens a test says so instead of an agent quietly reading two thirds of its contract. That guard immediately caught a second mistake of mine: I had written the Codex handle literally into the PR template, which GitHub POSTS -- every PR opened from it would have fired a real review. Also from the bot review: AGENTS.override.md added to the material-file and agent-layer lists, since it was newly recognised as a precedence path and was on neither. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green; reuse lint green. --- .github/pull_request_template.md | 4 +- AGENTS.md | 166 +++++------------- CONTRIBUTING.md | 41 ++--- docs/PRD.md | 4 +- ...ed-cap-closes-on-the-unmetered-provider.md | 38 ++-- tests/test_agent_contract_is_runnable.py | 33 ++++ 6 files changed, 120 insertions(+), 166 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 13f2a1a8..f05e55e0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -34,7 +34,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Risk rationale: - Final head SHA: - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped -- Codex closing read — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged. Where the provider stamps the commit itself, quote that; where it does not — which is the live case, since Codex's CLI records the working directory and session but not the commit — give the **procedural pin** instead: `git rev-parse HEAD` in the review worktree immediately **before** and immediately **after** the run, plus the PR head at arming time, all three the same 40-hex value. Say which of the two you are giving; the pin proves the head did not move under the read, not that the provider read it. Anything this read surfaces is disposed of above before it closes +- Codex closing read — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged. It must be a **posted** review — asked with the Codex review command named in `AGENTS.md` §Review, written in prose here because a handle in this template would fire a real review on every PR opened from it — so the head is the `commit_id` the provider itself recorded; a local CLI run posts nothing and cannot close. Anything this read surfaces is disposed of above before it closes - Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter - **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); no finding from either is left outstanding, with the thread resolved on each — cleared by being fixed, deferred-and-tracked, dropped sub-floor, or **withdrawn by the provider that raised it**; *outstanding* is the test and those are the known ways of clearing one; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing read's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by an **external** provider — metered up to the commit the second review read, the closing read after it, which is the guarantee the conditions buy and not a metered read of everything; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass @@ -59,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head is either the provider's own (a posted review's `commit_id`) or, where the provider records none — which is the live case for Codex's CLI — the **procedural pin**: `git rev-parse HEAD` in the review worktree immediately before and after the run, and the PR head at arming time, all three identical, quoted as a pin and not as an attestation. What is never acceptable is a head asserted with neither, since that could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head is the provider's own — a posted review's `commit_id`. A head asserted by the author is never acceptable, since it could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/AGENTS.md b/AGENTS.md index 0ce3e249..526076ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -155,30 +155,21 @@ validity turns on it being the right test — must satisfy both. - **You are never the only reviewer of your own diff.** Before merge at least one external provider must have reviewed **every substantive change reaching the merge**, and reported what it found. - Author-side output never satisfies this, and a green status check with no review body is not a - review. *Author-side* names whose judgement it is and not which machine ran it: the verdict must - be the provider's, quoted as it wrote it, and a provider's own review posted on the pull request - carries the strongest form of that because GitHub attests it (§This machine). - **Normally the head it read *is* the head you merge, and then this bullet is satisfied by - inspection.** Where it is not — the cap-spent close below is the case that reaches it — what makes - the difference safe is that the final head may differ from the externally reviewed one only by - changes that answer recorded findings, by the non-material exceptions, or by a required `main` - conflict resolution. §Review's fourth condition is what enforces that, and the closing read is - what confirms nothing else crept in. So no unreviewed substance merges, which is the property this - bullet is protecting; the reviewed *commit* being the merged commit was only ever the ordinary way - of getting it. Quote the provider and name the 40-hex head its read covered in the PR body — from - the provider's own artifact where it carries one, and from the procedural pin below where it does - not. + Author-side or local output never satisfies this, and a green status check with no review body is + not a review. Quote the provider and name the 40-hex `commit_id` of the review in the PR body. + Normally the head it read *is* the head you merge. Where the cap-spent close below applies, the + final head may differ only by changes the fourth condition there admits, and the closing read is + what confirms nothing else crept in. - **Open as a draft and get it green there.** Every required check runs on a draft, so the diff reaches fully green before anyone is asked to read it. Opening ready is **not forbidden** but is never free: it spends a metered provider on a diff no unmetered one has read, so record the reason in the PR. - **The lane is cheapest provider first, and the order is the point.** On the green diff — the draft by default, or the ready PR whose reason is recorded — **Codex** first, unmetered and so - uncapped, until it surfaces nothing blocking. *Unmetered* is a fact about the **CLI**, which is - what this lane runs: the GitHub Codex bot's code reviews are metered on this account and its meter - is spent — both of its appearances in this repository are the same usage-limit refusal — so asking - it is asking a provider that will decline, and a decline is not a review. Then **optionally one Greptile review**, if the + uncapped, until it surfaces nothing blocking. Ask the GitHub bot with an `@codex review` comment; + it posts a review carrying a `commit_id`, which is what the gate wants. It has refused for quota + before and may again — that is a wait like any other refusal, and **availability is determined by + asking, never inferred from an earlier refusal**. Then **optionally one Greptile review**, if the seat has budget: a *review*, since a standard one costs a credit and a TREX one three. Then ready-for-review if it is not already, and **CodeRabbit last** — last of the *metered* providers, which is the spend the order buys. The unmetered one is not confined to the front and may read @@ -194,7 +185,7 @@ validity turns on it being the right test — must satisfy both. Otherwise: Executable code, scientific claims, data, schema, locks, CI and release configuration, and **every file that states a rule** — `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, `docs/PRD.md`, `docs/adr/**`, `.agents/**`, `docs/agents/**`, `.claude/**`, - `.github/pull_request_template.md`, `.greptile/**` — are material, and a material push re-arms + `.github/pull_request_template.md`, `.greptile/**`, and `AGENTS.override.md` anywhere — are material, and a material push re-arms the review. The rule-stating files are on that list for a specific reason: a push that changes what the gate requires must not keep evidence gathered under the old requirement. - **Metered credits are the maintainer's money.** Greptile is 50 credits per seat per month shared @@ -212,95 +203,41 @@ validity turns on it being the right test — must satisfy both. - **A spent cap closes on Codex rather than on a maintainer.** When two *completed* CodeRabbit reviews stand on this PR — each one it submitted with a body, since a throttle, a quota refusal or a failed run reviewed nothing — and **no finding they raised is left outstanding**, with the thread - resolved on each, then a **fresh Codex read of the final head** closes - the gate in their place: a full read recorded on the pull request — a review the provider posted, - or its own run verdict quoted and pinned to the head by the rule below — never an earlier Codex - pass re-quoted, since the head that pass read is not the head being merged. *Outstanding* is the - test and the ways of clearing one are **fixed, deferred-and-tracked, dropped sub-floor, or - withdrawn by the provider that raised it** — that last is not hypothetical, a provider retracting - a false positive is how #434's own record reads, and an earlier draft naming only the first three - shut the close on it. Any list of dispositions can miss one the way any list of sources did; what - cannot is *nothing left open*. Anything that read surfaces is cleared the same way before it - closes — the close is a *substitute for the clean pass*, not a lower bar than it. That review is then *the clean review* - the merge binding below names. -- **The closing read must be pinned to the head it closes, and the pin must be checkable by someone - who was not there.** What that rules out is a head *asserted* after the fact: a push landing while - the read is in flight would otherwise let a PR name a commit the provider never saw, and binding - the merge exists so nobody can do that. Where the provider stamps the commit itself — a posted - review carries a `commit_id` — quote it and you are done. **Where it does not, the pin is - procedural and must be recorded as such**: run the read against the exact head being merged, and - record `git rev-parse HEAD` in that worktree **immediately before and immediately after** the run - together with the PR's head at arming time, all three equal. A read whose head moved under it is - not a close, and the equality is what says it did not. Say which of the two you did. - **Codex's local CLI emits no head-stamped artifact today** — its run record carries the working - directory, version and session id, not the commit — so the procedural pin is the live path here, - and calling it "provider-attested" would be false. What it buys is weaker and worth naming: it - proves the head did not move across the read, not that the provider read that head, and it rests - on the worker reporting the three values honestly. That is the same trust the rest of this section - already places in a worker who quotes a review. Requiring an attestation the tooling cannot produce - would not buy the stronger property — it would shut the close permanently, which is the deadlock - this whole branch exists to remove. -- **When the diff touches an agent-layer path, the closing read runs isolated.** Those paths are the - ones §Review already names — `.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` — plus - **`AGENTS.override.md`** anywhere, which the CLI loads *with precedence* and which no other rule - here mentions. The command is - `codex review --strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false --base origin/main`. - Both overrides are needed and they close different doors: `project_doc_max_bytes` governs the - `AGENTS.md` family, while repository **skills** are injected through a separate switch that - defaults to on, so a branch-modified `SKILL.md` stayed model-visible through a read that looked - isolated. `--strict-config` is what makes a mistyped key fail loudly rather than silently leave - either door open — the failure mode being a read that reports as isolated and is not. All three - keys were checked against the installed 0.147.0 rather than assumed. Say in the PR that you did it. Otherwise the CLI reads instructions out of the checkout it - runs in, and a pull request editing them supplies the rules to the one provider reading its final - head — the branch grading itself by its own unmerged contract, which is what *"only agent - instructions on the default branch govern"* refuses at the top of this file. - **This trigger deliberately over-approximates, because the exact set is not something this - contract can state truthfully.** Four attempts to name it were wrong in both directions: *any - rule-stating file* fired on diffs that were never at risk; `AGENTS.md`+`CLAUDE.md` missed - `AGENTS.override.md`, which the CLI also loads and gives precedence; adding that still missed - branch-local skills under `.agents/skills/**`, whose metadata the CLI injects; and `CLAUDE.md` - itself may not be loaded at all when a root `AGENTS.md` is present. Establishing the real - discovery order needs knowledge of the tool's internals that reading its binary does not give, - and it changes between versions. So this trigger is a **policy choice and not a claim about - Codex**: over-fire, and take the cost. That cost is real — the switch is all-or-nothing, so it - denies the reviewer `main`'s contract as well as the branch's, and the read is less informed than - an ordinary one. It is the right way round, because over-firing costs review *quality* on a - narrow class of PRs while under-firing leaves a *self-grading* path open, and there is no narrower - switch: `--base` picks the diff and cannot substitute `origin/main`'s copy of the instructions. - **#451 tracks pinning the real set down**; until it does, the over-approximation stands. + resolved on each, then a **fresh Codex review of the final head** closes the gate in their place. + It must be a **posted** review, so it carries the `commit_id` the merge binds; an earlier Codex + pass re-quoted is not one, since the head that pass read is not the head being merged. *Nothing + left outstanding* is the test — fixed, deferred-and-tracked, dropped sub-floor and withdrawn by the + provider are the ways of clearing a finding, not the test itself. Anything the closing review + surfaces is cleared the same way before it closes: this is a *substitute for the clean pass*, not a + lower bar. That review is then *the clean review* the merge binding below names. ADR-0065 carries + the reasoning and the drafts that were wrong. +- **A CLI read of a diff that edits agent instructions must not be run under them.** The CLI + discovers `AGENTS.md`, `AGENTS.override.md` (which takes precedence) and `CLAUDE.md` from the + checkout, and injects repository skills, so a branch editing any of those would otherwise grade + itself. Run + `codex review --strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false --base origin/main` + — two switches because the skills one is separate and defaults to on, and `--strict-config` so a + mistyped key fails loudly rather than leaving a read that reports as isolated and is not. This does + not reach the **posted** bot review, whose loading is not ours to configure; **#451** covers that. - **Four things shut that close, and each is readable off the pull request rather than out of your own account of why you did something.** A refusal is **not** a spent cap: it reviewed nothing, so - it is a wait, and waiting is still what you do. If either completed review came back clean, its - evidence still stands under the non-material rule above, **and it read the head you are merging**, - **that** review is the gate, it has already closed, and none of this applies. All three, because a - clean review whose head a permitted non-material push has since moved does **not** shut this - branch: its evidence survives, but `--match-head-commit` binds a commit no metered provider has - named, and the cap forbids asking for a third to name it. Shutting the branch there would strand a - clean review followed by a formatting commit — the one PR in the queue with nothing whatever wrong - with it. So that case takes the ordinary close: the cap is genuinely spent, and a fresh Codex read - of the final head closes and names it under every condition here. That is **more** work than the - clean review it follows, never less, which is why widening the branch this way opens nothing. What - the close may never do is stand in for a metered read that never - happened. The second completed review must have been asked - **after the first one's findings were disposed of** — by commits that answer them, or, where the - disposition is a deferral or a sub-floor drop, by the replies and resolutions that record it. - Asking twice at one head with nothing answered in between is one review asked twice and buys the - close nothing. **The test is the disposal, not a new commit**: a review answered wholly on the - record moves no head, so demanding one would re-create the deadlock this rule exists to remove. + it is a wait. If either completed review came back clean, its evidence still stands under the + non-material rule above, **and it read the head you are merging**, **that** review is the gate and + none of this applies — all three, since a clean review whose head a non-material push has moved + leaves `--match-head-commit` binding a commit no metered provider named, and that case takes the + ordinary close instead of stranding. The second completed review must have been asked **after the + first one's findings were disposed of** — by commits that answer them, or by the replies and + resolutions recording a deferral or drop. **The test is the disposal, not a new commit**: a review + answered wholly on the record moves no head. Asking twice at one head with nothing answered in + between is one review asked twice and buys the close nothing. And **nothing but disposal may land after the cap is spent**: everything added after the commit - the second completed review actually *read* — its `commit_id`, never its `submitted_at` — must do - one of three things. It must **answer something already recorded on this pull request that you were - required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a - condition a human sign-off attached, a closing read's own finding — or be one of the - non-material exceptions above, or be **the resolution of a conflict in the `main` merge this - contract requires**. **The first is a test on the change, not on its source**, and the examples are - illustrations rather than the rule: four drafts of it enumerated *who* may raise a finding, each - omitted somebody — first the closing read, then Greptile, then CI alerts and human sign-off — and - every omission was the identical deadlock, because the omitted party's finding still had to be - fixed, the fix answered nobody on the list, and the cap forbade asking the metered provider again. - Any list of sources will keep omitting one; *compelled by something already on the record* cannot, - and it draws the line exactly where it belongs, since what the condition excludes is scope you - chose to add rather than work you were obliged to do. The conflict resolution is on that footing too and is not optional: + the second completed review actually *read* — its `commit_id`, never its `submitted_at` — must + **answer something already recorded on this pull request that you were required to address** (a + finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, + the closing review's own), or be one of the non-material exceptions above, or be **the resolution + of a conflict in the `main` merge this contract requires**. The first is a test on the change and + **not on its source** — the examples illustrate it rather than bound it, and ADR-0065 records why + every attempt to bound it by source deadlocked. The conflict resolution is not optional: §Concurrent GitHub Flow orders you to merge a freshly fetched `origin/main` and resolve it here, while the non-material list covers that merge only when it is *clean*. Both admissions turn on the same fact — the change answers something already read, or reconciles two things already read — so @@ -312,18 +249,10 @@ validity turns on it being the right test — must satisfy both. hunk passes any per-commit test while smuggling exactly the scope this shuts out, so every hunk has to trace to one of the three. New scope pushed after the cap has spent it is scope **no metered provider will ever read**, and what the close is entitled to be is a further opinion on a - diff **every substantive part of which some external provider has already read** — never a first - opinion on an unread one. Two earlier drafts of that sentence overclaimed and both are worth - keeping visible, because the claim is the whole argument for the close being safe. It is not a - *twice*-read diff: where review 1 was clean and an in-scope material push drew review 2, the added - part carries one metered read. And the coverage is not all *metered* either: a fix answering - review 2 lands after that review's `commit_id` by design, as do a permitted conflict resolution - and anything the closing read itself raises, and the only provider that reads those is the closing - read. So the guarantee is **external** coverage of every substantive part, metered up to the - second review's commit and the closing read after it — which is exactly what §Review's first - bullet requires, and no more than that. - So new scope shuts the close and the PR waits for a gate it can actually - satisfy. Motive is not a test + diff **every substantive part of which some external provider has already read** — metered up to + the commit the second review read, the closing review after it. Not *twice*-read and not all of it + metered: two earlier drafts claimed both and ADR-0065 records why each was false. New scope + therefore shuts the close and the PR waits for a gate it can actually satisfy. Motive is not a test and never becomes one; these four are, and they are also why spending an ask to reach the close would buy nothing if it worked, since the close costs the disposal of every finding and a further review on top — more work than the clean pass it replaces. @@ -340,7 +269,8 @@ validity turns on it being the right test — must satisfy both. `Deferred: … Tracked in #N` and resolve the thread. Fixing a non-serious finding in the PR is scope breach, not diligence. - **On agent-layer paths, a sub-floor finding is dropped rather than tracked.** Those paths are - `.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` and the agent test modules. Reply + `.agents/`, `docs/agents/`, `AGENTS.md`, `AGENTS.override.md`, `CLAUDE.md` and the agent test + modules. Reply `Noted; below the floor on an agent-layer path and not tracked (ADR-0064)` and resolve the thread. This inverts the rule above deliberately and only here, because only here does the output feed back into the input — sixteen agent-layer issues came from that loop in ten days. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2940811a..3366128c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -265,26 +265,15 @@ Before requesting review / merging, confirm: and every finding is disposed of, the Codex read of the final head that closed the gate in its place** — and that is a verdict a completed review reached rather than an absence of one: record the review itself — permalink, and the - commit it read, **which must be the final head for whichever review closes the gate**. That - commit is the review's own `commit_id` where the provider posts one, and the **procedural - pin** where it does not, which is the live case for Codex's CLI. On the cap-spent path - CodeRabbit's two are recorded at whatever heads they read, and it is the Codex closing read - that closes and names the final one. **The review-object fields are CodeRabbit's** - — `submitted_at` with a state of - **`COMMENTED` or `APPROVED`** (a `PENDING` review has no `submitted_at` and is not a submitted - one; a `DISMISSED` one is a verdict *withdrawn* and proves nothing) — and a Codex close run - from the CLI rather than posted as a review has none of them: record what it does emit, and do - not demand fields that only exist on a review. It does not emit the commit either — its run - record carries the working directory, version and session id — so the head comes from the - **procedural pin** §Review sets out: `git rev-parse HEAD` immediately before and after the run - and the PR head at arming time, all three equal, recorded as a pin rather than described as an - attestation. - **The body convention is CodeRabbit's too** — the opening of its body, where the clean verdict - is written by the `Actionable comments posted:` line being **absent** rather than reading `0`. - A Codex artifact has no such line and never will; what stands in its place is the verdict the - run actually emitted, quoted, against the commit **the procedural pin identifies** — not one - the run names, since it names none. Asking a Codex close for CodeRabbit's marker, or for a - commit its artifact does not carry, would leave that path with no recordable evidence at all. + commit it read, **which must be the final head for whichever review closes the gate**. On the + cap-spent path CodeRabbit's two are recorded at whatever heads they read, and it is the posted + Codex review that closes and names the final one — asked with an `@codex review` comment, since + the closer has to carry a `commit_id` and a local CLI run does not. A `PENDING` review has no + `submitted_at` and is not a submitted one; a `DISMISSED` one is a verdict *withdrawn* and + proves nothing. **The `Actionable comments posted:` convention is CodeRabbit's** — its clean + verdict is written by that line being **absent** rather than reading `0`, and a Codex review + has no such line: what stands in its place is the verdict it posted, quoted, against the commit + it names. **A review of an earlier head does not close the gate**, whether a material push moved the head or a non-material one did. It still stands as one of the two completed reviews, which is exactly what the cap-spent path relies on: each recorded at the head it read, with the Codex @@ -362,11 +351,13 @@ to an unrequested PR with *"Auto reviews are disabled on this repository"*. A pr that was never asked has not declined — so if you are waiting on a review, check that a request was actually posted. -**Codex is asked through its CLI here, not through the GitHub bot.** The bot does trigger -on opening a PR for review, marking a draft ready, or an `@codex review` comment — but its -code reviews are metered on this account and the meter is spent, so every one of its -appearances in this repository is the same usage-limit refusal, and a refusal is not a -review. Run `codex review --base origin/main` in the PR's worktree instead, and add +**Codex has two delivery paths and they are not interchangeable.** The GitHub bot triggers +on opening a PR for review, marking a draft ready, or an `@codex review` comment, and posts a +review carrying a `commit_id`. The CLI runs locally and posts nothing. The bot has refused for +quota before — #427 and #428, 2026-08-07 — and a refusal is not a +review. Ask it with an `@codex review` comment when the leg has to carry a `commit_id` — the +close does. For ordinary lane reads a local `codex review --base origin/main` in the PR's +worktree is fine and costs nothing; add `--strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false` whenever the diff touches an **agent-layer path** — `.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` — or an `AGENTS.override.md` anywhere, so the branch does not supply diff --git a/docs/PRD.md b/docs/PRD.md index 8aa3b0c3..23b914e5 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1092,8 +1092,8 @@ the gate protects is that **every substantive change reaching the merge was exte §Review is the operative statement of that and of what makes it hold when the reviewed commit is not the merged one. **No provider auto-reviews this repository** — CodeRabbit reports auto reviews disabled, Greptile is held by `.greptile/config.json`'s `skipReview: "AUTOMATIC"`, and the GitHub Codex bot fires only on open-for-review, draft-ready, or an `@codex review` comment — so a provider that was not asked has not declined. That -bot's reviews are metered on this account and its meter is spent, so the Codex leg is run through the **CLI**; `AGENTS.md` -§Review states how, including the isolation flag a rule-editing PR needs. The +bot posts a review carrying a `commit_id`, which is what the cap-spent close requires; the CLI runs locally and posts +nothing, so it serves ordinary lane reads. `AGENTS.md` §Review states which is needed where. The one exception is a branch cut **before** that config landed: it is read from the PR's source branch, so such a branch still auto-fires Greptile on open. The credit is spent regardless, so the review is answered and the optional Greptile step recorded as spent, never discarded as unsolicited. diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index b57a9c43..518e8028 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -227,7 +227,7 @@ an agent's account of its own reasoning: the fix produced: each round is still read by the provider that closes it, and unread scope stays excluded, because a closing read cannot raise a finding about a hunk it never saw. -### The closing read must be pinned to the head it closes +### The closing review must carry the head it closes The SHA that reaches `--match-head-commit` must not be one the author asserted after the fact. This is a fifth condition in substance, and it took three review rounds pulling in different directions @@ -241,24 +241,24 @@ name a head the provider never saw, which is precisely what binding the merge ex draft that came out of those two required an artifact *the provider itself stamps with the commit*, and said the gate stays shut until one exists. -Round three established, by looking, that **no such artifact exists on this repository today**, so -that draft shut the gate permanently: - -- The GitHub Codex bot has **never posted a review here.** Both of its appearances in the repository's - history — on #427 and #428, 2026-08-07 — are the same usage-limit refusal. Its code reviews are - metered on this account and the meter is spent, which also qualifies this record's *"unmetered and - so uncapped"* framing: that is true of the **CLI**, which is what the lane actually runs. -- The CLI leaves a rollout record carrying `cwd`, `cli_version` and a session id — **not the commit - it read.** - -So the requirement was written for evidence the tooling does not produce, and would have replaced a -deadlock with a deadlock. What survives is the *property* rather than the mechanism: the head is -**pinned** rather than attested. Where a provider stamps the commit, quote it. Where none does, the -**procedural pin** — the term `AGENTS.md` §Review, `CONTRIBUTING.md` and the pull-request template -all use for it — is `git rev-parse HEAD` immediately before and immediately after the run alongside -the PR head at arming time, all three equal, which proves the head did not move under the read and -does not pretend to prove the provider read it. That is weaker, it is stated as weaker, and it rests on the -same worker honesty the rest of the section already assumes of anyone quoting a review. +Round three concluded that no such artifact existed, on the evidence that the GitHub Codex bot's only +two appearances in this repository — #427 and #428, both 2026-08-07 — were usage-limit refusals, and +that the CLI's rollout record carries `cwd`, `cli_version` and a session id but **not the commit it +read**. From that it built a *procedural pin*: `git rev-parse HEAD` before and after the run, equal +to the PR head at arming time, standing in for an attestation nothing could give. + +**That was wrong, and the way it was wrong is the more useful record.** The refusals were five days +stale, and no one had asked the bot on this pull request. When it finally was asked, it **posted a +review in nine minutes**, carrying `commit_id c26a683b843bf12361d6dbcabe4dbdadfe103bc3` — the +provider-attested head the whole detour existed to substitute for. The pin is deleted, and the +closing review is a **posted** one. + +Three things had to be true at once for that error to survive as long as it did: an observation was +turned into a rule (*the bot declines*), the rule was **load-bearing** (it forced the close onto the +weaker path and pushed a rewrite of `AGENTS.md:156`, which #439 explicitly put out of scope), and it +was never re-tested, because it explained the evidence well enough that re-testing felt unnecessary. +**Availability is determined by asking.** A provider that refused last week has not declined today, +and the cost of finding out is one comment. ### The closer must not read the rules the branch is proposing diff --git a/tests/test_agent_contract_is_runnable.py b/tests/test_agent_contract_is_runnable.py index fbf89f25..3b619990 100644 --- a/tests/test_agent_contract_is_runnable.py +++ b/tests/test_agent_contract_is_runnable.py @@ -847,3 +847,36 @@ def test_a_page_that_arms_the_merge_says_what_sha_to_supply() -> None: "reaches the merge command as a literal and the binding guard is a guess: " f"{undefined}. State that it is the 40-hex head the clean review read." ) + + +# --- AGENTS.md has to fit in the window the tools that read it actually load --- +# A contract nobody finishes reading is worse than a shorter one, and the failure is SILENT: the +# reader gets a prefix and no error. Codex's CLI loads project documents up to a default cap of +# 32,768 bytes and simply stops there, mid-word. PR #441 grew this file from 21,361 to 35,178 bytes +# and cut the load off inside the cluster bullet, so every Codex worker lost the rest of §WSL +# clusters and Slurm, the whole of §Handoff and cleanup, and the whole of §This machine -- including +# `TETHER_ALLOW_NONSTRICT_X509`, without which `claim.py` cannot reach the GitHub API on the +# maintainer's machine at all. Nothing in the repository would have reported that. +# +# The bound is the tool's, not ours, so this guard keeps real headroom under it rather than sitting +# on the line: a later version could lower the cap, and a file at 32,700 bytes would start +# truncating on an unrelated commit. +_PROJECT_DOC_CAP_BYTES = 32_768 +_HEADROOM_BYTES = 3_072 + + +def test_the_contract_fits_inside_the_project_document_cap() -> None: + """`AGENTS.md` stays far enough under the loader's cap that its tail is never dropped. + + Asserted on **bytes**, matching how the cap is applied -- a character count would pass while a + file full of the em dashes and typographic quotes this contract uses truncated anyway. + """ + size = len((_REPO / "AGENTS.md").read_bytes()) + budget = _PROJECT_DOC_CAP_BYTES - _HEADROOM_BYTES + assert size <= budget, ( + f"AGENTS.md is {size:,} bytes and the working budget is {budget:,} " + f"({_PROJECT_DOC_CAP_BYTES:,}-byte loader cap minus {_HEADROOM_BYTES:,} of headroom). " + "Past the cap the tail is dropped silently and workers lose whatever sections sit at the " + "end -- Handoff and This machine, including the TLS workaround. Move rationale into the " + "ADR that records the decision and leave the operative rule here." + ) From 3e7a1594d28910fef349091c2a2a3a47435ddfbc Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 12:07:59 -0500 Subject: [PATCH 30/43] fix(agents): finish both sweeps the previous commit left half-done Codex re-read of 0e3a279, two P1, both mine and both the same failure: I fixed the place that was named and not every place that mirrors it. The bot-declines claim survived in two more spots after being deleted from the lane bullet -- AGENTS.md's cap bullet still called the bot a separate metered provider whose meter is spent, and ADR-0065 still opened its isolation section with "the Codex leg runs through the CLI", which is no longer how the close works. Both gone. The ADR section is also reframed to what it now guards: the close is a POSTED review, so isolation is about ordinary CLI lane reads, and the posted review's own loading is not ours to configure -- #451 covers that half. AGENTS.override.md was added to AGENTS.md's lists last commit and to none of the mirrors. CONTRIBUTING.md's material list, CONTRIBUTING.md's agent-layer list and docs/PRD.md's material list all carry it now, so a worker following any rule-stating copy classifies a nested override the same way rather than retaining stale review evidence on it. Swept by grepping for the lists themselves this time rather than for the lines Codex cited -- which is what should have happened when each of these was first introduced, and is the defect this pull request has now committed six times. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green. AGENTS.md 28,302 bytes, inside the 29,696-byte working budget the new guard sets. --- AGENTS.md | 7 +++---- CONTRIBUTING.md | 5 +++-- docs/PRD.md | 2 +- ...etered-cap-closes-on-the-unmetered-provider.md | 15 ++++++++------- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 526076ed..2af9bb02 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -278,10 +278,9 @@ validity turns on it being the right test — must satisfy both. They accept bug fixes and safety fixes only; a capability change needs a maintainer-opened issue and may not originate in a review finding. - **Two completed reviews per METERED provider, then stop.** The cap bounds how many times a - provider whose reads cost money or quota is made to *read the diff*, so **the Codex CLI is - uncapped** — it is unmetered, and throttling it bought nothing but slower convergence. The GitHub - Codex bot is a different provider for this purpose: its reviews are metered and its meter is - spent, so it declines rather than reads (§the lane, above). + provider whose reads cost money or quota is made to *read the diff*, so **Codex is + uncapped** — it is unmetered, and throttling it bought nothing but slower convergence. It has + refused for quota before and may again; that is a wait, not a cap. Otherwise **a request that produced no review is not one of the two** — a throttle, a quota refusal or a failed run reviewed nothing, which is the same rule — a refusal means the provider did not review — diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3366128c..c820b423 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -407,7 +407,8 @@ renumbering (renumber-only — touching a word of the decision is not) are all n that exception list WINS over the material paths below, while executable code, scientific claims, data, schema, locks, CI/release config and the governance text itself (`AGENTS.md`, `CLAUDE.md`, this file, `docs/PRD.md`, `docs/adr/**`, `.agents/**`, `docs/agents/**`, -`.claude/**`, `.github/pull_request_template.md`, `.greptile/**`) are material — the +`.claude/**`, `.github/pull_request_template.md`, `.greptile/**`, and `AGENTS.override.md` +anywhere) are material — the list is *every file that states a rule*, because a push changing what the gate requires must not keep evidence gathered under the old requirement. A material push re-arms the review but raises no ceiling: there are **at most two completed reviews per @@ -496,7 +497,7 @@ only provider that reads those. Metered up to the second review's commit, the cl it, external throughout — which is what `AGENTS.md` §Review's first bullet asks for and no more. Nothing counts this for you; the merged history is auditable and you are trusted with it. On agent-layer paths -(`.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` and the agent test modules) a +(`.agents/`, `docs/agents/`, `AGENTS.md`, `AGENTS.override.md`, `CLAUDE.md` and the agent test modules) a finding below the severity floor is **dropped rather than tracked**, because there the follow-up issue becomes another agent-layer pull request and the loop feeds itself (ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` diff --git a/docs/PRD.md b/docs/PRD.md index 23b914e5..9bd438ea 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1103,7 +1103,7 @@ rebasing `main` in without conflict resolution, formatting, comment/docstring ed non-material — and those exceptions WIN over the material paths, which is what lets an ADR renumber-only change keep its review evidence — while executable code, scientific claims, data, schema, locks, CI/release configuration and the governance text itself (`AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, this document, `docs/adr/**`, `.agents/**`, `docs/agents/**`, `.claude/**`, `.github/pull_request_template.md` and -`.greptile/**`) are material — the list is *every file that states a rule*, because a push that +`.greptile/**`, and `AGENTS.override.md` anywhere) are material — the list is *every file that states a rule*, because a push that changes what the gate requires must not keep evidence gathered under the old requirement. A material push re-arms the review, and a PR gets **at most two completed reviews per metered provider**, Codex being unmetered and uncapped — needing a third usually means the issue was scoped too large. The lane does **not** stop for the maintainer: when the cap is spent, a fresh Codex read of the final head closes the gate instead (ADR-0065), subject to conditions diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 518e8028..73413820 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -260,17 +260,18 @@ was never re-tested, because it explained the evidence well enough that re-testi **Availability is determined by asking.** A provider that refused last week has not declined today, and the cost of finding out is one comment. -### The closer must not read the rules the branch is proposing +### A CLI read must not run under the rules the branch is proposing -Because the Codex leg runs through the CLI, and the CLI discovers `AGENTS.md` from the checkout it -runs in, a pull request that edits `AGENTS.md` supplies the instructions to the one provider reading -its final head. The branch would be graded by its own unmerged contract — precisely what +**The close itself is a posted review, so it is not the case this guards.** What it guards is the +ordinary lane read: the CLI discovers `AGENTS.md`, `AGENTS.override.md` and `CLAUDE.md` from the +checkout it runs in and injects repository skills, so a pull request editing any of them supplies +the instructions to a provider reading it — the branch graded by its own unmerged contract, which *"only agent instructions on the default branch govern; unmerged edits are inert"* refuses in the -first paragraph of that file. Nothing about the close created this; the close is what makes it -reachable, by putting a rule-editing PR's fate in the hands of a CLI read. +first paragraph of that file. The **posted** review's loading is not ours to configure at all, and +**#451** covers that half. So on a diff touching an **agent-layer path** — `.agents/`, `docs/agents/`, `AGENTS.md`, -`CLAUDE.md`, the set §Review already names for other purposes — the closing read runs +`CLAUDE.md`, the set §Review already names for other purposes — a CLI read runs `codex review --strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false --base origin/main`. The overrides do different jobs and all are load-bearing: `project_doc_max_bytes` turns off the `AGENTS.md` family, `skills.include_instructions` turns off repository **skills**, which are injected From 856614f39027630f589c92a74bf77dd6a18f5360 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 12:41:31 -0500 Subject: [PATCH 31/43] fix(agents): say what the CLI is for, now that local output satisfies nothing again Codex round on 3e7a159, two P1. The first is substantive rather than a mirror. Restoring "author-side or local output never satisfies this" -- which reverting the bot detour required, and which #439 always demanded -- has a consequence I had not followed through: a local CLI run cannot be the Codex LEG either. CONTRIBUTING.md still told workers to use `codex review --base origin/main` for "ordinary lane reads", which produces evidence the gate cannot accept, so a worker following it would have to stop on the conflict. The CLI is now stated for what it is: an AUTHOR-SIDE tool, in the same category as running the tests, satisfying no leg and recorded as none. Run it freely -- it is unmetered and it is genuinely useful, which the twenty-five rounds on this pull request demonstrate. The Codex leg is the posted review. That is worth being plain about: those twenty-five rounds were author-side diligence, not lane legs, and the PR body says so rather than counting them as review evidence. Second P1, the same sweep failure a seventh time: ADR-0065's isolation trigger still listed agent-layer paths and omitted AGENTS.override.md, which the same section says two paragraphs earlier that the CLI loads WITH PRECEDENCE. All three copies of that trigger now name the files the CLI discovers rather than a path class. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green. AGENTS.md 28,595 bytes against the 29,696 budget. --- AGENTS.md | 9 +++++--- CONTRIBUTING.md | 23 ++++++++++--------- ...ed-cap-closes-on-the-unmetered-provider.md | 4 ++-- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2af9bb02..bbc09c34 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -211,10 +211,13 @@ validity turns on it being the right test — must satisfy both. surfaces is cleared the same way before it closes: this is a *substitute for the clean pass*, not a lower bar. That review is then *the clean review* the merge binding below names. ADR-0065 carries the reasoning and the drafts that were wrong. -- **A CLI read of a diff that edits agent instructions must not be run under them.** The CLI +- **The Codex CLI satisfies no leg; it is an author-side tool.** Local output satisfies nothing + (first bullet), so a CLI run finds your own defects before a provider is asked — the same category + as running the tests. Run it freely, it is unmetered; just never record it as the Codex leg. + **On a diff that edits agent instructions, do not run it under them:** the CLI discovers `AGENTS.md`, `AGENTS.override.md` (which takes precedence) and `CLAUDE.md` from the - checkout, and injects repository skills, so a branch editing any of those would otherwise grade - itself. Run + checkout, and injects repository skills, so a branch editing any of those would otherwise shape + its own reader. Run `codex review --strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false --base origin/main` — two switches because the skills one is separate and defaults to on, and `--strict-config` so a mistyped key fails loudly rather than leaving a read that reports as isolated and is not. This does diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c820b423..8ace47ff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -355,17 +355,18 @@ request was actually posted. on opening a PR for review, marking a draft ready, or an `@codex review` comment, and posts a review carrying a `commit_id`. The CLI runs locally and posts nothing. The bot has refused for quota before — #427 and #428, 2026-08-07 — and a refusal is not a -review. Ask it with an `@codex review` comment when the leg has to carry a `commit_id` — the -close does. For ordinary lane reads a local `codex review --base origin/main` in the PR's -worktree is fine and costs nothing; add -`--strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false` whenever the -diff touches an **agent-layer path** — `.agents/`, `docs/agents/`, `AGENTS.md`, `CLAUDE.md` — -or an `AGENTS.override.md` anywhere, so the branch does not supply -the instructions its own reviewer follows. That trigger deliberately over-fires: the switch -also denies the reviewer `main`'s contract, and four attempts to name the exact set Codex -loads were wrong in both directions, so the contract stops claiming to know it and errs -toward isolation instead. `AGENTS.md` §Review carries the reasoning; **#451** is where the -real set gets established. +review. **The Codex leg is the posted review**, asked with an `@codex review` comment, because +`AGENTS.md` §Review requires a `commit_id` no local run can produce and says in the same breath +that local output satisfies nothing. + +The CLI is still worth running and satisfies **no leg**: it is an author-side tool for finding +your own defects before a provider is asked, in the same category as running the tests. Use it +freely — it costs nothing and it is uncapped — and do not record it as the Codex leg. When you +run it on a diff that touches `AGENTS.md`, `AGENTS.override.md` (anywhere) or `CLAUDE.md`, add +`--strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false`, so the branch +does not supply the instructions its own reader follows. **#451** is where the exact set the CLI +loads gets established; until then that trigger over-fires deliberately, which costs the reader +`main`'s contract and is the cheaper mistake. One exception, and it has already cost money: `.greptile/config.json` is read from the pull request's **source branch**, so a branch cut before that file landed still diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 73413820..abf21bda 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -270,8 +270,8 @@ the instructions to a provider reading it — the branch graded by its own unmer first paragraph of that file. The **posted** review's loading is not ours to configure at all, and **#451** covers that half. -So on a diff touching an **agent-layer path** — `.agents/`, `docs/agents/`, `AGENTS.md`, -`CLAUDE.md`, the set §Review already names for other purposes — a CLI read runs +So on a diff touching `AGENTS.md`, **`AGENTS.override.md` anywhere**, or `CLAUDE.md` — the files +the CLI discovers, the override included precisely because it takes precedence — a CLI read runs `codex review --strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false --base origin/main`. The overrides do different jobs and all are load-bearing: `project_doc_max_bytes` turns off the `AGENTS.md` family, `skills.include_instructions` turns off repository **skills**, which are injected From a1625c5f31ef6087d8d6d1daaeb3c38f66490ac5 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:23:28 -0500 Subject: [PATCH 32/43] fix(agents): finish the CLI-is-not-a-leg sweep and give the trigger both routes Codex round on 856614f, three P1. CLI-as-evidence survived in two more places after the previous commit fixed one: CONTRIBUTING.md still called a quoted CLI run "the weaker one the Codex lane actually runs", and docs/PRD.md still said the CLI "serves ordinary lane reads". Both now say what AGENTS.md says -- the leg is the posted review, the CLI satisfies none. The isolation trigger was narrowed to three filenames last commit and that contradicted this ADR's OWN reasoning four paragraphs later: the CLI reaches the model by two routes, discovered files AND injected repository skills, so a diff touching only .agents/skills/** edits none of the three files and still shapes its own reader. All three copies now name both routes. This is the fifth scoping of that trigger, and every previous one failed the same way -- by naming things instead of naming the mechanism. Third P1: the template's Greptile row offered only "reviewed the final head", which is false whenever a later CodeRabbit fix moves the head past it -- forcing either a misstatement or a purchased second review to satisfy a checkbox. It now records the head Greptile actually read and says why that need not be the final one. This was raised in round 7, deferred to #449 as a pre-existing gap; it is fixed here instead because it is labelled P1 and because my own line-62 edit made the template contradict itself, which moved it from pre-existing to introduced. #449 is closed as resolved by this PR. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green. AGENTS.md 28,738 bytes against the 29,696 budget. --- .github/pull_request_template.md | 2 +- AGENTS.md | 9 +++++---- CONTRIBUTING.md | 11 ++++++----- docs/PRD.md | 4 ++-- ...nt-metered-cap-closes-on-the-unmetered-provider.md | 6 ++++-- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f05e55e0..7336e551 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -35,7 +35,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Final head SHA: - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped - Codex closing read — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged. It must be a **posted** review — asked with the Codex review command named in `AGENTS.md` §Review, written in prose here because a handle in this template would fire a real review on every PR opened from it — so the head is the `commit_id` the provider itself recorded; a local CLI run posts nothing and cannot close. Anything this read surfaces is disposed of above before it closes -- Greptile: reviewed the final head — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3) | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter +- Greptile: reviewed — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3), and name the **full 40-hex** head it read, which need not be the final one: Greptile is asked before CodeRabbit, so a later finding-fix legitimately moves the head past it, and only the provider that *closes* the gate must reach the final head. Buying a second review to make a checkbox true is not a reason to spend a credit | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter - **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); no finding from either is left outstanding, with the thread resolved on each — cleared by being fixed, deferred-and-tracked, dropped sub-floor, or **withdrawn by the provider that raised it**; *outstanding* is the test and those are the known ways of clearing one; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing read's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by an **external** provider — metered up to the commit the second review read, the closing read after it, which is the guarantee the conditions buy and not a metered read of everything; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it diff --git a/AGENTS.md b/AGENTS.md index bbc09c34..756536c4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -214,10 +214,11 @@ validity turns on it being the right test — must satisfy both. - **The Codex CLI satisfies no leg; it is an author-side tool.** Local output satisfies nothing (first bullet), so a CLI run finds your own defects before a provider is asked — the same category as running the tests. Run it freely, it is unmetered; just never record it as the Codex leg. - **On a diff that edits agent instructions, do not run it under them:** the CLI - discovers `AGENTS.md`, `AGENTS.override.md` (which takes precedence) and `CLAUDE.md` from the - checkout, and injects repository skills, so a branch editing any of those would otherwise shape - its own reader. Run + **On a diff that edits agent instructions, do not run it under them.** The CLI takes them from + the checkout by two routes: it discovers `AGENTS.md`, `AGENTS.override.md` (which takes + precedence) and `CLAUDE.md`, **and** it injects repository skills from `.agents/skills/**`. A diff + touching either route shapes its own reader, so the trigger is both — a skill-only change edits + none of the three files and still qualifies. Run `codex review --strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false --base origin/main` — two switches because the skills one is separate and defaults to on, and `--strict-config` so a mistyped key fails loudly rather than leaving a read that reports as isolated and is not. This does diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ace47ff..798c2047 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -337,10 +337,10 @@ Opening ready is not forbidden, but it spends a metered provider on a diff no ch seen; the old rationale for allowing it turned on the round counter ADR-0064 retires, so what remains is simply that it costs more for nothing. Record the reason in the PR. Author-side review and status-only output do not satisfy it — and *author-side* is about whose judgement it -is, not which machine ran it. The verdict has to be the provider's, quoted as it wrote it; a -review it posted on the pull request is the strongest form, because GitHub attests that it said -so, and a CLI run quoted verbatim is the weaker one the Codex lane actually runs. What has to -hold either way is that **every substantive change reaching the merge was externally reviewed**, +is, not which machine ran it. The verdict has to be the provider's and **posted on the pull +request**, so GitHub attests that it said so and the review carries the `commit_id` the merge +binds; a local run quoted by its author is not that, however faithfully it is quoted. What has to +hold is that **every substantive change reaching the merge was externally reviewed**, which `AGENTS.md` §Review states operatively and which its fourth condition is what enforces on the cap-spent path. **Exhaustion is not incapacity** — a provider with no budget left has not reviewed: Greptile out of credits is skippable and never blocks, while @@ -362,7 +362,8 @@ that local output satisfies nothing. The CLI is still worth running and satisfies **no leg**: it is an author-side tool for finding your own defects before a provider is asked, in the same category as running the tests. Use it freely — it costs nothing and it is uncapped — and do not record it as the Codex leg. When you -run it on a diff that touches `AGENTS.md`, `AGENTS.override.md` (anywhere) or `CLAUDE.md`, add +run it on a diff that touches `AGENTS.md`, `AGENTS.override.md` (anywhere), `CLAUDE.md` **or +`.agents/skills/**`** — two routes, discovered files and injected skills — add `--strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false`, so the branch does not supply the instructions its own reader follows. **#451** is where the exact set the CLI loads gets established; until then that trigger over-fires deliberately, which costs the reader diff --git a/docs/PRD.md b/docs/PRD.md index 9bd438ea..cc4c97b6 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1092,8 +1092,8 @@ the gate protects is that **every substantive change reaching the merge was exte §Review is the operative statement of that and of what makes it hold when the reviewed commit is not the merged one. **No provider auto-reviews this repository** — CodeRabbit reports auto reviews disabled, Greptile is held by `.greptile/config.json`'s `skipReview: "AUTOMATIC"`, and the GitHub Codex bot fires only on open-for-review, draft-ready, or an `@codex review` comment — so a provider that was not asked has not declined. That -bot posts a review carrying a `commit_id`, which is what the cap-spent close requires; the CLI runs locally and posts -nothing, so it serves ordinary lane reads. `AGENTS.md` §Review states which is needed where. The +bot posts a review carrying a `commit_id`, which every leg requires; the CLI runs locally, posts nothing and therefore +satisfies no leg — it is an author-side tool, useful before a provider is asked and recorded as none. The one exception is a branch cut **before** that config landed: it is read from the PR's source branch, so such a branch still auto-fires Greptile on open. The credit is spent regardless, so the review is answered and the optional Greptile step recorded as spent, never discarded as unsolicited. diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index abf21bda..1552f23b 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -270,8 +270,10 @@ the instructions to a provider reading it — the branch graded by its own unmer first paragraph of that file. The **posted** review's loading is not ours to configure at all, and **#451** covers that half. -So on a diff touching `AGENTS.md`, **`AGENTS.override.md` anywhere**, or `CLAUDE.md` — the files -the CLI discovers, the override included precisely because it takes precedence — a CLI read runs +So on a diff touching `AGENTS.md`, **`AGENTS.override.md` anywhere**, `CLAUDE.md` **or +`.agents/skills/**`** — the two routes by which the checkout reaches the model, discovered files and +injected skills, the override included because it takes precedence and the skills because a +skill-only diff edits none of the three files — a CLI read runs `codex review --strict-config -c project_doc_max_bytes=0 -c skills.include_instructions=false --base origin/main`. The overrides do different jobs and all are load-bearing: `project_doc_max_bytes` turns off the `AGENTS.md` family, `skills.include_instructions` turns off repository **skills**, which are injected From ef797b2336478a27dd79273972ee332bfc3102ae Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:32:16 -0500 Subject: [PATCH 33/43] fix(agents): say "posted review" on the three pointer surfaces too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found by auditing the claim across all eight files myself instead of waiting for the next round to name one — which is what should have been happening for the last several commits. SKILL.md, .greptile/README.md and openai.yaml all still said a Codex "read" closes the gate. That wording predates the correction and now reads as a CLI run, which satisfies no leg at all — so all three were pointing workers at evidence the gate rejects. They say "posted Codex review" now, and SKILL.md says why in one clause: the closer has to carry a commit_id. Same commit also brings their disposition wording in line — "no finding left outstanding" rather than "every finding disposed of" — so the pointer surfaces and the operative rule use one vocabulary. openai.yaml re-parsed with PyYAML. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .agents/skills/tether-worker/SKILL.md | 5 +++-- .agents/skills/tether-worker/agents/openai.yaml | 2 +- .greptile/README.md | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.agents/skills/tether-worker/SKILL.md b/.agents/skills/tether-worker/SKILL.md index 15b53a23..87a51a5b 100644 --- a/.agents/skills/tether-worker/SKILL.md +++ b/.agents/skills/tether-worker/SKILL.md @@ -83,8 +83,9 @@ gap — that is what invalidated reviews across three PRs at once under the old **Open the PR as a draft** and get the checks green there. Then follow `AGENTS.md` §Review: an external provider reads the final head, you fix what is serious and defer or drop the rest, and CodeRabbit with no actionable comments is the last metered gate before merge. If its two-review cap -is spent and every finding is disposed of, a fresh Codex read of the final head closes the gate in -its place — §Review carries the conditions, and no maintainer is waited on for it. +is spent and no finding is left outstanding, a fresh **posted** Codex review of the final head +closes the gate in its place — §Review carries the conditions, and no maintainer is waited on for +it. Posted, because the closer has to carry a `commit_id`; a local CLI run satisfies no leg. You do not have to sit and watch it. A review takes as long as it takes, and a short-lived worker that polls is spending tokens to wait — so **write the state into the PR body before you go**: diff --git a/.agents/skills/tether-worker/agents/openai.yaml b/.agents/skills/tether-worker/agents/openai.yaml index 80c18e11..0a5226d2 100644 --- a/.agents/skills/tether-worker/agents/openai.yaml +++ b/.agents/skills/tether-worker/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Tether Worker" short_description: "Claim one accepted issue and open its draft PR onto the review lane" - default_prompt: "Use $tether-worker to claim issue #N with .agents/bin/claim.py, implement it in an isolated worktree, open a DRAFT PR, request the first Codex review, write the lane state into the PR body, and exit. Do not arm auto-merge: that happens at the end of the review lane, once the gate is closed — by a clean CodeRabbit review, or by a Codex read of the final head when CodeRabbit's two-review cap is spent. AGENTS.md §Review carries the conditions on that second path and this prompt states none of them, so read them there rather than inferring them from this line." + default_prompt: "Use $tether-worker to claim issue #N with .agents/bin/claim.py, implement it in an isolated worktree, open a DRAFT PR, request the first Codex review, write the lane state into the PR body, and exit. Do not arm auto-merge: that happens at the end of the review lane, once the gate is closed — by a clean CodeRabbit review, or by a posted Codex review of the final head when CodeRabbit's two-review cap is spent. AGENTS.md §Review carries the conditions on that second path and this prompt states none of them, so read them there rather than inferring them from this line." diff --git a/.greptile/README.md b/.greptile/README.md index 306399d6..0fb75c6c 100644 --- a/.greptile/README.md +++ b/.greptile/README.md @@ -99,5 +99,5 @@ reviews per metered provider** in `AGENTS.md` §Review applies here as everywher ceiling rather than a second credit to plan on, so ask again only if the first found something blocking and the seat still has budget. A request that produced no review — a throttle, a quota refusal, a failed run — is not an ask and spends nothing. CodeRabbit, not Greptile, is the metered gate — and when CodeRabbit's own -cap is spent with every finding disposed of, an unmetered Codex read closes it — §Review carries the +cap is spent with no finding left outstanding, an unmetered **posted** Codex review closes it — §Review carries the conditions on that, and this page states none of them. Greptile is never the closer either way. From c28052c8e8d10f37fbdc512352d34e072da7dfba Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 14:05:00 -0500 Subject: [PATCH 34/43] docs(agents): one word for the closer, since the other one caused three defects Terminology audit, run rather than waited for. The contract used "closing read" 29 times and "closing review" 5, across five files, for the same thing -- and "read" now denotes precisely what CANNOT close, since a local CLI read satisfies no leg. That ambiguity produced three separate P1s in the last two rounds: SKILL.md, .greptile/README.md and openai.yaml each pointed workers at a "Codex read of the final head" that the gate rejects. So: "closing read" -> "closing review" everywhere (29 occurrences), and every "fresh Codex read of the final head" -> "fresh POSTED Codex review of the final head". Uses of "read" that are correct are untouched -- "a CLI read", "the head it read", "twice-read", "the commit the second review read". No rule changes; this is the vocabulary catching up with the rule so the next reader cannot make the mistake three files just made. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green. AGENTS.md 28,742 bytes against the 29,696 budget. --- .github/pull_request_template.md | 6 ++-- AGENTS.md | 4 +-- CONTRIBUTING.md | 12 ++++---- docs/PRD.md | 4 +-- ...ed-cap-closes-on-the-unmetered-provider.md | 30 +++++++++---------- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7336e551..79becddc 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -34,9 +34,9 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Risk rationale: - Final head SHA: - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped -- Codex closing read — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing read, since the head that pass read is not the head being merged. It must be a **posted** review — asked with the Codex review command named in `AGENTS.md` §Review, written in prose here because a handle in this template would fire a real review on every PR opened from it — so the head is the `commit_id` the provider itself recorded; a local CLI run posts nothing and cannot close. Anything this read surfaces is disposed of above before it closes +- Codex closing review — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing review, since the head that pass read is not the head being merged. It must be a **posted** review — asked with the Codex review command named in `AGENTS.md` §Review, written in prose here because a handle in this template would fire a real review on every PR opened from it — so the head is the `commit_id` the provider itself recorded; a local CLI run posts nothing and cannot close. Anything this read surfaces is disposed of above before it closes - Greptile: reviewed — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3), and name the **full 40-hex** head it read, which need not be the final one: Greptile is asked before CodeRabbit, so a later finding-fix legitimately moves the head past it, and only the provider that *closes* the gate must reach the final head. Buying a second review to make a checkbox true is not a reason to spend a credit | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing read above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); no finding from either is left outstanding, with the thread resolved on each — cleared by being fixed, deferred-and-tracked, dropped sub-floor, or **withdrawn by the provider that raised it**; *outstanding* is the test and those are the known ways of clearing one; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing read's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by an **external** provider — metered up to the commit the second review read, the closing read after it, which is the guarantee the conditions buy and not a metered read of everything; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing read above is quoted. That closing read is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing review above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); no finding from either is left outstanding, with the thread resolved on each — cleared by being fixed, deferred-and-tracked, dropped sub-floor, or **withdrawn by the provider that raised it**; *outstanding* is the test and those are the known ways of clearing one; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing review's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by an **external** provider — metered up to the commit the second review read, the closing review after it, which is the guarantee the conditions buy and not a metered read of everything; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing review above is quoted. That closing review is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) @@ -59,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head is the provider's own — a posted review's `commit_id`. A head asserted by the author is never acceptable, since it could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing read that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head is the provider's own — a posted review's `commit_id`. A head asserted by the author is never acceptable, since it could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing review that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/AGENTS.md b/AGENTS.md index 756536c4..a2da7d65 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -158,7 +158,7 @@ validity turns on it being the right test — must satisfy both. Author-side or local output never satisfies this, and a green status check with no review body is not a review. Quote the provider and name the 40-hex `commit_id` of the review in the PR body. Normally the head it read *is* the head you merge. Where the cap-spent close below applies, the - final head may differ only by changes the fourth condition there admits, and the closing read is + final head may differ only by changes the fourth condition there admits, and the closing review is what confirms nothing else crept in. - **Open as a draft and get it green there.** Every required check runs on a draft, so the diff reaches fully green before anyone is asked to read it. Opening ready is **not forbidden** but is @@ -261,7 +261,7 @@ validity turns on it being the right test — must satisfy both. would buy nothing if it worked, since the close costs the disposal of every finding and a further review on top — more work than the clean pass it replaces. - **Clearing the gate is not authority to merge.** They are different things and the second is still - per-PR, explicit, and never inferred. Escalate to the maintainer only when the closing read + per-PR, explicit, and never inferred. Escalate to the maintainer only when the closing review surfaces something blocking that you may not resolve inside this item's scope. - **Never write a provider's handle in a comment you do not intend as a request.** A mention fires the bot even inside backticks — a code span is not an escape. Describe the command in prose diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 798c2047..73a36410 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -277,7 +277,7 @@ Before requesting review / merging, confirm: **A review of an earlier head does not close the gate**, whether a material push moved the head or a non-material one did. It still stands as one of the two completed reviews, which is exactly what the cap-spent path relies on: each recorded at the head it read, with the Codex - closing read covering the final one. The non-material case is worth naming because it looks + closing review covering the final one. The non-material case is worth naming because it looks like an exception and is not one — a clean review followed by a formatting commit keeps its evidence, so the cap is spent with nothing outstanding, and the Codex close is what names the commit `--match-head-commit` binds. That is the ordinary close doing ordinary work, not a @@ -328,7 +328,7 @@ cheapest provider first: Codex on the green diff, uncapped — the draft by defa ready PR whose reason is recorded; then optionally one metered Greptile **review** if the seat has budget, a review being one credit as a standard and three as a TREX; then CodeRabbit with no actionable comments, which is the last metered gate before merge — and -when that cap is spent with every finding disposed of, a fresh Codex read of the final head +when that cap is spent with every finding disposed of, a fresh posted Codex review of the final head closes the gate in its place rather than a maintainer doing it — under conditions `AGENTS.md` §Review sets and this summary does not restate.** **Open as a draft and get it green there** — every required check runs on a draft, so the diff reaches fully green before anyone is asked to @@ -457,7 +457,7 @@ re-request while the status check reads `pending`. **Greptile is one *review* in credit — a standard review costs one, a TREX review three. Two is the shared ceiling, not a second review to plan on, so ask again only if the first found something blocking and the seat still has budget. **A spent cap is not a stuck PR**: when every finding those two reviews raised is disposed of and every -thread resolved, a fresh Codex read of the final head closes the gate in their place, so the +thread resolved, a fresh posted Codex review of the final head closes the gate in their place, so the PR finishes on an unmetered read rather than on a maintainer. **`AGENTS.md` §Review sets four conditions on that and is the operative statement of all of them**; the other two are above — a provider that refused reviewed nothing, so a spent cap is not what an unavailable CodeRabbit @@ -479,7 +479,7 @@ every hunk added after the commit the second completed review actually read — `commit_id`, never its `submitted_at`, since a material push landing while that review is still running is a push it never saw — must answer a finding **already recorded on the pull request that you were required to address** (a review finding from any provider, a CodeQL or -`secret-scan` alert, a condition a human sign-off attached, the closing read's own finding — +`secret-scan` alert, a condition a human sign-off attached, the closing review's own finding — illustrations, not the rule, which is a test on the change and not on its source), or be one of the non-material exceptions above, **or be the resolution of a conflict in the `main` merge the contract requires** — that merge is non-material only when @@ -494,8 +494,8 @@ produced. New scope pushed past that point is scope no metered provider will eve the close is a further opinion on a diff every substantive part of which **some external provider** has already read — never a first opinion on an unread one. Not *twice*-read, and not all of it *metered*: a fix answering review 2 lands after that review's `commit_id` by design, as do a -permitted conflict resolution and anything the closing read raises, and the closing read is the -only provider that reads those. Metered up to the second review's commit, the closing read after +permitted conflict resolution and anything the closing review raises, and the closing review is the +only provider that reads those. Metered up to the second review's commit, the closing review after it, external throughout — which is what `AGENTS.md` §Review's first bullet asks for and no more. Nothing counts this for you; the merged history is auditable and you are trusted with it. On agent-layer paths diff --git a/docs/PRD.md b/docs/PRD.md index cc4c97b6..a34c9566 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1062,7 +1062,7 @@ has not — until it surfaces nothing blocking; it is the free provider, and throttling it bought nothing but slower convergence. Then **optionally one Greptile review**, if the seat has budget — a *review*, not a credit, since a standard one costs one credit and a TREX one three; then ready-for-review if it is not already; then **CodeRabbit with no actionable comments, -which is the last metered gate before merge — and when that two-review cap is spent, a fresh Codex read +which is the last metered gate before merge — and when that two-review cap is spent, a fresh posted Codex review of the final head closes the gate in its place, so a reviewed PR finishes on an unmetered read rather than on a maintainer (ADR-0065)**. **`AGENTS.md` §Review is the operative statement of the conditions that close is subject to, and this document deliberately restates none of them** — `CONTRIBUTING.md` @@ -1106,7 +1106,7 @@ governance text itself (`AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, this docume `.greptile/**`, and `AGENTS.override.md` anywhere) are material — the list is *every file that states a rule*, because a push that changes what the gate requires must not keep evidence gathered under the old requirement. A material push re-arms the review, and a PR gets **at most two completed reviews per metered provider**, Codex being unmetered and uncapped — needing a third usually means the issue was scoped too large. The lane does **not** stop for the maintainer: when the cap is -spent, a fresh Codex read of the final head closes the gate instead (ADR-0065), subject to conditions +spent, a fresh posted Codex review of the final head closes the gate instead (ADR-0065), subject to conditions `AGENTS.md` §Review states and this document, again deliberately, does not. That bound is a **convention a worker keeps, not a counter that publishes labels.** ADR-0064 retired the round ledger, diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 1552f23b..25ba9d94 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -139,7 +139,7 @@ an agent's account of its own reasoning: pull-request template had no state for it, `CONTRIBUTING.md` contradicted it, a rebinding read that found something serious deadlocked, and the arming rule still sourced the SHA from "the clean review". The mechanism was the problem. Narrowing this condition instead deletes it: the - case simply takes the **ordinary close**, since the cap is genuinely spent and a fresh Codex read + case simply takes the **ordinary close**, since the cap is genuinely spent and a fresh posted Codex review of the final head closes and names it under every condition here. That is strictly *more* work than the clean review it follows, never less, so widening the branch this way opens nothing — and the rule that must not bend is untouched: the close may never stand in for a metered read @@ -162,7 +162,7 @@ an agent's account of its own reasoning: the second completed review actually read** — its `commit_id`, never its `submitted_at` — must answer something already recorded on the pull request **that the worker was required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human - sign-off attached, a closing read's own finding — or be one of the existing non-material + sign-off attached, a closing review's own finding — or be one of the existing non-material exceptions, or be the resolution of a conflict in the `main` merge this contract requires. Anchored at the reviewed commit and applied per change, both for the reasons below, and stated as a test on the change rather than on its source for the reason after those. @@ -182,7 +182,7 @@ an agent's account of its own reasoning: while smuggling in exactly the scope the condition excludes. It also has to be anchored at the commit the second review **read**, not at the clock. An earlier - phrasing here said *"between the second completed review and the closing read"*, which sounds + phrasing here said *"between the second completed review and the closing review"*, which sounds equivalent and is not: a material push landing while that review is still running is after its `commit_id` but before it completed, so a clock-anchored window waves through the one change the provider demonstrably never saw. This record is a rule-stating file and was accepted carrying the @@ -191,9 +191,9 @@ an agent's account of its own reasoning: And the set is stated as a **test on the change rather than on its source**, because every version that named sources omitted one. Four drafts, four omissions, one failure mode: - 1. only the two CodeRabbit reviews' findings — which shut the close against any closing read that + 1. only the two CodeRabbit reviews' findings — which shut the close against any closing review that found something; - 2. plus the closing read's own — which still omitted **Greptile**, whose findings a worker is + 2. plus the closing review's own — which still omitted **Greptile**, whose findings a worker is equally obliged to fix; 3. plus *any provider* — which still omitted **CodeQL and `secret-scan` alerts, and conditions a human sign-off attaches**, none of which come from a review provider and all of which are @@ -216,16 +216,16 @@ an agent's account of its own reasoning: `main`'s on its own pull request; a resolution carrying new logic of its own is new scope like any other and shuts the close. - And the allowed set has to include **the closing read's own findings**, which the first three + And the allowed set has to include **the closing review's own findings**, which the first three drafts of this condition did not. A sixth Codex review of this record's pull request found it, and it is the third time a draft here re-created the deadlock it removes: the bullet above - requires the closing read to dispose of whatever it surfaces *before* it closes, and that fix is + requires the closing review to dispose of whatever it surfaces *before* it closes, and that fix is a material push answering no CodeRabbit finding — so a set holding only *their* findings shut the - close against every closing read that found anything, while the cap forbade asking the metered - provider again. The branch was reachable only by the closing reads doing their job, which is the + close against every closing review that found anything, while the cap forbade asking the metered + provider again. The branch was reachable only by the closing reviews doing their job, which is the worst possible selection. The remedy is not a narrower set but another stamped read of the head the fix produced: each round is still read by the provider that closes it, and unread scope stays - excluded, because a closing read cannot raise a finding about a hunk it never saw. + excluded, because a closing review cannot raise a finding about a hunk it never saw. ### The closing review must carry the head it closes @@ -339,13 +339,13 @@ review on top. It is strictly more work than the clean pass it replaces. ### The close is a substitute, not a discount -Anything the closing read surfaces is cleared the same way — **left not outstanding**, by being +Anything the closing review surfaces is cleared the same way — **left not outstanding**, by being fixed, deferred-and-tracked, dropped sub-floor, or withdrawn by the provider that raised it — before it closes. That last one was missing from three drafts, and it is not hypothetical: a provider retracting a false positive is how #434's own record reads, so the close was shut on its own motivating example unless a worker mislabelled the disposition. The test is *nothing left open*; the four are the known ways of getting there, the same way condition 4 tests the change rather than -naming who may raise a finding. An earlier draft held the closing read +naming who may raise a finding. An earlier draft held the closing review to *"nothing blocking"*, which would have silently dropped two severity bands relative to the zero-actionable-comments bar it replaces, and on agent-layer paths those findings are not tracked at all. The bar does not move; only who holds it does. @@ -358,7 +358,7 @@ misreading. `AGENTS.md:32–33` — *"Do not infer merge authority"* — is unto ### What the merge binds to `--match-head-commit` still names *"the 40-hex head the clean review read."* When the close applies, -the Codex closing read **is** that clean review. The merge bullet is left byte-identical: it is the +the Codex closing review **is** that clean review. The merge bullet is left byte-identical: it is the only place in `AGENTS.md` satisfying both mechanical guards in `tests/test_agent_contract_is_runnable.py` — the `_SHA_DEFINED` window and the arming-page floor — and its referent was always provider-neutral in words. Only context had pointed it at CodeRabbit. @@ -385,7 +385,7 @@ way it was not before. Two things bound that. It is a fresh read of the exact he a re-quoted earlier pass. And it is reached only after two completed metered reviews, with condition 4 above refusing the close to any scope that landed after the cap was spent — so **every substantive part of the merging diff has had at least one external read**, metered up to the commit the second -review read and the closing read after it, and the close is a further opinion on it rather than a +review read and the closing review after it, and the close is a further opinion on it rather than a first opinion on an unread one. This paragraph has now been wrong twice in the same direction, which is worth keeping visible because @@ -395,7 +395,7 @@ in-scope material push then draws review 2, the added part carries **one** metered read, not two. The correction then claimed one *metered* read of every substantive part, and the next round showed that false too: a fix answering review 2 lands after that review's `commit_id` by design, as do a -permitted conflict resolution and anything the closing read raises, and no metered provider ever sees +permitted conflict resolution and anything the closing review raises, and no metered provider ever sees them. What the conditions actually buy is **external** coverage throughout — which is what §Review's first bullet asks for, and it is worth noticing that the honest version of this claim turned out to be exactly the property that bullet already states, rather than something stronger the close was From 13e311d4712a9eddeb254141e3c341ab4992019b Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 14:49:44 -0500 Subject: [PATCH 35/43] docs(agents): finish the rename the line-based grep could not see Codex round on c28052c, one P1: four instances of the closer described as a "Codex read" survived the normalisation commit -- CONTRIBUTING.md:17 and :265, ADR-0065:115, and the PR template's Review-complete item. The mechanism is worth recording because it will recur. My sweep grepped for "closing read" and "fresh Codex read of the final head" line by line. All four survivors are LINE-WRAPPED -- "a fresh Codex\nread of the final head" -- so no line contains the phrase and grep reported clean on a file that was not. A prose audit over wrapped text has to match across newlines; this one now does, with a regex allowing arbitrary whitespace between the words, and it found the three the line-based pass missed plus one more phrasing Codex had not cited. Final audit across all eight rule-stating files leaves exactly one "Codex reads", in .greptile/README.md -- "Codex reads the diff before any metered provider does" -- which is about lane order and is correct. This is the second time on this pull request that a sweep I ran to prevent the next finding became the next finding. The first was fixing the cited line instead of its mirrors; this is matching the cited spelling instead of the concept. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .github/pull_request_template.md | 2 +- CONTRIBUTING.md | 4 ++-- ...65-a-spent-metered-cap-closes-on-the-unmetered-provider.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 79becddc..cb3910ca 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -59,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head is the provider's own — a posted review's `commit_id`. A head asserted by the author is never acceptable, since it could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh Codex read of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing review that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head is the provider's own — a posted review's `commit_id`. A head asserted by the author is never acceptable, since it could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh posted Codex review of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing review that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 73a36410..815add11 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ requires a substantive final-head review on one fixed lane, cheapest provider fi **Codex on the green diff until nothing blocking remains, then optionally one metered Greptile review, then CodeRabbit with no actionable comments before merge — or, when CodeRabbit's two-review cap is spent and every finding is disposed of, a fresh Codex -read of the final head closing the gate in its place.** +review of the final head closing the gate in its place.** Copilot is advisory only and never satisfies a leg. The ruleset still requires zero GitHub approval reviews; load-bearing changes additionally need any qualified human/domain judgment specified in `AGENTS.md`. This scales to required human reviews + `CODEOWNERS` if @@ -262,7 +262,7 @@ Before requesting review / merging, confirm: result from every provider the lane reached — **either** a substantive review **or** that provider's own quoted "nothing to review" for the head it read, a Codex 👍 included. **CodeRabbit with no actionable comments is required — or, if its two-review cap is spent - and every finding is disposed of, the Codex read of the final head that closed the gate in + and every finding is disposed of, the Codex review of the final head that closed the gate in its place** — and that is a verdict a completed review reached rather than an absence of one: record the review itself — permalink, and the commit it read, **which must be the final head for whichever review closes the gate**. On the diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 25ba9d94..dff9d9d4 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -112,7 +112,7 @@ Greptile rather than inventing a principle for it. ## Decision outcome **When the metered cap is genuinely spent and every finding is genuinely disposed of, a fresh Codex -read of the final head closes the gate in the metered provider's place. The maintainer escalation is +review of the final head closes the gate in the metered provider's place. The maintainer escalation is deleted rather than supplemented.** ### What makes it narrow From bdf4bcd81f4f56925f37ed9c019c0c033fb8e25b Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:28:16 -0500 Subject: [PATCH 36/43] fix(agents): scope the override to "anywhere" in the lists that had not said so Codex round on 13e311d, one P1: the material list said `AGENTS.override.md` anywhere while the agent-layer list said the bare filename, so a nested pkg/AGENTS.override.md was material and yet outside both the sub-floor drop rule and the feature-complete boundary -- which is the self-feeding path those two rules exist to close. Audited the qualifier on every mention rather than the two Codex named, and found four bare: both agent-layer lists (AGENTS.md, CONTRIBUTING.md) and both CLI-discovers descriptions (AGENTS.md, ADR-0065). All now say "anywhere". The one remaining bare mention is ADR-0065's account of a superseded draft, where the narrower wording is the point. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green. AGENTS.md 28,760 bytes against the 29,696 budget. --- AGENTS.md | 6 +++--- CONTRIBUTING.md | 2 +- ...-a-spent-metered-cap-closes-on-the-unmetered-provider.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a2da7d65..70781188 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -215,7 +215,7 @@ validity turns on it being the right test — must satisfy both. (first bullet), so a CLI run finds your own defects before a provider is asked — the same category as running the tests. Run it freely, it is unmetered; just never record it as the Codex leg. **On a diff that edits agent instructions, do not run it under them.** The CLI takes them from - the checkout by two routes: it discovers `AGENTS.md`, `AGENTS.override.md` (which takes + the checkout by two routes: it discovers `AGENTS.md`, `AGENTS.override.md` anywhere (which takes precedence) and `CLAUDE.md`, **and** it injects repository skills from `.agents/skills/**`. A diff touching either route shapes its own reader, so the trigger is both — a skill-only change edits none of the three files and still qualifies. Run @@ -273,8 +273,8 @@ validity turns on it being the right test — must satisfy both. `Deferred: … Tracked in #N` and resolve the thread. Fixing a non-serious finding in the PR is scope breach, not diligence. - **On agent-layer paths, a sub-floor finding is dropped rather than tracked.** Those paths are - `.agents/`, `docs/agents/`, `AGENTS.md`, `AGENTS.override.md`, `CLAUDE.md` and the agent test - modules. Reply + `.agents/`, `docs/agents/`, `AGENTS.md`, `AGENTS.override.md` anywhere, `CLAUDE.md` and the agent + test modules. Reply `Noted; below the floor on an agent-layer path and not tracked (ADR-0064)` and resolve the thread. This inverts the rule above deliberately and only here, because only here does the output feed back into the input — sixteen agent-layer issues came from that loop in ten days. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 815add11..2b842502 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -499,7 +499,7 @@ only provider that reads those. Metered up to the second review's commit, the cl it, external throughout — which is what `AGENTS.md` §Review's first bullet asks for and no more. Nothing counts this for you; the merged history is auditable and you are trusted with it. On agent-layer paths -(`.agents/`, `docs/agents/`, `AGENTS.md`, `AGENTS.override.md`, `CLAUDE.md` and the agent test modules) a +(`.agents/`, `docs/agents/`, `AGENTS.md`, `AGENTS.override.md` anywhere, `CLAUDE.md` and the agent test modules) a finding below the severity floor is **dropped rather than tracked**, because there the follow-up issue becomes another agent-layer pull request and the loop feeds itself (ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index dff9d9d4..d9f11360 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -263,7 +263,7 @@ and the cost of finding out is one comment. ### A CLI read must not run under the rules the branch is proposing **The close itself is a posted review, so it is not the case this guards.** What it guards is the -ordinary lane read: the CLI discovers `AGENTS.md`, `AGENTS.override.md` and `CLAUDE.md` from the +ordinary lane read: the CLI discovers `AGENTS.md`, `AGENTS.override.md` anywhere and `CLAUDE.md` from the checkout it runs in and injects repository skills, so a pull request editing any of them supplies the instructions to a provider reading it — the branch graded by its own unmerged contract, which *"only agent instructions on the default branch govern; unmerged edits are inert"* refuses in the From 742b8d34639a32b938c652c49fdcec307f56eea4 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:48:24 -0500 Subject: [PATCH 37/43] fix(agents): measure what a clean Codex result actually is, and accept it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex round on bdf4bcd, one P1, and it lands on the last unexamined assumption in this whole branch: the closer had to be "a posted review carrying a commit_id", while CONTRIBUTING and ADR-0065 both described Codex's clean result as a bare 👍 reaction with no commit. If both were true the close was unsatisfiable in exactly the case it exists for -- a clean run. MEASURED on this pull request rather than argued: - every Codex run WITH findings posted a review object with a full 40-hex commit_id (seven of them, 16:18Z through 20:42Z); - the CLEAN run at ef797b2 posted NO review object at all -- an issue comment, "Codex Review: Didn't find any major issues." with "Reviewed commit: ef797b2336" on its own line. So the evidence is provider-written and does name the commit; what it lacks is the commit_id FIELD. The rule now asks for a posted artifact that NAMES the commit it read, which both shapes satisfy, with the short SHA expanded by git rev-parse -- mechanical, redoable by anyone, so the head is still the provider's and not the author's. The "bare 👍 carrying no commit" is Codex's own documentation of its no-suggestions result. It is not what this repository observes, and five rounds of rule-writing rested on it without anyone looking -- the same failure as the bot-is-unavailable detour, one layer down. AGENTS.md 29,175 bytes: the first draft of this fix left only 188 bytes of budget, so the detail moved to ADR-0065 and the rule kept the operative half. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .github/pull_request_template.md | 2 +- AGENTS.md | 9 +++++++-- CONTRIBUTING.md | 5 ++++- ...tered-cap-closes-on-the-unmetered-provider.md | 16 +++++++++++++++- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cb3910ca..5f3eeaa2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -34,7 +34,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Risk rationale: - Final head SHA: - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped -- Codex closing review — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing review, since the head that pass read is not the head being merged. It must be a **posted** review — asked with the Codex review command named in `AGENTS.md` §Review, written in prose here because a handle in this template would fire a real review on every PR opened from it — so the head is the `commit_id` the provider itself recorded; a local CLI run posts nothing and cannot close. Anything this read surfaces is disposed of above before it closes +- Codex closing review — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing review, since the head that pass read is not the head being merged. It must be **posted by the provider and name the commit it read** — asked with the Codex review command named in `AGENTS.md` §Review, written in prose here because a handle in this template would fire a real review on every PR opened from it. Two shapes both count: a run with findings posts a review whose `commit_id` is the full 40-hex, and a **clean** run posts a comment carrying `Reviewed commit: ` — expand that with `git rev-parse` and record both. A local CLI run posts nothing and cannot close. Anything this read surfaces is disposed of above before it closes - Greptile: reviewed — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3), and name the **full 40-hex** head it read, which need not be the final one: Greptile is asked before CodeRabbit, so a later finding-fix legitimately moves the head past it, and only the provider that *closes* the gate must reach the final head. Buying a second review to make a checkbox true is not a reason to spend a credit | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter - **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing review above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); no finding from either is left outstanding, with the thread resolved on each — cleared by being fixed, deferred-and-tracked, dropped sub-floor, or **withdrawn by the provider that raised it**; *outstanding* is the test and those are the known ways of clearing one; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing review's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by an **external** provider — metered up to the commit the second review read, the closing review after it, which is the guarantee the conditions buy and not a metered read of everything; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing review above is quoted. That closing review is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass diff --git a/AGENTS.md b/AGENTS.md index 70781188..324fc44f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -204,8 +204,13 @@ validity turns on it being the right test — must satisfy both. reviews stand on this PR — each one it submitted with a body, since a throttle, a quota refusal or a failed run reviewed nothing — and **no finding they raised is left outstanding**, with the thread resolved on each, then a **fresh Codex review of the final head** closes the gate in their place. - It must be a **posted** review, so it carries the `commit_id` the merge binds; an earlier Codex - pass re-quoted is not one, since the head that pass read is not the head being merged. *Nothing + It must be **posted by the provider and name the commit it read**; a re-quoted earlier pass is not + one. **Codex says it two ways and both count**: a run with findings posts a *review* whose + `commit_id` is the full 40-hex, and a **clean** run posts a *comment* carrying + `Reviewed commit: `. Expand the short one with `git rev-parse` and record both — the + expansion is mechanical and redoable, so the head stays the provider's. Requiring a `commit_id` + *field* would accept only the finding case and strand the clean one, which is the case the close + exists for (ADR-0065). *Nothing left outstanding* is the test — fixed, deferred-and-tracked, dropped sub-floor and withdrawn by the provider are the ways of clearing a finding, not the test itself. Anything the closing review surfaces is cleared the same way before it closes: this is a *substitute for the clean pass*, not a diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b842502..5012ef8b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -439,7 +439,10 @@ feature-complete** (ADR-0064) — they take bug and safety fixes only, so a capa change needs a maintainer-opened issue and may never originate in a review finding. Dropping the sub-floor finding without that second rule would still leave a reviewer able to commission new agent machinery through the deferral above. If a **selected** provider reports nothing to review at the -head it read — a deletion, a pure rename, or Codex's 👍 reaction, which is its documented +head it read — a deletion, a pure rename, or Codex's clean result, which its own documentation +calls a 👍 reaction and which on this repository is a posted **comment** reading +`Codex Review: Didn't find any major issues.` with `Reviewed commit: ` on its own line +(measured, 2026-08-12). Either way it is its documented "no suggestions" — that satisfies its leg; quote it. A statement from the author, or from any other commenter, never does. **Exhaustion is not incapacity**: a provider with nothing to say has reviewed, a provider with no budget left has not. Greptile out of credits is diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index d9f11360..0c17c0c3 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -247,7 +247,21 @@ that the CLI's rollout record carries `cwd`, `cli_version` and a session id but read**. From that it built a *procedural pin*: `git rev-parse HEAD` before and after the run, equal to the PR head at arming time, standing in for an attestation nothing could give. -**That was wrong, and the way it was wrong is the more useful record.** The refusals were five days +**Round seven then measured what the clean result actually is**, and it is neither of the things +five rounds had argued about. Every Codex run *with findings* on this pull request posted a **review +object** carrying a full 40-hex `commit_id`. The clean run at `ef797b2` posted **no review object at +all** — an issue comment, reading `Codex Review: Didn't find any major issues.` with +`Reviewed commit: ef797b2336` on its own line. So the closer's evidence is provider-written and does +name the commit, but a rule demanding a `commit_id` *field* accepts only the finding case and +strands the clean one, which is the case the close exists for. The rule now asks for **a posted +artifact that names the commit it read**, which both shapes satisfy, with the short SHA expanded by +`git rev-parse` — mechanical, redoable by anyone, and therefore still not an author's assertion. + +The *"bare 👍 carrying no commit"* that rounds one and two argued over is Codex's own documented +description of its no-suggestions result. It is not what this repository observes, and five rounds +of rule-writing rested on it without anyone looking. + +**The earlier detour was wrong too, and the way it was wrong is the more useful record.** The refusals were five days stale, and no one had asked the bot on this pull request. When it finally was asked, it **posted a review in nine minutes**, carrying `commit_id c26a683b843bf12361d6dbcabe4dbdadfe103bc3` — the provider-attested head the whole detour existed to substitute for. The pin is deleted, and the From 237541b3c8329152caacc0d15a72a8af95b996a6 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 16:26:40 -0500 Subject: [PATCH 38/43] fix(agents): state the evidence shape once, and scope AGENTS.md anywhere too Codex round on 742b8d3, two P1. The first is my own fix landing in one place: I taught the CLOSE that a clean Codex result is a comment rather than a review object, and left the ORDINARY leg still demanding a review carrying commit_id. A worker with a clean first-leg result would have had documented evidence the same file called insufficient. Rather than repeat the shapes a third time, they are now stated ONCE in the "never the only reviewer" bullet, where evidence belongs, and the lane and close bullets refer to it. That is a smaller contract and it removes the failure mode rather than patching this instance of it -- the same move that fixed the enumerations. Second P1: AGENTS.override.md was scoped "anywhere" while AGENTS.md next to it was not, so a nested src/tether/AGENTS.md -- which governs its own subtree -- would be neither material nor agent-layer, and a push adding one could keep stale review evidence and escape the drop and feature-complete rules. Scoped in all four lists: AGENTS.md's material and agent-layer lists, CONTRIBUTING.md's two, and docs/PRD.md's. Worth noting the asymmetry was created by the fix two commits ago. Adding a qualifier to one item of a list implies the others lack it, which is a way of introducing a defect that no amount of care about the item you are editing will catch. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green. AGENTS.md 29,272 bytes against the 29,696 budget. --- AGENTS.md | 26 ++++++++++++++------------ CONTRIBUTING.md | 4 ++-- docs/PRD.md | 2 +- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 324fc44f..985ad193 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -156,7 +156,12 @@ validity turns on it being the right test — must satisfy both. - **You are never the only reviewer of your own diff.** Before merge at least one external provider must have reviewed **every substantive change reaching the merge**, and reported what it found. Author-side or local output never satisfies this, and a green status check with no review body is - not a review. Quote the provider and name the 40-hex `commit_id` of the review in the PR body. + not a review. **Quote the provider and name the 40-hex head its artifact records.** Providers say + it in two shapes and both count: a review object carries `commit_id`, while Codex's *clean* result + is a posted comment carrying `Reviewed commit: ` — expand that with `git rev-parse` and + record both, the expansion being mechanical and redoable rather than an assertion of yours. This + applies to **every** leg, the ordinary one included; requiring a `commit_id` field would accept + only the runs that found something. Normally the head it read *is* the head you merge. Where the cap-spent close below applies, the final head may differ only by changes the fourth condition there admits, and the closing review is what confirms nothing else crept in. @@ -167,7 +172,8 @@ validity turns on it being the right test — must satisfy both. - **The lane is cheapest provider first, and the order is the point.** On the green diff — the draft by default, or the ready PR whose reason is recorded — **Codex** first, unmetered and so uncapped, until it surfaces nothing blocking. Ask the GitHub bot with an `@codex review` comment; - it posts a review carrying a `commit_id`, which is what the gate wants. It has refused for quota + it posts an artifact naming the head it read, in either shape the first bullet describes. It has + refused for quota before and may again — that is a wait like any other refusal, and **availability is determined by asking, never inferred from an earlier refusal**. Then **optionally one Greptile review**, if the seat has budget: a *review*, since a standard one costs a credit and a TREX one three. Then @@ -183,7 +189,7 @@ validity turns on it being the right test — must satisfy both. is why `docs/agents/adr.md` can say a renumber needs no fresh review even though `docs/adr/**` is a material path. A renumber that also edits a word of the decision is not renumber-only. Otherwise: Executable code, scientific claims, data, schema, locks, CI and release - configuration, and **every file that states a rule** — `AGENTS.md`, `CLAUDE.md`, + configuration, and **every file that states a rule** — `AGENTS.md` **anywhere**, `CLAUDE.md`, `CONTRIBUTING.md`, `docs/PRD.md`, `docs/adr/**`, `.agents/**`, `docs/agents/**`, `.claude/**`, `.github/pull_request_template.md`, `.greptile/**`, and `AGENTS.override.md` anywhere — are material, and a material push re-arms the review. The rule-stating files are on that list for a specific reason: a push that changes @@ -204,13 +210,9 @@ validity turns on it being the right test — must satisfy both. reviews stand on this PR — each one it submitted with a body, since a throttle, a quota refusal or a failed run reviewed nothing — and **no finding they raised is left outstanding**, with the thread resolved on each, then a **fresh Codex review of the final head** closes the gate in their place. - It must be **posted by the provider and name the commit it read**; a re-quoted earlier pass is not - one. **Codex says it two ways and both count**: a run with findings posts a *review* whose - `commit_id` is the full 40-hex, and a **clean** run posts a *comment* carrying - `Reviewed commit: `. Expand the short one with `git rev-parse` and record both — the - expansion is mechanical and redoable, so the head stays the provider's. Requiring a `commit_id` - *field* would accept only the finding case and strand the clean one, which is the case the close - exists for (ADR-0065). *Nothing + It must be **posted by the provider and name the commit it read**, in either shape the first + bullet describes; a re-quoted earlier pass is not one, since the head that pass read is not the + head being merged. *Nothing left outstanding* is the test — fixed, deferred-and-tracked, dropped sub-floor and withdrawn by the provider are the ways of clearing a finding, not the test itself. Anything the closing review surfaces is cleared the same way before it closes: this is a *substitute for the clean pass*, not a @@ -278,8 +280,8 @@ validity turns on it being the right test — must satisfy both. `Deferred: … Tracked in #N` and resolve the thread. Fixing a non-serious finding in the PR is scope breach, not diligence. - **On agent-layer paths, a sub-floor finding is dropped rather than tracked.** Those paths are - `.agents/`, `docs/agents/`, `AGENTS.md`, `AGENTS.override.md` anywhere, `CLAUDE.md` and the agent - test modules. Reply + `.agents/`, `docs/agents/`, `AGENTS.md` and `AGENTS.override.md` anywhere, `CLAUDE.md` and the + agent test modules. Reply `Noted; below the floor on an agent-layer path and not tracked (ADR-0064)` and resolve the thread. This inverts the rule above deliberately and only here, because only here does the output feed back into the input — sixteen agent-layer issues came from that loop in ten days. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5012ef8b..76609c42 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -407,7 +407,7 @@ Review evidence **survives a non-material push**, so addressing findings does no restart the gate — merging `main` in cleanly, formatting, comment and docstring edits, and ADR renumbering (renumber-only — touching a word of the decision is not) are all non-material, and that exception list WINS over the material paths below, while executable code, scientific claims, data, -schema, locks, CI/release config and the governance text itself (`AGENTS.md`, +schema, locks, CI/release config and the governance text itself (`AGENTS.md` anywhere, `CLAUDE.md`, this file, `docs/PRD.md`, `docs/adr/**`, `.agents/**`, `docs/agents/**`, `.claude/**`, `.github/pull_request_template.md`, `.greptile/**`, and `AGENTS.override.md` anywhere) are material — the @@ -502,7 +502,7 @@ only provider that reads those. Metered up to the second review's commit, the cl it, external throughout — which is what `AGENTS.md` §Review's first bullet asks for and no more. Nothing counts this for you; the merged history is auditable and you are trusted with it. On agent-layer paths -(`.agents/`, `docs/agents/`, `AGENTS.md`, `AGENTS.override.md` anywhere, `CLAUDE.md` and the agent test modules) a +(`.agents/`, `docs/agents/`, `AGENTS.md` and `AGENTS.override.md` anywhere, `CLAUDE.md` and the agent test modules) a finding below the severity floor is **dropped rather than tracked**, because there the follow-up issue becomes another agent-layer pull request and the loop feeds itself (ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` diff --git a/docs/PRD.md b/docs/PRD.md index a34c9566..47b09426 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1101,7 +1101,7 @@ step recorded as spent, never discarded as unsolicited. Review evidence **survives a non-material push**, so responding to findings does not restart the gate: merging or rebasing `main` in without conflict resolution, formatting, comment/docstring edits and ADR renumbering are non-material — and those exceptions WIN over the material paths, which is what lets an ADR renumber-only change keep its review evidence — while executable code, scientific claims, data, schema, locks, CI/release configuration and the -governance text itself (`AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, this document, `docs/adr/**`, +governance text itself (`AGENTS.md` anywhere, `CLAUDE.md`, `CONTRIBUTING.md`, this document, `docs/adr/**`, `.agents/**`, `docs/agents/**`, `.claude/**`, `.github/pull_request_template.md` and `.greptile/**`, and `AGENTS.override.md` anywhere) are material — the list is *every file that states a rule*, because a push that changes what the gate requires must not keep evidence gathered under the old requirement. A material push re-arms the review, and a PR gets **at most two completed reviews per metered provider**, Codex being unmetered and uncapped — From 0970f4dcb26e5426593bc6c85132f0d65aac6fd5 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:03:28 -0500 Subject: [PATCH 39/43] fix(agents): make the mirrors defer on evidence shape instead of restating it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex round on 237541b, one P1: the two-shape rule landed in AGENTS.md and six mirrors still demanded a review object carrying commit_id -- CONTRIBUTING.md in four places, docs/PRD.md, the PR template and SKILL.md. A worker following any detailed surface would reject the clean artifact the operative rule accepts. Fixed by POINTING rather than restating. None of the six now describes the artifact; each says the provider posts something naming the head it read and sends the reader to §Review for the shapes. That is the one discipline that has not broken once tonight: every surface converted to a pointer has stayed fixed, and every surface that restates a rule has drifted from it within a round or two. Audited by grepping `commit_id` across all six files and classifying every hit rather than fixing the lines Codex cited: seven asserted the closer's evidence shape and are changed; the rest are about CodeRabbit's own review, the anchor commit the scope freeze uses, or ADR-0065's history, and are untouched and correct. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green. --- .agents/skills/tether-worker/SKILL.md | 2 +- .github/pull_request_template.md | 2 +- CONTRIBUTING.md | 11 ++++++----- docs/PRD.md | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.agents/skills/tether-worker/SKILL.md b/.agents/skills/tether-worker/SKILL.md index 87a51a5b..d8584945 100644 --- a/.agents/skills/tether-worker/SKILL.md +++ b/.agents/skills/tether-worker/SKILL.md @@ -85,7 +85,7 @@ external provider reads the final head, you fix what is serious and defer or dro CodeRabbit with no actionable comments is the last metered gate before merge. If its two-review cap is spent and no finding is left outstanding, a fresh **posted** Codex review of the final head closes the gate in its place — §Review carries the conditions, and no maintainer is waited on for -it. Posted, because the closer has to carry a `commit_id`; a local CLI run satisfies no leg. +it. Posted, because the closer has to name the head it read; a local CLI run satisfies no leg. You do not have to sit and watch it. A review takes as long as it takes, and a short-lived worker that polls is spending tokens to wait — so **write the state into the PR body before you go**: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5f3eeaa2..047bede3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -59,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head is the provider's own — a posted review's `commit_id`. A head asserted by the author is never acceptable, since it could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh posted Codex review of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing review that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head comes from the provider's own posted artifact, in either shape `AGENTS.md` §Review describes. A head asserted by the author is never acceptable, since it could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh posted Codex review of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing review that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 76609c42..73fc4d8c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -268,7 +268,8 @@ Before requesting review / merging, confirm: commit it read, **which must be the final head for whichever review closes the gate**. On the cap-spent path CodeRabbit's two are recorded at whatever heads they read, and it is the posted Codex review that closes and names the final one — asked with an `@codex review` comment, since - the closer has to carry a `commit_id` and a local CLI run does not. A `PENDING` review has no + the closer has to be posted and name the head it read — in either shape `AGENTS.md` §Review + describes — and a local CLI run posts nothing. A `PENDING` review has no `submitted_at` and is not a submitted one; a `DISMISSED` one is a verdict *withdrawn* and proves nothing. **The `Actionable comments posted:` convention is CodeRabbit's** — its clean verdict is written by that line being **absent** rather than reading `0`, and a Codex review @@ -338,7 +339,7 @@ seen; the old rationale for allowing it turned on the round counter ADR-0064 ret what remains is simply that it costs more for nothing. Record the reason in the PR. Author-side review and status-only output do not satisfy it — and *author-side* is about whose judgement it is, not which machine ran it. The verdict has to be the provider's and **posted on the pull -request**, so GitHub attests that it said so and the review carries the `commit_id` the merge +request**, so GitHub attests that it said so and the artifact names the head the merge binds; a local run quoted by its author is not that, however faithfully it is quoted. What has to hold is that **every substantive change reaching the merge was externally reviewed**, which `AGENTS.md` §Review states operatively and which its fourth condition is what enforces on @@ -352,11 +353,11 @@ that was never asked has not declined — so if you are waiting on a review, che request was actually posted. **Codex has two delivery paths and they are not interchangeable.** The GitHub bot triggers -on opening a PR for review, marking a draft ready, or an `@codex review` comment, and posts a -review carrying a `commit_id`. The CLI runs locally and posts nothing. The bot has refused for +on opening a PR for review, marking a draft ready, or an `@codex review` comment, and posts an +artifact naming the head it read — a review object, or a comment when the run is clean (§Review). The CLI runs locally and posts nothing. The bot has refused for quota before — #427 and #428, 2026-08-07 — and a refusal is not a review. **The Codex leg is the posted review**, asked with an `@codex review` comment, because -`AGENTS.md` §Review requires a `commit_id` no local run can produce and says in the same breath +`AGENTS.md` §Review requires a posted artifact naming the head, which no local run produces and says in the same breath that local output satisfies nothing. The CLI is still worth running and satisfies **no leg**: it is an author-side tool for finding diff --git a/docs/PRD.md b/docs/PRD.md index 47b09426..4b061779 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1092,7 +1092,7 @@ the gate protects is that **every substantive change reaching the merge was exte §Review is the operative statement of that and of what makes it hold when the reviewed commit is not the merged one. **No provider auto-reviews this repository** — CodeRabbit reports auto reviews disabled, Greptile is held by `.greptile/config.json`'s `skipReview: "AUTOMATIC"`, and the GitHub Codex bot fires only on open-for-review, draft-ready, or an `@codex review` comment — so a provider that was not asked has not declined. That -bot posts a review carrying a `commit_id`, which every leg requires; the CLI runs locally, posts nothing and therefore +bot posts an artifact naming the head it read, which every leg requires — §Review gives the two shapes; the CLI runs locally, posts nothing and therefore satisfies no leg — it is an author-side tool, useful before a provider is asked and recorded as none. The one exception is a branch cut **before** that config landed: it is read from the PR's source branch, so such a branch still auto-fires Greptile on open. The credit is spent regardless, so the review is answered and the optional Greptile From 3e1bf6371cf89cdec129f03fbaa3253f65a2c579 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:58:35 -0500 Subject: [PATCH 40/43] =?UTF-8?q?fix(agents):=20answer=20CodeRabbit=20revi?= =?UTF-8?q?ew=201=20=E2=80=94=20three=20Major,=20one=20Minor=20fixed,=20tw?= =?UTF-8?q?o=20deferred?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First CodeRabbit review of this PR, at 0970f4dc: six actionable comments, three Major and three Minor. MAJOR, and the most valuable finding of the whole lane: docs/PRD.md said "at most two completed reviews per metered provider" and then "when THE CAP is spent, a fresh posted Codex review closes the gate". The antecedent was ambiguous, and the wrong reading is not cosmetic -- it lets GREPTILE's cap being spent open the close, with CodeRabbit never having reviewed twice at all. That is the gate opening without the thing it substitutes for ever happening. Now scoped to CodeRabbit's cap by name, with Greptile's optionality stated beside it. MAJOR: the PRD had auto-merge "armed at the end of that lane by whoever completes it", which reads as lane completion conferring merge authority -- the misreading this PR itself calls the most damaging available one, sitting in the PRD while AGENTS.md was promoting the opposite to its own rule. Separated explicitly. MAJOR: condition 4 has no machine check. True, and the specific example given is already shut by "the unit is the change, not the commit" -- but the general point stands: whether a hunk answers a recorded finding is judged by the same worker the condition constrains. A check is out of scope by construction (#439 rules out restoring ADR-0064's machinery) and would only be a different unchecked rule anyway, since any hunk-to-finding mapping is prose. ADR-0065 gains "What condition 4 does not buy", saying what it is worth against a careless worker and what it is not worth against a deliberate one. AGENTS.md names it inline. MINOR, fixed: the byte-budget guard's failure message said "Past the cap" when it fires at the working budget, which would send a maintainer hunting truncation that has not happened. tests/ is an agent test module so this was droppable; fixed anyway because the diagnostic is what someone reads at 2am. MINOR, deferred to #453: ADR-0065's AGENTS.md line anchors are stale -- and the follow-up is not a renumber, because line references into a file under active edit cannot be kept true. They become rule-name citations. Plus launcher vs slot launcher in ADR-0064 and the ADR index. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green. AGENTS.md 29,397 bytes against the 29,696 budget. --- AGENTS.md | 3 ++- docs/PRD.md | 14 ++++++++----- ...ed-cap-closes-on-the-unmetered-provider.md | 21 +++++++++++++++++++ tests/test_agent_contract_is_runnable.py | 8 ++++--- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 985ad193..ba66192c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -258,7 +258,8 @@ validity turns on it being the right test — must satisfy both. running is a push it never saw, and a time-anchored window would wave it through. **The unit is the change, not the commit**: a commit that fixes a recorded finding *and* carries an unrelated hunk passes any per-commit test while smuggling exactly the scope this shuts out, so every hunk - has to trace to one of the three. New scope pushed after the cap has spent it is scope **no + has to trace to one of the three — a judgement you apply and nothing checks for you, which + ADR-0065 states plainly rather than dressing up as mechanical. New scope pushed after the cap has spent it is scope **no metered provider will ever read**, and what the close is entitled to be is a further opinion on a diff **every substantive part of which some external provider has already read** — metered up to the commit the second review read, the closing review after it. Not *twice*-read and not all of it diff --git a/docs/PRD.md b/docs/PRD.md index 4b061779..08b10980 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1105,9 +1105,10 @@ governance text itself (`AGENTS.md` anywhere, `CLAUDE.md`, `CONTRIBUTING.md`, th `.agents/**`, `docs/agents/**`, `.claude/**`, `.github/pull_request_template.md` and `.greptile/**`, and `AGENTS.override.md` anywhere) are material — the list is *every file that states a rule*, because a push that changes what the gate requires must not keep evidence gathered under the old requirement. A material push re-arms the review, and a PR gets **at most two completed reviews per metered provider**, Codex being unmetered and uncapped — -needing a third usually means the issue was scoped too large. The lane does **not** stop for the maintainer: when the cap is -spent, a fresh posted Codex review of the final head closes the gate instead (ADR-0065), subject to conditions -`AGENTS.md` §Review states and this document, again deliberately, does not. +needing a third usually means the issue was scoped too large. The lane does **not** stop for the maintainer: when +**CodeRabbit's** two-review cap specifically is spent — not any other provider's, since Greptile is optional and its +exhaustion never blocks — a fresh posted Codex review of the final head closes the gate instead (ADR-0065), subject to +conditions `AGENTS.md` §Review states and this document, again deliberately, does not. That bound is a **convention a worker keeps, not a counter that publishes labels.** ADR-0064 retired the round ledger, the `agent:round-*` / `agent:review-capped` / `agent:gate-blocked` labels and the launcher that consumed them, after @@ -1183,8 +1184,11 @@ the ADR-0052 run was a lease that only a sleeping human could renew. Every agent is a peer: it claims one issue, works one isolated worktree/branch/PR, opens the review lane on a draft, and hands off. Auto-merge — bound to the reviewed head with `--match-head-commit` — is armed at the **end** of that -lane by whoever completes it, never on the draft, since the mandatory review gate — CodeRabbit, or the Codex close -when its cap is spent (ADR-0065) — is not a required check and nothing else would hold the merge. No agent waits on another, and no agent merges on another's +lane, never on the draft, since the mandatory review gate — CodeRabbit, or the Codex close +when its cap is spent (ADR-0065) — is not a required check and nothing else would hold the merge. Completing the lane +is **not** what authorises the arming: clearing the gate and having authority to merge are separate, the second is +per-PR and explicit and is never inferred from the first, and a worker who finishes the lane without it records that +the gate is satisfied and hands off. No agent waits on another, and no agent merges on another's behalf. ADR-0052's coordinator, leases, run records and guarded-merge monopoly are retired, not merely superseded. **Label taxonomy** (prefixed namespaces, so labels group and filter cleanly): diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index 0c17c0c3..b20f7a70 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -340,6 +340,27 @@ throughout and found twenty-odd defects in the text feeding them, so there is no here — but "no sign it mattered" is not the property the trust boundary asks for, and the last round of this pull request was re-run with the flag. +### What condition 4 does not buy + +It is applied by the worker, and **nothing checks it mechanically.** Every hunk after the cap has to +trace to a recorded finding, a non-material exception, or the required `main` merge's conflict +resolution — and whether a given hunk really does is a judgement, made by the same worker the +condition constrains. A determined author can present unrelated logic as finding disposal and the +closing review would be the first and only read of it. + +That is stated rather than closed, for two reasons. A machine check is **out of scope by +construction**: #439 rules out restoring the machinery ADR-0064 removed, and ADR-0064's own finding +was that review state has no concurrency content and needs no ledger. And the alternative on offer +is not a stronger check but a *different* unchecked rule, since any mapping from hunk to finding is +prose either way. + +What the condition does buy is narrower and real: it is **per hunk rather than per commit**, so +bundling an unrelated change into a fix commit does not launder it; it is anchored at a commit rather +than a clock, so a push mid-review cannot slip through; and it puts the claim on the record, where +the merged history is auditable. Against a careless worker it works. Against a deliberate one it is a +statement of intent, and this record does not pretend otherwise — the same honesty the procedural-pin +section above required before that mechanism was deleted. + **Motive is deliberately not a test.** An earlier draft closed the gaming path with *"an ask made to spend the cap is not one of the two."* That was rejected on review for two reasons: a motive is not checkable by anyone, including the agent itself; and read strictly it **restores the deadlock in a diff --git a/tests/test_agent_contract_is_runnable.py b/tests/test_agent_contract_is_runnable.py index 3b619990..04c1ec8e 100644 --- a/tests/test_agent_contract_is_runnable.py +++ b/tests/test_agent_contract_is_runnable.py @@ -876,7 +876,9 @@ def test_the_contract_fits_inside_the_project_document_cap() -> None: assert size <= budget, ( f"AGENTS.md is {size:,} bytes and the working budget is {budget:,} " f"({_PROJECT_DOC_CAP_BYTES:,}-byte loader cap minus {_HEADROOM_BYTES:,} of headroom). " - "Past the cap the tail is dropped silently and workers lose whatever sections sit at the " - "end -- Handoff and This machine, including the TLS workaround. Move rationale into the " - "ADR that records the decision and leave the operative rule here." + "This is the working budget, not the cap itself -- the file may still be under the " + f"{_PROJECT_DOC_CAP_BYTES:,}-byte loader limit and this still fails, deliberately, so the " + "headroom is not spent. Past the CAP the tail is dropped silently and workers lose whatever " + "sections sit at the end -- Handoff and This machine, including the TLS workaround. Move " + "rationale into the ADR that records the decision and leave the operative rule here." ) From bd3bd507a1776562a0738c445b8bbb46ff336297 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:01:22 -0500 Subject: [PATCH 41/43] style(tests): wrap the budget diagnostic to the 100-column limit ruff E501 on the message rewritten in the previous commit -- 101 characters. Caught by CI's lint and pre-commit jobs rather than locally, because the local gate this contract names is the pytest matrix and ruff is only reached through pre-commit, which does not run in this lane (recorded in the PR body). One line wrapped; no wording changed. --- tests/test_agent_contract_is_runnable.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_agent_contract_is_runnable.py b/tests/test_agent_contract_is_runnable.py index 04c1ec8e..818bd583 100644 --- a/tests/test_agent_contract_is_runnable.py +++ b/tests/test_agent_contract_is_runnable.py @@ -878,7 +878,8 @@ def test_the_contract_fits_inside_the_project_document_cap() -> None: f"({_PROJECT_DOC_CAP_BYTES:,}-byte loader cap minus {_HEADROOM_BYTES:,} of headroom). " "This is the working budget, not the cap itself -- the file may still be under the " f"{_PROJECT_DOC_CAP_BYTES:,}-byte loader limit and this still fails, deliberately, so the " - "headroom is not spent. Past the CAP the tail is dropped silently and workers lose whatever " - "sections sit at the end -- Handoff and This machine, including the TLS workaround. Move " + "headroom is not spent. Past the CAP the tail is dropped silently and workers lose " + "whatever sections sit at the end -- Handoff and This machine, including the TLS " + "workaround. Move " "rationale into the ADR that records the decision and leave the operative rule here." ) From 9166b27b0700b3152d1d617588456bb428da8206 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 20:45:03 -0500 Subject: [PATCH 42/43] =?UTF-8?q?fix(agents):=20answer=20CodeRabbit=20revi?= =?UTF-8?q?ew=202=20=E2=80=94=20three=20fixed,=20one=20false=20positive,?= =?UTF-8?q?=20one=20deferred?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second and last permitted CodeRabbit review, at bd3bd507: header reads "Actionable comments posted: 5", though it posted zero inline comments and the findings appear only in its agent-prompt block. Not clean either way. FIXED. The Findings field in the PR template listed three dispositions where the cap-spent line above it lists four -- "withdrawn by the provider" was missing, so the field could not record the disposition #434 actually used. CONTRIBUTING.md's copy had the same gap. Both now carry it, and both say the test is whether anything is left OUTSTANDING rather than which of the four cleared it. FIXED. ADR-0065 said §Review grows "from 80 lines to 152". Measured: 163. The figure was taken once and left to rot while the section kept changing -- the second time this record has misstated its own cost. Corrected, and both numbers are now tied to the revisions they were counted at, since a count without its revision is the same defect as the line anchors #453 exists to remove. FIXED. docs/PRD.md said "No provider auto-reviews this repository" a few lines before explaining that a branch cut before .greptile/config.json landed still auto-fires Greptile. Qualified to pull requests opened under the current configuration, which is what the following sentence already assumed. FALSE POSITIVE, not fixed and said so: the template does not contain a literal Codex handle. Line 37 reads "the Codex review command named in AGENTS.md §Review" -- prose, exactly as the rule requires -- and test_no_posted_template_carries_a_provider_handle passes on it. That guard exists because I DID write a handle there earlier in this PR and it caught me. DEFERRED, already tracked: slot launcher in ADR-0064 and the ADR index, raised in review 1 and tracked in #453. Re-raising does not change its severity. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green; ruff clean, added to the local routine after CI caught an E501 the pytest matrix could not see. --- .github/pull_request_template.md | 2 +- CONTRIBUTING.md | 5 +++-- docs/PRD.md | 2 +- ...-a-spent-metered-cap-closes-on-the-unmetered-provider.md | 6 ++---- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 047bede3..e0ec36df 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -38,7 +38,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Greptile: reviewed — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3), and name the **full 40-hex** head it read, which need not be the final one: Greptile is asked before CodeRabbit, so a later finding-fix legitimately moves the head past it, and only the provider that *closes* the gate must reach the final head. Buying a second review to make a checkbox true is not a reason to spend a credit | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter - **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing review above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); no finding from either is left outstanding, with the thread resolved on each — cleared by being fixed, deferred-and-tracked, dropped sub-floor, or **withdrawn by the provider that raised it**; *outstanding* is the test and those are the known ways of clearing one; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing review's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by an **external** provider — metered up to the commit the second review read, the closing review after it, which is the guarantee the conditions buy and not a metered read of everything; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing review above is quoted. That closing review is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass -- Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064). Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it +- Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064) | `` **withdrawn by the provider that raised them** — quote the withdrawal and resolve the thread, since a finding retracted by its author is cleared but is not something you fixed, deferred or dropped, and the cap-spent line above tests whether anything is left *outstanding* rather than which of the four cleared it. Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) ## Type of change diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 73fc4d8c..cce2e30a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -460,8 +460,9 @@ does not license hammering: honour the retry interval the refusal names, and nev re-request while the status check reads `pending`. **Greptile is one *review* in practice**, and a review is not always one credit — a standard review costs one, a TREX review three. Two is the shared ceiling, not a second review to plan on, so ask again only if the first found something blocking and the seat still has budget. **A spent cap -is not a stuck PR**: when every finding those two reviews raised is disposed of and every -thread resolved, a fresh posted Codex review of the final head closes the gate in their place, so the +is not a stuck PR**: when **no finding those two reviews raised is left outstanding** — cleared by +being fixed, deferred-and-tracked, dropped sub-floor, or **withdrawn by the provider that raised +it**, that last quoted on the thread like any other disposition — and every thread resolved, a fresh posted Codex review of the final head closes the gate in their place, so the PR finishes on an unmetered read rather than on a maintainer. **`AGENTS.md` §Review sets four conditions on that and is the operative statement of all of them**; the other two are above — a provider that refused reviewed nothing, so a spent cap is not what an unavailable CodeRabbit diff --git a/docs/PRD.md b/docs/PRD.md index 08b10980..7e1e2394 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1089,7 +1089,7 @@ is a **wait**, not unavailability, and a request that produced no review is not Author-side review and a green or status-only result do not satisfy the gate; *author-side* names whose judgement it is rather than which machine ran it, and what the gate protects is that **every substantive change reaching the merge was externally reviewed** — `AGENTS.md` -§Review is the operative statement of that and of what makes it hold when the reviewed commit is not the merged one. **No provider auto-reviews this repository** — CodeRabbit reports auto reviews +§Review is the operative statement of that and of what makes it hold when the reviewed commit is not the merged one. **No provider auto-reviews a pull request opened under the current configuration** — CodeRabbit reports auto reviews disabled, Greptile is held by `.greptile/config.json`'s `skipReview: "AUTOMATIC"`, and the GitHub Codex bot fires only on open-for-review, draft-ready, or an `@codex review` comment — so a provider that was not asked has not declined. That bot posts an artifact naming the head it read, which every leg requires — §Review gives the two shapes; the CLI runs locally, posts nothing and therefore diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index b20f7a70..d42b49ae 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -438,11 +438,9 @@ smuggling in. An argument that overstates its own premise is worth less than the and this one took two rounds of being caught to stop doing it. **If Codex's review quality degrades, this paragraph is the part of the record that stops holding**, and nothing in this repository would detect that. -A second cost, and it is the larger of the two: **§Review grows from 80 lines to 152** — it nearly -doubles — in a file ADR-0064 deliberately shrank, and whose resident-context driver, *"`AGENTS.md` is +A second cost, and it is the larger of the two: **§Review grows from 80 lines to 163** — it doubles — in a file ADR-0064 deliberately shrank, and whose resident-context driver, *"`AGENTS.md` is read on every model call by every agent"*, argues against every addition. An earlier draft of this -paragraph said *"roughly twenty lines"*; a review measured it and the real figure is around four -times that. The +paragraph said *"roughly twenty lines"*, and a later one said 152; both were measured once and then left to rot as the section kept changing. The figures above are for the merge base and for the revision this record ships with — a count stated without the revision it was taken at is the same defect as an ADR citing a line number. The understatement is recorded rather than quietly corrected, because a decision record that under-reports its own cost is how a cost stops being weighed. From 353a4aaa3c554271ddfa778f9e50690dcdfbf598 Mon Sep 17 00:00:00 2001 From: bioedca <131809205+bioedca@users.noreply.github.com> Date: Wed, 12 Aug 2026 22:41:51 -0500 Subject: [PATCH 43/43] =?UTF-8?q?fix(agents):=20answer=20CodeRabbit=20revi?= =?UTF-8?q?ew=203=20=E2=80=94=20the=20closing=20bar=20was=20"no=20majors",?= =?UTF-8?q?=20not=20"nothing=20actionable"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third review, maintainer-authorised, at 9166b27: four actionable comments plus the already-deferred slot-launcher nitpick. My lean that a third read would be clean was WRONG, and the findings were real -- one of them the most consequential single defect CodeRabbit has raised on this PR. THE BAR WAS LOWER THAN THE RULE CLAIMED. The close required a Codex review with "nothing blocking", and Codex's clean result reads "Didn't find any major issues". Those are not the same sentence: a run that found something MINOR says exactly that while still sounding benign, so the close could have been satisfied by a review that found things. That is precisely the discount ADR-0065 says the close must never be -- written into the rule by the same record that forbids it. AGENTS.md now requires nothing ACTIONABLE, says why the wording matters, and sends any finding of any severity through disposal and a re-read. CAP ACCOUNTING counted "each one it submitted with a body". Reviews 2 and 3 both posted bodies with ZERO inline comments; the inverse -- inline findings and no body -- is equally possible, and would not have counted. A review is now counted by being SUBMITTED in state COMMENTED or APPROVED carrying a body or inline findings, with throttles, refusals, failed runs, PENDING, DISMISSED, bare status checks and thread replies all excluded by name. Mirrored in the template. Also fixed: the template's Review-complete item demanded a full 40-hex head without admitting the short-SHA expansion the same file describes two lines up; and the PRD did not say that a throttled retry consumes no completed-review slot. Deferred, already tracked: slot launcher (#453). BUDGET. These fixes pushed AGENTS.md to 30,120 bytes -- over the guard added earlier in this PR. It was not raised. ~430 bytes of rationale moved from §Review into ADR-0065, which now records that §Review is at its practical ceiling and the next rule added there must displace something. The guard caught its own author, one commit after being written. Local gates: 2939 passed, 19 skipped, 34 deselected; mkdocs --strict green; ruff clean. AGENTS.md 29,691 of a 29,696 budget. --- .github/pull_request_template.md | 4 +- AGENTS.md | 48 ++++++++++--------- docs/PRD.md | 2 +- ...ed-cap-closes-on-the-unmetered-provider.md | 8 ++++ 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e0ec36df..ef15ea28 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -36,7 +36,7 @@ linked work maps to one. The checklist, not CI, enforces those fields. - Codex — first, on the green diff, and not optional (the draft by default; a ready-opened PR whose reason is recorded is asked there, at the same point in the lane): reviewed, nothing blocking outstanding (quote it) | reviewed, findings answered below | not reviewed (say why). Unmetered, so uncapped - Codex closing review — required whenever the CodeRabbit line below records a **spent cap**, whatever heads those two reviews read, **including where one came back clean at a head a permitted non-material push has since moved**: that review's evidence still stands, but no metered provider has named the commit the merge binds, and the cap forbids asking for a third to name it: n/a — a clean CodeRabbit review at the head being merged, with nothing since | **closed the gate** — quote it exactly as the CodeRabbit gate is quoted: permalink or run artifact, the **full 40-hex** head it read **which must be the final head**, when, and what it said. A re-quoted earlier Codex pass is **not** a closing review, since the head that pass read is not the head being merged. It must be **posted by the provider and name the commit it read** — asked with the Codex review command named in `AGENTS.md` §Review, written in prose here because a handle in this template would fire a real review on every PR opened from it. Two shapes both count: a run with findings posts a review whose `commit_id` is the full 40-hex, and a **clean** run posts a comment carrying `Reviewed commit: ` — expand that with `git rev-parse` and record both. A local CLI run posts nothing and cannot close. Anything this read surfaces is disposed of above before it closes - Greptile: reviewed — quote its verdict, not only the spend (spent N credits; a standard review is 1, a TREX review 3), and name the **full 40-hex** head it read, which need not be the final one: Greptile is asked before CodeRabbit, so a later finding-fix legitimately moves the head past it, and only the provider that *closes* the gate must reach the final head. Buying a second review to make a checkbox true is not a reason to spend a credit | skipped — no budget this month | skipped (say why). Balance from ` .agents/bin/greptile_usage.py`, where `` is your lane's interpreter -- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing review above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand, each submitted with a body; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); no finding from either is left outstanding, with the thread resolved on each — cleared by being fixed, deferred-and-tracked, dropped sub-floor, or **withdrawn by the provider that raised it**; *outstanding* is the test and those are the known ways of clearing one; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing review's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by an **external** provider — metered up to the commit the second review read, the closing review after it, which is the guarantee the conditions buy and not a metered read of everything; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing review above is quoted. That closing review is then the `` the merge below binds to +- **CodeRabbit — the last metered gate**: no actionable comments (quote the review — permalink, the **full 40-hex** `commit_id` it read **which must be the final head when CodeRabbit is what closes the gate**, its `submitted_at`, its state — **`COMMENTED` or `APPROVED`**, since `DISMISSED` is a verdict withdrawn and `PENDING` is unsubmitted — and the opening of its body, which must show that **`Actionable comments posted:` is ABSENT**: zero is written by that line not being there, and a clean body opens straight onto `🧹 Nitpick comments` or `No actionable comments were generated`. A review of an earlier head does not qualify: where every push since it is non-material its evidence still stands and it is one of the two completed reviews, but it is the Codex closing review above that names the merging head, so that case is recorded as **cap spent, closed by Codex** and not here. A `PENDING` one is not submitted, a `DISMISSED` one is a verdict withdrawn, and a green status check with no review body is **not** the gate) | in flight (status check `pending` — never re-request, it aborts the run) | throttled, retrying after the stated interval *and* a non-pending status check (a wait, not a freeze) | unavailable (freezes the PR) | **cap spent, closed by Codex** — two *completed* reviews stand — each **submitted**, in state `COMMENTED` or `APPROVED`, carrying a body or inline findings of its own; a throttle, quota refusal, failed run, `PENDING` or `DISMISSED` review, bare status check or reply on someone else's thread is none of them; the second was asked only after the first one's findings were **disposed of** (by commits that answer them, or by the replies and resolutions recording a deferral or drop — same `commit_id` is fine, since disposal on the record moves no head); no finding from either is left outstanding, with the thread resolved on each — cleared by being fixed, deferred-and-tracked, dropped sub-floor, or **withdrawn by the provider that raised it**; *outstanding* is the test and those are the known ways of clearing one; **nothing but disposal and the non-material exceptions landed after the commit the second review read** — every hunk since answers something already recorded on this PR **that you were required to address** — a review finding from any provider, a CodeQL or `secret-scan` alert, a condition a human sign-off attached, the closing review's own finding; the test is the change, not who raised it — or is a clean `main` merge / formatting / comment or docstring edit / **ADR renumber-only** (a renumber that also edits a word of the decision is material, not an exception), or is the resolution of a conflict in the `main` merge the contract requires, which admits the reconciliation only — so no new scope reached the merge unread by an **external** provider — metered up to the commit the second review read, the closing review after it, which is the guarantee the conditions buy and not a metered read of everything; neither came back clean **at the head being merged with its evidence still standing** — all three, since a clean review that a later material push re-armed is not a gate that already closed, reading it as "neither was ever clean" would strand the case where review 1 was clean at an earlier head and review 2 then found something, and a clean review whose head a *non-material* push has since moved lands **here** rather than on the line above, because its evidence stands but no metered provider has named the commit the merge binds; and the Codex closing review above is quoted. That closing review is then the `` the merge below binds to - Provider that did not review: none | which, and why — a quota refusal means the provider **did not review**, and never counts as a pass - Findings: `` serious (fixed) | `` below the floor (deferred to #____, or dropped if this is an agent-layer path — ADR-0064) | `` **withdrawn by the provider that raised them** — quote the withdrawal and resolve the thread, since a finding retracted by its author is cleared but is not something you fixed, deferred or dropped, and the cap-spent line above tests whether anything is left *outstanding* rather than which of the four cleared it. Dropped is not silent: reply on the thread in the wording `AGENTS.md` §Review gives, and resolve it - Human sign-off: n/a | release/tag/signing | new scientific claim **or citation** (reviewer and evidence) @@ -59,7 +59,7 @@ Confirm before requesting review: - [ ] **Data policy respected** — no raw/private/unlicensed data or large data in ordinary Git; issue-authorized redistributable fixtures carry license and provenance in named small or LFS/gated paths. - [ ] **No secrets committed** — no token, key, credential, or private path in code, tests, logs, or fixtures; `secret-scan` and push protection are green. - [ ] **Code scanning clean** — CodeQL (GitHub code-scanning *default setup*, hence no `codeql.yml` workflow) reports no new alerts on this PR. -- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head comes from the provider's own posted artifact, in either shape `AGENTS.md` §Review describes. A head asserted by the author is never acceptable, since it could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh posted Codex review of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing review that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. +- [ ] **Review complete** (`AGENTS.md` §Review) — the diff went green before anything was asked to read it, on a draft by default or on a ready-opened PR whose reason is recorded above, and then **Codex on that green diff before any metered provider**; **every provider the lane reached** has a result recorded above — a quoted verdict at the head it read, or the reason it produced none — and **the provider that closed the gate reviewed the final head** — where that is Codex, quote what it emits and skip the fields that exist only on a review — but the **full 40-hex** head comes from the provider's own posted artifact, in either shape `AGENTS.md` §Review describes — a review's `commit_id`, or a clean run's `Reviewed commit: ` expanded with `git rev-parse`, which is mechanical and redoable and so still the provider's head rather than yours. A head asserted by the author is never acceptable, since it could name a commit the provider never saw. Where the closer is CodeRabbit, its verdict quoted with all six of **which provider it was** — its name, never its @-handle, since a mention in the PR body fires the bot — permalink, the **full 40-hex** `commit_id` it read, `submitted_at`, a state of **`COMMENTED` or `APPROVED`**, and **what it actually said**: the submitted review body, or enough of it to establish the verdict, since metadata alone records that a provider ran and not what it found; and **the gate is closed at that head** — either **CodeRabbit returned no actionable comments** there, asked with the **full-review** command, or its two-review cap is spent under the conditions the CodeRabbit line above sets out and a **fresh posted Codex review of that head closed it in their place**. On the cap-spent path CodeRabbit's two reviews are recorded at **whatever heads they read** — earlier ones where a fix moved the head, the same one where the disposal was a deferral or drop — and it is the Codex closing review that names the final head; requiring CodeRabbit itself to reach the final head there would demand the third review the cap forbids. Neither silence nor a green `CodeRabbit` status check is the gate — both are also what a request that reviewed *nothing* leaves behind. A provider that could not act is recorded above with the reason, and a quota refusal means the provider **did not review**, and never counts as a pass. Serious findings fixed; the rest deferred to one follow-up issue, or dropped without one if this is an agent-layer path (ADR-0064) — dropping still owes the thread the reply `AGENTS.md` §Review words, so the decision is on the record rather than inferred from silence. Every conversation resolved. - [ ] **Provenance stamped** — coordinates / corrections / app-version / parameters written into the `.tether` for any new analysis (NFR-REPRO). - [ ] **New tunables registered in PRD §11.2** (single source of truth), not hardcoded. - [ ] **Scientific/statistical claims carry a citation**; **SPDX `GPL-3.0-or-later`** header on every new source file (`reuse lint` green). diff --git a/AGENTS.md b/AGENTS.md index ba66192c..8814344f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -204,15 +204,20 @@ validity turns on it being the right test — must satisfy both. are *paused*; they are *disabled* here, so it reviews nothing and says so in words that read like a clean pass). Read its commit status before every ask — `pending` means one is running and a second request destroys it. A fair-use refusal naming a retry time is a **wait**, not - unavailability; **never** accept its usage-based-billing offer, which is the maintainer's spending - decision. + unavailability, and the time it names is a floor rather than a guarantee; **never** accept its + usage-based-billing offer, which is the maintainer's spending decision. - **A spent cap closes on Codex rather than on a maintainer.** When two *completed* CodeRabbit - reviews stand on this PR — each one it submitted with a body, since a throttle, a quota refusal or - a failed run reviewed nothing — and **no finding they raised is left outstanding**, with the thread - resolved on each, then a **fresh Codex review of the final head** closes the gate in their place. + reviews stand on this PR — **submitted**, in state `COMMENTED` or `APPROVED`, carrying a body or + inline findings of its own; a throttle, quota refusal, failed run, `PENDING` or `DISMISSED` + review, bare status check, or a reply on someone else's thread is none of them — and **no finding + they raised is left outstanding**, with the thread resolved on each, then a **fresh Codex review of the final head** closes the gate in their place. + **It must report nothing actionable, which is not the same as nothing *major*.** Codex's clean + result reads *"Didn't find any major issues"* — a run that found something minor says otherwise + while still sounding benign, so read what it reported. A closing review carrying findings of any + severity is disposed of first and re-read. The close substitutes for a *clean* pass; accepting + "no majors" would make it the discount ADR-0065 forbids. It must be **posted by the provider and name the commit it read**, in either shape the first - bullet describes; a re-quoted earlier pass is not one, since the head that pass read is not the - head being merged. *Nothing + bullet describes; a re-quoted earlier pass is not one, its head not being the one merged. *Nothing left outstanding* is the test — fixed, deferred-and-tracked, dropped sub-floor and withdrawn by the provider are the ways of clearing a finding, not the test itself. Anything the closing review surfaces is cleared the same way before it closes: this is a *substitute for the clean pass*, not a @@ -255,22 +260,19 @@ validity turns on it being the right test — must satisfy both. neither lets unread scope through, and a resolution carrying new logic of its own is new scope like any other. Anchor it at the commit and not the clock, because a material push landing while that review is still - running is a push it never saw, and a time-anchored window would wave it through. **The unit is - the change, not the commit**: a commit that fixes a recorded finding *and* carries an unrelated - hunk passes any per-commit test while smuggling exactly the scope this shuts out, so every hunk - has to trace to one of the three — a judgement you apply and nothing checks for you, which - ADR-0065 states plainly rather than dressing up as mechanical. New scope pushed after the cap has spent it is scope **no - metered provider will ever read**, and what the close is entitled to be is a further opinion on a - diff **every substantive part of which some external provider has already read** — metered up to - the commit the second review read, the closing review after it. Not *twice*-read and not all of it - metered: two earlier drafts claimed both and ADR-0065 records why each was false. New scope - therefore shuts the close and the PR waits for a gate it can actually satisfy. Motive is not a test - and never becomes one; these four are, and they are also why spending an ask to reach the close - would buy nothing if it worked, since the close costs the disposal of every finding and a further - review on top — more work than the clean pass it replaces. -- **Clearing the gate is not authority to merge.** They are different things and the second is still - per-PR, explicit, and never inferred. Escalate to the maintainer only when the closing review - surfaces something blocking that you may not resolve inside this item's scope. + running is a push it never saw. **The unit is the change, not the commit**: a commit that fixes a + recorded finding *and* carries an unrelated hunk would otherwise smuggle exactly the scope this + shuts out, so every hunk traces to one of the three — a judgement you apply and nothing checks for + you, which ADR-0065 states plainly rather than dressing up as mechanical. New scope pushed after the cap has spent it is scope **no + metered provider will ever read**, so the close is entitled to be a further opinion on a diff + **every substantive part of which some external provider has already read** — metered up to the + commit the second review read, the closing review after it, and no stronger than that (ADR-0065). + New scope therefore shuts the close. Motive is not a test and never becomes one; these four are, + and they are also why spending an ask to reach the close buys nothing — it costs the disposal of + every finding and a further review on top. +- **Clearing the gate is not authority to merge.** Different things; the second is per-PR, explicit + and never inferred. Escalate only when the closing review surfaces something blocking that you may + not resolve inside this item's scope. - **Never write a provider's handle in a comment you do not intend as a request.** A mention fires the bot even inside backticks — a code span is not an escape. Describe the command in prose instead. diff --git a/docs/PRD.md b/docs/PRD.md index 7e1e2394..d1a6161f 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1085,7 +1085,7 @@ no single one was sufficient — the lane keeps that property while spending the works in, billed per **completed review** — one credit for a standard review, **three for a TREX** one; Copilot is budgeted the same way and is **advisory only** — it never satisfies a leg, and a quota refusal from it means the provider **did not review**, never a pass. Exhaustion and incapacity differ: Greptile out of credits is skippable, CodeRabbit unavailable **freezes the PR** — though a fair-use refusal that names a retry time -is a **wait**, not unavailability, and a request that produced no review is not one of the two completed reviews. +is a **wait**, not unavailability — the interval it names being a floor rather than a guarantee — and the retry is a fresh provider request that consumes no completed-review slot, since a request that produced no review is not one of the two. Author-side review and a green or status-only result do not satisfy the gate; *author-side* names whose judgement it is rather than which machine ran it, and what the gate protects is that **every substantive change reaching the merge was externally reviewed** — `AGENTS.md` diff --git a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md index d42b49ae..fac0cd71 100644 --- a/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md +++ b/docs/adr/0065-a-spent-metered-cap-closes-on-the-unmetered-provider.md @@ -444,6 +444,14 @@ paragraph said *"roughly twenty lines"*, and a later one said 152; both were mea understatement is recorded rather than quietly corrected, because a decision record that under-reports its own cost is how a cost stops being weighed. +**§Review is now at its practical ceiling, and that is a finding rather than a footnote.** Answering +CodeRabbit's third review pushed `AGENTS.md` to 30,120 bytes — over the guard — and the fix was not +to raise the budget but to cut ~430 bytes of rationale out of §Review and into this record. It now +sits within a handful of bytes of the limit. The next rule added to that section has to displace +something, and the honest reading is that **§Review has no room left for prose that is not +operative**. That is the byte guard doing exactly what it was added for, one commit after it was +added, on the author who added it. + What the length buys is the four shutting conditions and the stamped-head rule, which cannot be compressed without turning the branch into a judgment call. What it also carries is the *reasoning* behind each condition — most of it added one review round at a time, as each draft was shown to