fix(docs): resolve three dead anchors, and ship the check that finds them (#1431) #1439

Merged
bosun merged 1 commit from i/1431-dead-anchors into main 2026-09-07 10:17:16 +02:00
Owner

For #1431. Head 18f2a909 on 24491072.

A heading's punctuation is dropped rather than replaced, so the spaces around it collapse into one hyphen — while an author counting space · punctuation · space writes two.

### Tokens & permissions                    ->  tokens-permissions
### Single-line bullets — the no-hard-wrap  ->  single-line-bullets-the-no-hard-wrap

Both routes to the token section were dead, in a guide whose recommended path needs RELEASE_TOOLKIT_TOKEN.

The complement, with its before-value

BEFORE  58 files · 26 distinct anchors · 2 unresolved (3 occurrences)
AFTER   58 files · 25 distinct anchors · 0 unresolved

📌 The distinct count drops by one, and the reason is stated rather than explained away: tokens--permissions and tokens-permissions were two distinct anchors in that file and are now one. A count that moved for a reason other than the fix would be the alarming case.

Units, since this repo has spent the day on that: 26/25 are distinct anchors per file; the 30 quoted on the tracker are occurrences. Different questions, both correct.

scripts/anchor-check.py

The AC asks that the complement be the thing that runs, so it is a script rather than a paragraph. Three controls, each fired separately:

a real dead anchor        rc=1
renderer unreachable      rc=2   <- could-not-grade, NOT clean
selector matches nothing  rc=2   <- could-not-grade, NOT clean
baseline                  rc=0

The two could-not-grade paths matter more than the positive one — a renderer that stopped answering and a clean corpus emit the same zeros.

🔴 My own sweep failed on its own documentation first

Extracting links from raw text reported three "dead anchors" that were code examples inside the section documenting anchors:

UNRESOLVED  docs/conventions.md  #[a-z0-9-]*--[a-z0-9-]*
UNRESOLVED  docs/conventions.md  #some-heading
UNRESOLVED  docs/conventions.md  #…

The detector firing on the prose that describes it. ¶39 again — a change that documents what it fixes contains the thing it fixes — and the instrument is what needed fixing, not the doc. The script strips fences and inline code spans, and a positive control proves it still catches a real break afterwards.

Not on the tracker — in docs/conventions.md, beside the other author-time rules, with both failure modes named:

  • it false-positives on a heading that genuinely renders a double hyphen;
  • it false-negatives on every other transform — v0.57.0v0-57-0, a leading emoji dropping with no dash — so an author who trusts the regex ships a dead link it cannot see.

Exhaustive on today's corpus, and not by construction.

⚠️ The third class is SCOPE, not an AC

A link that resolves to the wrong section is invisible to both checks. A resolver answers does this land somewhere, never does this land where you meant, because the substrate holds no copy of the intent. A dead link announces itself; a live wrong one lands the reader somewhere plausible and they blame their own reading.

Recording it as an AC would commission work that cannot succeed, so the script's own pass message says what it does not check.

Gates

go build · go vet · go test ./... · bats tests/ (198 arms) · register-check · fragment-check · changelog-body-check · contract-paths-check — all rc=0. Fragment 423 chars, warns on nothing.

⚠️ readme-pin-check may be red from main's mirror backfill; #1435 owns it and it is optional.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MMmaXmMhZdAAnttWBS6zqa

For #1431. Head `18f2a909` on `24491072`. A heading's punctuation is **dropped rather than replaced**, so the spaces around it collapse into *one* hyphen — while an author counting *space · punctuation · space* writes two. ``` ### Tokens & permissions -> tokens-permissions ### Single-line bullets — the no-hard-wrap -> single-line-bullets-the-no-hard-wrap ``` Both routes to the token section were dead, in a guide whose recommended path needs `RELEASE_TOOLKIT_TOKEN`. ## The complement, with its before-value ``` BEFORE 58 files · 26 distinct anchors · 2 unresolved (3 occurrences) AFTER 58 files · 25 distinct anchors · 0 unresolved ``` 📌 **The distinct count drops by one, and the reason is stated rather than explained away**: `tokens--permissions` and `tokens-permissions` were two distinct anchors in that file and are now one. A count that moved for a reason *other* than the fix would be the alarming case. **Units, since this repo has spent the day on that**: 26/25 are *distinct anchors per file*; the 30 quoted on the tracker are *occurrences*. Different questions, both correct. ## `scripts/anchor-check.py` The AC asks that the complement be **the thing that runs**, so it is a script rather than a paragraph. Three controls, each fired separately: ``` a real dead anchor rc=1 renderer unreachable rc=2 <- could-not-grade, NOT clean selector matches nothing rc=2 <- could-not-grade, NOT clean baseline rc=0 ``` **The two could-not-grade paths matter more than the positive one** — a renderer that stopped answering and a clean corpus emit the same zeros. ## 🔴 My own sweep failed on its own documentation first Extracting links from raw text reported three "dead anchors" that were **code examples inside the section documenting anchors**: ``` UNRESOLVED docs/conventions.md #[a-z0-9-]*--[a-z0-9-]* UNRESOLVED docs/conventions.md #some-heading UNRESOLVED docs/conventions.md #… ``` The detector firing on the prose that describes it. ¶39 again — a change that documents what it fixes contains the thing it fixes — and the instrument is what needed fixing, not the doc. The script strips fences and inline code spans, and a positive control proves it still catches a real break afterwards. ## Recorded where a link author will meet it Not on the tracker — in `docs/conventions.md`, beside the other author-time rules, with **both** failure modes named: - it **false-positives** on a heading that genuinely renders a double hyphen; - it **false-negatives** on every other transform — `v0.57.0` → `v0-57-0`, a leading emoji dropping with no dash — so an author who trusts the regex ships a dead link it cannot see. **Exhaustive on today's corpus, and not by construction.** ## ⚠️ The third class is SCOPE, not an AC A link that **resolves to the wrong section** is invisible to both checks. A resolver answers *does this land somewhere*, never *does this land where you meant*, because the substrate holds no copy of the intent. A dead link announces itself; a live wrong one lands the reader somewhere plausible and they blame their own reading. Recording it as an AC would commission work that cannot succeed, so the script's own pass message says what it does not check. ## Gates `go build` · `go vet` · `go test ./...` · `bats tests/` (198 arms) · `register-check` · `fragment-check` · `changelog-body-check` · `contract-paths-check` — all `rc=0`. Fragment 423 chars, warns on nothing. ⚠️ `readme-pin-check` may be red from main's mirror backfill; #1435 owns it and it is optional. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01MMmaXmMhZdAAnttWBS6zqa
fix(docs): resolve three dead anchors, and ship the check that finds them (#1431)
All checks were successful
changelog-body-check / check (pull_request) Successful in 0s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / changelog fragment-kind (pull_request) Successful in 52s
fragment-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 4s
tests / workflow-schema (pull_request) Successful in 32s
tests / dated-examples (pull_request) Successful in 34s
register-check / register-drift check (pull_request) Successful in 49s
go-ci / lint + build + test (pull_request) Successful in 1m11s
register-check / check (pull_request) Successful in 0s
go-ci / page landing-tree failure (pull_request) Has been skipped
workflow-parse-check / workflow parse and schema (pull_request) Successful in 27s
workflow-parse-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m20s
release / decide + act (push) Successful in 10s
release / release (push) Successful in 0s
tests / dated-examples (push) Successful in 4s
go-ci / record reviewed vs landed commit (push) Successful in 17s
tests / contract-paths (push) Successful in 4s
prepared-uncut-check / toolkit-self prepared-uncut controls (push) Successful in 23s
check-self-bootstrap / check (push) Successful in 24s
gitea-twin-check / check (push) Successful in 25s
tests / workflow-schema (push) Successful in 27s
tests / shellcheck (push) Successful in 24s
prepared-uncut-check / prepared-but-uncut release (push) Successful in 48s
prepared-uncut-check / check (push) Successful in 0s
go-ci / lint + build + test (push) Successful in 1m11s
tests / bats (push) Successful in 1m14s
release / fire-cut (push) Has been skipped
go-ci / page landing-tree failure (push) Has been skipped
18f2a90901
A heading's punctuation is DROPPED rather than replaced, so the spaces
around it collapse into ONE hyphen — while an author counting
space·punctuation·space writes two.

  ### Tokens & permissions                   -> tokens-permissions
  ### Single-line bullets — the no-hard-wrap -> single-line-bullets-the-no-…

Both routes to the token section were dead, in a guide whose recommended
path needs `RELEASE_TOOLKIT_TOKEN`.

## The complement, with its before-value

  BEFORE  58 files · 26 distinct anchors · 2 unresolved (3 occurrences)
  AFTER   58 files · 25 distinct anchors · 0 unresolved

📌 The distinct count drops by one for a reason worth stating rather than
explaining away: `tokens--permissions` and `tokens-permissions` were two
distinct anchors in that file and are now one. A count that moved for a
reason other than the fix would be the alarming case.

## scripts/anchor-check.py

The AC asks that the complement be the thing that RUNS, so it is a script
rather than a paragraph. It renders each file's headings through the
forge and reports the set difference, with the signature demoted to a
pre-screen in the prose.

Three controls, each fired separately:

  a real dead anchor       rc=1
  renderer unreachable     rc=2   <- could-not-grade, NOT clean
  selector matches nothing rc=2   <- could-not-grade, NOT clean

Both could-not-grade paths matter more than the positive one: a renderer
that stopped answering and a clean corpus emit the same zeros.

🔴 MY OWN SWEEP FAILED ON ITS OWN DOCUMENTATION FIRST. Extracting links
from raw text reported three "dead anchors" that were code EXAMPLES inside
the section documenting anchors — the detector firing on the prose that
describes it. ¶39 again: a change that documents what it fixes contains
the thing it fixes. The script strips fences and inline code, and a
positive control proves it still catches a real break.

## Recorded where a link author will meet it

Not on the tracker — in `docs/conventions.md`, beside the other
author-time rules, with BOTH failure modes named: the signature
false-positives on a genuine double hyphen and false-NEGATIVES on every
other transform (`v0.57.0` -> `v0-57-0`, a leading emoji dropping with no
dash). It is exhaustive on today's corpus and not by construction.

⚠️ AND THE THIRD CLASS IS STATED AS SCOPE, NOT AS AN AC. A link that
RESOLVES to the wrong section is invisible to both checks: a resolver
answers "does this land somewhere", never "does this land where you
meant", because the substrate holds no copy of the intent. Recording it
as an AC would commission work that cannot succeed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMmaXmMhZdAAnttWBS6zqa
bosun requested review from rigger 2026-09-07 10:02:38 +02:00
rigger approved these changes 2026-09-07 10:14:46 +02:00
rigger left a comment

Approved at exact head 18f2a909012909431056f3b30efb2da6d586bf0e.

Verified against the live Forgejo renderer: base 24491072e99ee3062d3418c23369a9b51b25c129 reproduces 58 files, 26 global distinct anchors, and exactly the two unresolved anchor names (three occurrences); the PR head is 58 files, 25 distinct, 0 unresolved. The only distinct-count reduction is the two tokens--permissions occurrences becoming tokens-permissions; the single-line-bullets anchor is a one-for-one rename.

Controls were run independently: clean head rc=0; known real dead-anchor base rc=1; renderer unreachable rc=2; empty selector rc=2. Raw extraction reproduces the three fenced documentation false positives, while the fence-stripped check still catches the real base break. The pass output explicitly records the unresolved wrong-section class as outside the resolver's scope.

Approved at exact head `18f2a909012909431056f3b30efb2da6d586bf0e`. Verified against the live Forgejo renderer: base `24491072e99ee3062d3418c23369a9b51b25c129` reproduces 58 files, 26 global distinct anchors, and exactly the two unresolved anchor names (three occurrences); the PR head is 58 files, 25 distinct, 0 unresolved. The only distinct-count reduction is the two `tokens--permissions` occurrences becoming `tokens-permissions`; the single-line-bullets anchor is a one-for-one rename. Controls were run independently: clean head rc=0; known real dead-anchor base rc=1; renderer unreachable rc=2; empty selector rc=2. Raw extraction reproduces the three fenced documentation false positives, while the fence-stripped check still catches the real base break. The pass output explicitly records the unresolved wrong-section class as outside the resolver's scope.
rigger approved these changes 2026-09-07 10:14:46 +02:00
rigger left a comment

Approved at exact head 18f2a909012909431056f3b30efb2da6d586bf0e.

Verified against the live Forgejo renderer: base 24491072e99ee3062d3418c23369a9b51b25c129 reproduces 58 files, 26 global distinct anchors, and exactly the two unresolved anchor names (three occurrences); the PR head is 58 files, 25 distinct, 0 unresolved. The only distinct-count reduction is the two tokens--permissions occurrences becoming tokens-permissions; the single-line-bullets anchor is a one-for-one rename.

Controls were run independently: clean head rc=0; known real dead-anchor base rc=1; renderer unreachable rc=2; empty selector rc=2. Raw extraction reproduces the three fenced documentation false positives, while the fence-stripped check still catches the real base break. The pass output explicitly records the unresolved wrong-section class as outside the resolver's scope.

Approved at exact head `18f2a909012909431056f3b30efb2da6d586bf0e`. Verified against the live Forgejo renderer: base `24491072e99ee3062d3418c23369a9b51b25c129` reproduces 58 files, 26 global distinct anchors, and exactly the two unresolved anchor names (three occurrences); the PR head is 58 files, 25 distinct, 0 unresolved. The only distinct-count reduction is the two `tokens--permissions` occurrences becoming `tokens-permissions`; the single-line-bullets anchor is a one-for-one rename. Controls were run independently: clean head rc=0; known real dead-anchor base rc=1; renderer unreachable rc=2; empty selector rc=2. Raw extraction reproduces the three fenced documentation false positives, while the fence-stripped check still catches the real base break. The pass output explicitly records the unresolved wrong-section class as outside the resolver's scope.
bosun merged commit 18f2a90901 into main 2026-09-07 10:17:16 +02:00
bosun deleted branch i/1431-dead-anchors 2026-09-07 10:17:16 +02:00

Landing identity record

  • PR: #1439
  • landed commit (server merge_commit_sha): 18f2a909012909431056f3b30efb2da6d586bf0e
  • effective official approval(s):
    • @rigger, review #6934, stamped commit: 18f2a909012909431056f3b30efb2da6d586bf0e
  • replay comparison: no identity change (stamped SHA equals landed SHA)

This is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.

<!-- release-toolkit:landing-review-record-v1 landed=18f2a909012909431056f3b30efb2da6d586bf0e --> ## Landing identity record - PR: #1439 - landed commit (server merge_commit_sha): `18f2a909012909431056f3b30efb2da6d586bf0e` - effective official approval(s): - @rigger, review #6934, stamped commit: `18f2a909012909431056f3b30efb2da6d586bf0e` - replay comparison: no identity change (stamped SHA equals landed SHA) This is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.
Sign in to join this conversation.
No description provided.