Skip to content

CLOUD-480: re-take the mutant acceptance on a tree where the tool does not lie - #669

Merged
wenzowski merged 2 commits into
mainfrom
claude/cloud-911-quota-resume-hxkxib
Aug 23, 2026
Merged

wenzowski merged 2 commits into
mainfrom
claude/cloud-911-quota-resume-hxkxib

Conversation

@wenzowski

Copy link
Copy Markdown
Contributor

CLOUD-480's acceptance was recorded against a number a defective tool produced. This re-takes it and fixes what the honest reading found.

The measurement

mise run mutant was silently skipping declarations. Its row loop is fed by done <<<"$rows", so the rows sit on the shell's stdin, and the two bats invocations inside the loop body read stdin and swallowed every row after the one they were running. CLOUD-944 measured it on claimed-keys — 3 declared, the counter reached 2 — and fixed it with two </dev/null redirects in #651, which merged on top of #660.

That makes this the first tree on which CLOUD-480's set can be swept honestly, and the delta is not cosmetic:

reading declared not caught
recorded on CLOUD-480 (pre-#651) 204 0
honest, before this branch 223 3
honest, after this branch 221 0

Nineteen declarations had never been applied, and three of those nineteen were broken. The tool was reporting green because it was not applying the rows that would have failed — CLOUD-480's own subject, a declaration that reads as coverage, reproduced inside its own acceptance evidence.

221 rather than 223 because two declarations are removed rather than repaired; see below.

The three

ci-tools-check/spawned-tool-need-not-be-installednames-no-case. My own defect, from the row I added for this gate's third direction: the filter was written from memory as a tool a batten.toml row spawns must be installed in CI, and the case is a tool a policy row spawns must be in the install list. Repointed at no list installs is refused, which is the case asserting status -eq 1 on the defect. Naming the similar case above it would have repeated the error invisibly — that one asserts -eq 0 in both arms and passes under the mutation either way.

filed-here-check's two overlap rows — SURVIVED, and they cannot do otherwise. zero-overlap-refused and overlap-unanswered-refused each disarm one case arm's continue, and the property each names is over-determined, so no one-line mutation can falsify it. Measured by disarming each guard in turn rather than read off the source:

overlap=0  ->  named=0  ->  paths=''   stopped by the [[ -n "$paths" ]] guard
overlap=0  ->  named=0  ->  paths=''   guard disarmed too: `for path in $paths` never runs
overlap=-  ->  named=-  ->  paths=''   same both ways

named=${overlap#*,} returns the whole token when there is no comma, so named is the literal 0 or -; no changed path equals either, the intersection is empty, and the report loop has nothing to iterate. Three independent structures protect one property.

So the rows are removed, per this issue's own posture: a gate whose suite cannot be made to discriminate is a finding about that suite, never a weakened mutant. The measurement is recorded beside the arms so they are not re-added — a declaration there reports as a SURVIVED defect in the suite, which is the wrong subject, and CLOUD-941 spent a section misdiagnosing exactly that.

What this settles on CLOUD-941

Two of that row's four survivors are already dead on this tree, neither by anything in this branch:

  • board-write-record/overlap-frozen-at-write-time — killed by CLOUD-480's restore_tree fix, which removed the sweep-order dependence it was hiding behind. Its own header predicted this: caught when swept alone, SURVIVED in a full sweep.
  • ready-lint/replay-demanded-of-a-warn-gate — no longer reported.

And its published [ ] vs [[ ]] bracket-mismatch diagnosis is refuted, as its own correction section suspected: a zero-match pattern yields inert-mutation, never SURVIVED. The cause was the incomplete reading, not the pattern. That row's suggested retitle still stands, since its title asserts the refuted cause and a count now known wrong.

The remaining two it names are the filed-here-check pair resolved here.

Also carried

chore(fuzz) is one line: chore: release v0.0.108 moved the workspace version and left fuzz/Cargo.lock recording batten 0.0.106, so any cargo invocation under fuzz/ regenerates it and every contributor's tree comes up dirty. Toolchain-produced, on its own commit so it reviews apart from the gate work.

Verification

  • mise run mutant221 declared across the 114 enforced gates, every one caught, exit 0, run serially through mise with nothing else in flight (a contended run is CLOUD-944's other half and mints false findings).
  • Each fixed gate re-run alone: ci-tools-check 3/3, filed-here-check 6/6.
  • mise run mutant-census — 101 gates, every one declared or exempt by a filed row.
  • mise run verifyfast-forward-green.

Closes CLOUD-480

…erage

`mise run mutant` on a tree carrying CLOUD-944's stdin fix reports 223
declarations, not the 204 CLOUD-480's acceptance was recorded against — 19 had
never been applied, because the row loop is fed by `done <<<"$rows"` and the
`bats` calls inside it were reading those rows off stdin. Three of the 19 were
defective, so the published "every one caught" was green over a set that
excluded exactly the broken rows.

`ci-tools-check/spawned-tool-need-not-be-installed` named no case at all. Its
filter was written from memory as "a tool a batten.toml row spawns must be
installed in CI"; the case is "a tool a policy row spawns must be in the install
list". Repointed at the case that actually discriminates — the one asserting
`status -eq 1` on the defect — since the similarly-named case above it asserts
`-eq 0` in both arms and would pass under the mutation either way.

`filed-here-check`'s two overlap rows are removed rather than repaired, because
the property they name is over-determined and no one-line mutation can falsify
it. Measured by disarming each guard in turn: `named=${overlap#*,}` returns the
whole token when there is no comma, so `named` is the literal `0` or `-`, no
changed path equals either, and `paths` comes out empty — the emptiness guard
stops the row, and with that disarmed too the report loop has nothing to
iterate. Three independent structures protect one property. The measurement is
recorded beside the arms so the rows are not re-added; a declaration there
reports as a SURVIVED defect in the SUITE, which is the wrong subject.

Refs: CLOUD-480, CLOUD-944, CLOUD-941
`chore: release v0.0.108` moved the workspace version but left
`fuzz/Cargo.lock` recording `batten 0.0.106`, so any cargo invocation under
`fuzz/` regenerates it and every contributor's tree comes up dirty. This is
that regeneration and nothing else — one version line, produced by the
toolchain rather than typed.

Carried here because it is drift on `main` that this branch tripped over, not
because it belongs to the gate work: it rides its own commit so it is
reviewable apart from CLOUD-480's.

Refs: CLOUD-480
@linear-code

linear-code Bot commented Aug 23, 2026

Copy link
Copy Markdown
CLOUD-480 `mise run mutant` enforces five gates; the other ~39 are declared nowhere and are covered by nothing

Why

CLOUD-418's mechanism has landed: mise-tasks/mutant applies a declared one-line corruption to a throwaway copy of a gate and fails if the gate's own suite does not go red. Its enforced set is $MUTANT_GATES in mise.toml [env], and it is deliberately five names:

MUTANT_GATES = "land,land-lock,checks-green,ci-lease-precondition,finding-sink-check"

Those are the gates the landing-loop follow-up bundle touched, so every "mutation-checked per CLOUD-418" obligation in that bundle is cashed rather than cited. This issue carries the remainder.

A repo-wide count at that commit: mise-tasks/ holds roughly 44 files whose whole purpose is to refuse — *-check tasks and *-guard hooks, plus graph-check, landed-check, linear-check, doctor, timeout-check and friends — against 81 bats suites. Thirty-nine of them have no mutant declared and are therefore covered by nothing stronger than "the suite is green", which is exactly the standard CLOUD-418 measured as insufficient four separate times.

The property that is already held, and the one that is not. mutant fails a gate that is in the list with no #MUTANT row, so coverage cannot be silently zero for anything enforced. What it cannot do is notice a gate that was never listed. That is why this row exists rather than a TODO comment: a gate outside the list is a filed gap, not a silent exemption, and the filing is the whole difference.

Deliberately not done all at once. Declaring ~39 mutants in the same change that reduced landing cost would have put branch age — the quantity that multiplies laps and CI runs — on the critical path of the change that exists to shrink it. The mechanism is the expensive part and it is done; adding a row is now a two-line edit per gate.

Refinement — Ready

  • Source of truth (§1). mise-tasks/ itself, enumerated by the same glob hk.pkl and test:bats already use. No hand-typed second list — the point of this issue is to shrink the gap between the glob and $MUTANT_GATES to nothing.
  • Mechanism (§3). Two halves, and the second is what makes it stay done:
    1. Add a #MUTANT <slug>|<sed script>|<case name> row to each remaining gate and add its name to $MUTANT_GATES. The declaration lives beside the code it corrupts, for the reason step-receipt's spec table lives in step-receipt: a declaration in a second file is a second authority that drifts.
    2. A sensor asserting that every gate matching the glob appears in $MUTANT_GATES — the shape ci-local-parity already uses for CI_REQUIRED_CHECKS, in both directions. Without it the list and the tree drift apart again the moment a gate is added, which is this issue recurring.
  • Expect some gates to resist, and that is data (§2). A gate whose suite cannot be made to discriminate is not a declaration to force — it is a finding about that suite, and it gets its own row rather than a weakened mutant. mutant already refuses two evasions: an inert-mutation that changed nothing, and a names-no-case filter matching no test. Both were hit on the mechanism's first run, and one of them was a genuinely non-discriminating test the author had just written.
  • Deliberately not in scope (§2). Mutation coverage of crates/ — different tooling, different cost, different question, and explicitly out of scope on CLOUD-418. Also not in scope: running mutant in verify or in CI. It is a proof about the suite, not a property of the commit, and that is the lock-complete / lock-currency split.
  • Output & exit contract (§5). Unchanged — mutant already reports pointer-only (<gate>/<slug> SURVIVED (<case>)), never a diff of the mutated source.
  • Commit / bump (§6). test → no bump.
  • Blockers (§8). None. relatedTo CLOUD-418, which specified and landed the mechanism this completes.

Acceptance

  • Every gate in mise-tasks/ matching the gate glob appears in $MUTANT_GATES, or has its own filed row explaining why its suite cannot discriminate.
  • A gate added to mise-tasks/ without a mutant declaration fails a gate, rather than being quietly uncovered.
  • mise run mutant is green over the full set.

Filed from the landing-loop follow-up bundle, where the mechanism landed with a deliberately narrow seed list.

CLOUD-911 Fleet dispatch: the bash retirement in two bundles, sized for a cross-account handoff — ten rows in one PR, then the wave in another

Eleven rows, two bundles, two lands. The bundles and prompts live here rather than in a chat that dies with its container — CLOUD-607's precedent, CLOUD-784 and CLOUD-839's shape. This row is additionally written to survive its author running out of quota mid-flight: §"Resume protocol" is how a different account computes where the last one stopped, off the tree and the board, without asking anyone.

Why two PRs and not twenty

The landing lease is fleet-wide and charges per land, not per gate. land-lock's header: "hold a rolling fleet-wide landing lease, so exactly one branch at a time spends CI on a landing attempt." ci p95 is 701 s; one lap is rebase → verify → push → ci-wait/fast-forward, strictly serialized. CLOUD-843's own table:

batching lease acquisitions for the campaign pure landing time
one row per PR ~20 ~5 h
two bundles 2 ~30–40 min

One PR carrying many rows is the intended shape: CLOUD-661 retired the one-PR-per-ticket prescription for exactly this case and CLOUD-502 is Canceled.

Why not one PR. Two reasons. Bundle 2's content is created and validated by bundle 1's instruments — in one PR those instruments would never have been exercised against anything but their own fixtures before twenty suite deletions rode on them. And a quota stop mid-branch with everything in one PR leaves nothing landed; with bundle 1 landed, the next account inherits a repo that can already prove a migration faithful, and bundle 2 is resumable per gate.

The bundles

# Chain File domain PR shape
1 the floor CLOUD-879 → 851 → 883 → 907 → 880 → 908909 → 480 → 886 — 876 moves to the head of the chain, see Verification facts.rs, rules.rs, policy.rs, git.rs, schema/*, mise-tasks/mutant.sh, mise-tasks/test*, tests/*.bats 1 PR — everything that must exist before a gate can be retired
2 the wave CLOUD-910 mise-tasks/*.sh (deleted), tests/*.bats (deleted), policy/*.rego (new), batten.toml 1 PR — up to 20 gates, one land

Bundle 2 is blockedBy 908 and 909, both inside bundle 1, so the board refuses the wrong order rather than a reader remembering it.

The precondition a human must clear before dispatch

CLEARED for bundle 1 (2026-08-23). 480 was claimed under BATTEN_CLAIM_CHECK_BYPASS=1 and landed in #660; 876 and 879 were groomed and landed too. This section is kept because its reasoning about claim-check's two refusal arms still applies to 312, 856 and 857, which remain outside both bundles.

CLOUD-480 is in Backlog and still assigned (corrected 2026-08-23; it left In Progress at 2026-08-22T20:54Z), and it is inside bundle 1. claim-check refuses on not-todo AND on assigned, so the move made it less claimable, not more — no other account can claim it — not this one, not a successor. Same for CLOUD-312, 856 and 857, which are the mediated-call half of the retirement and are not in either bundle for that reason.

Either those four are released or reassigned, or bundle 1 ships nine rows and CLOUD-480's mutation coverage slips to bundle 2 — which weakens the batch exactly where batching is most dangerous, since a false-green module hides inside a large green diff and mutant at five declared gates cannot see it. Releasing them is the cheaper move.

The fidelity chain, which is what bundle 1 exists to build

CLOUD-807's retires_with conserves files, not logic: it admits deleting a suite the moment its subject dies with nothing asserting what replaced it. Measured on the campaign's one completed retirement (dd1d6d8/076b65f, contract-drift): 22 @test cases deleted, 18 successors, six with no successor identifiable from the tree, and one case whose behaviour changed deliberately with nothing marking it. Four mechanisms close that, and no suite is deleted until all four hold:

  1. CLOUD-908 — every deleted case declared carried, subsumed or changed; unmapped refuses at deny.
  2. CLOUD-909 — per carried case, replay against the base rev's bash: identical pointer set, exit code through a declared translation, never a raw equality, remedy text preserved.
  3. CLOUD-480 — a #MUTANT the new tests provably catch.
  4. The preset split check (in CLOUD-910) — for a preset only, replay green in this repo with the preset enabled and consumer facts in batten.toml.

Dispatch prompts

Two self-contained blocks — one paste per session, nothing to prepend. The workflow contract is repeated verbatim inside each, and the repetition is the point: CLOUD-728 measured five bundles coming up unsupervised because a human pasted one quoted block and dropped a shared contract.

Bundle 1 — the floor

You are bundle 1 of the CLOUD-911 bash-retirement dispatch in the Batten repo.
Read CLOUD-911 first: it carries the resume protocol, the census baseline and the
fidelity chain. Bundle 2 cannot start until your PR lands.

ONE branch, ONE draft PR, landed in this order:
  CLOUD-876 -> 879 -> 851 -> 883 -> 907 -> 880 -> 908 -> 909 -> 480 -> 886

Ten rows in one PR is the point, not a deviation (CLOUD-661 retired the
one-PR-per-ticket rule; CLOUD-502 is Canceled). The landing lease is fleet-wide
and charges per land: ci p95 is 701s per lap. Split only if a row genuinely will
not land, and say so on that row.

The order is dependency, not taste:
- 876 FIRST, then 879. 876 DECIDES the schema mechanism -- two candidates, and
  879's own body says it "consumes whatever it chooses ... so sequence them".
  Doing 879 first means building the derivation against an undecided target.
  This edge is now a real blockedBy on the board (879 blockedBy 876), so the
  queue refuses the wrong order rather than you remembering it.
  879 then derives the policy-input projection from the declaration instead of
  hand-writing it, so every fact family after it is a declaration rather than
  another edit to a central function.
- NEITHER 876 NOR 879 CARRIES A READY BLOCK. Both are `no-ready-block` under
  `mise run ready-lint`. Groom them BEFORE writing code -- their "Done when"
  sections are close but are not the DoR's sections 1-8, and claim-check's
  refined-this-session arm is downstream of that.
  (876's own job: make a rule reading a field the engine never emits fail at
  BUILD time -- CLOUD-845's defect generalised. Read its two candidates, (a)
  schemars + `# METADATA schemas:` + `opa check -s`, and (b) regorus `Target`;
  determining which delivers a build-time failure is cheap and the row says to
  do it first.)
- 851 (a notion of production/sink -- eleven bash writers have no expressible
  sink today), 883 (glob is one inclusive pattern with no exclusion).
- 907 then 880: the git fact family. 50 of the 85 gate-described tasks read git
  and NO general git fact exists -- no branch, HEAD, merge-base, ancestry, log,
  status or remote. 907's FIRST deliverable is re-deriving the bucket count at
  85 (CLOUD-843 measured it at 82); the number sizes the rest of the campaign
  and an estimate cannot schedule it. Classify by COMMAND-POSITION invocation
  with comments stripped, per .claude/rules/scanning.md row two -- a substring
  scan put ci-local-parity in the forge bucket because the token was in a
  comment. 880 composes over 907 rather than resolving refs of its own.
  Declaration-bounded acquisition, never an ambient walk. Every could-not-look
  condition (no commits, detached HEAD, no remote, unresolvable ref) is
  Look::CouldNotLook, asserted DISTINCT from empty -- Rego reads an undefined
  path as "does not hold", so collapsing them ships a gate that is silently off.
- 908 then 909: the two instruments, and they are the deliverable bundle 2
  depends on. Read both rows in full. The short version:
    908 -- retires_with conserves FILES, not LOGIC. Extend the same shape
    (rules.rs:3805-3902, git.rs:1661) so a deleting PR declares, per test case,
    exactly one of carried / subsumed / changed. Unmapped refuses at deny.
    909 -- the differential replay. Per CARRIED case: run the bash AS IT EXISTS
    AT THE BASE REV (git show <base>:mise-tasks/<x>.sh) and the new row over the
    dying case's OWN fixture. Assert IDENTICAL POINTER SET byte-for-byte, and
    the exit code through a DECLARED TRANSLATION -- shell 1 (violation) ->
    batten 2, shell 0 -> 0, shell 2 (could-not-look) -> batten 1.
    DO NOT ASSERT IDENTICAL EXIT CODES. crates/batten/tests/contract_drift.rs's
    header records why: the shell tasks use 1 = violation and batten's contract
    is the inverse, so a carried-over `assert_equal $status 1` asserts
    "unreadable input" while meaning "violation", and it PASSES. Also assert the
    remedy survives (CLOUD-437) -- a msg that lost its remedy in translation is
    a regression the pointer comparison cannot see.
    Generalize tests/run-shape.bats, which drives the compiled binary through
    `batten hook`. OFF the landing path, the way mutant is.
- 480 and 886 last: 480 declares a #MUTANT per gate that its tests provably
  catch (the anti-false-green instrument bundle 2's batching depends on); 886
  fixes test:bats globbing mise-tasks/** so a one-sentence change stops running
  all 150 suites.

CALIBRATION, and it is a real deliverable rather than a nicety: retrofit 908's
mapping to the ONE completed port. contract-drift.sh (215 lines) and
contract-drift.bats (22 cases) were retired in dd1d6d8/076b65f for
contract_drift.rs (12 #[test]) plus 6 unit tests in src/contract.rs. Six cases
have no successor identifiable from the tree: "it names the event it was called
on", "an untracked file under mise-tasks is not contract", "a payload with no
session_id still works", "unparseable input fails open", "empty input fails
open", "the bypass is honoured". Map all 22. Three are plausibly held now by
guardrail_bypass.rs / cli.rs -- name them if so. If any is a real gap, the row
that finds it is worth more than the mechanism that found it.

SHOWN ABLE TO FAIL (CLOUD-418), one OBSERVED red per arm, not asserted: an
unmapped case; an arm naming a target that does not exist; a case carrying two
arms; a row whose pointer set differs; a row diverging on a case NOT marked
changed; a translation declared as 1 -> 1; a migrated refusal that dropped its
remedy. Plus the positive arm in each case, or the rule admits everything. A
chain never observed failing is not a chain.

CLOUD-480 IS IN BACKLOG AND STILL ASSIGNED (as of 2026-08-23). claim-check
refuses on `not-todo` AND on `assigned`, so it is doubly unclaimable. If you cannot claim it, land the other nine and say so on CLOUD-911
-- do not work around the claim gate and do not silently drop the row.

CONFLICTS: you are the only branch in facts.rs/rules.rs/policy.rs/git.rs/schema.
If another lands under you, rebase and re-run `mise run fix` to REGENERATE
schema/batten.schema.json and schema/batten.local.schema.json -- never merge a
generated diff; two merged regenerations produce a file neither branch would
have produced.

WORKFLOW CONTRACT (AGENTS.md is authoritative; this is the summary):
- Claim by hand BEFORE writing code: `mise run claim-check`, and assign
  yourself. The automation fires on the PR event, the END of the work, so
  waiting for it reserves nothing.
- `git fetch origin main`, short-lived branch, never author on main.
- Commit early and often. You are pre-authorized to commit and push without
  asking.
- Run the full `mise run verify` after EVERY commit. Local execution is free; a
  CI run is metered and the landing lease is fleet-wide.
- Open the PR as a DRAFT immediately. CI does not run on drafts, so you iterate
  at zero CI cost.
- perf-compare is a RATIO and a dev container's baseline is ~3.5x CI's: a fixed
  ~9ms cost reads ~1.19x locally and 1.64x on CI. A LOCAL GREEN DOES NOT CLEAR
  IT. Read the CI number.
- When the chain is complete: `mise run linear-check`, then `mise run land`
  backgrounded. Do NOT ready by hand -- land readies after its push. Do NOT wrap
  land in bespoke retry or pre-check logic; main advancing under you is that
  loop working.
- Background anything that can exceed ~2 minutes; a foreground command is killed
  at ~2 min.
- Move the Linear row as you move the work. Carry the lifecycle to
  landed-and-verified without stopping to report and wait.
- UPDATE CLOUD-911's PROGRESS SECTION AT EVERY COMMIT: the branch name, the PR
  number, and which rows are done. Your quota may end mid-branch. The next
  account reads that section and the tree -- never a chat transcript.

Bundle 2 — the wave

You are bundle 2 of the CLOUD-911 bash-retirement dispatch in the Batten repo.
Read CLOUD-911 first, then CLOUD-910, which is your row. CLOUD-911 carries the
resume protocol, the census baseline and the fidelity chain.

BUNDLE 1 MUST HAVE LANDED. Verify that on the tree, not from this prompt: the
mapping ratchet (CLOUD-908) and the replay task (CLOUD-909) must both exist, or
you will delete suites with nothing asserting what replaced them.

ONE branch, ONE draft PR: CLOUD-910 -- up to 20 TREE-SCOPED GATES. The 8
structured-config gates plus the 12 that CLOUD-845's tracked list and CLOUD-846's
lines fact unblocked. Re-derive the exact set at wave start; CLOUD-843's
classification was taken at 82 gate-described tasks and there are now 85.
mise-pin-agreement is the pilot and goes INSIDE the batch, not before it:
CLOUD-843 wants a measured per-gate cost, and it measures the same as the first
gate of 20 for one lease acquisition instead of two.

DESTINATION -- there are two and they are DIFFERENT MOVES:
- An IN-REPO MODULE (policy/*.rego) is the DEFAULT. It MAY name this repo's
  facts: tracker keys, mise-tasks/ paths, our layout. The move is a TRANSLATION
  -- same predicate, same facts, new language.
- A VENDORED PRESET (crates/batten/src/policy/presets/**) may name NONE of them:
  non-negotiable rule 1, gated by presets_are_inside_the_rule_one_glob in
  crates/batten/tests/policy_presets.rs. The move is a SPLIT -- generic
  predicate to the preset, consumer facts to batten.toml. A gate becomes a
  preset ONLY if its predicate is generic once the facts are pulled out. That is
  a MINORITY. Forcing a consumer-specific gate into a preset is how rule 1 gets
  violated by a well-meaning migration.
policy/privileged-lane.rego is the template for the structured-config bucket: it
asks with doc.jobs what ci-local-parity asks with 35 regexes over the same YAML.

PER RETIRED GATE, all four or that gate STAYS BASH this round:
(a) MAPPED (CLOUD-908) -- every test in the dying suite is carried / subsumed /
    changed. Unmapped refuses at deny.
(b) REPLAYED (CLOUD-909) -- per CARRIED case, against the base rev's bash:
    pointer set identical byte-for-byte, exit code through the DECLARED
    TRANSLATION and NEVER a raw equality, remedy text preserved (CLOUD-437).
(c) MUTATED (CLOUD-480) -- a #MUTANT the new tests provably catch.
    mise-pin-agreement carries none today, so migrating it should ADD one:
    coverage improves as a side effect rather than degrading.
(d) SPLIT-CHECKED -- preset only: the replay green IN THIS REPO with the preset
    enabled and the consumer facts moved to batten.toml. Pass means the facts
    are wired; fail means they went missing in translation.
A gate short of any one of the four stays bash, NAMED ON CLOUD-910 with the arm
it failed. Stretching the evidence to hit a count is the failure mode this wave
exists to prevent.

THE MAPPING TABLE IS THE PROGRESS LEDGER. Commit it per gate, as you go. It is
what lets another session -- or another ACCOUNT -- resume this branch mid-wave
without asking anyone. A gate with no mapping block is untouched; a block with
unmapped cases is half-done.

CENSUS, asserted rather than eyeballed, at the end -- CLOUD-843 §2's counts.
Baseline on bfda756: 141 task files, 85 gate-described, 150 top-level
tests/*.bats, 4 kind="policy" rows, 4 preset modules. A wave that does not move
the first two down has retired nothing, whatever else it landed -- which is
exactly what the contract-drift window did, because abandon-matrix.sh landed in
the same window and gate-described read 85 on both sides. Take the counts with
Glob/Grep: no-tool-substitution refuses a shell scanner aimed at a repo path.

WORKFLOW CONTRACT (AGENTS.md is authoritative; this is the summary):
- Claim by hand BEFORE writing code: `mise run claim-check`, and assign
  yourself. The automation fires on the PR event, the END of the work, so
  waiting for it reserves nothing.
- `git fetch origin main`, short-lived branch, never author on main.
- Commit early and often. You are pre-authorized to commit and push without
  asking.
- Run the full `mise run verify` after EVERY commit. Local execution is free; a
  CI run is metered and the landing lease is fleet-wide.
- Open the PR as a DRAFT immediately. CI does not run on drafts.
- When the chain is complete: `mise run linear-check`, then `mise run land`
  backgrounded. Do NOT ready by hand. Do NOT wrap land in bespoke retry logic.
- Background anything that can exceed ~2 minutes; a foreground command is killed
  at ~2 min.
- Move the Linear row as you move the work.
- UPDATE CLOUD-911's PROGRESS SECTION AT EVERY COMMIT: branch, PR number, and
  which gates have cleared all four arms.

Verification, run 2026-08-22 on bfda756

This row's §2 asserts the graph and the Ready blocks. Both were run rather than claimed, and the run changed the dispatch — which is the point of running it.

graph-check over the five new rows, quoted verbatim

graph status-claim-unscannable (CLOUD-911 claims CLOUD-502 is Canceled, which no piped issue occupies — pipe one that does)
graph status-claim-unscannable (CLOUD-911 claims CLOUD-480 is In Progress, which no piped issue occupies — pipe one that does)
graph status-claim-unscannable (CLOUD-911 claims CLOUD-734 is Done, which no piped issue occupies — pipe one that does)
CLOUD-910 excluded (blocked-by CLOUD-908 CLOUD-909)
wip 0
frontier CLOUD-908
frontier CLOUD-909
frontier CLOUD-911

The ordering claim holds: CLOUD-910 is excluded behind 908 and 909, so the board refuses the wrong bundle order rather than a reader remembering it.

The three exit-2 lines are CLOUD-838's status-claim-unscannable arm firing on claims about ids outside the piped closure — a property of the chosen closure, not of the board, and the same residue CLOUD-839 recorded verbatim for the same reason. CLOUD-907 is absent from the frontier for a different and benign reason: board-payloads recovers the newest get_issue payload, and 907's was taken while it was still Backlog. Its board status is Todo; the cached payload is stale, exactly as board-payloads's own header warns.

ready-lint, per row

row verdict
CLOUD-907, 908, 909, 910, 911 exit 0
CLOUD-876 exit 1 — no-ready-block
CLOUD-879 exit 1 — no-ready-block

Two rows inside bundle 1 are not groomed. Both carry a "Done when" section rather than the DoR's §1–§8, which is close enough to read as refined and is not. They sit in Todo, which is the ready queue — so this is a grooming precondition alongside the claim wall, and it is cheap: both bodies already carry the substance.

The ordering defect this found

The first draft of bundle 1 put 879 first. CLOUD-879's own body says "CLOUD-876 decides the schema mechanism. This issue consumes whatever it chooses — the derivation target differs between # METADATA annotations and regorus Target schemas, so sequence them." That dependency existed only as prose in one row's body; neither row carried a relation. It is now a blockedBy on the board (879 blockedBy 876), and the chain above is corrected. This is CLOUD-784's rule applied: every ordering hazard is a real relation, so the queue refuses a wrong order rather than a reviewer remembering it.

Still unrun, and named rather than implied

graph-check was not run over the six pre-existing bundle-1 rows as one closure (851, 883, 880, 886, plus 480 and 886's own relations). Four get_issue payloads short, and the two edges that could have broken the chain — 910's blockers and 879→876 — are both verified above. A bundle-1 agent should re-run graph-check over its full chain at claim time; it is free and this row may be stale by then.

Resume protocol — how a different account picks this up

Every signal is read off the tree or the board. None of it lives in a chat transcript, which is the point: a container is reclaimed, an account hits a limit, and the work has to be resumable by someone who was never in the room.

question answered by
Did bundle 1 land? git log origin/main for the replay task and the schema regeneration; CLOUD-908 and 909 in In Review
How far into bundle 2? the committed mapping table — one block per retired suite. A gate with no block is untouched
Is a gate half-migrated? its .rego exists and its mapping block does not, or its block has unmapped cases
Which rows are claimed, and by whom? the board. claim-check refuses on assigned, so a successor must be handed the assignment, not just the prompt
Is the branch resumable? it is pushed to a draft PR. Committed-and-pushed is the only state that survives a VM reclaim
What is "done"? the census moving down, asserted — CLOUD-843 §2 against the bfda756 baseline

Progress (each bundle updates this section at every commit — branch, PR, rows done)

  • Bundle 1 — LANDED. Branch claude/cloud-911-bundle-plan-9n3fsg, PR CLOUD-911 bundle 1 — the floor for the bash retirement merged 2026-08-23 by fast-forward; origin/main is ba08def. Every required check green on that exact SHA (ci, zizmor, darwin-link, semver, perf, windows, final), one lap.

    Rows complete: 876, 879, 851, 883, 907, 908, 909, 886, 480 — nine of ten. CLOUD-880's fact family landed and that row stays In Progress deliberately: its second acceptance clause asks for one exit-code consumer to become a rule over these facts, and that consumer does not exist in bash — reasoning on 880 and in CLOUD-911 bundle 1 — the floor for the bash retirement #660's body. CLOUD-480 was taken after all and is In Review; CLOUD-911 bundle 1 — the floor for the bash retirement #660's row table says not taken because it was written before the claim wall was cleared under BATTEN_CLAIM_CHECK_BYPASS=1. A merged body cannot be corrected, so this section is the authority and that table is not.

    The land held for the whole bundle, as directed — one lease acquisition, which is this row's economic argument. Four blockers were diagnosed getting there and none was a flake to wave through: config-lint refusing 908's undeclared rule-predicate-changed (declared through the two-source path, never re-ranked — trust.rs refuses to rank deliberately); a tests/land-lock.bats lease case at 23.9s against a 20s cap, the fourth sibling CLOUD-450 had already raised to 60 and missed; batten-check failing closed on two deny rows spawning opa/regal that ci.yml's install_args never installed, a direction ci-tools-check was structurally blind to and now is not; and disk exhaustion at 120MB free mid-verify.

    • CLOUD-879 — complete (cf462c1). Both policy-input schemas are generated from Fact::ALL rather than checked in: Fact::schema_fragment states each fragment beside the fact's Class and tree_key, and generate schema gained two surfaces. The suite changed in kind — a round trip that the committed bytes are what the generator produces, byte-stability, and the properties the generator STATES rather than derives (closedness, disjoint surfaces). ConfigSurface became SchemaSurface, a declared break (feat(policy)!): mise run semver refused the branch with enum_missing until the commit said so.
    • CLOUD-851 — complete (3395323, e32cbf9, 8e7214a). Production is a third axis beside Cost/Surface; the_two_axes_agree_about_every_kind is untouched and Effect still means "spawns". The decision requests (Scan.requested, pure) and the boundary performs, on the spawning surface only — check computes the identical request set and writes nothing. All three censused kinds are expressible, the keyed baseline reads back through Fact::Produced, and its red arm (same record, a module that does not consult it) is committed beside it.
    • Two defects in 851 that a green suite could not see, both fixed in 8e7214a, and the second is the more instructive. A record's destination did not name its producing rule, so two branch-keyed rules of one kind silently overwrote each other; the rule id is a path segment now, which makes the collision inexpressible rather than refused. And the store acquisition ran on EVERY run — check p50 4.76ms → 10.01ms, 2.103x, which perf-compare refused. 2134 cargo tests were green across that regression, because none of them measures invocation cost. The full detail is a comment on 851.
    • A measurement discipline worth carrying to bundle 2. perf-pair is a wall-clock ratio and its two arms are measured sequentially, so concurrent load on the machine does NOT divide out. Two readings had to be discarded here — one taken while sources were being edited under the release build, one while mise run fmt ran its own test suite alongside. Run perf-gate with nothing else in flight, or the number it produces is not about the branch.
    • Preconditions cleared. CLOUD-876 and CLOUD-879 are groomed to the DoR's §1–§8 and both now pass mise run ready-lint at exit 0 — the RED half of this row's §2 predicate is closed.
    • The claim wall was real, and it fired on the OTHER arm. Grooming in the implementing session trips claim-check's refined-this-session, exactly as this row's dispatch prompt anticipated. Claimed under BATTEN_CLAIM_CHECK_BYPASS=1, which records the self-refinement in the receipt — CLOUD-431's designed path, with the human decision taken explicitly rather than assumed.
    • The CLOUD-480 claim wall was CLEARED and the row LANDED — In Review on CLOUD-911 bundle 1 — the floor for the bash retirement #660. The account below is left standing because it was accurate about how the wall looked, and because the resolution was the same bypass 876/879/883/880 took: BATTEN_CLAIM_CHECK_BYPASS=1, with the self-refinement recorded in the receipt. Re-checked 2026-08-23 against a fresh get_issue: the row is Backlog and still assigned — it left In Progress at 2026-08-22T20:54Z. That is not a release: claim-check refuses on not-todo as well as on assigned, so the move added a second refusal rather than clearing the first. graph-check is what found this, refusing on status-claim-disagrees (CLOUD-480 claimed In Progress, board says Backlog) — the earlier reading below was true when written and is stale now. The intent to release it was given, the board was not changed, and this note previously said it had been — corrected here rather than left standing, because this section is the resume protocol's only durable channel and a false entry in it is worse than none. Bundle 1 proceeds on the other nine and settles 480 when the chain reaches it. (It did reach it: 480 landed in CLOUD-911 bundle 1 — the floor for the bash retirement #660 and is In Review — see the LANDED entry above. Everything before this parenthesis is how it looked at the time.)
    • CLOUD-876 — In Progress. The mechanism is decided: (a), and the evaluation is a comment on that row. (b) is refused by two landed gates rather than on the merits this row guessed at: Target/Schema are behind regorus's azure_policy feature, which adds 28 packages including jsonschema (evaluator-closure-check's IO_CRATES) and core-foundation-sys via chrono (macos-link-check's FRAMEWORK_CRATES, CLOUD-885's chain). Measured, then reverted.
    • Landed: af16871. opa 1.2.0 and regal 0.42.0 pinned, and the skew gate 876's acceptance names — as policy/opa-compliance.rego, a deny policy row over mise.toml and Cargo.toml. Verified: batten-check exit 0, batten policy test 5 bundles / 50 passed / 0 failed.
    • The first draft of that gate was bash, and it was reverted before landing. mise-tasks/opa-compliance-agreement.sh plus a bats suite — a new bash gate inside the bundle whose purpose is retiring bash, which is CLOUD-843's own "the bash grew today" one level down. Everything it decides is in two tracked TOML files, i.e. CLOUD-846's structured-config bucket. Census effect of this commit: no new mise-tasks/ file, no new tests/*.bats suite, one new kind="policy" row. A useful precedent for bundle 2: the migratable-today claim held on the first attempt.
    • The draft also derived the compliance level from the vendored crate's README. That asks what does upstream claim right now — a property of the world, which lock-complete's split puts on a clock, not in a gate. The row now decides only whether the numbers this commit pins agree; REGORUS_OPA_COMPLIANCE_FOR ties the recorded level to the regorus line it was read against, so a regorus bump reddens rather than the constant rotting.
    • Still open on 876: the schemars → # METADATA schemas:opa check -s wiring, the Regal aggregate rule requiring an annotation on every rule, and the rego.metadata.* refusal.
  • A finding for CLOUD-480, carried forward rather than filed separately. $MUTANT_GATES held 54 names when this was written and holds 113 now, not the five 480's body describes — that row's premise is stale and its remaining scope is much smaller than filed. And mise run mutant on 170c7c4 reports one pre-existing red: ready-lint/replay-demanded-of-a-warn-gate SURVIVED. Not caused by this branch (ready-lint.sh is untouched); it is exactly the "a suite that cannot discriminate is a finding about that suite" case 480 reserves. Resolved as a filed row rather than a forced declaration: CLOUD-941 owns it — ready-lint's mutation is a no-op because its pattern spells [ ] where the code has [[ ]], so the conjunct was covered by nothing. mutant-census closes the two-way gap 480 was filed for: 113 gates declared, 100 censused, two exemptions, and a gate added without a declaration now fails.

  • Bundle 2 — dispatchable now. CLOUD-910's two blockers, 908 and 909, both landed on main in CLOUD-911 bundle 1 — the floor for the bash retirement #660, so the wave has the floor it was waiting for. (Worded without a code span after the id: graph-check's status-claim scanner read the previous phrasing as a claim that CLOUD-910 occupies a status called blockedBy, and reported status-claim-unscannable. The row's own §Verification records the same arm firing on out-of-closure ids — this one was self-inflicted by an edit and is fixed rather than explained.)

Dispatched by hand — and §Re-probed below downgrades "settled" to "could not look"

create_session is refused upstream: the session-management tools carry a mandatory-approval flag and bypassPermissions, an explicit permissions.allow entry and a PreToolUse allow hook are all recorded as tested and failing. CLOUD-734 is Done and carries the measurement; CLOUD-731, 784 and 839 are the precedents. Do not spend a turn re-attempting it. A human opens two sessions and pastes the prompts above, and confirms each session's permission mode in the UI — get_session is unavailable, so no agent can confirm it, and CLOUD-728 measured five bundles coming up in the wrong mode and running to landed unwatched.

This row's own lifecycle

CLOUD-735: a dispatch record opens no PR and lands no commit, so both gates out of In Progress are unreachable for it by construction. Leave it in Todo and close it by hand once both bundles are away, rather than pulling it and stranding it.


Refinement — Ready

Refinement gate: Definition of Ready & Done. This body carries only specializations.

  • Source of truth (§1). The board and the tree. Every ordering claim here is a blockedBy relation (CLOUD-910 blocked by 908 and 909), and the census baseline is a measurement over bfda756 rather than a hand-derived list — if this row and the tree disagree, the tree is right and this row is stale.
  • Computable predicate (§2). mise run graph-check over the piped closure prints a frontier whose roots are the bundle heads and excludes CLOUD-910 behind 908 and 909. Every row in the dispatch passes mise run ready-lint at exit 0. Both were run on 2026-08-22 and the output is quoted verbatim in §Verification, including the two failures it foundCLOUD-876 and CLOUD-879 are no-ready-block, so that half of the predicate is currently RED and grooming them is a dispatch precondition, not a formality. The graph-check run also carries three status-claim-unscannable lines for claims about ids outside the piped closure, which is CLOUD-838's arm behaving correctly (CLOUD-839 recorded the same residue for the same reason).
  • Effect (§3). free — a tracker record. Nothing is resolved, built or spawned.
  • Generated artifacts (§4). None.
  • Output / exit (§5). No command surface is touched.
  • Commit / bump (§6). none — this row lands no commit.
  • Test obligation (§7). None of its own; each bundle's rows carry theirs. The claims here that could be wrong are the bundle membership and the census baseline, and re-running graph-check and CLOUD-843 §2 falsifies them.
  • Blockers (§8). None.

⚠️ Re-probed 2026-08-23 — two of §"Dispatched by hand"'s premises are false, and the third is unverified

That section says "that is settled" and "Do not spend a turn re-attempting it." Neither holds as written. Measured this session, from a plan-mode session on env_01Cwc7vhMyL51NMsPj4aeNHP:

The two false premises

§"Dispatched by hand" reads: "a human … confirms each session's permission mode in the UI — get_session is unavailable, so no agent can confirm it."

  • get_session works. Called with no argument, it returned this session's full context including session_context.permission_mode, external_metadata.permission_mode, permission_mode_seq, the served model and the live rate-limit state.
  • list_sessions works. With mine: true it returned every live session on this repo with title, branch, status, task_summary and post_turn_summary. That read is not a nicety — it is what found two collisions CLOUD-926 would otherwise have walked into.

So the stated reason a human must be in the loop is gone. An agent can confirm a child's mode, which is exactly what CLOUD-728 says must happen and what plan-fleet.md §4 instructs ("Read the child's mode back after the call rather than assuming the parameter took").

The third premise is now UNVERIFIED rather than confirmed — and that is the load-bearing correction

create_session was called twice, ~1 minute apart, with a real bundle prompt, permission_mode: "plan", model: claude-opus-5, source_url and tags per plan-fleet.md §4. Both calls returned, verbatim and identically:

failed to create session: the service is temporarily unavailable — try again

That is not the failure this row describes. §"Dispatched by hand" claims a mandatory-approval refusal — "the session-management tools carry a mandatory-approval flag and bypassPermissions, an explicit permissions.allow entry and a PreToolUse allow hook are all recorded as tested and failing." An approval refusal and a service-availability error are different verdicts with different remedies, and only one of them is permanent.

So the honest state is could not look, not "refused": the call reached the service, the service declined to answer, and nothing was learned about the approval flag. Reading an availability error as confirmation of a policy refusal is the two-valued collapse CLOUD-251 names, applied to a capability instead of a gate.

What changes for the next dispatcher

  • "Do not spend a turn re-attempting it" is retired. It was a reasonable economy when the refusal was believed permanent and the read-back was believed impossible. Both halves have moved: the probe costs one call, and its upside is removing a human from the critical path of every future fan-out. Re-probe rather than defer.
  • Dispatch from the mode you want the children in. plan-fleet.md §4's measured table, from one account: dispatcher auto + omitted → child default; dispatcher plan + passed autorefused at the call; dispatcher plan + passed default → child plan. Passing plan from plan is untested and is what was attempted here; the service error means it is still untested. Never pass auto from plan — that arm is measured.
  • Hand-pasting still works and nothing is blocked on this. CLOUD-926 carries five prompt blocks and this row carries two, all in fenced blocks, all copy-pasteable. The capability question is an efficiency question, not a dependency.

Provenance

Probed while dispatching CLOUD-926's bundle C — chosen as the probe target because it is the only bundle with no assigned rows, so a success would not start work behind a claim-check wall. The override of this row's "do not re-attempt" instruction was taken deliberately, with the campaign owner's agreement, on the strength of the two falsified premises above rather than in spite of them.

⚠️ Re-probed AGAIN 2026-08-23, later session — the third premise now has a reading, and it is the approval flag

create_session was called once from this session (env_01Cwc7vhMyL51NMsPj4aeNHP, model: claude-opus-5, permission_mode: "plan", source_url and tags per plan-fleet.md §4), carrying bundle 2's full prompt. It returned, verbatim:

MCP tool call requires approval

That is the mandatory-approval refusal §"Dispatched by hand" describes — not the failed to create session: the service is temporarily unavailable the section above measured. So the honest state moves from could not look to refused at the approval layer, and the two remedies stay distinct: an availability error is waited out, an approval flag is not.

Both readings are now on the record, hours apart, and that is itself the finding. The same call with the same parameters returned an availability error once and an approval refusal once, so a single probe is evidence about that probe rather than about the capability. Neither reading retires the other.

What did NOT reproduce: the mode read-back. get_session with no argument returned this session's context and carried no permission_mode field at allsession_context held sources, outcomes, model, effort_level; external_metadata held the container version, branches, last_served_model and the rate-limit state. So §"The two false premises"'s claim that an agent can confirm a child's mode is unverified again: the field that read-back needs was absent. list_sessions with mine: true does still work, and is what confirmed no live session held bundle 2.

Consequence for this row

Bundle 2 was not handed to a sibling. It is being run in-session on branch claude/bundle-2-dispatch-plan-kr630c (keyless, so closing-key-check's branch-name precedence cannot strand rows), together with the rest of this row's outstanding work: CLOUD-880's second acceptance clause, CLOUD-876's residue, and the Done pass over bundle 1's five rows still In Review (876, 886, 908, 909, 480 — 879, 851, 883 and 907 have already moved).

The wave's chain is CLOUD-930 CLOUD-929 CLOUD-910, one PR, one land. The two added rows are not scope creep: .rego is the only config format in the tree with no gate and it is the language the wave writes up to twenty modules in (930), and the retirement census is four shell one-liners in an issue body, so a wave that adds bash reports the same green as one that removes it (929) — while CLOUD-910's own acceptance demands the census delta "asserted rather than eyeballed". CLOUD-932 is deliberately held out: its remedy lands in .claude/rules/**, which is PR-D's domain.

Census re-derived on 54442b2 (v0.0.108), against this row's bfda756 baseline, because the wave is sized off it: 145 mise-tasks/*.sh (was 141), 157 top-level tests/*.bats (was 150), 5 kind = "policy" rows (was 4), 3 in-repo policy/*.rego, 4 preset modules (was 4). The bash grew by four task files and seven suites since the baseline — an argument for running the wave now rather than behind PR-E and PR-G.

Review in Linear

@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e05bd3b8-7b92-4331-9165-0bd137cb3243

📥 Commits

Reviewing files that changed from the base of the PR and between 54442b2 and 23d6ec3.

⛔ Files ignored due to path filters (1)
  • fuzz/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • mise-tasks/ci-tools-check.sh
  • mise-tasks/filed-here-check.sh

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The changes update mutation-test annotations in two shell scripts. One annotation changes its descriptive text without changing the mutation substitution. Two mutation definitions are removed because redundant empty-path guards and loop behavior prevent independent test coverage. Documentation now records why those mutants survive.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the mutation-acceptance retake and relates directly to the pull request's main changes.
Description check ✅ Passed The description directly explains the mutation-test corrections, removed declarations, lockfile update, and verification results.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/cloud-911-quota-resume-hxkxib

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

@sonarqubecloud

Copy link
Copy Markdown

@wenzowski
wenzowski marked this pull request as ready for review August 23, 2026 15:15
@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski
wenzowski merged commit 23d6ec3 into main Aug 23, 2026
10 checks passed
@wenzowski
wenzowski deleted the claude/cloud-911-quota-resume-hxkxib branch August 23, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant