refactor(ci): retire hook-latency-drift and sbom-check - #830
Conversation
CLOUD-1270 `hook-latency-drift` — 24.6s, 2.2% of the corpus, in no bundle and no row's scope: it is a REPORTER on a clock, not a gate, so the campaign has no disposition for it
DECIDED 2026-09-02 — disposition 3, retire whole; the measurement moves into the scheduled workflowOwner's decision, taken by hand after the program and suite were read ( Why not 2. The Why not 1. 24.3s is the largest non-landing-loop suite outside CLOUD-1318, and the program decides nothing — the seconds buy a reporter's regression suite, not a gate's. What 3 is, narrowed. No new **Bump. ** Dispatched in the bats-cost bundle on CLOUD-1151. Why
How it got there, stated so the gap is legible rather than embarrassing: CLOUD-1170's table What it actually is, from its own headerThat description settles most of the disposition question by itself, and it maps onto a split
Its only invoker is The question this row has to answerA reporter is not a completion gate, and Batten's scope reminder does not list benchmarking.
2 is the one worth pricing first, because it is the only one that recovers the seconds Deliberately not assertedWhich of the three is right. This row's job is to give the number an owner, not to pick a Refinement — Ready (give an ownerless 24.6s a disposition) Refinement gate: Definition of Ready & Done. This body carries only specializations.
Acceptance
Found by subtracting every dispatched bundle from CLOUD-1318 ONE PR: retire `sbom-check` onto the tool-verdict family — 1 program, 1 suite, 18.3s (3.6% of the corpus), 2 file arms + 14 case arms
Why Four retirements were each recorded as blocked on something. Read live against Every figure below is read from The bundle
Membership is CITED, not restated: each unit's blocked/unblocked verdict, its home and its predicate belong to the row named in the last column. Re-deriving a member list into this body is CLOUD-1166 arm 3, which that row records as committed into a dispatch prompt once already. The closure is checkable, and it terminatesEvery one of the seven dying suites declares a And no open PR touches any of the six. Verified by diffing every open head against Why
|
| Measurement | Value | Source |
|---|---|---|
| Whole gate | 111.1s | mise.toml:750-760 |
test:bats, in-gate at nproc=4 |
100.3s | mise.toml:750-760 |
test:bats standalone (in-gate contention ≈1.11x) |
90.26s | mise.toml:750-771 |
batten-check, cold clone |
9.47s | hk.pkl:462-464 (CLOUD-212 run 1, D8) |
batten-check, warm, 3 runs |
133–141ms | hk.pkl:462-464 |
| Cheapest gate in the same run | 0.16s | CLOUD-212 run 1, D8 |
no-docs-tree |
~95ms | hk.pkl:58 |
The gate now contains three whole-workspace cargo invocations (cargo clippy --all-targets --all-features, cargo test --workspace, cargo run -p batten -- check), the 1039-case bats suite, two syft scan dir:. passes in sbom-check, a JVM start per pkl step, node for prettier, and python3 in macos-link-check. Seven further cargo run/cargo metadata invocations sit outside the serialised cargo-fmt → cargo-clippy → test → batten-check chain and can contend with it for the target-dir lock.
Of the 38 steps, 5 are receipt-gated (cargo-fmt, cargo-clippy, test, test:bats, batten-check). policy-budget, config-lint, schema-check, completions-check, skill-check, sbom-check, token-bench-check and macos-link-check are not, several of them cargo- or scan-shaped. Four steps carry no glob and run on every commit whatever it touched.
Why nothing catches it
The rule already exists and is prose. mem:toolchain-and-hooks carries a section headed "Keep the hooks fast — hk.pkl is living config", stating that the hook "runs on every commit, so its latency is a constant tax" and that whoever touches it must "re-check the hook is still optimal". The mechanism that section ships is hk-version, which fails on a version pin drifting between mise.toml and the amends URL — a real gate for a different property. Nothing measures a cost, and nothing fails on one.
That is non-negotiable 2 unmet in this repo's own gate config: a rule without a runnable check is half a change, and prose is feedforward only. Every step added since that section was written was added against a budget nothing enforces, which is why the number is 111s and no commit ever went red for it.
Moving a step out of pre-commit costs no coverage
This is the property that makes a tier split a latency change rather than a weakening of the gate, and it is structural rather than a promise:
mise run ci→hooks→hk check --allruns the samegatemapping, andhk check --allignores globs. CI runs every step.mise run verifyrunsciand writes a SHA-keyed receipt.ready-guarddeniesgh pr readywithout averifyreceipt for that exact HEAD;landdepends onverified.
So a profiled step still runs before anything is readied or landed. The split decides when the cost is paid, never whether the check runs.
Mechanism
The obvious design is unavailable, and the reason is worth recording. A per-step tier annotation cannot exist: Pkl classes are closed, Step (hk 1.54.0 Config.pkl:308) is not open, and none of its 30 properties is a tier or a free-form field. Measured:
$ pkl eval - # class Step { glob: String? } … new Step { tier = "fast" }
–– Pkl Error –– Cannot find property `tier` in object of type `Step`.
That fails the pkl gate step at check time. A second barrier sits behind it: Config.pkl's renderer spreads s.toMap() into a Dynamic, so an extra key would reach hk's Rust deserializer too. A sibling Mapping<String, String> filtered with gate.toMap().filter(…) evaluates, but writes every step name in two places, and a rename then drops a step out of its tier silently — the second-authority failure non-negotiable 6 names.
hk already ships the mechanism. Step.profiles: List<String>? is upstream's selector: a step runs only if its profile is enabled. hk exposes --profile, the -s/--slow shorthand, and HK_PROFILE/HK_PROFILES; Config.display_skip_reasons carries profile-not-enabled and profile-explicitly-disabled as first-class skip reasons. The tier is therefore not invented here — it is the upstream selector, adopted, with the selector logic staying upstream where it is already tested.
- Each expensive step declares
profiles = List("slow"):cargo-clippy,test,test:bats,batten-check,token-bench-check,sbom-check. Everything else is unprofiled and always selected. pre-commitruns without that profile.checkandfix— the hooksmise run ciandmise run fmtdrive, and thereforeverifyand CI — enable it.- The profile sits on the step it governs, so there is no second table to drift.
One empirical question the implementation settles first, rather than assuming. Hook.env is documented as merging into each step's process environment, so whether HK_PROFILES set there feeds hk's selection before dispatch, or only reaches the child, is not settled by the schema text. Confirm with hk check --plan --why. If it only reaches the child, the fallback is the invocation site (hk check --all --profile slow in mise run hooks), which is equally single-authority.
The gate. mise-tasks/hook-profile-check, in the shared gate, globbed on hk.pkl, reading hk check --plan --json — confirmed working in this tree, emitting per step name, status (included/skipped), reasons[].kind and fileCount:
- every step in the slow tier is still
includedunder thecheckhook — the load-bearing assertion; - every step in that tier is
skippedunder--profile '!slow', asserted as data rather than inferred from a timing.
The tier set is derived, not declared (revised during implementation). The first draft of this clause said "every expensive step declares the profile". That is not computable: a hand-listed set of expensive steps would be a second authority over what hk.pkl already defines and would go stale in the silent direction, and "expensive" is a wall-clock property that a gate over committed bytes cannot see. The gate instead derives the tier — a step hk excludes for a missing profile is, by construction, one that declared it — and the cost question runs on the clock half.
Which direction is the false green, since the two failures are not symmetric: a tier that stops being skipped at pre-commit is merely slow, loud and self-correcting. A tier that stops being run by check removes clippy, the test suite and batten-check from mise run ci, verify and CI at once — green everywhere, nothing tested. Deleting the config-level profiles line does exactly that with no other symptom, which is what profiled-step-not-in-check exists to red.
No arithmetic at commit time. Whether the fast tier is still fast is a property of the world, so it runs on a clock: a scheduled re-measure reds on drift, the lock-complete/lock-currency and timeout-check/timeout-drift split. The ceiling is set from the measurement taken in the landing change.
The draft PR is pushed before verify
With the expensive steps off the commit path, this ordering carries the safety the gate used to. Drafts trigger no CI — ci-local-parity gates that property — so pushing early costs nothing, makes the work durable against a container reclaim, and gives issue-guard its artifact at the earliest computable moment. Commit often, push the draft, then verify.
Refinement — Ready
Specializations only; the shared clauses live in Definition of Ready & Done.
- Source of truth (§1).
hk.pkl'sgatemapping. The profile lives on the step it governs, never in a second list — a separate table would be a second authority over a sethk.pklalready defines, and it would narrow silently the next time a step is added. - Mechanism as a computable predicate (§2).
mise run hook-profile-check, in the sharedgate, globbed onhk.pkl, overhk check --plan --json: the slow tier is non-empty (anti-vacuity, exit 2), every step in it isincludedundercheck, and every step in it isskippedunder--profile '!slow'. The tier is derived from the plan, never listed in the gate. It is a task and not a[[rule]]because the predicate addresses nodes in a structured document, which no rule kind expresses —forbidsees bytes,commandsees an exit code and is refused bybatten checkoutright. CLOUD-452 is the linked capability gap, and this is the second consumer its own re-open predicate names. - Effect (§3).
read— a check over committed bytes and gate configuration. No new command, no write. - Generated artifacts + drift gate (§4). Nothing generated. The scheduled re-measure is the drift sensor on the fast tier's wall clock, and it is the clock half rather than a gate on the commit.
- Output & exit contract (§5). Pointer-only:
hk.pkl:<line>and a rule id, never a step's output or a command line. Exit 1 = violation, 2 = could not read the input — themise-tasks/*-checkconvention, which is the inverse of batten's own numbering (mem:toolchain-and-hooks). - Commit / bump (§6).
ci→ no bump. - Test obligation (§7).
tests/hook-profile-check.batsfor the gate's decision table, plus cases extendingtests/hk-selection.batsrather than a second selection suite — it exists for CLOUD-224, already asserts selection throughhk check --plan --jsonwithjq '.steps[] | select(.name==$s) | .status', and its header already argues why plan JSON is the only form the negative case can take (a timing cannot tell a skipped step from a cached one). New cases: each expensive step isskippedunderpre-commitandincludedundercheck; an unprofiled step isincludedin both. The case that regresses silently is a new expensive step with no profile. - Blockers (§8). None. Related: CLOUD-452, the capability gap this links per §2. Overlapping scopes to reference rather than re-file: CLOUD-224, CLOUD-424, CLOUD-398, CLOUD-439, CLOUD-386, CLOUD-488.
Acceptance
- The pre-commit wall clock is measured and both numbers — before and after — are recorded on this issue. Reasoning about the improvement does not count.
- The ceiling is derived from that measurement, not chosen.
- Removing the config-level profile — the change that would silently drop the slow tier from CI — turns the gate red, mutation-checked rather than argued.
- Every profiled step is proven still to run under
verifyand CI — read from plan JSON, not from a timing — so the split is shown to move cost rather than drop coverage.
Not in this issue
Per-step content-addressed receipts (CLOUD-424), glob selection for glob-less steps (CLOUD-224), core contention between chains (CLOUD-398, CLOUD-439), the shell suite as the pole (CLOUD-386), and the gate spent before git commit -F - is refused (CLOUD-488). Each of those makes the gate faster; none of them makes "too slow" fail, and none decides where a check belongs. The batten hook PreToolUse budget (CLOUD-207, CLOUD-479) is a different layer and keeps its own number.
CLOUD-664 The SBOM lists 244 components for 198 distinct things: one entry per workflow reference, the root package twice, and a spurious `./action`
Why
The SBOM reports 244 components for 198 distinct things, and every per-component conformance denominator is computed over the inflated number. Measured 2026-08-18 by regenerating the document from origin/main 41180c3 (v0.0.79).
Three separate causes, found while answering CLOUD-629 and CLOUD-630:
- One entry per workflow reference, not per action. The 53
pkg:githubentries are 9 unique(name, version)pairs:actions/checkoutappears 21 times,jdx/mise-action20,Swatinem/rust-cache4,actions/cache2,actions/upload-artifact2. syft's github-actions cataloger emits a component per reference site, so the inventory says this repository depends onactions/checkouttwenty-one times. - The root package is listed twice.
battenappears once aspkg:cargo/batten@0.0.79(sourceInfo: acquired package info from rust cargo manifest: /Cargo.lock) and again with no purl and no sourceInfo — two entries for the document's own subject, and the purl-less one carriessupplier: NOASSERTION. - A spurious
./actioncomponent. Name./action, versionUNKNOWN,supplier: "Organization: .", acquired from a local composite-action reference in.github/workflows/test.yml. It is a relative path in this repository, not a dependency of it. It can never be enriched, because there is nothing to enrich.
The honest inventory is 188 external crates + batten + 9 unique actions = 198.
Why this matters beyond tidiness
It is the denominator every sibling issue reasons about. no-license=244 is really 198; the non-cargo license gap is 9 unique actions, not 55 entries. A reader of the document cannot tell how many things this repository actually depends on, which is the one question an inventory exists to answer.
Not the same as narrowing the scan. CLOUD-608 explicitly declined shrinking the document's scope to buy conformance. This is not that: no real dependency leaves the inventory. A duplicate entry and a self-referential path were never things the repository depends on, so removing them corrects the claim instead of weakening it.
Refinement — Ready (2026-08-18)
- Source of truth (§1).
mise-tasks/sbomis the single authority for what the documents contain, and it already owns the scan invocation and the exclude list for exactly this class of reason — its header records thetests/batsexclusion as load-bearing rather than tidiness. Component identity is the triple(name, versionInfo, purl). Whether the fix is a syft configuration or a post-process over its output is an implementation call; the identity rule is not. - Predicate (§2). A
sbom-checkclause exits non-zero when any of three conditions holds in the produced document: two components share an identical(name, versionInfo, purl)triple; the root package appears more than once; or a component's name is a relative path (./…) or itsversionInfoisUNKNOWN. A command and an exit code over one file; nothing is judged. - Effect (§3).
mise-tasks/sbomalready writes the documents and is classified as a producing task; no new subprocess and no network call. Thesbom-checkclause is read-only and joins the allowlist beside its siblings. - Output and exit (§5). Pointer-only per house-style §6:
batten.spdx.json:0 sbom-components-inflated (entries=244 distinct=198 dup=<n> pathlike=<n>)— counts and the document path, never a component list. Exit contract unchanged: 0 clean, 1 finding. - Commit / bump (§6).
fix(sbom)→ patch; the workspace is 0.0.79, below 0.1.0, where every release-worthy type collapses to a patch. - Test obligation (§7). Four cases.
- A document carrying one action referenced from two workflows yields one component. Fails on today's output, which yields two.
- The root package appears exactly once, and that once carries its purl.
- A relative-path component name is refused, so the
./actionshape cannot come back when a workflow gains another local action. sbom-check's existingsbom-package-driftclause still passes byte-identical, and this is the case that bounds the change: that clause compares thepkg:cargocount against the rootCargo.lock, and the cargo entries are already distinct by(name, version)— the four names appearing twice (getrandom,hashbrown,syn,windows-sys) are genuinely two versions each. A dedup that changed the cargo count would have merged two real dependencies, which is the one way this fix could do damage.
- Blockers (§8). None.
relatedToCLOUD-628, CLOUD-629 and CLOUD-630 (the three enrichment issues whose denominators this corrects) and CLOUD-631 (the conformance ceiling, which moves with the denominator).
CLOUD-1149 `repoints_at_the_declared_successor` cannot match how bash spells a sibling, and its own comment names the case it does not admit — 27 governed callers are unrepointable by a two-character gap
Why
policy/shell-retirement.rego:255 admits a sibling edit that repoints a caller at a retired program's declared successor:
repoints_at_the_declared_successor(line, removed) if {
some was in removed
some gone in delta.deleted
contains(was, gone)
some succ in successors_for(gone)
replace(was, gone, succ) == line
}gone is a member of delta.deleted, and a deleted path is repo-relative — the module says so itself ("A deleted path is classified BY PATH... mise-tasks/** minus the explicitly non-shell paths"), and governed_when_deleted is a startswith("mise-tasks/") test. So gone is mise-tasks/ready-lint.sh, and contains(was, gone) fires only on a line carrying that literal string.
Bash does not spell a sibling that way. The measured line:
mise-tasks/graph-check.sh:163 lint="$(dirname "$0")/ready-lint.sh"
contains("lint=\"$(dirname \"$0\")/ready-lint.sh\"", "mise-tasks/ready-lint.sh") is false. only_drops_a_retired_reference (:200-204) carries the identical requirement and fails identically. The edit is therefore V-SHELL-RULE-EDITED, which declares one route and no override and no bypass_env.
The clause's own comment names the case it cannot handle
policy/shell-retirement.rego:244-245:
"THE CAMPAIGN COULD NOT COMPLETE A RETIREMENT IT HAD ITSELF MANDATED without this... Measured on
mise-tasks/graph-check.sh, whose one line resolves the readiness gate by path."
That is the exact line above, and the clause as written does not admit it. Comment and code disagree, and the comment is the half that has been believed. This is not a documentation nit: the comment is what a reader consults to decide whether a retirement is landable, and it says yes where the predicate says no.
Measured blast radius
41 relative-resolution call sites across 27 governed callers. Every one forces its caller into the same delta as its callee, because there is no admitted spelling for repointing it. Census against 2f1be52, 132 governed programs:
| class | count | suite seconds recoverable |
|---|---|---|
| FREE (no inbound governed ref) | 83 | 481.4 |
| REPOINTABLE (literal path only) | 7 | 29.6 |
| BUNDLED (forced by this defect or a multi-subject header) | 39 | 373.0 |
| GENUINELY BLOCKED (runtime, not policy) | 3 | 238.1 |
The largest forced bundle is the board-gate chain: ready-lint → graph-check → released → board-sweep, plus bot-issue via a live invocation in its suite. Six programs, eleven deleted paths, ~45.5s of suite, none of which is individually blocked by anything but this.
mise-tasks/board-sweep.sh:229-230 is the hardest case and bounds any textual fix:
for gate in graph-check duplicate-close-check released in-progress-drain done-pr-check spec-ref-check; do
[[ -x "$here/$gate.sh" ]] || {
The filename is computed from a task name, so no substitution of any kind reaches it. A fix that only widens string matching does not clear this one.
Two directions, and the row must pick one
- Widen the match. Admit a line that names
gone's basename where the surrounding text resolves to the same path ($(dirname "$0"),$here,$BATS_TEST_DIRNAME/..). Cheap, but it loosens a clause whose narrowness is its safety argument — the module is explicit that byte-exactness is "what makes that structural rather than hoped". - Normalise the call sites first. Land a one-line-per-caller normalisation to literal repo-relative paths as an ordinary change — which the gate does not currently permit either, since that edit is itself
V-SHELL-RULE-EDITED. So this direction needs its own admitted shape.
Neither is obviously right and the row's job is to decide, with the board-sweep.sh computed-name case as the acceptance test for whichever is chosen.
Refinement — Ready (decide the admission's shape, with the computed-name case as the test)
Refinement gate: Definition of Ready & Done. This body carries only specializations.
- **Authority boundary (§1). **
policy/shell-retirement.rego's admission clauses and their comment block.policy/is not governed —governed_at_headselectsmise-tasks/paths plustests/**/*.batsand nothing else — so this module is an ordinary editable file and no retirement is required to change it. Nomise-tasks/program and no.batsfile is edited or added. - Computable predicate (§2). A sibling line that resolves a retired program through
$(dirname "$0"),$hereor$BATS_TEST_DIRNAME/..is either admitted when it repoints at the declared successor, or refused with a verdict that names why it cannot be spelled — never refused with a comment claiming the case is covered. - The comment is in scope and is not a nit (§2). Whichever direction is chosen,
:238-245must stop naminggraph-check.shas a case it handles if it does not. A remedy a reader cannot act on is CLOUD-1050's recorded class. - Deliberately not in scope (§2). Retiring any program. Weakening
V-SHELL-RULE-EDITEDto carry an override or abypass_env— the no-override property is the design and this row does not touch it. The three runtime-blocked programs (ci-lease-precondition,land-lock,land), whose blocker is a from-trunk blob fetch and not this clause. - **Effect (§3). **
read. A policy module decides; it writes nothing. - Output and exit (§5). Pointer-only: the refusing path and line, the retired path it names, never the line's contents.
V-SHELL-RULE-EDITED's existing verdict token and route are unchanged unless the chosen direction adds a class, in which case the[[verdict]]registry gains a row — a module raising a token no row declares fails to load. - **Commit / bump (§6). **
fix(policy)— patch. - Test obligation (§7). Module
test_rules pin the predicate;crates/batten/tests/*.rsover the compiled binary is the tier that proves the engine builds the input the predicate reads. Shown able to fail per CLOUD-418, four cases: (a) a$(dirname "$0")repointing at the declared successor behaves as the chosen direction says; (b) a$(dirname "$0")line repointed at something the ledger does not declare is still refused — the anti-vacuity case, without which (a) is satisfied by admitting everything; (c) theboard-sweep.sh:229-230computed-name shape is handled or explicitly refused with a nameable verdict; (d) an unrelated edit smuggled into the same file is still refused. **Mutated: **shell-retirementis in$MUTANT_GATESand a mutation over the new conjunct must discriminate — a mutation some other conjunct already excludes will survive, and surviving is the only way you find out. - Blockers (§8). None.
relatedToCLOUD-1121 (which added the clause), CLOUD-1051 (which added the truncation clause one case earlier), CLOUD-843 (the campaign this gates), CLOUD-1132 (the two-shapes doctrine surface), CLOUD-1148.
Acceptance
- The clause and its comment agree about
mise-tasks/graph-check.sh, in whichever direction the row decides. - The
board-sweep.shcomputed-name case is either admitted or refused with a verdict a reader can act on — not silently unmatched. - The board-gate bundle (
ready-lint,graph-check,released,board-sweep,bot-issue) is shown landable, or the row records exactly what still stops it. - No override and no
bypass_envis added toV-SHELL-RULE-EDITED.
Found by censusing all 132 governed programs for retirement cost after a grooming session twice recorded the wrong reason for the same set of blocked programs — first "by-path callers make retirement impossible", then "repoints_at_the_declared_successor makes them repointable". Both were wrong; the discriminator is literal repo-relative path versus relative resolution.
CLOUD-1159 Retire the SBOM/release-artifact unit — `sbom-check` (38.5s) is UNBLOCKED, CLOUD-1265's producer LANDED; `sbom.sh` is a producer, not a gate, and owes a disposition rather than a fact
DECIDED 2026-09-02 — sbom.sh STAYS as the producer; its seconds are accepted
Owner's decision on the reachability question the 2026-09-02 correction block below poses. Of the two admissible answers, the first is taken: mise-tasks/sbom.sh stays as the producer whose output sbom-check's successor reads a record of. Its suite's cost (4.8s at origin/main ee1c9f52) is a cost the campaign accepts, not a retirement it waits on. No spawning producer verb is added — CLOUD-1176's growth is not bought for a program that decides nothing.
Consequence for the bundle: unchanged from the block below. sbom-check.sh + tests/sbom-check.bats retire alone under CLOUD-1318; sbom.sh is neither deleted nor edited, and sbom-check.sh:44's constructed-sibling read is an admitted repointing (CLOUD-1149 arms 2 and 3).
BOTH BLOCKERS IN THE TABLE BELOW HAVE RESOLVED, IN OPPOSITE DIRECTIONS — 2026-09-02
The table below records
sbom-checkblocked on CLOUD-1265 andsbom.shblocked on
CLOUD-1251. Read live againstorigin/main9ece058c, neither verdict still holds, and
the two moved for different reasons — which is why this note replaces both rather than
flipping one flag.
sbom-check(38.5s) is DISPATCHABLE. The producer exists.CLOUD-1265 is In Review, which in this repository means landed on
main, and the
mechanism is checkable rather than asserted:git cat-file -e origin/main:crates/batten/src/record.rs # existsIts own header: "The write half of the two out-of-tree verdict stores (CLOUD-1265) … Both
shipped correct, and both shipped with the only writer in the tree being a test … This is***
***that producer."That is the channel
sbom-check.sh:213'scargo metadata --format-version 1 --offline
needs, and it settles the regenerate-and-compare half too:record.rsstates the shape as
"amisetask or a CI step runs the tool, reduces its answer to<name> <token>lines,*
*and pipes them here" — so the successor never spawns, which is what house style §5 requires
of acheck.
sbom.sh(12.1s) is NOT blocked on a fact, and never was. It is not a gate.CLOUD-1251 DECIDED, 2026-09-01: answer 2. "A producer resolves the runtime-discovered
set outside the engine, and a module reads what was written. The engine does not grow a*
*glob." So the[[rule.external]]route the "sixth wrong verdict" section below sends
sbom.shdown is closed permanently rather than pending —crates/batten/src/rules.rs:3947
still refuses anypaththat is not a single relative path beneath its root, and that is
now the settled design.The deeper reading is that out-of-root was the wrong axis for this program entirely.
mise-tasks/sbom.sh:2is#MISE description="Effect: derive the SPDX and CycloneDX inventories of this tree…". It derives an artifact; it decides nothing.cargo_src_roots
andcopyright_ofexist to FILL the SPDXPackageOriginatorfield, not to adjudicate — so
no fact could ever have carried it, because a fact feeds a predicate and this program has
none.That is the class the HOMES section below already assigns to
dist.sh: "NOT POLICY AT ALL*
— it builds a release artifact. That is a build task, not a gate; it owes no successor and
**no ledger arm beyond its own deletion, if it is retired at all." *sbom.shbelongs in that
row, not the blocked one.So the live question for
sbom.shis REACHABILITY — CLOUD-1174's fourth axis, which asks
whether a unit's seconds need a retirement at all — and it is a decision this row owes in
writing, with two admissible answers:
- it stays, as the producer whose output
sbom-check's successor reads a record of, and
its 12.1s is a cost the campaign accepts rather than a retirement it is waiting on; or- it becomes a spawning producer verb on CLOUD-1265's landed precedent, whose §3 is
write— "it spawns the validator and writes under$GIT_DIR… it is emphatically not
check" — at the cost of one more top-level noun, which is the growth CLOUD-1176 and
CLOUD-1194 exist to price.What is NOT admissible is what this row does today: recording
sbom.shas blocked on a
fact family that has decided, in writing, that it will never carry it. That is CLOUD-1166
arm 4 in the direction this row has already been corrected in twice.The consequence for a bundle
sbom-check.shandtests/sbom-check.batsretire now, on their own. The caller/callee
direction is the harmless one: CLOUD-1149's landed arms 2 and 3 admit the constructed-sibling
repointing atsbom-check.sh:44, andsbom.shsurviving its caller drags nothing in**.****
**38.5s, one program, two deleted paths, two ledger arms.Nothing else in the body below is changed by this note.
THE TITLE SAID "ONLY sbom.sh" AND THAT IS ONE PROGRAM SHORT — corrected 2026-08-31
sbom-check (38.5s, the unit's largest member) has a second input this row has never
recorded. mise-tasks/sbom-check.sh:213:
if ! meta=$(cargo metadata --format-version 1 --offline 2>/dev/null); then
echo "::error:: sbom-check: could not read cargo metadata, so whether the document's originators agree with the manifests is unverified." >&2That is a third-party tool's verdict keyed to a pinned version over a known input —
precisely input.tree["tool-verdict"]'s shape, and precisely the channel that has no**
**producer: crates/batten/src/tools.rs reads .git/batten-tools/<tool>‖<version>‖<digest>
and the only writer in the tree is a test, so the fact resolves null on every real
checkout. That gap is CLOUD-1265, and sbom-check
is a named consumer of it.
The row's own header at :203-213 says why the read exists and cannot be dropped:
PackageOriginator is who WROTE a component, which only cargo metadata's authors
answers, and both halves of the supplier check are asserted.
So the blocked set is TWO, not one:
| program | s | blocker | evidence |
|---|---|---|---|
sbom.sh |
12.1 | CLOUD-1251 | :322 globs "$home"/registry/src/*/ — a discovered set |
sbom-check |
38.5 | CLOUD-1265 | :213 cargo metadata --format-version 1 --offline |
That is 50.6s of the unit's 62.6s blocked, not 12.1s. The dispatchable remainder is six
programs and ~12.0s — release-assets-check 5.3, ntia-check 4.2, render/cli 0.9,
sbom-binary 0.8, checksums 0.6, dist 0.2 — which is a materially different bundle from
the one this row's title advertises.
One thing I claimed and am withdrawing before it propagates
I first recorded sbom-check.sh:44 — SBOM="$(cd "$(dirname "$0")" && pwd)/sbom.sh" — as a
second blocker, on the reasoning that it executes a blocked program. That is wrong and the**
**row already refutes it: CLOUD-1149 landed arms 2 and 3 of
is_retired_reference_by_text for exactly that constructed-sibling spelling, so the site is
an admitted repointing and no caller dies with its callee. sbom-check's blocker is :213
and nothing else.
The cause is the one this row already records twice. CLOUD-1166
arm 4 — a blocked verdict must quote its source — and the "sixth wrong verdict" section below
records that an UNBLOCKED verdict owes the same quotation. This is the seventh instance and
it ran in both directions inside one pass: sbom-check was cleared without reading :213,
then blocked on :44 without reading CLOUD-1149's landed arms.
THE TITLE ASSERTED THE GLUE ITS OWN BODY DISSOLVED — corrected 2026-08-31
The title read "9 programs, 59.8s, glued by six
$(dirname "$0")resolutions through
sbom.sh". The body's own CORRECTION section says that glue is gone: CLOUD-1149
landed arms 2 and 3 ofis_retired_reference_by_textfor exactly the constructed-sibling
spelling, so every one of the six glue sites is now an admitted repointing and no
caller dies with its callee. A correction block does not correct a title, and the
title is what a queue listing renders — so this row went on reading as a nine-program
monolith after it had stopped being one.And #780 has landed, so the count and the seconds have both moved
The body says "PR #780 has not landed, so
reference-checkhas not retired". It has.
Verified atorigin/main5b98174— #780's head sha **is **main's head:git cat-file -e origin/main:mise-tasks/reference-check.sh # gone git cat-file -e origin/main:tests/reference-check.bats # goneThe unit is 8 programs, not 9, and its largest single member left with it
(reference-check.bats, 14.8s). That is also the second landed demonstration that this
unit slices —reference-checkretired alone, in a bundle containing none of the other
eight, exactly as the correction predicted.Current seconds, from
bench/suites/RESULTS.mdat5b98174(138 suites, 1097.1s),
not from this body:sbom-check38.5 ·sbom12.1 ·release-assets-check
5.3 ·ntia-check4.2 ·signing-posture— not a member ·render-cli0.9 ·
sbom-binary0.8 ·checksums0.6 ·dist0.2 = 62.6s, higher than the
59.8s this body quotes despite one member leaving, because the corpus was regenerated
after CLOUD-1198. ReadRESULTS.md, never this body.
blockedByCLOUD-1154 removed — the body already said it was satisfied"Forge — ANSWERED … This row's
blockedByon it is satisfied." The relation outlived
the sentence; it is gone now.What is genuinely still blocked, and it is ONE program
sbom.shonly, on CLOUD-1251 —:322globs
"$home"/registry/src/*/across discovered registries × ~280 package directories. The
"sixth wrong verdict" section below establishes that and is correct; it is the one part
of this row that should be read as current. Every other member is unblocked, and the
homes section already assigns each one.So the dispatchable shape is: retire the seven, carve
sbom.shout with its reason
recorded, and let CLOUD-1251 return it — not a nine-program delta, and not a wait.Nothing else in the body below is changed by this note.
Why
Unit 2 of the 83-unit partition of the governed shell. 9 programs, 9 suites, 59.8s — 4.8% of the 1244.6s serial suite. Fourth-largest unit in the tree.
Programs: sbom.sh, sbom-check.sh, sbom-binary.sh, ntia-check.sh, dist.sh, checksums.sh, render/cli.sh, release-assets-check.sh, reference-check.sh.
Suites: sbom-check.bats 26.1, reference-check.bats 14.8, sbom.bats 5.9, release-assets-check.bats 5.8, ntia-check.bats 4.2, render-cli.bats 1.1, sbom-binary.bats 0.9, checksums.bats 0.8, dist.bats 0.2.
5 of 9 in $MUTANT_GATES (sbom, sbom-check, ntia-check, reference-check, release-assets-check). 19 #MUTANT rows to re-home (sbom 12, ntia-check 4, sbom-check 1, release-assets-check 1, reference-check 1).
The glue — why these nine are one delta
Every admission in policy/shell-retirement.rego tests contains(was, gone) against the repo-relative path. A caller resolving a sibling through $(dirname "$0") carries no such literal, so no admission fires and the caller must die in the same delta.
sbom.sh←sbom-check.sh:44(SBOM="$(cd "$(dirname "$0")" && pwd)/sbom.sh"),ntia-check.sh:74,release-assets-check.sh:80render/cli.sh←reference-check.sh:50,release-assets-check.sh:85, andtests/reference-check.bats:37,48,118,123,130sbom-binary.sh←release-assets-check.sh:104checksums.sh←release-assets-check.sh:177dist.sh←sbom-binary.sh:52
Self-contained — the closure terminates. install-check.sh:6,40,130 reaches dist.sh only by literal path, so it is droppable/repointable and does not join.
CORRECTION (2026-08-31) — THE GLUE IS DISSOLVED, AND ONE OF THE NINE IS ALREADY RETIRING WITHOUT THE OTHER EIGHT
The paragraph above is stale, and this row's own relatedTo predicted it: it lists
CLOUD-1149 as the row "whose fix would let five of the nine retire separately".
CLOUD-1149 is Done. It added arms 2 and 3 of is_retired_reference_by_text
(policy/shell-retirement.rego:390, :398) for exactly the constructed-sibling
spelling — $(cd "$(dirname "$0")" && pwd)/sbom.sh and $here/x.sh. Every one of the**
**six glue sites listed above is that spelling, so each caller's repointing is now an
ADMITTED ADDITION and the caller no longer has to die with the callee.
Measured rather than argued: reference-check is being retired right now, on its
own, in PR #780 ("retire config-deprecations, reference-check, schema-check and*
**skill-check"*) — a bundle that contains none of the other eight. Under this row's glue
argument that PR could not exist. It does, and it is one of this unit's largest suites
(reference-check.bats, 14.8s).
So unit 2 is not one delta. It slices along the six $(dirname "$0") edges, and the
remaining question per program is its HOME and its BLOCKER, not its co-deletion.
Both stated blockers have also landed
- Out-of-root — THIS BULLET WAS WRONG AND IS CORRECTED IN THE SECTION BELOW. It read:
"ANSWERED, and it is NOT the discovered-set class —sbom.sh:321reads
${CARGO_HOME:-$HOME/.cargo}, a DECLARED path beneath a NAMED root variable … a*
single path, so it does not hit CLOUD-1251's one-path-per-row limit.sbom.shis
**expressible." ***Do not act on that sentence. **CLOUD-1167 being Done is true;
the verdict aboutsbom.shis not. - **Forge — ANSWERED. **
release-assets-check.sh:125andchecksums.sh:58run
gh release view/download; CLOUD-1154 is Done andinput.tree.forge
exists. This row'sblockedByon it is satisfied. The waiting half still never
moves into the engine — that is CLOUD-1144's settled shape, not a blocker here. - The clause reading "the same gap
hooks-wiring-checksits behind, and it has no row*
***yet" is stale twice over: CLOUD-1160 was filed for it and is now Done.
A SIXTH wrong verdict, found 2026-08-31 auditing the correction above — sbom.sh IS CLOUD-1251's class
The correction pass above cleared sbom.sh by reading ONE LINE of it, and the line below it is the answer. mise-tasks/sbom.sh:318–323:
# The unpacked source root. Several registries can be present; each package is
# looked up under all of them, so a vendored or alternate registry resolves too.
cargo_src_roots() {
local home="${CARGO_HOME:-$HOME/.cargo}"
printf '%s\n' "$home"/registry/src/*/
}That is a glob, and the comment above it says why in as many words: "Several registries can be present; each package is looked up under all of them." The members are resolved at runtime. Downstream, copyright_of at :326 is called per <name>-<version> directory, and :308 states the search reads "every pinned byte" — measured at :311 as 280 external crates.
So the shape is registries (discovered) × ~280 package directories (per lockfile) × every file in each. That is exactly CLOUD-1251's one-path-per-[[rule.external]]-row limit, not the single declared path the bullet claimed. sbom.sh is still blocked — on the very row the bullet said it missed.
Two things this changes:
sbom.shstays out of any dispatchable set until CLOUD-1251 answers — and it is a second named consumer of that row alongsidetranscript-corpus-check, worth stating because CLOUD-1251 is sized against one consumer.- A discovered-set fact may still not be enough here, and that is left OPEN rather than answered either way: this predicate reads the CONTENT of hundreds of out-of-root files and regexes it. Whether
input.tree.externalis meant to carry that volume is CLOUD-1251's to decide. Naming it open is the honest record; asserting either answer here would repeat the defect this section corrects.
The cause, stated because it is the same one twice. The bullet was written during the pass that corrected five other verdicts for exactly this — classifying without reading far enough — and it stopped one line short of the file's own comment. CLOUD-1166 arm 4 says a blocked verdict must quote its source; an UNBLOCKED verdict owes the same quotation, and this is the measured instance for that direction. A path:line is not evidence unless the code around it was read.
The "Successor feasibility" section below is SUPERSEDED in part — read this before it
It closes: "So this unit cannot retire whole until a fact projects a path outside the repo root. That is the same gap hooks-wiring-check sits behind, and it has no row yet." The conclusion holds — the unit cannot retire whole — but for the reason in the section immediately above (CLOUD-1251's discovered set), not the one it gives: an out-of-root fact DOES exist now (CLOUD-1167, Done), and "it has no row yet" was false when written.
Re-verified 2026-08-31 against origin/main 0683ce53
Every path:line this row cites still resolves, and all nine programs plus all nine suites are still tracked — PR #780 has not landed, so reference-check has not retired and the 59.8s has not moved:
mise-tasks/sbom.sh:321—local home="${CARGO_HOME:-$HOME/.cargo}", and:322, the glob the correction missedmise-tasks/checksums.sh:58—gh release view --json tagName;:69gh release downloadmise-tasks/release-assets-check.sh:125—gh release view --json tagName;:133and:204likewisemise-tasks/reference-check.sh:80—cargo run --quiet -p batten -- spec --format json
What this row still owes
Not a nine-program delta. Per program: its home (the HOMES section below is still**
good) and, for the two syft wrappers, whether a command rule is the disposition or
**whether CLOUD-1145's DELETE question applies — that row's §11 point, that a
comparator over a derivation which can never drift guards nothing, reaches
reference-check and render/cli directly.
And the 59.8s figure is going stale as this is written, because PR #780 removes
reference-check.bats. Read bench/suites/RESULTS.md as regenerated, never this body.
Read each program before recording why it cannot move. Three blocked verdicts on
CLOUD-1163 and five of six blocked classes on CLOUD-1164
were corrected the same day, all from the same cause: the program was classified from
its category rather than read.
Successor feasibility, stated because the campaign has been measuring only landability
This unit is partly blocked, and the split matters:
- Expressible today:
reference-checkandrender/clishell out tobattenitself (reference-check.sh:80iscargo run -p batten -- spec --format json). Folding that away is the port — the same shape as CLOUD-1145, where the port is also the performance fix. - Blocked:
sbom.sh:321reads${CARGO_HOME:-$HOME/.cargo}— a path outside the repo root, which no tree fact projects.release-assets-check.sh:125andchecksums.sh:58aregh release view/download, which need CLOUD-1154's producer.dist.sh:149is a build product.
So this unit cannot retire whole until a fact projects a path outside the repo root. That is the same gap hooks-wiring-check sits behind, and it has no row yet — it is named here rather than left implicit.
HOMES (2026-08-30): most of this unit is PRIOR ART, not a port
This row said *"successors land in *crates/batten/ and/or policy/*.rego" for all nine. CLOUD-1176 makes the home the first question, and §9 answers most of it: "Rules and actions are inspectable declarative config that name a command already on the operator's PATH — never a downloaded, executed binary."
- **PRIOR ART — **
sbom,sbom-binary,sbom-check,ntia-check,checksums. The SBOM itself is produced bysyft(sbom.sh:682,sbom-binary.sh:120), already pinned and on PATH. These programs are wrappers that spawn a tool and adjudicate its output. The wrapper deletes; what survives is a[[rule]]of kindcommandnamingsyft, which is exactly §9's "universal extension surface". Batten does not grow an SBOM subsystem — the scope reminder lists neither SBOM nor supply chain, and CLOUD-455 is the row about not re-deriving a tool we already ship. - **EXISTING §2 VERB — **
reference-check,render/cli. Both shell tobatten … spec --format json(reference-check.sh:80) to compare a committed doc against what the binary renders. That isgenerate's territory under §11, the same shape as CLOUD-1145 — and it inherits CLOUD-1145's §11 question: if the committed reference doc is a derivation that "can never drift", the comparator guards nothing and the disposition is DELETE. - **NOT POLICY AT ALL — **
dist. It builds a release artifact. That is a build task, not a gate; it owes no successor and no ledger arm beyond its own deletion, if it is retired at all. - **BLOCKED — **
release-assets-check.gh release view/downloadneeds CLOUD-1154's producer, and the waiting half never moves at all.
So the 19 #MUTANT rows do not all need re-homing — a mutation over a wrapper that deletes has nothing to move to, and CLOUD-1080's WITHDRAWAL arm is the spelling. Settle each program's home before counting arms.
Refinement — Ready (one delta, nine programs; blocked on an out-of-root fact)
Refinement gate: Definition of Ready & Done. This body carries only specializations.
- Authority boundary (§1). The nine programs above and their nine suites are deleted. Each carries the home named in the section above — PRIOR ART, an existing §2 verb, or a withdrawal; no new
crates/battenverb. 18 deleted paths; a// carried:arm where a successor exists and CLOUD-1080's withdrawal arm where none is owed. The five$MUTANT_GATESentries and 19#MUTANTrows move to the successors' tier.hk.pkl'ssbom-check/reference-checksteps and.github/workflows/release-artifacts.yml'sdist/sbom-binary/sbom/render:cli/checksumsinvocations are ungoverned and repoint freely. - Computable predicate (§2). Unchanged in substance: does the committed SBOM match a fresh scan, does the NTIA minimum-element set hold, do the published release assets match what
distproduced, and does the committed reference doc match what the binary renders. Only where the predicate lives moves. - Deliberately not in scope (§2). Changing what an SBOM contains or which NTIA fields are required. Retiring
install-check, which reachesdist.shby literal path and is its own unit. - **Effect (§3). **
readfor the check half.dist,sbom-binaryandchecksumsproduce artifacts and keep their existing effect class — a successor that renders to a scratch directory and compares must not start writing over the tree. - Output and exit (§5). Pointer-only: the artifact path and the field that differs, never an SBOM's contents. Exit follows the
0/1/2/3table; a could-not-look reading (no release, no network) is3, never a false2. - **Commit / bump (§6). **
refactor(ci)— no bump. 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 — 18 paths, 18 arms — each naming where its cases went, sinceretires_withconserving files rather than logic is CLOUD-908's recorded failure. Shown able to fail per CLOUD-418: a deliberately drifted SBOM is reported, a missing NTIA element is reported, and the anti-vacuity mirror — a clean tree passes. Mutated: all 19 rows re-homed,mutant-censusgreen across the move. Replayed: old and new run over the same tree and agree artifact-for-artifact. - Blockers (§8). Rewritten 2026-09-02. None. Every row this clause used to wait on has resolved — the forge producer is Done and its relation was removed, the tool-verdict producer has landed, and the out-of-root question has decided in a direction that closes
sbom.sh's route rather than opening it, sosbom.showes a REACHABILITY decision on this row rather than a wait on another. The correction block at the top of this body names all three and quotes the evidence.relatedToCLOUD-1151 (the wave owner), CLOUD-1149 (whose landed arms let the members retire separately), CLOUD-1265, CLOUD-1251, CLOUD-1174 (the reachability axis), CLOUD-1080 (the withdrawal spelling), CLOUD-908, CLOUD-418.
Acceptance
- All 9 programs and 9 suites deleted, each with the correct arm for its home, no other governed file edited.
- The SBOM half names
syftthrough acommandrule rather than re-deriving it — CLOUD-455's question answered in writing. - No new
crates/battenverb;batten spec --format jsonemits exactly the committed row set. - An out-of-root fact exists for
$CARGO_HOME, orsbom.shis carved out with its reason recorded. mutant-censusgreen; all 19 mutations honoured at their new homes.bench/suites/RESULTS.mdregenerates with all nine suites absent and the serial total down ~59.8s.
Unit 2 of 83 from a full partition of the 132 governed programs into minimal landable units.
CLOUD-1176 The retirement campaign has one disposition — "port it into the core" — so it launders bash-era scope creep into `crates/batten`, and the ratchet's landed WITHDRAWAL arm has never been used
RESTORED TO TODO TWICE ON 2026-08-31 — 04:36:53 (Done) and 17:28:35 (In Review). Neither transition shipped anything of this row's.
The second one is the instructive one, because it was guarded and the guard did not reach. PR #789 carried
DO-NOT-CLOSE CLOUD-1176on its own line andclosing-key-checkpassed exactly as designed — it closed CLOUD-1239 and CLOUD-1182 and stranded nothing. But two of #789's commits carryRefs: CLOUD-1176, and the tracker's attach-and-transition automation fires on the trailer, not on the body. So the row went Todo → In Progress at 16:19:14 (the PR event) → In Review at 17:28:35 (the merge), whileDO-NOT-CLOSEsat in the body doing the only job it has.
DO-NOT-CLOSEis aclosing-key-check**token, not a board-automation one. **mise-tasks/closing-key-check.shdefines it to answer "does this body decline to close a key it served" — a question about the PR body, decided locally, before the push. Nothing in this repository can stop the tracker acting on aRefs:trailer, so the marker bounds the gate's surface and not the board's. A session reading the marker as protection against a board move is reading a guarantee that was never offered, and that is what happened here.The honest options are two, and neither is the marker: cite the row in prose without a
Refs:trailer (what this PR's own memory commit ended up doing, for exactly this reason), or accept the In Review and restore it. #789 chose the trailer deliberately — the citation is honest, the row's finding IS partly mechanised by it — and paid this cost.What #789 actually landed against this row, so a later reader is not misled by the attachment:
has_policy_surfacegained a preset arm (CLOUD-1239), and an engine-source successor now declareskind:verborkind:mechanismwith 113 landed arms annotated — 77 verb, 36 mechanism (CLOUD-1182). That closes this row's "a tree-wide search for CLOUD-1176 returns zero hits" finding and makes the one-disposition claim a number. It does not meet this row's acceptance: no gate reports a PR proposing an unspecced verb, and no row has been re-dispositioned.The 04:36:53 Done was worse and is recorded below.
ORIGINAL NOTE — RESTORED TO TODO 2026-08-31. This row was marked Done at 04:36:53 by automation and nothing had shipped.
A grooming session put
Refs: CLOUD-1176in PR #751's body; the tracker attached the PR and completed the row. #751 is an unmerged draft. Nothing in this row's acceptance was met: no gate reports a PR proposing an unspecced verb, and no row has been re-dispositioned.What #751 actually does is a fraction of one acceptance bullet — it writes three of the five homes into
.claude/rules/toolchain.md's retirement section, and fixeshas_policy_surfaceso a PRESET successor can be spelled at all (0 of 609 landed arms name one, because the arm did not exist). That is CLOUD-1182's territory more than this row's, and it is not a claim on this row.Two rules were broken by the automation and are worth stating because both are already written down: AGENTS.md says Done means RELEASED and is yours to set, never the merge's — so no PR event may complete a row; and
mem:workflow/board-statessays a Done row's body is a record, so a row completed with nothing shipped is a false record that a later reader has no way to detect.
Why
Every row in this campaign assumes the successor is a thing built in
crates/batten. The rows say so in terms — CLOUD-1168's §2 requires the port
"conserve that logic unchanged, defect included***", and CLOUD-1145's §1 says
"the successor is a verb in crates/batten/"*. No row asks whether the
predicate belongs in Batten at all.
That is a machine for moving bash-era scope creep into the core, and it
contradicts the governing documents in four places.
What the spec says, and what the corpus actually contains
§2's command surface is a DECLARED INTENT, not a closed list — and this row said the opposite. §2's own CLOUD-244 note settles it: "SURFACE is authoritative for what ships and this section for what is intended", and spec::tests::the_emitted_surface_is_exactly_the_committed_row_set "fails when a verb is added, renamed, or re-parented — the prompt to reconcile this section in the same change." That is a drift gate that demands reconciliation, never a prohibition on new verbs. §2 also states plainly that the doc-vs-binary comparison "is not yet a gate: this section is out of tree" (CLOUD-95).
Measured against the emitted spec, 2026-08-30 — 18 rows disagree, so "closed" is false empirically as well as textually:
- §2 names, never shipped (6):
gate,guard,main,pr,questions,scan - shipped, §2 never names (12):
attribution,claim,commit,override,payload,perf,ready,receipt,semver,state,target,wiring - agreeing: 17
So the refusal this row is built on is §9 and rule 1, not §2. A new verb is not forbidden; a verb carrying consumer-specific behaviour is. Every conclusion this row draws survives on that footing — the board gates are CLOUD-*-shaped, hooks-wiring-check is hook-runner concern, ci-local-parity is a file-shape linter — and none of them needed §2 to be closed.
And §2 ALREADY NAMES the verbs this campaign has been re-inventing. pr create|ready|land|watch|dispatch (write), gate ("the one merge/ready bar"), main status|sync, scan. pr watch is the CI-wait shape, declared and unshipped; pr land is the landing loop; gate is the ready bar. A retirement whose successor is one of those is landing an intended verb, not widening the surface — which is the opposite of what this row told six others.
§9 and the document's closing line. "Consumer-specific behaviour is
reconstructed through extension surfaces, never baked into the core" — and
"Consumer-specific detail is intentionally absent — it belongs in the consumer's*
*own repo, never in the core."
Non-negotiable rule 1. No consumer-specific identifiers in crates/batten.
The board gates are CLOUD-*/Linear-shaped by construction, so porting them into
the core violates it outright rather than marginally.
The scope reminder. Batten is "not a hook runner, file-shape linter,*
**secret scanner, AST linter, or reference monitor." *hooks-wiring-check is
hook-runner concern; ci-local-parity is a YAML file-shape linter.
And §11 undercuts the single largest row. Completions and man pages are
derivations of the runtime-emitted spec "so the shipped binary and the generated*
**docs can never drift". derived-check is 210.8s — 16.9% of the bats corpus —*
**guarding a drift the design says cannot happen.
The mechanism already exists and the campaign does not use it
2026-08-30 — the missing half is now filed as CLOUD-1182, and it is narrower than this row. This row's predicate is "name your home from the five". CLOUD-1182's is one level down and is the reason the default has been "port it":
shell-retirement'shas_policy_surface(shell-retirement.rego:549-559) accepts a successor atpolicy/*.regoORcrates/batten/src/*.rsand does not distinguish them. So a CLI-verb port clears the retirement gate exactly as well as a module port, and the[rule.conserves]arm markers (// carried:,// subsumed:) record where the predicate went but never what kind of successor it became. Nine ports took the verb route and the ledger cannot tell you that.The two rows are complementary, not duplicates: this one chooses the home before design; CLOUD-1182 makes the choice legible in the ledger afterwards, so the count is reviewable instead of emergent. CLOUD-1182 is unblocked and is the cheaper of the two.
Measured while filing it: a tree-wide search for
CLOUD-1176returns zero hits — not in.claude/,batten.toml,policy/*.rego, orcrates/. That is expected for a Todo row, and it is also the thing to fix on landing: this row's doctrine has to end up somewhere a gate or a rules file can reach.Related, and it is why the campaign's own numbers do not converge:
bash-surface-not-growingforbids growth but has no descending ceiling, andbatten.toml:2701records the census going 141 → 148 acrossv0.0.106→v0.0.113while this campaign ran. CLOUD-1195 gives it a ceiling.One home is now cheaper than this row assumed.
batten check --rule <id>(surface.rs:797) already makes a[[rule]]row invocable by name — its doc comment says it exists precisely so "a gate ported out ofmise-tasks/… stays byte-identical and out of the changed-file set". Nineteenpolicy/*.regomodules ride it and none appears insurface.rs. So CONSUMER MODULE costs the surface nothing, which strengthens this row's case against porting.
conserves grew a WITHDRAWAL arm in CLOUD-1080 (Done), filed precisely
because "all three [arms] name a successor, so a deletion whose subject is gone*
*can only pass by lying or by a waiver config-lint refuses."
So DELETE is spellable today. What is missing is the doctrine that makes
anyone reach for it. Every row reaches for a port because the ratchet's shape
suggests one, which is the same defect one level up from CLOUD-908's
"retires_with conserves files, not logic".
The five homes
A disposition is chosen before a successor is designed:
| home | criterion | where |
|---|---|---|
| PRESET | generic policy any consumer wants | crates/batten/src/policy/presets/** (CLOUD-836) — five bundles ship today |
| CONSUMER MODULE | a real predicate carrying rule-1 identifiers | policy/*.rego in this repo |
| A §2 VERB — SHIPPED OR INTENDED | §2 already names it, whether or not it ships yet | shipped: doctor hooks, check, config lint, payload field, perf. Intended and unshipped: pr watch, pr land, gate, scan, main sync — landing one is fulfilling §2, not widening it |
| PRIOR ART | a command already on the operator's PATH does it | §9; CLOUD-455's shape |
| DELETE | the predicate should not exist | CLOUD-1080's withdrawal arm |
"Port into the core" is not on the list. Core work is confined to the fact*
*families the four live homes read — which is what CLOUD-1154/1167/1168/1170 are,
correctly understood: enabling substrate, not new surface.
Refinement — Ready (choose the disposition before designing the successor)
Refinement gate: Definition of Ready & Done. This body carries only specializations.
- **Authority boundary (§1).
.claude/rules/toolchain.md's retirement section
**and whichever gate the predicate below lands in. No **mise-tasks/program and
****no **tests/**/*.batsis edited or added —V-SHELL-RULE-EDITEDand
V-SHELL-RULE-ADDEDrefuse both. - Computable predicate (§2). A retirement PR names its subject's home from
the five above. A PR whose successor is acrates/battenverb not in the emitted spec is a finding unless it names the §2 row it fulfils or the §2 change it asks for — checkable againstbatten spec --format json's row set. This is a reconciliation demand, not a refusal (see the correction above): §2 declares six verbs that do not ship, so "absent from the spec" and "forbidden" are different answers and the gate must not conflate them. - The negative half is the point (§2). Without the refusal, "port it" stays
the default because the ratchet's admitted disposition implies one. A gate that
only records a home is a log, not a gate (rule 2). - Deliberately not in scope (§2). Choosing any individual program's home —
each is its own row. ChangingSubjectFacts::died, the admission clauses, or
CLOUD-1080's arm. Widening §2 — a row that needs a new verb argues for it there. Reconciling §2's 18-row disagreement with the emitted surface, which the correction above measures and which needs its own row: §2 itself says that comparison is not yet a gate because the section is out of tree. - **Effect (§3). **
read. - Output and exit (§5). Pointer-only: the subject path and the home it
declared, never a line of its body. Exit follows the0/1/2/3table. - **Commit / bump (§6). **
feat(policy)— patch until0.1.0if it lands a
gate;docs— no bump if the decision is doctrine-only. The row states which. - Test obligation (§7). Over the compiled binary in
crates/batten/tests/; no
.batsadded or edited. Shown able to fail per CLOUD-418: a retirement
declaring a home passes; one declaring a new verb absent from the emitted spec
is reported; and the anti-vacuity mirror — a retirement whose successor IS an
existing §2 verb is not reported, without which the first is satisfied by a gate
that flags every retirement. - Blockers (§8). None.
relatedToCLOUD-1080 (the withdrawal arm this makes
reachable), CLOUD-836 (presets), CLOUD-908 (conserves files not logic),
CLOUD-455 (the prior-art question nobody asks), CLOUD-1145 and CLOUD-1168 (the
two rows most changed by it), CLOUD-1151 (the wave owner), CLOUD-1174 (whose
generated table gains the home column), CLOUD-418.
Acceptance
- The five homes are written where a retiring author reads them, with DELETE
named as CLOUD-1080's arm rather than left implicit. - A PR proposing a new
crates/battenverb without a §2 row is reported. - At least one row is re-dispositioned as a consequence — the proof this is
doctrine rather than a preamble. CLOUD-1145 is the candidate: §11 says its drift
cannot happen. batten spec --format jsonstill emits exactly the committed row set.
Found by pressure-testing the campaign's own framing against the house style: every
filed row assumed a port, and four separate clauses say most of these predicates do
not belong in the core.
CLOUD-1265 Nothing WRITES a `tool-verdict` record, so `validator-verdict-clean` decides nothing on any real checkout — build the producer verb, and park three retirements (38.3s) behind it until it exists
Why
CLOUD-1171 landed the READ half of the third-party-validator fact and it is correct.
crates/batten/src/tools.rs resolves .git/batten-tools/<tool>‖<version>‖<digest> through
record_key/record_path/verdicts, keyed by the triple that makes a record stale by
construction, and policy/validator-verdict-clean.rego adjudicates what it finds — three
answers kept properly apart (null = could-not-look, absent = no record under this key,
present-and-empty = the tool ran and found nothing).
Nothing in the tree WRITES one. Checkable rather than asserted:
git grep -l 'batten-tools' -- crates/ policy/ mise-tasks/ .github/
# crates/batten/src/tools.rs <- the reader
# crates/batten/tests/tool_verdict_facts.rs <- a TEST, the only writer
So on every real checkout input.tree["tool-verdict"] resolves null, the module's
is_object guard declines, refused is empty, and the gate is byte-identical to a clean**
**tree on the decision surface. That is CLOUD-845's dead gate, shipped.
The load-time tier cannot see this, which is why it shipped green
policy/validator-verdict-clean.rego's own test_could_not_look_does_not_fault asserts
count(violation) == 0 with input as {"tree": {"tool-verdict": null}} — and passes,
correctly, over a repository where null is the permanent state. .claude/rules/policy-modules.md
names this exact failure: "a with input as case … fabricates the very shape the engine may*
be unable to produce, so it passes over a key nothing fills and over a channel nothing
*populates."
It is also the field instance that file records one level out: OpenTelemetry's weaver
printing ✔ No 'after_resolution' policy violation, exit 0, over a knowingly-broken registry,
because the module read a key the schema never built. Same shape, same silence, same green.
What it costs, in seconds that are otherwise landable today
Three retirements have no other blocker and cannot move until a producer exists. Measured
against bench/suites/RESULTS.md at origin/main:
| gate | s | what its verdict is about |
|---|---|---|
renovate-config-validator |
32.3 | renovate-config-validator's findings over renovate.json |
pkl-check |
5.4 | pkl over hk.pkl |
hook-profile-check |
0.6 | the hook profile validator |
| 38.3 |
CLOUD-1199's filled second-input table records the same gap from the wrapper family's side;
CLOUD-1163 unit 6 (token-bench-drift) is a fourth consumer, an identity check the same
channel is the answer for.
What to build, and the one property that must not be got wrong
A producer verb — batten tools record — mirroring the shape receipt record and
state record already use. It runs the validator outside the engine (a mise task, a CI
step), because §5 makes check read and structurally incapable of spawning, and writes the
keyed record this reads back.
It must digest the input ITSELF, never accept a caller-supplied digest. That is the whole
anti-staleness property: tools.rs::verdicts reads the subject from root and digests it
there precisely "because the digest is what makes the record stale-by-construction and a*
*caller that supplied one could supply the wrong one." A producer taking a digest on the
command line hands that guarantee back to the caller and the family stops working.
There is one parser for the record's line shape — crate::forge::parse — and a second
would be two authorities over the same bytes.
Refinement — Ready (the write half of a landed read; no new fact, no new module)
Refinement gate: Definition of Ready & Done. This body carries only specializations.
-
**Authority boundary (§1). **
crates/batten/src/tools.rsgains the write path; the verb is
registered incrates/batten/src/surface.rs.policy/validator-verdict-clean.regois
unchanged — it is already correct and is the consumer, not the defect. No
mise-tasks/program and notests/**/*.batsis added or edited. -
Computable predicate (§2). After a producer run over a declared
[[rule.tool]]row, a
freshbatten checkon the same tree resolvesinput.tree["tool-verdict"]to an object
carrying that row's id — and to a different answer once the input's bytes change. -
The discriminating pair (§2). A validator that found something must produce a REFUSAL
through the module, and a clean one must not. Today both are silent, which is the whole
bug: an assertion that only proves the module loads passes over the dead version. -
Deliberately not in scope (§2). Changing what any validator decides. Making absence a
refusal —validator-verdict-clean.rego's METADATA already records that as a deliberate
direction and a separate predicate. Retiring the three gates above; each is its own row and
each consumes this. -
**Effect (§3). **
write— it spawns the validator and writes under$GIT_DIR. It is on
the spawning side of §5's split, whereperfalready sits, and it is emphatically not
check. -
Output and exit (§5). Pointer-only: the finding's NAME and a
path:line, a count or a
status token. Never the validator's report or its diagnostic prose —tools.rs's own
header says a validator's output is the likeliest place in this family for a secret to
appear, so non-negotiable rule 4 is decided at the boundary rather than at the report. -
**Commit / bump (§6). **
feat(facts)— patch until0.1.0. -
Test obligation (§7). Over the compiled binary, and the second tier is the one that**
**matters here — awith input ascase is what let the dead version ship. Three
discriminating observations, perCLOUD-418: the producer writes a record and a subsequent
checkREFUSES over a finding; the same over a clean verdict does NOT refuse (the
anti-vacuity mirror); and a record written before the input changed is not found rather
than found-and-wrong, which is the keying the whole family turns on. -
Blockers (§8). None. It
blocksthe three retirements above.relatedToCLOUD-1171
(which landed the read half),CLOUD-845(the dead-gate class),CLOUD-1253(Done while
the condition persists — this is an instance its own §2 predicate cannot reach, since the
row carries noconservesarm). -
Weakens (§8), groomed late and recorded as such. The producer's caller is a new inline
mise.tomltask body, whichinline-task-bodies-not-growingcounts (31 -> 32) and denies. The two dispositions its ownno_fix_reasonnames are "migrate the predicate onto a rule kind, or waive the increase deliberately with a reason and an expiry" — and the predicate did migrate: whatpkl-checkdecided ispolicy/validator-verdict-clean.rego's now, off theconfig-validatorrow. What is left inline decides nothing; it RUNS the validator, which §5 makes structurally impossible inside the engine, so there is no rule kind for that body to become.mise-tasks/pkl-check.shand its 76-line suite leave the tree in the same commit, so the bash surface falls while amise.toml-only count rises by one — the shape the row's own header warns about, where "a row that denies its own campaign's wins is worse than the hole it closes". Unnarrowed, because a ratchet finding's path is the glob plus the counts; expiring 2026-10-31, and expected to lapse unused, sincebase = "origin/main"makes the floor 32 the moment this lands.Recorded honestly: this clause was groomed after the work started, on 2026-08-31, once
config-lintrefused the change — the same sequenceCLOUD-1164records for its ownWeakensclause. The alternative (collapsing the task body to a one-line shim) was examined and declined: it drops the/root/.ccr/ca-bundle.crtfallback carried verbatim out of the retired program, which is the arm this container actually takes, so a validhk.pklwould fail as though malformed.**Weakens: **
waiver-addedatwaiver[inline-task-bodies-not-growing] -
Weakens (§8), the second clause, and it is a STRENGTHENING the gate cannot tell from a weakening. The producer needs a second declared subject to be proven on at all, so
validator-verdict-cleangains a sibling[[rule.tools]]row (renovate-config,renovate-config-validatoroverrenovate.json5) beside the landedconfig-validatorrow.config-lintreads any edit to an existing rule's predicate asrule-predicate-changedand refuses it againstorigin/main, which is correct by design: house style §8 loads policy out of band precisely so a branch cannot lower the bar it is judged by, and a gate that tried to distinguish "added a subject" from "removed one" would be judging intent rather than bytes. So the admission is groomed rather than argued at PR time.The alternative — a rule of its own, which
hook-profilein this same change takes and which produces no smell — was examined and declined for the sibling: what counts as a clean verdict is the same question for both validators, and two rules over one module is two adjudications of one question.hook-profileis separate for the opposite reason, that it reads present-and-empty as a REFUSAL where a validator reads it as clean.Recorded honestly: groomed after the work started, on 2026-09-01, once
config-lintarmed againstorigin/mainrefused it underverify. An earlier unarmed run had reported0 smell(s), which is what let it reachverifyat all — the unarmed form does not compute the base-ref class, and reading its silence as a pass was my error rather than the gate's.**Weakens: **
rule-predicate-changedatrule[validator-verdict-clean].tools
Acceptance
batten tools recordexists, digests its input itself, and writes through the one parser.- A deliberately-broken subject produces a refusal end to end over the compiled binary; the
same subject repaired produces none. - A record whose input has since changed is absent rather than answering.
- At least one of the three gates above is shown unblocked as a consequence.
- No validator output text appears in any finding, asserted.
Found while filling CLOUD-1199's second-input column: the tool-verdict class read as
unblocked because the fact had landed, and the fact had landed with nothing to fill it.
CLOUD-1271 `bench/suites/RESULTS.md` is not reproducible and records no conditions, so every campaign figure is unanchored — measured, `session-start` is 24x cheaper on a warm container than the committed corpus says
MEASURED 2026-08-31 — THE ROW BELOW IS REFUTED, AND WHAT IT IS WRONG ABOUT IS BIGGER THAN THE ROW
This row was filed claiming tests/session-start.bats costs 135.9s and needs a one-line
mise.toml fix. The premise was never measured. I measured it.
A full-corpus mise run test:bats in this container, against the committed
bench/suites/RESULTS.md. Both are full-corpus runs and comparable in kind:
| suite | RESULTS.md |
measured here | ratio |
|---|---|---|---|
| corpus total | 1097.1s | 688.8s | 0.63x |
land-lock |
195.1 | 145.6 | 0.75x |
land |
96.2 | 84.7 | 0.88x |
helpers |
36.1 | 26.1 | 0.72x |
pre-commit-staging |
23.3 | 4.5 | 0.19x |
session-start |
135.9 | 5.7 | 0.04x |
Most suites land in a narrow 0.63–0.88x band — a faster machine, or different parallelism.
**Two do not. **session-start is 24x cheaper here; pre-commit-staging 5x.
The split confirms the mechanism and refutes the remedy
The reading was right: setup() stubs mise install at :59, only :150/:175 opt back
in, so the cost IS provisioning. But provisioning is already warm in this container, the
suite costs 5.7s, and there is nothing here to fix. The 135.9s is a cold-cache artifact of**
**whichever machine wrote the corpus — and nothing in RESULTS.md says which machine, or
whether it was warm, or what parallelism it ran under.
So this row is now the corpus finding, not the suite finding
bench/suites/RESULTS.md is the campaign's single cost authority. Every bundle, every
makespan argument, every "largest available retirement" is denominated in it. It is not**
**reproducible across containers and records no conditions, so a consumer cannot tell whether
two corpora are comparable — and for the provisioning-bound suites they demonstrably are not.
Two consequences that change live decisions rather than bookkeeping:
- The makespan-floor argument is environment-dependent. Cold, Bundle A
(CLOUD-1170) hands the floor tosession-startat 135.9s. Warm, it hands it to
landat 84.7s andsession-startis nowhere near the top. This row's own Urgent**
**justification, written below, holds in one environment and evaporates in the other, and
nothing recorded which one CI is. - Every "retire it and save N seconds" claim is unanchored wherever N came from this file.
The fix, and the prior art for it
perf-record stamps metric=wall-clock into every series entry precisely so a later
instruction-count series can never be diffed against it and read as a step change. Same**
**defect, same fix: stamp the series. A corpus artifact carries the conditions it was measured
under, and two corpora with different stamps are refused rather than silently diffed.
§1 IS CONSTRAINED BY SHAPE, and this is the part an implementer will hit first
mise-tasks/suite-bench.sh is a governed program — two landable shapes, retire it whole or
leave it alone, and V-SHELL-RULE-EDITED has one route with no bypass. So "make suite-bench
*record its conditions" *is not a landable change on its own.
It is landable as part of CLOUD-1163 unit 7 (suite-bench-check + suite-bench;
both die in one delta, since tests/suite-bench-check.bats:2 declares two subjects). The**
**conditions land in unit 7's successor. That also gives unit 7 a reason to be sequenced ahead
of its siblings.
What replaces the clauses below
- §2 — a computable predicate. The clause below says "costs materially less than 135.9s",
which is a model verdict and not a threshold (non-negotiable rule 3). Replace with: a corpus
artifact carries the conditions it was measured under — at minimum the commit, whether the
toolchain was warm, and the parallelism — and a consumer comparing two corpora with
different stamps is refused, not silently given a diff. - §4 — generated artifacts, omitted below and owed:
bench/suites/RESULTS.mdis generated
and this changes its format.suite-bench-check's successor is the gate over it. - §7 — the discriminating pair: two corpora with different stamps must fail to compare;
two with the same stamp must compare. Anti-vacuity mirror: an ordinary same-conditions
regeneration still works. - Priority. Urgent below was justified by the makespan floor, which the measurement shows
is environment-dependent. It stands on the corpus finding instead — an unanchored cost model
under a campaign steered by cost — not on the 135.9s.
The eight rows that inherit this
CLOUD-1170, CLOUD-1268, CLOUD-1194, CLOUD-1164,
CLOUD-1159, CLOUD-1270,
CLOUD-1163 and CLOUD-1151 all carry figures from this corpus.
Deliberately not corrected one by one. Every dispatched prompt already carries "re-derive*
**every second from the tree"*, and restating one finding across eight bodies is CLOUD-1166's
defect. The finding lives here; those rows cite it.
How the wrong row got filed, since it is the mechanism and not an apology
ready-lint was run over this row and exited 0, and that was read as "groomed to Ready". It
lints only the clauses present and reported exactly three checks — cites-body, bump,
cites-blockers. It cannot see a §2 that is not computable, a §7 that is a procedure rather
than a discriminating case, or a §4 that is missing. Substituting a partial gate's silence**
**for the judgement it cannot make is the same shape as reading a dead gate's green as
coverage (CLOUD-845). The two-minute measurement that refutes the row was
available in the container the whole time and was written into a dispatch prompt as step 1 for
somebody else.
Everything below is the ORIGINAL filing and is superseded by the block above. It is kept
rather than deleted because the dating is the argument: the row was filed, linted green, and
refuted by its own first measurement inside one hour.
Why
tests/session-start.bats is 135.9s, 12.4% of the 1097.1s corpus — the second-largest**
**suite in the tree, behind only land-lock (195.1s). No row owns it.
Verified 2026-08-31, list_issues query:"session-start" project:Batten: every row naming
session-start is about the hook's behaviour — CLOUD-517 (step ordering), CLOUD-218
(rustup race), CLOUD-1085 (bootstrap absent), CLOUD-1079 (user-level hooks). None owns the**
****suite's cost. **CLOUD-1156 is Done, and CLOUD-1268's title now states this suite is not one of
its members.
The measurement — read, not divided
An earlier pass recorded 135.9s over 10 cases as "~13.6s each, and that is provisioning."
That was arithmetic on the corpus total. Read at origin/main:
-
10
@testcases. -
:59—setup()installs amisestub that swallows the expensive verb:if [ "$1" = install ] && [ -z "${SESSION_START_REAL_INSTALL:-}" ]; then exit 0; fi
-
real_install_or_skipis defined at:74and called at exactly two sites::150and
:175.
So 8 of 10 cases never install anything. The whole 135.9s is two real mise install runs.
The per-case cost is nothing like uniform, and that single fact decides the disposition.
Three consequences
-
It is not a retirement, and not CLOUD-1268's. A port to
crates/batten/tests/*.rs
runs the same two installs and pays the same wall clock — it moves 136 seconds from one
lane to another. CLOUD-1268 struck it out for this reason; what that strike-out did not do
is give it a home, which is what this row is. -
The remedy is one line, and the file is ungoverned. The suite states it itself at
:73— "Idempotent and warm, so the second install costs milliseconds." Warm
mise installonce in[tasks."test:bats"].governed_at_headselectsmise-tasks/
paths carrying a shebang or#MISE description, plus any.batsundertests/, and
mise.tomlis in neither set — so noV-SHELL-RULE-EDITED, noconservesarm, no
retirement. This is CLOUD-1198's shape exactly: build once, feed the seam the programs
already declare..claude/hooks/session-start.shis ungoverned by the same predicate — ungoverned for edits
and for deletion. Filing this suite under "the subject cannot die" is what sent three
consecutive passes toward a retirement it never needed. -
**It becomes the makespan floor the moment Bundle A lands. **
test:batsruns
--no-parallelize-within-files, so the lane cannot finish faster than its longest single
suite:-
today — longest suite
land-lock195.1s, corpus 1097.1s -
after Bundle A (CLOUD-1170) — longest suite
session-start135.9s, corpus 866.2s -
after this row — longest suite
land96.2s (CLOUD-1148's), corpus ~866sUntil this lands, no further bundle moves the makespan at all — they shrink the total
and leave the floor standing. That is why this is Urgent despite being a one-line change.
-
Settle it before writing anything
Two minutes, and it decides whether this row is a one-liner or something larger:
mise install # warm the toolchain
mise run test:bats -- tests/session-start.bats # time it warm, against 135.9s
If it collapses, land the [tasks."test:bats"] change and record the measured delta. If it
does not, the two installing cases are doing something the stub cannot cover and the row
re-scopes on evidence rather than on this body.
How it came to be unowned, recorded because the mechanism is the point
This row exists because a grooming session created the orphan and then lost the note:
- Recorded "the largest number in the tree with no owning row. File one." — not filed.
- Found CLOUD-1156 owned the class, called that note wrong, and folded this suite into
CLOUD-1268. - Proved it does not belong in CLOUD-1268, struck it out, and wrote "file its remedy as*
*its own row" — not filed. - Rewrote the working plan file, deleting the "File one" note — the only surviving record.
- Folded the filing into a correction item, did the correction half, dropped the filing half,
and reported the pass complete.
Net: the suite went from owned by a Done row to owned by nothing, and the trace was
removed. That is CLOUD-1166's class with a new destination — not a count or a membership list
going stale, but a deferral restated three times and deleted on the fourth. The rule that
covers it is the same one: a finding's home is a row, and a note that says "file one" is not**
**a home.
Refinement — Ready (measure it warm, then land one line)
Refinement gate: Definition of Ready & Done. This body carries only specializations.
- Authority boundary (§1).
mise.toml's[tasks."test:bats"]. Ungoverned — no
mise-tasks/program and notests/**/*.batsis edited or added, and none may be:
V-SHELL-RULE-EDITEDrefuses touching the suite, which is why the fix lives on the runner
rather than in the cases. - Computable predicate (§2).
tests/session-start.batscosts materially less than 135.9s
inbench/suites/RESULTS.mdafter the change, with the two installing cases still
exercising a real install rather than being stubbed out. - Coverage must not fall (§2).
real_install_or_skipat:150and:175exists so those
two cases test the real thing. A change that makes them cheap by making them stubs has
deleted the coverage rather than the cost, and is the failure to refuse. - Deliberately not in scope (§2). Retiring the suite or the hook — neither is governed and
neither needs to die. CLOUD-1268's ledger marker. Changing what the hook does (CLOUD-517,
CLOUD-218, CLOUD-1085 each own a behaviour question here). - Effect (§3).
readfor the measurement; the change is a task-body edit. - Output and exit (§5). The delta is reported from
RESULTS.mdregenerated, never
predicted from this body. - Commit / bump (§6).
perf(ci)orfix(ci)— no bump. Nothing undercrates/. - Test obligation (§7). The suite itself is the test and runs unedited — that is the
discriminator: if it needs editing to get faster, the diagnosis in this row is wrong.
Shown able to fail per CLOUD-418: the warm timing must be recorded before and after, and a
run that does not exercisereal_install_or_skip's two cases is not a pass. - Blockers (§8). None. It blocks the makespan benefit of every dispatched bundle once
CLOUD-1170's Bundle A lands.relatedToCLOUD-1198 (the precedent this copies), CLOUD-1268
(which struck it out and left it homeless), CLOUD-1156 (Done, owns the class it was wrongly
filed under), CLOUD-1170 (Bundle A, which hands it the floor), CLOUD-1140 (suite cost),
CLOUD-1166 (the deferral-goes-stale class this is an instance of).
Acceptance
- The warm-run timing is recorded on this row as a measurement, not a prediction.
bench/suites/RESULTS.mdregenerates withtests/session-start.batsmaterially cheaper,
and the suite file is unedited.- The two
real_install_or_skipcases still perform a real install, asserted. RESULTS.mdreports the new longest suite after Bundle A lands, and it is not this one.- CLOUD-1268 links here, so its strike-out has a destination rather than a dead end.
Found by asking what happened to the number a previous pass said to file a row for, and
finding that nothing had.
|
Warning Review limit reachedNext included review available in 54 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 ignored due to path filters (1)
📒 Files selected for processing (12)
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 |
…ays was `mise-tasks/hook-latency-drift.sh` (126 lines) and `tests/hook-latency-drift.bats` (6 cases) are deleted. CLOUD-1270's decided disposition 3: the timing at `:91` and the budget comparison become an inline step of `.github/workflows/hook-latency-drift.yml`, which is ungoverned and was already the program's only invoker. No new `crates/batten` verb, no `perf-record`-style producer, no rule row — nothing enters the core, so CLOUD-1176's five-homes question is not reopened. The program's own `#MISE description` settled this: "Report (never gate) whether the fast pre-commit tier still costs what its budget claims — a question about the world, so it runs on a clock". `.claude/rules/toolchain.md` splits exactly there, and a reporter that gates no commit was buying a 24.2s regression suite for arithmetic. WHAT MOVED, verbatim rather than re-derived: the `hk check --all --profile "!$PROFILE"` loop, the median of `$RUNS` integer samples, the `BUDGET + SLACK` ceiling and the `BUDGET / LOOSE_FACTOR` floor, both drift directions and their tokens. The four constants are `env:` entries on the step — the same written-once-as-data placement `perf-assert`'s BUDGETS table uses — so the measurement that derived them (8s, 7s, 8s on a session container against 275s with the tier enabled) still reads beside the number it produced. `hk` ABSENT STAYS COULD-NOT-LOOK. The retired program's `:70` exited 2; the step exits 1 under `set -euo pipefail`, which fails the run rather than passing it. That is the one property a successor could most easily lose, and it is now structural rather than asserted. THE COVERAGE LOSS IS REAL AND THE LEDGER SAYS SO. All eight arms are `withdrawn`, because a workflow step is not drivable from `crates/batten/tests/**` and a fixture re-running the same shell in a test would be a second implementation of the thing under test rather than coverage of it. Six case arms name what each pinned and where it now lives unasserted; the two file arms name each other as the path this delta retires, which is what `withdrawn_subjects` demands. THE SERIAL TOTAL WENT UP, AND THAT IS NOT THIS CHANGE. The corpus regenerates at 117 suites with `hook-latency-drift` absent — the checkable half of CLOUD-1270's acceptance — but 505.5s against the committed 458.7s, because it was measured on a different machine. The acceptance asks for "the serial total down by the measured amount" and no honest reading of two corpora can produce that today: nothing stamps the conditions a corpus was taken under, so the two numbers are not comparable and the suite count is the only thing that is. That is CLOUD-1271's finding met in practice rather than in theory, and CLOUD-1163 unit 7 is where the stamp lands. Closes CLOUD-1270 Refs: CLOUD-1270, CLOUD-509, CLOUD-1176, CLOUD-1271, CLOUD-1080 Admits: b07dbc18c944e984110e469bccc2d5e7d2269f7a029d8f6649fb357e284f22d3 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: .github/workflows/hook-latency-drift.yml Admits-head: b49639b Admits-epoch: 6063b92bef084c720fdc6fb1fa0de1ebd751debe58cd14562dbd539fde96cc9c Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: CLOUD-1270's 24.3s stays on the corpus permanently, which is the largest non-landing-loop suite outside CLOUD-1318 and the whole reason the row exists. The alternatives are both refused rather than merely worse: disposition 2 (retire the suite alone) needs the // ported: arm, which 'suite port held' refuses when the surviving subject is a governed, unretired program — the same weld CLOUD-1164 records for token-bench — and disposition 1 leaves the seconds unowned, which is the state the row was filed to end. Without this write the program keeps a 6-case suite whose every case pays 3 x sleep 2 to test arithmetic, and the measurement it guards is a wall clock that no commit can regress. Admits-answer-precondition: The workflow file IS the owning surface, and under CLOUD-1270's decided disposition 3 the change is unspellable anywhere else. The row's own decision block says so: '.github/workflows/hook-latency-drift.yml is ungoverned, so the hk check --all --profile timing at :91 and the budget comparison become an inline step of that workflow', with 'no new crates/batten verb and no perf-record-style producer' and 'nothing enters the core'. There is no batten verb that writes a workflow, and a mise task is exactly what is being retired — the successor has to be the step itself or the measurement is lost. The fact satisfying the precondition: the write is one steps[] entry gaining an env block and an inline run body, replacing a one-line 'mise run hook-latency-drift' invocation, landing in a pull request where actionlint and zizmor judge the result and the diff is in front of every reviewer. Admits-answer-rejected-route: R-USE-THE-OWNING-SURFACE does not apply: a workflow has no owning tool above it — there is no batten verb that writes .github/workflows, and the task layer that used to hold this body is the thing being retired, so routing through it would be re-creating the subject. R-RESTORE-IT does not apply either: nothing here is a mistaken write to undo; the file keeps its schedule, concurrency, permissions and both existing steps, and only the final step's body changes from a task invocation to the task's own contents.
`mise-tasks/sbom-check.sh` (382 lines) and `tests/sbom-check.bats` (14 cases) are deleted. The scan stays outside — house style §5 makes `check` `read` and structurally unable to spawn, so `syft` remains a command on PATH and `mise-tasks/sbom.sh` remains the producer that derives the documents. What moves in is the ADJUDICATION, which is the half that had no successor. `sbom.sh` SURVIVES deliberately: it decides nothing, so it is a producer rather than a gate, and its disposition is CLOUD-1159's rather than this row's. `sbom-check.sh:44`'s constructed-sibling spawn of it is an admitted repointing under CLOUD-1149's arms 2 and 3, so the caller dies and the callee lives with nothing dragged in. TWO OF THE TEN PREDICATES READ NO RECORD AT ALL, which is what keeps the producer's trusted surface narrow rather than total. The expected cargo count is `Cargo.lock`'s own `source = ` lines and the action mapping is every SHA-pinned `uses:` against `sbom-actions.tsv`'s key column — both properties of committed text, so `line_sources` carries them and the module decides them itself. Only the counts that require opening a DERIVED document travel through `input.tree["tool-verdict"]`. `an_unmapped_action_pin_is_refused_from_committed_text_alone` drives that half with no record involvement at all. THE KEY IS A TRIPLE and each leg refuses a different lie: the tool, its pinned version (kept equal to `mise.toml`'s `aqua:anchore/syft` — CLOUD-664 is what a syft bump does to this document), and the digest of `Cargo.lock`, taken rather than declared, so a verdict goes stale by construction. Both are cases rather than claims: `a_record_from_another_version_does_not_answer` and `a_verdict_does_not_survive_its_input`. THE VERDICT NAMES ARE THE VOCABULARY'S, NOT MINE. `policy test` refused the four classes I first wrote as free prose — a name is `<subject> <action> <condition>` drawn from `[vocabulary]`, which is a measurement (o200k_base: 9.9 tokens for the old spelling against 3.0 curated, ~2,000 tokens a session at ~300 refusals). They are `tool read broken`, `manifest count wrong`, `manifest state missing` and `pin table missing`, and they add no words to the table. A FALSE POSITIVE THE LOAD-TIME TIER CAUGHT, recorded because the fix is not the obvious one. Binding `lock_lines` separately does NOT make `declared` undefined when `Cargo.lock` was not read: a Rego comprehension over an undefined variable yields an EMPTY array, so `declared` still resolved to 0 and every honest cargo count read as drift against a file nobody opened. The rule has to DEMAND the array (`is_array(lock_lines)`), and `test_an_unreadable_lockfile_reports_no_drift` is what turned red. The action-pin half carries the same guard for the same reason: an unreadable table is could-not-look, not "nothing is mapped". THREE ANSWERS, AND EMPTY IS A FINDING. Absent is could-not-look — the ordinary state of a checkout whose globs never fired, where refusing would deny every clone until a producer runs. Present-and-empty is the producer having recorded nothing, which would let every count pass over an absent key, so it refuses. That is `hook-profile`'s reading rather than `validator-verdict-clean`'s, and for `hook-profile`'s reason. `hk.pkl` keeps the `slow` profile and the same two globs and points at `record-sbom` instead. The scan's cost therefore stays exactly where it was: what comes off the corpus is the bats suite, not the gate. Closes CLOUD-1318 Refs: CLOUD-1318, CLOUD-262, CLOUD-664, CLOUD-667, CLOUD-1149, CLOUD-1159, CLOUD-1265, CLOUD-1171 Admits: 29c87ce5ed2d90a981b7e7128f9acdbf338f5775236cb45086a3633e3b1418a3 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 5c589fe Admits-epoch: 6063b92bef084c720fdc6fb1fa0de1ebd751debe58cd14562dbd539fde96cc9c Admits-author: alec@wenzowski.com Admits-prev: ce6e64e46cde0664400115cd5f9d6dea72ec6206bc32273171582fb007164b17 Admits-answer-lost: CLOUD-1318 cannot land and mise-tasks/sbom-check.sh stays a 382-line shell program whose suite is the largest non-landing-loop cost in the corpus outside the landing loop itself, measured 25.4s of 505.5s on this container. Worse than the seconds: the predicate it carries has no successor without these rows, so the published SBOM would keep being judged by a program the retirement campaign has already frozen — shell edit refused declares one route and no override, so the gate cannot be repaired in place if syft changes shape again, which is exactly what CLOUD-664 and CLOUD-1149 record happening twice already. Admits-answer-precondition: batten.toml IS the owning surface this class names, and every part of the write is unspellable anywhere else by construction. A [[rule]] row is the only way to register policy/sbom-inventory.rego at all; [[verdict]] rows are the only place a raised token may be declared, and a module raising an undeclared token FAILS TO LOAD, so without them the successor does not exist rather than merely misbehaving; a [[rule.tools]] row is the only route to a tool-verdict record; and .claude/rules/policy-modules.md refuses an inline regex outright, so the action-pin shape can only be a [[pattern]] row. house style section 8 leaves batten.toml with no writer: batten config reads it and nothing writes it. The fact satisfying the precondition: the write is one rule row, one tools row, one pattern row and four verdict rows, landing in a pull request where the diff is in front of every reviewer and review bot, with config-lint judging the result against origin/main and policy-test loading the module. Admits-answer-rejected-route: R-USE-THE-OWNING-SURFACE does not apply: batten.toml has no owning surface above it, unlike .serena/memories/**, whose [[redirect]] row names write_memory/edit_memory/rename_memory. Its own [[redirect]] row says the remedy for a protected path is to change it in a pull request, which is what this is: branch claude/bundle-bats-retirements-fah2t0, closing CLOUD-1318. R-RESTORE-IT does not apply either: these are additions registering a new module, not a mistaken write to undo, and restoring the path would discard the deliverable. Admits: df871b0dba25d6faaa441ff40013ff2cc15fd5b10a18b2e74c772d641a2bfd84 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 5c589fe Admits-epoch: 874a6cf485f641f090454db9acfe0be21b0d0a4cd2d5549922ebd71309d17405 Admits-author: alec@wenzowski.com Admits-prev: 29c87ce5ed2d90a981b7e7128f9acdbf338f5775236cb45086a3633e3b1418a3 Admits-answer-lost: policy/sbom-inventory.rego cannot decide sbom-action-unmapped at all. That predicate is the one CLOUD-667 added as the last conformance gap and the one a renovate bump breaks: a pin moves, the committed licence table no longer describes it, and the published document silently degrades. Without the row the module either fails to load, or -- if the clause were dropped -- the retirement would land a successor quietly weaker than the program it replaces, which is the laundering the campaign exists to refuse. Admits-answer-precondition: The [[pattern]] registry in batten.toml is the ONLY place a regex may be declared for a policy module: .claude/rules/policy-modules.md makes an inline regex a LOAD-TIME refusal, so policy/sbom-inventory.rego cannot carry this literal itself and the module does not load without the row. This is the second of three writes for one change (CLOUD-1318); the first registered the rule and its tool row, the third declares the verdict tokens the module raises. The fact satisfying the precondition: one [[pattern]] row naming the SHA-pinned uses: shape, landing in the same pull request as its only consumer, where config-lint judges it against origin/main. Admits-answer-rejected-route: R-USE-THE-OWNING-SURFACE does not apply: batten.toml has no owning tool above it and its own [[redirect]] row names a pull request as the remedy for a protected path, which is what this is. R-RESTORE-IT does not apply: this is an addition its consumer needs, not a mistaken write to undo. Admits: 62ad48e9a300ac431744b2460ee6af0bac0e45171b75dbd4e8404cecd7c57e12 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 5c589fe Admits-epoch: 302c5fe4b3f62558d19b9eb325df566f8289d3a0b7bb9716e08c3ac18a86b8eb Admits-author: alec@wenzowski.com Admits-prev: df871b0dba25d6faaa441ff40013ff2cc15fd5b10a18b2e74c772d641a2bfd84 Admits-answer-lost: The successor does not load, so CLOUD-1318 cannot land and mise-tasks/sbom-check.sh stays frozen -- a 382-line program the campaign will not let anyone edit, guarding a published document that vendor reviewers read and nobody here does. The four classes are also the only place the REMEDY is written down: which of a bad scan, a drifted count, an unenriched field and a moved action pin an author is looking at, and what to do about each. CLOUD-1050 records why that cannot go back into a message string -- a refusal naming no remedy, naming a task that does not exist, or offering an override with no precondition were all expressible and none checkable. Admits-answer-precondition: A [[verdict]] row is the ONLY place a raised token may be declared, and a module raising an undeclared token FAILS TO LOAD -- so without these four rows policy/sbom-inventory.rego does not exist as a gate rather than merely misbehaving. The refusal is symmetric: a row nothing raises also fails the load, so the four are exactly the four the module raises and no more. This is the third and last write for one change (CLOUD-1318), after the rule row and the pattern row. The fact satisfying the precondition: four [[verdict]] rows, each carrying the remedy prose that a msg field could not be checked for, landing in the same pull request as the module that raises them. Admits-answer-rejected-route: R-USE-THE-OWNING-SURFACE does not apply: batten.toml has no owning tool above it and its own [[redirect]] row names a pull request as the remedy, which is what this is. R-RESTORE-IT does not apply: these are additions the module cannot load without, not a mistaken write to undo. Admits: 429a281a19eab3cbec4fdf2044053ba424dff7b0d36c75e8c203c5de20daf0c1 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/sbom-inventory.rego Admits-head: 5c589fe Admits-epoch: 7d56f6b652defbc775a9889e512531d2d344a0b4cfdde962f80b8af2b877d13f Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: The module ships a FALSE POSITIVE that fires on exactly the tree it is meant to protect. Measured by its own case test_an_unreadable_lockfile_reports_no_drift: a Rego comprehension whose body references an undefined variable yields an EMPTY array rather than undefined, so declared resolves to 0 whenever Cargo.lock was not read, and every honest cargo count reads as package drift. That is a gate that refuses a clean tree for a file nobody opened -- the could-not-look answer dressed as a finding, and the direction that gets a gate switched off. Admits-answer-precondition: policy/sbom-inventory.rego IS the owning surface: it is the module whose predicate is wrong, and a predicate can only be repaired in the module that states it. It became protected in this same change, when the [[rule]] row registering it landed -- 'a registered module is protected because enabling policy protects it'. There is no batten verb that edits a module; mise run policy-test is what checks the edit, and it is what FOUND this defect. The fact satisfying the precondition: one guard line added to one rule body, plus its comment, landing in the same pull request that introduces the module, with policy-test green over it before it is pushed. Admits-answer-rejected-route: R-USE-THE-OWNING-SURFACE does not apply: batten.toml's [[redirect]] for policy/** names the remedy as 'change it in a pull request -- mise run policy-test is what checks the edit before it lands', which is exactly this. R-RESTORE-IT does not apply: restoring would return the module to the state whose own test is red, not to a working one.
`mise run verify` refused this branch at `target-prune`, and the refusal was correct rather than a disk fault: `[prune.warm]`/`[prune.cold]` declared a basis of 164 test files against a live 175, eleven past the basis and one outside the tolerance of 10. Free space was never the constraint — the same lap reported 21077MB against a 7938MB declared warm floor. This branch caused the eleventh: `crates/batten/tests/it/sbom_inventory.rs` is the compiled-binary tier CLOUD-1318's retirement owes. The other ten arrived on `main` under the tolerance, which is the trend the gate exists to surface. The remedy is the block's own, taken in the half it sanctions: `count` moves to the live reading with `measured`, and both floors are untouched. Re-deriving a floor downward would claim an independent measurement — a build from an empty `target` for cold, a minimal post-prune tree for warm — that I did not take, and the block warns against exactly that in the two entries above this one. A floor too high only refuses laps; one too low fails silently inside a test run. The count written is `batten target prune`'s own `live` reading of 175, not `git ls-files`'s 174 over the same glob. The gate is what the basis is compared against, so a basis refreshed from a second reader would red on the next lap while reading as correct in review. The one-file disagreement is recorded in the block as a pointer for CLOUD-1158's floor re-derivation rather than resolved here. Refs: CLOUD-1318 Admits: 46a463f581d16ffa4d84ab106776bbcec114cf850fbc935c869e4799252af068 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 257c1df Admits-epoch: fa50e77c82d3f41005c192f1935bac452017d8506862b31e5ba1b7205b9521cc Admits-author: alec@wenzowski.com Admits-prev: 62ad48e9a300ac431744b2460ee6af0bac0e45171b75dbd4e8404cecd7c57e12 Admits-answer-lost: The protected-path gate's assurance that no unreviewed hand edits `batten.toml`. What is written is two integers and no floor: `count = 164` -> `175` in both basis tables, `measured` unchanged at today's date, and a block comment recording the move. Floors are untouched, so nothing this write does can lower a threshold. Admits-answer-precondition: The refusal is `[prune.warm]`/`[prune.cold]`'s own basis-drift gate, which fires exactly as its block instructs: `declared 164, live 175, tolerance 10`, and its stated remedy is to move `count` and `measured` together in `batten.toml`. The gate's remedy IS a write to the protected file, so the admission is the route rather than a way around it. Admits-answer-rejected-route: Raising `tolerance` would silence the trend counter rather than refresh it. Lowering the floors would claim an independent measurement (a build from an empty `target`) that I did not take, which this block explicitly warns against. `BATTEN_HOOK_BYPASS` would skip the gate rather than answer it.
b5ed8e8 to
9e7a9a9
Compare
|
❌ The last analysis has failed. |
|
/fast-forward |
Closes CLOUD-1270
Closes CLOUD-1318
Two retirements from the bats-cost bundle, one commit each. One branch and one PR rather than one per row, per
AGENTS.md's "one commit one issue, one branch many rows, one PR all of it".CLOUD-1270 —
hook-latency-driftmise-tasks/hook-latency-drift.sh(126 lines) and its 6-case suite are deleted. Disposition 3, decided on the row: the timing loop and the budget comparison become an inline step of.github/workflows/hook-latency-drift.yml, which is ungoverned and was already the program's only invoker. No new verb, no producer, nothing enters the core.The program's own
#MISE descriptionsettled it — "Report (never gate) … a question about the world, so it runs on a clock" — and.claude/rules/toolchain.mdsplits exactly there.hkabsent stays could-not-look: the step exits 1 underset -euo pipefailrather than passing.All eight ledger arms are
withdrawn, and the coverage loss is stated rather than dressed up. Every case pinned arithmetic against asleep-basedhkstub; a workflow step is not drivable fromcrates/batten/tests/**, and a fixture re-running the same shell in a test would be a second implementation of the thing under test rather than coverage of it.CLOUD-1318 —
sbom-checkmise-tasks/sbom-check.sh(382 lines) and its 14-case suite are deleted. The scan stays outside — §5 makescheckreadand unable to spawn — sosbom.shremains the producer and only the adjudication moves intopolicy/sbom-inventory.rego.sbom.shsurvives deliberately (CLOUD-1159 owns its disposition).Two of the ten predicates read no record at all, which keeps the producer's trusted surface narrow: the expected cargo count is
Cargo.lock's ownsource =lines, and the action mapping is every SHA-pinneduses:againstsbom-actions.tsv. Both are committed text, carried byline_sources, so the producer cannot get them wrong on the module's behalf. Only counts needing a derived document go throughinput.tree["tool-verdict"].Three things the gates corrected during the work, recorded because each is a finding rather than a mechanic:
lock_linesseparately does not makedeclaredundefined whenCargo.lockwas unread — a Rego comprehension over an undefined variable yields an empty array, sodeclaredresolved to0and every honest count read as drift against a file nobody opened. The rule has to demand the array.policy testrefused four free-prose class names; a name is<subject> <action> <condition>drawn from[vocabulary], which is a measurement (9.9 tokens vs 3.0 curated). They add no words to the table.record-sbomreddened bothtests/hk-selection.batscases, which name the slow tier's steps literally — and that suite is governed, soshell edit refusedoffers no route. The step stayssbom-checkand runsmise run record-sbom, which is the same conventionclaim-check,perf-pair,mutantandconfig-lintall kept.Corpus
bench/suites/RESULTS.mdregenerates at 116 suites with both retired suites absent (118 → 117 → 116).The serial total is not comparable across these runs and should not be read as one. It reports 530.7s against 505.5s measured an hour earlier on this same container, after deleting ~50s of suites. Nothing in the tree explains that: it is machine noise between two unstamped measurements. The suite count is the only figure that carries across runs. That is CLOUD-1271's finding met twice in practice, and it makes the conditions stamp in CLOUD-1163 unit 7 worth more than its 0.6s suggests.
Refs: CLOUD-1270, CLOUD-1318, CLOUD-262, CLOUD-509, CLOUD-664, CLOUD-1149, CLOUD-1159, CLOUD-1176, CLOUD-1265, CLOUD-1271
🤖 Generated with Claude Code
https://claude.ai/code/session_01X6dcTC79zN5KBiMrZs6hac
Generated by Claude Code