docs: repoint 73 backlog citations at the archive (BACKLOG #1095) - #279
Merged
Conversation
Retiring a backlog item moves it verbatim from docs/BACKLOG.md into
docs/archive/backlog/BACKLOG-CLOSED.md. Every citation that named the live file
keeps pointing at a file the item is no longer in. The link still resolves, so
nothing in CI can see it. #1094 fixed two such markers in CLAUDE.md section 12;
this is the same defect at repo scale.
73 citations across 34 files, href-only. No prose was rewritten. Visible labels
changed ONLY where leaving them would contradict the target -- a label reading
`BACKLOG.md` pointing at the archive -- and then only to `BACKLOG-CLOSED.md`.
THE TEST IS "DOES THE CITED FILE CONTAIN THE ITEM", NOT "IS THE ITEM CLOSED".
Those differ, and keying on closure would corrupt correct citations: #1073 is
closed and still legitimately in the live ledger. Item locations came from
parse_items imported from scripts/docs/backlog_status_check.py, per CLAUDE.md
section 11 -- never a hand-rolled scan of the banner alphabet.
DELIBERATELY NOT TOUCHED, each for a stated reason:
Both ledger files -- ZERO edits to docs/BACKLOG.md and BACKLOG-CLOSED.md.
Only two sites named them and both are excluded, so this change costs no
conflict against the merge trains or the pending #1096 filing. The one real
site (#322 at BACKLOG.md:2720) is left because the file is the most
contended in the repo and the item number is visible in plain text a search
away.
docs/CONNECTIONS.md:2436 -- the #27 serial/ASTM row. Already fixed on a branch
inside merge train #274. Sweeping it from origin/main would re-fix stale
text and collide.
QUOTATIONS OF THE DEFECT. docs/BACKLOG.md:6319, inside #1094, reads "Two of
its markers CITED [`docs/BACKLOG.md`](BACKLOG.md) #26 and #27" -- past
tense, describing rot that is already fixed. Repointing it would corrupt a
historical record. A regex cannot tell this from a live pointer, which is
the reason this was not done with sed.
THE WRONG-NUMBER CLASS, which is a different defect and must not be swept into
this one. ADR 0068:10 cites #11 and ADR 0113:9 cites #239; both numbers are
absent from the live ledger, but the ARCHIVE's #11 ("`check` dry-run
cross-products") and #239 ("Re-measure Steps view estate coverage") are
unrelated to WebAuthn passkeys and to a Windows tray manager respectively.
Repointing would convert a vague reference into a confidently wrong one that
lands the reader on the wrong item. Left, and reported.
MIXED-LOCATION LINKS, where one link covers items in both files so no single
target is correct: docs/AI-OFF-MATRIX.md:50 (six items), docs/adr/0001:13
(#1 archived, #3 live), THROUGHPUT-IMPROVEMENTS.md:215 (#62 live, so its
link is already correct).
VERIFICATION
- Plan applied by literal replacement on the named line only, requiring the
quoted string to occur EXACTLY ONCE there; a mismatch aborts rather than
fuzzy-matching. Dry run: 73/73 clean, 0 problems, before anything was
written.
- All 35 distinct anchor fragments introduced match exactly one real "## N."
heading in the archive, checked after applying, with a known-bad fragment
run through the same check to prove it can report a miss. Fragments were
derived with a slugger that does NOT collapse consecutive spaces -- the
doubled hyphens are correct, not typos.
- All 74 archive hrefs in the changed files resolve to the archive from their
own directory depth; the relative prefix differs by depth and was computed
per file, not pattern-matched.
- Coverage confirmed with a DELIBERATELY LOOSER regex than the one that built
the work list: it finds exactly one wrong-file site outside this change set,
docs/CONNECTIONS.md:2436, which is the intended exclusion.
- backlog_status_check.py: OK, 365 items. No mixed line endings introduced.
All 34 changed files are markdown; diff is 67 insertions / 67 deletions,
line-for-line.
- Staged by explicit path from the plan, cross-checked against git's modified
set, so nothing another session is editing was swept in.
Not included: the broken-href class (13 sites, mostly (docs/BACKLOG.md) written
from inside docs/testing/master-test-plan/), the 12 line anchors past EOF, and
the 31 in-range anchors that drifted onto unrelated text. Those are separate
classes under #1095 and are catchable by a link checker, which this repo still
does not run.
This was referenced Aug 7, 2026
Conflict was docs/PHI.md, and it is the semantic kind rather than the textual kind: the two sides changed ADJACENT ROWS of one table, so git could not auto-merge them although they are independent. main's side (from #257, in merge train #274) rewrote ROW 10 -- the [alerts] webhook TLS escape now routes through the clamped weakened_tls_escape_permitted(posture) rather than the raw escape. That is a security-posture description. this branch rewrote ROW 11 -- repointing [#323](BACKLOG.md) at archive/backlog/BACKLOG-CLOSED.md#323-... since #323 archived. A mechanical keep-one-side loses one of them, and the two losses are not equal. Taking this branch's block wholesale would REVERT main's row-10 security prose; taking main's block wholesale would restore the stale href this branch exists to fix. Resolved by keeping BOTH: main's row 10 and this branch's row 11. The row-11 href was re-applied by literal single-occurrence replacement with an assertion that it occurred exactly once, aborting rather than fuzzy- matching -- the same discipline the sweep itself used. VERIFIED AFTER RESOLUTION, in both directions: all 26 distinct archive hrefs this branch introduced are present main's row-10 clamp prose present (weakened_tls_escape_permitted(posture)) stale [#323](BACKLOG.md) occurrences remaining: 0 conflict markers remaining: 0 docs/adr/0030 carries main's BACKLOG #331 amendment, byte-identical to main doc-drift guards: 93 passed A caution on the verification itself, because it nearly produced a false alarm: a first scan reported 4 of the 26 hrefs missing. They were present. The scan wrapped its file reads in `except Exception: pass`, so the four ADR files whose paths exceed the Windows limit under this deep temp worktree were silently counted as misses -- an unreadable file was indistinguishable from a missing href. Caught by grepping the four files directly. Print what you scanned, and never let a bare except stand in for a negative result.
wshallwshall
enabled auto-merge (squash)
August 7, 2026 20:44
wshallwshall
added a commit
that referenced
this pull request
Aug 7, 2026
… (BACKLOG #1095) (#280) Links throughout docs/testing/master-test-plan/ were written as if from the repo root -- ](messagefoundry/pipeline/alerts.py), ](tests/), ](.github/workflows/ci.yml). From a file three levels deep those resolve to docs/testing/master-test-plan/messagefoundry/... and simply 404. The targets are real; only the prefix was wrong. Each is rewritten to its true shortest relative path. This is the CATCHABLE half of #1095, and it is the half a link checker would find -- which matters, because this repo runs no link checker at all. It is a different defect from the citation rot in PR #279: there the link resolved and the item behind it had moved; here the link never resolved. 333 hrefs across 14 files. 256 insertions / 256 deletions, line-for-line, all under docs/testing/. No prose changed. GUARDS, each protecting a specific way this could have done damage: - FENCED CODE SKIPPED. A path inside a ``` block is sample output being shown, not a link to follow. (Zero were found in fences here, but the guard ran.) - ONLY hrefs whose repo-root reading actually EXISTS were touched. Where the target is absent either way the link is broken for a different reason and was left, not papered over. - WITHHELD DIRECTORIES NEVER TOUCHED. The plan states at :653 that docs/security/, docs/reviews/ and docs/marketing/ are gitignored post-cutover and that a missing path there is expected, not a defect. Rewriting those would convert a deliberate publishing boundary into a apparent repair. Zero were matched, and the exclusion is in the script so a re-run stays safe. LEFT ALONE, and named so nobody "finishes" it wrongly: 24 hrefs of the form ](messagefoundry/pipeline/alerts.py:27) -- the repo's file:line citation convention embedded in a link target. The :27 suffix means they cannot resolve as a path no matter what prefix is applied; fixing them means either dropping the line number or adopting GitHub's #L27 anchor form, and that is a convention decision, not a repair. 23 are in 15-alerting-and-observability.md. One genuine miss remains: 08-publishing-and-promotion.md:43 cites ../VERSION-CONTROL.md, which does not exist anywhere in the tree. A BUG IN MY OWN SCRIPT, caught and recorded because the failure was silent: the first pass used href.lstrip("./") to strip a leading "./". lstrip takes a CHARACTER SET, so ".github/workflows/ci.yml" lost its leading dot and became "github/workflows/ci.yml", which does not exist -- so 28 real dotfile links were misclassified as "target missing" and skipped. They looked identical to genuine misses in the output. Caught only by reading the list of supposed misses and noticing .gitignore among them. Fixed to strip one leading "./" and no more. VERIFICATION - Broken links under docs/testing/ went 357 -> 24, and the 24 are enumerated above rather than summarised as a count. - A deliberately planted broken href was run through the same checker after the fix to prove it still reports a miss; a checker that has stopped seeing the class would also report zero. - No mixed line endings introduced; CRLF preserved. - Every changed file is under docs/testing/; staged by explicit path. Cut from origin/main, NOT from the PR #279 branch: the two change sets touch disjoint files and stacking would have widened an open PR.
wshallwshall
added a commit
that referenced
this pull request
Aug 8, 2026
…nk-resolution gate (BACKLOG #1095) (#281) * docs: fix the 270 links archiving broke, and gate the cause (BACKLOG #1095) ROOT CAUSE, measured rather than inferred. Closing a backlog item moves its text VERBATIM from docs/BACKLOG.md into docs/archive/backlog/BACKLOG-CLOSED.md -- two directories deeper -- and nothing rewrites its relative links. A link written "adr/0083-x.md" was correct while the item lived in docs/ and resolves to docs/archive/backlog/adr/0083-x.md the moment it lands. The proof is the distribution, not the story: 267 of the archive's 270 broken links resolve CLEANLY when read from docs/. That is what pins the cause to the move rather than to authoring error. The other 3 are ADR slug rot -- the ADR merged under a different title than the one cited: 0080-tls-syslog-forwarding.md -> 0080-offbox-forwarding-tls-defaults.md 0144-handler-config-taint-rules.md -> 0144-security-lint-gate-over-admin-authored-router-handler-config.md 0065-mount-the-web-console-in-process.md -> 0065-web-ops-dashboard.md None was ever committed under the cited name, so those citations were written from working titles that changed before the ADR merged. Repointed by ADR NUMBER, and only where exactly one docs/adr/NNNN-* exists. All 270 repaired. 208 insertions / 208 deletions in the archive, line-for-line. THERE IS NOTHING TO FIX UPSTREAM. I looked for the archival tooling before writing a guard: none exists. The move is manual, so #1094's remark that "the archival pass generates the anchor" describes a generator that was never built. A guard is therefore the only thing that can catch the next one. THE GUARD scripts/docs/link_check.py -- resolves every relative markdown link. Repo-wide capable, takes an optional subtree. Prints WHAT IT SCANNED next to the verdict, because a green run over three files reads identically to a green run over the repo. Deliberately does NOT check: absolute URLs (reachability is a network question), fragments (heading slugs churn on every retitle), the withheld directories docs/security|reviews|marketing (gitignored post-cutover -- the master test plan states a missing path there is a publishing boundary, not a defect), fenced code (a path inside a fence is sample output), and file.py:27 citation targets (they cannot resolve whatever prefix is used; repairing them is a convention decision). A checker that overreaches is one people delete. tests/test_archive_link_resolution.py asserts the archive is clean, and: - refuses to pass on an empty scan (>= 1 file, >= 200 links), the same anti-narrowing floor backlog_status_check.py uses. Without it the test passes just as happily when a path change makes it read nothing. - PLANTS A BROKEN LINK in a throwaway repo and asserts the checker reports exactly it. A green result is evidence only if the checker can see the class. - pins the withheld-directory exemption. SCOPE IS THE ARCHIVE ONLY, deliberately. Repo-wide the checker still reports 341 unresolved, because PRs #279 and #280 are not merged yet. A gate that is red on arrival gets suppressed rather than fixed -- the same reasoning the ledger gate records -- so this asserts only the subtree that is clean. Widening it is a one-line change to _SUBTREE once the rest lands. A BUG MY OWN TEST CAUGHT, recorded because it would have shipped silently: the first version compared the RAW HREF against the withheld prefixes. A relative link "../../security/X.md" shares no prefix with the repo-root form "docs/security/", so the exemption matched nothing and would have flagged every withheld link the moment the gate widened. Only the planted-case test found it. VERIFICATION - Broken relative links in the archive: 270 -> 0, over 277 links checked. - A known-missing path run through the same checker afterwards to prove it still reports a miss. - ruff format, ruff check, mypy strict clean; bandit passes with the repo's standard nosec B603 B607 form on the three fixed-argv git calls. - The 7 doc-drift guards run by hand (99 passed) -- they are SKIPPED on a docs-only PR because .md is in the noncode allowlist and fire on the push to main afterwards, so a green PR check is not evidence there. - backlog_status_check.py: OK, 365 items, unchanged. - Cut from origin/main. Touches no file in PR #279 or #280, and neither of those touched the archive, so there is no conflict in either direction. * fix(docs): widen the link gate to the repo, after removing three false positives The gate was archive-only because a repo-wide assertion was red over pre-existing breakage, and a gate red on arrival gets suppressed rather than fixed. This makes it repo-wide. Three of the eight failures standing in the way were not defects. Inline code. The checker already skips fenced blocks on the grounds that a path inside them is being shown, not offered. A backtick span is the same argument at smaller scale, and four sites turn on it: a regex whose character class contains "](", two VS Code command: URIs -- one quoted in ADR 0110 as an attack payload -- and ADR 0160 quoting the very link it records as removed. Repointing any of them would corrupt the text. Tested by POSITION, not shape, so the dominant idiom [`x.md`](../x.md) keeps being checked; the test asserts both halves, because a shape rule would stop checking most of the docs while staying green. Cost, measured: 10 links of 5,344 no longer checked, five of them previously passing. docs/releases/ is gitignored by ADR 0160 Phase 1, like docs/security/ and its siblings. One archived throughput doc still cites the v0.1 plan that moved out. .claude/ is the one to remember. It is gitignored but PRESENT in a long-lived local checkout, so a repo-wide run passes there and fails on CI's clean clone. The first measurement of this class was taken in such a checkout and undercounted it by exactly 7 links; widening on that number would have put the gate red on arrival -- the outcome the first paragraph exists to avoid. Read a gate result as a fact about the configuration it ran in. The exemption list is pinned as a set, so adding a prefix is a deliberate act with a failing test attached rather than a silent widening of what goes unchecked. Verified by planting a break in a real doc outside the archive and watching the widened gate fail, then restoring: 5,327 links across 347 files, zero unresolved. * docs: repair the last three unresolved links, and four dead line anchors The three genuine breaks left repo-wide once the false positives were removed: ADR slug rot in docs/BACKLOG.md:3073. Two ADRs merged under different titles than the ones cited -- 0094 as granular-expiry-only-tls-relaxation, 0118 as secure-by-default-security-configuration-section. The number is the identity, so these repoint with confidence; the same class as the three the archive pass found. One survivor of the 333-href repair (#280) in the same subtree: 08-publishing-and-promotion.md cited ../VERSION-CONTROL.md where the file is two levels up, not one. Four BACKLOG.md:<line> anchors in the archive become item references. They are here rather than on the docs-only branch because this PR already edits this file within three lines of them, and adjacent hunks conflict. Root cause of the whole anchor class: commit 4ea1501 (the master test plan) is NOT a descendant of 03f1fbd (the 185-item archival) -- parallel branches, so anchors written against an 8,742-line ledger landed beside a commit that cut it to 3,858. Each of the four was recovered from its citing prose and adversarially re-verified against the item it names; the line numbers were dead, the item numbers survive archival, which is the point. * ci(docs): run the link gate on docs-only PRs, where the links actually break Widening the gate to the repo left a hole I put there. `pytest` is gated on `code == 'true'`, so on a docs-only PR the new repo-wide assertion did not run at all -- it would have fired on the push to main afterwards, after the merge. The PRs most likely to break a relative markdown link are exactly the ones that were skipping the check. ci.yml already documents this class and keeps an ungated list for it, citing four docs-only PRs that merged on 2026-08-04 with citation errors no guard caught -- one of them about inbound citations of `docs/releases/`, which is the same directory this gate now treats as withheld. The gate belongs on that list; it is added to both the printed manifest and the pytest invocation, so the "print what you scanned" property is preserved. Verified by running the step's exact command line: 184 passed, 89 skipped. Also renames test_archive_link_resolution.py -> test_link_resolution.py. The old name was accurate when the scope was `docs/archive/backlog` and became a false claim the moment the assertion went repo-wide -- a file whose name understates what it enforces is the same defect as a commit message that understates its own change. Nothing outside the file referenced it by name. * fix(docs): resolve links against the tracked set only, never the filesystem The gate now gives the same answer in a long-lived local checkout and on CI's clean clone BY CONSTRUCTION, rather than by enumerating the paths where the two differ. For a repo-wide invariant that is the difference between a control and a coin flip, and it is the property the PR body was about to claim on the strength of an exemption list that did not provide it. The old resolver accepted `target in tracked OR (root/target).exists()`. That second arm passes anything merely PRESENT, and gitignored-but-present paths are exactly the set a developer has and CI does not. It is the mechanism of the .claude/ finding, not a defence against it: 7 docs link to .claude/settings.json, which passed here and would have failed on the runner, and the first repo-wide measurement undercounted by exactly those 7. Dropping the fallback outright would have been wrong, and measuring said so before I proposed it: `git ls-files` lists files and NEVER directories, so 122 legitimate directory links (docs/adr, environments, .github/workflows) resolve only via the filesystem arm. So tracked_paths() derives every ancestor prefix of every tracked file and resolves against files plus derived directories. Repo-wide result is unchanged at 5,327 links, zero unresolved. Two tests pin the pair, and the first is demonstrated rather than argued: a file PRESENT ON DISK but untracked must fail (verified by re-running the old resolver against the same planted repo -- it reports 0 failures where the new one reports 1), and a directory containing a tracked file must resolve. .claude/ stays in WITHHELD as POLICY, not as protection, and its comment now says so. It was added as protection; keeping that framing after the structural fix would be a compensating control resting on a premise that is no longer why it holds (SDS-3.7). Removing the entry would now make those 7 links fail honestly and identically in every checkout.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Authored by the session in worktree
sweet-dhawan-57cbc8; pushed and opened by the coordinator at itsrequest. BACKLOG #1095 -- repoints citations that named
docs/BACKLOG.mdfor items that actually livein
docs/archive/backlog/BACKLOG-CLOSED.md. Href-only; no prose rewritten. 34 files, +67/-67, all markdown.Coordinator verification
I verified the queue-safety claims independently before pushing (not the author's anchor methodology,
which is their own and is described below):
a02208d64d5f6304, not stacked on an unmerged PR headorigin/mainexactlydocs/BACKLOG.mduntoucheddocs/CONNECTIONS.mduntoucheddocs/archive/backlog/BACKLOG-CLOSED.mduntouchedThose three exclusions are what make this safe to run alongside the open queue: it cannot collide with the
unarmed #1096 filing in #278, and it cannot fight merge train #274 over
docs/CONNECTIONS.md(whose #273fix is inside that train -- the author deliberately excluded that site rather than re-fixing it from
origin/main).A docs-only PR is the blind mode here, not the cheap one -- the doc-drift guards live in pytest gated
on
code == 'true', so they skip pre-merge and fire on the push tomainafterwards. Run locally againstthis exact SHA in a detached worktree:
test_doc_ref_handle,test_crit2_inline_doc_drift,test_docs_runbooks,test_docs_security_pathways,test_security_doc_drift,test_asvs_file_surface_doc_drift,test_backlog_status_check-- 99 passed.What the author deliberately LEFT, so nobody "finishes the job" wrongly
docs/BACKLOG.md:6319inside #1094 says markers "CITEDdocs/BACKLOG.md fix(harness): enforce the reconcile's read >= sent//2 floor unconditionally #26 and fix(ci): let the free-threaded canary report a problem — it was structurally unable to #27" -- past tense, describing rot already fixed. Repointing it
would corrupt a historical record. This class is why the sweep was not done with
sed.absent from the live ledger, but the archive's fix(ci): lint and SAST the same files locally and in CI, and pin the two together #11 and fix(coord): two silent-failure defects in the session-mail installer and broadcast path, with regressions #239 are unrelated items -- repointing would turn
a vague reference into a confidently wrong one. Left and reported.
AI-OFF-MATRIX:50,adr/0001:13,THROUGHPUT-IMPROVEMENTS:215) -- no single target is correct.Author's verification, as reported
Applied by literal replacement on the named line, requiring the quoted string to occur exactly once there,
aborting rather than fuzzy-matching; dry run 73/73 clean before anything was written. All 35 anchor
fragments match exactly one real
## N.heading, with a known-bad fragment pushed through the same checkto prove it reports a miss. Coverage proved with a deliberately looser regex than the one that built
the work list: exactly one wrong-file site remains outside the change set, and it is the intended
CONNECTIONS.mdexclusion.Do not "fix" the doubled hyphens in the anchor fragments -- the slugger does not collapse spaces, so
they are correct.
Merge ordering
Auto-merge deliberately NOT armed. This branch's base already equals current
main, so it is mergeableimmediately -- and that is exactly the problem: landing it now advances
mainand knocks merge train #274(seven PRs, mid-CI) BEHIND, costing it a full ~35-minute cycle. It gets armed once the trains land.
The author's note on conflicts: their changed lines sit on the merge base, so if any of the 34 files does
conflict, a mechanical keep-both would restore the stale href. Take their side on those lines.