Repository navigation
refactor(ci): retire config-lint into the verb it already called, and fix the admission defect that forced it - #809
Conversation
CLOUD-841 `config-lint` reads a receipt that names no weakening as "no receipt", so a commit trailer alone admits a weakening the groom never saw
Why CLOUD-789 gave
"The trailer alone admits nothing" is not what the code does. so a receipt that exists and names no weakening yields the same empty string as no receipt at all. The admission arm then keys on that: The message says "no claim receipt here" — CI's honest state, where none was ever written. On a developer branch the receipt is right there under Measured 2026-08-21 on Adding a The last line is the defect in one sentence: it says groomed decision about a decision the groom never saw. The weakening in that instance was real and deliberate and is recorded on CLOUD-807 — the point here is that nothing forced it to be, and the local half that the header calls "the strong half" did not run. Why this matters more than a wording fix. CLOUD-789's whole argument is that the strong check runs where the receipt is — The three states are two in the code. Note Prior art on the same shape. This is CLOUD-615's class exactly: a fallback that reads "could not look" for "looked and found nothing", where the lenient branch is the one that fires. CLOUD-820 is removing the same shape from Refinement — Ready (three states, and only absence falls back) Refinement gate: Definition of Ready & Done. This body carries only specializations.
Acceptance
CLOUD-1162 Retire `config-lint` (23.5s) and `board-diff-overlap` (24.1s) — both fact preconditions have LANDED; `config-lint` is clear, `board-diff-overlap` needs a path extractor `captured` cannot give
Why Units 15 and 16 of the 83-unit partition. Two separate deltas — this row carries both because each is small and neither glues to the other. 38.1s combined, 3.1% of the 1244.6s serial suite.
Neither has an inbound reference from any governed file. Two deleted paths each, two ledger arms each, zero drag-in. CORRECTION (2026-08-29):
|
| unit | home | why |
|---|---|---|
15 config-lint |
existing verb — config lint today, rule id check config after CLOUD-1193 |
measured against the emitted surface: config lint already ships, effect read. The shell is a wrapper that spawns it and adjudicates its output, so the successor is not a new verb at all — it is the wrapper collapsing into the verb it already calls, plus a consumer module for the Weakens:-trailer half once CLOUD-1168 lands. That also is the 21.2s fix: the cost is cargo run start-up paid once per case. |
16 board-diff-overlap |
consumer module — policy/*.rego |
the whole predicate is written in a tracker's vocabulary — issue keys, issue bodies, a board payload — which non-negotiable rule 1 keeps out of crates/batten and out of a preset alike. The producer it waits on (CLOUD-1154) is substrate; this consumer is not. |
Refinement — Ready (two deltas, two different missing fact families; neither lands today)
Refinement gate: Definition of Ready & Done. This body carries only specializations.
- Authority boundary (§1). Four deleted paths across two PRs —
config-lint.sh+ its suite,board-diff-overlap.sh+ its suite — and **four **// carried:ledger arms. Successors land at the homes named above —config lint's existing verb, and apolicy/*.regoconsumer module — never as a newcrates/battenverb. Both$MUTANT_GATESentries and all 4#MUTANTrows move with them.mise.tomlandhk.pklare ungoverned and repoint freely. - Computable predicate (§2) — the DECISION is conserved, never the defect (CLOUD-1176). Where either program's shell carries a tracked defect, the successor implements the corrected decision and that defect's own row records the change; a retirement that launders a known defect forward into Rust is the failure this campaign exists to avoid, not evidence of fidelity.
config-lint: doesbatten config lintreport a weakening the groom never saw, over the config at a named base ref.board-diff-overlap: does a row this branch spun off name code the branch was holding open — the intersection of the branch's changed set with the paths a filed issue names. - Deliberately not in scope (§2). Changing what
config lintconsiders a weakening. Regeneratingtracked-at-filing.txt— it is a frozen replay and tracking the present would make it measure nothing. - **Effect (§3). **
readfor both. Neither writes. - Output and exit (§5). Pointer-only:
path:lineand the rule id forconfig-lint; a count and a path forboard-diff-overlap— never an issue body, which is a consumer's prose. Exit follows the0/1/2/3table; a could-not-look reading (no base ref, no payload) is3, never a false2. - **Commit / bump (§6). **
refactor(ci)— no bump, per PR. Below0.1.0every release-worthy type collapses to a patch, butrefactoris not one: it releases nothing at any version. CLOUD-595's correction. - Test obligation (§7). Over the compiled binary in
crates/batten/tests/; **no **.batsfile is added or edited (V-SHELL-RULE-ADDEDrefuses one atdeny). **One **// carried:arm per deleted path — four paths, four arms (CLOUD-908). Shown able to fail per CLOUD-418: forconfig-lint, a config carrying an ungroomed weakening is reported and a clean one passes; forboard-diff-overlap, the three-row REPLAY case must still report1 crates/batten/src/git.rsfor CLOUD-739 and CLOUD-740 and1 mise-tasks/macos-link-checkfor CLOUD-737, and the control — the same bodies against a branch holding nothing open must report nothing, without which the first is satisfied by a gate that fires on everything. Mutated: 1 + 3 rows re-homed,mutant-censusgreen. - **Blockers (§8). **
blockedByCLOUD-1154 — forboard-diff-overlaponly.blockedByCLOUD-1168 — forconfig-lintonly, per the correction above: itsWeakens:trailer read has no fact behind it.relatedToCLOUD-1151 (the wave owner), CLOUD-1140 (suite cost), CLOUD-908, CLOUD-418.
Acceptance
- Four paths deleted across two PRs, four ledger arms, no governed file edited.
config-lint's successor makes **no **cargo runper case — the 21.2s is the point, and a port that keeps spawning has missed it.config-lint's successor reads theWeakens:trailer from CLOUD-1168's projection, never by re-deriving a trailer scan.board-diff-overlap's frozen fixture is carried byte-identical.- Both anti-vacuity controls observed.
mutant-censusgreen; 4 mutations honoured at their new homes.
Units 15 and 16 of 83.
CLOUD-843 The retirement campaign has no row: eight capability rows cite "so the 79 gates have somewhere to migrate onto", and nothing owns the migration — measured, the bash grew today
Why
Eight rows in this campaign are justified by a migration nobody owns. CLOUD-833's title is literally "so none of the 79 gate-described mise-tasks has a surface to migrate onto"; CLOUD-832 exists so a bundle can carry 79 predicates; CLOUD-807 built the permit that lets a suite die with its subject. CLOUD-312 owns the 11 hook bodies and only those.
The 82 gate-described mise-tasks/ programs have no owning row. Searched 2026-08-21; the closest hits are all capability rows citing the campaign as their justification. This is DoR §2's own failure mode one level up: the backlog grew the engine, and the thing the engine was grown for was never filed.
Measured on main @ 12cca46 (v0.0.99), against the same census that morning
| Surface | Morning | Now | Δ |
|---|---|---|---|
mise-tasks/ files |
133 | 136 | +3 |
mise-tasks/ lines |
27,799 | 28,590 | +791 |
| gate-described tasks | 79 | 82 | +3 |
tests/*.bats lines |
34,654 | 36,052 | +1,398 |
| bats cases | 2,485 | 2,548 | +63 |
| gate tasks migrated | — | 0 | — |
kind = "policy" rows |
0 | 1 — and it is trunk-based-preset, a vendored preset, not a migrated gate |
One bash file was retired all day: .claude/hooks/batten-hook.sh, 65 lines (CLOUD-824). Against +791 lines added. The campaign to delete bash added bash, and nothing on the board was positioned to notice, because no row carries the number.
The classification, by what each task invokes
Not by substring. A string scan over these files put ci-local-parity and pipefail-grep-check in the forge bucket — the same instrument that counted 14 spawn sites where name resolution found 9 (CLOUD-743). Classified instead by the external programs each task actually invokes in command position:
| bucket | count | can it migrate? |
|---|---|---|
tree — no git, no forge, no build |
22 | yes, on CLOUD-833's tree surface, today |
| git facts | 50 | needs a git fact on the tree document; scope unproven |
| build/bench | 3 | probably never — they run cargo/hyperfine |
| forge | 7 | last, and some may legitimately stay |
The 50 is the number that matters and it was not expected: this is a git policy engine, so most gates read git. The "79 gates become Rego rows" framing every capability row inherited is unproven for 60 of the 82. That is this row's first deliverable to settle, not assume.
The pilot
mise-pin-agreement — "every tool version named in .mcp.json agrees with mise.toml's pin — the second place a pin is written cannot drift from the first".
Chosen on data: 107 lines of bash, 10 cases, 108 lines of bats — the smallest of the structured-document gates. Both inputs are formats Fact::Document already parses (CLOUD-772: TOML/YAML/JSON/JSON5), and the predicate is agreement between two parsed trees, which is what Rego is for. No git, no spawn, no stdin.
It also carries no #MUTANT directive, so it is one of CLOUD-480's undeclared gates. Migrating it should add a declared mutation — coverage improves as a side effect rather than degrading.
Rejected as pilot: no-docs-tree, which an earlier plan named. It is not a mise-tasks/ program at all — it is a batten.toml rule in the hk gate. Recorded because the name was carried in prose across three documents before anyone checked the tree.
Sequencing, and the one trap
- Settle the 50. Determine what a git-fact gate needs on the tree surface. Until then "79 gates migrate" is an estimate, not a plan.
- CLOUD-835 lands — the destination for the 1,570 bats cases.
- The pilot, end to end, one gate: Rego module,
test_rules, delete the task and its suite, MUTANT declared. The pilot converts the estimate into a measured cost per gate. Do not batch before it. - Waves by bucket, cheapest first.
The trap, and it is new as of today. CLOUD-807 landed retires_with, so a suite may now be deleted exactly when its declared subject dies. That is correct and it was the precondition for retiring anything — but it makes migrating without CLOUD-835 worse, not better. The ratchet will admit deleting a suite whose task died, with nothing asserting the Rego that replaced it. The permit made coverage evaporation quiet. 835 is a hard blocker, not a nicety.
Refinement — Ready
Refinement gate: Definition of Ready & Done. This body carries only specializations.
- Source of truth (§1). The tree. The census below is the authority on progress, not a burndown restated anywhere;
batten.tomlowns which gates are policy rows andmise-tasks/owns which are still bash. No second list of what has migrated. - Computable predicate (§2). The census, re-run at every wave boundary:
ls mise-tasks/ | wc -l ; cat mise-tasks/* | wc -l
grep -l '#MISE description="Gate' mise-tasks/* | wc -l
ls tests/*.bats | wc -l ; cat tests/*.bats | wc -l ; grep -h '^@test' tests/*.bats | wc -l
grep -c 'kind = "policy"' batten.toml
A wave that does not move these down has retired nothing, whatever else it landed. That predicate is the whole point of this row existing: today's +791 was invisible because nothing computed it.
- Effect (§3).
read— migration moves predicates between surfaces; no verb is added and no rule kind'sAuthoritychanges. - Generated artifacts (§4).
schema/batten.schema.jsononly if a row key changes; the per-wavebatten.tomlrows are authored.derived-checkandschema-checkgate both. - Output & exit (§5). Unchanged — a migrated gate keeps its exit contract, and a Rego predicate reports pointer-only by construction. A migrated gate's refusal text must still name its remedy (CLOUD-437); a
msgthat lost the remedy in translation is a regression the bats case would not catch. - Commit / bump (§6).
refactorper wave — no bump. The answers are identical by construction; a wave that changes a verdict is not a migration. - Test obligation (§7). Per wave, and the pilot establishes the shape: (a) every case in the retired suite has a
test_rule that fails when the predicate is wrong; (b) the retired task and its suite are both gone, admitted byretires_withbecause the subject died; (c) the gate's declared mutation is caught bymise run mutant— formise-pin-agreementthat is a mutation it does not have today; (d) the census moves down by the retired count, asserted rather than eyeballed. - Blockers (§8).
blockedByCLOUD-835 — see the trap above; without a test destination the permit lets coverage disappear silently.relatedToCLOUD-833 (the surface, landed), CLOUD-832 (predicate ids, landed), CLOUD-807 (the permit, landed), CLOUD-312 (the 11 hook bodies — the other half of the retirement, and not this row), CLOUD-480 (the undeclared gates a migration should shrink), CLOUD-772 (the document substrate the tree bucket consumes), CLOUD-839 (the capability dispatch that bought the machinery).
Acceptance
- The 50 git-fact gates have a stated verdict: migratable on a named fact, or not, with the reason.
mise-pin-agreementis a policy row; its task and suite are deleted;mutantcatches its declared mutation; the census is down by one gate and ~215 lines.- The measured cost per gate from the pilot is recorded here, and the waves are sized from it rather than from the count.
- Every wave re-runs the census and records the delta.
Found while auditing what the CLOUD-839 fleet landed, by asking the question the capability rows never had to answer: how much bash actually went away.
PRESSURE-TESTED 2026-08-21 — wave 1 cannot dispatch yet, and the reason is a run rather than a reading
CLOUD-835 landed (62719ff, v0.0.100), so the blocker in the trap above is cleared and the 1,570 cases have a destination. Before dispatching a wave on that, the path was walked end to end against the release binary in a throwaway git fixture. It does not hold yet.
What the run showed
Two modules in one enabled bundle. One copied verbatim from policy.rs's own module doc; one written against what rules::tree_document actually builds. A stray.o tracked. Each with a test_ rule in the shape the vendored presets use.
$ batten policy test
policy test: 1 bundle(s), 2 passed, 0 failed EXIT: 0
$ batten check
policy/ msrv-must-be-pinned EXIT: 2
The doc-shaped module passes its test and gates nothing. input.tree.tracked is documented at policy.rs:143-147 and never emitted — tree_document builds documents and missing, and nothing else. Rego makes the failure silent: iterating an undefined path yields no violations, so a dead gate and a clean tree are byte-identical. The test_ rule passes because with input as lets the author fabricate the very shape the engine cannot produce.
That is CLOUD-845, and it is a hard blocker on this row rather than tidy-up: every wave-1 agent starts from that doc, and combined with retires_with the failure mode is green tests, silent gate, deleted bash task that used to work. Ten gates migrated that way would show the census going down while enforcing nothing — the exact number this row exists to make honest.
And wave 1 is smaller than the bucket count suggests
The 20-odd tree gates were re-read by what they open, not by what they invoke. Fact::Document parses TOML, YAML, JSON, JSON5 — and Pkl, declarable-never-parsed.
| reads | gates | migratable |
|---|---|---|
| structured config only | 8 | yes |
| markdown | 4 | no |
.bats / .rs / .pkl text |
5 | no |
| no file literals | 3 | partly — needs the tracked list |
That is CLOUD-846. Wave 1 is 8, not 22. The pilot mise-pin-agreement is in the 8 and is unaffected — both its inputs are parsed formats — so the pilot choice above stands.
BUNDLE W0 — the unblocker. Dispatch-ready now.
Both rows are unblocked, both are rules.rs / facts.rs / policy.rs / schema — one file domain, so one agent, one branch, one draft PR, per CLOUD-839's sizing. Nothing else in the campaign can start until it lands.
Superseded 2026-08-21 by the six-bundle dispatch at the foot of this row. W0's chain grew from two rows to five once the acquisition boundary was traced (CLOUD-849/850/851); it is now bundle A there. The prompt below is still accurate for the 845→846 half and is kept because bundle A's prompt builds on it.
| # | Chain | File domain | PR shape |
|---|---|---|---|
| W0 unblock-migration | CLOUD-845 → CLOUD-846 | rules.rs (tree_document), policy.rs (module doc + policy test), facts.rs, git.rs (list_tree), schema/* |
1 PR |
845 first: it fixes the input the doc promises and closes the false-green class. 846 then adds the lines fact on top of a tree_document that is already correct, and its §5 assertion (a finding may see a line, never carry one) is easier to state once 845's input-shape check exists.
W0 unlocks wave 1 at 8 gates. It does not unlock the other 12: those wait on 846's lines fact landing and being demonstrated, which is 846's own acceptance (d).
Dispatch prompt — one paste, self-contained
You are bundle W0 of the CLOUD-843 bash-retirement campaign in the Batten repo. Read
CLOUD-843 first: it carries the census, the bucket classification and the pilot choice.
Nothing else in the campaign can start until your PR lands.
YOUR CHAIN - one branch, one draft PR, landed in this order:
CLOUD-845 -> CLOUD-846
CLOUD-845 first. There is a REPRODUCTION on that row - run it before you change anything,
and keep it as the test. Two modules in one bundle, one copied from policy.rs's module
doc and one written against what rules::tree_document actually builds: `batten policy
test` reports 2 passed, exit 0, while `batten check` reports only one predicate. The
doc-shaped gate is dead and its test is green.
Three parts, and the third is the one worth having:
- Emit `input.tree.tracked`. Do not just delete the doc example - a tracked-path list is
what a whole class of these gates needs. `git::list_tree` already exists at git.rs:784
and CLOUD-833 already uses it for bundle membership under --config-from. Bound it by
declaration the way `documents` is bounded; an ambient walk would make the `read`
classification a lie by degrees.
- Make policy.rs's module doc true, and assert it: every field its examples reference
exists in what tree_document emits. Same shape spawn_census.rs:216 uses against
clippy.toml. This defect is CLOUD-589's class recurring in the file that landed
CLOUD-831, which was filed for exactly it - so an assertion, not a careful edit.
- `batten policy test` refuses a `with input as` naming a key the engine cannot produce,
at exit 1 (config fault, not a policy verdict). This closes the CLASS. Without it every
field added to the input document reopens the same hole. CLOUD-834 is making the
document's keys the Fact variants asserted by exhaustive match - validate against that
same table, do not build a second list.
Then CLOUD-846 on the same branch: a lines fact, `input.tree.lines[<path>]`, so a module
can decide over a .bats or .md file. Lines rather than raw text, and the reason is rule 4
rather than convenience - a module may SEE a line, a finding may never CARRY one. Assert
that; it is the clause with teeth and the one that keeps pointer-only structural. A
declared path the tree lacks is could-not-look, never an empty array. Acceptance (d) is a
demonstration, not a claim: migrate one of the four markdown gates as proof.
CROSS-BUNDLE: you are the only branch in flight on this campaign. CLOUD-834 is In Progress
in the CLOUD-839 fleet and also touches the policy input document - it projects the Fact
variants into it. Coordinate through that row rather than racing it: if 834 lands first,
rebase and validate against the table it built.
WORKFLOW CONTRACT (AGENTS.md is authoritative; this is the summary):
- Claim by hand BEFORE writing code: `mise run claim-check`, and assign yourself. The
automation fires on the PR event, the end of the work, so waiting for it reserves nothing.
- `git fetch origin main`, short-lived branch, never author on main.
- Commit early and often. You are pre-authorized to commit and push without asking.
- Run the full `mise run verify` after EVERY commit. Local execution is free; a CI run is
metered and the landing lease is fleet-wide.
- Open the PR as a DRAFT immediately (`gh pr create --draft`). CI does not run on drafts.
- When the chain is complete: `mise run linear-check`, then `mise run land` backgrounded.
Do NOT ready by hand - land readies after its push. Do NOT wrap land in bespoke retry or
pre-check logic; main advancing under you is that loop working.
- Background anything that can exceed ~2 minutes; a foreground command is killed at ~2 min.
- Move the Linear row as you move the work. Carry the lifecycle to landed-and-verified
without stopping to report and wait.
Wave 1, after W0 lands
Eight structured-config gates, pilot first: mise-pin-agreement end to end — module, test_ rules, task and suite deleted, a #MUTANT directive declared where it has none today. The pilot converts the estimate into a measured cost per gate, and the waves are sized from that number rather than from the count. Do not batch before it.
A second thing the fixture run turned up, recorded here rather than filed because it is a one-line observation and its home is this campaign's tooling: the protected-path gate matches batten.toml by basename, so it refused writes to a fixture's batten.toml in a temp directory outside the repository — and the advertised BATTEN_GH_GUARD_BYPASS=1 did not take as an inline environment assignment. Every wave-1 agent will hand-build such a fixture. Whoever hits it should file it rather than work around it silently.
DISPATCH 2026-08-21 — six bundles, and the one lever that decides wall-clock
Gates per PR, not agents
The fleet-wide landing lease charges per land, not per gate — one branch spends CI at a time, ci p95 ≈ 701s.
| batching | lease acquisitions for 82 gates | pure landing time |
|---|---|---|
| one gate per PR | 82 | ~20 h |
| one wave per PR | 6 | ~1.5 h |
That 13× is the whole answer to "fastest", and every other choice is noise beside it. Migration is embarrassingly parallel per gate, which makes one-agent-per-gate the tempting and slowest schedule. Fan out the authoring, serialize the landing. Past ~8 concurrent PRs each extra worker adds landing time (every land forces every other branch to rebase) without removing work time, so do not dispatch 30.
Two tracks, run concurrently
The objective is 82 gate tasks and 11 hook bodies (CLOUD-312). They share almost nothing.
Track 2 has a free start: run-shape-guard is 630 lines, opens exactly one file (mise.toml), and is otherwise pure string analysis of command — which the envelope already carries. It needs no Document fact, so it is migratable now, before any capability lands. Earliest census movement available, one land. Its last two families need CLOUD-613, which is Backlog with no Ready block.
The bundles
| # | Bundle | Rows, in order | File domain | Why together |
|---|---|---|---|---|
| A | Acquisition — the long pole, gates all of track 1 | CLOUD-849 → 845 → 850 → 846 → 851 | rules.rs, facts.rs, policy.rs, schema/ |
A strict chain on one function; splitting means agents rebasing onto each other's edits to tree_document |
| B | Ready-block gate family | CLOUD-852 → 842 → 595 → 826 → 751 | mise-tasks/ready-lint, tests/ready-lint.bats |
Five rows, one 35-line §6 block. Any split is a guaranteed conflict for zero parallelism |
| C | Board-gate wiring | CLOUD-825 | the seven board gates + their invokers | Disjoint from B; released is fed /dev/null and three gates have no invoker |
| D | Hook surface | CLOUD-461 → 525 | hook.rs, lib.rs, doctor |
The two capabilities gating contract-drift and stop-guard retirement — the hook half |
| E | Envelope content fact | CLOUD-758 | hook.rs, facts.rs |
Prospective Write/Edit content; the hook bodies reading tool_input need it. Folds into D if the fleet is cut to five |
| F | Instruments + base ref | CLOUD-844, CLOUD-720 | .claude/rules/, resolve.rs |
Two small independents |
CLOUD-852 has landed (PR #625, b405ca8) — bundle B starts at 842.
B is the highest-leverage non-obvious bundle. It is not on the objective's critical path, it is on the throughput path: every row of every later wave passes ready-lint, and that gate misread a negation, cannot check the claim it reports checking, refuses a corpus it was changed out from under, and accepts a §7 naming tests that do not exist. 82 migrations run through it is 82 chances to ship a Ready block nobody can trust.
Order
T+0 — five agents. A is 5 deep and lands last; the rest are shallow and clear the lease before A needs it. Agent 5 takes the run-shape-guard partial migration plus C.
T+1 — after A lands. One agent, one PR: the mise-pin-agreement pilot plus all 8 structured-config gates. Not the pilot alone — its purpose is the measured per-gate cost, measured just as well inside a batch of 9, for one lease instead of two.
T+2 — three parallel PRs: the 12 lines-fact gates, the git-fact gates this row's verdict clears, and the remaining hook bodies.
T+3 — the git remainder. ~10 lease acquisitions total.
What gates the schedule, and neither is on the bundle list
- A must land first and nothing parallelises it. Every hour it slips slips all of track 1.
- CLOUD-480 must land before wave 2, not after. Batching 8–24 gates per PR means one false-green module hides inside a large green diff;
mutantat its current coverage cannot see it, andretires_withadmits the suite deletion anyway. Batching raises the value of the anti-false-green instrument, so it comes first. - Waves 1–3 have no owner. This row's Acceptance stops after the pilot. Either it grows to carry them or a sibling row does — dispatching a wave against a row that does not claim it is how work lands with nothing recording that it did.
Dispatch is BY HAND, and that is settled
create_session is refused upstream: the session-management tools carry a mandatory-approval flag — "requires explicit approval regardless of permission mode" — and bypassPermissions, an explicit permissions.allow entry and a PreToolUse allow hook are all recorded as tested and failing (#76264, #87548). mem:connector-allowlist-recovery's STOP section carries the mechanism and the tell. Do not spend a turn re-attempting it. A human opens the sessions and pastes the prompts; each bundle's rows carry full Ready blocks, so a prompt need only name the chain, the file domain and the workflow contract.
CLOUD-1199 Eleven gates wrap Batten's OWN verbs — SIX have now retired, and the acceptance clause this row owes (every second-input cell filled, including the "none"s) is answered below: 82.0s left, one member genuinely blocked
THE SECOND-INPUT COLUMN, FILLED (2026-08-31) — this is the artifact this row owes, and it is complete
The acceptance clause reads: "Every member's second-input cell is filled in with a quoted line, including the members whose answer is "none" — an empty cell and an unexamined cell are indistinguishable, and that is what let a bundle ship two blocked members in 2026-08-31's sizing." Every one of the eleven is below. Seconds are from bench/suites/RESULTS.md at origin/main, never from this body.
SIX OF ELEVEN ARE ALREADY GONE, tested with [ -f mise-tasks/<member>.sh ] at head rather than inferred from the PR list. PR #780 landed four; derived-check (CLOUD-1145, SUBSUMED) and man-pages went separately.
| member | s | second input — QUOTED | class | verdict at head |
|---|---|---|---|---|
hooks-wiring-check.sh |
51.4 | :366 merged_file="$HOME/$merged_rel" |
out-of-root | UNBLOCKED — see below |
config-lint.sh |
23.5 | :174 trailers=$(git log --format='%(trailers:key=Weakens,valueonly)' origin/main..HEAD ...) |
commit trailers | UNBLOCKED — CLOUD-1187 Done |
mcp-allow-check.sh |
5.9 | :327 for candidate in /tmp/mcp-config-cse_*.json; do |
DISCOVERED set | BLOCKED — CLOUD-1251, genuinely open |
render/cli.sh |
0.9 | none — read end to end, 90 lines; every input is argv or cargo run … generate markdown at :75 |
— | UNBLOCKED, with a caveat below |
ci-drift.sh |
0.3 | TWO, not one: :27 repo="$(gh repo view --json nameWithOwner --jq .nameWithOwner)" and :29 if ! payload="$(gh api "repos/$repo/rules/branches/$branch")" |
forge × 2 | UNBLOCKED — CLOUD-1154 Done; both reads are one record |
~~derived-check.sh~~ |
— | n/a | — | RETIRED (CLOUD-1145, SUBSUMED) |
~~man-pages.sh~~ |
— | n/a | — | RETIRED |
~~schema-check.sh~~ |
— | none | — | RETIRED (#780) |
~~config-deprecations.sh~~ |
— | none | — | RETIRED (#780) |
~~reference-check.sh~~ |
— | none | — | RETIRED (#780) |
~~skill-check.sh~~ |
— | none (.claude/skills/batten/SKILL.md is tracked and in-root) |
— | RETIRED (#780) |
| 82.0 | 4 land, 1 blocked |
The title's 345.6s / 28.3% is spent. It was measured over a 1219.4s corpus; the corpus is 1097.1s at head and six members have left it. What remains is 82.0s, of which 76.1s is landable today and 5.9s is mcp-allow-check.
hooks-wiring-check and mcp-allow-check look identical by category and are OPPOSITE — which is the whole reason this column exists
Both "read a path under a directory outside the tree". One is declarable and one is not, and only reading the program tells them apart:
hooks-wiring-check.sh:125-128**is a FOUR-ROW CONSTANT. **MERGED="${HOOKS_WIRING_MERGED-claude-code .claude/settings.json/claude-code .claude/settings.local.json/claude-code .claude/launcher-settings.json/gemini-cli .gemini/settings.json}"— a declared roster of(harness, relative path)pairs, each resolved against$HOMEat:366. That is exactly[[rule.external]]'s shape: one path under one named root variable, per declared row. Four rows, four declarations, nothing discovered. CLOUD-1167 is Done and answers it completely. It is the largest single-program retirement available in the campaign and it is unblocked.mcp-allow-check.sh:327is a GLOB over a suffix minted per session. No consumer can enumerate the ids in advance, so no declared row reaches it. CLOUD-1251, real, open.
Declared-vs-discovered is the distinction, not in-root-vs-out-of-root — and this row's own 2026-08-31 correction filed mcp-allow-check under CLOUD-1167, which is the wrong owner for exactly that reason.
render/cli has no second INPUT and does have a second CONSUMER
The same failure shape one level over, and the column as specified does not catch it. render/cli.sh:42-45 states it: "One authority: release-assets-check asks --names rather than spelling it a second time." So retiring the program must carry the --names contract, or the release path breaks on an artifact name nothing emits. It is also not a gate — its #MISE description is Effect: render … the publish-time artifact, never committed — so its successor is the emit markdown verb it already calls at :75, and the retirement is a wrapper collapse with a name contract attached.
One sibling gap, recorded as a pointer rather than re-derived here
Not a member of this family, but the same class and it has no owner: nothing in the tree WRITES a tool-verdict **record. **crates/batten/src/tools.rs reads .git/batten-tools/<tool>‖<version>‖<digest> via record_key/record_path/verdicts; the only writer is crates/batten/tests/tool_verdict_facts.rs. So policy/validator-verdict-clean.rego resolves null, its own test_could_not_look_does_not_fault passes, and the module decides nothing on any real checkout — CLOUD-845's dead gate, shipped. CLOUD-1171 owns the channel and is Done while the condition persists, which is CLOUD-1253's class. Three retirements wait on the producer — renovate-config-validator (32.3s), pkl-check (5.4s), hook-profile-check (0.6s) — and a batten tools record verb is the whole of it: it must digest the input ITSELF, never take a caller-supplied digest, because that is what makes a record stale by construction.
Why
Deriving the blocked classes from the tree rather than from ticket prose returned a family nobody had filed, and it is the largest one: 11 programs, 345.6s of the 1219.4s bats corpus (28.3%).
Every member shells out to batten itself and adjudicates the output:
| program | line | what it runs |
|---|---|---|
derived-check.sh |
:106 |
generate completions / generate man, per artifact |
man-pages.sh |
:33 |
spec --format json |
config-lint.sh |
:130 |
config lint |
schema-check.sh |
:46 |
generate schema |
config-deprecations.sh |
:60 |
config deprecations |
mcp-allow-check.sh |
:228 |
policy tools |
hooks-wiring-check.sh |
:219 |
doctor hooks -J |
reference-check.sh |
:80 |
spec --format json |
render/cli.sh |
:75 |
the render path |
skill-check.sh |
:94 |
batten |
ci-drift.sh |
:34 |
batten |
The spellings above are the CURRENT ones, and nine of eleven are scheduled to change (2026-08-30)
Pressure-tested against the imperative VERB OBJECT surface this campaign is moving onto — CLOUD-1184 declares the grammar, CLOUD-1190 moves 233 call sites onto it, CLOUD-1193 retires five singleton nouns into rule ids.
| member | calls today | successor spelling |
|---|---|---|
derived-check.sh |
generate completions / generate man |
emit completions / emit man |
man-pages.sh, reference-check.sh |
spec --format json |
show spec |
schema-check.sh |
generate schema |
emit schema |
render/cli.sh |
the render path | emit markdown |
mcp-allow-check.sh |
policy tools |
ls tool |
config-lint.sh |
config lint |
rule id check config (CLOUD-1193) |
hooks-wiring-check.sh |
doctor hooks -J |
none recorded |
config-deprecations.sh |
config deprecations |
none recorded |
skill-check.sh, ci-drift.sh |
bare batten |
unchanged |
The disposition is unaffected — no fact is missing, the successor is still the verb. What changes is the characters it is spelled with, so every per-member verdict this row records must be written in the target grammar, not in today's, or it is re-work the moment the wave lands.
**Two members name a leaf nothing has given a home. doctor hooks (surface.rs:1972) and config deprecations (surface.rs:1880) are noun-headed paths that grammar assertion #1 will refuse, and **CLOUD-1193's five-row table lists neither. CLOUD-1160 **names **doctor hooks as its successor too, so a withdrawal of the parent silently withdraws a successor two rows depend on. Naming those destinations is CLOUD-1193's work, not this row's — but this row cannot record a verdict for those two members until it happens.
This row is a PREREQUISITE for the imperative wave, which is why it is Urgent
CLOUD-1190 §1 names mise-tasks/** as its sharp edge: "Every moving call site under mise-tasks/ is checked against shell-retirement before the wave, and any that cannot be edited forces a retirement or a mise.toml task-name indirection — never a quiet edit."
Eight of this row's eleven members ARE those call sites, and the edit is refused with no override. Verified in policy/shell-retirement.rego: all three admitted edits — only_drops_a_retired_reference (:190-228), truncates_a_retired_reference (:269-275) and repoints_at_the_declared_successor (:255-261) — require some gone in delta.deleted with contains(line, gone), i.e. the line must spell a repo-relative path this same delta deletes. A line reading cargo run -p batten -- generate man contains no such path, so it matches none of the three, and V-SHELL-RULE-EDITED declares one route with no bypass_env.
So renaming generate → emit inside derived-check.sh is unlandable, and CLOUD-1190's own escape for that case is retirement — which is this row's disposition. Retiring the eleven removes eight of the ~20 mise-tasks/** call sites the wave is structurally unable to touch. That is a blocks edge, not a relatedTo.
The classification error, and it is the point of this row
A tree-scoped census reads these as blocked, on either of two premises. Both are wrong.
"The engine may not spawn." Refuted already by CLOUD-1171: batten perf ships, effect write, and perf.rs builds two binaries and runs hyperfine. §5's split is check = read and structurally incapable of spawning; enforce/exec are the spawning side. An execution is outside check, not outside the engine.
"The expected artifact is unreadable." The committed side is tracked and readable. The EMITTED side is produced under .gitignored target/, and Fact::Tracked's walk honours .gitignore (facts.rs:420-427, whose own doc warns this is how "a module author writes a predicate about the index and gets an answer about the checkout"). So a Rego module cannot see the emitted side — true, and irrelevant, because a Rego module is the wrong successor.
The successor is the verb these programs already call, plus a compiled-binary tier. That is CLOUD-1176's EXISTING §2 VERB home. Nothing is missing from the fact model. What is missing is the disposition being written down, which is why 345.6s has been sitting in the blocked column.
The proof this shape works, already landed as a decision
CLOUD-1145 is a member of this family and its disposition is SUBSUMED: crates/batten/tests/surface.rs:66 and :222 already assert derived-check's drift predicate over the compiled binary. No fact, no module — a Rust test over the emitted bytes, which can see target/ because it is not a .gitignore-honouring tree walk. The other ten members have the same shape and no row says so.
This row is not a fact family and must not be built as one. Filing it as substrate would add a Fact variant nothing needs — the dead-gate class CLOUD-845 records, one level up.
What each member still owes individually
The disposition is shared; the per-program verdict is not. Three sub-shapes:
- Pure wrapper — the verb already decides and the shell only adjudicates its exit code (
config-lint's:130,mcp-allow-check's:228,hooks-wiring-check's:219). The successor is the verb; the wrapper collapses. - Comparator — the shell diffs a committed artifact against emitted bytes (
derived-check,schema-check,reference-check). The successor is a compiled-binary test, per CLOUD-1145. - Second input — the program ALSO reads something outside this family, and that read is a genuine blocker.
config-lint.sh:174reads aWeakens:commit trailer (CLOUD-1162 unit 15, blocked on the commit-metadata fact);hooks-wiring-check.sh:366reads$HOME/$merged_rel(CLOUD-1160, blocked on the out-of-root fact).
Clearing one input is not clearing the program — the recorded failure this campaign has now committed three times, most recently on config-lint where :130 was cleared and :174 was never read.
MEASURED 2026-08-31: the second-input count is FOUR, not two — and this row's own table is what misled
A grooming session sized a retirement bundle straight off the "what it runs" table above, took skill-check and ci-drift as **bare **batten and mcp-allow-check as a pure wrapper, and put all three in a landable bundle. Two of the three are blocked, and this row's table is why: it records what each member runs and not what else each member reads, so a reader who trusts it gets exactly the failure the paragraph immediately above warns about — for the fourth and fifth time.
| member | second input | owner |
|---|---|---|
ci-drift.sh |
:29 — gh api "repos/$repo/rules/branches/$branch", the FORGE |
CLOUD-1154 |
mcp-allow-check.sh |
:327 — iterates /tmp/mcp-config-cse_*.json, OUTSIDE the repo root |
CLOUD-1167 |
ci-drift is listed above as running bare batten; it does, at :34, and then reads branch-protection rules off the forge. mcp-allow-check is listed as the archetypal pure wrapper; it is, at :228, and then globs a path no tree fact can project.
So the acceptance clause below is answered with 4, not 2 — config-lint, hooks-wiring-check, ci-drift, mcp-allow-check — and the members verified to have NO second input are config-deprecations, reference-check, schema-check and skill-check (skill-check reads .claude/skills/batten/SKILL.md, which is tracked and in-root, so it is not CLOUD-1167's class). Those four are the ones actually unblocked today, worth 65.7s against bench/suites/RESULTS.md at 144 suites / 1440.9s.
The fix this row owes is structural, not a correction to two cells. Every member needs its second-input column filled in with the same evidence a blocked one carries — the line, quoted — including the members where the answer is "none", because an empty cell and an unexamined cell are indistinguishable and that indistinguishability is what produced this instance.
Refinement — Ready (record the disposition; per-member verdicts, no new fact)
Refinement gate: Definition of Ready & Done. This body carries only specializations.
- Authority boundary (§1). This row lands no code. It records a disposition and a per-member verdict onto CLOUD-1174's generated table, and amends each member's retirement row to name the EXISTING-VERB home rather than a missing fact. No
mise-tasks/program and notests/**/*.batsis edited or added. NoFactvariant is added — that is the whole finding. - Computable predicate (§2). For each of the 11 members: does it read anything other than Batten's own output? If no, its disposition is EXISTING §2 VERB and it is unblocked today. If yes, name the second input and the family that owns it. The verdict is per-program and recorded, never inferred from the family.
- Deliberately not in scope (§2). Retiring any member — each is its own row. Adding a fact. Changing what any verb decides. Deciding whether
target/should be visible to a tree fact: it should not, and this row's finding is that no member needs it to be. - Effect (§3). None — this row is a recorded decision.
- Output and exit (§5). The artifact is CLOUD-1174's table gaining a per-member disposition column value. Pointer-only, as that table already is.
- Commit / bump (§6).
docs— no bump. Nothing undercrates/. - Test obligation (§7). No code, so no test tier. The check is that each of the 11 members' retirement rows names its home and its second input, and that no row in this family is left citing "the engine may not spawn" or "the artifact is unreadable" — both refuted above.
- Blockers (§8). Nothing blocks this row. It
blocksCLOUD-1190 — eight members aremise-tasks/**call sites that wave cannot legally edit, per the section above. The two members whose successor verb has no recorded destination wait on CLOUD-1193 naming it; the other nine do not.relatedToCLOUD-1174 (whose table this fills a column of), CLOUD-1176 (the five homes), CLOUD-1145 (the landed proof of the comparator shape), CLOUD-1171 (which refuted the spawn premise), CLOUD-1162 (a member with a real second input).
Acceptance
- All 11 members carry a recorded disposition, and the count of members blocked on a missing fact is stated — measured 2026-08-31 it is four:
config-lint,hooks-wiring-check,ci-drift(forge) andmcp-allow-check(out-of-root). The difference between "wrapper" and "wrapper with a second input" is what this row exists to make legible. - ✅ ANSWERED 2026-08-31 — see the filled table at the top of this body.
Every member's second-input cell is filled in with a quoted line, including the members whose answer is "none"— an empty cell and an unexamined cell are indistinguishable, and that is what let a bundle ship two blocked members in 2026-08-31's sizing. The count of members blocked on a missing fact is ONE (mcp-allow-check, CLOUD-1251), not four:config-lint's trailer read,hooks-wiring-check's$HOMEroster andci-drift's two forge reads all had their fact land (CLOUD-1187, CLOUD-1167, CLOUD-1154 — all Done). - **No **
Factvariant is added by this row, and no member's row still cites a missing fact it does not need. - The seconds are restated per member from
bench/suites/RESULTS.mdrather than from this body. - At least one member other than CLOUD-1145 is shown to be unblocked today as a consequence.
- Every recorded verdict names its successor in the TARGET grammar, not today's spelling — nine of eleven differ, and a verdict written in the current spelling is re-work the wave will have to redo.
- The two members with no recorded destination (
doctor hooks,config deprecations) are named as such, with CLOUD-1193 recorded as owning the gap rather than this row inventing a name.
Found by deriving the blocked classes from the tree instead of from ticket prose, while sizing a "build every missing fact family" bundle. Six families had no row at all; this is the largest, and it turned out not to be a fact family.
CLOUD-418 A new gate is never shown to fail, so a test that cannot discriminate ships as coverage
Why
This repository's most-repeated failure is a claim nothing exercises. land's refusal branch was dead code for months (CLOUD-235). timeout-check's budgets were placeholders that could not fire (CLOUD-352). A shape rule whose pattern was a program could never match and read as coverage (CLOUD-401). Each was caught after the fact.
It happened again, live, while building the landing lease (CLOUD-393). A concurrency test was written for a real race — observe() reading FETCH_HEAD, which is one file per clone while the heartbeat runs beside held/release in the same checkout. The test was green. Then the buggy version was restored to check the test could catch it, and it passed on the broken code too: every process fetches the same lease ref, so a crossed read yields a different generation of the same lease rather than an observably foreign one. The test asserted nothing.
That was found only because someone chose to mutate and re-run — a discipline nothing asks for and nothing checks. The green suite before that check and the green suite after it were indistinguishable.
Root cause. The obligation is stated as "a rule ships with a runnable gate" — a gate that exists. Nothing requires evidence the gate discriminates. A test that passes on both the fixed and the broken code satisfies every rule this repo currently has.
Scope, deliberately narrow. Not mutation testing over the workspace, which is a research project and a large CI bill. The claim here is about mise-tasks/*-check and the guards — the files whose entire purpose is to refuse — where the mutation is usually a one-line inversion and the suite is bats, so a run is seconds.
Refinement — Ready
- Source of truth (§1). The gate's own suite, run against a deliberately broken copy of the gate. A pass there is the defect; the verdict is an exit code, not a judgement.
- Mechanism (§3). Undecided between two, and choosing is what Ready needs:
- Author-side, checked in. Each gate declares one or more
mutantcases — a stated one-line corruption and the test name that must go red. A task runs them, and a mutant nothing catches fails. Costs the repo one small fixture per gate; runs locally, off the landing path. - Scheduled sweep. A weekly job applies mechanical mutations to
mise-tasks/*-checkand reports any whose suite stays green. No per-gate authoring, weaker coverage, and it belongs besidebranch-age-checkin the hygiene sweep, so no new CI minutes.
- Author-side, checked in. Each gate declares one or more
- Deliberately not in scope (§2). Mutation coverage of
crates/. Different tooling, different cost, different question. - Output (§7). Pointer-only: the gate, the mutant, and the test that failed to notice. Never a diff of the mutated source.
Test obligation
The mechanism must catch the case that motivated it: the FETCH_HEAD mutation of mise-tasks/land-lock against tests/land-lock.bats as it stood before the structural assertion replaced it. That pair is a known-good fixture — a real gate, a real mutant, and a real suite that missed it.
Commit / bump (§6): feat(gate) — patch until 0.1.0 regardless of type.
Blockers (§8): none.
Acceptance
- Every
*-checktask has at least one mutation its suite is proven to catch. - A gate whose suite passes on a broken copy fails.
- The
land-lock/FETCH_HEADpair is covered as a regression fixture, so the case that motivated this cannot recur silently.
|
Warning Review limit reachedNext included review available in 42 minutes. View limit detailsLimit details: You’ve used the included review currently available. Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (8)
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing. Comment |
`config lint`'s admission arm reads two sources that must AGREE — a `Weakens: <smell> <key>` commit trailer, and a groomed Ready block that named the same pair BEFORE the work started. `mise-tasks/config-lint.sh`'s own header says so: "They AGREE. Where both are readable, a trailer naming something the groom did not is refused, and the trailer alone admits nothing." THE SECOND SOURCE HAS NEVER BEEN WRITTEN. `mise-tasks/claim-check.sh` extracted those clauses and emitted a `weakens` line per pair; the migration onto `batten claim` did not carry them, and `claim::mint` writes six line kinds of which none is that one. So every receipt in every clone is silent, `grep -E '^weakens '` finds nothing, and the gate takes its `[ -z "$groomed" ]` arm — the one meant for CI, where no receipt exists at all. A trailer alone admits, which is exactly the "asserted in the change that performs it" shape house style section 8 refuses. Measured on this branch's own predecessor: a receipt naming two groomed clauses, and `config-lint` reporting "no claim receipt here to check it against" over a receipt sitting in `$GIT_DIR`. CLOUD-841 filed the lenient-fallback half of this in 2026-08 and its own note reads "claim-check must keep minting a receipt when the groom named nothing — it already does". That sentence is why the hole stayed invisible: the half it takes for granted is the half that went missing. THE TRACKER NORMALISES THE SPELLING, WHICH THE SHELL'S GRAMMAR COULD NOT SURVIVE. An author types `**Weakens:** ` and the tracker stores `**Weakens: **`, moving the trailing space inside the emphasis. The retired regex anchored on `\*\*Weakens:\*\*[[:space:]]`, so it matched a body typed into a local file and never one the tracker returned. Both spellings are accepted here, and the first case pins it — restoring the old grammar verbatim would have re-landed a dead gate, which is the whole risk of a port that trusts its predecessor. Five cases, three of them anti-vacuity: a body naming no weakening extracts nothing, a clause QUOTED mid-sentence is not a declaration (this repository's own rules files describe the grammar), and two code spans with the wrong joiner are a sentence rather than a pair. Refs: CLOUD-1162 Refs: CLOUD-841
…s not two `config lint` computes the base-ref smells and stopped there, so the whole admission decision lived in `mise-tasks/config-lint.sh` — which is why retiring that program needs this half first. It now runs in the verb, under exactly the condition that produces a base-ref smell in the first place: a `--config-from` base ref. An unarmed run is byte-identical, output and exit code, which matters because the unarmed form is what a consumer without a trunk convention runs. THREE STATES, AND TWO OF THEM WERE ONE (CLOUD-841). The shell read the groom with `grep -E '^weakens ' "$claim"`, so a receipt that EXISTS and names no weakening produced the same empty string as no receipt at all, and the admission arm keyed on that emptiness. The message it printed — "no claim receipt here to check it against" — is CI's honest state and was being printed over a receipt sitting in `$GIT_DIR`. That is evidence of absence read as absence of evidence, and it is the lenient direction: a trailer minted inside the change that performs the weakening admitted it, which is the shape house style section 8 refuses. Groom::Unreadable no receipt -> the trailer alone admits (CI's half) Groom::Read(named) names this pair -> admitted, both sources agree Groom::Read(other) looked, did not -> REFUSED, whatever the trailer says The third row is the one that is green today and goes red here. WHAT ADMITS IS UNFORGEABLE BY THE AUTHOR AT PR TIME, which is the property the whole design rests on. There is no flag, no environment variable and no config key: the two sources are written at different moments, and the earlier one is minted by `claim` before the work starts. Anything settable in the change under review would be a self-issued permit. The trailer read goes through git's own trailer parse rather than a scan of the message body, so a line quoted mid-message cannot pose as a declaration and this cannot disagree with what `attribution` reports about the same commit. An empty `Weakens:` trailer is dropped rather than matched: it satisfies every "did the author declare something" reading and names nothing, which is V-WEAKENS-DECLARES-NOTHING's own class. Pointer-only on both halves: the receipt carries the smell id and the config key, the report carries those plus a verdict token. The clause's prose stays in the groomed body, where a reviewer reads it in context. Seven cases. Four decide the matrix; three are the mirrors that stop a one-directional gate passing as coverage — a groom with no trailer admits nothing, a groom naming a DIFFERENT pair refuses this one, and one unadmitted smell leaves its neighbours admitted rather than collapsing the set. Refs: CLOUD-1162 Refs: CLOUD-841
Unit 15 of CLOUD-1162's partition. `mise-tasks/config-lint.sh` spawned
`batten config lint` and adjudicated the answer, so the successor was never a new
verb — it is the wrapper collapsing into the thing it already called. Two deleted
paths, 23.5s of the bats corpus, and almost all of it was `cargo run` start-up
paid once per case, which is why the port IS the performance fix.
THE TASK NAME SURVIVES, and it has to. `verify`'s own body, `.github/workflows/ci.yml`
and `hk.pkl` all name it, and `ci-local-parity` holds the workflow and `verify` to
running the same task — so a rename would need a workflow edit to land a workflow
edit. Every call site is byte-identical, which is what keeps this retirement at
exactly one program.
The replacement body is ONE LINE, deliberately: `inline-task-bodies-not-growing`
counts `run = '''` and its `"""` twin, and a multi-line body is where a predicate
hides. A single-line dispatch carries no predicate — every decision is the
verb's, and the `${VAR:+...}` is argv assembly. No ratchet moves and no waiver is
owed, which is the disposition its own `no_fix_reason` asks for rather than the
one this campaign reached for last time.
THE LEDGER, AND THREE OF ITS ARMS ARE NOT `carried:`. Seventeen cases carried,
six subsumed into gates that already own them, and:
changed: "with no claim receipt the trailer alone admits" — the case still
holds for an ABSENT receipt, which is what it names, but the shell
reached that arm for a SILENT one too. The successor tells them
apart, so the case is narrower than the behaviour it pinned.
withdrawn: three cases whose subject this deletes — a grep over the program's
own bytes for a BYPASS branch, a refusal text the shell composed,
and a header claim reconciled against the workflow tree. A program
that does not exist cannot carry a hatch or make a claim.
Four compiled-binary cases, and the centre one is the pair a `with input as`
equivalent cannot reach: byte-identical trailer, byte-identical config, a receipt
that is absent versus one that is silent, opposite verdicts. That is the engine
being shown to BUILD the two inputs — a receipt found under the branch's own
name, a trailer read out of a real commit — rather than a fixture asserting the
shape the reader may be unable to produce.
The gate leaves $MUTANT_GATES with its suite, per the two-shapes rule.
Refs: CLOUD-1162
Refs: CLOUD-841
Refs: CLOUD-1199
Two defects in the retirement directly before this one, both caught by `verify` and both mine. THE COMMENT TRIPPED THE RULE IT WAS EXPLAINING. The new `config-lint` task body is one line specifically to stay off `inline-task-bodies-not-growing`, and the comment saying so quoted the two spellings it counts. The rule is a substring ratchet over `mise.toml` and strips no comments, so the note incremented the count it was describing — 32 to 33, suppressed only because the previous change's waiver is unnarrowed and happened to cover it. That suppression is the part worth naming: the waiver's own reason says it is "expected to lapse unused" once its base moves, and a second increase quietly riding it would have made that false while reading as true. The spellings are described rather than quoted now, the count is back to 32, and the waiver lapses as written. `tests/config-lint.bats`'s own retired case for the sibling rule had this exact shape and said so — "prose may discuss a bypass; code may not have one" — with comments stripped first. This ratchet has no such strip, which is a real difference between the two and not one I am asserting should change here. ONE CASE, TWO ARMS. The ledger recorded "with no claim receipt the trailer alone admits" as both `carried:` and `changed:`. It is `changed:`: the case still holds for a receipt that is ABSENT, which is what its name says, but the shell reached that arm for a receipt that was merely SILENT too, and the successor tells those apart. The duplicate is removed and `bats-tests-not-deleted` is clean. Refs: CLOUD-1162
`bench/suites/RESULTS.md` is generated, and two bundles retiring suites in parallel conflict on it every lap. Hand-merging two machine-written cost tables would publish a number no measurement produced, so the conflict is resolved by taking the trunk's and re-deriving from a fresh `test:bats` report over the merged tree. 127 suite(s), 832.5s serial, 2289/2289 cases green. Read the COUNT rather than the delta: most of the suites that have left since this branch's last reading are other bundles' retirements landing alongside it, and the serial total moves with whichever machine took the measurement. Refs: CLOUD-1162
5185ad2 to
c130eac
Compare
|
❌ The last analysis has failed. |
|
/fast-forward |
Why
mise-tasks/config-lint.shwas a wrapper: it spawnedbatten config lint— thesuccessor — and then adjudicated the answer against a claim receipt and a commit trailer.
That adjudication carried a defect nothing could reach past it, and repairing it meant
editing authored shell, which
V-SHELL-RULE-EDITEDrefuses with no override. So the fixand the retirement are one change. That is the campaign working as designed, not a
coincidence — and it is why filing the defect as a ticket instead of retiring the program
was the wrong disposition.
The defect, in three states that were two
config-lint.sh's own header states the contract: "They AGREE. Where both are readable, atrailer naming something the groom did not is refused, and the trailer alone admits
nothing." It read the groom as
grep -E '^weakens ' "$claim", so a receipt that existsand names no weakening produced the same empty string as no receipt at all, and the
admission arm keyed on that emptiness. The message it printed — "no claim receipt here to
check it against" — is CI's honest state, and it was being printed over a receipt sitting
in
$GIT_DIR.The third row is the one that was green and is now red. Filed as CLOUD-841 in 2026-08.
And it had a layer underneath that 841's own body assumes away. 841 notes "claim-check
must keep minting a receipt when the groom named nothing — it already does". It does not.
The port onto
batten claimstopped writing theweakensline entirely, so every receiptin every clone read as empty and the lenient arm was the only reachable one. Measured on
this branch's own predecessor: a receipt naming two groomed clauses, and
config-lintreporting "no claim receipt here" over it.
Either half alone still ships a gate that decides nothing, so both land here.
One more thing the shell's grammar could not have done. It anchored on
\*\*Weakens:\*\*[[:space:]]. The tracker normalises an author's**Weakens:**to**Weakens: **, moving the space inside the emphasis — so the regex matched a body typedinto a local file and never one the tracker returned. Restoring it verbatim would have
re-landed a dead gate; both spellings are accepted, with a case pinning the tracker's.
What landed
fix(board)—claim::mintextracts the groomed clauses and writesweakens <ID> <smell> <key>. Five cases, three of them anti-vacuity.fix(gate)—lint::groom/declared/admissionsdecide the three states in theverb. The trailer read goes through git's own trailer parse, so a line quoted mid-message
cannot pose as one and this cannot disagree with
attributionabout the same commit. Anempty
Weakens:trailer is dropped rather than matched —V-WEAKENS-DECLARES-NOTHING'sclass. Seven cases.
refactor(ci)— the retirement. Two deleted paths, corpus 135 → 131 suites(three of those four are other bundles' retirements landing in parallel; this one is
tests/config-lint.bats). Four compiled-binary cases.fix(ci)— two defectsverifycaught in the retirement, both mine. Below.What admits is unforgeable by the author at PR time
There is no flag, no environment variable and no config key. The two sources are written at
different moments, and the earlier one is minted by
claimbefore the work starts —anything settable inside the change under review would be a self-issued permit. House style
§8 loads policy out of band for exactly this reason.
Proven, not asserted
The centre case is the pair a
with input asequivalent cannot reach: byte-identicaltrailer, byte-identical config, a receipt that is absent versus one that is silent,
opposite verdicts. That shows the engine builds the two inputs — a receipt found under
the branch's own name, a trailer read out of a real commit — rather than a fixture
asserting a shape the reader may be unable to produce.
The ledger
Two file arms, both
kind:mechanism—config lintalready shipped, so no command surfacewidens. Seventeen carried, six subsumed, and three arms that are not
carried::changed:— "with no claim receipt the trailer alone admits" still holds for anABSENT receipt, which is what it names, but the shell reached that arm for a SILENT one
too. The successor tells them apart, so the case is narrower than the behaviour it pinned.
withdrawn:×3 — a grep over the program's own bytes for aBYPASSbranch, a refusaltext the shell composed, and a header claim reconciled against the workflow tree. A
program that does not exist cannot carry a hatch or make a claim.
The task name survives, so
verify,.github/workflows/ci.ymlandhk.pklare allbyte-identical and the four wiring cases still hold where they are.
Two defects this PR's own
verifycaughtBoth mine, both in the retirement commit, and the first is the more interesting:
body is one line specifically to stay off
inline-task-bodies-not-growing, and thecomment saying so quoted the two spellings it counts. It is a substring ratchet over
mise.tomlthat strips no comments, so the note tripped the rule it was describing.It was suppressed by the unnarrowed waiver from feat(facts): build the verdict-store producer, and retire three wrappers into the rows it wakes #797 — whose own reason says it is
"expected to lapse unused". A second, unrelated increase riding it would have made that
false while reading as true. The spellings are described rather than quoted now, the
count is back to its floor, and the waiver lapses as written.
carried:andchanged:. It ischanged:.Left undone, and stated plainly
board-diff-overlap(unit 16 of CLOUD-1162) is not in this PR, on the row's owninstruction: its issue-body half extracts paths from prose, and
input.tree.capturedis adeclared reduction —
present,countor a bounded token, never a payload. That is adesign boundary, not a gap to widen, and nothing owns the engine-side extractor it needs.
(the dropped
weakensline, the tracker's normalisation) are folded in here.Closes CLOUD-841
DO-NOT-CLOSE CLOUD-1162
DO-NOT-CLOSE CLOUD-1199
Refs: CLOUD-418