fix(hook): deliver the class definition on a first sighting - #906
Draft
wenzowski wants to merge 1 commit into
Draft
fix(hook): deliver the class definition on a first sighting#906wenzowski wants to merge 1 commit into
wenzowski wants to merge 1 commit into
Conversation
|
Warning Review limit reachedNext included review available in 32 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: Advanced Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (23)
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 |
wenzowski
force-pushed
the
claude/cloud-1637-first-sighting-renders-the-definition
branch
6 times, most recently
from
September 8, 2026 05:55
f44b976 to
9b6f605
Compare
A three-word class token is a pointer to a definition, and the definition
never arrived. `deny_text` filled its route list from `command_routes`, so a
class whose routes are all `document` or `issue` resolved to an empty list,
took the empty-routes early return, and emitted the same bare line on the
FIRST sighting as on a repeat. The gloss rendered on no arm at all.
Counted over batten.toml and verdict.rs: 112 of 162 consumer classes and 32
of 39 vendored ones declare no `command` route, so 144 of 201 classes were
bare on every firing.
The first-sighting arm now renders
{token} {pointers} {rule-id} — {gloss}; {routes}
and the repeat arm renders `{token} {pointers} {rule-id}`, so the repeat is a
byte prefix of the first sighting. Routes are every non-override route
rendered by kind — run/read/see — through an exhaustive match over RouteKind
with no wildcard arm, so a kind added later is a compile error rather than a
route silently dropped.
The rule id is on both arms. 66 of this config's 128 rows declare no class of
their own and raise their kind's native one, so it is the only discriminator
for half the population, not a tie-break for a rare collision.
A consumer row's `reason` no longer reaches the line as `Fix::Run`. It is
prose written for a reader with no budget pressure — one is ~700 characters
and ends by naming a different rule — and it has a destination, which this
change builds: `batten policy rule <id>`. CLOUD-1286 said that verb was where
the remedy went; it had never been written, so for every row whose class
gloss is generic the specific remedy was unreachable from the refusal naming
it. The projection it prints is `explain`'s existing rule fallback, reused
rather than re-rendered.
Sightings are keyed on the rule AND the class. Each name alone is wrong in
one direction and both were measured: under a class key the first of
fourteen `shape` rows consumed the sighting for all of them, and under a rule
key firing `verdict-not-discarded`'s three classes in order left the second
and third compact on their own first firing.
The undeclared arm is bounded too. It returned before any ceiling was
consulted and repeated its long form identically forever; it now sheds the
hatch sentence, which is the constant part, and never sheds reason-or-fix.
`[refusal] first_sighting_max_tokens` bounds the long arm, read off a
measurement rather than chosen; `refusal::validate` refuses one at or below
its sibling, since the first sighting is the repeat plus a gloss and a route.
Over budget, routes drop from the end and the gloss never does; when the line
is already irreducible it is emitted, because no truncator exists and half a
command is not a way out.
GLOSS_MAX's comment now records that 120 is chosen rather than measured, the
way [hook_output] records the same about its own figure.
Closes CLOUD-1637
BREAKING CHANGE: `refusal::Ceiling` gains a public `first_sighting_max_tokens`
field, so a downstream constructing it with a struct literal no longer compiles
(`constructible_struct_adds_field`). The field is `Option`, defaults to absent,
and absent means unbounded — so no CONFIG breaks and no behaviour changes for a
consumer who does not declare it. Only the Rust struct literal does. Declared
rather than worked around: the field has to be public for `[refusal]` to
deserialize into it and for the `trust.rs` ratchet to read it, and
`#[non_exhaustive]` would be the larger break.
Admits: b532fd94f6d591d25b82340517d05975a437c57df7591a4da40b3b8b96d16773
Admits-rule: protected-mutation
Admits-verdict: path write refused
Admits-subject: batten.toml
Admits-anchor: call:7fe35efd0b28cf086de2086028764a0a8eb7cf9b
Admits-epoch: f94fe13bc60f874e757d2545b7b1e8f283572bc344979bbacf9a278d9e12c3d4
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: CLOUD-1637 cannot land. The row requires a consumer-declared `[refusal] first_sighting_max_tokens` bounding the first-sighting arm; without it the arm that carries the class definition is bounded by nothing, which is half the change (non-negotiable rule 2) — the engine would ship a ceiling nothing declares and 144 of 201 classes stay bare.
Admits-answer-precondition: The change is two new keys in the [refusal] table itself — `first_sighting_max_tokens` and its provenance comment. No surface this class names can express an edit to the policy authority: `config read first` reads batten.toml and `patch run first` restores it, and neither writes. The write lands as a reviewed diff in the PR closing CLOUD-1637, which is the remedy the [[redirect]] row for batten.toml names verbatim ("change it in a pull request ... `mise run config-lint` is what checks the edit before it lands"), and config-lint runs in verify before it lands.
Admits-answer-rejected-route: `config read first` (document, batten.toml). Rejected because it is a READ of the authority and the work is a write to it — following it tells me what the table currently declares, which I have already read, and leaves the new key undeclared. `patch run first` (command, git restore) is rejected for the stronger reason that it undoes the edit rather than making it.
wenzowski
force-pushed
the
claude/cloud-1637-first-sighting-renders-the-definition
branch
from
September 8, 2026 06:35
9b6f605 to
f82caf5
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-1637
A three-word class token is a pointer to a definition, and the definition never arrived.
deny_textfilled its route list fromcommand_routes, so a class whose routes are alldocumentorissueresolved to an empty list, took the empty-routes early return, and emitted the same bare line on the first sighting as on a repeat. The gloss rendered on no arm at all.Counted over
batten.tomlandverdict.rs: 112 of 162 consumer classes and 32 of 39 vendored ones declare nocommandroute — 144 of 201 classes bare on every firing.The two arms
{token} {pointers} {rule-id} — {gloss}; {routes}{token} {pointers} {rule-id}Measured in a fixture whose sightings store started empty:
$GIT_DIR/batten-sightings/<digest>is present between the two. That is the row's acceptance clause, with the amendment's two corrections applied: the program ishead(notgrep), and the route target isrules/scanning.md(not the.claude/stub, which five of six harnesses cannot read).Routes are every non-override route rendered by kind —
run/read/see— through an exhaustive match overRouteKindwith no wildcard arm, so CLOUD-1639's new variants will be a compile error here rather than a route silently omitted.Two decisions that depart from the Ready block
1. The sighting key is the pair, not the rule id. The second amendment prescribes keying on the rule id, having found that a class key let the first of fourteen
shaperows consume the sighting for all of them. The rule id alone is wrong the other way, and it was measured:verdict-not-discardedis one row raisingverdict read dropped,verdict carry otherandturn watch dropped, and firing the three in order left the second and third compact on their own first firing — the same silent withholding, one axis over. The key is now(rule, class), which is what the line actually delivers. Worth an amendment on the row so the single key is not reinstated as a simplification.2.
batten policy rule <id>did not exist, and this builds it. CLOUD-1286 moved a row'sreasonoff the hot path saying that verb was where it went; it had never been written. So for every row whose class gloss is generic the specific remedy was unreachable from the refusal naming it —call name refusedsays only that the call matches a shape the config refuses, while "read the row throughbatten mcp call Linear get_issue" isno-raw-issue-read's ownreason. Non-negotiable rule 2: a rule ships with its mechanism. It is a thin wrapper overexplain's existing rule fallback, not a second renderer.This is why the diff touches
surface.rs— one additiveCommandDecland oneFlagDecl. Flagging it because a sibling session owns that file.Also in scope
Fix::Runis a dedup key only, never rendered. A consumerreasonis prose written for a reader with no budget pressure;an-update-owes-a-recent-read's is ~700 characters and ends by naming a different rule, which is what CLOUD-1286 removed the prefix to prevent. The doc prose athook.rs:4403-4423that argued the opposite is rewritten rather than left contradicting the code.[refusal] first_sighting_max_tokens = 64, read off a measurement (widest line the tree emits today is 36 estimated tokens) plus the declared maxima (a 120-char gloss is ~30 on its own).refusal::validaterefuses one at or belowmax_tokens, since the first sighting is the repeat plus a gloss and a route. Thetrust.rsratchet gained the second comparison so an override cannot raise it unreported.GLOSS_MAX's comment now records that 120 is chosen, not measured, the way[hook_output]records the same about its own figure. CLOUD-1117 is the trial that would settle it.[epoch]: floor not raised, and why.min_batten_versionis compared against the running build, which here is built from this tree, so naming the next release makes the repo refuse its own config for the life of the PR.deny_unknown_fieldsalready delivers what[epoch]wants.lib.rs(a second///started beneath it, so it documentedexplain_redirectstwo functions away) is reattached toexplain_rule.Verification
refusal_ceiling.rs— 9 passed, both arms each against its own ceiling, plus the completeness arm (no declared class would render a first sighting with no route), the shared-class two-row case, and the store-written case.//MUTANT document-route-droppedis anchored onverdict::render_route, where the kind mapping lives, with//MUTANT-SUITE refusal_ceiling.rs.Full
mise run verifyis the gate on this branch.Note on attribution
This repo's
[attribution] trailer_denyrefuses^Co-Authored-By:.*(Claude|Anthropic),^Claude-Session:andclaude\.ai/code, andbody_denyrefuses🤖 Generated with. AGENTS.md non-negotiable rule 8 makes that config outrank the harness's identity request, so neither the commit nor this body carries them.The
batten.tomlwrite went through the declared override route; the admission and its three answers are in the commit'sAdmits:block.Generated by Claude Code