Skip to content

fix(dl-router): reach the Discord original by rewriting the proxy URL, not by reading a link that is never there - #1286

Open
ZacxDev wants to merge 6 commits into
mainfrom
fix/dl-router-discord-original-url
Open

ZacxDev wants to merge 6 commits into
mainfrom
fix/dl-router-discord-original-url

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member

preferOriginalUrl has been inert in production since it shipped. It reads info.linkUrl, which Chrome populates only from an ancestor <a> — and a Discord image attachment has none.

Measured, against the live client

3 image attachments, 2 channels, 2 message shapes (single image + mosaic):

measured
<img src> host media.discordapp.net — 3/3
ancestor <a> around the image 0 of 3 (div[role=button].clickableWrapper)
cdn.discordapp.com anchor in the message 3/3, a sibling at depth 9
same pathname (unambiguous pairing) true
ex/hm/is values across the two hosts byte-identical

So every real right-click lands on if (!linkUrl) return srcUrl and downloads the resized webp thumbnail — the defect #1110 set out to fix, still live for images. Every existing unit test supplied a linkUrl, so both suites stayed green over a feature that could not fire: the defect lived in the seam, not in either component.

Why a rewrite, not a DOM read

A prior session eliminated host-rewriting because "the two hosts carry different query params". They do not — the proxy URL carries ex/hm/is plus the resize params. Probed from the page context of a live Discord tab, with both controls:

status
positive control — the message's own cdn anchor 206
under test — proxy url, host swapped, resize params dropped 206
negative control — same url, signature removed network error

That also closes the gap the handoff flagged as never probed: whether a cdn URL without a valid signature actually fails. It does.

Chosen over a content script, which would need a discord.com host permission and couple to Discord's hashed class names (they rotate every deploy). originalFromPreview is pure and rewrites only what discordChannelId recognises as an attachment, so an avatar or emoji on the same proxy host comes back untouched — pinned by a test that reaches that guard by a path no earlier check rejects.

Behaviour changes, both deliberate

Two existing assertions move toward the clicked image's own original: the no-link case, and the mismatched-path case (the live mosaic shape, where the only anchor on offer belongs to a sibling image).

Test matrix

The production path is pinned at the service-worker seam, asserting which URL was downloaded (not merely that something happened):

  • red at aba48864 — downloads media.discordapp.net/...?format=webp&width=550
  • green at HEAD — downloads cdn.discordapp.com/...?ex=1&is=2&hm=3

Gate, both tiers, base a7dac5bd

  • nix build .#checks.x86_64-linux.nodetestsrc 0
  • nix build .#checks.x86_64-linux.pytestsrc 0 (built one at a time)
  • dev-host gate.sh node tier — 1455 pass / 0 fail (dl-router 543, was 537)

One earlier pytests run went red on test_six_writers_with_a_tiny_busy_timeout_still_land_every_row. Attributed to load, not to this change: the whole 1020-test dl-router target took 391 s in that run against 115 s at base — load inflates every test, an assertion inflates one — the test is itself a deliberate 5 ms busy-timeout load reproduction, this diff contains no Python, and a genuine re-run was green.

The dev-host pytest tier also shows 3 failures; all 3 were confirmed to fail identically at base.

manifest 0.3.2 -> 0.3.3, because 0.3.2 was never actually loaded by the browser — so the outstanding restart check becomes "brave://extensions reads 0.3.3".

🤖 Generated with Claude Code

https://claude.ai/code/session_01BePLrAzQrg983BX2FSdEjR

ZacxDev and others added 2 commits September 4, 2026 00:49
…, not by reading a link that is never there

preferOriginalUrl has been inert in production since it shipped. It reads
`info.linkUrl`, which Chrome populates only from an ANCESTOR <a> -- and a
Discord image attachment has none.

MEASURED 2026-09-03 against the live client, 3 image attachments across 2
channels and 2 message shapes (single image, mosaic):

  <img src> host ......................... media.discordapp.net   3/3
  ancestor <a> around the image .......... 0 of 3
  cdn.discordapp.com anchor in the message  3/3, a SIBLING at depth 9
  same pathname (unambiguous pairing) .... true
  ex/hm/is values across the two hosts ... byte-identical

So every real right-click lands on `if (!linkUrl) return srcUrl` and downloads
the resized webp thumbnail -- the exact defect #1110 set out to fix, still live
for images. The unit tests all supplied a linkUrl, so both suites stayed green
over a feature that could not fire: the defect lived in the seam, not in either
component.

The fix rewrites instead of reading the DOM: proxy host -> cdn host, resize
knobs dropped, signature carried. A prior session ruled this out on the grounds
that the two hosts carry different query params. They do not -- the proxy URL
carries ex/hm/is PLUS the resize params. Probed from the page context of a live
Discord tab, with both controls:

  positive control  the message's own cdn anchor .............. 206
  under test        proxy url, host swapped, resize dropped ... 206
  negative control  same url, signature removed ............... network error

That also closes the gap the handoff flagged as never probed: whether a cdn URL
without a valid signature actually fails. It does.

Chosen over a content script because the alternative needs a discord.com host
permission and couples to Discord's hashed class names, which rotate on every
deploy. `originalFromPreview` is pure and rewrites only what
`discordChannelId` recognises as an attachment, so an avatar or emoji on the
same proxy host comes back untouched -- pinned by a test that reaches that
guard by a path no earlier check rejects.

Two existing assertions change on purpose, both toward the clicked image's own
original: the no-link case, and the mismatched-path case (the live mosaic
shape, where the only anchor on offer belongs to a sibling image).

Test matrix, the production path pinned at the service-worker seam:
  red  at aba4886  downloads media.discordapp.net/...?format=webp&width=550
  green at HEAD     downloads cdn.discordapp.com/...?ex=1&is=2&hm=3

node: 1455 pass / 0 fail (dl-router 543, was 537).

manifest 0.3.2 -> 0.3.3: 0.3.2 was never actually loaded by the browser, so the
outstanding restart check becomes "brave://extensions reads 0.3.3".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BePLrAzQrg983BX2FSdEjR
Claude-Session-Id: e13f042e-3be9-449d-a597-4b9a7ea7d259
…contradicts, and keep tier-1 correlation reachable

Adversarial audit of #1286 returned one deploy-blocking finding and three
should-fix ones. All addressed here.

1. (blocking) `SKILL.md` carried a 🔴 "Never reach the original by rewriting a
   proxy URL's host" rule, whose stated reason -- that the two hosts carry
   different signature parameters -- is what this PR measured to be false. The
   skill deploys via `mkOutOfStoreSymlink`, so it is LIVE off the working tree
   with no `home-manager switch`: the next agent to hit a download-routing
   symptom would have read a false NEVER and reverted `originalFromPreview`.
   That is the "safety comment whose falsity would lead a maintainer to delete
   the guard it describes" shape, exactly.

2. Rewriting the download url made `correlateCapture` tier 1 structurally
   impossible for Discord attachments: the capture holds the element's src,
   the DownloadItem holds the rewritten url, so every such download fell to
   tier 3 -- the tier this repo documents as the fragile one that once
   "silently degraded routing to the catch-all with no error anywhere".
   Concretely, two images clicked in one message inside the 15 s window would
   bind the first download to the SECOND image's context.

   `onMenuClicked` now stamps the outgoing url onto the capture it came from,
   and tier 1 checks that third field. Same trick and the same reason as
   `playerDownload` synthesising a capture around its media url. The stamp
   matches on `srcUrl`, never on recency -- taking "the newest capture" would
   reproduce the very defect it removes.

3. Comments corrected against the code, per "a comment is a claim too":
   - the `""` resize-param entry does NOT strip a stray `&` (the urlencoded
     parser skips empty sequences); it covers `?=value`, which is a different
     and never-observed shape. Said so, and stopped claiming a measurement.
   - `KNOWN UNHANDLED VARIANT` is now handled and the note invited a widening
     that would make it worse.
   - "A <video> is unaffected" is false post-rewrite; video was measured at
     zero points and has no fallback if the rewrite is wrong for it.

Tests, all shown to kill their mutant:
  - `?=value` empty-named param      (the audit's M1, previously SURVIVED)
  - proxy src + non-origin link      (M10, previously SURVIVED, reachable)
  - proxy src + non-attachment link  (M11, previously SURVIVED)
  - tier 1 matches `downloadUrl`, and binds to the CLICKED capture not the
    newest -- the multi-image case stated above
  - the stamp is gated on the url having changed

node: 550 pass / 0 fail (was 543).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BePLrAzQrg983BX2FSdEjR
Claude-Session-Id: e13f042e-3be9-449d-a597-4b9a7ea7d259
@ZacxDev
ZacxDev force-pushed the fix/dl-router-discord-original-url branch from 3bcb503 to 261a4cf Compare September 4, 2026 05:49
@ZacxDev

ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Round 1 adversarial audit complete; fixes landed in 261a4cf5. Both sandbox tiers green on the rebased tree at base 2882d2c7: nodetests RESULT: PASS (dl-router 550/550, total 1462/0 fail), pytests RESULT: PASS (dl-router 1020/1020, total 21073 passed / 0 failed).

1. SKILL.md's live 🔴 "never reach the original by rewriting a proxy URL's host" rule rewritten; its three constituent claims (original on a wrapping <a>, hosts carry different signature params, a video src is always the origin) replaced with the measured position.
2. correlateCapture tier 1 now also matches a capture's `downloadUrl`, and onMenuClicked stamps the outgoing url onto the capture it came from — claimed to restore tier-1 binding for rewritten Discord downloads, which had fallen to tier 3.
3. stampDownloadUrl matches on the clicked element's `srcUrl` rather than on recency — claimed to bind a download to its own capture, not to the newest one, in a multi-image message.
4. The `""` resize-param comment rewritten: the stray-`&` mechanism it asserted is false (the urlencoded parser skips empty sequences); the entry actually covers `?=value`, which is unobserved and defensive.
5. The `KNOWN UNHANDLED VARIANT` docstring rewritten: that variant is now handled, and the widening it invited would replace a correct answer with a downscaled proxy copy.
6. The `// (A <video> is unaffected)` comment replaced: proxy-host video srcs ARE rewritten, video was measured at zero points, and there is no fallback if the rewrite is wrong for it.
7. Five new tests claimed to kill mutants the round-1 audit reported SURVIVED or newly introduced: M1 (`""` entry), M10 (wrong-link-host exit), M11 (non-attachment-pair exit), M13 (tier 1 drops downloadUrl), M14 (stamp takes newest capture).
8. Emoji removed from the two dl-router extension sources to satisfy source_hygiene.test.mjs's ASCII rule, which the round-1 fixes had broken.

Not addressed, deliberately, with reasons: audit M7 (preview-host guard) is near-inert — the attachment guard already rejects everything it would catch. Audit M12 is genuinely unreachable dead code (both hostOf calls already parsed both URLs, so the new URL cannot throw) and is pre-existing, not introduced here. Neither is a coverage gap.

…ection, and it survived at two more sites

Round 2 found the round-1 fix reproduced the round-1 blocker in the opposite
direction, plus two sites the round-1 grep missed.

F1 (my regression). The rewritten SKILL.md rule landed inside `## Player
buttons / embedded video downloads`, a section documenting the TOML `media`
ordered list -- whose first accessor is an attachment anchor. That list is
resolved by `container.querySelectorAll`, a DESCENDANT query, so a sibling
anchor resolves fine; the ancestor-only constraint belongs to
`chrome.contextMenus`' `info.linkUrl`, which that section never uses. Worse,
`playerDownload` never calls `originalFromPreview` at all, so for the
player-button path the anchor accessor is the ONLY route to the original --
while the new prose called it futile and pointed at the rewrite instead. An
agent reading it would have deleted a working accessor from live config.

The context-menu path now has its own subsection, the `linkUrl` fact is scoped
to it, and the player path says plainly that it does not rewrite.

F2. The refuted "Do NOT reach the original by rewriting a proxy URL's host"
claim survived VERBATIM in `config.example.toml` -- the file an operator copies
rules from. Round 1 fixed one of its sites and called it done.

A wider grep than the auditor's found a THIRD site: the comment on
`service_worker.test.mjs`'s existing menu test still asserted the original sits
"on the wrapping <a href>", which is why that test's supplied `linkUrl` looked
sufficient for years. Corrected, and it now says why both menu tests exist.

F3. The `downloadUrl` comment claimed `onMenuClicked` stamps "the url it hands
to chrome.downloads.download". The stamp runs BEFORE the streaming branch,
where the url goes to the sidecar's /fetch instead. Nothing misroutes; the
sentence was wider than the code.

F4. The preview-host guard survived round 1's sweep and was waved off as
"near-inert because the attachment guard already rejects everything it would
catch". That mechanism is wrong: DISCORD_CDN_HOSTS holds BOTH hosts, so
`discordChannelId` accepts an origin-host attachment too, and without the host
check an origin URL carrying resize knobs would have them stripped. Now pinned
by a test, and the mutant dies on it (fail 1, its own assertion).

node: 551 pass / 0 fail (was 550).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BePLrAzQrg983BX2FSdEjR
Claude-Session-Id: e13f042e-3be9-449d-a597-4b9a7ea7d259
@ZacxDev

ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Round 2 delta audit complete; fixes landed in 54d2a654. Both sandbox tiers green: nodetests RESULT: PASS (dl-router 551/551, total 1463/0 fail), pytests RESULT: PASS (21073 passed / 0 failed).

Round 2's own summary of the code half: "the fix does what it claims, the mutation results are real, and I found no regression it introduced in executable code." Both its findings were prose.

1. F1 — the round-1 SKILL.md rule was rewritten AGAIN, because it had landed inside `## Player buttons / embedded video downloads`, a section whose TOML `media` list is resolved by container.querySelectorAll (a DESCENDANT query) and whose path never calls originalFromPreview. The context-menu path now has its own subsection; the `info.linkUrl` ancestor fact is scoped to it; the player path states plainly that it does not rewrite and that its anchor accessor is the only route to the original.
2. F2 — the refuted "do NOT rewrite a proxy URL's host" claim was removed from `config.example.toml`, the second of its sites, which round 1 missed.
3. A THIRD site the round-2 grep did not name was found and fixed: `service_worker.test.mjs`'s existing menu-test comment still asserted the original sits "on the wrapping <a href>", which is why that test's supplied linkUrl looked sufficient. It now states why both menu tests exist.
4. F3 — the `downloadUrl` comment no longer claims onMenuClicked stamps "the url it hands to chrome.downloads.download"; it names the streaming branch, where the url goes to the sidecar's /fetch instead.
5. F4 — the preview-host guard (audit M7) is now pinned by a test rather than declined. The decline reason was wrong in mechanism: DISCORD_CDN_HOSTS holds BOTH hosts, so an origin-host attachment would have had its resize knobs stripped without the guard. The mutant now dies on that test's own assertion, fail 1.

Not changed, and why: round 2's two 🟢 non-findings (the preferOriginalUrl:407 bare-srcUrl return, semantically identical since originalFromPreview is the identity for a non-preview host; and stampDownloadUrl applying no tab filter, where an identical srcUrl already implies the same message) were left alone — both change nothing a reader does. Audit M12 remains unreachable pre-existing dead code.

…CLAIMED and lost, and close the subsection I left open

Round 3 caught a false claim in round 2's own commit message, plus the same
wrong-section shape one level down.

F3 was never in `54d2a654`. I made that edit, then destroyed it: the M7
mutation battery restored `route_core.js` with `git checkout --` while the
comment fix was still uncommitted, and I staged and committed the reverted
file. The commit message and the PR's `audit-claims round=2` block both assert
it was fixed; against the tree they were false. The edit is here now, and the
lesson is the ordinary one -- a fact verified before a restore is not a fact
after it. Re-checking the diff immediately before committing is what would
have caught it, and is what I did this time.

The comment now says what the code does: the stamp runs BEFORE the streaming
branch, so on the ordinary path the url does reach `chrome.downloads.download`
and for a manifest it goes to the sidecar's /fetch instead, where the stamp is
inert rather than wrong.

The round-2 SKILL.md fix introduced the first `###` inside `## Player buttons
/ embedded video downloads` and nothing closed it, so 45 lines of
player-button guidance -- the "Important details" block, the whole "Buttons
don't appear" troubleshooting list including "ONE malformed accessor kills the
WHOLE rule", and the DEPLOY ORDER note -- were filed under a heading saying
they are about the context menu, a path with no rules, no accessors and no
buttons. Third appearance of correct-content-wrong-section in this PR: shipped
at section level in round 1, caught in round 2, reproduced at subsection level
in round 2's own fix. Closed with an explicit heading that says why it exists.

Also dropped an overclaim round 2 introduced in both prose sites: "sibling,
inside the container" was called "exact" on the strength of a measurement that
only supports the NEGATIVE half. 0 ancestor <a> of 3 says the anchor is not an
ancestor; it says nothing about whether it falls inside any given `container`,
and this repo's own note records it as nine levels away. Both sites now state
the descendant rule and tell the reader to confirm their container encloses it.

node: 551 pass / 0 fail (unchanged -- this round touches one test comment and
no assertions).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BePLrAzQrg983BX2FSdEjR
Claude-Session-Id: e13f042e-3be9-449d-a597-4b9a7ea7d259
@ZacxDev

ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

🔴 Retraction of a claim in the audit-claims round=2 block above. Item 4 of that block said the downloadUrl comment "no longer claims onMenuClicked stamps the url it hands to chrome.downloads.download; it names the streaming branch." That was false against the tree. The edit was made and then destroyed before the commit: the M7 mutation battery restored route_core.js with git checkout -- while the fix was still uncommitted, and the reverted file was staged and committed. Round 3 caught it by reading the tree instead of the claim. It is landed now in 852585a0 and verified present in the commit itself, not only in the working tree.

Round 3 fixes landed in 852585a0. Both sandbox tiers green: nodetests RESULT: PASS (dl-router 551/551, total 1463/0 fail), pytests RESULT: PASS (21073 passed / 0 failed).

1. F3 (round 2's lost edit) is now actually in the tree and in the commit: the `downloadUrl` comment states that the stamp runs BEFORE the streaming branch, so the url reaches chrome.downloads.download on the ordinary path and goes to the sidecar's /fetch for a manifest, where the stamp is inert rather than wrong.
2. 🟡-1 — the `###` context-menu subsection introduced in round 2 was left open, so 45 lines of player-button guidance (Important details, the "Buttons don't appear" list, the DEPLOY ORDER note) sat under a context-menu heading. Closed with an explicit `### Player button details` heading that states why it exists.
3. 🟢-3 — the "sibling, inside the container" claim was dropped at BOTH prose sites. The measurement (0 ancestor <a> of 3) supports only the negative half; whether the anchor falls inside any given `container` was never measured, and this repo's own note records it nine levels from the image. Both sites now state the descendant rule and tell the reader to confirm their container encloses it.
4. The "this exercise the swap" typo in service_worker.test.mjs was corrected.

Round 3's own verdict on the code: no executable code in its range, both tiers green, the M7 mutant reproduced dying fail 1 on its own assertion, and its fourth-site sweep found no further copy of the refuted claim — reported with a positive control (the same patterns matched 3 lines at 261a4cf5, proving the patterns can match).

Not changed: round 3's two stated non-findings (SKILL.md:215 being marginally wider than the code, scoped correctly by the next sentence; preferOriginalUrl:407's bare srcUrl return, semantically identical).

…ould misdirect a specific action

Round 4 found no behavioural defect (verdict: safe to merge, no 🔴, no 🟡) but
four load-bearing claims that are false. All four are sentences this ladder's
own earlier rounds wrote. Fixing them rather than shipping them, because each
one names an action a reader would take.

F1. "the stamp is inert there rather than wrong" is false, and it is the
OPPOSITE error to the one it replaced. VERIFIED against the code: `startFetch`
runs its own `correlateCapture` on the same url (service_worker.js:1509), and
tier 1 reads `c.downloadUrl`. A stamp also implies the rewrite fired, which
implies a cdn attachment target, which implies `directFile` -- so `streaming`
reduces to `manifest`, and that fetch correlates at tier 1 against the capture
just stamped. The stamp is DECISIVE on the streaming path, not inert. Believing
"inert" is what would license moving the stamp below the branch or dropping the
clause as download-only, silently demoting the fetch to tier 2/3.

F2. `config.example.toml` still asserted the original sits on a sibling anchor
"INSIDE the container" as bare fact, four lines above the round-3 hedge saying
containment was never measured. One comment block, two contradictory claims.
The bare claim is gone; SKILL.md had already been swept correctly.

F3. The round-3 heading note said the context-menu path "has no rules". False:
it has no PLAYER rules, but it is governed by `[site_rules."<host>".context]`,
which `content_capture.js` reads on the contextmenu event and which decides
where a menu download files. An operator whose menu downloads land in the
catch-all would have read that line and skipped the actual fix.

F4. The round-3 typo repair changed the subject's number instead of the verb's,
turning a true singular claim into a false plural one: "these exercise the swap"
attributes swap coverage to both menu tests, but the second supplies no
`linkUrl` and therefore cannot reach the swap at all. A maintainer trimming
duplication would delete the swap branch's only coverage -- the same
false-coverage belief that let this feature ship inert in the first place.

Also removed the emoji I reintroduced into route_core.js; dl-router extension
sources are ASCII-only and the suite enforces it. Second time this round-trip.

node: 551 pass / 0 fail (unchanged -- comments only, no assertions touched).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BePLrAzQrg983BX2FSdEjR
Claude-Session-Id: e13f042e-3be9-449d-a597-4b9a7ea7d259
@ZacxDev

ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Round 4 returned safe to merge, no 🔴, no 🟡 — but four 🟢 findings, each a false sentence naming an action a reader would take. Fixed rather than shipped, in 6de885d7. Both sandbox tiers green: nodetests RESULT: PASS (dl-router 551/551, total 1463/0), pytests RESULT: PASS (21073 passed / 0 failed).

1. F1 — "the stamp is inert there rather than wrong" was FALSE and the opposite error to the one it replaced. Verified against the code: startFetch runs its own correlateCapture on the same url and tier 1 reads c.downloadUrl; a stamp implies the rewrite fired, which implies a cdn attachment target, which implies directFile, so `streaming` reduces to `manifest` and that fetch correlates at tier 1 against the capture just stamped. The comment now says the stamp is DECISIVE there, and names what believing "inert" would license.
2. F2 — config.example.toml's bare "on a sibling <a> INSIDE the container" claim removed; it sat four lines above the round-3 hedge that contradicted it. SKILL.md had already been swept correctly.
3. F3 — the round-3 heading note said the context-menu path "has no rules". It has no PLAYER rules but IS governed by [site_rules."<host>".context], read by content_capture.js on the contextmenu event. Corrected, and it now points at that rule as the fix when menu downloads land in the catch-all.
4. F4 — the round-3 typo repair changed the subject's number instead of the verb's, making "these exercise the swap" attribute swap coverage to both menu tests; the second supplies no linkUrl and cannot reach the swap. Rewritten to say which test is the swap branch's only coverage and why deleting it would remove that coverage.
5. Emoji removed from route_core.js again — dl-router extension sources are ASCII-only and the suite enforces it. Second time in this ladder.

Round 4's own position on the ladder, recorded because it matters: it explicitly declined to stop on the "prose could be reworded forever" basis, on the grounds that all four findings misdirect a specific concrete action rather than being stylistic. It also verified claim 1 in the tree AND the commit separately — the distinction that caught round 2's lost edit — and confirmed the round-3 heading fix held, with no fifth instance of correct-content-wrong-section.

Gap it flagged rather than covered: dev-host gate.sh --tier both was not run this round (no executable line and no Python changed; both sandbox tiers green).

…nd the menu note was under the wrong heading

Round 5 verified F1 link by link and found it correct, including proving
unreachable the specific exception it was asked to hunt (a stamp coexisting
with `streaming` true via `mediaType === "video"` — `stampDownloadUrl`'s
`!srcUrl` guard forecloses it). F2, F3's false half and the ASCII fix were
clean, the latter with a negative control that watched the hygiene guard go
red. Two findings remained, both mine.

The round-4 comment claimed THIS test is "the only one that reaches the swap"
and that deleting it "would remove the swap branch's only coverage". False:
`identity.test.mjs`'s "a proxy thumbnail is swapped for the original behind it"
also reaches the swap exit and kills the swap mutant on its own. Verified it is
on `origin/main` (from #1110), so it predates this PR entirely. The claim was
scoped in its REASON but unscoped in its CLAIM, and coverage is a suite-wide
property — a maintainer deduping `identity.test.mjs` could have read this and
deleted the unit-level guard. Now scoped to this file and it names the other
level explicitly.

That is the same species of defect the sentence was written to remove: round 4
replaced an over-attribution of coverage with an over-attribution of
uniqueness.

The F3 pointer — "if menu downloads land in the catch-all, add a context rule"
— sat UNDER the heading "everything below is about the BUTTON, not the menu",
so the operator it names was told by the heading to skip it. Fifth instance of
correct-content-wrong-section in this ladder. Moved to the end of the
context-menu subsection where its audience is.

While moving it, softened "decides where a menu download files": a context rule
contributes subject/tags as the first and most specific signal, but the
sidecar's /match weighs them against title, Open Graph, link text and the
url-derived signals. It is the strongest lever a config author has, not the
decision.

node: 551 pass / 0 fail (unchanged — comments and prose only).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BePLrAzQrg983BX2FSdEjR
Claude-Session-Id: e13f042e-3be9-449d-a597-4b9a7ea7d259
@ZacxDev

ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Round 5 fixes landed in 22474fa8. Both sandbox tiers green: nodetests RESULT: PASS (dl-router 551/551, total 1463/0 fail), pytests RESULT: PASS (21073 passed / 0 failed).

1. The round-4 comment claimed THIS test is "the only one that reaches the swap" and that deleting it would remove "the swap branch's only coverage". FALSE: identity.test.mjs's "a proxy thumbnail is swapped for the original behind it" also reaches the swap exit and kills the swap mutant alone — verified present on origin/main (from #1110), so it predates this PR. Now scoped to this file, and it names the unit-level test explicitly.
2. The F3 pointer ("if menu downloads land in the catch-all, add a context rule") sat UNDER the heading "everything below is about the BUTTON, not the menu", so the operator it names was told by the heading to skip it. Moved to the end of the context-menu subsection.
3. "decides where a menu download files" softened: a context rule contributes subject/tags as the first and most specific signal, but the sidecar's /match weighs them against title, Open Graph, link text and url-derived signals. Strongest lever, not the decision.

🔴 THE LADDER STOPS HERE, and this is the ESCAPE HATCH, not convergence

Round 5 was not a clean round — it returned one 🟡 and one 🟢, both fixed above. Under the findings-keyed rule that would mean a round 6. I am stopping instead, and recording why so the next reader knows this is open rather than absent:

  • No 🔴 in rounds 3, 4 or 5. Round 4's verdict was "safe to merge"; round 5's was "safe to merge".
  • Blast radius is bounded by "the document contains a false sentence." No finding since round 2 has touched an executable line. Round 5 confirmed the range contains no assertion, guard or code-path change.
  • The recurring shape was swept at every site, not only where reported. Round 5 checked the correct-content-wrong-section shape at all four edited sites and cross-checked both prose sites (SKILL.md, config.example.toml) against each other after four rounds of edits, finding no self-contradiction.
  • Round 6 would be a prose round auditing a prose fix to a prose defect — the non-terminating loop. Rounds 2, 3, 4 and 5 each found a defect introduced by the previous round's fix, every one of them a sentence rather than code.

What convergence DID look like, separately: round 5 verified F1 — the item most likely to be a third wrong correction of the same comment — link by link against service_worker.js, and proved unreachable the specific exception it was asked to hunt (a stamp coexisting with streaming true via mediaType === "video", foreclosed by stampDownloadUrl's !srcUrl guard). It also validated the ASCII hygiene guard with a negative control. The code half of this PR is settled.

Left open, deliberately: the two 🟢 non-findings round 5 declined to raise, and any further prose polish. If a future reader finds another false sentence in these files, it is unaudited, not cleared.

ZacxDev added a commit that referenced this pull request Sep 4, 2026
…very round after the first foun

Claude-Session-Id: e13f042e-3be9-449d-a597-4b9a7ea7d259
ZacxDev added a commit that referenced this pull request Sep 11, 2026
… and it is 655 interior, 3,727 tail (#1519)

* feat(audit-pr): report the churn no audit-claims block range covers

`claudedocs/audit-ladder-review-2026-09-04.md` measured each block's own
`from..to` range — correct per the header semantics, and only able to see the
churn the blocks CHAIN ACROSS. The review names the hole against itself: #1233
posted blocks for rounds 1, 2 and 4, and round 3's fixes sit in no block's
range, so they are in no column of its table. #1108 and #1219 both start at
round 2, putting their round-1 churn outside every range too. Its open item 5
asks for exactly this report.

`scripts/ladder-range-coverage.py` classifies every adjacency in a ladder's
chain of block ranges — TIGHT / GAP / OVERLAP / UNRELATED, plus the tail from
the last block to the head — and prints the GAP churn as the uncovered total.
OVERLAP and UNRELATED exist because neither is a gap with a size: an overlap is
the opposite error (two ranges double-counting), and two shas that do not reach
each other have no measurable distance. Reporting either as a 0-line gap would
hide it.

The churn command is NOT re-typed. `measure_range_churn` is extracted from
`audit-dispatch.py::measure_ledger` and called by both, so the report cannot
drift from the command the skill tells an auditor to run. The extraction keeps
all four read rules at four: rc 0 and silent stderr move into the shared core,
while `head_check` and the non-empty-range rule stay in `measure_ledger` —
deliberately, because the two callers want OPPOSITE things from an empty range.
For a delta round it is a broken question; for the gap between two blocks it is
the HEALTHY answer, and a core enforcing the delta rule would report every
well-formed ladder as unmeasurable.

A zero is refused when it cannot be told from a broken run: "every adjacency is
TIGHT" and "none of this PR's commits are in this checkout" both print 0, so at
least one block's own range must measure non-zero churn before any total is
reported (exit 4 otherwise). A malformed block and a bare `audited=<sha>` are
reported as the same hole by another route, with no size, since neither has a
second sha to measure to.

The window is [first block's `from`, head] on purpose — churn before the first
block is the PR's own work, not ladder work. A ledger starting at round 2 is
told it is missing round 1 rather than having a number invented for it.

Scope: this reports RAW lines. The payload/scaffolding split in that review was
made by hand per PR and no pathspec can make it, so this measures the SIZE of
the hole, not the payload the review under-counted by.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019cCU2P9PuuXRtkKWzAJrR5
Claude-Session-Id: 1f26b4b9-9085-4762-88be-cad876bebe2d

* feat(audit-pr): split interior from tail, and close the review's open item 5

Running the instrument over the 2026-09-04 review's own 20 ladders found that
one total would be misread, so it now reports two.

  INTERIOR  655 lines in 2 of 20 ladders — a round posted a block, a later round
            anchored past it, and nobody audited between. Unambiguous.
  TAIL      3,727 lines in 11 of 20 — churn after the LAST block. This conflates
            fixes posted after the final block (the ladder should have seen
            them) with development that continued after the ladder ended (it
            should not), and nothing in the ranges separates the two.

The tail is 6x the interior, so the 4,382 sum would have been quoted as an
under-count it does not support. The renderer prints the split above the
caveat and says which half is safe to quote.

Three findings recorded in the review doc, two of which it did not anticipate:

  * a SECOND interior gap exists and was named nowhere — #998 round 1 to round
    2, 3426590..0aecdbf, 131 lines. The review named only #1233.
  * the TAIL class is absent from the review entirely.
  * #958's "9 ranged blocks" is 8: its round-1 block is a bare `audited=<sha>`,
    which names no range at all.

And its claim that #958 chains end-to-end HOLDS — all 8 adjacencies TIGHT, 0
uncovered, alongside #1219, #1286, #1120, #1181, #1207 and #989. Those zeros
are the negative control: a dead detector prints 0 for them too, which is why
the positive control (#1233's gap, 524 lines at the exact range the review
names) is what makes them mean anything.

Three tail gaps are many commits at ZERO lines (#1064 125, #1274 10, #1110 7) —
`--not <base>` correctly excluding an upstream bring-in, shape A of the
reference file's range table. A commit count is not a churn count, and the
report says so.

Open item 5 is marked CLOSED with its residual stated rather than dropped: the
window excludes churn BEFORE the first block, so a ladder whose ledger starts at
round 2 still has round-1 churn unmeasured. The script reports that per ladder
instead of inventing a number.

The reference file now points at the instrument from the section documenting the
single-round range forms, since that is where a reader deciding how to measure
churn lands — and records that the review's own instrument was a scratchpad
variant that no longer exists, which is why this one is committed.

Battery: 19 rows, all as expected, including both SURVIVES controls. The floor
pin fired on its first growth (18 to 19 tests) and named its replacement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019cCU2P9PuuXRtkKWzAJrR5
Claude-Session-Id: 1f26b4b9-9085-4762-88be-cad876bebe2d

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
ZacxDev added a commit that referenced this pull request Sep 12, 2026
MEASURED, and it is the reason this exists: of the SIX genuine
`tekton/devrc-pytests` failures in the whole post-#1458 window, THREE — half —
were the same test on PRs 17, 40 and 40 commits behind `main`, all since merged,
and that test passes on current `main`. An earlier sample had 8 of 8 failing
open PRs 5-42 commits behind, with a rebase curing 4 outright. Nothing blocks a
merge here, so the only value the gate has is whether a human believes a red —
and half the genuine reds are already-fixed failures a human then debugs against
a diff that cannot reach them.

`scripts/stale-base-triage.py` answers that deterministically, running no tests:
it reads the NEWEST `tekton/devrc-pytests` status per context, maps each failing
test named in the description to the ONE file defining it, and emits the evidence
pair — the PR head's copy of that file is byte-identical to the merge-base's (so
this change never touched it) while `main` HAS moved it, in commits absent from
the head. Verdict per PR: INHERITED / NOT EXPLAINED BY STALENESS / COULD NOT
MEASURE.

Measured live, read-only, over all 51 open PRs: 20 red heads, 3 INHERITED
(#1286, #1194, #1038), 2 NOT EXPLAINED, 15 COULD NOT MEASURE, 4 broken-gate
`error` rows counted on their own line. That non-zero 3 is the positive control
for the count: a zero here would otherwise be indistinguishable from an
instrument wired to nothing.

🔴 REPORT ONLY BY DEFAULT. `COMMENT_MODE_DEFAULT` is the literal `"off"`, pinned
by `test_the_comment_mode_default_is_the_LITERAL_off` both as a value and as
source text, so arming the PR-comment writer costs a visible line in the arming
commit. Nothing was posted to GitHub at any point.

Traps this repo has already paid for, each handled and each pinned by a test:
* `/commits/{sha}/status` (SINGULAR) maps `error` onto `failure` — never read.
  Only the plural list endpoint, which carries no roll-up field at all.
* the list is NEWEST-FIRST — folded on `max(created_at)`, order-independent.
* `error` is a broken gate, not a code failure — its own summary line, never in
  the red total.
* a SQUASH merge never makes a head an ancestor of its base; ancestry is asked
  only about the PR head, and every "this landed" claim is made by blob OID.
* `git diff --quiet <ref> -- <path>` exits 0 when the path exists on NEITHER
  side; existence is proved with `git cat-file -e` first, pinned by an AST scan
  of the git invocations rather than a string search that its own comment would
  satisfy.
* the 140-char description cap means a description can prove "at least one test
  failed and here is its name" but never "these are all of them" — so an
  INHERITED verdict requires `failed=N` to have survived AND to equal the number
  of names. That is conservative on purpose: dismissing a real red is the one
  error this tool must not make.
* the test-name -> file mapping is searched, never guessed, with a distinct
  outcome for not-found, ambiguous, class-qualified, parametrised and
  cap-truncated names.

Testing. 56 tests; RED at base `60194765` (the module cannot collect — the
script does not exist there), GREEN at HEAD. Three enumerated mutation rounds
under PYTHONDONTWRITEBYTECODE=1, mutants derived from the AST plus targeted
textual ones: round 1 74 mutants / 15 survivors AND THE POSITIVE CONTROL
SURVIVED, which is what found that `CONTEXT` was pinned nowhere; round 2 82 / 3
with the control killed; round 3 81 mutants, 78 killed, 0 survived, control
killed. Round 1's survivors also found a real defect — the `on_main` half of the
evidence pair was a guard that can never run (`commits_touching` already walks
`merge-base..main`), so it is now measured and printed for verification rather
than gated on, and `not in_head` moved into `prove_candidates` where a test can
reach it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQx16RMr8YQYBfsXaBEsSm
Claude-Session-Id: a7f5b63b-6de5-4fdc-9814-c8a8fc7b2ba0
ZacxDev added a commit that referenced this pull request Sep 12, 2026
MEASURED, and it is the reason this exists: of the SIX genuine
`tekton/devrc-pytests` failures in the whole post-#1458 window, THREE — half —
were the same test on PRs 17, 40 and 40 commits behind `main`, all since merged,
and that test passes on current `main`. An earlier sample had 8 of 8 failing
open PRs 5-42 commits behind, with a rebase curing 4 outright. Nothing blocks a
merge here, so the only value the gate has is whether a human believes a red —
and half the genuine reds are already-fixed failures a human then debugs against
a diff that cannot reach them.

`scripts/stale-base-triage.py` answers that deterministically, running no tests:
it reads the NEWEST `tekton/devrc-pytests` status per context, maps each failing
test named in the description to the ONE file defining it, and emits the evidence
pair — the PR head's copy of that file is byte-identical to the merge-base's (so
this change never touched it) while `main` HAS moved it, in commits absent from
the head. Verdict per PR: INHERITED / NOT EXPLAINED BY STALENESS / COULD NOT
MEASURE.

Measured live, read-only, over all 51 open PRs: 20 red heads, 3 INHERITED
(#1286, #1194, #1038), 2 NOT EXPLAINED, 15 COULD NOT MEASURE, 4 broken-gate
`error` rows counted on their own line. That non-zero 3 is the positive control
for the count: a zero here would otherwise be indistinguishable from an
instrument wired to nothing.

🔴 REPORT ONLY BY DEFAULT. `COMMENT_MODE_DEFAULT` is the literal `"off"`, pinned
by `test_the_comment_mode_default_is_the_LITERAL_off` both as a value and as
source text, so arming the PR-comment writer costs a visible line in the arming
commit. Nothing was posted to GitHub at any point.

Traps this repo has already paid for, each handled and each pinned by a test:
* `/commits/{sha}/status` (SINGULAR) maps `error` onto `failure` — never read.
  Only the plural list endpoint, which carries no roll-up field at all.
* the list is NEWEST-FIRST — folded on `max(created_at)`, order-independent.
* `error` is a broken gate, not a code failure — its own summary line, never in
  the red total.
* a SQUASH merge never makes a head an ancestor of its base; ancestry is asked
  only about the PR head, and every "this landed" claim is made by blob OID.
* `git diff --quiet <ref> -- <path>` exits 0 when the path exists on NEITHER
  side; existence is proved with `git cat-file -e` first, pinned by an AST scan
  of the git invocations rather than a string search that its own comment would
  satisfy.
* the 140-char description cap means a description can prove "at least one test
  failed and here is its name" but never "these are all of them" — so an
  INHERITED verdict requires `failed=N` to have survived AND to equal the number
  of names. That is conservative on purpose: dismissing a real red is the one
  error this tool must not make.
* the test-name -> file mapping is searched, never guessed, with a distinct
  outcome for not-found, ambiguous, class-qualified, parametrised and
  cap-truncated names.

Testing. 56 tests; RED at base `60194765` (the module cannot collect — the
script does not exist there), GREEN at HEAD. Three enumerated mutation rounds
under PYTHONDONTWRITEBYTECODE=1, mutants derived from the AST plus targeted
textual ones: round 1 74 mutants / 15 survivors AND THE POSITIVE CONTROL
SURVIVED, which is what found that `CONTEXT` was pinned nowhere; round 2 82 / 3
with the control killed; round 3 81 mutants, 78 killed, 0 survived, control
killed. Round 1's survivors also found a real defect — the `on_main` half of the
evidence pair was a guard that can never run (`commits_touching` already walks
`merge-base..main`), so it is now measured and printed for verification rather
than gated on, and `not in_head` moved into `prove_candidates` where a test can
reach it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQx16RMr8YQYBfsXaBEsSm
Claude-Session-Id: a7f5b63b-6de5-4fdc-9814-c8a8fc7b2ba0
ZacxDev added a commit that referenced this pull request Sep 12, 2026
…red (#1524)

* feat(ci-signal): name the commit that already fixed a PR's inherited red

MEASURED, and it is the reason this exists: of the SIX genuine
`tekton/devrc-pytests` failures in the whole post-#1458 window, THREE — half —
were the same test on PRs 17, 40 and 40 commits behind `main`, all since merged,
and that test passes on current `main`. An earlier sample had 8 of 8 failing
open PRs 5-42 commits behind, with a rebase curing 4 outright. Nothing blocks a
merge here, so the only value the gate has is whether a human believes a red —
and half the genuine reds are already-fixed failures a human then debugs against
a diff that cannot reach them.

`scripts/stale-base-triage.py` answers that deterministically, running no tests:
it reads the NEWEST `tekton/devrc-pytests` status per context, maps each failing
test named in the description to the ONE file defining it, and emits the evidence
pair — the PR head's copy of that file is byte-identical to the merge-base's (so
this change never touched it) while `main` HAS moved it, in commits absent from
the head. Verdict per PR: INHERITED / NOT EXPLAINED BY STALENESS / COULD NOT
MEASURE.

Measured live, read-only, over all 51 open PRs: 20 red heads, 3 INHERITED
(#1286, #1194, #1038), 2 NOT EXPLAINED, 15 COULD NOT MEASURE, 4 broken-gate
`error` rows counted on their own line. That non-zero 3 is the positive control
for the count: a zero here would otherwise be indistinguishable from an
instrument wired to nothing.

🔴 REPORT ONLY BY DEFAULT. `COMMENT_MODE_DEFAULT` is the literal `"off"`, pinned
by `test_the_comment_mode_default_is_the_LITERAL_off` both as a value and as
source text, so arming the PR-comment writer costs a visible line in the arming
commit. Nothing was posted to GitHub at any point.

Traps this repo has already paid for, each handled and each pinned by a test:
* `/commits/{sha}/status` (SINGULAR) maps `error` onto `failure` — never read.
  Only the plural list endpoint, which carries no roll-up field at all.
* the list is NEWEST-FIRST — folded on `max(created_at)`, order-independent.
* `error` is a broken gate, not a code failure — its own summary line, never in
  the red total.
* a SQUASH merge never makes a head an ancestor of its base; ancestry is asked
  only about the PR head, and every "this landed" claim is made by blob OID.
* `git diff --quiet <ref> -- <path>` exits 0 when the path exists on NEITHER
  side; existence is proved with `git cat-file -e` first, pinned by an AST scan
  of the git invocations rather than a string search that its own comment would
  satisfy.
* the 140-char description cap means a description can prove "at least one test
  failed and here is its name" but never "these are all of them" — so an
  INHERITED verdict requires `failed=N` to have survived AND to equal the number
  of names. That is conservative on purpose: dismissing a real red is the one
  error this tool must not make.
* the test-name -> file mapping is searched, never guessed, with a distinct
  outcome for not-found, ambiguous, class-qualified, parametrised and
  cap-truncated names.

Testing. 56 tests; RED at base `60194765` (the module cannot collect — the
script does not exist there), GREEN at HEAD. Three enumerated mutation rounds
under PYTHONDONTWRITEBYTECODE=1, mutants derived from the AST plus targeted
textual ones: round 1 74 mutants / 15 survivors AND THE POSITIVE CONTROL
SURVIVED, which is what found that `CONTEXT` was pinned nowhere; round 2 82 / 3
with the control killed; round 3 81 mutants, 78 killed, 0 survived, control
killed. Round 1's survivors also found a real defect — the `on_main` half of the
evidence pair was a guard that can never run (`commits_touching` already walks
`merge-base..main`), so it is now measured and printed for verification rather
than gated on, and `not in_head` moved into `prove_candidates` where a test can
reach it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQx16RMr8YQYBfsXaBEsSm
Claude-Session-Id: a7f5b63b-6de5-4fdc-9814-c8a8fc7b2ba0

* fix(stale-base-triage): the verdict was a function of the failing test's NAME LENGTH (F2)

`names_provably_complete` could only prove completeness by reading `failed=N`
out of the status description. `failed=` is the LAST field in the gate's banner
and GitHub caps a description at 140 BYTES, so whether it survives is decided by
how long the failing test's name is — and on the three PRs this tool's
requirement was measured on (#1454, #1462, #1499) it did not survive. All three
resolved to COULD NOT MEASURE with the correct answer already in the row. The
tool fired on 0 of the 3 cases that justified it.

The fix is a SECOND route, and it is a proof rather than a heuristic.
`scripts/run-tests.sh` GUARD 4 defines

    collected = passed + skipped + failed + errors + xfailed + xpassed
    TOT_FAILED += failed + errors

so `collected - passed - skipped == failed + xfailed + xpassed >= failed`, and
names are a subset of the failures, so `len(names) <= failed <= derived`. When
`derived == len(names)` the inequality is squeezed shut and completeness is
PROVEN. `collected=`, `passed=` and `skipped=` all precede `failed=`, so they
are exactly the fields that survive the cut. The direct `failed=N` route is kept
and still tried first, because it is the stronger claim.

🔴 THE BOUND IS AN OVER-COUNT, NEVER AN UNDER-COUNT, and that direction is the
whole safety argument: an under-count would certify a completeness the row does
not have and dismiss a real red. `xfailed`/`xpassed` can only inflate it, and
`_TOTALS_RE` demands the three fields ADJACENT AND IN ORDER so that a number the
cap cut SHORT cannot shrink it — `collected=` and `passed=` are each proven
intact by having matched banner text after them, and `skipped=`, the only one
that can be short, subtracts LESS and so errs toward withholding.

MEASURED, read-only, 2026-09-11 (provably-complete rows / red heads):

  population                                   old      new
  100 most-recent closed PRs                   5/28    18/28
  - the three justifying PRs #1454 #1462 #1499  0/3      3/3
  58 open PRs                                  4/26     7/26
  - #1518, #1515 (9 commits behind, worked)     0/2      2/2

SOUNDNESS CONTROL on the same real rows: 17 carried BOTH a visible `failed=N`
and a derivable bound; the two agreed EXACTLY on all 17, with zero under-counts.
Genuine multi-failure reds stay withheld — #1177 derives 39 against one name,
#1280 failed=5, #1440 failed=3, all still COULD NOT MEASURE.

The coupling to `run-tests.sh` is pinned mechanically rather than asserted:
`test_the_run_tests_collected_arithmetic_this_derivation_rests_on_is_pinned`
reads the `collected=$((…))` and `TOT_FAILED=$((…))` expressions out of the
shell source and checks the CONTAINMENT relation, so renaming the shell locals
is fine and changing WHAT IS SUMMED is not. What it cannot check — that
`xfailed`/`xpassed` are non-negative — is stated in the test, not asserted.

Prior art is now cited in the header: `main-status-watch.py`'s
`screen_all_known_flakes` is the same completeness gate in 15 lines, and its
"would have fired ZERO times on 100 commits" is the same `failed=N` route being
eaten by the same cap. This file's contribution is the derived route and the
blob/commit evidence half; the gate itself is prior art and says so.

Red/green matrix: 13 of the 14 new tests fail at 5ad08e5 and pass at HEAD; the
14th is labelled in its own docstring as an invariant guard, not regression
coverage. Suite: 56 -> 70 tests, all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQx16RMr8YQYBfsXaBEsSm
Claude-Session-Id: a7f5b63b-6de5-4fdc-9814-c8a8fc7b2ba0

* test(stale-base-triage): close the three gaps the F2 mutation sweep found

Round 1 of the sweep on the new completeness logic killed 10 of 15. Four of the
five survivors were real gaps in the tests, not in the code:

* M14 — reordering `passed=`/`skipped=` in `run-tests.sh`'s TOTAL banner
  SURVIVED, because the pin searched the whole FILE and `run-tests.sh` prints
  TWO banners (a full-run one and a SCOPED one). Mutating one was satisfied by
  the other. The pin now requires EVERY banner line to carry the ordered
  fields, and asserts it found at least two.
* M11 — spelling the `skipped` group `(\d*)` SURVIVED: no fixture fed a row cut
  exactly on the `=`, which is what a cap landing one character early produces
  and which makes `int("")` RAISE on the reporting path. Added as a fixture.
* M10 — replacing the underivable-row refusal with a bound of 0 SURVIVED,
  because `names` is non-empty by then so 0 can never equal it and the VERDICT
  is identical. Only the operator-facing reason distinguishes them, and the two
  refusals call for different next steps, so both reasons are now pinned as
  whole strings.
* M5b — the naive three-separate-searches spelling of the totals regex is now
  driven explicitly and is killed by the existing out-of-order fixture.

Two survivors are argued EQUIVALENT rather than fixed, and are labelled as such
in the source:

* M5 — matching the three fields with `.*` between them instead of adjacently.
  `.*` still requires the ORDER, and a single-banner description contains
  exactly one of each field, so no input this parser can receive distinguishes
  them. The adjacency is kept for the truncation argument it encodes.
* M13 — `run-tests.sh` SHRINKING what it sums into `failed=` keeps the derived
  value an UPPER bound, so the derivation stays sound. The pin checks
  CONTAINMENT on purpose; the added comment says so, and M15 (a term LEAVING
  `collected` — the change that does break it) is killed by the same pin.

Round 2 over the fixed tree: 6/8 killed, the two above surviving as argued, no
new finding — so the ladder ends there. Both controls held in both rounds
(unmutated tree green; `COMMENT_MODE_DEFAULT = "on"` killed by four tests).
Run under PYTHONDONTWRITEBYTECODE=1, each edit verified to have changed the
file, verdicts read from the runner's own result lines.

Suite: 70 -> 71 tests, all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQx16RMr8YQYBfsXaBEsSm
Claude-Session-Id: a7f5b63b-6de5-4fdc-9814-c8a8fc7b2ba0

* refactor(ci-signal): one rule, one place — and the two copies had already diverged (D6)

`classify` in `stale-base-triage.py` was BYTE-IDENTICAL to the one in
`main-status-watch.py`. `_FAILING_RE`, `_FAILED_COUNT_RE`, `parse_failing_names`
and `parse_failed_count` were duplicated AND already divergent — only the newer
copy stripped the `TOTAL` truncation fragment, and only the newer copy folded
statuses by timestamp instead of by array position. The disagreement is the
finding; consolidating is what made it audible.

All five now live in `scripts/lib/ci_status.py`, with
`derived_failure_upper_bound` beside them. Both files import it by path. What
stays in each consumer is POLICY — which context may speak, which names are
known flakes, what completeness means, what to do about a red — because the two
answer those differently.

🔴 `main-status-watch.py` IS LIVE ON BOTH HOSTS, every 10 minutes, so each
divergence was reconciled deliberately rather than by taking whichever copy was
newer:

* THE FOLD. The live copy kept the FIRST row per context, which is correct only
  because GitHub returns the array newest-first — an assumption about someone
  else's response ordering, load-bearing, unstated and untested. (⚠ It is NOT
  the "yields the OLDEST post" bug: first-wins over a newest-first array yields
  the NEWEST. The defect is order-DEPENDENCE, not a wrong row today.) It now
  folds on `max(created_at)`, which agrees with first-wins on GitHub's current
  ordering and stays right if that changes. With no `created_at` anywhere every
  stamp compares equal and the FIRST row wins — the old rule, as the fallback.
  `test_newest_per_context_is_ORDER_INDEPENDENT_which_first_wins_was_not`
  reimplements the old rule and shows it returning the `pending` row on the
  order it does not expect, beside the new one right on both.
* THE CONTEXT FILTER moved out of the fold and into `commit_verdict`, where the
  policy belongs, and BEFORE the emptiness check — a commit carrying only a
  foreign pipeline's rows must have NO verdict, never green, or an unrelated
  green would close an open red episode. Pinned by a new unit test beside the
  existing end-to-end one.
* THE isinstance SKIP. The live copy deliberately had none, arguing the arm was
  unreachable (its walk validates every row is a dict and names the commit if
  not) and that silently dropping a row could drop the red. The shared function
  keeps the triage tool's skip: still unreachable for the watcher, unchanged
  behaviour for the triage tool. Neither contract moves; the module says why.
* THE FRAGMENT STRIP is now shared, and is provably INERT for the watcher: a row
  cut at `| TOTA` was necessarily cut before `failed=N` too, so its screen —
  which also demands the count — refuses either way.
* THE DERIVED COMPLETENESS ROUTE is deliberately NOT adopted by the watcher, and
  the file now says why: there, proving completeness makes the tool SPEAK; here
  it makes it stay SILENT about a red. Widening a silence over main's only
  automated detector is a change to make on purpose with its own measurement,
  not a free win inherited from a sibling.

Duplication is now refused mechanically:
`test_the_shared_predicates_are_NOT_re_declared_in_either_consumer` fails on a
local `def` of any of the five, in either file, and carries a positive control
so the scan cannot pass vacuously.

TWO PRE-EXISTING TESTS BROKE, both for the right reason, and both were fixed
rather than weakened: one used `def classify(...)` as its positive control for
the docstring-stripper (the function is simply elsewhere now — it uses
`commit_verdict` instead), and one copies the script to a tmp dir and runs it,
which has no sibling `lib/` (it now passes PYTHONPATH, and says why).

DEPLOYMENT. The unit runs the script straight out of the checkout
(`ExecStart=… %h/workspace/devrc/scripts/main-status-watch.py`), so a `git pull`
delivers both files and no home-manager switch is involved. A run landing in the
instant between the two files arriving fails to import; the unit is a
timer-driven oneshot with no `OnFailure=` toast and the next poll is 10 minutes
away, so that window costs one skipped poll. ⚠ `X-Restart-Triggers` in
`nix/home.nix` still names only `main-status-watch.py` and was deliberately NOT
edited — for a timer-driven oneshot that re-reads the file every run it is
cosmetic, and touching `home.nix` here would widen this change's blast radius
for no behavioural gain.

`test_main_status_watch.py`: 96 -> 99 tests, all green.
`test_stale_base_triage.py`: 71 tests, all green. Both suites: 170 green.
No live-system change was made: the deployed copy is the base clone on `main`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQx16RMr8YQYBfsXaBEsSm
Claude-Session-Id: a7f5b63b-6de5-4fdc-9814-c8a8fc7b2ba0

* refactor(stale-base-triage): delete --fetch and --json; keep --sweep, --help and the env seams (D1-D5)

The six deletion candidates were assessed against the BROKEN tool, so each was
re-measured after F2 rather than acted on. Decisions, with the evidence:

DELETE `--fetch`. It was the only git subcommand in this file that WRITES, in a
tool whose stated safety property is that it writes nothing, and `refs/` lives
in the COMMON git dir — a worktree gives zero isolation there, so "namespaced
under refs/stale-base-triage/*" was a weaker claim than the one the header
makes. MEASURED: this repo's PRs are same-repo branches and `origin`'s refspec
is `+refs/heads/*:refs/remotes/origin/*`, so an ordinary `git fetch origin`
already brings every head — 58 of 58 open PR heads resolved in the base clone
with no `--fetch` anywhere. It bought nothing and cost the tool its absolute
safety claim. The UNMEASURED message now says `git fetch origin`.

The guard got STRONGER, not weaker: `test_only_refs_under_its_own_namespace_are_
ever_written` (a relative claim) is replaced by `test_no_git_subcommand_that_
WRITES_is_ever_invoked`, which enumerates the read-only subcommands as an
ALLOWLIST off the AST — so a subcommand nobody enumerated is a write by default,
and `remote` is admitted only in its `get-url` form.

DELETE `--json`. No consumer: a whole-repo search for `stale-base-triage` /
`STALE_BASE_TRIAGE` returns this script and its test file, nothing else — no
unit, no skill, no nix, no caller. F2 does not change that; it makes the
RENDERED sweep useful, not a machine payload. It cost a module-level
`_JSON_MODE`, a `say()` indirection on every human line in the file, and a
second exit path computing `n_inherited` its own way.

KEEP `--sweep`, and it is now the primary mode. Its rationale is exactly what F2
reversed: MEASURED read-only over 58 open PRs, the sweep surfaces 7 provably
complete reds where it surfaced 4, and the three it gained (#1518, #1515, #1450)
are 9-94 commits behind and actively worked — not the 259/357/508-behind
abandoned ones the old gate happened to let through.

KEEP the self-parsing `--help`. What it prints is the `#` header — the env
ledger, the exit codes, the measured rationale — which argparse cannot see. The
alternative is copying the ledger into an epilog, i.e. reintroducing the
duplication the previous commit just removed, and the ledger is two-way pinned
against the code that reads it.

KEEP all four env seams and the ledger test. `STALE_BASE_TRIAGE_GH` and
`_REPO` are the seams the test harness points at a stub — they are why no test
in this file can reach real GitHub. `_BUDGET` is the only way to drive the
budget guard to zero. `_COMMENT_MODE` is the arming seam and FAILS CLOSED
across seven spellings. Each has a concrete in-repo consumer; none is
speculative.

`COMMENT_MODE_DEFAULT` is untouched: still the literal `"off"`, still pinned
both ways (value and source text). Nothing was posted to GitHub; every live run
in this work was read-only with mode=off.

VERIFIED AGAINST THE ORIGINAL SYMPTOM, on live data, not inferred from the code:
  old code (5ad08e5), `--pr 1518`:  VERDICT: COULD NOT MEASURE,  rc 0
  this tree,           `--pr 1518`:  VERDICT: INHERITED,          rc 10
and a four-PR read-only sweep returns INHERITED for #1518/#1515/#1450 with the
fix commits named, while #1177 (derived 39 against one name) stays COULD NOT
MEASURE with the HINT line.

⚠ The deleted-flag guard was SPELLED on its first draft and failed on the header
paragraph that explains the deletion — the same trap `_git_subcommands` is
AST-based to avoid. It now reads `add_argument` literals, module-level
assignments and string constants off the AST, so prose can neither satisfy nor
break it.

Suite: 71 tests, all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQx16RMr8YQYBfsXaBEsSm
Claude-Session-Id: a7f5b63b-6de5-4fdc-9814-c8a8fc7b2ba0

* docs(stale-base-triage): the cap is 140 BYTES, and the two files disagreed about it

`scripts/lib/ci_status.py` said BYTES and `stale-base-triage.py` said
CHARACTERS, in prose describing the same boundary. Bytes is what was measured:
every real row sampled on this repo is 138 CHARACTERS, which is 140 bytes once
the `—` in `FAILED: pytests —` is counted as the three UTF-8 bytes it is. A
140-CHARACTER cap would have produced 138-byte, 140-character rows, and none
was seen.

⚠ WHAT THAT MEASUREMENT DOES NOT SETTLE, now said in the file rather than
implied: whether the byte cut is GitHub's or the posting pipeline's own
truncation. Every row sampled carries exactly one multi-byte character, so the
two hypotheses were never separated — a row with two em-dashes would do it, and
none exists. The boundary is measured; its owner is not.

It matters because a fixture built on a character cut lands two bytes late and
quietly leaves `failed=` readable — i.e. it would exercise the DIRECT route
while claiming to test the derived one, which is a green proving nothing.
`cut_like_github` in the test file cuts on bytes for exactly that reason and
`test_CONTROL_the_synthetic_rows_land_on_the_real_140_BYTE_boundary` asserts
both lengths.

No behaviour change. Suite: 71 tests, all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQx16RMr8YQYBfsXaBEsSm
Claude-Session-Id: a7f5b63b-6de5-4fdc-9814-c8a8fc7b2ba0

* test(no-real-launchers): the consolidation made main-status-watch.py a `home-manager` hit — acknowledged and PINNED

`test_every_hazardous_binary_the_scripts_reach_is_stubbed_or_acknowledged` went
red on the D6 commit, and it was right to. `launcher_scan.hazard_hits` is a TEXT
scan over `scripts/` and says so in its own docstring — a prose mention counts,
deliberately and fail-closed. The shared-module import comment explains that the
systemd unit runs this file straight out of the checkout, so a `git pull`
delivers both files and no home-manager switch is involved, and that sentence is
exactly why importing a sibling module is safe here. It is the FIFTH file of
this shape.

🔴 RE-JUSTIFIED, NOT REWORDED, which is this repo's stated convention and is
written into the entry above the table — rewording to dodge the scanner removes
the sentence and keeps the risk.

🔴 AND PINNED, because that table entry says in its own words that an
acknowledgement "would otherwise blind the guard" — and it has been blinded
before: adding a real `home-manager switch` to a script whose name was already
acknowledged for other reasons produced a real launch with 54 guard tests green.
So two new tests carry this row:

  test_main_status_watch_SPAWNS_these_argv0_AND_NOTHING_ELSE — an AST walk
  asserting the spawn argv[0] set is exactly {git, <computed>, <not-a-list>},
  GROWS-OR-SHRINKS. Both opaque entries are named rather than waved at:
  `<computed>` is `[gh, "api", path]` behind the MAIN_STATUS_WATCH_GH stub seam,
  `<not-a-list>` is `trigger_deadman`'s `cmd`, whose production literal is
  pinned exactly — and refused a `--force` — by an existing test. The comment
  says outright that this pin is not the whole story on its own.

  test_home_manager_is_MENTIONED_but_never_SPAWNED — both halves of the
  acknowledgement's claim: the mention must still EXIST (or the row has outlived
  its sentence) and it must remain a MENTION, asserted against the argv[0] set
  AND against `_code_only()`, which strips comments and docstrings by AST.

BOTH CONTROLS WATCHED, in both directions:
  * injecting `subprocess.run(["home-manager", "switch"])` fails BOTH new tests,
    each with its own message — not a different guard's;
  * rewording the mention to "no HM switch" fails the mention-must-exist half
    AND the ledger's own file-set pin, because the row then over-claims.

⚠ FOUND BY RUNNING THE WHOLE `scripts/tests` TARGET, not by the change-scoped
mapping: `scoped-tests.sh` selected only the two suites named in the diff, and
this guard reads every file under `scripts/` regardless of what changed. A
change that only adds a COMMENT can go red here.

scripts/tests/{test_no_real_launchers,test_main_status_watch,test_stale_base_
triage}.py together: 252 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQx16RMr8YQYBfsXaBEsSm
Claude-Session-Id: a7f5b63b-6de5-4fdc-9814-c8a8fc7b2ba0

* fix(ci-signal): the consolidation moved code out from under the citation guard, and the first citation written into the new module dangled

ROUND 1 audit findings on #1524. Six filed, six addressed, plus one the same
fixture exposed in the sibling parser.

F1 — `scripts/lib/ci_status.py` cited
`test_newest_per_context_skips_a_malformed_row_rather_than_raising`, which
existed nowhere, and the arm it claimed was guarded was unguarded: deleting
`if not isinstance(row, dict): continue` SURVIVED all 172 tests. The deeper
cause is the guard, not the citation — both copies of
`test_every_test_this_script_names_actually_exists` are scoped to ONE script
file, so neither scanned the shared module the consolidation created. The
stale-base-triage copy now FOLLOWS its script's `scripts/lib` imports (derived,
not enumerated, so a second shared module is covered the day it appears) with a
positive control that fails if the import-following reaches nothing; the
main-status-watch copy names the owner rather than implying coverage it lacks.
The cited test is now written, and it kills the mutant.

F2 — `_git_subcommands` silently DROPPED what it could not constant-fold, so
its stated rule ("an unknown git subcommand is a write by default") was wider
than what it enforced: a computed head was ABSENT, not DENIED. It now emits
`<computed>` / `<not-a-list>` / `<empty-argv>` / `<no-argv>` sentinels — the
shape `test_main_status_watch.py`'s `_spawn_argv0_literals` already used 200
lines away in this same PR — reads argv from the SECOND POSITIONAL rather than
"whichever argument is a list", and the allowlist is a predicate a test drives
over synthetic source. The bare-`subprocess.run(["git", "push"])` hole the
finding also names is closed separately: every git spawn must be lexically
inside the one `_git` helper, and the spawn argv0 set is pinned
GROWS-OR-SHRINKS.

F3 — the `gh` write path was pinned by the literal word POST, which
`gh api -X PATCH`, `-X PUT`, `--method DELETE`, `gh pr comment` and
`gh api -f` (a POST with no method flag at all) each walk straight past. It now
asserts the STATE: an AST ledger of the `gh` verbs the source can reach
(`("api",)` and the one `("api", "-X", "POST")`), plus a receipt classifier that
decides read-vs-write from the method and the field flags. The harness stub also
had to start recording ONE LINE PER INVOCATION — a comment body contains
newlines, so `calls()` was returning prose fragments as if they were argvs,
which was invisible while the only question asked of a line was whether it
contained POST.

F4 — `TOTALS_RE` was unanchored and `re.search` takes the FIRST match, so a
description carrying an earlier per-target row derives a bound of 2 where the
run's own `failed=` is 27. That is the UNDER-count direction, the one error the
module explicitly forbids, and `run-tests.sh` really does print such rows
(`FAIL <dir> (collected=… passed=… skipped=… failed=… errors=…)`) before the
TOTAL banner. Both parsers are now anchored on `TOTAL` — `FAILED_COUNT_RE` had
the identical defect and is reachable from the same string, which the audit did
not file; fixing one and not the other would have left the same certification
hazard on the direct route. Both anchors err toward REFUSING. No reachable
exploit is claimed: what assembles the description is the posting pipeline in
homelab-talos and cannot be pinned from here.

F5 — the "140 BYTES" reconciliation (02bc6f8) had not finished;
`main-status-watch.py` said "140 characters" nineteen lines above its own
"THE 140-BYTE CAP". Swept all five sites including the test NAME, and replaced
the prose with a control: the two real truncated rows are 138 chars / 140 bytes,
asserted. The honest unknown is kept intact — whether the cut is GitHub's or the
pipeline's is still undetermined, because every sampled row carries exactly one
multi-byte character.

F6 — `sys.path.insert(0, …/scripts/lib)` prepended the whole shared-module
directory ahead of the standard library for every later import, including the
lazy `import traceback` on the unattended-crash path. `append` removes the class.

M2 (deleting `if not ctx: continue`) is NOT filed: an `""` key cannot match
either caller's context, so it is an equivalent mutant.

Evidence.
  Suites: test_stale_base_triage 71 -> 78, test_main_status_watch 101 -> 102.
  395 passed across the four affected files (the only files in the repo that
  reference any changed path; nothing else imports `ci_status`).
  ⚠ scoped-tests.sh is NOT evidence here and its PASS is withdrawn: it printed
  RESULT: PASS against the PRE-REBASE base, and on current main it correctly
  REFUSES (exit 4) because the diff touches `scripts/lib/**` — scoping a shared
  module selects the files that NAME it and drops every target reaching it by
  import. `scripts/gate.sh --tier both` is what covers that gap.
  RED at pr1524-head: the F4 test fails with derived=2 against the real 27.
  Mutation: 19 mutants, 18 KILLED, 1 SURVIVED — and the survivor is M4
  (first-match -> last-match), which is EQUIVALENT once anchored because
  `TOTAL collected=` occurs at most once on any banner this repo can post. That
  is asserted in the test rather than left unexplained, and reachability was
  confirmed separately: breaking the same statement kills 39 tests. Positive
  control KILLED in the batch; swept under PYTHONDONTWRITEBYTECODE=1 with
  __pycache__ removed between mutants.

COMMENT_MODE_DEFAULT is unchanged ("off"), both pins intact, nothing posted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQx16RMr8YQYBfsXaBEsSm
Claude-Session-Id: a7f5b63b-6de5-4fdc-9814-c8a8fc7b2ba0

* test(stale-base-triage): the write screen read a comment BODY as flags, and three guards were never exercised

ROUND 2's five findings. No payload change — every one of these is a weakness in
a guard this PR itself wrote.

F-R2-1 `gh_write_calls` parsed argument VALUES as flags, last-wins. Measured on
the previous version, same call shape as the real `post_comment`:

    -X POST /…/comments -f body=hello                       -> WRITE (control)
    -X POST /…/comments -f body=switch to --method GET now  -> read  (WRONG)
    -X POST /…/comments -f body=use -X GET here             -> read  (WRONG)
    /repos/o/r/x --input=payload.json                       -> read  (WRONG)
    /repos/o/r/x -fbody=hello                               -> read  (WRONG)

All five now classify as writes. The method is FIRST-wins and is scanned only up
to the first field flag — everything after one is body text — while field flags
are scanned to the end, in both the separated and the ATTACHED spelling `gh`
accepts. It does NOT stop at the first `/`-prefixed positional the way
`_gh_argv_shapes` does: `gh api /repos/o/r/x -X POST` is legal and would read as
clean, which is the one direction that matters. Reachable, so it is pinned end to
end as well: `comment_body` interpolates a `main` commit SUBJECT, and the new
test drives a hostile one through the real script.

F-R2-2 the spawn pin folded every computed argv0 into one set member, so
`binary = "git"; subprocess.run([binary, "push"])` appended to the real source
passed all four guards unchanged. Computed spawn heads are now asserted by NAME
(every one is the `gh` seam) and by COUNT (cross-checked against
`_gh_argv_shapes`), and the violating source is driven over synthetically.

F-R2-3 `_spawn_argv0`'s `<not-a-list>` arm — the net for `os.system("git push")`
— was never exercised; the mutant replacing it with `pass` SURVIVED. Driven over
synthetic source now, like `_git_subcommands`' sentinels.

F-R2-4 both scripts' `sys.path.append` carried a 🔴 comment and no guard;
reverting either to `sys.path.insert(0, …)` SURVIVED. Pinned in each file's own
test, structurally.

F-R2-5 five sites named GitHub as the cutter of the 140-byte description cap
while the new control says the measurement cannot distinguish GitHub's cut from
the pipeline's. The measured half (BYTES, not characters) is unchanged
everywhere; only the attribution is dropped, and each site now points at
`test_CONTROL_the_real_truncated_rows_land_on_the_BYTE_cap_not_the_CHAR_cap`.
`GITHUB_DESCRIPTION_BYTE_CAP`/`cut_like_github` renamed to `DESCRIPTION_BYTE_CAP`
/`cut_at_the_byte_cap` for the same reason. The unknown is NOT resolved in either
direction.

MUTATION TABLE (isolated `cp -a` copy, `PYTHONDONTWRITEBYTECODE=1`,
`__pycache__` cleared per mutant, verdicts counted from pytest's own result
lines; baseline in the copy 187 passed / 0 failed):

  mutant                                    verdict  killed by
  CONTEXT literal -> devrc-NOPE (control)   KILLED   test_the_context_is_PINNED_…
  method last-wins again                    KILLED   …reads_an_ARGUMENT_VALUE_as_DATA…
  body-stop removed                         KILLED   …reads_an_ARGUMENT_VALUE_as_DATA…
  `--input=` leaves the prefix tuple        KILLED   test_EVERY_field_flag_…
  attached short forms leave the tuple      KILLED   test_EVERY_field_flag_…
  M5 `<not-a-list>` arm -> pass             KILLED   …ARGV_IS_NOT_A_LIST_is_a_SENTINEL…
  M6 sbt sys.path.insert(0, …)              KILLED   …APPENDED_to_sys_path… (sbt)
  M8 msw sys.path.insert(0, …)              KILLED   …APPENDED_to_sys_path… (msw)
  second computed spawn in the real file    KILLED   …SECOND_computed_spawn… + the pin
  non-Name head folded into "gh"            KILLED   …SECOND_computed_spawn…

Each died on its own guard's assertion, naming the row or expression mutated —
not on a neighbouring guard's error. `method last-wins` SURVIVED on the first
sweep: with the body-stop in place the two rules only differ on a value carried
by a NON-body flag, so a `-H` row was added and it dies there.

Suites: `test_stale_base_triage.py` 84 + `test_main_status_watch.py` 103 = 187
passed (180 at 3aec90b, +7 new tests). `test_no_real_launchers.py` +
`test_audit_dispatch.py`, the other two files naming these scripts, 215 passed.
`COMMENT_MODE_DEFAULT = "off"` and both pins untouched; nothing posted anywhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session-Id: a7f5b63b-6de5-4fdc-9814-c8a8fc7b2ba0

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Zachary Lowden <dev@vetr.com>
ZacxDev added a commit that referenced this pull request Sep 13, 2026
…reen them out (#1629)

MEASURED, on this tool's own first live sweep. It named five PRs
`INHERITED — likely cured by rebase`. That is a falsifiable claim, so each was
tested by rebuilding the merged tree (PR head + current origin/main) and running
ONLY the named failing test:

    #1450  1 passed          -> INHERITED was RIGHT
    #1286  1 passed          -> INHERITED was RIGHT
    #1603  1 failed          -> INHERITED was FALSE
    #1194  1 failed          -> INHERITED was FALSE
    #1038  merge conflict    -> untestable, excluded from the rate

2 of 4 testable verdicts were FALSE. `#1600`'s own unit block calls ONE false
INHERITED disqualifying for arming, so this blocks arming until it is fixed.

THE MECHANISM. The INHERITED test is "the failing test's own FILE is
byte-identical at the head and the merge-base, and main has moved it". That is
sound for a test which exercises code it NAMES, and systematically wrong for a
repo-wide census/scanner guard, which inspects OTHER files: there an unchanged
guard file is the NORMAL state of a genuine, self-inflicted breakage, because
the PR never touches the guard — it adds a file the guard scans. Both false
verdicts were the same census guard, and in both the offender was a NEW FILE the
PR itself adds (#1194's was its own scripts/tests/test_break_glass_merge.py,
confirmed absent from main, so a rebase would have carried the offender along
with the red).

THE FIX reuses the derivation `ledger-check.sh` already runs (14daa42) rather
than growing a second one: `testlib.census_scan.analyze()` computes, from the
AST, every test whose verdict depends on the repo's FILE SET. A hand-kept list
of "the census guards" would itself be a ledger and would rot exactly the way
the ledgers it describes did. Membership => demote to NOT EXPLAINED BY
STALENESS, which is the conservative arm this tool already has.

FAIL-SAFE DIRECTION, and the bug my own test found. `census_scan.analyze()` on a
mis-rooted path RETURNS AN EMPTY RESULT rather than raising. The first version
trusted that and would have answered "not a census guard" for every test in the
repo — failing OPEN into precisely the bug being fixed. The trip is
`parsed == 0`; an unbuildable index RAISES and the caller records COULD NOT
MEASURE. A count floor was tried first and was wrong: it turned every
end-to-end fixture repo into COULD NOT MEASURE, because a small repo with no
census guards is a TRUE answer, not a broken scan. The production-strength
claim lives in the suite instead, as a positive control.

Placed LAST, after the evidence is computed, so a demoted verdict still prints
the candidates that WOULD have justified INHERITED — that evidence is what made
the original false verdicts falsifiable in one command. It can only ever demote;
it can never manufacture an INHERITED.

COST. The derivation is ~28s against a sweep that ran in 35s, so the index is
built on FIRST ASK and reused: a sweep that reaches no INHERITED branch pays
nothing. Pinned by a test that asserts the screen is not consulted for a test
the blob comparison already rejected.

Tests: 106 passed (was 93 before this change). The new block is red at
origin/main — 9 failed there, all on the absent symbol. The BEHAVIOURAL delta is
pinned on both sides with the identical fixture: base asserts ALPHA is
INHERITED (test_an_INHERITED_red_names_the_commit_that_already_fixed_it, still
green here), and the new test asserts the same fixture demotes when the oracle
says census guard — the two differ ONLY in the oracle's answer, so the screen is
isolated and nothing else moves.

Also widens the sys.path guard from "exactly one append" to "every mutation is
an append, and there are two". A count was the wrong pin: it goes red on an
addition that OBEYS the rule while saying nothing about one that breaks it.


Claude-Session: https://claude.ai/code/session_01298sLVSsAeZJpGNJTftdWv
Claude-Session-Id: 9ca6b87d-3d18-4866-9247-6ea38a486603

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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