Skip to content

fix(admission): a mint addressing no finding of a rule that fired refuses - #969

Merged
wenzowski merged 1 commit into
mainfrom
claude/eloquent-babbage-mbto6k
Sep 17, 2026
Merged

wenzowski merged 1 commit into
mainfrom
claude/eloquent-babbage-mbto6k

Conversation

@wenzowski

@wenzowski wenzowski commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

What

admission_anchor's zero arm bound call:<head> whenever (rule, subject)
matched no finding. apply_admissions only ever looks up a Finding anchor, so
a tree-scope override was articulated, spent, handed back a well-formed
Admits: block — and suppressed nothing. The ambiguity arm one line down
refuses precisely to avoid "an override that appears to work"; the zero arm
walked into it silently.

Measured 2026-09-06 on the CLOUD-1547 branch: nine admissions issued and spent,
eight findings unmoved.

The open question, settled first

The row required establishing why the match returned zero before touching the
fallback. It is not write ordering: admission_anchor never reads the findings
store, it runs the rule live. It was rule selection--rule carries a
predicate id and declared.id == rule selected nothing — which the
policy::publishers_of widening has since fixed. What survives is the silence,
plus a subject spelling nothing tells the caller: a finding's pointer is one
subject, while a refusal line renders all of them joined by a space.

How

Zero splits on whether the rule produced anything here.

  • Nothing produced → falls back to the HEAD exactly as every binding did. That
    is every mediated refusal, every engine-side name no bundle publishes, and
    every delta-scoped row over a base that will not resolve. Nothing that worked
    stops working.
  • The rule fired and the subject addressed none of it → refuses, naming the
    subjects the scan did see, so the caller can read the right one off the
    refusal (CLOUD-122).

Keyed on what the scan produced rather than on the row's declared scope:
scope is Tree by default and publishers_of selects rows without consulting
scope at all, so a scope-keyed guard would fire by omission and refuse a
delta-scoped row over an empty base — when the break-glass is needed most.

The resolution also moves ahead of the articulation, because it can now refuse
and the three answers are held in memory and never persisted.

Both [[waiver]] rows keyed to this row's removal go with it. The write to the
protected batten.toml they live in is articulated in the commit's Admits:
block — the mechanism this PR fixes, walked on the mediated arm where it already
worked.

Tests

crates/batten/tests/it/admission.rs, over the compiled binary:

  • a_mint_addressing_no_finding_of_a_rule_that_fired_refuses_rather_than_binding_the_call
  • a_mint_for_a_rule_that_produced_no_finding_still_falls_back_to_the_call

Two mutations, neither shadowing the other — if true kills the first, if false kills the second, both verified. an_admission_for_another_subject_admits_nothing
is rewritten over two findings: it used to mint for a subject with no finding at
all and its own comment said it relied on "falling back to a Call anchor the
tree path cannot match", so it was asserting the defect. It now spends against
one of two findings and asserts the other survives, which is the harvesting
property it was written for.

Deliberately not here

Refusal::subject returns None for Count/Artifact subjects while
first_pointer renders them, so refusal.rs's claim that the two make "the same
choice" is false. Its only consumer is admit_mediated — the mediated surface,
which has no finding — so it is not a cause of this row, and changing it would
widen which mediated classes are admissible. Filed as CLOUD-1826.

Closes CLOUD-1551

🤖 Generated with Claude Code

https://claude.ai/code/session_01Qbw3zwPXfwifgfg4ckf9Bm

@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 24 minutes.

Check out review usage here.

View limit details

Limit 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.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 2cb6cd5c-76d0-4d4d-977f-c69fa906c3b2

📥 Commits

Reviewing files that changed from the base of the PR and between 6f998b0 and 7b0e09b.

📒 Files selected for processing (3)
  • batten.toml
  • crates/batten/src/lib.rs
  • crates/batten/tests/it/admission.rs

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…uses

The anchor resolver bound `call:<head>` whenever `(rule, subject)` matched no
stored finding, and `apply_admissions` only ever looks up a `Finding` anchor —
so a tree-scope override was articulated, spent, and queried by nothing. The
ambiguity arm one line down refuses precisely to avoid "an override that appears
to work"; the zero arm walked into it silently. Measured 2026-09-06: nine
admissions issued and spent, eight findings unmoved.

The open question the row required settling first — why the match returned zero
while the findings were live — resolves to rule SELECTION, not write ordering:
`admission_anchor` never reads the findings store, it runs the rule live, and
`declared.id == rule` selected nothing because `--rule` carries a predicate id.
The `policy::publishers_of` widening already fixed that. What survives is the
silence, plus a subject spelling nothing tells the caller.

So zero now splits on whether the rule PRODUCED anything here. Nothing produced
still falls back to the HEAD exactly as every binding did — every mediated
refusal, every engine-side name no bundle publishes, every delta-scoped row over
a base that will not resolve. A rule that fired whose findings the subject
addresses none of refuses, and names the subjects the scan did see, because a
finding's pointer is one subject while a refusal line renders them all.

Keyed on what the scan produced rather than on the row's declared `scope`:
`scope` is `Tree` by default, and `publishers_of` selects rows without
consulting scope at all, so a scope-keyed guard would fire by omission and
refuse a delta-scoped row over an empty base — when the break-glass is needed
most.

The resolution also moves ahead of the articulation, because it can now refuse
and the answers are held in memory and never persisted.

Both `[[waiver]]` rows keyed to this row's removal go with it, and the write to
the protected `batten.toml` they live in is articulated below.

Refs: CLOUD-1551

Admits: 447623761002e664dd553982c5d991ee37dfc4385d7b259d248e2a55003d5f92
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:55ba1908fc6041bb83e4eb51002d6aa048abd2d1
Admits-epoch: 461ff9cd2dcc92a40cf05b40b0f5d8061f8cd6400341dfbc554b55ecebb93be6
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: CLOUD-1551's acceptance requires both waivers removed in the same change; leaving them lands the fix with its own evidence still suppressed
Admits-answer-precondition: the change IS the removal of two [[waiver]] rows, which live in batten.toml and nowhere else, so no narrower surface can express it
Admits-answer-rejected-route: git restore would discard the removal, and config read first points at the very file this change edits

Admits: 4cf1c94ddf6722387227c1e372fc97790e946bc43c515b5668fa85134b494343
Admits-rule: issue file same
Admits-verdict: issue file same
Admits-subject: crates/batten/src/lib.rs
Admits-anchor: finding:b80118c0a8c1a1ae6b5f99d8d1b997c5d84655ce5217cc29d1a5484c28feea23
Admits-epoch: e62b450968208680e4ed7d5cd6b5be263da64f3598c306ced4a0b8d845f3564d
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: this diff does not touch admit_mediated at all; the divergence is a mediated-surface policy choice about which classes become admissible, which CLOUD-1826 says outright must be decided rather than defaulted into, so folding it in would widen a fix into a policy change under cover of a landing
Admits-answer-precondition: CLOUD-1826 DOCUMENTS the boundary of the change being landed: it exists because planning CLOUD-1551 established that the Refusal::subject divergence is NOT this row cause, and it names lib.rs:13845 and lib.rs:18289 as the evidence for that boundary rather than as work deferred out of this diff
Admits-answer-rejected-route: task run first would close a row whose own body defers the decision it exists to frame; task run other would put a closing key in the PR body for work this PR does not implement; task run last cannot apply, since the citation IS the boundary statement and stripping lib.rs from the row would leave it evidence-free
@wenzowski
wenzowski force-pushed the claude/eloquent-babbage-mbto6k branch from 800984e to 7b0e09b Compare September 17, 2026 09:34
@wenzowski
wenzowski marked this pull request as ready for review September 17, 2026 09:34
@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski
wenzowski merged commit 7b0e09b into main Sep 17, 2026
24 checks passed
@wenzowski
wenzowski deleted the claude/eloquent-babbage-mbto6k branch September 17, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant