From 2e72118e049100428ed3cc8015a2f466b5972157 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 02:28:22 +0000 Subject: [PATCH 1/9] fix(land): the lap cap said stop, and stopping is how a branch stops landing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lap cap's message ended "Look before lapping again." An agent read that as an instruction to stop lapping until the fleet quietened, and stopped — 55 minutes, on a branch carrying one commit. ## Why that is the worst available move `mise-tasks/land.sh`'s own header says it: *"each lap rebases onto a little more landed work, so conflicts arrive one small resolvable increment at a time instead of accumulating until a branch cannot land at all."* Lapping **is** the catch-up mechanism, so a stopped branch ages while the target keeps moving. A cap is a checkpoint, not a stop sign. The message was not wrong — it was ambiguous in the unsafe direction: a rationale for stopping, then an imperative whose most natural reading is stop. It now names a runnable check *and* an imperative that continues. ## Re-applied across CLOUD-865's rename This content was authored against `mise-tasks/land`, which `main` no longer carries. It is re-applied by hand onto `mise-tasks/land.sh` rather than merged: the branch stood one commit past `f3eb5cd` while `main` had moved 57 commits on, and the clone was shallow, so `git merge-base` resolved to nothing at all and the two histories read as unrelated. Replaying the two hunks onto current `main` sidesteps a rename conflict in both files. The surrounding code has also moved to `[[ ]]` where the original hunk's context used `[ ]`. ## Deliberately one message There are **420** terminal refusals under `mise-tasks/`, and a bats case apiece would be 420 bespoke assertions written in the language the retirement campaign (CLOUD-843) exists to delete. The case here says so in its own comment so it is not copied. A text predicate over the class was measured and does not survive. Against a generous detector — backticks, `mise run`, `git`, `gh`, `cargo`, a `BATTEN_*` knob, any path — only **103 of 420** name a runnable object, so it fires on **75%**. Most of that is good messages: a could-not-look diagnostic has no remedy by construction. Rego cannot do better; `policy/run-shape.rego:19` records that this build of regorus carries no `regex` builtins (CLOUD-885). ## Where the general property actually comes from Structurally, and it already exists: a rule kind requires `no_fix_reason` (`rules.rs`), and ingest refuses a finding without one — *"a finding a caller cannot act on is not storable"* (`findings.rs`). A gate acquires that the moment it becomes a policy row, which is CLOUD-843's migration. ## What this commit does NOT settle CLOUD-904, filed reviewing this change, records two defects the wording does not reach: the runaway message asserts an inference its own refunds refute, and the two exhaustion paths give imperatives inconsistent with their costs. The unconditional imperative here re-arms the only brake on that CI spend, and the declared mutation below is caught by identity rather than by discrimination. Both are superseded in the following commit; this one is CLOUD-871's worked instance and the provenance for it. DO-NOT-CLOSE: CLOUD-871's disposition is to fold into CLOUD-843 rather than ship a gate; both rows carry the reasoning and the measurements. This commit is the one worked instance, not the row's completion. Refs: CLOUD-871, CLOUD-904, CLOUD-865 --- mise-tasks/land.sh | 11 ++++++++++- tests/land.bats | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/mise-tasks/land.sh b/mise-tasks/land.sh index 35823dba2..0f272dd83 100755 --- a/mise-tasks/land.sh +++ b/mise-tasks/land.sh @@ -90,6 +90,7 @@ # MUTATION COVERAGE (CLOUD-418). `||`: applying # the script to a throwaway copy of this file must turn the named case RED. # A gate listed in $MUTANT_GATES with no row here fails `mise run mutant`. +#MUTANT lap-cap-may-read-as-stop|s/RUN THIS AGAIN/look/|names the continuing action #MUTANT exit-codes-collapse|s/^readonly LAND_EXIT_RUNAWAY=5$/readonly LAND_EXIT_RUNAWAY=4/|CLOUD-399: the two exhaustions are told apart by CODE #MUTANT declined-always|s/^\t\[\[ \"\$rc\" = 3 \]\]$/\ttrue/|red CI stops the lap without asking for the merge # CLOUD-369. The admission predicates, each proven to discriminate rather than @@ -1172,9 +1173,17 @@ admitted=0 admitted_sha= while :; do lap=$((lap + 1)) + # THE REMEDY NAMES THE CONTINUING ACTION, and that is not stylistic. This + # message used to end "Look before lapping again", which reads as STOP — and + # an agent stopped, for 55 minutes, on a one-commit branch. Stopping is the + # single worst move available here: this header's own opening paragraph says + # lapping is the catch-up mechanism, so a branch that stops ages while the + # target keeps moving, which is the "cannot land at all" state the design + # exists to prevent. A cap is a checkpoint, never a stop sign; the imperative + # has to say so, and it has to name the check rather than ask for judgement. [[ "$lap" -le "$max_laps" ]] || die_with "$LAND_EXIT_RUNAWAY" \ - "still not linear after $max_laps laps, each of which bought a CI matrix; \`main\` is moving faster than a lap takes. Look before lapping again." + "still not linear after $max_laps laps, each of which bought a CI matrix; \`main\` is moving faster than a lap takes. Check the current rate with \`git log --oneline --since=30.minutes origin/main | wc -l\`, then RUN THIS AGAIN — lapping is how a branch catches up, and stopping is how it stops being landable." # A lap holds the lease only across its own CI window. Dropping it here — at # the top, covering every `continue` below uniformly — means a lap that lost diff --git a/tests/land.bats b/tests/land.bats index 875b835df..1f0e4d4d9 100644 --- a/tests/land.bats +++ b/tests/land.bats @@ -1072,6 +1072,47 @@ runs_query_403() { : >"$BATS_TEST_TMPDIR/rc.runs"; } [[ "$(call_order)" == *push* ]] } +@test "the lap cap's remedy names the continuing action, not a judgement" { + # CLOUD-871's one worked instance, and the reason it is a case rather than a + # comment. This message used to end "Look before lapping again", which reads + # as STOP; an agent stopped for 55 minutes on a one-commit branch. Stopping is + # the worst move available: this task's own header says lapping IS the + # catch-up mechanism, so a stopped branch ages while the target moves. + # + # A cap is a checkpoint, not a stop sign. Two things have to hold, and the + # second is the one that rots: the remedy must name a runnable check, and the + # imperative must point at continuing. A remedy that only names the check is + # still ambiguous in the unsafe direction. + # + # DELIBERATELY SINGULAR — do not copy this for the other refusals. There are + # 420 terminal refusals under `mise-tasks/`, and a case apiece would be 420 + # bespoke assertions written in the language the retirement campaign exists to + # delete. A text predicate over them was measured and is unshippable: against a + # generous detector only 103 of the 420 name a runnable object, so it fires on + # 75%, and most of that is good messages — could-not-look diagnostics have no + # remedy by construction, and `Fix the regression` is honest with no command + # behind it. Rego cannot do better; regorus is built here without `regex` + # builtins (CLOUD-885). + # + # The general property is acquired STRUCTURALLY instead, and already is: a + # rule kind requires `no_fix_reason` (rules.rs) and ingest refuses a finding + # with no remedy — "a finding a caller cannot act on is not storable" + # (findings.rs). A gate gets that the moment it becomes a policy row, which is + # CLOUD-843's migration and its §5 acceptance criterion. This case exists + # because THIS message caused measured harm — an agent read it as stop and + # stopped for 55 minutes on a one-commit branch — not because the class needs + # one each. + echo 2 >"$BATS_TEST_TMPDIR/rc.mise.verify" + LAND_MAX_LAPS=2 run "$LAND" + [ "$status" -eq 5 ] + # A runnable check, not "look". + [[ "$output" == *"git log"* ]] + # And the imperative that continues the loop. + [[ "$output" == *"RUN THIS AGAIN"* ]] + # The wording that caused the stop must not come back. + [[ "$output" != *"Look before lapping again"* ]] +} + @test "a verify that keeps losing the race exhausts laps rather than spinning" { # The lap is bounded by the backstop that already exists. A `main` that # never stops moving must reach LAND_MAX_LAPS and say so, not loop forever. From e2058c9a02381b8610a4077e9140974ba9b774e3 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 02:50:16 +0000 Subject: [PATCH 2/9] feat(gate): closing-key-check subtracts the keys the branch served from the ones the body closes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `closing-key-check` held both halves of this answer and never subtracted them. It computes `named` — every key the body mentions — and used it only to decide whether there was anything to judge. Measured on `main` at `b2f8992`, three bodies piped to the gate: | body | exit | | -- | -- | | `Closes` CLOUD-593, CLOUD-655, CLOUD-661 | 0 | | names CLOUD-655/657/658/661 in prose, `Closes CLOUD-593` only | **0** | | `DO-NOT-CLOSE CLOUD-388` | 0 | Row 2 is the defect. Four rows strand — they never reach In Review, their work is on `main` — and the passing line announces that the board *will* move. Nothing in the log distinguishes it from row 1. `landed-check` catches the state afterwards, on a later sweep, by which time the board has been wrong for however long. Neither set already in the file will do, and both wrong answers are worth naming so they are not re-proposed. `named` is deliberately over-broad: a body cites related issues, prior measurements and superseded work as evidence, so requiring every mentioned key to be closed would refuse almost every correct PR this repository writes. `claimed-keys`' full chain is **circular** here. Its source 1 is a closing keyword in the body, so for a keyless bundle branch the claimed set is derived from the closing set and agrees with it by construction — the gate would pass on exactly the bodies it must refuse. The non-circular set is the commits: the **first** key of each `Refs:` trailer is the row that commit served, the rest are citations. `claimed-keys` gains `--refs-first-only` — source 3 in isolation, the exact mirror of the existing `--closing-only`. Consulting it rather than re-deriving the trailer scan keeps one authority on what a `Refs:` trailer claims, and carries the **speculation boundary** with it: `land` rebases a waiting branch onto another branch's unlanded head (CLOUD-369), and those borrowed commits carry the holder's keys. A local re-derivation would demand the body close a sibling's rows, which is CLOUD-748's shape — it cost two `verify` runs when `claim-race-check` hit it. The two narrowing flags are mutually exclusive: each names one source, so both together is a caller that has not decided which question it is asking. The first-key rule was practised and undocumented — `grep -n "Refs" .claude/rules/commits.md` returned nothing. A gate enforcing an unwritten convention is one the next author breaks without warning, so `commits.md` states it beside the note that `claimed-keys --refs-first-only` owns the extraction. This gate is a pure function of stdin plus the checked-out branch, and its suite runs from inside this repository — so a served set read unconditionally from git would make all 17 existing cases depend on whichever branch was checked out: green on `main`, red on a bundle branch, and telling nobody which. `--served-log ''` says "this branch served nothing"; the flag being absent says "read the branch". The two verdicts differ, which is why it is a flag and not a bare variable. Both controls are landed artifacts rather than fixtures invented to suit the predicate: PR #491's real body against its branch's seven commits exits 0, and the same branch with a body closing only CLOUD-593 exits 1 naming exactly CLOUD-655, CLOUD-657, CLOUD-658 and CLOUD-661. `DO-NOT-CLOSE` exempts the subtraction as well as the closing form — demanding a PR close every served key is the question the marker just declined. A branch whose commits carry no `Refs:` trailer is not judged, which is the reading `claimed-keys` documents and every caller takes. `--list` still decides nothing, even when keys are stranded. `mise run mutant` was **silently skipping declared mutations and reporting them as caught**. Its row loop is fed by `done <<<"$rows"`, so the rows are on the shell's stdin, and `bats` — invoked inside the loop body — reads stdin and swallowed every row after the one it was running. Measured on `claimed-keys`: three declared rows, the counter reached 2, and `claimed-keys-adopts-speculated` was never applied while the task printed "every one caught" and exited 0. That is the defect CLOUD-418 was filed about, reproduced inside the remedy built for it: a report of coverage over a set it never touched, failing in the passing direction. Two ` the head branch, standing in for source 2 # --title the PR title, ALSO source 2 — for a PR you did not author # the title is the other self-declaration of what the work @@ -65,7 +81,15 @@ # commit is claimed again and the waiter races the PR it is waiting on. # The mutation makes --closing-only fall through anyway, so a `Refs:` citation is # read as a claim — CLOUD-480's shape, which is the whole reason the flag exists. -#MUTANT claimed-keys-closing-only-falls-through|s/^if \[\[ "\$closing_only" -eq 0 \]\]; then$/if true; then/|--closing-only does not fall through to a Refs: trailer +# The anchor carries a leading tab: CLOUD-674 moved this branch inside the `else` +# of the `--refs-first-only` split, and the un-indented pattern silently stopped +# matching — reported by `mutant` as `inert-mutation`, which is the reading that +# catches a mutation whose subject moved out from under it. +#MUTANT claimed-keys-closing-only-falls-through|s/^\tif \[\[ "\$closing_only" -eq 0 \]\]; then$/\tif true; then/|--closing-only does not fall through to a Refs: trailer +# The mutation makes --refs-first-only fall back to the closing keyword, which is +# the circularity CLOUD-674 exists to avoid: the served set would be derived from +# the body it is about to be subtracted from, and agree with it by construction. +#MUTANT claimed-keys-refs-first-falls-back|s/^if \[\[ "\$refs_first_only" -eq 1 \]\]; then$/if false; then/|--refs-first-only ignores a closing keyword in the body #MUTANT claimed-keys-adopts-speculated|s/^\tif since=\$(spec_base_range); then$/\tif false; then/|a key carried only by a speculated commit is not claimed set -euo pipefail @@ -85,6 +109,7 @@ extra="" explicit=0 closing_only=0 +refs_first_only=0 branch="" title="" log="" @@ -109,6 +134,10 @@ while [[ "$#" -gt 0 ]]; do closing_only=1 shift ;; + --refs-first-only) + refs_first_only=1 + shift + ;; *) echo "::error:: claimed-keys: unknown argument" >&2 exit 2 @@ -116,6 +145,14 @@ while [[ "$#" -gt 0 ]]; do esac done +# Each flag names a different SINGLE source, so both together is not an +# intersection to compute — it is a caller that has not decided which question it +# is asking. Exit 2 is this file's "could not read the input" code. +if [[ "$closing_only" -eq 1 && "$refs_first_only" -eq 1 ]]; then + echo "::error:: claimed-keys: --closing-only and --refs-first-only each name one source; pick one" >&2 + exit 2 +fi + # THE COMMITS THIS BRANCH AUTHORED, WHICH IS NARROWER THAN THE ONES IT CARRIES # (CLOUD-748). `land`'s speculative linearization (CLOUD-369) rebases a waiting # branch onto the lease holder's published head, and says so plainly: it "puts @@ -155,10 +192,20 @@ if [[ "$explicit" -eq 0 ]]; then fi fi -claimed=$(extract "$(grep -oiE "$CLAIM_RE" <<<"$extra $log" || true)") -if [[ "$closing_only" -eq 0 ]]; then - [[ -n "$claimed" ]] || claimed=$(extract "$branch $title") - [[ -n "$claimed" ]] || claimed=$(extract "$(grep -oiE "Refs:[[:space:]]*CLOUD-[0-9]+" <<<"$log" || true)") +# Source 3 in isolation. `Refs:` is matched with only whitespace between it and +# the key, so this yields the FIRST key of each trailer and not the citations +# after it — which is the distinction CLOUD-674's predicate rests on, and it is a +# property of this pattern rather than an extra filter. +refs_first() { extract "$(grep -oiE "Refs:[[:space:]]*CLOUD-[0-9]+" <<<"$log" || true)"; } + +if [[ "$refs_first_only" -eq 1 ]]; then + claimed=$(refs_first) +else + claimed=$(extract "$(grep -oiE "$CLAIM_RE" <<<"$extra $log" || true)") + if [[ "$closing_only" -eq 0 ]]; then + [[ -n "$claimed" ]] || claimed=$(extract "$branch $title") + [[ -n "$claimed" ]] || claimed=$(refs_first) + fi fi [[ -n "$claimed" ]] && printf '%s\n' "$claimed" diff --git a/mise-tasks/closing-key-check.sh b/mise-tasks/closing-key-check.sh index 2995d70de..fc1ffffde 100755 --- a/mise-tasks/closing-key-check.sh +++ b/mise-tasks/closing-key-check.sh @@ -46,7 +46,12 @@ # body. # A gate listed in $MUTANT_GATES with no row here fails `mise run mutant`. #MUTANT named-but-unclosed-passes|s/^exit 1$/exit 0/|named, never closed - +# +# The subtraction is the row that needs it too: a gate that always returns the +# empty difference is a rubber stamp, and every OTHER case in this suite still +# passes under that mutation — precisely the state this file shipped in before +# CLOUD-674. +#MUTANT closing-key-strand-never-fires|s/^\tstranded=\$(comm -23 /\tstranded=$(true /|a body closing a strict subset of the served keys is refused set -euo pipefail # The opt-out, and the same string `released` matches on the issue side. Stated @@ -60,14 +65,37 @@ HOLD_MARKER='DO-NOT-CLOSE' CLOSING_VERBS='clos(e|es|ed)|fix(|es|ed)|resolv(e|es|ed)' LIST_ONLY= -case "${1:-}" in ---list) LIST_ONLY=1 ;; -"") ;; -*) - echo "usage: closing-key-check [--list] (PR body on stdin)" >&2 - exit 2 - ;; -esac +# The served set is INJECTABLE, and not only for the suite's benefit. This gate is +# a pure function of stdin plus the checked-out branch, and its existing cases run +# from inside this repository — so a served set read unconditionally from git would +# make every one of them depend on whatever branch happened to be checked out. +# +# Empty is DISTINCT from absent, which is why this is a flag rather than a bare +# variable: absent means "read the branch", `--served-log ''` means "this branch +# served nothing", and the two verdicts differ. +SERVED_LOG= +SERVED_LOG_GIVEN= +while [[ "$#" -gt 0 ]]; do + case "$1" in + --list) + LIST_ONLY=1 + shift + ;; + --served-log) + [[ "$#" -ge 2 ]] || { + echo "::error:: closing-key-check: --served-log needs a value" >&2 + exit 2 + } + SERVED_LOG="$2" + SERVED_LOG_GIVEN=1 + shift 2 + ;; + *) + echo "usage: closing-key-check [--list] [--served-log ] (PR body on stdin)" >&2 + exit 2 + ;; + esac +done # Exit 2 is "I could not read the input", distinct from exit 1 "this PR will not # move the board" — a caller piping nothing must not look like a passing body. @@ -132,7 +160,74 @@ if [[ -n "${LIST_ONLY:-}" ]]; then exit 0 fi +# THE SET THIS BRANCH SERVED, WHICH THIS GATE HELD BOTH HALVES OF AND NEVER +# SUBTRACTED (CLOUD-674). A bundle PR carrying eight rows and closing three +# strands five: they never reach In Review, their work is on `main`, and the +# passing line below announces that the board WILL move. Measured on `main` at +# `b2f8992` — a body naming CLOUD-655/657/658/661 in prose and closing only +# CLOUD-593 exited 0, indistinguishable in the log from a body that closed all +# five. +# +# THE COMPARISON SET IS THE COMMITS, and neither of the two sets already in this +# file will do. `named` is deliberately over-broad — a body cites related issues, +# prior measurements and superseded work as evidence, so requiring every mentioned +# key to be closed would refuse almost every correct PR this repo writes. And +# `claimed-keys`' full chain is CIRCULAR here: its source 1 is a closing keyword in +# the body, so for a keyless branch the claimed set is derived from the closing set +# and agrees with it by construction — the gate would pass on exactly the bodies it +# must refuse. +# +# `--refs-first-only` is that file's source 3 in isolation, added for this caller. +# Consulting it rather than re-deriving the trailer scan here keeps one authority +# on what a `Refs:` trailer claims, and carries the SPECULATION BOUNDARY with it: +# `land` rebases a waiting branch onto another branch's unlanded head (CLOUD-369), +# and those borrowed commits carry the holder's keys. A local re-derivation would +# demand the body close a sibling's rows — CLOUD-748's shape, which cost two +# `verify` runs when `claim-race-check` hit it. +served= +if [[ -n "${SERVED_LOG_GIVEN:-}" ]]; then + served=$("$(dirname "$0")/claimed-keys.sh" --refs-first-only --branch "" --title "" --log "$SERVED_LOG" 2>/dev/null || true) +else + served=$("$(dirname "$0")/claimed-keys.sh" --refs-first-only 2>/dev/null \" for each, or add $HOLD_MARKER if this PR is not meant to complete them." + } >&2 + exit 1 + fi echo "closing-key-check: closes $(tr '\n' ' ' <<<"$closing" | sed 's/ $//') — the merge will move the board" exit 0 fi diff --git a/mise-tasks/mutant.sh b/mise-tasks/mutant.sh index 353868415..994b162be 100755 --- a/mise-tasks/mutant.sh +++ b/mise-tasks/mutant.sh @@ -206,7 +206,19 @@ for gate in ${gates//,/ }; do # outcome. Both defects were invisible because they cancelled out. Costs # one extra filtered bats run per row, which is what an anti-vacuity term # is worth. - clean_out="$(cd "$work" && "$bats_bin" --filter "$want" "$suite" 2>&1)" + # `&1 &1)" + # Same reason as the clean run above: this must not eat the remaining rows. + out="$(cd "$work" && "$bats_bin" --filter "$want" "$suite" 2>&1 "* ]] +} + +@test "only the FIRST key of a Refs: trailer is served — the rest are citations" { + # The distinction the whole predicate rests on. This branch served CLOUD-593 + # alone; CLOUD-102 and CLOUD-654 are cited beside it and must not be demanded. + run bash -c "printf 'Closes CLOUD-593\n' | $RAW --served-log 'Refs: CLOUD-593, CLOUD-654, CLOUD-102'" + [ "$status" -eq 0 ] + [[ "$output" != *"served, not closed"* ]] +} + +@test "DO-NOT-CLOSE exempts the subtraction, not merely the closing form" { + # The marker says the PR deliberately does not complete its issue, so demanding + # it close every served key asks the question it just declined. + run bash -c "printf 'DO-NOT-CLOSE — part 1 of 3.\n\nCloses CLOUD-593\n' | $RAW --served-log '$SERVED_491'" + [ "$status" -eq 0 ] + [[ "$output" != *"served, not closed"* ]] +} + +@test "a branch whose commits carry no Refs: trailer is not judged" { + # No claim means do not judge — the reading `claimed-keys` documents and every + # caller of it takes. A gate that guessed here would block correct work. + run bash -c "printf 'Closes CLOUD-593\n' | $RAW --served-log 'a commit subject with no trailer at all'" + [ "$status" -eq 0 ] + [[ "$output" == *"the merge will move the board"* ]] +} + +@test "a single-ticket PR is unaffected" { + run bash -c "printf 'Closes CLOUD-593\n' | $RAW --served-log 'Refs: CLOUD-593'" + [ "$status" -eq 0 ] +} + +@test "--served-log '' is distinct from the flag being absent" { + # Empty means "this branch served nothing"; absent means "read the branch". + # Collapsing the two would make every case above depend on the checkout. + run bash -c "printf 'Closes CLOUD-593\n' | $RAW --served-log ''" + [ "$status" -eq 0 ] + run bash -c "printf 'Closes CLOUD-593\n' | $RAW --served-log" + [ "$status" -eq 2 ] + [[ "$output" == *"needs a value"* ]] +} + +@test "--list decides nothing, even when keys are stranded" { + # The flag changes what is emitted, never what is matched (CLOUD-774), and that + # has to survive a predicate that can now refuse. + run bash -c "printf 'Closes CLOUD-593\n' | $RAW --list --served-log '$SERVED_491'" + [ "$status" -eq 0 ] + [[ "$output" == "CLOUD-593" ]] +} + +@test "the served set ignores a closing keyword — the comparison is not circular" { + # `claimed-keys`' full chain answers from a closing keyword FIRST, so a served + # set taken from it would be derived from the body it is about to be subtracted + # from and agree with it by construction. `--refs-first-only` is what stops + # that, and this is the case that would catch a fall-back: the log carries a + # closing keyword for a key it does NOT serve. + run bash -c "printf 'Closes CLOUD-593\n' | $RAW --served-log 'Closes CLOUD-999 + +Refs: CLOUD-661'" + [ "$status" -eq 1 ] + [[ "$output" == *"CLOUD-661 served, not closed"* ]] + [[ "$output" != *"CLOUD-999"* ]] +} From f817d839d27ce7bc6d99d2a61d2f607e39c7d555 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 03:11:30 +0000 Subject: [PATCH 3/9] feat(gate): price a prose-only branch before it buys a matrix nothing can grade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured 2026-08-21, by doing it: a branch whose entire diff was two rewritten sentences of `//!` doc comment in `crates/batten/src/git.rs` went through `verify` and was on its way to `gh pr create` + `land` — a full required matrix (`ci`, `cross`, `commit-lint`, `zizmor`, `darwin-link`, `semver`, `perf`, `windows`, `final`) against a trunk landing every ~16 minutes. What stopped it was a human saying "don't you dare waste CI minutes for comments". That is the wrong mechanism, and the reason is the whole argument for this gate: the agent HAD the rule — it is in AGENTS.md — had just spent four laps of the landing loop on disk exhaustion and lease races, and still queued the matrix, because every gate it consulted said yes. Prose is feedforward only. AGENTS.md: "Local execution — bash, a build, the whole test suite — costs nothing... A CI run costs real minutes." `ci.yml`'s own header names the two economies it implements — drafts run nothing, and `main` is not a trigger. This is the third: **a change CI cannot have an opinion about should ride the next change that it can.** Nothing in `land`'s pre-ready set asked what the diff was WORTH; `verify` asks whether it is correct, `linear-check` whether it is landable, `ready-guard` whether both were proved. A comment in this repository can change a verdict: `every_stays_shelled_out_claim_names_its_price` scans a module doc for citations, `no_gix_gap_primitive_survives` scans `src/` for retired vocabulary, `spec-ref-check` resolves `CLOUD- §N` citations, `rules-drift` holds restated defaults against their mechanisms. Every one of those runs in `verify`, locally, for free — which is precisely why the economy HOLDS rather than fails. If a comment change breaks one, the author learns before a runner is spent. CI is confirming what was already proved, and on a prose-only diff it confirms nothing that could differ. Over `git diff --unified=0 origin/main...HEAD`: refuse when every `+`/`-` line is a comment for its file's language AND no path under `tests/` changed. The `tests/` conjunct is what makes the good case pass, and it is the difference between pricing batching and obstructing doc work: PR #604 — a doc rewrite plus the gate enforcing it — is admitted, while the follow-up carrying only the two sentences is not. An unrecognised extension counts as NOT a comment, so an unknown file type admits the branch. The failure direction is deliberate: wrong one way this spends someone else's minutes, wrong the other way it blocks correct work, and only the second cannot be recovered by waiting. Every could-not-look path exits 0 for the same reason. A Rust block comment reads as code, because `/* */` cannot be classified line-by-line without tracking state and guessing would fail in the refusing direction. `--diff-filter=d` drops deletions: a removed file has no surviving lines to classify, and treating it as prose would let a branch that deletes a module read as a doc change. `land`'s pre-ready set as the SIXTH stop, beside `deferral-check`, `filed-here-check` and `closing-key-check`, and in `verify:gated` immediately before the receipt write — last of the content gates, because asking what a branch is worth before telling the author whether it works is the wrong order. Not a CI job, and that is `ci-local-parity` satisfied rather than dodged: the constraint is that every task CI runs is one `verify` runs, not the reverse. It could not be one honestly either — by the time CI runs, the matrix this exists to avoid has already been bought. `ready-guard` refuses `gh pr ready` without a verify receipt for this exact HEAD, so the branch is stopped before it can spend a runner at all. `BATTEN_PROSE_ONLY_OVERRIDE=1` is the recorded escape, in the `BATTEN_FILED_HERE_OVERLAP` idiom: it writes what it overrode to `$GIT_DIR/batten-receipts/prose-only-overrides.` and prints the same, so a reviewer sees a decision rather than a silence. `tests/land.bats` carries a COUNT ASSERTION over `land`'s stopping conditions, so a new stop cannot be added silently — exactly what it is for, and it caught this one. 31 -> 32, with the reason recorded beside the count and a case exercising the stop. `closing-key-check`'s failure message in `land` also had to keep the substring `land.bats` asserts on. It now covers both halves — named-but-never-closed, and CLOUD-674's strands-the-rest — rather than being reworded out from under its own test. `suite-bench-check` refused the new suite until `bench/suites/RESULTS.md` recorded what it costs (0.7s, 0.1%). Regenerated with `mise run suite-bench --write`. Its "tracked files only" paragraph predicts `names-no-case` for an untracked suite. The real symptom for a NEW gate whose suite is also new is `case-already-red` — `cp` puts the gate in place while the suite is absent, so every case reads as red-before-mutation and points the reader at their assertions when the fix is `git add`. Both symptoms now written down with the case each belongs to. 14 cases, each building its own repository so none depends on the checkout; 4 declared mutations, every one caught; `tests/land.bats` 142/142; the full `test:bats` tier 2708/2708; `ci-local-parity` green. Closes CLOUD-827 Refs: CLOUD-827, CLOUD-514, CLOUD-323, CLOUD-240, CLOUD-418 --- bench/suites/RESULTS.md | 253 ++++++++++++++++----------------- mise-tasks/land.sh | 22 ++- mise-tasks/mutant.sh | 9 ++ mise-tasks/prose-only-check.sh | 184 ++++++++++++++++++++++++ mise.toml | 24 ++++ tests/land.bats | 28 +++- tests/prose-only-check.bats | 194 +++++++++++++++++++++++++ 7 files changed, 582 insertions(+), 132 deletions(-) create mode 100755 mise-tasks/prose-only-check.sh create mode 100644 tests/prose-only-check.bats diff --git a/bench/suites/RESULTS.md b/bench/suites/RESULTS.md index 3042249e8..653c5eb0f 100644 --- a/bench/suites/RESULTS.md +++ b/bench/suites/RESULTS.md @@ -6,164 +6,159 @@ runner measured it; the suite runs `--no-parallelize-within-files`, so a file's number is its own serial cost and is what an author adding a case to it pays. -- suites: 156 -- serial total: 909.6s +- suites: 151 +- serial total: 849.7s | seconds | share | suite | | ---: | ---: | --- | -| 135.9 | 14.9% | `tests/land-lock.bats` | -| 102.9 | 11.3% | `tests/derived-check.bats` | -| 102.2 | 11.2% | `tests/ci-wait.bats` | -| 80.2 | 8.8% | `tests/land.bats` | -| 51.4 | 5.7% | `tests/hooks-wiring-check.bats` | -| 37.6 | 4.1% | `tests/ci-local-parity.bats` | -| 36.1 | 4.0% | `tests/helpers.bats` | -| 34.6 | 3.8% | `tests/main-watch.bats` | -| 24.3 | 2.7% | `tests/hook-latency-drift.bats` | -| 15.6 | 1.7% | `tests/claim-check.bats` | -| 12.6 | 1.4% | `tests/board-diff-overlap.bats` | -| 10.1 | 1.1% | `tests/prebuilt-lint.bats` | -| 9.7 | 1.1% | `tests/config-lint.bats` | -| 9.6 | 1.1% | `tests/graph-check.bats` | -| 8.9 | 1.0% | `tests/run-shape-guard.bats` | -| 8.5 | 0.9% | `tests/token-bench.bats` | -| 7.7 | 0.8% | `tests/board-write-record.bats` | -| 7.5 | 0.8% | `tests/target-race.bats` | -| 6.6 | 0.7% | `tests/mcp-allow-check.bats` | -| 6.5 | 0.7% | `tests/ready-lint.bats` | -| 6.4 | 0.7% | `tests/filed-here-check.bats` | -| 6.1 | 0.7% | `tests/ready-guard.bats` | +| 129.8 | 15.3% | `tests/land-lock.bats` | +| 102.1 | 12.0% | `tests/ci-wait.bats` | +| 99.2 | 11.7% | `tests/derived-check.bats` | +| 73.4 | 8.6% | `tests/land.bats` | +| 49.5 | 5.8% | `tests/hooks-wiring-check.bats` | +| 36.1 | 4.2% | `tests/helpers.bats` | +| 34.6 | 4.1% | `tests/main-watch.bats` | +| 32.1 | 3.8% | `tests/ci-local-parity.bats` | +| 24.2 | 2.9% | `tests/hook-latency-drift.bats` | +| 13.8 | 1.6% | `tests/claim-check.bats` | +| 11.6 | 1.4% | `tests/board-diff-overlap.bats` | +| 9.0 | 1.1% | `tests/config-lint.bats` | +| 8.7 | 1.0% | `tests/prebuilt-lint.bats` | +| 8.7 | 1.0% | `tests/graph-check.bats` | +| 7.4 | 0.9% | `tests/token-bench.bats` | +| 7.3 | 0.9% | `tests/target-race.bats` | +| 7.3 | 0.9% | `tests/run-shape-guard.bats` | +| 7.2 | 0.8% | `tests/board-write-record.bats` | | 6.0 | 0.7% | `tests/renovate-config-validator.bats` | -| 5.5 | 0.6% | `tests/released.bats` | -| 5.4 | 0.6% | `tests/stop-guard.bats` | -| 5.4 | 0.6% | `tests/lock-complete.bats` | -| 5.3 | 0.6% | `tests/sbom-check.bats` | -| 5.1 | 0.6% | `tests/replay.bats` | -| 4.9 | 0.5% | `tests/session-start.bats` | -| 4.7 | 0.5% | `tests/singleton.bats` | -| 4.6 | 0.5% | `tests/mutant.bats` | -| 4.5 | 0.5% | `tests/step-receipt.bats` | -| 4.4 | 0.5% | `tests/in-progress-drain.bats` | -| 4.3 | 0.5% | `tests/release-assets-check.bats` | -| 4.0 | 0.4% | `tests/task-registry.bats` | -| 3.9 | 0.4% | `tests/pre-commit-staging.bats` | -| 3.8 | 0.4% | `tests/schema-check.bats` | -| 3.7 | 0.4% | `tests/with-lock.bats` | -| 3.5 | 0.4% | `tests/doctor-race.bats` | -| 3.5 | 0.4% | `tests/board-move-guard.bats` | -| 3.3 | 0.4% | `tests/target-ensure.bats` | -| 3.2 | 0.4% | `tests/land-divergence.bats` | -| 3.2 | 0.4% | `tests/reference-check.bats` | -| 2.9 | 0.3% | `tests/semver.bats` | -| 2.9 | 0.3% | `tests/board-sweep.bats` | -| 2.7 | 0.3% | `tests/issue-read-check.bats` | -| 2.7 | 0.3% | `tests/issue-read-guard.bats` | -| 2.4 | 0.3% | `tests/ready-cites-check.bats` | -| 2.4 | 0.3% | `tests/hk-selection.bats` | -| 2.0 | 0.2% | `tests/claim-race-check.bats` | +| 5.8 | 0.7% | `tests/mcp-allow-check.bats` | +| 5.8 | 0.7% | `tests/filed-here-check.bats` | +| 5.7 | 0.7% | `tests/ready-guard.bats` | +| 5.6 | 0.7% | `tests/ready-lint.bats` | +| 5.1 | 0.6% | `tests/released.bats` | +| 4.8 | 0.6% | `tests/stop-guard.bats` | +| 4.8 | 0.6% | `tests/session-start.bats` | +| 4.8 | 0.6% | `tests/lock-complete.bats` | +| 4.5 | 0.5% | `tests/singleton.bats` | +| 4.3 | 0.5% | `tests/sbom-check.bats` | +| 4.1 | 0.5% | `tests/step-receipt.bats` | +| 4.1 | 0.5% | `tests/release-assets-check.bats` | +| 4.0 | 0.5% | `tests/in-progress-drain.bats` | +| 3.8 | 0.5% | `tests/task-registry.bats` | +| 3.6 | 0.4% | `tests/with-lock.bats` | +| 3.6 | 0.4% | `tests/schema-check.bats` | +| 3.4 | 0.4% | `tests/doctor-race.bats` | +| 3.3 | 0.4% | `tests/board-move-guard.bats` | +| 3.1 | 0.4% | `tests/pre-commit-staging.bats` | +| 3.1 | 0.4% | `tests/reference-check.bats` | +| 2.9 | 0.3% | `tests/land-divergence.bats` | +| 2.9 | 0.3% | `tests/target-ensure.bats` | +| 2.8 | 0.3% | `tests/ready-cites-check.bats` | +| 2.7 | 0.3% | `tests/mutant.bats` | +| 2.6 | 0.3% | `tests/board-sweep.bats` | +| 2.6 | 0.3% | `tests/issue-read-check.bats` | +| 2.6 | 0.3% | `tests/hk-selection.bats` | +| 2.6 | 0.3% | `tests/semver.bats` | +| 2.2 | 0.3% | `tests/issue-read-guard.bats` | | 1.9 | 0.2% | `tests/fanout-guard.bats` | -| 1.9 | 0.2% | `tests/finding-sink-check.bats` | -| 1.9 | 0.2% | `tests/skill-check.bats` | -| 1.9 | 0.2% | `tests/landed-check.bats` | -| 1.8 | 0.2% | `tests/spec-ref-check.bats` | -| 1.7 | 0.2% | `tests/unlanded-check.bats` | +| 1.7 | 0.2% | `tests/claim-race-check.bats` | +| 1.7 | 0.2% | `tests/skill-check.bats` | +| 1.7 | 0.2% | `tests/landed-check.bats` | +| 1.6 | 0.2% | `tests/finding-sink-check.bats` | +| 1.6 | 0.2% | `tests/unlanded-check.bats` | | 1.6 | 0.2% | `tests/reclaim-census.bats` | -| 1.6 | 0.2% | `tests/suite-select.bats` | -| 1.6 | 0.2% | `tests/bot-issue.bats` | -| 1.5 | 0.2% | `tests/signing-posture.bats` | -| 1.4 | 0.2% | `tests/issue-search-guard.bats` | -| 1.4 | 0.2% | `tests/ci-slow-needed.bats` | -| 1.4 | 0.2% | `tests/tree-clean.bats` | -| 1.4 | 0.2% | `tests/claimed-keys.bats` | -| 1.4 | 0.1% | `tests/ci-lease-precondition.bats` | -| 1.3 | 0.1% | `tests/verify.bats` | -| 1.3 | 0.1% | `tests/run-shape.bats` | -| 1.3 | 0.1% | `tests/memories-check.bats` | -| 1.3 | 0.1% | `tests/alive.bats` | -| 1.2 | 0.1% | `tests/deferral-check.bats` | -| 1.2 | 0.1% | `tests/mutant-census.bats` | -| 1.2 | 0.1% | `tests/awk-regex-check.bats` | -| 1.2 | 0.1% | `tests/ready-lint-deferral.bats` | -| 1.2 | 0.1% | `tests/ci-tools-check.bats` | -| 1.1 | 0.1% | `tests/done-check.bats` | -| 1.1 | 0.1% | `tests/perf-record.bats` | -| 1.1 | 0.1% | `tests/spawn-census.bats` | -| 1.1 | 0.1% | `tests/nonverdict-scan.bats` | -| 1.1 | 0.1% | `tests/land-divergence-assert.bats` | +| 1.5 | 0.2% | `tests/spec-ref-check.bats` | +| 1.4 | 0.2% | `tests/bot-issue.bats` | +| 1.4 | 0.2% | `tests/closing-key-check.bats` | +| 1.4 | 0.2% | `tests/signing-posture.bats` | +| 1.3 | 0.2% | `tests/claimed-keys.bats` | +| 1.3 | 0.2% | `tests/issue-search-guard.bats` | +| 1.3 | 0.2% | `tests/ci-slow-needed.bats` | +| 1.3 | 0.1% | `tests/tree-clean.bats` | +| 1.2 | 0.1% | `tests/run-shape.bats` | +| 1.2 | 0.1% | `tests/memories-check.bats` | +| 1.2 | 0.1% | `tests/ci-lease-precondition.bats` | +| 1.2 | 0.1% | `tests/verify.bats` | +| 1.1 | 0.1% | `tests/ready-lint-deferral.bats` | +| 1.0 | 0.1% | `tests/awk-regex-check.bats` | +| 1.0 | 0.1% | `tests/perf-record.bats` | +| 1.0 | 0.1% | `tests/alive.bats` | +| 1.0 | 0.1% | `tests/done-check.bats` | +| 1.0 | 0.1% | `tests/deferral-check.bats` | | 1.0 | 0.1% | `tests/install-check.bats` | -| 1.0 | 0.1% | `tests/rules-drift.bats` | -| 1.0 | 0.1% | `tests/linear-check.bats` | -| 1.0 | 0.1% | `tests/attestation-check.bats` | -| 1.0 | 0.1% | `tests/hook-pin-check.bats` | -| 0.9 | 0.1% | `tests/ntia-check.bats` | -| 0.9 | 0.1% | `tests/target-prune.bats` | -| 0.9 | 0.1% | `tests/closing-key-check.bats` | -| 0.9 | 0.1% | `tests/doctor.bats` | -| 0.8 | 0.1% | `tests/gh-guard.bats` | -| 0.8 | 0.1% | `tests/done-pr-check.bats` | -| 0.8 | 0.1% | `tests/pr-unsubscribed.bats` | +| 1.0 | 0.1% | `tests/ci-tools-check.bats` | +| 0.9 | 0.1% | `tests/linear-check.bats` | +| 0.9 | 0.1% | `tests/land-divergence-assert.bats` | +| 0.9 | 0.1% | `tests/spawn-census.bats` | +| 0.9 | 0.1% | `tests/nonverdict-scan.bats` | +| 0.8 | 0.1% | `tests/rules-drift.bats` | | 0.8 | 0.1% | `tests/release-tracking-check.bats` | -| 0.8 | 0.1% | `tests/module-map-check.bats` | -| 0.8 | 0.1% | `tests/timeout-drift.bats` | -| 0.7 | 0.1% | `tests/perf-assert.bats` | -| 0.7 | 0.1% | `tests/sbom-binary.bats` | +| 0.8 | 0.1% | `tests/done-pr-check.bats` | +| 0.8 | 0.1% | `tests/hook-pin-check.bats` | +| 0.8 | 0.1% | `tests/attestation-check.bats` | +| 0.8 | 0.1% | `tests/ntia-check.bats` | +| 0.8 | 0.1% | `tests/target-prune.bats` | +| 0.7 | 0.1% | `tests/module-map-check.bats` | +| 0.7 | 0.1% | `tests/gh-guard.bats` | +| 0.7 | 0.1% | `tests/prose-only-check.bats` | +| 0.7 | 0.1% | `tests/pr-unsubscribed.bats` | +| 0.7 | 0.1% | `tests/doctor.bats` | +| 0.7 | 0.1% | `tests/render-cli.bats` | | 0.7 | 0.1% | `tests/checks-green.bats` | +| 0.7 | 0.1% | `tests/timeout-drift.bats` | | 0.7 | 0.1% | `tests/issue-search-check.bats` | -| 0.7 | 0.1% | `tests/render-cli.bats` | -| 0.7 | 0.1% | `tests/hook-matcher-check.bats` | -| 0.6 | 0.1% | `tests/mcp-attach-check.bats` | +| 0.6 | 0.1% | `tests/perf-assert.bats` | +| 0.6 | 0.1% | `tests/sbom-binary.bats` | +| 0.6 | 0.1% | `tests/hook-matcher-check.bats` | +| 0.6 | 0.1% | `tests/merged-pr-keys.bats` | +| 0.6 | 0.1% | `tests/evaluator-closure-check.bats` | | 0.6 | 0.1% | `tests/verified.bats` | | 0.6 | 0.1% | `tests/perf-compare.bats` | -| 0.6 | 0.1% | `tests/merged-pr-keys.bats` | | 0.6 | 0.1% | `tests/mcp-timeout-budget.bats` | | 0.6 | 0.1% | `tests/install.bats` | -| 0.6 | 0.1% | `tests/evaluator-closure-check.bats` | -| 0.6 | 0.1% | `tests/connector-verb-guard.bats` | -| 0.6 | 0.1% | `tests/hook-profile-check.bats` | -| 0.6 | 0.1% | `tests/stop-posture-check.bats` | +| 0.6 | 0.1% | `tests/mcp-attach-check.bats` | +| 0.5 | 0.1% | `tests/hook-profile-check.bats` | +| 0.5 | 0.1% | `tests/connector-verb-guard.bats` | +| 0.5 | 0.1% | `tests/stop-posture-check.bats` | | 0.5 | 0.1% | `tests/checksums.bats` | | 0.5 | 0.1% | `tests/publish-credential-check.bats` | -| 0.5 | 0.1% | `tests/pipefail-grep-check.bats` | -| 0.5 | 0.1% | `tests/macos-link-check.bats` | -| 0.5 | 0.1% | `tests/digest-major-agreement.bats` | -| 0.4 | 0.0% | `tests/abandon-matrix.bats` | -| 0.4 | 0.0% | `tests/connector-allow-guard.bats` | +| 0.4 | 0.1% | `tests/macos-link-check.bats` | | 0.4 | 0.0% | `tests/sonar-gate.bats` | +| 0.4 | 0.0% | `tests/pipefail-grep-check.bats` | +| 0.4 | 0.0% | `tests/digest-major-agreement.bats` | | 0.4 | 0.0% | `tests/msrv-pin-agreement.bats` | -| 0.4 | 0.0% | `tests/license-table-check.bats` | -| 0.4 | 0.0% | `tests/land-lock-check.bats` | +| 0.4 | 0.0% | `tests/connector-allow-guard.bats` | +| 0.4 | 0.0% | `tests/abandon-matrix.bats` | | 0.4 | 0.0% | `tests/branch-age-check.bats` | | 0.4 | 0.0% | `tests/serena-mcp.bats` | -| 0.4 | 0.0% | `tests/transcript-corpus-check.bats` | -| 0.4 | 0.0% | `tests/run-shape-guard-quoting.bats` | -| 0.4 | 0.0% | `tests/release-due.bats` | -| 0.4 | 0.0% | `tests/timeout-check.bats` | -| 0.4 | 0.0% | `tests/pkl-check.bats` | -| 0.4 | 0.0% | `tests/nonverdict-assert.bats` | -| 0.3 | 0.0% | `tests/suite-bench-check.bats` | -| 0.3 | 0.0% | `tests/commit-convention.bats` | +| 0.3 | 0.0% | `tests/license-table-check.bats` | +| 0.3 | 0.0% | `tests/land-lock-check.bats` | +| 0.3 | 0.0% | `tests/transcript-corpus-check.bats` | +| 0.3 | 0.0% | `tests/timeout-check.bats` | +| 0.3 | 0.0% | `tests/report-only-check.bats` | +| 0.3 | 0.0% | `tests/run-shape-guard-quoting.bats` | +| 0.3 | 0.0% | `tests/release-due.bats` | +| 0.3 | 0.0% | `tests/commit-attribution.bats` | | 0.3 | 0.0% | `tests/board-payloads.bats` | +| 0.3 | 0.0% | `tests/nonverdict-assert.bats` | +| 0.3 | 0.0% | `tests/commit-convention.bats` | +| 0.3 | 0.0% | `tests/pkl-check.bats` | | 0.3 | 0.0% | `tests/no-doctests.bats` | -| 0.3 | 0.0% | `tests/container-preflight.bats` | +| 0.3 | 0.0% | `tests/suite-bench-check.bats` | | 0.3 | 0.0% | `tests/cap-drift.bats` | -| 0.3 | 0.0% | `tests/report-only-check.bats` | | 0.3 | 0.0% | `tests/connector-allow-resolve.bats` | -| 0.3 | 0.0% | `tests/batten-glob-check.bats` | -| 0.3 | 0.0% | `tests/ci-drift.bats` | -| 0.3 | 0.0% | `tests/mise-pin-agreement.bats` | -| 0.3 | 0.0% | `tests/rust-paths-check.bats` | +| 0.3 | 0.0% | `tests/container-preflight.bats` | +| 0.2 | 0.0% | `tests/batten-glob-check.bats` | +| 0.2 | 0.0% | `tests/mise-pin-agreement.bats` | | 0.2 | 0.0% | `tests/coderabbit-config-check.bats` | -| 0.2 | 0.0% | `tests/commit-attribution.bats` | +| 0.2 | 0.0% | `tests/rust-paths-check.bats` | | 0.2 | 0.0% | `tests/git-hook.bats` | | 0.2 | 0.0% | `tests/mise-action-floor.bats` | -| 0.2 | 0.0% | `tests/perf-gate.bats` | -| 0.2 | 0.0% | `tests/token-bench-check.bats` | -| 0.2 | 0.0% | `tests/dist.bats` | +| 0.1 | 0.0% | `tests/dist.bats` | | 0.1 | 0.0% | `tests/task-fail-closed.bats` | | 0.1 | 0.0% | `tests/test-bats-parallel.bats` | -| 0.1 | 0.0% | `tests/evaluator-io-check.bats` | -| 0.1 | 0.0% | `tests/egress-check.bats` | | 0.1 | 0.0% | `tests/perf-pair.bats` | -| 0.1 | 0.0% | `tests/cross-check.bats` | +| 0.1 | 0.0% | `tests/egress-check.bats` | +| 0.1 | 0.0% | `tests/evaluator-io-check.bats` | | 0.1 | 0.0% | `tests/darwin-link.bats` | +| 0.1 | 0.0% | `tests/cross-check.bats` | | 0.0 | 0.0% | `tests/zizmor-split.bats` | diff --git a/mise-tasks/land.sh b/mise-tasks/land.sh index 0f272dd83..772525fa0 100755 --- a/mise-tasks/land.sh +++ b/mise-tasks/land.sh @@ -1444,7 +1444,27 @@ $verify_tail" # never saw is not evidence that the PR closes nothing. note_phase "closing-key-check(lap $lap)" if [[ -n "$body" ]] && ! printf '%s' "$body" | mise run closing-key-check; then - die "#$pr names its issue but never closes it, so merging it would leave the board a column behind. Write \"Closes \" in the body (or DO-NOT-CLOSE if this PR is not meant to complete it), then run land again." + die "#$pr names its issue but never closes it, or closes some of the keys its commits served and strands the rest (CLOUD-674) — the gate's own output above names which. Merging either way leaves the board a column behind for at least one row. Write \"Closes \" for every row this PR completes (or DO-NOT-CLOSE if it is not meant to complete them), then run land again." + fi + + # --- and the matrix must be able to have an opinion about the diff -------- + # + # CLOUD-827, the SIXTH stop, and the only one that is about what the change is + # WORTH rather than whether it is correct. `verify` asks whether it is right, + # `linear-check` whether it is landable, `ready-guard` whether both were + # proved — and then a full required matrix is spent on a diff no required + # check can say anything about that `verify` did not already say locally. + # + # Measured: a branch whose whole diff was two rewritten sentences of `//!` doc + # comment reached this point, and what stopped it was a human rather than a + # gate. The agent had the rule — it is in AGENTS.md — which is the definition + # of prose being feedforward only. + # + # No stdin: the predicate reads the diff. Every could-not-look path inside it + # exits 0, so this stops a lap only on a positive verdict. + note_phase "prose-only-check(lap $lap)" + if ! mise run prose-only-check; then + die "#$pr is a prose-only branch, so the matrix it is about to buy can confirm nothing that \`verify\` has not already proved locally. Put the content on the row that owns it and let the next change to those files carry it, or set BATTEN_PROSE_ONLY_OVERRIDE=1 if the prose is the deliverable and cannot wait — that records which branch used it." fi # --- take the lease before anything can start a run ----------------------- diff --git a/mise-tasks/mutant.sh b/mise-tasks/mutant.sh index 994b162be..c71531e07 100755 --- a/mise-tasks/mutant.sh +++ b/mise-tasks/mutant.sh @@ -79,6 +79,15 @@ trap 'rm -rf "$work"' EXIT # the moment this matters most is while a gate and its suite are being written, # and a sweep that could only see the last commit would report `names-no-case` # over every case not yet committed. Measured on its own first run. +# +# THE SYMPTOM OF AN UNTRACKED PAIR IS `case-already-red`, NOT `names-no-case`, and +# the line above predicts the wrong one. Measured 2026-08-23 while adding +# `prose-only-check`: a NEW gate whose suite is also new stages neither file, so +# `cp` puts the gate in place while the suite is absent — and every case then +# reads as red-before-mutation rather than as missing. That points the reader at +# their assertions when the actual fix is `git add`. `names-no-case` is the +# symptom of the narrower case the line above measured: a suite that IS tracked +# gaining an untracked CASE. git ls-files -z | tar --null -T - -cf - | tar -x -C "$work" || fail_input "could not stage the tracked tree" # The submodule's contents are not in the archive; the runner is the same binary diff --git a/mise-tasks/prose-only-check.sh b/mise-tasks/prose-only-check.sh new file mode 100755 index 000000000..42bd4ea67 --- /dev/null +++ b/mise-tasks/prose-only-check.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +#MISE description="Gate: a branch whose whole diff is comment lines buys a CI matrix no required check can have an opinion about (reads the diff; pointer-only)" +# +# CLOUD-827. Measured 2026-08-21 by doing it: a branch whose entire diff was two +# rewritten sentences of `//!` doc comment in `crates/batten/src/git.rs` went +# through `verify` and was on its way to `gh pr create` + `land` — a full required +# matrix (`ci`, `cross`, `commit-lint`, `zizmor`, `darwin-link`, `semver`, `perf`, +# `windows`, `final`) against a trunk landing every ~16 minutes. What stopped it +# was a human saying "don't you dare waste CI minutes for comments", which is the +# wrong mechanism: a rule that is only prose is feedforward, and the agent HAD the +# rule — it is in AGENTS.md — and still queued the matrix, because every gate it +# consulted said yes. +# +# THE ECONOMY IS ALREADY WRITTEN DOWN, which is what makes this an omission rather +# than a new opinion. AGENTS.md: "Local execution — bash, a build, the whole test +# suite — costs nothing... A CI run costs real minutes." `ci.yml`'s own header +# names the two economies it implements — drafts run nothing, and `main` is not a +# trigger. This is the third: a change CI cannot have an opinion about should ride +# the next change that it can. +# +# WHY THIS IS NOT "COMMENTS ARE FREE", which would be wrong in this repository. A +# comment here can change a verdict: `every_stays_shelled_out_claim_names_its_price` +# scans a module doc for citations, `no_gix_gap_primitive_survives` scans `src/` +# for retired vocabulary, `spec-ref-check` resolves `CLOUD- §N` citations in +# tracked files, `rules-drift` holds restated defaults against their mechanisms. +# Every one of those runs in `verify`, locally, for free. That is precisely why the +# economy HOLDS rather than fails: if a comment change breaks one, the author +# learns before a runner is spent. CI is confirming what was already proved, and on +# a prose-only diff it confirms nothing that could differ. +# +# THE `tests/` CONJUNCT IS WHAT MAKES THE GOOD CASE PASS, and it is the difference +# between pricing batching and obstructing doc work. A change that adds or edits a +# test is not prose-only — so PR #604, a doc rewrite PLUS the gate that enforces +# it, is admitted, while the follow-up carrying only the two sentences is not. +# +# AN UNRECOGNISED EXTENSION COUNTS AS NOT-A-COMMENT, so an unknown file type +# ADMITS the branch. The failure direction is deliberate: this gate spends someone +# else's minutes when it is wrong in one direction and blocks correct work when it +# is wrong in the other, and only the second is unrecoverable by waiting. +# +# WHERE IT RUNS. `land`'s pre-ready set, beside `deferral-check`, +# `filed-here-check` and `closing-key-check` — the three that already refuse a lap +# on grounds other than correctness — and in `verify`'s path, so enforcing it costs +# no runner. +# +# Pointer-only (non-negotiable 4): the changed paths and a count, never a line of +# the diff. A diff is content someone has not published yet. +# +# `BATTEN_PROSE_ONLY_OVERRIDE=1` mints over the refusal and RECORDS WHAT IT +# OVERRODE, the `BATTEN_FILED_HERE_OVERLAP` idiom — for when the prose IS the +# deliverable and cannot wait. The override is worth having only if it leaves a +# trace, so it writes to `$GIT_DIR/batten-receipts/prose-only-overrides.` +# and prints the same, so a reviewer sees a decision rather than a silence. +# +# MUTATION COVERAGE (CLOUD-418). The first row is the whole predicate: a gate that +# never refuses is the state this repository was in before it existed, and every +# other case in the suite still passes under it. The second and third are the two +# conjuncts, each of which alone would make the gate wrong in a different +# direction. +#MUTANT prose-only-never-refuses|s/^prose_only=1$/prose_only=0/|a comment-only diff with no test change is refused +#MUTANT prose-only-ignores-tests|s/^\ttests\/\*) touched_tests=1 ;;$/\ttests\/*) : ;;/|a comment change plus a test change is admitted +#MUTANT prose-only-unknown-is-comment|s/^\t\*) return 1 ;;$/\t*) return 0 ;;/|an unrecognised extension admits the branch +#MUTANT prose-only-override-unrecorded|s/^\tprintf '%s.n' "\$note" >>"\$record"$/\t:/|the override admits the branch and records which one +set -uo pipefail + +BASE="${PROSE_ONLY_BASE:-origin/main}" + +# EXIT 0 IS "DO NOT REFUSE", and every could-not-look path takes it. A gate that +# blocked landing because it failed to compute a diff would be the reason work +# cannot proceed, which is a worse defect than the matrix it is trying to save. +if ! git rev-parse --verify -q "$BASE" >/dev/null 2>&1; then + echo "prose-only-check: no $BASE to diff against — not judged" + exit 0 +fi + +# `--diff-filter=d` drops deletions: a removed file has no surviving lines to +# classify, and treating it as prose would let a branch that deletes a module read +# as a comment change. +files=$(git diff --name-only --diff-filter=d "$BASE...HEAD" 2>/dev/null) || { + echo "prose-only-check: the diff could not be computed — not judged" + exit 0 +} + +if [[ -z "${files//[[:space:]]/}" ]]; then + echo "prose-only-check: no diff against $BASE — nothing to price" + exit 0 +fi + +# Per-extension and deliberately narrow. Returning 1 for anything unrecognised is +# the admitting direction, per the header. +is_comment_line() { + local path="$1" line="$2" + # Strip the leading +/- and any indentation before classifying: a comment is a + # comment at any depth, and `git diff --unified=0` still emits the marker. + local text="${line:1}" + text="${text#"${text%%[![:space:]]*}"}" + # A blank line inside an otherwise-prose hunk is whitespace, not code. Counting + # it as code would make every reflowed comment block read as a code change. + [[ -n "$text" ]] || return 0 + case "$path" in + *.md) return 0 ;; + *.rs) + # `//`, `///` and `//!` all begin with `//`; block comments are deliberately + # NOT recognised, because a `/* */` run cannot be classified line-by-line + # without tracking state, and guessing here fails in the refusing direction. + [[ "$text" == //* ]] + ;; + *.sh | *.bash | *.bats | mise-tasks/*) + # `mise-tasks/` programs carry no extension (CLOUD-865 renamed most to `.sh`, + # but the pattern stays so a re-added extensionless task is still read). + [[ "$text" == \#* ]] + ;; + *) return 1 ;; + esac +} + +touched_tests=0 +for f in $files; do + case "$f" in + tests/*) touched_tests=1 ;; + esac +done + +# `--unified=0` so only changed lines are emitted: context lines are unchanged by +# definition and classifying them would make a comment edit next to code read as a +# code change. +prose_only=1 +noncomment_count=0 +current="" +while IFS= read -r line; do + case "$line" in + '+++ '*) continue ;; + '--- '*) continue ;; + 'diff --git '*) + # `b/` is the post-image name, which is the one `--diff-filter=d` + # guarantees exists. + current="${line##*" b/"}" + continue + ;; + '@@'*) continue ;; + '+'* | '-'*) + [[ -n "$current" ]] || continue + if ! is_comment_line "$current" "$line"; then + prose_only=0 + noncomment_count=$((noncomment_count + 1)) + fi + ;; + esac +done < <(git diff --unified=0 "$BASE...HEAD" 2>/dev/null) + +file_count=$(printf '%s\n' "$files" | grep -c . || true) + +if [[ "$touched_tests" -eq 1 ]]; then + echo "prose-only-check: $file_count file(s) changed, including under tests/ — not prose-only" + exit 0 +fi + +if [[ "$prose_only" -eq 0 ]]; then + echo "prose-only-check: $file_count file(s) changed, $noncomment_count non-comment line(s) — not prose-only" + exit 0 +fi + +# From here the branch IS prose-only. The override is read at the refusal rather +# than at the top, so the record names a decision that was actually needed. +if [[ -n "${BATTEN_PROSE_ONLY_OVERRIDE:-}" ]]; then + branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo detached) + gitdir=$(git rev-parse --git-dir 2>/dev/null || echo .git) + record="$gitdir/batten-receipts/prose-only-overrides.$branch" + mkdir -p "$(dirname "$record")" 2>/dev/null || true + note="prose-only-check: OVERRIDDEN on $branch — $file_count prose-only file(s)" + printf '%s\n' "$note" >>"$record" + echo "$note" + exit 0 +fi + +{ + echo "::error:: this branch's whole diff is comment lines and no test changed, so a full CI matrix would confirm nothing $file_count file(s):" + while IFS= read -r f; do + [[ -n "$f" ]] || continue + echo " $f" + done <<<"$files" + echo "Put the content on the row that owns it and let the next change to these files carry it, or set BATTEN_PROSE_ONLY_OVERRIDE=1 if the prose is the deliverable and cannot wait — that records which branch used it." +} >&2 +exit 2 diff --git a/mise.toml b/mise.toml index 30949b830..1603542f7 100644 --- a/mise.toml +++ b/mise.toml @@ -1445,6 +1445,30 @@ if ! mise run perf-gate; then echo "::error:: verify: perf-gate failed — this branch made an invocation path measurably slower than its merge base, or the measurement could not run. No receipt written." >&2 exit 1 fi +# The COST gate (CLOUD-827), and it is deliberately last of the content gates: +# every question above is "is this branch correct", and this one is "is the matrix +# it is about to buy able to have an opinion about it". Asking that before the +# correctness gates would refuse a branch before telling the author whether it +# even works. +# +# In `verify` AND in `land`'s pre-ready set, for the reason `perf-gate` is here: +# `ready-guard` refuses `gh pr ready` without a verify receipt for this exact +# HEAD, so a prose-only branch is stopped before it can spend a runner at all. +# That is strictly earlier and cheaper than a check that reds after the matrix has +# been paid for — and this gate exists precisely because nothing in the pre-ready +# set asked what the diff was WORTH. +# +# NOT a CI job, and that is `ci-local-parity` satisfied rather than dodged: the +# constraint is that every task CI runs is one `verify` runs, not the reverse. It +# also could not be one honestly — by the time CI runs, the matrix this gate +# exists to avoid has already been bought. +# +# Every could-not-look path inside it exits 0, so this stops a run only on a +# positive verdict. `BATTEN_PROSE_ONLY_OVERRIDE=1` is the recorded escape. +if ! mise run prose-only-check; then + echo "::error:: verify: this branch's whole diff is comment lines and no test changed, so the matrix it would buy can confirm nothing verify has not already proved. Put the content on the row that owns it, or set BATTEN_PROSE_ONLY_OVERRIDE=1 to record the exception. No receipt written." >&2 + exit 1 +fi # The receipt write is the binary's job (CLOUD-203): `batten receipt record` # keys the claim to the exact commit verified — an amend or a rebase produces a # new HEAD and therefore no receipt, which is the point (see ready-guard) — and diff --git a/tests/land.bats b/tests/land.bats index 1f0e4d4d9..10f8d6bb6 100644 --- a/tests/land.bats +++ b/tests/land.bats @@ -1227,6 +1227,24 @@ Refs: CLOUD-192" [ "$(comments)" -eq 0 ] } +@test "a prose-only branch stops before review is asked for" { + # CLOUD-827's stop, and the only one in this set that prices what the change is + # WORTH rather than whether it is correct. Measured: a branch whose whole diff + # was two rewritten sentences of `//!` doc comment reached the ready and a full + # required matrix, and what stopped it was a human rather than a gate. + # + # Asserted before the comment count for the reason every stop here is: stopping + # after asking for the merge would already have spent the thing the stop exists + # to withhold — and here that thing IS the spend. + task_fails prose-only-check + run "$LAND" + [ "$status" -eq 1 ] + [[ "$output" == *"prose-only branch"* ]] + [ "$(comments)" -eq 0 ] + # It must not have readied either: the ready is the event that buys the run. + [ "$(grep -c '^ready$' "$BATS_TEST_TMPDIR/calls")" -eq 0 ] +} + @test "a missing verify receipt stops the lap" { # `verified` reads the receipt keyed to this exact HEAD. Landing had no such # precondition before, so a branch readied by any other route could still be @@ -1951,8 +1969,14 @@ head_verdict() { echo "$1" >"$BATS_TEST_TMPDIR/rc.mise.checks-green"; } # operator — a reset that fails means the undo point is gone, a replay that # fails means another branch's commits will not come off, and the second is # the one that must never reach a push. Exercised below. - [ "$stops" -eq 31 ] || { - echo "land has $stops stopping conditions; this suite covers 31." + # 32 since CLOUD-827: a prose-only branch, whose whole diff is comment lines + # with no test change. It is the only stop here that is about what the change + # is WORTH rather than whether it is correct — every gate above it asks + # whether the branch works, and this one asks whether the matrix it is about + # to buy can have an opinion about it. Exercised below, with the admitting + # direction held by the gate's own suite rather than duplicated here. + [ "$stops" -eq 32 ] || { + echo "land has $stops stopping conditions; this suite covers 32." echo "Add a case for the new one — an unexercised exit is how the refusal path stayed dead." return 1 } diff --git a/tests/prose-only-check.bats b/tests/prose-only-check.bats new file mode 100644 index 000000000..db49b7d4e --- /dev/null +++ b/tests/prose-only-check.bats @@ -0,0 +1,194 @@ +#!/usr/bin/env bats +# subject: mise-tasks/prose-only-check.sh +# CLOUD-827. A branch whose entire diff was two rewritten sentences of `//!` doc +# comment was on its way to a full required matrix against a trunk landing every +# ~16 minutes. What stopped it was a human, which is the wrong mechanism: the +# agent had the rule (AGENTS.md) and every gate it consulted said yes. +# +# Each case builds its own repository, because the predicate reads +# `origin/main...HEAD` and a case that leaned on the checkout would assert about +# whichever branch happened to be current. + +setup() { + GATE="$BATS_TEST_DIRNAME/../mise-tasks/prose-only-check.sh" + REPO="$BATS_TEST_TMPDIR/repo" + mkdir -p "$REPO" + # `git init -b`, not `git branch -f`: forcing the checked-out branch fails, and + # CI hides it only because the runner still defaults to `master` (CLOUD-282). + git init -q -b claude/prose-fixture "$REPO" + cd "$REPO" || return 1 + git config commit.gpgsign false + git config user.email t@t + git config user.name t +} + +commit() { git add -A && git commit -q -m "$1"; } + +# The base every case diffs against. Pointing `origin/main` at the first commit +# makes everything after it "this branch's work". +base() { + commit "base" + git update-ref refs/remotes/origin/main HEAD +} + +@test "a comment-only diff with no test change is refused" { + # THE ACCEPTANCE CASE, and the one the first mutation targets: with the + # predicate stubbed to never refuse, this is the only case that reddens. + printf 'fn a() {}\n' >src.rs + base + printf '// a rewritten sentence\nfn a() {}\n' >src.rs + commit "docs: reword" + run "$GATE" + [ "$status" -eq 2 ] + [[ "$output" == *"would confirm nothing"* ]] + [[ "$output" == *"src.rs"* ]] +} + +@test "a comment change plus a test change is admitted — the PR #604 shape" { + # This prices batching, never doc work. A doc rewrite that also carries the + # gate enforcing it is exactly the change that SHOULD land. + printf 'fn a() {}\n' >src.rs + mkdir -p tests + printf 'old\n' >tests/t.bats + base + printf '// a rewritten sentence\nfn a() {}\n' >src.rs + printf 'new assertion\n' >tests/t.bats + commit "docs+test" + run "$GATE" + [ "$status" -eq 0 ] + [[ "$output" == *"including under tests/"* ]] +} + +@test "a comment change plus any code line is admitted" { + printf 'fn a() {}\n' >src.rs + base + printf '// note\nfn a() { let x = 1; }\n' >src.rs + commit "docs+code" + run "$GATE" + [ "$status" -eq 0 ] + [[ "$output" == *"non-comment line"* ]] +} + +@test "a .md-only diff is refused — the whole file is prose" { + printf 'one\n' >README.md + base + printf 'one\ntwo\n' >README.md + commit "docs: expand" + run "$GATE" + [ "$status" -eq 2 ] +} + +@test "an unrecognised extension admits the branch" { + # The admitting direction is deliberate. Wrong one way this spends someone + # else's minutes; wrong the other way it blocks correct work, and only the + # second cannot be recovered by waiting. + printf 'a: 1\n' >conf.yaml + base + printf 'a: 2\n' >conf.yaml + commit "chore: config" + run "$GATE" + [ "$status" -eq 0 ] +} + +@test "an empty diff is not judged" { + printf 'fn a() {}\n' >src.rs + base + run "$GATE" + [ "$status" -eq 0 ] + [[ "$output" == *"nothing to price"* ]] +} + +@test "no base to diff against is not judged, rather than refused" { + printf 'fn a() {}\n' >src.rs + commit "only" + run "$GATE" + [ "$status" -eq 0 ] + [[ "$output" == *"not judged"* ]] +} + +@test "a shell comment counts as prose, and code in the same file does not" { + printf 'echo hi\n' >t.sh + base + printf '# a comment\necho hi\n' >t.sh + commit "docs: comment" + run "$GATE" + [ "$status" -eq 2 ] + + printf '# a comment\necho bye\n' >t.sh + commit "code too" + run "$GATE" + [ "$status" -eq 0 ] +} + +@test "a Rust block comment is NOT read as prose" { + # `/* */` cannot be classified line-by-line without tracking state, and + # guessing would fail in the refusing direction — so it reads as code. + printf 'fn a() {}\n' >src.rs + base + printf '/* a block */\nfn a() {}\n' >src.rs + commit "docs: block" + run "$GATE" + [ "$status" -eq 0 ] +} + +@test "a reflowed comment block with blank lines is still prose" { + # A blank line inside an otherwise-prose hunk is whitespace, not code. + # Counting it as code would make every reflowed comment block read as a code + # change, which is the common case this gate is about. + printf '// one\nfn a() {}\n' >src.rs + base + printf '// one\n\n// two\nfn a() {}\n' >src.rs + commit "docs: reflow" + run "$GATE" + [ "$status" -eq 2 ] +} + +@test "a deleted file is not read as a comment change" { + # A removed file has no surviving lines to classify. Treating it as prose + # would let a branch that deletes a module read as a doc change. + printf 'fn a() {}\n' >src.rs + printf 'fn b() {}\n' >gone.rs + base + rm gone.rs + commit "refactor: drop a module" + run "$GATE" + [ "$status" -eq 0 ] +} + +@test "the override admits the branch and records which one it admitted" { + printf 'fn a() {}\n' >src.rs + base + printf '// reworded\nfn a() {}\n' >src.rs + commit "docs: reword" + BATTEN_PROSE_ONLY_OVERRIDE=1 run "$GATE" + [ "$status" -eq 0 ] + [[ "$output" == *"OVERRIDDEN"* ]] + [[ "$output" == *"claude/prose-fixture"* ]] + # The trace is the whole point: a visible decision rather than a silence. + run cat "$(git rev-parse --git-dir)/batten-receipts/prose-only-overrides.claude/prose-fixture" + [ "$status" -eq 0 ] + [[ "$output" == *"OVERRIDDEN"* ]] +} + +@test "the refusal names paths and a count, never a line of the diff" { + # Rule 4. A diff is content someone has not published yet. + printf 'fn a() {}\n' >src.rs + base + printf '// customer detail in the comment\nfn a() {}\n' >src.rs + commit "docs: reword" + run "$GATE" + [ "$status" -eq 2 ] + [[ "$output" != *"customer detail"* ]] + [[ "$output" == *"src.rs"* ]] +} + +@test "the remedy names where the content should go, not merely a flag" { + printf 'fn a() {}\n' >src.rs + base + printf '// reworded\nfn a() {}\n' >src.rs + commit "docs: reword" + run "$GATE" + [ "$status" -eq 2 ] + [[ "$output" == *"the row that owns it"* ]] + [[ "$output" == *"BATTEN_PROSE_ONLY_OVERRIDE"* ]] +} From 4ab5dc16e89000d57b85eeab844f1f66917f9aea Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 03:20:37 +0000 Subject: [PATCH 4/9] fix(land): the lap cap reported a diagnosis its own refunds had already removed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `charge_wait` DECREMENTS `lap` on every path that bought no CI — the lease held by someone else, the lease won over a `main` that had moved, and the bot giving no readable answer — and the comment beside it says why: "a busy fleet would otherwise exhaust LAND_MAX_LAPS on waiting alone and give up without ever having attempted". So a lap counts ONLY when it actually spent a matrix, and reaching the cap means exactly one thing: this branch spent `max_laps` matrices and none of them landed. It does **not** mean `main` outran a lap — the laps `main` outran were refunded before they could count. The message said the second thing: still not linear after $max_laps laps, each of which bought a CI matrix; `main` is moving faster than a lap takes. Look before lapping again. The first clause was right. The second is the inference the refunds removed, and it is the same diagnosis the bot-silence refund's own comment records CLOUD-413 measuring wrong twice over across 24 laps. CLOUD-413 stopped CHARGING the lap and left the sentence that reported the wrong diagnosis standing. ## The two exhaustion paths contradicted their costs CLOUD-399 made them distinguishable by exit code; their remedies were never reconciled. | exit | path | CI spent | said | | -- | -- | -- | -- | | `LAND_EXIT_FLEET_SATURATED` | never won the lease | **none** | wait, or land later | | `LAND_EXIT_RUNAWAY` | spent `max_laps` matrices | `max_laps` **matrices** | ambiguous | The path that cost nothing told the caller to stop; the path that cost two matrices did not say what to do. "Free implies stop, expensive implies go" is not a defensible pairing whichever posture is correct. ## What this lands, and why it is not the wording change #651 proposed The runaway refusal now states what its accounting supports — the laps, the matrices spent, and that none landed — and no longer asserts the refuted inference. The literal survives in the file's explanatory comments, which earn it by describing the bug. The remedy names a continuing action AND the spend that action re-commits, rather than an unconditional "RUN THIS AGAIN". That imperative was #651's proposal and it re-arms the only brake on this spend: it buys `max_laps` more matrices with nothing about the branch or `main` changed. What decides it is WHY the laps lost, so the refusal points at this run's own `::error::` lines and at `gh pr view --json isDraft` — a draft PR means CI went red and this task re-drafted it, which is the cheapest available discriminator between a defect and pure contention. The check the superseded text named (`git log --since=30.minutes origin/main`) measured `main`'s rate, which is the very inference this row refutes — a runnable object pointed at the wrong question. ## The mutations now discriminate The predecessor was `s/RUN THIS AGAIN/look/` against an assertion hardcoding `RUN THIS AGAIN`: it reddened its case by matching the same literal from both sides, which proves the string is present and nothing about the property. Replaced with two that break the PROPERTY — restore the refuted diagnosis into the emission site, and give the expensive path the free path's remedy. Both turn a named case red; `mutant` reports 11 declared across `land`, every one caught. ## The case pair, which is what makes the second clause checkable CLOUD-904 §7 asks for cases that read BOTH messages, and neither existing case did — so no single-message assertion could see the mismatch. `land.bats` gains that pair: one case on what the runaway refusal states, one reading the fleet-saturated and runaway remedies against each other and against their costs. Four other assertions were pinned to the superseded text and had to move with it, which is itself the finding: `:1111` asserted the refuted clause absent, `:1195` asserted it PRESENT inside CLOUD-399's exit-code case, and `:1165`/`:1492` asserted "after N laps". Only one of the four was about the message; the rest treated it as an incidental handle. Each is now matched on what its own case is actually about. Closes CLOUD-904 Refs: CLOUD-904, CLOUD-413, CLOUD-399, CLOUD-871, CLOUD-418 --- mise-tasks/land.sh | 40 +++++++++++---- tests/land.bats | 118 ++++++++++++++++++++++++++++++++------------- 2 files changed, 114 insertions(+), 44 deletions(-) diff --git a/mise-tasks/land.sh b/mise-tasks/land.sh index 772525fa0..7f72d70d8 100755 --- a/mise-tasks/land.sh +++ b/mise-tasks/land.sh @@ -90,7 +90,14 @@ # MUTATION COVERAGE (CLOUD-418). `||`: applying # the script to a throwaway copy of this file must turn the named case RED. # A gate listed in $MUTANT_GATES with no row here fails `mise run mutant`. -#MUTANT lap-cap-may-read-as-stop|s/RUN THIS AGAIN/look/|names the continuing action +# CLOUD-904 replaces an identity mutation with two that discriminate. The +# predecessor was `s/RUN THIS AGAIN/look/` against an assertion hardcoding `RUN +# THIS AGAIN`: it reddened its case by matching the same literal from both sides, +# which proves the string is present and nothing about the property. These two +# break the PROPERTY instead — the first restores the refuted diagnosis into the +# emission site, the second gives the expensive path the free path's remedy. +#MUTANT lap-cap-asserts-refuted-diagnosis|s/Laps that bought nothing were already refunded, so this is not/\\`main\\` is moving faster than a lap takes, and this is/|the lap cap's refusal states what its own accounting supports +#MUTANT lap-cap-remedies-swapped|s/If every lap lost only to contention, run this again, which commits another \$max_laps matrices./The fleet is saturated: wait, or land later./|the two exhaustions give imperatives consistent with their costs #MUTANT exit-codes-collapse|s/^readonly LAND_EXIT_RUNAWAY=5$/readonly LAND_EXIT_RUNAWAY=4/|CLOUD-399: the two exhaustions are told apart by CODE #MUTANT declined-always|s/^\t\[\[ \"\$rc\" = 3 \]\]$/\ttrue/|red CI stops the lap without asking for the merge # CLOUD-369. The admission predicates, each proven to discriminate rather than @@ -1173,17 +1180,30 @@ admitted=0 admitted_sha= while :; do lap=$((lap + 1)) - # THE REMEDY NAMES THE CONTINUING ACTION, and that is not stylistic. This - # message used to end "Look before lapping again", which reads as STOP — and - # an agent stopped, for 55 minutes, on a one-commit branch. Stopping is the - # single worst move available here: this header's own opening paragraph says - # lapping is the catch-up mechanism, so a branch that stops ages while the - # target keeps moving, which is the "cannot land at all" state the design - # exists to prevent. A cap is a checkpoint, never a stop sign; the imperative - # has to say so, and it has to name the check rather than ask for judgement. + # THE REMEDY IS DERIVED FROM THE ACCOUNTING ABOVE, NOT RESTATED BESIDE IT + # (CLOUD-904). `charge_wait` REFUNDS the lap on every path that bought no CI — + # the lease held by someone else, the lease won over a `main` that had moved, and + # the bot giving no readable answer. So a lap counts ONLY when it actually spent + # a matrix, and reaching the cap means exactly one thing: this branch spent + # `max_laps` matrices and none of them landed. + # + # It does NOT mean `main` outran a lap. That inference is what the refunds + # removed, and it is the same diagnosis the comment at the bot-silence refund + # above records CLOUD-413 measuring wrong twice over across 24 laps. The message + # asserted it anyway for as long as the refunds have existed, so the sentence + # reported a state its own file had already made impossible. + # + # THE TWO EXHAUSTION PATHS MUST NOT CONTRADICT THEIR COSTS. The fleet-saturated + # exit spent NOTHING and may say wait; this one spent `max_laps` matrices, so it + # names a continuing action AND the spend the caller is re-committing. An + # unconditional "run this again" is not that: it re-arms the only brake on this + # spend and buys `max_laps` more matrices with nothing about the branch or `main` + # changed. What decides it is WHY the laps lost — a rebase conflict, a failed + # `verify` or red CI are defects that will lose again, while pure contention is + # the case that converges. [[ "$lap" -le "$max_laps" ]] || die_with "$LAND_EXIT_RUNAWAY" \ - "still not linear after $max_laps laps, each of which bought a CI matrix; \`main\` is moving faster than a lap takes. Check the current rate with \`git log --oneline --since=30.minutes origin/main | wc -l\`, then RUN THIS AGAIN — lapping is how a branch catches up, and stopping is how it stops being landable." + "stopped after $max_laps laps, having spent $max_laps CI matrices and none of them landed. Laps that bought nothing were already refunded, so this is not \`main\` outrunning a lap. Read this run's \`::error::\` lines for how each lap ended, and \`gh pr view $pr --json isDraft,statusCheckRollup\` — a draft PR means CI went red and this task re-drafted it. A rebase conflict, a failed \`verify\` or red CI will lose again; fix it first. If every lap lost only to contention, run this again, which commits another $max_laps matrices." # A lap holds the lease only across its own CI window. Dropping it here — at # the top, covering every `continue` below uniformly — means a lap that lost diff --git a/tests/land.bats b/tests/land.bats index 10f8d6bb6..25d91e05d 100644 --- a/tests/land.bats +++ b/tests/land.bats @@ -1072,54 +1072,101 @@ runs_query_403() { : >"$BATS_TEST_TMPDIR/rc.runs"; } [[ "$(call_order)" == *push* ]] } -@test "the lap cap's remedy names the continuing action, not a judgement" { - # CLOUD-871's one worked instance, and the reason it is a case rather than a - # comment. This message used to end "Look before lapping again", which reads - # as STOP; an agent stopped for 55 minutes on a one-commit branch. Stopping is - # the worst move available: this task's own header says lapping IS the - # catch-up mechanism, so a stopped branch ages while the target moves. +@test "the lap cap's refusal states what its own accounting supports" { + # CLOUD-904, and the first of a PAIR — the second case below reads this one's + # remedy against the fleet-saturated one, which is the assertion neither of them + # used to make. # - # A cap is a checkpoint, not a stop sign. Two things have to hold, and the - # second is the one that rots: the remedy must name a runnable check, and the - # imperative must point at continuing. A remedy that only names the check is - # still ambiguous in the unsafe direction. + # `charge_wait` REFUNDS the lap on every path that bought no CI, so reaching the + # cap means exactly one thing: this branch spent `max_laps` matrices and none of + # them landed. It does NOT mean `main` outran a lap — that inference is the one + # the refunds removed, and the same diagnosis CLOUD-413 measured wrong twice over + # across 24 laps. The message asserted it anyway for as long as the refunds have + # existed. # - # DELIBERATELY SINGULAR — do not copy this for the other refusals. There are - # 420 terminal refusals under `mise-tasks/`, and a case apiece would be 420 - # bespoke assertions written in the language the retirement campaign exists to - # delete. A text predicate over them was measured and is unshippable: against a - # generous detector only 103 of the 420 name a runnable object, so it fires on - # 75%, and most of that is good messages — could-not-look diagnostics have no - # remedy by construction, and `Fix the regression` is honest with no command - # behind it. Rego cannot do better; regorus is built here without `regex` - # builtins (CLOUD-885). + # CLOUD-871's worked instance lives here too: the remedy must name a runnable + # object rather than ask for judgement. An agent read "Look before lapping again" + # as STOP and stopped for 55 minutes on a one-commit branch, which is the worst + # move available — this task's own header says lapping IS the catch-up mechanism, + # so a stopped branch ages while the target moves. # - # The general property is acquired STRUCTURALLY instead, and already is: a - # rule kind requires `no_fix_reason` (rules.rs) and ingest refuses a finding - # with no remedy — "a finding a caller cannot act on is not storable" - # (findings.rs). A gate gets that the moment it becomes a policy row, which is - # CLOUD-843's migration and its §5 acceptance criterion. This case exists - # because THIS message caused measured harm — an agent read it as stop and - # stopped for 55 minutes on a one-commit branch — not because the class needs - # one each. + # DELIBERATELY SINGULAR — do not copy this for the other refusals. There are 420 + # terminal refusals under `mise-tasks/`, and a case apiece would be 420 bespoke + # assertions in the language the retirement campaign exists to delete. A text + # predicate over the class was measured and is unshippable: against a generous + # detector only 103 of 420 name a runnable object, so it fires on 75%, and most + # of that is good messages. Rego cannot do better — regorus is built here with no + # `regex` builtins (CLOUD-885). The general property is acquired STRUCTURALLY + # instead: a rule kind requires `no_fix_reason` and ingest refuses a finding with + # no remedy, which a gate gets the moment it becomes a policy row (CLOUD-843). echo 2 >"$BATS_TEST_TMPDIR/rc.mise.verify" LAND_MAX_LAPS=2 run "$LAND" [ "$status" -eq 5 ] - # A runnable check, not "look". - [[ "$output" == *"git log"* ]] - # And the imperative that continues the loop. - [[ "$output" == *"RUN THIS AGAIN"* ]] - # The wording that caused the stop must not come back. + # What the accounting supports: the spend, and that it did not land. + [[ "$output" == *"spent 2 CI matrices"* ]] + [[ "$output" == *"none of them landed"* ]] + # The refuted inference must not be asserted at the emission site. The literal + # is allowed in the file's explanatory comments, which earn it by describing the + # bug — this reads the REFUSAL, not the file. + [[ "$output" != *"is moving faster than a lap takes"* ]] + # A runnable object, not "look". + [[ "$output" == *"gh pr view"* ]] + # And the wording that caused the 55-minute stop must not come back. [[ "$output" != *"Look before lapping again"* ]] } +@test "the two exhaustions give imperatives consistent with their costs" { + # CLOUD-904's discriminating assertion, and the reason it is a PAIR: each case + # reads BOTH messages. CLOUD-399 made the two exits distinguishable by code and + # their remedies were never reconciled — the path that cost NOTHING told the + # caller to stop, and the path that cost `max_laps` matrices was ambiguous. + # "Free implies stop, expensive implies go" is not a defensible pairing, and no + # single-message assertion can see it. + # + # The unspent path may say wait. The spent path must name a continuing action AND + # the spend the caller is re-committing — an unconditional "run this again" + # re-arms the only brake on that spend. + echo 1 >"$BATS_TEST_TMPDIR/rc.mise.land-lock" + pr_state MERGED + LAND_LOCK_MAX_WAITS=1 run "$LAND" + [ "$status" -eq 4 ] + saturated="$output" + + setup + + echo 2 >"$BATS_TEST_TMPDIR/rc.mise.verify" + LAND_MAX_LAPS=2 run "$LAND" + [ "$status" -eq 5 ] + runaway="$output" + + # The unspent path names its zero cost and names waiting. + [[ "$saturated" == *"spent no CI matrix"* ]] + [[ "$saturated" == *"wait, or land later"* ]] + + # The spent path names the cost it already paid... + [[ "$runaway" == *"spent 2 CI matrices"* ]] + # ...names a continuing action... + [[ "$runaway" == *"run this again"* ]] + # ...and names the spend that action re-commits, which is what stops the + # continuing imperative from being unconditional. + [[ "$runaway" == *"commits another 2 matrices"* ]] + + # The remedies must not be interchangeable: the expensive path must not be + # telling the caller to wait, which is the free path's answer. + [[ "$runaway" != *"wait, or land later"* ]] +} + @test "a verify that keeps losing the race exhausts laps rather than spinning" { # The lap is bounded by the backstop that already exists. A `main` that # never stops moving must reach LAND_MAX_LAPS and say so, not loop forever. echo 2 >"$BATS_TEST_TMPDIR/rc.mise.verify" LAND_MAX_LAPS=3 run "$LAND" [ "$status" -eq 5 ] - [[ "$output" == *"still not linear after 3 laps"* ]] + # CLOUD-904 rewrote this refusal: "still not linear" asserted that `main` + # outran a lap, which the refunds in `charge_wait` already make impossible. + # What this case is about is the BACKSTOP firing after N laps, so it matches + # on the count rather than on the diagnosis that used to accompany it. + [[ "$output" == *"after 3 laps"* ]] [ "$(verify_calls)" -eq 3 ] [ "$(comments)" -eq 0 ] } @@ -1149,7 +1196,10 @@ runs_query_403() { : >"$BATS_TEST_TMPDIR/rc.runs"; } LAND_MAX_LAPS=1 run "$LAND" runaway="$status" [ "$runaway" -eq 5 ] - [[ "$output" == *"moving faster than a lap takes"* ]] + # CLOUD-904 removed the refuted diagnosis this used to match on. The subject of + # THIS case is the exit CODES, so it needs any string that identifies the runaway + # refusal; the content of that refusal is the pair of cases above. + [[ "$output" == *"none of them landed"* ]] # The property itself, stated once: distinguishable, and neither is the # generic stop that every other `die` in this task uses. From 4ada0f7542672888173267c29541afb7c5542bce Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 03:28:19 +0000 Subject: [PATCH 5/9] feat(gate): landed-check gains an asserted arm for rows no derivation can reach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both existing halves of the disjunction turn on a CLAIM, which is right. The consequence nobody priced: a key whose work landed through a `Refs:`-only pull request satisfies NEITHER, and never will. No later event can put a closing key on an already-merged PR, and no later commit will claim an id whose work is already on `main`. The row is undrainable by gate, permanently, and the gate reports it as not landed — true of the evidence and false of the tree. ## The measurement came first, and it decided the remedy The row is explicit that shipping a remedy before measuring is what would make it a guess. Measured 2026-08-23 against `main` at 1180 commits and every merged pull request: | column | rows | undrainable by both derived arms | ...and mentioned on `main` | | -- | -- | -- | -- | | In Progress | 37 | 37 | **5** | | In Review | 51 | 7 | 7 | **5, not 30.** That is what makes a per-row ASSERTED arm defensible where a derived one would not be — at 30 this would have argued for a board-model change instead. All 37 In Progress rows being undrainable is expected rather than alarming: 32 are not mentioned on `main` at all and are correctly live work. A method note worth keeping, because it nearly produced a wrong number: the first run passed `main`'s log to `claimed-keys` through `--log` in argv and got ZERO claimed keys, which would have made arm 1 look structurally dead. That is the `Argument list too long` hazard both `landed-check` and `merged-pr-keys` already document — the log is over 1 MB. Through stdin the same call returns 44. A measurement tool failing silently in the zero direction is the same shape as the defect being measured. CLOUD-270 reproduces exactly, and its column has since been wrong in THREE directions: Done (by hand) -> In Progress (three seconds after PR #639 merely CITED it) -> Backlog. Landed work now sits in "not yet Ready". Each move is someone guessing because no gate can answer, which is the argument for a recorded route over a hand-move. ## The arm `--landed-by `, `` lines the caller ASSERTS carry the work. It is an assertion, not a derivation, and that difference is why it is a separate flag rather than a widening of the other two. Three properties keep it honest, each with a case: * **Per-id and explicit.** A mention still never counts, anywhere. Nothing here reads `main`'s log for a bare key — CLOUD-804's distinction survives intact, and a case asserts that a citing commit plus an unrelated assertion still exits 0. * **Reported separately.** A row drained this way prints `asserted by --landed-by: `, so a reader can tell the caller's word from the gate's evidence instead of trusting the union. Its anti-vacuity twin asserts a DERIVED landing is not labelled asserted — if every finding said "asserted" the label would carry no information. * **Optional, absent-is-empty.** Unlike `--merged-prs`, whose absence is exit 2 because it would silently halve a disjunction that almost always answers. Absent here only ever ADDS to the landed set, so it cannot manufacture a false green. An unreadable file is still exit 2. The forgery risk is real and is the price: the caller assembles the file, so a wrong line lands a row that never landed. It is stated in the header rather than left implicit, because it is what buys reach over rows no derivation can see. ## Verification 31 cases green. Two new `#MUTANT` rows — dropping the arm returns the population to permanently undrainable, deleting the `asserted` label collapses the caller's word into evidence — and `mutant` reports 4 declared across `landed-check`, every one caught. ## What this does NOT do The five rows are not moved. This lands the mechanism; using it is a board sweep with a human deciding each assertion, which is the dispatcher's Done-pass work rather than something to bulk-apply from a bundle. CLOUD-270 in particular now sits in Backlog and needs a decision about which column is true, not an assertion that it landed. Closes CLOUD-903 Refs: CLOUD-903, CLOUD-804, CLOUD-270, CLOUD-469, CLOUD-378, CLOUD-418 --- mise-tasks/landed-check.sh | 92 +++++++++++++++++++++++++++++++++-- tests/landed-check.bats | 99 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 3 deletions(-) diff --git a/mise-tasks/landed-check.sh b/mise-tasks/landed-check.sh index eded333db..640f5358a 100755 --- a/mise-tasks/landed-check.sh +++ b/mise-tasks/landed-check.sh @@ -75,6 +75,51 @@ # # --merged-prs `` lines, one per closing key # found in a MERGED pull request's body. +# --landed-by `` lines the caller ASSERTS carry +# the work, for rows both halves above are structurally +# unable to reach. See "THE THIRD ARM" below. +# +# THE THIRD ARM, AND WHY IT HAD TO EXIST (CLOUD-903). Both halves above turn on a +# CLAIM, which is right. The consequence nobody priced: a key whose work landed +# through a `Refs:`-only pull request satisfies NEITHER, and never will. No later +# event can put a closing key on an already-merged PR, and no later commit will +# claim an id whose work is already on `main`. The row is undrainable by gate, +# permanently, and the gate reports it as not landed — true of the evidence and +# false of the tree. +# +# MEASURED 2026-08-23, over the whole board rather than reasoned about. Against +# `main` at 1180 commits and every merged pull request: +# +# In Progress 37 rows, ALL 37 undrainable by the two arms above +# ...mentioned on main 5 (work plausibly landed, no claim anywhere) +# ...never mentioned 32 (genuinely live work — correctly In Progress) +# In Review 51 rows, 7 undrainable and mentioned +# +# So the population is SMALL — 5, not 30 — which is what makes a per-row asserted +# arm defensible where a derived one would not be. CLOUD-270 reproduces the row's +# own instance exactly: still undrainable, still mentioned on `main`, its work on +# `main` since 2026-08-09 through PRs #198 and #201, neither of which closes it. +# +# ITS COLUMN HAS NOW BEEN WRONG IN THREE DIRECTIONS, which is the argument for a +# recorded route rather than a hand-move: Done (2026-08-11, by hand) -> In +# Progress (2026-08-21, three seconds after PR #639 merely CITED it) -> Backlog +# (2026-08-22). Landed work now sits in "not yet Ready". +# +# THIS ARM IS AN ASSERTION, NOT A DERIVATION, and the difference is the whole +# reason it is a separate flag. The caller assembles the file, so a wrong line +# lands a row that never landed — the forgery risk is real and is the price of +# reaching rows no derivation can. Three properties keep it honest: +# +# * It is PER-ID and EXPLICIT. A mention still never counts, anywhere, which is +# CLOUD-804's distinction surviving intact. Nothing here reads `main`'s log +# for a bare key; the caller must name the row and the evidence. +# * It is REPORTED SEPARATELY. A row drained by this arm prints `asserted` and +# names the ref, so a reader can tell an asserted landing from a derived one +# rather than having to trust the union. +# * It is OPTIONAL and absent-is-empty, unlike `--merged-prs`. Absent evidence +# there is exit 2 because it would silently halve a disjunction that is +# almost always answered by it; absent here is simply "no assertions", which +# is the ordinary case and cannot manufacture a false green. # # ABSENT EVIDENCE IS EXIT 2, NEVER A SHORT SWEEP. Without the file, half the # disjunction cannot be evaluated and this would report a near-clean column it @@ -89,9 +134,16 @@ # under-report the 3% measurement predicts. #MUTANT landed-check-mention-is-a-landing|s@^if ! claimed_ids=.*@claimed_ids=$(jq -r '.[].id' <<<"$issues"); if false; then@|CLOUD-804: a commit citing an id as PRIOR ART does not land it #MUTANT landed-check-ignores-merged-prs|s@^if ! merged_ids=.*@merged_ids=""; if false; then@|CLOUD-804: commits on main with the key only in a MERGED PR body still land it +# The third arm's two properties, each of which alone would make it dishonest. +# The first drops the arm entirely, which puts CLOUD-903's population back to +# permanently undrainable. The second collapses its report into the derived +# halves, so a reader can no longer tell the caller's word from evidence. +#MUTANT landed-check-ignores-asserted|s@^landed=\$(printf@asserted_ids=""; landed=$(printf@|an asserted landing drains a row no derived half can reach +#MUTANT landed-check-hides-assertion|s@(asserted by --landed-by: [$][{]ref:-no ref given[}])@@|an asserted landing is REPORTED as asserted, never as derived set -euo pipefail merged_prs="" +landed_by="" have_evidence=0 while [[ "$#" -gt 0 ]]; do case "$1" in @@ -104,6 +156,14 @@ while [[ "$#" -gt 0 ]]; do have_evidence=1 shift 2 ;; + --landed-by) + [[ "$#" -ge 2 ]] || { + echo "::error:: landed-check: --landed-by needs a value" >&2 + exit 2 + } + landed_by="$2" + shift 2 + ;; *) echo "::error:: landed-check: unknown argument" >&2 exit 2 @@ -172,17 +232,43 @@ if ! merged_ids=$(awk -F'\t' 'NF >= 1 && $1 ~ /^CLOUD-[0-9]+$/ { print $1 }' "$m exit 2 fi -# The union is the landed set. Both halves are key sets, so membership is a +# HALF THREE, the asserted arm. Unreadable is exit 2 for the same reason the +# others are — a caller who named a file this cannot open has not supplied an +# empty assertion set, they have supplied one nobody read. Absent is empty, which +# is the ordinary case. +asserted_ids="" +if [[ -n "$landed_by" ]]; then + if [[ ! -r "$landed_by" ]]; then + echo "::error:: --landed-by names a file that cannot be read: ${landed_by}. That is a caller problem, not a clean board." >&2 + exit 2 + fi + if ! asserted_ids=$(awk -F'\t' 'NF >= 1 && $1 ~ /^CLOUD-[0-9]+$/ { print $1 }' "$landed_by" | sort -u); then + echo "::error:: --landed-by could not be parsed: ${landed_by}." >&2 + exit 2 + fi +fi + +# The union is the landed set. All three halves are key sets, so membership is a # fixed-string whole-line match — never a substring, which is how CLOUD-17 would # match CLOUD-179. -landed=$(printf '%s\n%s\n' "$claimed_ids" "$merged_ids" | grep -vE '^[[:space:]]*$' | sort -u || true) +landed=$(printf '%s\n%s\n%s\n' "$claimed_ids" "$merged_ids" "$asserted_ids" | grep -vE '^[[:space:]]*$' | sort -u || true) fail=0 while IFS= read -r id; do [[ -n "$id" ]] || continue if grep -qxF "$id" <<<"$landed"; then [[ "$fail" = 0 ]] && echo "::error:: issues are In Progress while their commits are on main — landed is In Review (AGENTS.md):" >&2 - echo " $id In Progress -> In Review" >&2 + # WHICH ARM DRAINED IT IS PART OF THE FINDING. A derived landing is + # evidence; an asserted one is the caller's word, and a reader who cannot + # tell them apart has to trust the union. The ref travels with it so the + # assertion can be checked rather than taken. + if [[ -n "$asserted_ids" ]] && grep -qxF "$id" <<<"$asserted_ids" && + ! grep -qxF "$id" <<<"$claimed_ids" && ! grep -qxF "$id" <<<"$merged_ids"; then + ref=$(awk -F'\t' -v want="$id" 'NF >= 2 && $1 == want { print $2; exit }' "$landed_by") + echo " $id In Progress -> In Review (asserted by --landed-by: ${ref:-no ref given})" >&2 + else + echo " $id In Progress -> In Review" >&2 + fi fail=1 fi done < <(jq -r '.[] | select(.status == "In Progress") | .id' <<<"$issues" | sort -u) diff --git a/tests/landed-check.bats b/tests/landed-check.bats index 61101a287..2f64f8022 100644 --- a/tests/landed-check.bats +++ b/tests/landed-check.bats @@ -268,3 +268,102 @@ Closes CLOUD-42" [ "$status" -eq 1 ] [[ "$output" != *"customer detail"* ]] } + +# ─── CLOUD-903: the third arm, for rows no derivation can reach ─────────────── +# +# Both derived halves turn on a CLAIM, which is right — and leaves a key whose +# work landed through a `Refs:`-only pull request satisfying NEITHER, forever. No +# later event can put a closing key on an already-merged PR, and no later commit +# will claim an id whose work is already on `main`. +# +# Measured 2026-08-23 over the whole board: of 37 In Progress rows, all 37 were +# undrainable by the two derived halves and 5 were mentioned on `main` with no +# claim anywhere. CLOUD-270 reproduces it exactly — its work has been on `main` +# since 2026-08-09 through PRs #198 and #201, neither of which closes it, and its +# column has since been wrong in three directions (Done, In Progress, Backlog). + +asserted() { + printf '%s\t%s\n' "$1" "$2" >>"$LB" +} + +@test "an asserted landing drains a row no derived half can reach" { + # THE ACCEPTANCE CASE. Nothing claims CLOUD-903 and no merged PR closes it, so + # both derived halves are false and always will be. + LB="$BATS_TEST_TMPDIR/landedby-$BATS_TEST_NUMBER.tsv" + : >"$LB" + asserted CLOUD-903 "#198" + run bash -c "printf '%s' '[{\"id\":\"CLOUD-903\",\"status\":\"In Progress\"}]' | $GATE --merged-prs '$EV' --landed-by '$LB'" + [ "$status" -eq 1 ] + [[ "$output" == *"CLOUD-903"* ]] +} + +@test "an asserted landing is REPORTED as asserted, never as derived" { + # The caller's word and the gate's evidence must not read the same. A reader + # who cannot tell them apart has to trust the union, and the ref travels so + # the assertion can be checked rather than taken. + LB="$BATS_TEST_TMPDIR/landedby-$BATS_TEST_NUMBER.tsv" + : >"$LB" + asserted CLOUD-903 "#198" + run bash -c "printf '%s' '[{\"id\":\"CLOUD-903\",\"status\":\"In Progress\"}]' | $GATE --merged-prs '$EV' --landed-by '$LB'" + [ "$status" -eq 1 ] + [[ "$output" == *"asserted by --landed-by"* ]] + [[ "$output" == *"#198"* ]] +} + +@test "a DERIVED landing is not labelled asserted, even with the flag supplied" { + # The anti-vacuity twin of the case above: if every finding said "asserted", + # the label would carry no information. + LB="$BATS_TEST_TMPDIR/landedby-$BATS_TEST_NUMBER.tsv" + : >"$LB" + asserted CLOUD-999 "#1" + merged_pr CLOUD-903 "#500" + run bash -c "printf '%s' '[{\"id\":\"CLOUD-903\",\"status\":\"In Progress\"}]' | $GATE --merged-prs '$EV' --landed-by '$LB'" + [ "$status" -eq 1 ] + [[ "$output" == *"CLOUD-903"* ]] + [[ "$output" != *"asserted by --landed-by"* ]] +} + +@test "a MENTION still never counts — CLOUD-804's distinction survives the new arm" { + # The property the whole third arm had to preserve. A commit that CITES the id + # as prior art must not drain it, and supplying an assertion file for some + # OTHER row must not change that. + git commit -q --allow-empty -m "docs: cite CLOUD-903 as prior art" + git branch -f main HEAD + git update-ref refs/remotes/origin/main main + LB="$BATS_TEST_TMPDIR/landedby-$BATS_TEST_NUMBER.tsv" + : >"$LB" + asserted CLOUD-999 "#1" + run bash -c "printf '%s' '[{\"id\":\"CLOUD-903\",\"status\":\"In Progress\"}]' | $GATE --merged-prs '$EV' --landed-by '$LB'" + [ "$status" -eq 0 ] + [[ "$output" == *"no In Progress issue has commits on main"* ]] +} + +@test "the flag is optional — absent is no assertions, not could-not-look" { + # Unlike --merged-prs, whose absence is exit 2 because it would silently halve + # a disjunction that almost always answers. Absent here cannot manufacture a + # false green: it only ever ADDS rows to the landed set. + run bash -c "printf '%s' '[{\"id\":\"CLOUD-903\",\"status\":\"In Progress\"}]' | $GATE --merged-prs '$EV'" + [ "$status" -eq 0 ] +} + +@test "a --landed-by file that cannot be read is exit 2, not an empty assertion set" { + run bash -c "printf '%s' '[{\"id\":\"CLOUD-903\",\"status\":\"In Progress\"}]' | $GATE --merged-prs '$EV' --landed-by '$BATS_TEST_TMPDIR/nope.tsv'" + [ "$status" -eq 2 ] + [[ "$output" == *"cannot be read"* ]] +} + +@test "--landed-by needs a value" { + run bash -c "printf '%s' '[{\"id\":\"CLOUD-903\",\"status\":\"In Progress\"}]' | $GATE --merged-prs '$EV' --landed-by" + [ "$status" -eq 2 ] + [[ "$output" == *"needs a value"* ]] +} + +@test "an asserted row that is not In Progress is untouched" { + # The arm widens what counts as landed; it does not widen which column is + # judged. An In Review row is already where a landing puts it. + LB="$BATS_TEST_TMPDIR/landedby-$BATS_TEST_NUMBER.tsv" + : >"$LB" + asserted CLOUD-903 "#198" + run bash -c "printf '%s' '[{\"id\":\"CLOUD-903\",\"status\":\"In Review\"}]' | $GATE --merged-prs '$EV' --landed-by '$LB'" + [ "$status" -eq 0 ] +} From 0e927ea0834dd04fa44704197b110ba2ba462a33 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 03:33:31 +0000 Subject: [PATCH 6/9] fix(land): a verify failure on a speculative tree says whose tree it was MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `land` speculatively linearizes while waiting for the lease and says so — "speculatively linearized onto @ — the main that is about to exist" — and then, on a `verify` failure, emitted an unconditional message whose two sentences are both wrong in that case. "Reproduce and fix locally" points at a defect the author did not write, and "CI is not where you discover this" implies discovery is overdue when the tree under test is not the one the author will ever push. Measured 2026-08-19: a two-commit branch touching only `.serena/memories/*` failed on `Cargo.lock sbom-ntia-conformance` and `mise-tasks/claim-race-check claim-not-raced` — neither file touched by either commit. Rebasing off the speculative base was green first try. The cost is not the wasted `verify`; that is the speculation's accepted price. It is the reasoning needed afterwards to work out whose failure it was, and the live risk that an author takes the message at its word and starts repairing a sibling's branch through their own. On 2026-08-22 the masked failure was in `land`'s OWN suite, which is the most expensive possible wrong place to send someone. ## It names a suspicion, never a verdict That distinction is load-bearing rather than cautious, and CLOUD-727 is the row that earned it: it asserted a borrowed-base attribution and RETRACTED it the same day, twice, both times from a sample that did not separate the variable. An identical-looking `doctor` refusal later reproduced with no speculation at all — the dangling hook symlinks were a real environment change arriving early, not borrowed breakage, and the speculation had been a correct bet. So the message says which tree was under test and how to find out. It does not decide. "If it still fails off the borrowed base, it is yours" is the whole posture in one clause. ## Both recoveries, because `rebase --onto` is not the only one The speculation is local and nothing borrowed has been pushed, so `git reset --hard origin/` is available and cheaper whenever the remote still holds the clean branch. The row asks for it by name. ## Scope `mise-tasks/land.sh`'s `verify`-failure branch alone. No change to `settle_speculation`, to the bet's record, or to any exit code — CLOUD-369's design is sound and this does not reopen it. The new arm precedes the generic one for the same reason CLOUD-861's disk arm does: the generic advice is actively wrong on this path, and an arm after it would never be reached. ## Verification Two cases in opposite directions, which is what stops the fix widening a message that is already right in the common case: a failing `verify` under a speculative base names it, and a failing `verify` with no speculation is byte-identical to today. The speculative case is driven through the RECOVERY path rather than the lap's own speculation, and that is a real constraint rather than a convenience: the lap places its bet AFTER `verify` runs, so only an adopted bet has `spec_base` set before the first verify. The bet must settle as PENDING — the ordinary reading, and the only one that leaves the tree linearized while the lap proceeds. `tests/land.bats` also carries a count assertion over `land`'s stopping conditions, so a new stop cannot be added silently: 32 -> 33, with the reason recorded beside the count. 145/145 green. Closes CLOUD-727 Refs: CLOUD-727, CLOUD-369, CLOUD-861, CLOUD-732, CLOUD-862 --- mise-tasks/land.sh | 35 ++++++++++++++++++++++++++++ tests/land.bats | 58 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/mise-tasks/land.sh b/mise-tasks/land.sh index 7f72d70d8..c2a93fac4 100755 --- a/mise-tasks/land.sh +++ b/mise-tasks/land.sh @@ -1374,6 +1374,41 @@ while :; do free_mb="$(df -Pm . 2>/dev/null | awk 'NR == 2 { print $4 }')" die "verify could not run on $(git rev-parse --short HEAD): the disk filled during it (${free_mb:-unknown}MB free now). This is the environment, NOT this branch — there is nothing here to reproduce. Reclaim and run \`mise run land\` again: \`mise run target-prune\` takes the superseded artifacts, and \`target/debug/incremental\` is the one it cannot (it is never superseded, only unbounded)." fi + # CLOUD-727, and it precedes the generic arm below for the same reason the + # disk arm does: that arm's two sentences are both WRONG on a speculatively + # linearized tree. "Reproduce and fix locally" points at a defect the author + # did not write, and "CI is not where you discover this" implies discovery is + # overdue when the tree under test is not the one the author will ever push. + # + # This task already HOLDS the fact — `spec_base` is set, and it printed that + # base a few lines earlier ("speculatively linearized onto @ — the + # main that is about to exist"). It then emitted the unconditional message + # anyway, so the reader had to reconstruct whose tree they were looking at. + # + # MEASURED 2026-08-19: a two-commit branch touching only `.serena/memories/*` + # failed on `Cargo.lock sbom-ntia-conformance` and `mise-tasks/claim-race-check + # claim-not-raced`, neither file touched by either commit. Rebasing off the + # speculative base was green first try. The cost is not the wasted `verify` — + # that is the speculation's accepted price — it is the reasoning afterwards, + # and the live risk that an author takes the message at its word and starts + # repairing a sibling's branch through their own. On 2026-08-22 the masked + # failure was in `land`'s OWN suite, which is the most expensive possible + # wrong place to send someone. + # + # BOTH RECOVERIES ARE NAMED, because `rebase --onto` is not the only one and + # the cheaper one is available whenever the remote still holds the clean + # branch: the speculation is local and nothing borrowed has been pushed. + # + # IT NAMES A SUSPICION, NEVER A VERDICT. The failure may still be the + # author's — CLOUD-727 records an instance where an identical-looking refusal + # reproduced with no speculation at all, and treating "speculative" as the + # explanation because it is the salient difference is the error that row + # retracted twice in one day. So this says which tree was under test and how + # to find out; it does not decide. + if [[ "$verify_rc" != 0 ]] && [[ -n "$spec_base" ]]; then + die "verify failed on $(git rev-parse --short HEAD) (exit $verify_rc), but this tree is SPECULATIVE: it carries $(git rev-parse --short "$spec_base")'s unlanded commits as well as your own, so the failure may not be yours. Find out with \`git rebase --onto origin/main $(git rev-parse --short "$spec_base")\` and re-run \`mise run verify\` — or, since nothing borrowed has been pushed, \`git reset --hard origin/$branch\`. If it still fails off the borrowed base, it is yours. Its last words: +$verify_tail" + fi [[ "$verify_rc" = 0 ]] || die "verify failed on $(git rev-parse --short HEAD) (exit $verify_rc). Reproduce and fix locally; CI is not where you discover this. Its last words: $verify_tail" diff --git a/tests/land.bats b/tests/land.bats index 25d91e05d..c8ad7a0eb 100644 --- a/tests/land.bats +++ b/tests/land.bats @@ -2019,14 +2019,21 @@ head_verdict() { echo "$1" >"$BATS_TEST_TMPDIR/rc.mise.checks-green"; } # operator — a reset that fails means the undo point is gone, a replay that # fails means another branch's commits will not come off, and the second is # the one that must never reach a push. Exercised below. + # 33 since CLOUD-727: a `verify` failure on a SPECULATIVELY linearized tree. + # It is a separate stop rather than a branch inside the generic verify one for + # the reason the disk stop is: the generic arm's advice — "reproduce and fix + # locally" — is wrong when the tree under test carries another branch's + # unlanded commits, and it precedes that arm for the same reason. Exercised + # below, with an anti-vacuity twin holding the narrowing to its scope: an + # ordinary failure with no speculation must still get the original advice. # 32 since CLOUD-827: a prose-only branch, whose whole diff is comment lines # with no test change. It is the only stop here that is about what the change # is WORTH rather than whether it is correct — every gate above it asks # whether the branch works, and this one asks whether the matrix it is about # to buy can have an opinion about it. Exercised below, with the admitting # direction held by the gate's own suite rather than duplicated here. - [ "$stops" -eq 32 ] || { - echo "land has $stops stopping conditions; this suite covers 32." + [ "$stops" -eq 33 ] || { + echo "land has $stops stopping conditions; this suite covers 33." echo "Add a case for the new one — an unexercised exit is how the refusal path stayed dead." return 1 } @@ -2537,6 +2544,53 @@ holder_is_green() { [ "$(comments)" -eq 0 ] } +@test "a verify failure on a SPECULATIVE tree names the borrowed base" { + # CLOUD-727. `land` already holds the fact — it printed the base a few lines + # earlier — and then emitted an unconditional message whose two sentences are + # both wrong here: "reproduce and fix locally" points at a defect the author did + # not write, and "CI is not where you discover this" implies discovery is overdue + # when the tree under test is not the one the author will ever push. + # + # Measured 2026-08-19: a two-commit branch touching only `.serena/memories/*` + # failed on two findings in files neither commit touched, and rebasing off the + # speculative base was green first try. On 2026-08-22 the masked failure was in + # `land`'s OWN suite — the most expensive possible wrong place to send someone. + # + # DRIVEN THROUGH THE RECOVERY PATH, because the lap's own speculation is placed + # AFTER verify runs: a bet is adopted at the top of the lap, so `spec_base` is + # set before the first verify rather than after it. The bet must settle as + # PENDING — the ordinary reading, and the only one that leaves the tree + # linearized while the lap proceeds. + stranded + spec_head holder-branch + echo 0 >"$BATS_TEST_TMPDIR/rc.spec_live" + task_fails verify + run "$LAND" + [ "$status" -eq 1 ] + [[ "$output" == *"this tree is SPECULATIVE"* ]] + # BOTH recoveries, because `rebase --onto` is not the only one and the cheaper + # one is available whenever the remote still holds the clean branch. + [[ "$output" == *"rebase --onto origin/main"* ]] + [[ "$output" == *"reset --hard"* ]] + # A SUSPICION, never a verdict: this row retracted two attributions in one day + # for treating "speculative" as the explanation because it was the salient + # difference, so the message must say how to find out rather than decide. + [[ "$output" == *"may not be yours"* ]] + [[ "$output" == *"If it still fails off the borrowed base, it is yours"* ]] + # The advice that is wrong here must not also be present. + [[ "$output" != *"Reproduce and fix locally"* ]] +} + +@test "a verify failure with NO speculation still gets the original advice" { + # The anti-vacuity twin, and what stops the fix widening a message that is + # already right in the common case. Same failure, no borrowed base. + task_fails verify + run "$LAND" + [ "$status" -eq 1 ] + [[ "$output" == *"Reproduce and fix locally"* ]] + [[ "$output" != *"this tree is SPECULATIVE"* ]] +} + @test "A WAITER THAT IS NOT ADMITTED STAYS IN DRAFT — this is what bounds the cost" { # The negative that gives the case above its meaning. Without it, "every # waiter readies" would pass the test above and spend a matrix per session, From abef9cea0d99f2b051b183476bd56a5b03df7abe Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 09:22:33 +0000 Subject: [PATCH 7/9] fix(gate): the DO-NOT-CLOSE opt-out is per-key where it names one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The strand subtraction shipped with one escape: a `DO-NOT-CLOSE` line, which exempts the WHOLE body. The first real body it judged was the one that introduces it, and that body needs neither answer. ## What the gate said about its own PR This branch's commits serve seven first-`Refs:` keys. Five are rows it completes. Two are not: CLOUD-926 is the dispatch record the bundle rides under, and CLOUD-871's disposition is to fold into CLOUD-843 rather than ship a gate. So: ``` ::error:: this PR closes some of the keys its commits served and strands the rest: CLOUD-871 served, not closed CLOUD-926 served, not closed ``` Correct, and the only escape on offer was the global marker — which would have admitted the body by switching the subtraction off entirely. **The gate would have shipped passing its own PR for the one reason a bundle must never pass: nobody checked.** A gate that is vacuous on the first body it meets is CLOUD-418's class, reached from the config side instead of the test side. ## The narrowing A marker line that NAMES keys exempts exactly those; a marker line that names none keeps the global reading. That is what CLOUD-527 is about, and it is already in this row's §8 as the marker the predicate must honour — the per-key form existed in the suite (`the marker may name the issue it declines to close`) and the subtraction simply did not read the key. `hold_global` is a SEPARATE variable rather than a mutation of `hold`, and the distinction is load-bearing: `hold` says the marker is present at all, which is what the "declines to complete" verdict reads and what `DO-NOT-CLOSE CLOUD-192 — part 1 of 3` must still satisfy. Collapsing them made the keyed marker stop reporting "declines", inverting the case that exists precisely because the marker's most natural form once failed as the inverse of its author's intent. The key scan reads off the marker's own line and is deliberately NOT `$CLOSING_VERBS`-bounded: the marker ENDS in the closing verb, which is why `DO-NOT-CLOSE CLOUD-388` once read as CLOSING CLOUD-388. Keys are taken from the line, so the verb that ends the marker cannot supply a close. ## A second finding, caught by a `forbid` row rather than by me `batten-check` refused `tests/landed-check.bats:331 no-branch-f-main` — a fixture this branch added in CLOUD-903's commit used `git branch -f main HEAD`. That rule exists because the identical mistake previously passed only by accident of the runner's git defaulting to `master`, which made `main` a different branch; the rationale is on the row in `batten.toml`. This fixture would have been the third instance. Fixed by using the suite's own `land()` helper, which already does checkout-commit-update-ref correctly — reuse rather than a third spelling. ## Verification 32 cases in `tests/closing-key-check.bats`, including three new ones: a keyed marker exempts that key, a keyed marker does NOT excuse a key it never named, and a bare marker still declines the whole body. Two declared mutations, both caught — the new one flips `hold_global` back to `hold`, which is exactly the collapse this narrowing prevents. `tests/landed-check.bats` 31/31. `batten-check` clean. Verified against the real body: this PR's own, which now passes, and the same body with one `Closes` removed, which still refuses and names the stranded key. Refs: CLOUD-674, CLOUD-527, CLOUD-903, CLOUD-418 --- mise-tasks/closing-key-check.sh | 54 +++++++++++++++++++++++++++++++-- tests/closing-key-check.bats | 35 +++++++++++++++++++++ tests/landed-check.bats | 4 +-- 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/mise-tasks/closing-key-check.sh b/mise-tasks/closing-key-check.sh index fc1ffffde..aca27ff2e 100755 --- a/mise-tasks/closing-key-check.sh +++ b/mise-tasks/closing-key-check.sh @@ -52,6 +52,10 @@ # passes under that mutation — precisely the state this file shipped in before # CLOUD-674. #MUTANT closing-key-strand-never-fires|s/^\tstranded=\$(comm -23 /\tstranded=$(true /|a body closing a strict subset of the served keys is refused +# The mutation makes a KEYED marker take the global exit, which is the collapse +# this narrowing exists to prevent: naming one key would excuse every other +# served key, and the gate would be the bare marker with extra steps. +#MUTANT closing-key-held-goes-global|s/^\thold_global=$/\thold_global="\$hold"/|a keyed marker does not excuse a key it never named set -euo pipefail # The opt-out, and the same string `released` matches on the issue side. Stated @@ -216,15 +220,61 @@ if grep -qE "^[[:space:]]*$HOLD_MARKER" <<<"$body"; then hold=1 fi +# THE OPT-OUT IS PER-KEY WHERE IT NAMES ONE, AND THE DISTINCTION IS THE WHOLE +# REASON THIS GATE IS NOT VACUOUS ON ITS OWN PR (CLOUD-527, CLOUD-674). +# +# A bare `DO-NOT-CLOSE` line exempts the WHOLE body, which is right for the +# several-PRs-per-issue case it was built for (CLOUD-186) and wrong for a bundle: +# the first real body this subtraction judged was the one that introduces it, +# carrying seven served keys of which two — the dispatch record it rides under, +# and a row whose disposition is to fold into another — must not be closed. The +# global marker would have admitted that body by switching the subtraction off +# entirely, so the gate would have shipped passing its own PR for the one reason +# it must never pass a bundle: nobody checked. +# +# So a marker line that NAMES keys exempts exactly those, and a marker line that +# names none keeps the global reading. Both are line-anchored, because a body +# that merely DISCUSSES the marker has not used it — the same distinction the +# adjacency rule draws for the closing verb, and the one PR #404 was excused by. +# +# The key scan here is deliberately NOT `$CLOSING_VERBS`-bounded: the marker ends +# in the closing verb, which is exactly why `DO-NOT-CLOSE CLOUD-388` once read as +# CLOSING CLOUD-388. Keys are read off the marker's own line, so the verb that +# ends the marker cannot supply a close. +held=$(grep -E "^[[:space:]]*$HOLD_MARKER" <<<"$body" | + grep -oiE '(^|[^0-9A-Za-z-])CLOUD-[0-9]+([^0-9]|$)' | + grep -oiE 'CLOUD-[0-9]+' | tr '[:lower:]' '[:upper:]' | sort -u || true) + +# A marker naming keys is per-key, so it must not also take the global exit — +# without this the first named key would switch the subtraction off for every +# other served key, which is the failure being fixed. +# +# `hold_global` is SEPARATE from `hold` rather than a mutation of it, because the +# two answer different questions and one case reads each. `hold` says the marker +# is present at all, which is what the "declines to complete" verdict below is +# about and what `DO-NOT-CLOSE CLOUD-192 — part 1 of 3` must still satisfy. +# `hold_global` says the marker declined to name anything, which is the only +# reading that may switch the subtraction off wholesale. Collapsing them made the +# keyed marker stop reporting "declines", inverting the case that exists because +# the marker's most natural form once failed as the inverse of its author's +# intent. +hold_global="$hold" +if [[ -n "$hold" && -n "${held//[[:space:]]/}" ]]; then + hold_global= + if [[ -n "${stranded//[[:space:]]/}" ]]; then + stranded=$(comm -23 <(sort <<<"$stranded") <(sort <<<"$held") || true) + fi +fi + if [[ -n "$closing" ]]; then - if [[ -n "${stranded//[[:space:]]/}" && -z "$hold" ]]; then + if [[ -n "${stranded//[[:space:]]/}" && -z "$hold_global" ]]; then { echo "::error:: this PR closes some of the keys its commits served and strands the rest:" while IFS= read -r id; do [[ -n "$id" ]] || continue echo " $id served, not closed" done <<<"$stranded" - echo "Write \"Closes \" for each, or add $HOLD_MARKER if this PR is not meant to complete them." + echo "Write \"Closes \" for each, or \"$HOLD_MARKER \" on its own line to decline just that one. A bare $HOLD_MARKER line declines the whole body." } >&2 exit 1 fi diff --git a/tests/closing-key-check.bats b/tests/closing-key-check.bats index f29b957d2..25a4498cf 100644 --- a/tests/closing-key-check.bats +++ b/tests/closing-key-check.bats @@ -297,3 +297,38 @@ Refs: CLOUD-661'" [[ "$output" == *"CLOUD-661 served, not closed"* ]] [[ "$output" != *"CLOUD-999"* ]] } + +@test "a marker naming a key exempts THAT key and no other" { + # CLOUD-527's per-key opt-out, and the case that stops this gate being + # vacuous on the first real body it judged — its own. That PR served seven + # keys and had to close five: the dispatch record it rides under and a row + # whose disposition is to fold elsewhere must not be closed, while the other + # five must. A bare marker admits that body by switching the subtraction off + # entirely, which is passing a bundle for the one reason a bundle must never + # pass: nobody checked. + log=$'feat: a\n\nRefs: CLOUD-10, CLOUD-99\n\nfeat: b\n\nRefs: CLOUD-20\n' + run bash -c "printf 'Closes CLOUD-10\n\nDO-NOT-CLOSE CLOUD-20 — folded elsewhere.\n' | $GATE --served-log '$log'" + [ "$status" -eq 0 ] + [[ "$output" == *"closes CLOUD-10"* ]] +} + +@test "a keyed marker does not excuse a key it never named" { + # The narrowing has to be exactly as wide as the keys on the marker lines. + # If naming one key switched the whole subtraction off, this gate would be + # the bare marker with extra steps. + log=$'feat: a\n\nRefs: CLOUD-10\n\nfeat: b\n\nRefs: CLOUD-20\n\nfeat: c\n\nRefs: CLOUD-30\n' + run bash -c "printf 'Closes CLOUD-10\n\nDO-NOT-CLOSE CLOUD-20 — folded elsewhere.\n' | $GATE --served-log '$log'" + [ "$status" -eq 1 ] + [[ "$output" == *"CLOUD-30 served, not closed"* ]] + # The declined one must NOT be reported: it was answered, not stranded. + [[ "$output" != *"CLOUD-20 served"* ]] +} + +@test "a bare marker still declines the whole body" { + # The several-PRs-per-issue case (CLOUD-186) the marker was built for is + # untouched: a marker that names nothing keeps the global reading. + log=$'feat: a\n\nRefs: CLOUD-10\n\nfeat: b\n\nRefs: CLOUD-20\n' + run bash -c "printf 'Closes CLOUD-10\n\nDO-NOT-CLOSE — part 1 of 3.\n' | $GATE --served-log '$log'" + [ "$status" -eq 0 ] + [[ "$output" == *"closes CLOUD-10"* ]] +} diff --git a/tests/landed-check.bats b/tests/landed-check.bats index 2f64f8022..7798ba7a8 100644 --- a/tests/landed-check.bats +++ b/tests/landed-check.bats @@ -327,9 +327,7 @@ asserted() { # The property the whole third arm had to preserve. A commit that CITES the id # as prior art must not drain it, and supplying an assertion file for some # OTHER row must not change that. - git commit -q --allow-empty -m "docs: cite CLOUD-903 as prior art" - git branch -f main HEAD - git update-ref refs/remotes/origin/main main + land "docs: cite CLOUD-903 as prior art" LB="$BATS_TEST_TMPDIR/landedby-$BATS_TEST_NUMBER.tsv" : >"$LB" asserted CLOUD-999 "#1" From 9e63f4e1b68346f8c0ddb0cd79b8080122fa7bc6 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 10:17:44 +0000 Subject: [PATCH 8/9] fix(land): the runaway refusal counts the matrices it bought instead of inferring them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured on this branch's own landing, one commit after the message was rewritten: `land` hit the lap cap and announced stopped after 2 laps, having spent 2 CI matrices and none of them landed against **zero** check-runs on the head. The PR was still a draft, `ci.yml` had never fired, and the only run on the SHA was SonarCloud, which is not draft-gated. Both laps died to `main` moving under `verify`, well before `gh pr ready`. ## The premise was false, and it was mine CLOUD-904's argument — repeated verbatim in the commit that landed it — is that `charge_wait` refunds every lap that bought no CI, so "a lap counts ONLY when it actually spent a matrix". That fails in both directions. There are **five** refund sites, not the three CLOUD-904 names: the two in `charge_wait`, the bot-silence path, `charge_transient`, and the admitted-successor push. And they still miss the ordinary case — a lap where `main` moves while `verify` runs aborts before the ready, buys nothing, and is charged anyway. That is the exact path this branch took, twice. So `lap` is an attempt counter. Deriving a spend from it overstates the cost, and a refusal that overstates what it cost is the defect CLOUD-904 exists to fix — a message asserting what the accounting does not support — reproduced one level down by the fix for it. ## Counted, not inferred `paid_laps` increments at the single site that buys a matrix: the `gh pr ready` that starts CI. The refusal reports that number and says which it is, so a reader cannot mistake it for the lap count again. Counting at the one cause rather than refunding at each non-cause is the point: enumerating non-causes is what produced five refund sites and still missed one. The refund policy itself is untouched — CLOUD-904 scopes it out, and the lap budget still bounds attempts, which is what the backstop is for. The re-commitment is now an upper bound (`up to another 2`) rather than a promise of exactly that many, since a re-run can land on its first lap. ## Both declared mutations had gone stale, and one was invisible Rewriting the message invalidated both `#MUTANT` rows against it — `lap-cap-remedies-swapped` reported `inert-mutation` (its pattern matched nothing), and `lap-cap-asserts-refuted-diagnosis` reported `SURVIVED` (it matched but no longer broke the property). Neither is visible in a diff, and before this branch fixed `mutant`'s stdin defect the second would have been skipped entirely and reported as caught. Re-anchored to the text that now exists; the discriminating value is `bought 0 CI matrices`, which the previous code could not produce. ## The plural is computed outside the refusal, and shellcheck is why Inline pluralisation inside the `die_with` argument compares the literal `\"$paid_laps\"` against `1` — every quote in that string is escaped, so the test can never match (SC2193, caught by the pre-commit gate). The noun is computed a few lines up where quoting is ordinary. ## Verification `tests/land.bats` 145/145. `mise run mutant land` reports 11 declared, every one caught. `shellcheck --severity=style` clean. The case asserts the counted spend AND that the old wording is absent, so a regression to inference reddens rather than passing quietly. Refs: CLOUD-904, CLOUD-413, CLOUD-399, CLOUD-941, CLOUD-418 --- mise-tasks/land.sh | 56 ++++++++++++++++++++++++++++++++++++++-------- tests/land.bats | 20 +++++++++++------ 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/mise-tasks/land.sh b/mise-tasks/land.sh index c2a93fac4..6a5f0f10a 100755 --- a/mise-tasks/land.sh +++ b/mise-tasks/land.sh @@ -96,8 +96,8 @@ # which proves the string is present and nothing about the property. These two # break the PROPERTY instead — the first restores the refuted diagnosis into the # emission site, the second gives the expensive path the free path's remedy. -#MUTANT lap-cap-asserts-refuted-diagnosis|s/Laps that bought nothing were already refunded, so this is not/\\`main\\` is moving faster than a lap takes, and this is/|the lap cap's refusal states what its own accounting supports -#MUTANT lap-cap-remedies-swapped|s/If every lap lost only to contention, run this again, which commits another \$max_laps matrices./The fleet is saturated: wait, or land later./|the two exhaustions give imperatives consistent with their costs +#MUTANT lap-cap-asserts-refuted-diagnosis|s/The count is what the readying recorded, not the lap number/\\`main\\` is moving faster than a lap takes/|the lap cap's refusal states what its own accounting supports +#MUTANT lap-cap-remedies-swapped|s/run this again, which commits up to another \$max_laps./the fleet is saturated: wait, or land later./|the two exhaustions give imperatives consistent with their costs #MUTANT exit-codes-collapse|s/^readonly LAND_EXIT_RUNAWAY=5$/readonly LAND_EXIT_RUNAWAY=4/|CLOUD-399: the two exhaustions are told apart by CODE #MUTANT declined-always|s/^\t\[\[ \"\$rc\" = 3 \]\]$/\ttrue/|red CI stops the lap without asking for the merge # CLOUD-369. The admission predicates, each proven to discriminate rather than @@ -1178,14 +1178,32 @@ admitted=0 # moves HEAD makes it stale, and a stale one means the run in flight grades a # commit this branch no longer has — so the pair runs again for the new head. admitted_sha= +# Matrices actually bought, incremented at the one site that buys one. See the +# comment there for why this is counted rather than inferred from `lap`. +paid_laps=0 while :; do lap=$((lap + 1)) - # THE REMEDY IS DERIVED FROM THE ACCOUNTING ABOVE, NOT RESTATED BESIDE IT - # (CLOUD-904). `charge_wait` REFUNDS the lap on every path that bought no CI — - # the lease held by someone else, the lease won over a `main` that had moved, and - # the bot giving no readable answer. So a lap counts ONLY when it actually spent - # a matrix, and reaching the cap means exactly one thing: this branch spent - # `max_laps` matrices and none of them landed. + # Computed here rather than inline in the refusal below: inside that + # argument every quote is escaped, so a `[[ ]]` there compares the literal + # `\"$paid_laps\"` against `1` and can never match (SC2193). + matrices=matrices + [[ "$paid_laps" -ne 1 ]] || matrices=matrix + # THE REMEDY IS DERIVED FROM THE ACCOUNTING, NOT RESTATED BESIDE IT (CLOUD-904), + # and the spend it names is COUNTED at the ready rather than inferred from this + # counter. + # + # The inference was tried first and is wrong. It rested on "`charge_wait` + # refunds every lap that bought no CI, so a lap counts only when it spent a + # matrix" — and that premise fails in both directions. There are FIVE refund + # sites, not the three CLOUD-904 names (the two in `charge_wait`, bot silence, + # `charge_transient`, and the admitted-successor push), and they still miss the + # ordinary case: a lap where `main` moves while `verify` runs aborts before the + # ready, buys nothing, and is charged anyway. + # + # Measured on PR #651 while landing this very change: two laps, both lost to + # `main` moving under `verify`, `gh pr ready` never reached, ZERO check-runs on + # the head — and the refusal announced "having spent 2 CI matrices". So `lap` + # is an attempt counter and nothing more; `paid_laps` is the spend. # # It does NOT mean `main` outran a lap. That inference is what the refunds # removed, and it is the same diagnosis the comment at the bot-silence refund @@ -1203,7 +1221,7 @@ while :; do # the case that converges. [[ "$lap" -le "$max_laps" ]] || die_with "$LAND_EXIT_RUNAWAY" \ - "stopped after $max_laps laps, having spent $max_laps CI matrices and none of them landed. Laps that bought nothing were already refunded, so this is not \`main\` outrunning a lap. Read this run's \`::error::\` lines for how each lap ended, and \`gh pr view $pr --json isDraft,statusCheckRollup\` — a draft PR means CI went red and this task re-drafted it. A rebase conflict, a failed \`verify\` or red CI will lose again; fix it first. If every lap lost only to contention, run this again, which commits another $max_laps matrices." + "stopped after $max_laps laps, having bought $paid_laps CI $matrices and landed nothing. The count is what the readying recorded, not the lap number: a lap that ended before the ready bought nothing, so $paid_laps of $max_laps is what this cost. Read this run's \`::error::\` lines for how each lap ended, and \`gh pr view $pr --json isDraft,statusCheckRollup\` — a draft PR means CI went red and this task re-drafted it. A rebase conflict, a failed \`verify\` or red CI will lose again; fix it first. If every lap lost only to contention, run this again, which commits up to another $max_laps." # A lap holds the lease only across its own CI window. Dropping it here — at # the top, covering every `continue` below uniformly — means a lap that lost @@ -1705,6 +1723,26 @@ $verify_tail" gh pr ready "$pr" >/dev/null 2>&1 || die "could not mark #$pr ready for review, so CI would never start." readied=1 + # THE ONE PLACE A MATRIX IS ACTUALLY BOUGHT, and therefore the only honest + # place to count one (CLOUD-904). The readying is the event that starts CI; + # every lap that ends before here spent nothing. + # + # The runaway refusal used to DERIVE its spend from the lap counter, on the + # premise that `charge_wait` refunds every lap that bought no CI. That + # premise is false in both directions and this counter is what replaces it: + # there are FIVE refund sites rather than the three CLOUD-904 names, and + # they still do not cover the ordinary case — a lap where `main` moved + # while `verify` ran aborts before the ready, buys nothing, and is charged + # anyway. Measured on PR #651: two laps, both lost that way, `gh pr ready` + # never reached, ZERO check-runs on the head — and the refusal said "having + # spent 2 CI matrices". A refusal that overstates what it cost is the same + # defect CLOUD-904 exists to fix, one level down: a message asserting what + # the accounting does not support. + # + # Counted here rather than refunded at each non-spending exit because the + # spend has ONE cause and many non-causes; enumerating the non-causes is + # what produced five refund sites and still missed one. + paid_laps=$((paid_laps + 1)) echo "land: lap $lap — readied #$pr before pushing, so the push's own event is the one confirming run" fi diff --git a/tests/land.bats b/tests/land.bats index c8ad7a0eb..506cedac5 100644 --- a/tests/land.bats +++ b/tests/land.bats @@ -1102,9 +1102,15 @@ runs_query_403() { : >"$BATS_TEST_TMPDIR/rc.runs"; } echo 2 >"$BATS_TEST_TMPDIR/rc.mise.verify" LAND_MAX_LAPS=2 run "$LAND" [ "$status" -eq 5 ] - # What the accounting supports: the spend, and that it did not land. - [[ "$output" == *"spent 2 CI matrices"* ]] - [[ "$output" == *"none of them landed"* ]] + # What the accounting supports: the spend COUNTED at the ready, and that it + # did not land. Zero here is the discriminating value — these laps fail + # `verify`, so the ready is never reached and nothing is bought. Asserting a + # spend equal to the lap count is exactly the overstatement this replaced: + # measured on PR #651, two such laps reported "spent 2 CI matrices" against + # zero check-runs on the head. + [[ "$output" == *"bought 0 CI matrices"* ]] + [[ "$output" == *"landed nothing"* ]] + [[ "$output" != *"spent 2 CI matrices"* ]] # The refuted inference must not be asserted at the emission site. The literal # is allowed in the file's explanatory comments, which earn it by describing the # bug — this reads the REFUSAL, not the file. @@ -1143,13 +1149,13 @@ runs_query_403() { : >"$BATS_TEST_TMPDIR/rc.runs"; } [[ "$saturated" == *"spent no CI matrix"* ]] [[ "$saturated" == *"wait, or land later"* ]] - # The spent path names the cost it already paid... - [[ "$runaway" == *"spent 2 CI matrices"* ]] + # The spent path names the cost it already paid, counted rather than inferred. + [[ "$runaway" == *"bought 0 CI matrices"* ]] # ...names a continuing action... [[ "$runaway" == *"run this again"* ]] # ...and names the spend that action re-commits, which is what stops the # continuing imperative from being unconditional. - [[ "$runaway" == *"commits another 2 matrices"* ]] + [[ "$runaway" == *"commits up to another 2"* ]] # The remedies must not be interchangeable: the expensive path must not be # telling the caller to wait, which is the free path's answer. @@ -1199,7 +1205,7 @@ runs_query_403() { : >"$BATS_TEST_TMPDIR/rc.runs"; } # CLOUD-904 removed the refuted diagnosis this used to match on. The subject of # THIS case is the exit CODES, so it needs any string that identifies the runaway # refusal; the content of that refusal is the pair of cases above. - [[ "$output" == *"none of them landed"* ]] + [[ "$output" == *"landed nothing"* ]] # The property itself, stated once: distinguishable, and neither is the # generic stop that every other `die` in this task uses. From faac28dd77adece6638529aa23baeaf0e313386b Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 10:45:33 +0000 Subject: [PATCH 9/9] chore(bench): regenerate the suite cost record over all 157 tracked suites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rebase onto current `main` collided on `bench/suites/RESULTS.md`, and I resolved it on the wrong side: this branch's copy carried 151 rows against main's 156, so taking mine silently dropped the six suites main had added — `ci-drift`, `mutant-census`, `perf-gate`, `replay`, `suite-select`, `token-bench-check`. `suite-bench-check` refused the tree and named all six, which is what the gate is for. ## Regenerating on top of the wrong base does not fix it, and the header says why `suite-bench` records what each suite costs **from a run that already happened** — it parses the last `test:bats` output rather than measuring. So two successive `--write` invocations both reported `151 suite(s), 1076.5s`, byte-identical, because both re-parsed the same stale run. Reading that as "the write did not take" would have been the wrong diagnosis; the write took, and the input was old. The sequence that actually works is main's record as the base, then a full `test:bats` on the rebased tree, then `--write`: | step | suites | | -- | -- | | this branch's stale copy | 151 | | `origin/main` | 156 | | after a fresh full tier | **157** | 157 is the tracked set: main's six plus this branch's `prose-only-check`. ## What the fresh tier also confirms **2800 of 2800 green**, up from 2708 before the rebase. That is main's six new suites plus this branch's two running against this branch's changes, which is the evidence the conflict resolutions were sound rather than merely free of markers — `MUTANT_GATES` unioned to 114 entries, and `closing-key-check`'s two mutation declarations kept side by side instead of one overwriting the other. The whole table is rewritten because every row is a re-measurement on this machine. Hand-adding a single row would have meant inventing a timing for it, which is the one thing a generated cost record must not carry. Refs: CLOUD-926 --- bench/suites/RESULTS.md | 304 ++++++++++++++++++++-------------------- 1 file changed, 155 insertions(+), 149 deletions(-) diff --git a/bench/suites/RESULTS.md b/bench/suites/RESULTS.md index 653c5eb0f..cd10423f9 100644 --- a/bench/suites/RESULTS.md +++ b/bench/suites/RESULTS.md @@ -6,159 +6,165 @@ runner measured it; the suite runs `--no-parallelize-within-files`, so a file's number is its own serial cost and is what an author adding a case to it pays. -- suites: 151 -- serial total: 849.7s +- suites: 157 +- serial total: 1206.8s | seconds | share | suite | | ---: | ---: | --- | -| 129.8 | 15.3% | `tests/land-lock.bats` | -| 102.1 | 12.0% | `tests/ci-wait.bats` | -| 99.2 | 11.7% | `tests/derived-check.bats` | -| 73.4 | 8.6% | `tests/land.bats` | -| 49.5 | 5.8% | `tests/hooks-wiring-check.bats` | -| 36.1 | 4.2% | `tests/helpers.bats` | -| 34.6 | 4.1% | `tests/main-watch.bats` | -| 32.1 | 3.8% | `tests/ci-local-parity.bats` | -| 24.2 | 2.9% | `tests/hook-latency-drift.bats` | -| 13.8 | 1.6% | `tests/claim-check.bats` | -| 11.6 | 1.4% | `tests/board-diff-overlap.bats` | -| 9.0 | 1.1% | `tests/config-lint.bats` | -| 8.7 | 1.0% | `tests/prebuilt-lint.bats` | -| 8.7 | 1.0% | `tests/graph-check.bats` | -| 7.4 | 0.9% | `tests/token-bench.bats` | -| 7.3 | 0.9% | `tests/target-race.bats` | -| 7.3 | 0.9% | `tests/run-shape-guard.bats` | -| 7.2 | 0.8% | `tests/board-write-record.bats` | -| 6.0 | 0.7% | `tests/renovate-config-validator.bats` | -| 5.8 | 0.7% | `tests/mcp-allow-check.bats` | -| 5.8 | 0.7% | `tests/filed-here-check.bats` | -| 5.7 | 0.7% | `tests/ready-guard.bats` | -| 5.6 | 0.7% | `tests/ready-lint.bats` | -| 5.1 | 0.6% | `tests/released.bats` | -| 4.8 | 0.6% | `tests/stop-guard.bats` | -| 4.8 | 0.6% | `tests/session-start.bats` | -| 4.8 | 0.6% | `tests/lock-complete.bats` | -| 4.5 | 0.5% | `tests/singleton.bats` | -| 4.3 | 0.5% | `tests/sbom-check.bats` | -| 4.1 | 0.5% | `tests/step-receipt.bats` | -| 4.1 | 0.5% | `tests/release-assets-check.bats` | -| 4.0 | 0.5% | `tests/in-progress-drain.bats` | -| 3.8 | 0.5% | `tests/task-registry.bats` | -| 3.6 | 0.4% | `tests/with-lock.bats` | -| 3.6 | 0.4% | `tests/schema-check.bats` | -| 3.4 | 0.4% | `tests/doctor-race.bats` | -| 3.3 | 0.4% | `tests/board-move-guard.bats` | -| 3.1 | 0.4% | `tests/pre-commit-staging.bats` | -| 3.1 | 0.4% | `tests/reference-check.bats` | -| 2.9 | 0.3% | `tests/land-divergence.bats` | -| 2.9 | 0.3% | `tests/target-ensure.bats` | -| 2.8 | 0.3% | `tests/ready-cites-check.bats` | -| 2.7 | 0.3% | `tests/mutant.bats` | -| 2.6 | 0.3% | `tests/board-sweep.bats` | -| 2.6 | 0.3% | `tests/issue-read-check.bats` | -| 2.6 | 0.3% | `tests/hk-selection.bats` | -| 2.6 | 0.3% | `tests/semver.bats` | -| 2.2 | 0.3% | `tests/issue-read-guard.bats` | -| 1.9 | 0.2% | `tests/fanout-guard.bats` | -| 1.7 | 0.2% | `tests/claim-race-check.bats` | -| 1.7 | 0.2% | `tests/skill-check.bats` | -| 1.7 | 0.2% | `tests/landed-check.bats` | -| 1.6 | 0.2% | `tests/finding-sink-check.bats` | -| 1.6 | 0.2% | `tests/unlanded-check.bats` | -| 1.6 | 0.2% | `tests/reclaim-census.bats` | -| 1.5 | 0.2% | `tests/spec-ref-check.bats` | -| 1.4 | 0.2% | `tests/bot-issue.bats` | -| 1.4 | 0.2% | `tests/closing-key-check.bats` | -| 1.4 | 0.2% | `tests/signing-posture.bats` | -| 1.3 | 0.2% | `tests/claimed-keys.bats` | -| 1.3 | 0.2% | `tests/issue-search-guard.bats` | -| 1.3 | 0.2% | `tests/ci-slow-needed.bats` | -| 1.3 | 0.1% | `tests/tree-clean.bats` | -| 1.2 | 0.1% | `tests/run-shape.bats` | -| 1.2 | 0.1% | `tests/memories-check.bats` | -| 1.2 | 0.1% | `tests/ci-lease-precondition.bats` | -| 1.2 | 0.1% | `tests/verify.bats` | -| 1.1 | 0.1% | `tests/ready-lint-deferral.bats` | -| 1.0 | 0.1% | `tests/awk-regex-check.bats` | -| 1.0 | 0.1% | `tests/perf-record.bats` | -| 1.0 | 0.1% | `tests/alive.bats` | -| 1.0 | 0.1% | `tests/done-check.bats` | -| 1.0 | 0.1% | `tests/deferral-check.bats` | -| 1.0 | 0.1% | `tests/install-check.bats` | -| 1.0 | 0.1% | `tests/ci-tools-check.bats` | -| 0.9 | 0.1% | `tests/linear-check.bats` | -| 0.9 | 0.1% | `tests/land-divergence-assert.bats` | -| 0.9 | 0.1% | `tests/spawn-census.bats` | -| 0.9 | 0.1% | `tests/nonverdict-scan.bats` | -| 0.8 | 0.1% | `tests/rules-drift.bats` | -| 0.8 | 0.1% | `tests/release-tracking-check.bats` | -| 0.8 | 0.1% | `tests/done-pr-check.bats` | -| 0.8 | 0.1% | `tests/hook-pin-check.bats` | -| 0.8 | 0.1% | `tests/attestation-check.bats` | -| 0.8 | 0.1% | `tests/ntia-check.bats` | -| 0.8 | 0.1% | `tests/target-prune.bats` | -| 0.7 | 0.1% | `tests/module-map-check.bats` | -| 0.7 | 0.1% | `tests/gh-guard.bats` | -| 0.7 | 0.1% | `tests/prose-only-check.bats` | -| 0.7 | 0.1% | `tests/pr-unsubscribed.bats` | -| 0.7 | 0.1% | `tests/doctor.bats` | -| 0.7 | 0.1% | `tests/render-cli.bats` | -| 0.7 | 0.1% | `tests/checks-green.bats` | -| 0.7 | 0.1% | `tests/timeout-drift.bats` | -| 0.7 | 0.1% | `tests/issue-search-check.bats` | -| 0.6 | 0.1% | `tests/perf-assert.bats` | -| 0.6 | 0.1% | `tests/sbom-binary.bats` | -| 0.6 | 0.1% | `tests/hook-matcher-check.bats` | -| 0.6 | 0.1% | `tests/merged-pr-keys.bats` | -| 0.6 | 0.1% | `tests/evaluator-closure-check.bats` | -| 0.6 | 0.1% | `tests/verified.bats` | -| 0.6 | 0.1% | `tests/perf-compare.bats` | -| 0.6 | 0.1% | `tests/mcp-timeout-budget.bats` | -| 0.6 | 0.1% | `tests/install.bats` | -| 0.6 | 0.1% | `tests/mcp-attach-check.bats` | -| 0.5 | 0.1% | `tests/hook-profile-check.bats` | -| 0.5 | 0.1% | `tests/connector-verb-guard.bats` | -| 0.5 | 0.1% | `tests/stop-posture-check.bats` | -| 0.5 | 0.1% | `tests/checksums.bats` | -| 0.5 | 0.1% | `tests/publish-credential-check.bats` | -| 0.4 | 0.1% | `tests/macos-link-check.bats` | -| 0.4 | 0.0% | `tests/sonar-gate.bats` | -| 0.4 | 0.0% | `tests/pipefail-grep-check.bats` | -| 0.4 | 0.0% | `tests/digest-major-agreement.bats` | -| 0.4 | 0.0% | `tests/msrv-pin-agreement.bats` | -| 0.4 | 0.0% | `tests/connector-allow-guard.bats` | -| 0.4 | 0.0% | `tests/abandon-matrix.bats` | -| 0.4 | 0.0% | `tests/branch-age-check.bats` | -| 0.4 | 0.0% | `tests/serena-mcp.bats` | -| 0.3 | 0.0% | `tests/license-table-check.bats` | -| 0.3 | 0.0% | `tests/land-lock-check.bats` | -| 0.3 | 0.0% | `tests/transcript-corpus-check.bats` | -| 0.3 | 0.0% | `tests/timeout-check.bats` | -| 0.3 | 0.0% | `tests/report-only-check.bats` | -| 0.3 | 0.0% | `tests/run-shape-guard-quoting.bats` | -| 0.3 | 0.0% | `tests/release-due.bats` | -| 0.3 | 0.0% | `tests/commit-attribution.bats` | -| 0.3 | 0.0% | `tests/board-payloads.bats` | -| 0.3 | 0.0% | `tests/nonverdict-assert.bats` | -| 0.3 | 0.0% | `tests/commit-convention.bats` | -| 0.3 | 0.0% | `tests/pkl-check.bats` | -| 0.3 | 0.0% | `tests/no-doctests.bats` | -| 0.3 | 0.0% | `tests/suite-bench-check.bats` | -| 0.3 | 0.0% | `tests/cap-drift.bats` | +| 144.3 | 12.0% | `tests/land-lock.bats` | +| 140.6 | 11.7% | `tests/derived-check.bats` | +| 102.4 | 8.5% | `tests/ci-wait.bats` | +| 92.2 | 7.6% | `tests/land.bats` | +| 88.8 | 7.4% | `tests/session-start.bats` | +| 72.1 | 6.0% | `tests/hooks-wiring-check.bats` | +| 44.0 | 3.6% | `tests/ci-local-parity.bats` | +| 36.1 | 3.0% | `tests/helpers.bats` | +| 34.7 | 2.9% | `tests/main-watch.bats` | +| 24.3 | 2.0% | `tests/hook-latency-drift.bats` | +| 24.2 | 2.0% | `tests/config-lint.bats` | +| 20.5 | 1.7% | `tests/commit-convention.bats` | +| 20.0 | 1.7% | `tests/token-bench.bats` | +| 17.7 | 1.5% | `tests/claim-check.bats` | +| 14.9 | 1.2% | `tests/board-diff-overlap.bats` | +| 13.2 | 1.1% | `tests/prebuilt-lint.bats` | +| 11.2 | 0.9% | `tests/run-shape-guard.bats` | +| 11.2 | 0.9% | `tests/graph-check.bats` | +| 10.4 | 0.9% | `tests/target-race.bats` | +| 9.1 | 0.8% | `tests/board-write-record.bats` | +| 8.0 | 0.7% | `tests/ready-guard.bats` | +| 8.0 | 0.7% | `tests/stop-guard.bats` | +| 7.9 | 0.7% | `tests/mcp-allow-check.bats` | +| 7.9 | 0.7% | `tests/renovate-config-validator.bats` | +| 7.7 | 0.6% | `tests/ready-lint.bats` | +| 7.7 | 0.6% | `tests/mutant.bats` | +| 7.3 | 0.6% | `tests/released.bats` | +| 7.2 | 0.6% | `tests/filed-here-check.bats` | +| 6.7 | 0.6% | `tests/sbom-check.bats` | +| 6.6 | 0.5% | `tests/replay.bats` | +| 6.4 | 0.5% | `tests/lock-complete.bats` | +| 6.2 | 0.5% | `tests/step-receipt.bats` | +| 6.2 | 0.5% | `tests/in-progress-drain.bats` | +| 5.6 | 0.5% | `tests/task-registry.bats` | +| 5.4 | 0.4% | `tests/release-assets-check.bats` | +| 4.8 | 0.4% | `tests/schema-check.bats` | +| 4.8 | 0.4% | `tests/hk-selection.bats` | +| 4.7 | 0.4% | `tests/singleton.bats` | +| 4.4 | 0.4% | `tests/land-divergence.bats` | +| 4.3 | 0.4% | `tests/reference-check.bats` | +| 4.0 | 0.3% | `tests/board-move-guard.bats` | +| 3.9 | 0.3% | `tests/with-lock.bats` | +| 3.7 | 0.3% | `tests/unlanded-check.bats` | +| 3.7 | 0.3% | `tests/tree-clean.bats` | +| 3.7 | 0.3% | `tests/semver.bats` | +| 3.6 | 0.3% | `tests/doctor-race.bats` | +| 3.6 | 0.3% | `tests/verify.bats` | +| 3.6 | 0.3% | `tests/pre-commit-staging.bats` | +| 3.5 | 0.3% | `tests/issue-read-check.bats` | +| 3.5 | 0.3% | `tests/board-sweep.bats` | +| 3.4 | 0.3% | `tests/target-ensure.bats` | +| 3.0 | 0.2% | `tests/landed-check.bats` | +| 2.9 | 0.2% | `tests/spec-ref-check.bats` | +| 2.9 | 0.2% | `tests/issue-read-guard.bats` | +| 2.8 | 0.2% | `tests/ready-cites-check.bats` | +| 2.5 | 0.2% | `tests/skill-check.bats` | +| 2.4 | 0.2% | `tests/timeout-drift.bats` | +| 2.4 | 0.2% | `tests/suite-select.bats` | +| 2.3 | 0.2% | `tests/closing-key-check.bats` | +| 2.3 | 0.2% | `tests/fanout-guard.bats` | +| 2.3 | 0.2% | `tests/signing-posture.bats` | +| 2.2 | 0.2% | `tests/finding-sink-check.bats` | +| 2.0 | 0.2% | `tests/claim-race-check.bats` | +| 2.0 | 0.2% | `tests/bot-issue.bats` | +| 1.8 | 0.2% | `tests/issue-search-guard.bats` | +| 1.8 | 0.2% | `tests/reclaim-census.bats` | +| 1.7 | 0.1% | `tests/ci-tools-check.bats` | +| 1.7 | 0.1% | `tests/claimed-keys.bats` | +| 1.6 | 0.1% | `tests/run-shape.bats` | +| 1.6 | 0.1% | `tests/memories-check.bats` | +| 1.6 | 0.1% | `tests/ci-slow-needed.bats` | +| 1.6 | 0.1% | `tests/ci-lease-precondition.bats` | +| 1.6 | 0.1% | `tests/ready-lint-deferral.bats` | +| 1.6 | 0.1% | `tests/target-prune.bats` | +| 1.5 | 0.1% | `tests/install-check.bats` | +| 1.5 | 0.1% | `tests/mutant-census.bats` | +| 1.5 | 0.1% | `tests/spawn-census.bats` | +| 1.5 | 0.1% | `tests/awk-regex-check.bats` | +| 1.5 | 0.1% | `tests/alive.bats` | +| 1.4 | 0.1% | `tests/land-divergence-assert.bats` | +| 1.4 | 0.1% | `tests/nonverdict-scan.bats` | +| 1.4 | 0.1% | `tests/deferral-check.bats` | +| 1.2 | 0.1% | `tests/perf-record.bats` | +| 1.2 | 0.1% | `tests/linear-check.bats` | +| 1.2 | 0.1% | `tests/rules-drift.bats` | +| 1.2 | 0.1% | `tests/done-check.bats` | +| 1.1 | 0.1% | `tests/ntia-check.bats` | +| 1.1 | 0.1% | `tests/verified.bats` | +| 1.0 | 0.1% | `tests/transcript-corpus-check.bats` | +| 1.0 | 0.1% | `tests/attestation-check.bats` | +| 1.0 | 0.1% | `tests/release-tracking-check.bats` | +| 1.0 | 0.1% | `tests/perf-assert.bats` | +| 1.0 | 0.1% | `tests/hook-pin-check.bats` | +| 1.0 | 0.1% | `tests/install.bats` | +| 1.0 | 0.1% | `tests/gh-guard.bats` | +| 1.0 | 0.1% | `tests/checks-green.bats` | +| 1.0 | 0.1% | `tests/prose-only-check.bats` | +| 1.0 | 0.1% | `tests/render-cli.bats` | +| 1.0 | 0.1% | `tests/pr-unsubscribed.bats` | +| 0.9 | 0.1% | `tests/done-pr-check.bats` | +| 0.9 | 0.1% | `tests/issue-search-check.bats` | +| 0.9 | 0.1% | `tests/sbom-binary.bats` | +| 0.9 | 0.1% | `tests/module-map-check.bats` | +| 0.9 | 0.1% | `tests/doctor.bats` | +| 0.8 | 0.1% | `tests/stop-posture-check.bats` | +| 0.8 | 0.1% | `tests/timeout-check.bats` | +| 0.8 | 0.1% | `tests/mcp-attach-check.bats` | +| 0.8 | 0.1% | `tests/mcp-timeout-budget.bats` | +| 0.8 | 0.1% | `tests/evaluator-closure-check.bats` | +| 0.8 | 0.1% | `tests/hook-matcher-check.bats` | +| 0.7 | 0.1% | `tests/perf-compare.bats` | +| 0.7 | 0.1% | `tests/merged-pr-keys.bats` | +| 0.7 | 0.1% | `tests/hook-profile-check.bats` | +| 0.7 | 0.1% | `tests/checksums.bats` | +| 0.6 | 0.1% | `tests/connector-verb-guard.bats` | +| 0.6 | 0.1% | `tests/macos-link-check.bats` | +| 0.6 | 0.0% | `tests/publish-credential-check.bats` | +| 0.6 | 0.0% | `tests/land-lock-check.bats` | +| 0.6 | 0.0% | `tests/sonar-gate.bats` | +| 0.6 | 0.0% | `tests/serena-mcp.bats` | +| 0.5 | 0.0% | `tests/abandon-matrix.bats` | +| 0.5 | 0.0% | `tests/pipefail-grep-check.bats` | +| 0.5 | 0.0% | `tests/pkl-check.bats` | +| 0.5 | 0.0% | `tests/digest-major-agreement.bats` | +| 0.5 | 0.0% | `tests/branch-age-check.bats` | +| 0.5 | 0.0% | `tests/report-only-check.bats` | +| 0.5 | 0.0% | `tests/msrv-pin-agreement.bats` | +| 0.5 | 0.0% | `tests/token-bench-check.bats` | +| 0.5 | 0.0% | `tests/run-shape-guard-quoting.bats` | +| 0.5 | 0.0% | `tests/connector-allow-guard.bats` | +| 0.4 | 0.0% | `tests/release-due.bats` | +| 0.4 | 0.0% | `tests/suite-bench-check.bats` | +| 0.4 | 0.0% | `tests/license-table-check.bats` | +| 0.4 | 0.0% | `tests/no-doctests.bats` | +| 0.4 | 0.0% | `tests/batten-glob-check.bats` | +| 0.4 | 0.0% | `tests/board-payloads.bats` | +| 0.4 | 0.0% | `tests/nonverdict-assert.bats` | +| 0.4 | 0.0% | `tests/cap-drift.bats` | +| 0.4 | 0.0% | `tests/task-fail-closed.bats` | +| 0.4 | 0.0% | `tests/container-preflight.bats` | +| 0.3 | 0.0% | `tests/ci-drift.bats` | | 0.3 | 0.0% | `tests/connector-allow-resolve.bats` | -| 0.3 | 0.0% | `tests/container-preflight.bats` | -| 0.2 | 0.0% | `tests/batten-glob-check.bats` | -| 0.2 | 0.0% | `tests/mise-pin-agreement.bats` | -| 0.2 | 0.0% | `tests/coderabbit-config-check.bats` | -| 0.2 | 0.0% | `tests/rust-paths-check.bats` | -| 0.2 | 0.0% | `tests/git-hook.bats` | -| 0.2 | 0.0% | `tests/mise-action-floor.bats` | -| 0.1 | 0.0% | `tests/dist.bats` | -| 0.1 | 0.0% | `tests/task-fail-closed.bats` | -| 0.1 | 0.0% | `tests/test-bats-parallel.bats` | -| 0.1 | 0.0% | `tests/perf-pair.bats` | -| 0.1 | 0.0% | `tests/egress-check.bats` | +| 0.3 | 0.0% | `tests/rust-paths-check.bats` | +| 0.3 | 0.0% | `tests/commit-attribution.bats` | +| 0.3 | 0.0% | `tests/mise-pin-agreement.bats` | +| 0.3 | 0.0% | `tests/test-bats-parallel.bats` | +| 0.3 | 0.0% | `tests/coderabbit-config-check.bats` | +| 0.3 | 0.0% | `tests/mise-action-floor.bats` | +| 0.3 | 0.0% | `tests/git-hook.bats` | +| 0.2 | 0.0% | `tests/perf-gate.bats` | +| 0.2 | 0.0% | `tests/dist.bats` | | 0.1 | 0.0% | `tests/evaluator-io-check.bats` | +| 0.1 | 0.0% | `tests/egress-check.bats` | +| 0.1 | 0.0% | `tests/perf-pair.bats` | +| 0.1 | 0.0% | `tests/zizmor-split.bats` | | 0.1 | 0.0% | `tests/darwin-link.bats` | | 0.1 | 0.0% | `tests/cross-check.bats` | -| 0.0 | 0.0% | `tests/zizmor-split.bats` |