Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 0 additions & 80 deletions batten.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8059,86 +8059,6 @@ expires = "2026-10-31"
# The expiry is short on purpose. PR #842 carries CLOUD-1387's fix; this should
# be removed by whoever lands it, and the date is what forces the question if
# they do not.
# CLOUD-1547's own row, refused by the gate it is landing beside — and the two
# reasons it cannot be cleared are both mechanism defects rather than facts about
# this branch.
#
# The row is NOT a punt: this PR implements CLOUD-1547 and closes it, so its §1
# names these paths because they are the files this change edits. That is exactly
# what `closes` exempts. The exemption cannot fire here because the `pr-closes`
# record is minted from a `gh pr view --jq .body` call and there is **no `gh` on
# this host** — so `closes` is empty, and empty is not `closes_unreadable` either,
# because nothing ever attempted the call for the blocked store to record.
# CLOUD-1126 named that shape and CLOUD-1481 carries the residual.
#
# AND THE CLASS'S OWN OVERRIDE ROUTE IS SPENT WITHOUT SUPPRESSING. `issue file
# same` declares `path admit first`; nine admissions were issued and spent against
# these subjects and the findings did not move. The consumption is NOT missing —
# `admission::Anchor::Finding` exists for exactly this and `apply_admissions`
# suppresses stored findings by fingerprint. What fails is the ANCHOR RESOLVER:
# `lib.rs:5428` matches stored findings on `(rule, path)`, and on ZERO matches it
# falls back to `Anchor::Call`. Measured here, with the findings live and being
# reported in the same session: `Admits-anchor: call:2dee1726…`, and nothing
# queries a call anchor for a tree finding. The ambiguity arm refuses precisely
# to avoid "an override that appears to work"; the zero arm walks into it.
# CLOUD-1551, which also records why the match returned zero as the open question.
#
# A `[[waiver]]` and never `severity = "allow"`: this names the rule, states why,
# and LAPSES. Short on purpose — it should be removed by whoever lands CLOUD-1551,
# and the date is what forces the question if they do not. UNNARROWED because the
# eight findings are eight paths under one row, and a `path =` would need eight
# rows that all lapse together anyway.
#
# IT NAMES THE PREDICATE, NOT THE RULE, and that is the narrow spelling rather
# than a slip. `waiver::apply` filters FINDINGS and a policy finding carries the
# predicate id, so this suppresses `filed-over-own-diff` and leaves
# `filed-unrefined` — the rule's other predicate — still deciding.
#
# `config-lint` refused exactly this spelling until the same change repaired it:
# `waiver-names-no-rule` read only `config.rules`, so it blessed `filed-here`
# (which suppresses nothing) and refused this one (which does). CLOUD-1553 is
# that inversion; the fix is in `lint.rs` in this commit, because a wrongly
# refusing gate is a defect to repair rather than a ticket to file.
[[waiver]]
rule = "issue file same"
reason = "CLOUD-1547 is implemented and closed by this PR, so `closes` is the exemption that applies; it cannot fire because `pr-closes` is minted from a `gh pr view` call and there is no `gh` on this host (CLOUD-1126, residual on CLOUD-1481). The class's own `path admit first` override route does not consume — eight admissions were spent and the findings did not move, because the anchor resolver falls back to a `call:` anchor on zero stored-finding matches and nothing queries one for a tree finding (CLOUD-1551). Remove this with CLOUD-1551."
expires = "2026-10-11"

# THE SIBLING ARM OF THE SAME DEFECT, and the arm above was scoped to half of it.
# `filed-over-own-diff` and `filed-and-left-open` are two predicates in one module
# (`policy/filed-here.rego`) and CLOUD-1551 defeats the override route for both;
# only the first was waived, because only the first had fired yet.
#
# MEASURED HERE, and it is a sharper mechanism than "the override suppresses
# nothing". It suppresses until HEAD moves. Two consecutive laps of `mise run
# land` over one tree, one finding, one spent admission (`06b322be`, anchored
# `call:550939ab`): lap 1 replayed nothing and reported 0; lap 2 replayed onto a
# trunk that had moved and reported 1. The rebase rewrote `550939ab` as
# `7b705e64` — `git cat-file` still finds the object and
# `git merge-base --is-ancestor` exits 1 — so the anchor addressed a commit the
# branch no longer contains.
#
# WHICH MAKES THE NARROW ROUTE STRUCTURALLY UNAVAILABLE RATHER THAN FRAGILE, and
# that is why this is a waiver rather than another admission. `override request`
# anchors at the current HEAD, and the commit carrying the resulting `Admits:`
# block BECOMES a new HEAD, so a re-mint is stale the moment it lands; a commit
# on top has the same shape. And this waiver cannot be narrowed by `path` either:
# `Waiver::path` is a path glob and this rule's subject is `{"artifact": id}` with
# no path in it. Rule-wide is the only shape available, which is exactly the cost
# CLOUD-1551 imposes and exactly why it should be paid down rather than lived in.
#
# The articulation the admission was meant to carry is not lost — it is in
# `13c0dd5e`'s message, hash-bound, where a reviewer reads it. What CLOUD-1635 is
# and why it is deferred: `mcp-allow-check.sh` is a governed shell gate, the fix
# was attempted as an edit, `shell-rule-retired` refused it correctly, and the row
# is re-scoped to the retirement `rules/toolchain.md` says it always was.
#
# Remove this with CLOUD-1551, alongside its sibling above.
[[waiver]]
rule = "issue file held"
reason = "CLOUD-1551 defeats this rule's override route the same way it defeats `filed-over-own-diff` above, and measurably worse: an admission binds `call:<head>`, so the replay every landing lap performs when trunk moves orphans the anchor and the spent admission stops suppressing. Measured on two consecutive laps of one tree — lap 1 replayed nothing and reported 0, lap 2 replayed and reported 1, with `06b322be` spent throughout. Re-minting cannot escape it, since the commit carrying the `Admits:` block becomes the new HEAD; and `Waiver::path` cannot narrow this rule, whose subject is an artifact id rather than a path. The deferral's articulation is hash-bound in `13c0dd5e` regardless. Remove this with CLOUD-1551."
expires = "2026-10-11"

[[waiver]]
rule = "claim mint absent"
reason = "CLOUD-1387: `captured::reduce` selects the first capture whose bytes MENTION the key rather than the one it is the subject of, so a response quoting the row shadows its real payload and `present` answers false over a row that is on a project. Fix is open in PR #842; remove this waiver with it."
Expand Down
102 changes: 87 additions & 15 deletions crates/batten/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6200,12 +6200,22 @@ fn run_override_spend(
/// `a_mint_for_a_mediated_rule_anchors_the_call_not_a_tree_finding` pins it,
/// because the property lives two files away from the code that relies on it.
///
/// **Falls back to [`admission::Anchor::Call`] rather than failing**, and the
/// fallback is never weaker than what shipped before: a situation with no tree
/// finding behind it — a mediated refusal, or a protocol-level mint — binds the
/// HEAD exactly as every binding did, so nothing that worked stops working. What
/// it cannot do is suppress a tree finding, because `apply_admissions` builds a
/// `Finding` anchor and the two tokens are tagged apart.
/// **Falls back to [`admission::Anchor::Call`] rather than failing, but ONLY
/// where the rule produced no finding at all** (CLOUD-1551). That fallback is
/// never weaker than what shipped before: a situation with no tree finding
/// behind it — a mediated refusal, a protocol-level mint, a delta-scoped row
/// whose base will not resolve — binds the HEAD exactly as every binding did, so
/// nothing that worked stops working. What it cannot do is suppress a tree
/// finding, because `apply_admissions` builds a `Finding` anchor and the two
/// tokens are tagged apart.
///
/// **A rule that FIRED and a subject that addressed none of it refuses**, on the
/// ambiguity arm's reasoning below and for the identical reason: the caller
/// would answer the questions, spend the address, and suppress nothing. The
/// refusal names the subjects the scan did see, because a mismatch here is
/// usually a spelling — a finding's pointer is the first path-bearing subject or
/// the first subject rendered (CLOUD-1051), while a refusal line renders all of
/// them joined by a space.
///
/// **Ambiguity REFUSES rather than falling back**, and the asymmetry with the
/// absent case is the point. Two findings sharing `(rule, path)` mean the pair
Expand Down Expand Up @@ -6328,9 +6338,11 @@ fn admission_anchor(
// answer for an engine-side rule name, which is a legitimate thing to
// mint against — `protected-mutation` has a real override route.
//
// Silent, deliberately: reporting the rule as unknown here would decide
// CLOUD-1551's open question about an anchor that binds `call:` when
// nothing matches, and this row must not settle that by accident.
// Silent, and CLOUD-1551 settled it that way rather than leaving it
// open: a rule nothing publishes produced no finding, which is the same
// state the zero arm below falls back on. The refusal there is reserved
// for a rule that FIRED and a subject that addressed none of it, and
// this return cannot be that.
if publishers.is_empty() {
return head();
}
Expand Down Expand Up @@ -6383,11 +6395,59 @@ fn admission_anchor(
matched.dedup();
match matched.len() {
1 => Ok(admission::Anchor::Finding(matched.remove(0))),
// NOTHING TO ANCHOR falls back, and the fallback is never weaker than
// what shipped: a situation with no tree finding behind it — a mediated
// refusal, or a protocol-level mint — binds the HEAD exactly as every
// binding did.
0 => head(),
// ZERO SPLITS IN TWO, AND THE SPLIT IS CLOUD-1551 (with CLOUD-1374 and
// CLOUD-1378 folded into it). One arm is the honest fallback this
// always was; the other is the ambiguity arm's defect wearing a smaller
// count, and it shipped silent.
//
// THE DISCRIMINATOR IS WHAT THE RULE PRODUCED, NEVER THE ROW'S DECLARED
// SCOPE. `scope` is `RuleScope::Tree` by default, so a row that omits
// the key reads as tree-scoped; and the widening above selects rows
// through `policy::publishers_of`, which reads a bundle's declared set
// and never consults scope at all. "A tree-scoped row was selected"
// therefore fires by omission, and it would refuse a delta-scoped row
// over an empty base — precisely when a caller needs the break-glass.
// What the two arms differ on is whether there was a finding to
// address, and this scan already holds that fact.
0 => {
let mut named: Vec<&str> = scan
.findings
.iter()
.filter(|finding| finding.rule == rule)
.map(|finding| finding.path.as_str())
.collect();
named.sort_unstable();
named.dedup();
if named.is_empty() {
// NOTHING THIS RULE PRODUCED, so there is no tree finding to
// address and the fallback is never weaker than what shipped: a
// situation with no tree finding behind it — a mediated
// refusal, a protocol-level mint, a row whose base would not
// resolve — binds the HEAD exactly as every binding did.
return head();
}
// THE RULE FIRED AND THE SUBJECT ADDRESSED NONE OF IT, which is the
// ambiguity arm's failure mode reached by a different count.
// `apply_admissions` looks up a `Finding` anchor, so a `Call` one
// minted for a tree finding is queried by nothing: the caller
// answers the questions, spends the address, and suppresses
// nothing. Measured 2026-09-06 on the CLOUD-1547 branch — nine
// admissions issued and spent, eight findings unmoved.
//
// IT NAMES WHAT THE SCAN DID SEE, because that is the fix
// (CLOUD-122) and the cause is a spelling more often than not: a
// finding's pointer is the first path-bearing subject, or the first
// subject RENDERED when it carries none (CLOUD-1051), while a
// refusal line renders every subject joined by a space. A caller
// copying that line mints a subject no finding carries. Pointers
// rather than content (rule 4) — the same strings `check` prints.
Err(UsageError::raise(format!(
"no finding for rule `{rule}` names subject `{subject}`, so an admission bound \
to it would suppress nothing; that rule names {} subject(s) here: {}",
named.len(),
named.join(", ")
)))
}
// AMBIGUITY REFUSES, because falling back here is a silent no-op. The
// caller would be handed an address, spend it, and suppress nothing:
// `apply_admissions` looks up a `Finding` anchor, so a `Call` one stored
Expand Down Expand Up @@ -6462,6 +6522,19 @@ fn run_override_request(
)));
};

// BEFORE THE ARTICULATION IS ASKED FOR, never after it has been written
// (CLOUD-1551). Resolving the anchor can now REFUSE — a rule that fired
// whose findings this subject addresses none of — and the answers are held
// in memory and never persisted, so refusing below would charge the caller
// three written paragraphs and throw them away. Where a harness wires this
// verb as a declared route and feeds the answers from a file, that is a
// loop an agent thrashes in rather than a one-off cost. An unbindable mint
// now refuses before anything is composed.
//
// The scan this pays for is the narrowed one (CLOUD-1571), so the
// questions-only path costs one rule over one subject rather than the tree.
let anchor = admission_anchor(root, &config, rule, subject)?;

let mut raw = String::new();
if std::io::stdin().read_to_string(&mut raw).is_err() {
raw.clear();
Expand All @@ -6480,7 +6553,6 @@ fn run_override_request(
return Ok(ExitCode::Usage);
}

let anchor = admission_anchor(root, &config, rule, subject)?;
// The SAME epoch `config epoch` reports, resolved through the same function,
// so an admission cannot bind a generation the caller could not look up.
let (epoch, _) = epoch::describe(root, None)?;
Expand Down
Loading
Loading