feat(receipt): owe a code review over the branch's change, and price it at the ready transition - #880
Draft
wenzowski wants to merge 4 commits into
Draft
feat(receipt): owe a code review over the branch's change, and price it at the ready transition#880wenzowski wants to merge 4 commits into
wenzowski wants to merge 4 commits into
Conversation
|
Warning Review limit reachedNext included review available in 24 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (13)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…n be owed CLOUD-1484. `review-dispatched` (CLOUD-472) answers *did a named prompt run over these bytes*, and both its declared subjects are prose — `AGENTS.md` and a tracker body. Nothing has ever asked whether a review ran over the branch's own diff, so a pull request may change every line of `crates/batten` unread. THE DISPATCH INVERTS RATHER THAN REPEATING. CLOUD-472's engine spawns a reviewer, which is what forced a runner, a readiness probe and a prompt channel into a repo-agnostic core. Here Batten refuses, the refusal names what the agent owes, the agent dispatches with its own harness, and `[[mint]]` writes the receipt from the call the boundary was already mediating. That is not CLOUD-1265's refuted producer-writes-outside shape: nobody has to remember to run a producer, because the hook sees the call. `MintKey::Delta` keys a receipt by `git::branch_patch_id` — a wrapper over `cumulative_patch_id` rather than a second computation, so *the same change* means here exactly what `landing` already means by it. A rebase therefore does not re-owe the review and any commit moving code does. The boundary refuses to mint at all while the tree is dirty: a receipt keyed to committed bytes must not be taken in a session looking at something else. `selects_at`/`selects` are the load-bearing pair. Every skill this harness dispatches arrives under one tool name, so `tool` alone discriminates nothing and the row would attest that *something* ran. `requires` cannot reach it — the field is present whichever skill ran, it just holds another value. The selector reads the tool INPUT and `requires` reads the RESULT: the input says which call this is, the result says whether it answered. Two measurements changed the design while building it. A skill's result on this host is the bare string `Launching skill: <name>`, which is not JSON, so `record_mints` gave up before reaching the row — this is the first row whose whole reading is of the input and the repository, so the result is now `null` rather than an early return. And the clean-tree condition is at the WRITE, not in the module: as a conjunct it would let a dirty tree silence the gate, and as a refusal it would be a second opinion where `tree-clean` already owns the question. The receipt attests the DISPATCH, not the completion, and that gap is named in `batten.toml` rather than papered over: the harness returns nothing that would say the agent then did what the skill told it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015H3zMrSGxu4K3fMZu4KFX1
Generated, never hand-edited: `mise run schema` over the four surfaces together, so none can be refreshed alone and drift while the others look current. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015H3zMrSGxu4K3fMZu4KFX1
`/code-review` was dispatched over this branch, which is what the gate exists to
demand, and it found six things. Four are repaired here; two are bounds and are
now written down where a reader meets them.
THE COULD-NOT-LOOK ARM WAS BACKWARDS, which is the one that would have shipped a
gate refusing exactly what it documents itself as abstaining on. `looked` guarded
`is_object(input.tree.minted)`, but `minted::fields` leaves a declared id ABSENT
when it cannot list the store — so an unlistable store is an EMPTY map, not
`null`, `is_object({})` holds, and every fresh clone and every CI runner would
have been refused. The guard is per-id now.
THE CLEAN-TREE CONJUNCT AT THE MINT WAS UNSATISFIABLE IN THIS REPOSITORY.
`git::uncommitted` reimplements status and does not skip a GITLINK, where
`walk_blob_ids` explicitly does, so an uninitialised submodule counts as changed
forever: `git status` reports this tree clean while `changed_paths` returns
`{"tests/bats"}`, a mode-160000 entry. `Ok(0)` could never hold and the receipt
could never be minted. Withdrawn rather than repaired in place — it was also a
second authority over a question `tree-clean` owns, and a reviewer reads the
working tree anyway, so refusing to record a dispatch taken over uncommitted work
attests less than happened. The primitive's defect is CLOUD-1500.
`delta_sources` DOES NOT NARROW WHAT A MODULE SEES. `rules.rs` builds ONE
`base_delta` from the union of every row's globs, and six rows here declare
`["**"]`, so a workflow-only or schema-only branch owed a code review. The module
carries the reviewable prefixes itself.
A DECLARED MUTATION WAS NON-DISCRIMINATING. `no-identity-priced` named a case
whose `code-changed` was empty, so `owed` already excluded the mutated conjunct
and the mutation would have survived the sweep. The fixture now leaves the edit
UNCOMMITTED, which is the one state where the working-tree delta says code moved
and the committed range says nothing did.
Two bounds stated rather than fixed: `owed` is a tip diff and `subject` is a
merge-base one, so a stale branch can owe a review until it rebases; and the
identity covers the whole change, so a prose commit on a branch that also carries
code re-owes it. Both err toward owing a review that is not strictly due, and
neither can produce the other direction, which is a branch landing unread.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015H3zMrSGxu4K3fMZu4KFX1
…usal The per-id guard made the old case's expectation wrong, and it was wrong for a reason worth keeping: `minted` is keyed by DECLARED ROW, and `minted::fields` inserts an entry for every row whose store it could list — so an id missing from the map means the engine never looked for it, never that it looked and found nothing. Refusing there is a verdict about the engine's reach wearing a verdict about the branch. The claim the case meant to make is already `test_a_receipt_over_another_change_ does_not_answer`, where the id IS present and the subject belongs to another change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015H3zMrSGxu4K3fMZu4KFX1
wenzowski
force-pushed
the
claude/prose-dispatch-gate-verify-u0fnx3
branch
from
September 5, 2026 23:14
002cf49 to
3e55a8d
Compare
|
❌ The last analysis has failed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes CLOUD-1484.
Closes CLOUD-1547.
Why
review-dispatched(CLOUD-472, #826) already answers did a named prompt run over these bytes, keyed by two digests and refusing absence only. Both its declared subjects are prose —AGENTS.mdand a tracker body. Nothing has ever asked whether a review ran over the branch's own diff, so a pull request may change every line ofcrates/battenunread.Three measurements on
38fa830, stated rather than inferred:AGENTS.mdand the refinement body; neither is the diff;review::record_pathwrites under$GIT_DIR, which is empty on every fresh clone and every CI runner;batten-review-runneris on no PATH and has no implementation in the tree, soreview::resolvereturnsNoRunner, the whole fact collapses to could-not-look, andrequired contains "ready-pressure-test"has never refused anything.What changed
The dispatch inverts rather than repeating. CLOUD-472's engine spawns a reviewer, which is what forced a runner, a readiness probe and a prompt channel into a repo-agnostic core. Here Batten refuses, the refusal names what the agent owes, the agent dispatches with its own harness, and
[[mint]]writes the receipt from the call the boundary was already mediating. Nothing about that harness reachescrates/batten:tool,selects_atandselectsare stringsbatten.tomlsupplies and the engine matches.This is not CLOUD-1265's refuted producer-writes-outside shape.
tool-verdict's store went measured dead because somebody had to remember to run the producer; nobody has to remember here, because the hook sees the call.MintKey::Delta+key_basekey a receipt bygit::branch_patch_id— a wrapper over the existingcumulative_patch_id, not a second computation, so the same change means here exactly whatlandingalready means by it. A rebase does not re-owe the review; any commit moving code does.selects_at/selectsnarrow a row to one call. Every skill this harness dispatches arrives under one tool name, sotoolalone discriminates nothing. The selector reads the tool input,requiresreads the result.base-deltagainspatch-id, so a module can name the change without re-deriving an identity from the path lists.ReceiptKey::Deltais the read side, so areceiptrule can be keyed the same way the mint files.ready-needs-reviewis that row.The gate was run over itself seven times, and that is the finding
/code-reviewwas dispatched over this branch — the obligation the gate imposes — repeatedly, because each fix moved the patch identity and re-owed one. Six rounds found real defects; the seventh came back clean. Four from round one would have shipped a broken gate:lookedguardedis_object(input.tree.minted), butminted::fieldsleaves a declared id ABSENT when it cannot list the store — so an unlistable store is an empty map,is_object({})holds, and every fresh clone and every CI runner would have been refused, precisely the arm the METADATA promises is silent.git::uncommitteddoes not skip a GITLINK, so the uninitialisedtests/batssubmodule counts as changed forever:Ok(0)could never hold and the receipt could never be written. Withdrawn rather than repaired in place — it was also a second authority over a questiontree-cleanowns. The primitive's own defect is CLOUD-1500.delta_sourcesdoes not narrow what a module sees.rules.rsbuilds ONEbase_deltafrom the union of every row's globs, and six rows here declare["**"].no-identity-pricednamed a conjunct another already excluded, so it would have survived the sweep.And the last round found the one that mattered most:
delta := input.tree["base-delta"]was dereferenced with nois_objectguard while its siblinglooked_atcarried one.rules.rsprojectsnullwhen the base does not resolve, indexingnullis a hard Rego fault, and a fault takes the whole bundle down — so on any fresh clone with noorigin/mainthe gate would have been off, silently, along with every other predicate in it.And then the loop itself was the defect (CLOUD-1547)
Seven dispatches in one session is not a gate working, it is a gate priced at the wrong point.
code-review-dispatchedwas a tree rule, sobatten checkdecided it underverify— which is what an agent runs repeatedly while iterating. Every fix commit moved the identity and owed another review, over bytes the deterministic gates had not judged yet.The keying was right; the enforcement point was wrong. So the module, its tier and its
MUTANT_GATESentry are deleted,patch read neveris withdrawn naming the successor, and the question is asked once at the event that spends CI minutes:A rebase-only lap resolves the same identity and buys nothing. A content change files the next review under a name the row does not look up.
Two things this does NOT do, in
batten.tomlrather than discovered laterThe freeze invariant does not ship. A
push-needs-reviewrow is what would keep the readied head from changing under the ready gate — pricing to a transition is only sound if the head is frozen. It was written and measured, and it is too blunt to commit: areceiptrow selects on a command pattern, so it fires on every push. Measured over the suite, it deniedforced_push.rs's benign fixture push, and 15 test files drive a push unrelated to review. What it wants to refuse is a push to a branch whose PR is already ready, andforge.rscarries no draft field at all, so that narrowing is unsayable today. That is CLOUD-1446's gap and it closes in CLOUD-1548, where a declared loop can name the ready state. The tier keeps a push case over its own config, so the keying is shown not to be pattern-specific — but this repository is not frozen against a mid-ready push, and the header says so.Retiring the module widens the gate. It gated on
code-changedintersected with the reviewable prefixes, so a prose-only branch owed nothing. Areceiptrow has no such column, so a documentation-only branch now buys one review before readying. Deliberate and cheap at one dispatch per branch, but a widening rather than an oversight.Other gaps, named rather than papered over
The receipt attests the DISPATCH, not the completion. Measured on the live host: a skill arrives as
tool_name: "Skill"withtool_input: {"skill": "<name>"}, and its result is the bare stringLaunching skill: <name>. There is no field to require and nothing that says the agent then did what the skill told it.CI is honestly silent, not falsely green. The receipt store dies with the container, so until CLOUD-877 gives it a portable form this gate speaks where
landruns and abstains on a fresh checkout.Verification
Measured end to end on this branch, not asserted:
mise run test:cargo— 4881/4881 passed, 0 failed.crates/batten/tests/it/review_receipt_delta.rsover the compiled binary, driving realadjudicatecalls. The rebase and content-change cases are an anti-vacuity pair in the strict sense: aheadkeying fails the first and abranchkeying fails the second, so a suite carrying only one is silently satisfied by the wrong column.mise run policy-test— 59 bundles, 758 passed, 0 failed.batten ready lint --issue CLOUD-1547— green.key_baseunclassified and the schema test caught the generated artifacts going stale; both are gates doing their job, and neither was hand-edited around.🤖 Generated with Claude Code
https://claude.ai/code/session_015H3zMrSGxu4K3fMZu4KFX1