Skip to content

fix(canary): freeze the marker pair, drop the unsound version relation - #5303

Open
sanity wants to merge 20 commits into
mainfrom
fix/marker-pin-self-limiting
Open

fix(canary): freeze the marker pair, drop the unsound version relation#5303
sanity wants to merge 20 commits into
mainfrom
fix/marker-pin-self-limiting

Conversation

@sanity

@sanity sanity commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Problem

Two bugs in the release auto-update canary, one of which would have blocked the 0.2.126 release.

MARKER_LATEST_SEEN_SINCE names the first release whose binary emits Startup update check: GitHub reports latest release. Gate B skips its positive equality check for any previous release below that value.

Bug 1 — a self-detonating pin. The self-test added in #5290 asserted the constant was not below the crate version, and stated its own premise in its comment: "the marker is new in this tree, so no already-published release emits it." That premise was true only while 0.2.125 was unpublished. It expired at 00:11Z on 2026-08-13 when 0.2.125 shipped. The constant correctly stays at 0.2.125 forever, so the pin goes red on the next bump and blocks a healthy release. Reproduced: set the crate version to 0.2.126 against main and the pin fails.

Bug 2 — a silent one, found during review. Reword the marker text in every place a developer must touch it (auto_update.rs, freenet.rs, auto-update-canary.sh, and the log fixtures in both test files) and leave the constant alone: the entire suite passes. Nothing prompts anyone. A stale constant is the default outcome of a reword, not an escape from a warning. The consequence lands post-publish, where Gate B arms, greps for text the previous release never emitted, and trips the release Matrix alarm with a message blaming the node.

Solution

Delete the version relation entirely, and freeze the pair.

Three attempts at a relation between the constant and the crate version were considered, and the conclusion is that no fixed relation between two quantities on different clocks survives both publication and a reword:

relation correct when wrong when
constant >= crate (shipped in #5290) before publication after publication — blocks 0.2.126
constant <= crate (first attempt here) after publication during a reword — red for the correct value
constant <= crate + 1 (proposed in review) see correction below

Correction, found by a later reviewer: an earlier revision of this table claimed <= crate + 1 "also fires on a reword", and that is arithmetically false. Under a reword the correct constant is crate+1, and crate+1 <= crate+1 passes. The claim entered this description because it was relayed from a reviewer who withdrew their own suggestion on that reasoning; nobody checked the arithmetic. A loose upper bound of that shape appears to be phase-independent — it is violated only when the constant is more than one release ahead, which is never legitimate — so it is being re-evaluated as plausibility bounding alongside the freeze rather than as a replacement for it.

The two equality-style relations are what cannot work: the constant tracks release history and the crate version tracks this tree, so no relation asserting they stay in step survives both publication and a reword. What remains is:

  1. The value frozen against its historical literal 0.2.125. This catches strictly more than the relation ever did, including the empty string and forward creep at release time.
  2. The marker text frozen alongside it, so a reword forces the author to confront the version constant in the same edit. This is what closes Bug 2.
  3. A format guard on version_at_least, which previously accepted garbage as a pass: version_at_least "not-a-version" "0.2.125" returned true.

Why the marker freeze is base64-encoded

Written first as a plain literal, and it did not work. The mutation it exists to catch is a rename sweep — and the sweep rewrote the frozen expectation too, because the expectation was byte-identical to the value it guarded. The pin followed the rename and the suite stayed green.

Encoded, a text sweep cannot reach it, and a deliberate reword has to regenerate it, which is exactly the moment of attention the pin exists to create. The failure message prints the decoded old text, the new text, and the re-encode command; an empty decode fails loudly rather than making the comparison vacuous.

The version half stays plaintext deliberately, and the asymmetry is documented: a repo-wide sed on a version string is not how bumps happen here (they touch Cargo.toml and the lockfile), so the realistic wrong edit is a human raising it in one place, which a plain compare catches.

Also

  • gate_b_arm_case "0.2.124" no closes a verified vacuity hole: hardcoding the threshold inside the decision previously left the whole suite green, because the existing cases straddled the gap.
  • The frozen pin's failure message no longer reads as instructions for the creep edit that disarms Gate B.
  • The operator-facing comment regains its reword escape hatch, under a heading naming when the constant should and should not move.
  • The retraction-valve reason is written into the constant's comment, so the next reader does not conclude the skip branch is dead code. prev_version is the newest currently-published release, so a retraction legitimately puts it back below the constant.

Testing

Suite is 68 assertions, all green. Every pin was mutation-tested by applying the exact regression it names to a committed tree and confirming it goes red.

Mutation Result
crate → 0.2.126 (the landmine) main's pin FAILS, reproduced. This HEAD green. Defused.
marker reword sweep, constant left stale plaintext freeze: GREEN — the bug above. base64 freeze: RED, naming both strings
constant → 0.2.126 RED via the freeze (which catches strictly more than the deleted relation)
version.workspace = true in Cargo.toml green — the second spurious fire of the deleted relation, now gone
format guard removed 4 RED: not-a-version >= 0.2.125, 0.2.125 >= '', '' >= '', 0.2.125-rc.1 >= 0.2.125 all wrongly reported yes
threshold hardcoded to 0.2.124 in the decision RED (was green before the new case)
prev_emits_latest_seen made to skip on malformed input 2 RED

The refuse-vs-arm asymmetry is deliberate and both directions are pinned: version_at_least refuses malformed input because "is a >= b" has no answer for garbage, while prev_emits_latest_seen arms Gate B on it because "should Gate B assert anything" does, and skipping is the silent direction.

shellcheck -x clean; the exact CI shellcheck invocation rc=0; cargo fmt --all --check clean. No Rust files changed.

The ci(rule-lint) commits

Scope beyond the fix, called out as a deliberate exception to one-logical-change: without it the Rule Lint check rejects this PR for having no regression test, and test-exempt would put a false statement in the record on a PR that demonstrably has tests.

"ok is tightened to ["']ok[[:space:]]+- and pass is added. Measured on a fixture: echo "okay, the suite is starting" and bare echo "ok" are now rejected; single-quoted echo 'ok - ...' and pass "..." are now accepted.

An honest limit, now stated in the CI comment itself: this check has never recognised assertions. It recognises the success-reporting line by prefix. check "x" "a" "a" is a real call to a real helper that compares a value with itself and asserts nothing, and the old pattern already matched it. A shared assertion helper would make it tidier without making it non-decorative. Whether something is a real regression test belongs to review and mutation testing.

Two false negatives are left in deliberately and documented rather than chased with a fifth pattern: printf-style reporting, and an assertion whose only added line is its FAIL branch (counting FAIL would let a PR score by adding an error message alone).

Corrections to earlier revisions of this description

Kept visible rather than silently edited:

  • An earlier version said the suite was "32/32". It was 61 at that point and is 68 now.
  • An earlier version, and the comment added to ci.yml, claimed auto-update-canary_test.sh "has no helper at all". False — check() is at line 55 with 12 call sites. It is specialised to log-fixture cases; the file's other assertions have nothing to fixture and are written as bare echoes.
  • An earlier version proposed constant <= crate version as the fix. That is the approach this HEAD deletes, for the reason in the table above.

Fixes

Refs #5290. Prevents a self-inflicted block on the 0.2.126 release, and closes the silent reword path.

[AI-assisted - Claude]

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Rule Review: Scope creep — PR bundles multiple unrelated logical changes under a "fix" title

Rules checked: git-workflow.md
Files reviewed: 9

Warnings

  • .github/workflows/cross-compile.yml:317-434, scripts/auto-update-canary.sh:898-1186, docs/RELEASING.md:448-548 — This is a new "environmental classification" feature (new EXIT_UNVERIFIED_ENVIRONMENTAL=75 exit code, a GitHub-reachability corroboration probe runner_can_reach_github, a per-attempt retry loop for Gate B that didn't exist before, and a split of the notify job into two conditional steps) bundled into a PR titled "freeze the marker pair, drop the unsound version relation." This is a substantial behavior change to release-CI infrastructure, not a marker freeze. (rule: git-workflow.md "Does the PR bundle more than one logical change? → NO single focus: split it.")
  • .github/workflows/ci.yml:167-309, scripts/rule_lint_shell_assertion_counter_test.sh (new file, 262 lines) — Widening the Rule Lint shell-assertion-counter regex and adding its own dedicated regression-test file is an independent concern from the canary marker freeze named in the PR title; it has its own rationale, its own bug history ("wrong three times"), and no dependency on the marker-freeze change. (rule: git-workflow.md, same scope-creep clause)
  • scripts/auto-update-canary.sh:704-751 (sanitise_positive_int) and scripts/release_canary_wiring_test.sh:58-192 (yaml_job_block helper extraction) — Two more self-contained refactors riding along with the title's stated fix, each justified on its own but unrelated to marker freezing. (rule: git-workflow.md, same scope-creep clause)
  • Commit history (commits.txt) confirms the breadth: 20 commits spanning fix(canary), test(canary), docs(canary), and ci(rule-lint)/ci: scopes touch at minimum five distinct concerns (marker freeze, version-relation fix, environmental classification, Rule Lint counter, YAML-extraction refactor). AGENTS.md's contribution-scope policy (echoed in git-workflow.md) treats an accreted focused change as "grounds for closing the PR" — this PR is at meaningful risk of that read even though each individual commit is well-justified in isolation.

Info

(none beyond the above)


Rule review against .claude/rules/. WARNING findings block merge. ⚠️ 4 warning(s) — fix or add review-override label

@sanity sanity left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comprehensive PR Review: #5303

Summary

  • PR Title: fix(canary): pin MARKER_LATEST_SEEN_SINCE below the crate version, not above
  • Type: fix
  • CI Status: pending at time of review
  • Review tier: Full (touches .github/workflows/ci.yml — deploy/release/CI config is a listed high-risk surface)
  • HEAD SHA reviewed: da1b3a44c851baaa118ac9ba78d2d5403dfda178
  • Reviewers run: code-first, testing, skeptical, big-picture, plus a fifth lens specific to this change's failure mode (pin correctness: can it fail, will it fire spuriously). External models not run — opt-in only per the current review rule, so a fifth Claude lens was substituted rather than waiting on quota.

Verdict: Needs Changes — Re-review Required After Fix.

The PR's central claim is verified independently, by execution, by three separate reviewers: with the crate at 0.2.126, main's pin goes red (MARKER_LATEST_SEEN_SINCE (0.2.125) is BELOW the crate) and the PR's stays green. The landmine is real and the direction of the fix is right. The frozen fact was also checked against ground truth — the published v0.2.125 musl asset was downloaded and strings finds the marker.

But the review found that the replacement pin reintroduces the same defect class with the sign flipped, and that is blocking.


The blocking finding: no static relation is correct in both phases

main's pin (constant >= crate) was correct before publication and wrong after. This PR's pin (constant <= crate) is correct after publication and wrong during a marker reword — which the code explicitly anticipates (auto_update.rs and the source pin at _test.sh:864 both say "do not reword without updating both").

On a normal dev tree the crate version equals the last published release; the bump happens inside the release commit. So a marker reworded on main today first ships in crate+1. Then:

  • constant = crate+1 (the correct value) → relation pin RED, blocks a healthy release
  • constant = crate (green) → Gate B demands the NEW text from the published previous binary, which emits the OLD text → Gate B goes spuriously red post-publish, on the Matrix channel cross-compile.yml:764 alerts

Green test means broken canary; correct constant means red test. Verified by execution in an isolated worktree.

The conclusion is not to tune the inequality — it is that no static relation survives both phases. Two reviewers independently proposed relation tweaks (<= crate+1, and a conditional form); the lens that tested them withdrew its own suggestion after confirming it also fires on a reword.

Fix: delete the relation pin, keep the frozen literal. The frozen literal is phase-independent: it pins a value and forces a deliberate co-edit for any change, reword included. Deleting it also removes a second spurious fire found in testing: version.workspace = true in crates/core/Cargo.toml makes the relation pin fail with "could not read the crate version".


Must Fix (Blocking)

  1. Delete the constant <= crate version relation pin (scripts/auto-update-canary_test.sh:775), keep the frozen literal. Reason above.
  2. Add gate_b_arm_case "0.2.124" no (_test.sh:~479). Verified vacuity hole: replacing the decision body with a hardcoded version_at_least "$1" "0.2.124" leaves the entire suite green — nothing pins that the decision reads the constant rather than a literal. The existing cases straddle the gap (0.2.123 no / 0.2.125 yes), so any threshold in {0.2.124, 0.2.125} satisfies all of them. Verified both ways: red against the hardcode, green against real code, and it survives a legitimate reword so it adds no spurious-fire surface. Also correct the annotation "the one release that must skip" — two releases skip now.
  3. Reword the frozen pin's failure message (_test.sh:817-819). It currently instructs the reader to "change the constant and this expectation together, in one commit" — which is exactly the creep edit that disarms Gate B. Verified: constant + frozen literal + crate all moved to 0.2.126 leaves the suite fully green while Gate B skips its positive check (runtime-confirmed: constant=0.2.126, prev=0.2.125 -> SKIP). The failure message should not read as instructions for the failure mode.
  4. Restore the reword escape hatch in the operator-facing comment (scripts/auto-update-canary.sh:173-175). This PR narrowed it, from "…or this constant is set one release too early (bump it)" to "…not a reason to touch this constant". Under a reword, touching the constant is the correct response, and an on-call reader debugging a red Gate B lands here, not in the test file.
  5. Fix a factual error in the CI comment (.github/workflows/ci.yml:436): "auto-update-canary_test.sh has no helper at all" is false. check() is defined at _test.sh:55 with 12 call sites. Accurate statement: check() is specialised to log-fixture cases and the file's other assertions are bare echoes. Same error is in the PR body and will be corrected there too.
  6. Update the user-facing error text (ci.yml:448). It still says "an added 'check' assertion", naming one of three accepted forms, so a developer who trips the gate is told to add the wrong thing. This is the same misleading message that sent this PR's author down the wrong path.
  7. Tighten the new regex from "ok to "ok[[:space:]]+- (ci.yml:443). Unanchored, it counts echo "okay, the suite is starting" and bare echo "ok" as regression tests. Costs nothing: all 30 echo "ok sites in the repo use the ok - form.

Should Fix

  1. Add pass to the regex alternation. One word, and it closes four more shell self-tests that currently score zero (release_state_restore_test.sh, release_canary_wiring_test.sh, release-agent/deploy-local-gateway_test.sh, release-agent/gateway-auto-update_test.sh). The PR predicts a "fourth pattern" and defers it; this is the cheap part of that set.
  2. Reconcile contradictory adjacent sentences in auto-update-canary.sh:169-182: "makes this self-retiring" now sits beside "FROZEN … release history does not change".
  3. PR body says "Full canary suite 32/32"; the suite has 61 assertions. The body's weight rests on exact evidence, so the number should be right.

Consider / Follow-ups

  • Reconciling two reviewers who disagreed. Big-picture argues the version gate is now dead code (once 0.2.125 is published, prev >= constant always, so the skip branch is unreachable) and should be deleted, making Gate B's positive assertion unconditional. That is appealing but not safe as stated, and the blocking finding is why: the constant is precisely the mechanism that makes a marker reword survivable. Delete it and a reword release fails Gate B with no way to express "the previous binary legitimately emits different text". Recommend keeping the constant; if the gate is revisited, handle the reword case explicitly (e.g. the canary accepting the previous release's marker text).
  • One shared assertion helper or marker convention across all shell self-tests, replacing the regex-widening cycle. This is the third widening; two further unmatched idioms already exist.
  • Add the self-detonating-pin class to .claude/rules/bug-prevention-patterns.md. "A pin whose premise is this release is not yet published detonates the moment it publishes" generalises beyond this file, and right now the lesson lives only in a comment inside one test file. Also list the MARKER_LATEST_SEEN_SINCE pins in the enumeration at :153-157.
  • For whoever cuts 0.2.126: Gate B's positive equality check has never armed in production (it was skipped for 0.2.125 because prev was 0.2.123). 0.2.126 is the first release where it runs for real, so a previously-unexercised path goes live at release time.

What holds up

  • The landmine is real, reproduced independently three times.
  • The frozen literal does the load-bearing work: forward creep at release time is invisible to the relation pin and caught only by the freeze.
  • The "two pins are complementary" claim survived a deliberate attempt to refute it (one reviewer reported the failed refutation, which is the right thing to do).
  • Neighbouring untouched pins were audited and are live, each reddened by mutating its subject. Every whole-file-grep marker has its only hits in production code above the #[cfg(test)] boundary, so none is currently satisfiable by a comment or test block.
  • shellcheck -x clean; suite 61/61 green at HEAD.

Process note

The shared review worktree was briefly dirty with one lens's mutation edits while others were reading it — my error in co-locating them, corrected mid-review. Three reviewers noticed independently and re-ran against clean git archive exports, and both worktrees were verified clean afterwards. Findings above were taken from those clean runs. Flagging it because it could have produced phantom findings and did not only because the reviewers caught it.

[AI-assisted - Claude]

@sanity

sanity commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Review addendum: two corrections to my synthesis above, and one new finding

The fifth lens reported after I posted. It corrects the consolidated review in two places, both in the direction of "the problem is worse than stated", and adds a finding none of the other four had. Recording it here rather than editing the review above, so the correction is visible.

Correction 1 — the reword failure is SILENT, not a "tempting escape"

My review described a developer facing a red pin and taking a bad escape. That framing is wrong, and the truth is worse.

Verified by rewording MARKER_LATEST_SEEN in the complete set of places a developer must touch (auto_update.rs, freenet.rs, auto-update-canary.sh, and the log fixtures in both test files) while leaving the constant alone: the suite passes completely. All auto-update-canary assertions passed, lifecycle green, zero red.

So nobody is tempted, because nobody is prompted. A stale constant is the DEFAULT outcome of a reword, not an escape from a warning. The source pin at _test.sh:864 interpolates $MARKER_LATEST_SEEN, so it follows the rename by construction; the relation check and the freeze never look at the marker at all.

Severity also shifts. The old landmine fired in CI on the bump PR: cheap, pre-publish, with a message naming the constant. This one fires in the post-publish canary (auto-update-selfupdate-canary has needs: attach-to-release), trips the Matrix alarm at cross-compile.yml:764, and the alarm text blames the node — "a node on the previous release may not be able to auto-update to this one". Same defect class, strictly later and more expensive, and misdiagnosing.

This changes the recommended fix. In addition to deleting the relation pin, freeze the PAIR: add MARKER_LATEST_SEEN_FROZEN='Startup update check: GitHub reports latest release' alongside the version freeze at _test.sh:809. A reword then forces the author to confront the version constant in the same edit, which is the only thing that closes the silent path. This supersedes item 1 of my review as the highest-value change.

Correction 2 — the "delete the version gate" argument is refuted, and my reason was the weaker one

I argued deletion was unsafe because the constant is what makes a reword survivable. That holds, but there is a stronger and independent reason I missed.

prev_version is not "the previous version". It is .[0] of releases?per_page=30 filtered draft==false and prerelease==false (cross-compile.yml:718-720) — the newest currently-published release. Releases in this repo demonstrably do fail to publish: v0.2.124 is draft=true, published=null on the live API right now, which is the entire reason this PR exists.

So if 0.2.125 were ever retracted or re-drafted, prev_version becomes v0.2.123, and the skip branch correctly fires because 0.2.123 genuinely does not emit the line. The branch is a live safety valve for release retraction. Deleting it converts a retraction into a false fleet alarm.

Big-picture is right that the branch is dormant and wrong that it is dead. Dormant-but-correct is what a valve looks like.

New finding — version_at_least accepts garbage as a pass

scripts/auto-update-canary.sh:780, verified against GNU coreutils 9.4:

  • version_at_least "not-a-version" "0.2.125"TRUE. A non-empty malformed crate version silently passes; the -z guard at :772 covers empty only.
  • version_at_least "0.2.125" ""TRUE. An emptied constant passes the relation check.
  • "0.2.125-rc.1" >= "0.2.125" → TRUE, which contradicts the docstring's "semver" (pre-releases sort below in semver, above under sort -V). No practical impact; this repo cuts no rc tags.

A one-line format check on crate_version turns the first case from a silent pass into a loud failure. Worth doing regardless of the relation pin's fate, since version_at_least has other callers.

Also worth adding to item 7 (the regex)

The false-negative half was not covered above. Verified: echo 'ok - …' (single quotes) → 0. printf 'ok - …' → 0. echo "FAIL - …" >&2 → 0. So the matcher misses the branch that actually carries the assertion, and pins the spelling of one convention. That is the "pin the invariant, not the spelling of one violation" shape from .claude/rules/browser-assets.md rule 2 — in a gate whose entire history is that it cannot see its subject.

Revised recommendation

Unchanged verdict (Needs Changes — Re-review Required After Fix), with the must-fix list amended:

  • 1a (new, highest value). Freeze the marker string alongside the version.
  • 1b. Delete the relation pin, keep the freeze. Reinforced: the relation catches only the strictly-above-crate class, while the freeze catches everything else including the empty string and the most-likely-wrong value. The comment block at :750-771 presents this backwards.
  • New. Add a format check on crate_version.
  • Items 2-7 stand.
  • The "delete the version gate" follow-up is withdrawn, on the retraction-valve ground.

[AI-assisted - Claude]

sanity added a commit that referenced this pull request Aug 13, 2026
…gether

Addresses the Full-tier review on #5303. The relation pin this PR added was
wrong in the same way `main`'s was, with the sign flipped, and the review found
a silent failure neither pin could see.

DELETE THE RELATION PIN (review 1b). Two have now been tried and both were
correct in one phase and wrong in another. `constant >= crate` (#5290) rested on
"no published release emits the marker yet" and detonated the moment 0.2.125
published. `constant <= crate` is right after publication and wrong during a
marker REWORD: a marker reworded on main first ships in crate+1, so the correct
constant is crate+1, which the pin calls a failure -- while the value that makes
it green makes Gate B demand the new text from a published binary emitting the
old one, i.e. a post-publish red canary and a Matrix alarm blaming the node.
A `<= crate+1` variant fires on a reword too; the reviewer who proposed it
withdrew it after testing.

The generalisation, recorded in the comment so a third attempt is not made: the
constant tracks RELEASE HISTORY and the crate version tracks THIS TREE, and no
fixed relation between two quantities on different clocks holds across both
publication and reword. The freeze is phase-independent and covers strictly
more -- the relation only ever caught "strictly above crate version", while the
freeze catches every wrong value including the empty string and the most likely
wrong one. It also cannot be tripped by `version.workspace = true`, which broke
the relation pin's Cargo.toml read.

FREEZE THE PAIR (review 1a, the highest-value change). Reword
MARKER_LATEST_SEEN everywhere a developer must touch it and leave the constant
alone, and the whole suite passes -- verified, zero red. The source pin
interpolates `$MARKER_LATEST_SEEN`, so it follows the rename by construction,
and nothing else looks at the marker. A stale constant is the DEFAULT outcome of
a reword rather than an escape from a warning. `MARKER_LATEST_SEEN_FROZEN` now
sits beside the version freeze, so a reword fails in the same edit with the
version constant named.

version_at_least ACCEPTS GARBAGE (review, new finding). Measured: `"not-a-version"
>= "0.2.125"` was TRUE (non-numeric sorts after digits under `sort -V`) and
`"0.2.125" >= ""` was TRUE. Both now refuse, loudly, via `is_dotted_version`.
Pre-release tags are refused rather than ordered, because `sort -V` puts
`0.2.125-rc.1` above `0.2.125` where semver puts it below.

`prev_emits_latest_seen` deliberately goes the OTHER way on malformed input: it
ARMS Gate B rather than skipping. The two functions answer different questions.
"Is a >= b" has no answer for garbage, so the comparator refuses; "should Gate B
assert anything" does, and skipping is the silent direction this file exists to
remove.

Also from the review:
  2. `gate_b_arm_case "0.2.124" no`, plus the corrected annotation (two releases
     skip, not one). Verified vacuity hole: the old cases straddled the gap, so
     hardcoding the threshold to 0.2.124 inside the decision left the entire
     suite green.
  3. The frozen pin's failure message no longer reads as instructions for the
     creep edit that disarms Gate B. It now says the fix is almost certainly to
     put the value back, and points at the reword assertion above it.
  4. The operator comment in auto-update-canary.sh regains the reword escape
     hatch this PR had narrowed away, and no longer says "self-retiring" beside
     "FROZEN" (review 9).
  6. The Rule Lint error text named `check`, one of four accepted forms, so a
     developer tripping the gate was told to add the wrong thing. It now lists
     all four and names the two known blind spots.
  7. `"ok` tightened to `["']ok[[:space:]]+-`, and `pass` added. Measured on a
     fixture of eight lines: `echo "okay, the suite is starting"` and a bare
     `echo "ok"` no longer count, while single-quoted `echo 'ok - ...'` and the
     `pass` form now do (four more self-tests stop scoring zero). This PR's own
     diff still scores 2 against origin/main.
@sanity sanity changed the title fix(canary): pin MARKER_LATEST_SEEN_SINCE below the crate version, not above fix(canary): freeze the observed-latest marker and its release Aug 13, 2026
@sanity sanity changed the title fix(canary): freeze the observed-latest marker and its release fix(canary): freeze the marker pair, drop the unsound version relation Aug 13, 2026

@sanity sanity left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: #5303 at 98196ec69

Mandatory re-review after seven blocking findings were addressed. Four independent lenses, each in its own isolated worktree this time (last round's shared checkout was briefly dirty with one lens's mutations, which three reviewers noticed and worked around).

  • Review tier: Full (CI config). External models opt-in only per the current rule; four Claude lenses with distinct adversarial angles ran instead.
  • HEAD reviewed: 98196ec69. main has not moved since the branch point, so this is the content that would merge.
  • Verdict: Needs Changes — Re-review Required After Fix.

What holds up, verified independently and repeatedly

Between them the lenses executed roughly forty mutations. The following were attacked and could not be broken:

  • The base64 marker freeze does what it claims. A sed rename sweep across all four files holding the marker goes RED. A plaintext expectation would have been rewritten by the same sweep — it rewrote three other occurrences in that very file — so the encoding is doing real work.
  • Decode failure is loud in every mode: empty, corrupt, deleted assignment, and a stubbed-out base64 binary all produce a clean FAIL via the -z guard, never a vacuous comparison. No set -e, so no mid-suite abort.
  • Deleting the relation pin lost nothing. The freeze reddens for constant 0.2.999, '', not-a-version, 0.2.100, 0.2.126 — every state the relation caught and several it did not. The self-detonation claim was ground-truthed: crate at 0.2.126 leaves this HEAD green and reddens main's pin.
  • The frozen value is true. The published v0.2.125 musl asset was downloaded and strings-grepped: the marker is present.
  • Every source-scrape pin passes the AGENTS.md bounded-region rule. preflight_body/selfupdate_body are awk-bounded with an explicit empty-body FAIL; pin_marker binds to the emitting macro call, not a whole-file grep; every scrape is cross-file. No bare unbounded-anchor pin exists in the file. That is a clean bill on this repo's most common defect.
  • All eight new assertions go red under the mutation each one names.

Must Fix

1. The "pair freeze" does not force the pair, and the escape is its own error message. Found independently by three lenses. Two-step mutation: (a) reword sweep → RED, correctly; (b) then run only the copy-pasteable command the FAIL hands you, regenerating the encoded blob, and leave MARKER_LATEST_SEEN_SINCE alone → suite fully green, constant stale. That is exactly the state the pin exists to prevent. The two frozen values are independent assertions; the coupling is prose sitting directly above a one-liner that silences the alarm without honouring it.

The generalisation, which is the most useful thing this review produced: a freeze forces a decision only if the remediation cannot be performed without making that decision. Fix: encode both values as one blob, so regenerating the expectation is impossible without re-stating the version.

2. The freeze protects one of six markers, and the unprotected set includes the silent direction. Gate B's assert_detection_healthy runs over the previous release's binary, so the cross-version hazard belongs to Gate B, not to one constant. Verified: the identical sweep applied to MARKER_PARSE_FAIL or MARKER_CHECK_RAN leaves the suite green.

Directions differ and one is much worse. A MARKER_CHECK_RAN reword makes Gate B red against a healthy release — loud, same harm class as the bug being fixed. A MARKER_PARSE_FAIL reword makes Gate B's negative check stop matching, so a genuinely broken previous release passes. Silent false-pass, and that is the #5221 signature this canary exists to catch. Freeze that one here (it needs no version constant); scope the comment honestly for the rest; follow-up issue for the others.

3. An unreadable constant now makes Gate B skip silently — a regression in the direction this PR is about. prev_emits_latest_seen guards $1 only, so an emptied or corrupt MARKER_LATEST_SEEN_SINCE falls through to version_at_least, which refuses, which skips. Verified: on main the same input armed. The comment's claim that an unusable version arms rather than skips is true only of $1. The resulting note reads "first emitted by v" with an empty version. Defence-in-depth only — CI's freeze catches a bad constant first — but it is the one input class the code claims to handle. One line: test both operands.

4. The CI comment claims coverage that does not exist. Measured: the widening fixes three of the four files it names. release_state_restore_test.sh reports echo "PASS [$name]" (uppercase) and scores 0 before and after. A gate whose own documentation asserts coverage it lacks is the defect class this PR is about.

Worse, the dominant blind spot is undocumented while a hypothetical one is: [[:space:]] after the alternation excludes every underscore-suffixed helper (gate_b_arm_case, version_ge_case, trigger_case, pin_marker, …), 29 of 63 sites in the canary test alone. 4 of this PR's own 8 new assertions score zero; its count of 2 comes entirely from the freeze block's echo lines. Meanwhile the documented printf false negative does not occur anywhere in the repo. Swap them, and add the helper-call-site pattern.

Should Fix

5. A load-bearing rationale claim is arithmetically false — and it was mine. The description claimed <= crate + 1 "also fires on a reword". It does not: under a reword the correct constant is crate+1, and crate+1 <= crate+1 passes. The claim entered this PR because I relayed a reviewer's withdrawal of their own suggestion without checking the arithmetic behind it. Corrected in the description.

This matters because that claim is the stated reason no bound of any kind survives, which leaves the "wrong-but-consistent value" case unguarded — both frozen and live values are edited by the same hand in the same edit. A pure upper bound appears phase-independent (violated only when the constant runs more than one release ahead, which is never legitimate). Given that three relations in this file have now been wrong, it is to be mutation-tested across four phases before being trusted, not asserted on reasoning.

6. gate_b_arm_case "0.2.124" does not pin what its comment says. Verified: hardcoding 0.2.124 → red, 0.2.126 → red, the correct literal 0.2.125 → green. It pins "the threshold is exactly 0.2.125", not "the decision reads the constant". Two subshell cases fix it, or downgrade the claim.

7. Three of five is_dotted_version refusal clauses are unpinned — deleting .*, *., *..* leaves the suite green.

Follow-ups

  • The structural closure for finding 1: a networked check that downloads the published v$MARKER_LATEST_SEEN_SINCE musl asset and greps it for $MARKER_LATEST_SEEN. This is the only assertion that cannot be satisfied by editing the test file — reword-and-regenerate leaves the published binary still emitting the old text. It was already performed by hand during this review; making it a gate is the structural version. Belongs beside the canary jobs in cross-compile.yml, since it needs network.
  • Freeze the remaining Gate B markers.
  • Add both lessons this PR earned to .claude/rules/bug-prevention-patterns.md: a pin on a relation between two quantities on different clocks is right in one phase and wrong in another; and an expectation stored as a plaintext copy of the value it guards is rewritten by the same edit that changes the value.
  • docs/RELEASING.md "If Gate B fails" goes straight to "ship a fix release, roll nodes by hand". This PR's subject is a Gate B failure that is not a fleet problem, and that is the page an operator reads at 1am.

Note on the process

This PR has now been wrong four times in the same way — the original pin, its first replacement, the plaintext freeze, and the pair coupling. Each was found by applying the mutation the previous round named, to the fix the previous round produced. That is the practice working rather than failing: two of the four were caught by the author mutation-testing their own new pin and reporting the failure unprompted. It is worth stating plainly that the code is a real improvement over main on every axis it targets at every one of those iterations.

[AI-assisted - Claude]

sanity added a commit that referenced this pull request Aug 13, 2026
…two claims

Re-review round 1 on #5303. Two of the six items were factual errors in my own
comments; the other four are the fixes they imply.

FALSE COVERAGE CLAIM (item 1). The blind-spot inventory in ci.yml named
`release_state_restore_test.sh` as reporting through `pass`. It does not -- it
reports `echo "PASS [$name]"` and scored ZERO under the new pattern. A false
claim of coverage inside a comment whose stated purpose is honest disclosure of
blind spots is worse than an ordinary comment error.

The alternation is now derived from an ENUMERATION of every `*_test.sh` under
scripts/, not from whichever file was in front of me: `check` (4 files),
`ok`/`bad`, `pass` (3 files), `echo "ok   - "` (34 sites, all 34 using that
exact separator, so anchoring on it costs nothing), and `echo "PASS [...]"`
(the last file scoring zero). Measured counts replace three hedged
approximations that read as measured: "~49 assertions" is 56, "~30 sites" is 34,
"four files use pass" is three.

FALSE REFUTATION (found in the PR body, propagated into my code comment).
I wrote that a `<= crate+1` relation "fires on a reword too". It does not:
during a reword the correct constant is crate+1, and crate+1 <= crate+1 passes.
The claim came from a reviewer who withdrew their own suggestion on that basis
and I repeated it without checking a single comparison. The comment now states
the arithmetic, scopes the "no relation works" conclusion to EQUALITY-STYLE
relations, and records that such a bound is deliberately omitted for redundancy
rather than unsoundness -- with an instruction not to re-cite the refuted claim.

FROZEN AS ONE BLOB. The two-adjacent-assertions form failed the remediation
path: reword goes red, then following the pin's own failure message (regenerate
the encoded text) goes green again with the version constant untouched, because
the message never asked about it. A freeze forces a decision only if the
remediation cannot be performed without making that decision. Both values are
now one base64 blob, so the recipe cannot be run without supplying a version.
The comment states the residual honestly: this makes the question unavoidable,
it does not verify the answer -- regenerating with the new text and the old
version still passes, measured.

Also:
  2. Pin that `prev_emits_latest_seen` READS the constant. Hardcoding the
     current value inside it left the whole suite green, freeze included,
     because the constant was untouched and the freeze had nothing to disagree
     with. It matters exactly when the constant is supposed to move.
  3. `base64 -w0` is GNU-only and fails on macOS, which is where someone
     reading this failure is most likely to be. The printed recipe now uses
     `base64 | tr -d '\n'`.
  4. The comment no longer implies the reword class is closed. Gate B greps
     four other markers against the PREVIOUS release's binary -- MARKER_DISABLED,
     MARKER_CHECK_RAN, MARKER_CHECK_COMPLETE, MARKER_TRIGGERED_RE -- and
     rewording any of them produces the same post-publish false alarm with a
     worse message. Named as residual, with the legitimate asymmetry (Gate A
     reads a binary built from this tree, so a reword there is self-consistent).
     Follow-up to be filed.
  5. `docs/RELEASING.md` "If Gate B fails" went straight to "ship a fix release,
     roll nodes by hand". This PR's whole subject is a Gate B failure that is
     NOT a fleet problem, and that page is what an operator reads at 1am. It now
     splits the two cases, gives the grep that distinguishes them, and warns
     that the Matrix alarm names the worse one.
     `.claude/rules/bug-prevention-patterns.md` gains the two lessons this PR
     earned: a relation pin between quantities on different clocks, and an
     expectation stored as a plaintext copy of the value it guards (with the
     remediation-path corollary).
  6. Narration compressed. The generalisations stay; the round-by-round history
     of this PR's own reviews goes. A recorded mutation RESULT is durable, a
     record of which review round produced it is not. The ci.yml block stated
     its blind-spot list twice; only the error string, which is what a
     contributor actually sees, keeps the full list.

The operator-facing comment in auto-update-canary.sh is deliberately NOT
compressed: it is the escape hatch item 4 of the previous round asked to
restore, and it is where an on-call reader lands.

@sanity sanity left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review round 3: #5303 at f360228d8

Three independent lenses, isolated worktrees. Verdict: Needs Changes. The round-2 defect is confirmed fixed; three new ones were found, and one behavioural harness closes most of them.

Confirmed fixed, by a reviewer other than the author

The round-5 defect — a single-value freeze that could be silenced by following its own recipe — holds now. A reviewer swept failed to parsecould not parse across all four files, watched it go red, then did exactly what the failure message says and nothing else. Still red, on the behavioural backstop. That is the property the previous four rounds all failed. The backstop was also checked for faithfulness against v0.2.121:auto_update.rs:1528 rather than assumed.

Must fix

1. The retry loop's environmental flag is last-writer-wins. A hung updater on attempt 1 (a real finding, as the code's own comment says) followed by a port collision on attempt 2 yields exit 75 and the quiet "This is NOT a stranded fleet and needs no fleet action." A false-quiet on the exact class the gate exists to raise. Latch the flag: once any attempt is a non-environmental indeterminate, the verdict is fault.

2. A real #5221 can reach the quiet message via a reorder nothing pins. As shipped it cannot — a log holding both markers returns rc=1 because the parse-fail branch precedes the fetch-fail branch. That ordering is the only thing making the split safe, and no fixture in the suite holds both markers. Swap the branches (an innocent "infra check before product check" tidy) and the same log classifies environmental. One check case closes it.

3. "Retry only the environmental case" is pinned by nothing. Mutating [ "$rc" -eq 2 ] || break to also retry rc=1 leaves all three suites green. Deterministic faults still fail, but an intermittent real fault becomes a pass — a regression arriving as a flaky pass through the canary's own retry logic.

4. The classifier is pinned by substring presence only. Flipping && to ||, or keeping the call and ignoring its result, leaves everything green while every indeterminate becomes environmental. This is the exact shape the round-2 prev_emits_latest_seen fix closed, reintroduced one function over — and the pin's own failure message names this hazard and cannot see it.

1–4 are all closed by one thing, which both lenses proposed independently: a behavioural driver for cmd_selfupdate (stub curl/tar/run_node_until_check, plant fixture logs, assert attempt count and returned code). release_wait_for_binaries_test.sh:211 is the in-repo precedent.

5. A port collision is reported as "could not reach GitHub" in both the ::error:: and the Matrix message. The node never started. Gate A kept a dedicated port-collision message; Gate B lost it this round. Carry the cause, not just the flag.

6. The new self-test is invisible to the counter it guards. 26 bare expect sites; the pattern requires expect_. So ci.yml's "329 of 329, zero false positives" is false at this HEAD, and the discipline it prescribes ("re-run the enumeration before adding a pattern") was not followed by the commit that prescribes it.

7. The Gate B wiring pin has a hole. exit "$rc" is pinned; || rc=$? is not. Changing the latter to || true — the reflex fix — leaves the suite green while Gate B reports success on a broken updater.

Should fix

  • CANARY_ATTEMPTS=00 passes the zero guard, the loop never runs, and rc is unbound → exit 1 → the loud alarm.
  • The 75 constant and the workflow's literal if [ "$rc" -eq 75 ] are coupled and unpinned.
  • The backstop's fixture yields rc=2, not the rc=0 its message claims, because it omits the completion line that a real #5221 log carries.
  • MARKER_FETCH_FAIL now feeds a classification decision, not just a diagnosis, so a reword silently disables the environmental path and sends every blip back to the loud alarm. Comment corrected here; the freeze goes to #5309 as its top item. (Deliberately not frozen in this PR: its reword direction is a loud false alarm, not a silent pass. MARKER_PARSE_FAIL was frozen here precisely because its direction is silent.)
  • The body describes roughly 40% less than the diff and is being rewritten.

Deliberately NOT doing

No behavioural backstop for MARKER_LATEST_SEEN. After a legitimate reword the canary is meant to stop reading old binaries and skip, so such a pin would fire on the correct remediation. Raised unprompted by a reviewer; agreed. Worth recording so the lesson from MARKER_PARSE_FAIL is not over-applied.

Verified sound — please do not re-litigate

The vacuity guard is layered and the layer that matters works (forcing the exact first-draft FRAG='' bug is caught by 11 negative cases). Zero false positives across all 54 new pattern hits repo-wide. The notify split survives step reorder, reformatting, condition widening, and output deletion; cancelled/empty/never-set all land loud. Exit 75 propagates end to end. Every version-gate pin reddens under its named mutation, and the 0.2.124 straddle is confirmed closed. A real #5221 cannot currently be misclassified (marker ordering verified with a log containing both).

One thing nobody can test locally

Whether GitHub populates needs.<job>.outputs for a failed job. It is load-bearing for the whole notify split. If it comes back empty, everything takes the loud branch — so it degrades safely — but it needs watching on the first real tag run, and that belongs in the body.


Five of this PR's defects have now been found inside the fixes for earlier ones. That is worth stating plainly rather than hiding: each was caught by applying the mutation the previous round named, and two were caught by the author against their own work before anyone asked. The code is a real improvement over main at every iteration.

[AI-assisted - Claude]

sanity and others added 20 commits August 13, 2026 02:03
…t above

The self-test added in #5290 asserted the constant was NOT BELOW the crate
version. Its premise was stated in its own comment -- "the marker is new in
this tree, so no already-published release emits it" -- and that premise
expired when 0.2.125 was published. The constant is a historical fact and
correctly stays at 0.2.125, so the pin would have gone red on the 0.2.126
bump and blocked a healthy release. Verified before the change by setting
the crate version to 0.2.126: the old pin failed.

The relation worth pinning is the opposite one, because only one direction
is silent. `prev_emits_latest_seen` compares the PREVIOUS release against
the constant, and the previous release is always below this tree's version,
so a constant set ABOVE the crate version can never arm: Gate B skips its
only positive assertion on every release and still reports success. At or
below, the check runs. So the assertion is now `constant <= crate version`,
which no legitimate release can trip.

That relation alone permits the constant to creep forward with each release,
which passes every assertion here and disarms Gate B every time. So the
value is also frozen against its historical literal, with the reason: it
records which published release first emitted the marker, and release
history does not change. Changing it takes a deliberate two-line edit.

Also drops the same-major.minor and +5-patch-window clauses, both of which
were expressions of the expired premise, and replaces the duplicated
`gate_b_arm_case "0.2.125"` (the constant's own value) with 0.2.126, which
is what "every release after it" was meant to cover.

Refs #5290
Third instance of the same gap. The fix:-PR test requirement scores shell
self-tests by counting added assertion lines, and it has now missed the
assertion form of each file it was pointed at in turn: first `check` only
(missed the lifecycle test's `ok`/`bad` helpers, #5290), now `check|ok`
(misses `auto-update-canary_test.sh`, which has no helper and writes each
assertion as a bare `echo "ok   - ..."`).

That file is the one guarding the release canary, so a PR whose entire
regression test is a new assertion there scores zero and is told to add a
test it has already added.

Measured on this branch's own diff with the exact CI pipeline:
  before: 0   after: 2

Refs #5290
The comment claimed the file "has no helper at all". False: `check()` is
defined at the top of it with 12 call sites. What is true is narrower --
`check()` takes a log fixture and drives `assert_detection_healthy`, so the
file's source pins and constant-relation pins have nothing to fixture and are
written as bare `echo "ok   - ..."` lines.

Also states plainly what the matcher does, having measured it: it recognises
the success-REPORTING line of an assertion, by prefix, and never recognised
assertions. A decorative `echo "ok - fine"` satisfies it -- but so did
`ok "..."` and a self-comparing `check "x" "a" "a"` under the previous
pattern. Measured on three assert-nothing lines: the old pattern matched 2 of
3, the new one 3 of 3. The widening adds a spelling, not a weakness. What
stops a decorative test is review and mutation-testing the pin, not this
regex.
…gether

Addresses the Full-tier review on #5303. The relation pin this PR added was
wrong in the same way `main`'s was, with the sign flipped, and the review found
a silent failure neither pin could see.

DELETE THE RELATION PIN (review 1b). Two have now been tried and both were
correct in one phase and wrong in another. `constant >= crate` (#5290) rested on
"no published release emits the marker yet" and detonated the moment 0.2.125
published. `constant <= crate` is right after publication and wrong during a
marker REWORD: a marker reworded on main first ships in crate+1, so the correct
constant is crate+1, which the pin calls a failure -- while the value that makes
it green makes Gate B demand the new text from a published binary emitting the
old one, i.e. a post-publish red canary and a Matrix alarm blaming the node.
A `<= crate+1` variant fires on a reword too; the reviewer who proposed it
withdrew it after testing.

The generalisation, recorded in the comment so a third attempt is not made: the
constant tracks RELEASE HISTORY and the crate version tracks THIS TREE, and no
fixed relation between two quantities on different clocks holds across both
publication and reword. The freeze is phase-independent and covers strictly
more -- the relation only ever caught "strictly above crate version", while the
freeze catches every wrong value including the empty string and the most likely
wrong one. It also cannot be tripped by `version.workspace = true`, which broke
the relation pin's Cargo.toml read.

FREEZE THE PAIR (review 1a, the highest-value change). Reword
MARKER_LATEST_SEEN everywhere a developer must touch it and leave the constant
alone, and the whole suite passes -- verified, zero red. The source pin
interpolates `$MARKER_LATEST_SEEN`, so it follows the rename by construction,
and nothing else looks at the marker. A stale constant is the DEFAULT outcome of
a reword rather than an escape from a warning. `MARKER_LATEST_SEEN_FROZEN` now
sits beside the version freeze, so a reword fails in the same edit with the
version constant named.

version_at_least ACCEPTS GARBAGE (review, new finding). Measured: `"not-a-version"
>= "0.2.125"` was TRUE (non-numeric sorts after digits under `sort -V`) and
`"0.2.125" >= ""` was TRUE. Both now refuse, loudly, via `is_dotted_version`.
Pre-release tags are refused rather than ordered, because `sort -V` puts
`0.2.125-rc.1` above `0.2.125` where semver puts it below.

`prev_emits_latest_seen` deliberately goes the OTHER way on malformed input: it
ARMS Gate B rather than skipping. The two functions answer different questions.
"Is a >= b" has no answer for garbage, so the comparator refuses; "should Gate B
assert anything" does, and skipping is the silent direction this file exists to
remove.

Also from the review:
  2. `gate_b_arm_case "0.2.124" no`, plus the corrected annotation (two releases
     skip, not one). Verified vacuity hole: the old cases straddled the gap, so
     hardcoding the threshold to 0.2.124 inside the decision left the entire
     suite green.
  3. The frozen pin's failure message no longer reads as instructions for the
     creep edit that disarms Gate B. It now says the fix is almost certainly to
     put the value back, and points at the reword assertion above it.
  4. The operator comment in auto-update-canary.sh regains the reword escape
     hatch this PR had narrowed away, and no longer says "self-retiring" beside
     "FROZEN" (review 9).
  6. The Rule Lint error text named `check`, one of four accepted forms, so a
     developer tripping the gate was told to add the wrong thing. It now lists
     all four and names the two known blind spots.
  7. `"ok` tightened to `["']ok[[:space:]]+-`, and `pass` added. Measured on a
     fixture of eight lines: `echo "okay, the suite is starting"` and a bare
     `echo "ok"` no longer count, while single-quoted `echo 'ok - ...'` and the
     `pass` form now do (four more self-tests stop scoring zero). This PR's own
     diff still scores 2 against origin/main.
…annot follow it

The marker freeze added in the previous commit did not survive its own mutation
test. A reword is done as a `sed` sweep over the files that mention the marker
-- which is exactly the mutation the review used -- and that sweep rewrote the
frozen EXPECTATION along with everything else. Suite green, marker reworded,
`MARKER_LATEST_SEEN_SINCE` stale: the silent path the freeze exists to close was
still open, one level further out.

This is the self-satisfying-pin class from `.claude/rules/bug-prevention-patterns.md`,
reached by a different route: not an anchor that slides, but an expectation
stored as text identical to the value under test, so any rename of that value
renames the expectation too. The source pin has the same property by
construction (it interpolates `$MARKER_LATEST_SEEN`), and this pin was added
specifically to compensate for that -- while sharing the weakness.

Stored base64 instead. A text sweep cannot reach it; a deliberate reword
regenerates it, which is the intended cost. The failure message prints the
decoded old text, the new text, and the exact command to re-encode. An empty
decode fails loudly rather than making the comparison vacuous.

The version half stays plain text, with the asymmetry documented: the edit that
would rewrite it is a repo-wide sed on a version string, and version bumps here
touch Cargo.toml and the lockfile rather than these scripts. The realistic wrong
edit for it is a human raising it deliberately, which a plain comparison catches.

Mutation-tested: reword the marker in auto_update.rs, freenet.rs, the canary and
both test files, leave the constant alone. Before this commit the suite passed
with zero red. After it, the marker assertion fails and names both the old and
new text.
…two claims

Re-review round 1 on #5303. Two of the six items were factual errors in my own
comments; the other four are the fixes they imply.

FALSE COVERAGE CLAIM (item 1). The blind-spot inventory in ci.yml named
`release_state_restore_test.sh` as reporting through `pass`. It does not -- it
reports `echo "PASS [$name]"` and scored ZERO under the new pattern. A false
claim of coverage inside a comment whose stated purpose is honest disclosure of
blind spots is worse than an ordinary comment error.

The alternation is now derived from an ENUMERATION of every `*_test.sh` under
scripts/, not from whichever file was in front of me: `check` (4 files),
`ok`/`bad`, `pass` (3 files), `echo "ok   - "` (34 sites, all 34 using that
exact separator, so anchoring on it costs nothing), and `echo "PASS [...]"`
(the last file scoring zero). Measured counts replace three hedged
approximations that read as measured: "~49 assertions" is 56, "~30 sites" is 34,
"four files use pass" is three.

FALSE REFUTATION (found in the PR body, propagated into my code comment).
I wrote that a `<= crate+1` relation "fires on a reword too". It does not:
during a reword the correct constant is crate+1, and crate+1 <= crate+1 passes.
The claim came from a reviewer who withdrew their own suggestion on that basis
and I repeated it without checking a single comparison. The comment now states
the arithmetic, scopes the "no relation works" conclusion to EQUALITY-STYLE
relations, and records that such a bound is deliberately omitted for redundancy
rather than unsoundness -- with an instruction not to re-cite the refuted claim.

FROZEN AS ONE BLOB. The two-adjacent-assertions form failed the remediation
path: reword goes red, then following the pin's own failure message (regenerate
the encoded text) goes green again with the version constant untouched, because
the message never asked about it. A freeze forces a decision only if the
remediation cannot be performed without making that decision. Both values are
now one base64 blob, so the recipe cannot be run without supplying a version.
The comment states the residual honestly: this makes the question unavoidable,
it does not verify the answer -- regenerating with the new text and the old
version still passes, measured.

Also:
  2. Pin that `prev_emits_latest_seen` READS the constant. Hardcoding the
     current value inside it left the whole suite green, freeze included,
     because the constant was untouched and the freeze had nothing to disagree
     with. It matters exactly when the constant is supposed to move.
  3. `base64 -w0` is GNU-only and fails on macOS, which is where someone
     reading this failure is most likely to be. The printed recipe now uses
     `base64 | tr -d '\n'`.
  4. The comment no longer implies the reword class is closed. Gate B greps
     four other markers against the PREVIOUS release's binary -- MARKER_DISABLED,
     MARKER_CHECK_RAN, MARKER_CHECK_COMPLETE, MARKER_TRIGGERED_RE -- and
     rewording any of them produces the same post-publish false alarm with a
     worse message. Named as residual, with the legitimate asymmetry (Gate A
     reads a binary built from this tree, so a reword there is self-consistent).
     Follow-up to be filed.
  5. `docs/RELEASING.md` "If Gate B fails" went straight to "ship a fix release,
     roll nodes by hand". This PR's whole subject is a Gate B failure that is
     NOT a fleet problem, and that page is what an operator reads at 1am. It now
     splits the two cases, gives the grep that distinguishes them, and warns
     that the Matrix alarm names the worse one.
     `.claude/rules/bug-prevention-patterns.md` gains the two lessons this PR
     earned: a relation pin between quantities on different clocks, and an
     expectation stored as a plaintext copy of the value it guards (with the
     remediation-path corollary).
  6. Narration compressed. The generalisations stay; the round-by-round history
     of this PR's own reviews goes. A recorded mutation RESULT is durable, a
     record of which review round produced it is not. The ci.yml block stated
     its blind-spot list twice; only the error string, which is what a
     contributor actually sees, keeps the full list.

The operator-facing comment in auto-update-canary.sh is deliberately NOT
compressed: it is the escape hatch item 4 of the previous round asked to
restore, and it is where an on-call reader lands.
The residual named in the previous commit now has an issue. Rewording
MARKER_DISABLED, MARKER_CHECK_RAN, MARKER_CHECK_COMPLETE or MARKER_TRIGGERED_RE
produces the same post-publish false alarm as a MARKER_LATEST_SEEN reword, with
a less specific message, and only MARKER_LATEST_SEEN is frozen.
The canary's only NEGATIVE check greps MARKER_PARSE_FAIL. Rewording it
fails SILENTLY in the passing direction: Gate B puts the grep to the
PREVIOUS release's binary, which emits the OLD text, so a published
release carrying the live #5221 bug logs check-ran + warn +
check-complete and Gate B reports "OK: parsed GitHub's response".

Nothing caught this. pin_warn_literal interpolates $MARKER_PARSE_FAIL,
so it follows a rename by construction -- the same defect that left
MARKER_LATEST_SEEN's source pin blind. Measured: a sed sweep across the
three files a developer must touch leaves every assertion green.

Frozen as a single base64 value, not the pair structure used for
MARKER_LATEST_SEEN: that pair exists because Gate B SKIPS its positive
check for binaries predating the marker and the skip needs a version to
compare against. There is no skip branch here, so there is no second
value a remediation could quietly leave stale.

The failure message refuses to present "regenerate the blob" as the fix,
because it is not one: no published binary emits the new wording, so
re-stating the freeze alone just makes the suite agree with the blind
spot. It names the two real options (keep matching the old text as an
alternation, as MARKER_TRIGGERED_RE does; or accept the blindness
knowingly) and gives a portable regeneration recipe.

Also corrects the neighbouring comment's accounting: seven markers are
grepped, two are now frozen, and MARKER_FETCH_FAIL is grepped but named
in neither freeze nor #5309.

Refs #5309.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
 log

The value freeze alone notifies but cannot force. Measured: reword the
marker, follow the freeze's own recipe exactly and nothing else, and the
suite goes fully green while assert_detection_healthy answers RC=0 with
"OK: parsed GitHub's response" on a verbatim v0.2.121 #5221 log. That
is round 4's shape at one remove -- a single value can always be
re-stated, so the remediation never has to make the decision.

Drive the real function with the historical WARN line instead, stored
base64 so no rename sweep reaches it. After a reword this stays RED
until the detector can still read an already-published binary, and the
only other way green is deleting the assertion, which is the deliberate
reviewable form of accepting the blindness.

Scaffolding interpolates the live $MARKER_CHECK_RAN so this stays a pin
on MARKER_PARSE_FAIL alone and does not fire for markers #5309 owns. The
diagnosis is asserted alongside the exit code: rc=1 is also what "the
check never ran" returns.

Refs #5309.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
…l counter

The `[[:space:]]` right after `(check|ok|pass)` requires the helper's
name to END there, so every underscore-suffixed helper scored zero:
gate_b_arm_case, version_ge_case, trigger_case, norm_case,
timeout_guard_case, pin_marker, pin_warn_literal, check_vs_expected,
check_call_count, check_fallback, test_restores_persisted_value.

Measured across all 11 *_test.sh files: 329 assertion call sites, 275
seen, 54 missed, 42 of those in auto-update-canary_test.sh alone. Adding
a check_/pin_/assert_/expect_/test_ prefix pattern and a _case suffix
pattern takes it to 329 of 329 with zero false positives, verified line
by line against a hand-enumerated ground truth. On this PR's own diff
the count goes 4 -> 16.

It fails in the rejecting direction: an under-counting lint turns away a
fix: PR that HAS a regression test, and the cheapest response is to
reshape a good test until the grep likes it.

Nothing exercised the regex, which is why it has now been wrong three
times and each miss was found by a human noticing a wrong number. Adds
scripts/rule_lint_shell_assertion_counter_test.sh, which EXTRACTS the
live pattern from ci.yml rather than copying it (a copy is rewritten by
the same edit it guards) and runs it over 26 call-site shapes taken from
the repo's own test files, in both directions. Its own first draft
truncated the extraction and matched everything, so it carries an
explicit vacuity guard.

Also corrects the comment's blind-spot list: the printf-style false
negative it cited does not occur anywhere in scripts/, and is replaced
by the one that does (a bare string added to a table an assertion loops
over, e.g. SIGPIPE_SCRIPTS).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
…itHub fetch

The previous release's binary retries its startup GitHub fetch ZERO
times (startup_update_check_with_fetcher warns and returns on the first
Err), and it demonstrably fails: framework logged that WARN twice on
2026-08-11, at 17:08:04Z and 00:35:13Z. Gate B's subject IS a published
binary, so this cannot be fixed from the canary side.

Gate B had no retry at all -- CANARY_ATTEMPTS was read only by
cmd_preflight -- so one blip in a ~40s window decided a release's
post-publish verdict, and the resulting red job fired the Matrix alarm
saying a node on the previous release may not be able to auto-update to
this one. That is the #5221 text, sent because a socket did not open.

Three changes:

- cmd_selfupdate retries the indeterminate case, mirroring Gate A. It
  wipes home/cfg/data/logs/tmp/cache between attempts but KEEPS
  $work/bin: the downloaded previous release is the subject, not state,
  while $work/home holds the node's persisted GitHub poll cooldown and
  a retry that re-reads it cannot produce a different answer.
- node_could_not_reach_github classifies the run from the node's own
  WARN. Only that counts as environmental; "started and never logged an
  outcome" stays a real finding, because a hung updater looks exactly
  like it. Gate B exits 75 (EX_TEMPFAIL) for the environmental case,
  reusing the sysexits convention the file already follows with exit 64.
  Deliberately NOT 43, which is the NODE's port-collision exit code:
  overloading it would make "the canary exited 43" ambiguous at the one
  moment someone reads it under pressure.
- cross-compile.yml keys the Matrix wording off that code: a quiet
  message for environmental, the existing alarm otherwise, written as
  its exact negation so unanticipated states stay loud. The job is still
  RED either way -- unverified is not verified.

Gate A keeps exit 1: it blocks, and a stuck draft always needs a human.

Measured end-to-end against a fake node reproducing the framework log:
fetch failure -> 2 attempts, exit 75, environmental text; no-outcome ->
2 attempts, exit 1, message noting no fetch failure was reported; real
#5221 -> stops after attempt 1, exit 1, the real alarm.

Adds behavioural tests for the classifier in both directions, source
pins for the retry loop and exit code, and wiring pins so the notify
split cannot be silently collapsed or the step's exit code swallowed.
Documents the new exit path in docs/RELEASING.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
The whole-block scan was self-satisfying: the LOUD step's condition is
`!(<quiet_cond>)`, so the quiet condition is a substring of it and the
assertion passed on the loud step alone. Mutation-tested -- replacing
the quiet step's if: with a bare `result == 'failure'`, so the
reassuring message covers every red run including a real #5221, left the
suite fully green.

Split the notify job into steps, identify each by text from its own
message rather than by order, and check the conditions separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
…lure never retries

Three constraints from review, each closing a way the previous commit
could still go wrong.

1. RETRY ONLY THE INDETERMINATE OUTCOME. rc=1 already broke out of the
   loop immediately, but nothing pinned it, and the failure mode is the
   worst one available here: each attempt wipes the tree and boots a
   fresh node, so retrying a genuine detection fault would let any
   INTERMITTENT fault produce one passing attempt and Gate B would
   report a broken release healthy. A flaky pass on the post-publish
   gate is worse than no gate. Pinned behaviourally in the lifecycle
   test by counting node boots (2 for indeterminate, exactly 1 for a
   real #5221 parse failure), not by grepping for the break.

2. PERSISTENT INDETERMINACY MUST NOT BE ABSORBED. The quiet message
   tells the room a red release needs no action, so a single WARN from
   the node should not earn it: a persistent problem would report
   "environmental" release after release, nobody would be alarmed, and
   the gate would verify nothing while looking maintained.

   gate_b_unverified_class now demands corroboration from a second
   observer in the same run -- this runner's own fetch of the same
   endpoint. Node failed and the runner also cannot reach GitHub: real,
   quiet. Node failed on every attempt while the runner is fine: not a
   blip, loud. A port collision needs no probe. A sustained CI-wide
   outage cannot reach the quiet path at all, because Gate B's own
   tarball download would already have failed loudly. The residual
   window is documented rather than implied.

   Both messages now say plainly that the release is UNVERIFIED, and
   that consecutive occurrences mean the gate is not working.

3. THE NEW NOTIFY STEP IS PINNED. Steps are located by their "id:",
   not by a phrase from their message -- locating by message text was
   tried and an ordinary reword broke it on the first edit, which is
   the same expectation-stored-as-a-copy shape this PR exists to
   remove.

Also: the rule-lint counter could not see this PR's own new test file.
Its 26 assertions are bare "expect" calls and the pattern required an
"expect_" prefix, so the file whose whole job is to stop the counter
under-counting scored zero. Widened to the bare name (nothing in
scripts/ drives TCL expect(1)) and added a case for it. Re-measured
across all 12 files: 378 assertion sites, 286 seen before, 378 now,
zero false positives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
…r attempt

R3 review found six things. The substance is F1 (a bug) and F3 (why we
would otherwise ship a seventh).

F1, HIGH. The environmental flag was last-writer-wins: reassigned each
attempt, consumed once after the loop. Attempt 1 starts the check and
logs no outcome (a hung updater, a real finding); attempt 2 loses a port
race (environmental); the run exits 75 and the dev room is told it is
"not a stranded fleet and needs no fleet action". A false quiet on
exactly the class this gate exists to raise, produced by the retry added
to reduce false alarms. An unexplained indeterminate on ANY attempt now
latches and no later attempt can overwrite it. Its message no longer
claims the canary "never got to test" when an attempt did.

F3 and Gap 1. Three mutations that break the classifier in the QUIET
direction left the whole suite green, and one of them is this PR's own
disease again: changing `&&` to `||` in the candidacy test survives the
source pin because the CALL TEXT is unchanged -- the defect the round-2
prev_emits_latest_seen fix was written to close, one function over. Nor
could anything see the run-level latch, which is a property of a
SEQUENCE that no single-attempt test can express.

Adds a behavioural driver: stub run_node_until_check, script a sequence
of (node exit, fixture log) pairs, assert both the returned code and the
attempt count. Seven cases, including both latch orderings and the
"retry never touches a real failure" property that Gap 1 says nothing
pinned.

Gap 3. A real #5221 reaches the quiet message via a reorder nothing
pinned: the parse-fail branch precedes the fetch-fail branch, and that
ordering is the ONLY thing keeping a log holding both markers out of the
environmental class. No fixture in the suite held both. Added one.

F2. A port collision was reported as "could not reach GitHub" in the
Matrix message; the node never started and nothing fetched anything. The
script already worded its own error from the cause; the notify message
now names both causes and points at the log line that says which.

F6. `CANARY_ATTEMPTS=00` passed the digits-only guard, `seq 1 00` is
empty, the loop never ran and the read after it aborted under `set -u`
with "rc: unbound variable" -- a shell error where a verdict belongs.
All three budget guards now share one sanitiser that folds `10#`; `rc`
is initialised.

F5, one sentence not a fix: the same WARN also fires for a published
fetch-side regression, which would look environmental forever. Gate A
narrows it. The quiet message now says a recurrence is not the runner.

Item 5: the canary's exit constant and the workflow's literal 75 are one
contract in two files; pinned by reading the constant.

Item 7: the backstop fixture omitted the completion line, so it yielded
rc=2 while its message claimed rc=0. Three lines now, and it fails for
the reason it names.

Items 6 and 9, and M2: note that ci.yml's removed-tests guard scans
crates/core/**/*.rs only, so deleting a shell assertion is flagged
nowhere; correct the stale port-increment arithmetic; and correct the
claim that MARKER_FETCH_FAIL's reword is merely loud -- it is now the
input to the environmental classification, so a reword silently
reinstates the false fleet alarm.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
The block scanned for the Gate B pins included its own comments, which
quote the very constructs the pins require. Those assertions would have
passed against the EXPLANATION after the code was deleted -- the same
pin-satisfied-by-prose shape JOB_BLOCK already strips for
attach-to-release, and that this repo's own rules list twice.

Found because a mutation of '|| rc=$?' hit the comment on line 748
before the code on line 756, so nothing actually broke and the pin
stayed green. The harness accident is what exposed it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
…haviour

'Which KIND of indeterminate, recorded from the LAST attempt' was true of
the last-writer-wins version and became false when the latch landed. A
reviewer read the tree, read that line, and correctly concluded from it
that F1 was still present -- from the comment, not the code. A stale
comment on a fixed bug costs a review round, so the correction says what
it used to say and why it changed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
…he cause sticky by strength

Three findings from the final pass, plus the optional probe hardening.

FINDING 1, blocker. `notify_block` was the one extraction of three that
still did not drop comment-only lines, so two mutations left all four
suites green: dropping `always()` from the notify job's `if:` while
leaving a comment that mentions it, and -- the bad one -- widening the
quiet step's `if:` to `result == 'failure'` while leaving a one-line
comment recording the old condition, which fires "no fleet action is
indicated" on every red Gate B run including a real #5221. That second
one was saved only by luck: the comment had to be on ONE line, because
the `# ` on a wrapped continuation breaks the whitespace-collapsed match.

Fixed structurally rather than at the site. All three extractions now go
through one `yaml_job_block` helper, so a fourth caller cannot forget the
filter -- two single-site fixes in a row is what turned this into a
function. (`notify_steps` derives from `notify_block`, so it inherits it.)

FINDING 2. The latch held at the top tier but `env_cause` was still
last-writer-wins among EXPLAINED causes, and the two orderings of the
same pair disagreed: ports-then-github ran the probe and went loud, while
github-then-ports never probed at all, exited 75 quiet, and claimed
"every attempt hit a port collision on this host" -- false. Reachable at
the default two attempts. The fold is now sticky by STRENGTH, not
recency: unexplained > github > ports. Both orderings pinned.

FINDING 3. `gate_b_case` compared only exit code and attempt count, so
deleting the entire `ports)` arm of the message case left everything
green. Exit 75 is reached from two branches with different
operator-facing text, and the quiet Matrix message deliberately names the
job log line as the only disambiguator -- so that line was the one thing
untested. It now takes an expected-substring like `check()` does, which
is also what catches Finding 2's false message, since the exit code alone
is 75 in the correct ports-only case too.

Optional hardening, taken because it is one case statement. The probe
went through `resolve_expected_latest`, which collapses every non-answer
to `return 1`: a 403, a captive portal, a changed redirect shape, curl
missing. Read as corroboration, every one of those bought the quiet path.
It now asks curl directly and only connect-class exits (6/7/28/35) count
as "cannot reach"; everything else, including an HTTP error and a missing
curl, is loud. The endpoint is a named constant so the two callers cannot
drift onto different URLs. Eight cases stub curl's exit code rather than
the function, so what is tested is the classification and not a
restatement of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
…sserting counts in prose

B1, BLOCKER. `docs/RELEASING.md`'s "If Gate B fails" told the operator
that an every-attempt fetch failure gives exit 75 and the quiet warning,
and added "so if you are reading the #5221 text this is not what
happened". The corroboration probe made that false: the github cause now
also needs the RUNNER to fail the same fetch, and a hosted runner is
normally online by probe time, so the common case gives exit 1 and the
loud alarm. Verified by executing all five outcomes:

    fetch-fail x2, runner OFFLINE   exit 75  QUIET
    fetch-fail x2, runner ONLINE    exit 1   LOUD
    port collision x2               exit 75  QUIET
    no outcome logged x2            exit 1   LOUD
    real #5221 parse failure        exit 1   LOUD

An operator seeing the alarm over a log full of fetch-fail WARNs was
told this was not what happened, fell through to "a real detection or
install failure", and would cut a fix release for what the canary's own
message calls a probable poll-budget cooldown. The section also promised
three cases where the code produces five, and never mentioned the ports
route to 75 that the quiet Matrix message names. Rewritten as a table
keyed on the phrases the code actually emits.

B2. Two comments fifteen lines apart contradicted each other about how
the notify steps are located; the message-text form was replaced two
commits earlier. A reviewer reading top-down would recognise the
anti-pattern and report a defect already fixed. Removed, and the
surviving statement lives at the call site it describes.

B3. The claim that `grep -rnE "printf .*['\"](ok|PASS)"` over scripts/
returns nothing was the sole stated reason for DELETING a real entry
from the contributor-facing blind-spot list. It returns two:
test-install-sh.sh and test-uninstall-sh.sh, both
`pass() { printf 'PASS  %s\n' "$1"; }`. They escape the counter only
because the diff is globbed to `**/*_test.sh` and those are `test-*.sh`
-- a naming accident. Entry restored with the accurate reason.

One I introduced fixing Finding 2: the message said the node could not
reach GitHub "on all $CANARY_ATTEMPTS attempts", but sticky-by-strength
needs only ONE github attempt, so ports-then-github printed it falsely.
It now counts them and states the real number, and three driver cases
assert the count rather than a substring that skips over it. Also
corrected the latch comment, which described a loop that no longer only
records.

B4-B8: "the only thing keeping a real #5221 out" downgraded to "the
first of two" (the probe is the second); the lifecycle cross-check no
longer claims "REAL boots" when both sides are fakes differing only in
how much harness runs; six `freenet.rs` line numbers, all low by 12,
replaced with the phrases they name -- this file's own rules already say
a line number in a note about stale pins rots faster than its subject.

THE INSIGHT, carried into the code rather than only fixed. Seven of the
eight stale comments were load-bearing JUSTIFICATIONS -- the sentences
that tell the next reader they may stop checking -- and they rot exactly
when the thing they justify is strengthened. So every count and grep
result in this PR that could rot is now computed where it can go red:
`rule_lint_shell_assertion_counter_test.sh` recomputes that no
`*_test.sh` is invisible to the counter, and that the printf blind spot
is still unreached, instead of restating measured numbers. The prose
numbers that moved three times inside this PR are gone. Written up as a
new section in `.claude/rules/bug-prevention-patterns.md`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
…files

The invisible-*_test.sh check used `git ls-files`, so an untracked new
file was invisible to it -- and a contributor adding a test file has not
necessarily staged it yet, which makes that file exactly the one whose
convention the counter may not know. Found by mutation: a probe file
written with an unrecognised convention did not trip the check that
exists to catch precisely that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
…nothing reached

The count fix landed on the loud github branch and not on its quiet
sibling twelve lines above. Both are reached with env_cause=github,
which under sticky-by-strength needs only ONE github attempt, so
ports-then-github with the runner DOWN printed "in 2 attempts" when one
attempt was a port collision. The comment above the loud branch was
stale in the same way ("on every attempt").

Found because a reviewer's mutation reported PATTERN NOT FOUND with the
suite green. An unapplied mutation is not a pass; the string was missing
because the unfixed copy was in a different branch.

Enumerated the rest by grep rather than by recall. Four sites
interpolate CANARY_ATTEMPTS into a message; the two in Gate A's
post-loop failure and Gate B's classifier-bug branch are legitimately
unanimous, because both loops only continue while rc=2, so reaching
them means every attempt was indeterminate. The ports message is
likewise accurate: env_cause=ports with no unexplained attempt implies
every attempt was a port collision.

Two new driver cases reach the quiet github branch, which the three
count-asserting cases never did -- they all have the runner UP and so
land on the loud sibling. That is why the sibling had no coverage.

Also adds the enumeration rule to bug-prevention-patterns.md: a defect
found at one site is not fixed until every site of that shape has been
grepped for, and three of tonight's four instances of that miss were
caught only by an unapplied mutation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dyjTKM35KGZXjE7jmDTsX
@sanity
sanity force-pushed the fix/marker-pin-self-limiting branch from 95c8350 to 46dbaf2 Compare August 13, 2026 07:06
@sanity

sanity commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

On the scope-creep warnings

The rule-review check flags four of these, and a human-equivalent big-picture reviewer raised the same objection earlier in this PR's review. It deserves an answer on the record rather than a silent override label, because the objection is basically correct on its face: this PR contains four logical changes and its title names one.

The counter-argument, and it is a causal one rather than a convenience one.

The Gate B environmental classification (exit 75, the corroboration probe, the retry loop, the notify split) is not merely bundled with the marker freeze. It is caused by it.

Gate B's positive equality check has never executed in production — it was skipped for 0.2.125 because the previous release predated the marker. The constant frozen in this PR is precisely what makes it arm for the first time on 0.2.126. And the marker it then requires is emitted only when the node's own GitHub fetch succeeds — a fetch with no retry, which was observed failing twice in one day on a single real machine during this PR's review.

So shipping the marker half alone would ship the change that arms the check without the change that stops it false-alarming, and the first symptom would be a post-publish 🚨 telling the team the fleet may be stranded, on a healthy release. Splitting here does not reduce risk; it sequences the two halves in the order that produces the incident.

The rule-lint half is load-bearing in a narrower and more boring way, verified rather than asserted: on this branch's diff the pre-existing regex scores 0 added shell assertions, so without the widening, Rule Lint rejects this PR for having no regression test. test-exempt was rejected as an alternative because it would put a false statement in the record on a PR that demonstrably has tests.

What is genuinely separable and is being kept separate: the Gate A reordering — moving cargo publish downstream of the pre-publish canary so a blocked gate burns a deletable tag instead of a permanent crates.io version — is a distinct branch, already written, deliberately not in this PR.

What this PR should have done differently: the title and body named one change for most of the PR's life. Both now describe all four. That was the reviewer's actual condition for shipping it whole, and it was the fair criticism.

For anyone auditing later: this went through four review rounds (three full, one focused), which found seven instances of the same defect class — a gate that cannot see its subject — every one of them inside the fix for the previous one. The last of those was found because a mutation returned PATTERN NOT FOUND alongside a green suite and someone investigated instead of moving on.

[AI-assisted - Claude]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant