Context
Direct follow-up to #12735 (closed by #12739, merged 2026-06-08T08:59:55Z). #12739 pinned the 31 mutable /blob/dev/ GitHub links in active release notes to immutable commit SHAs and added a fail-at-source generator guard. During the #12739 review I caught that the same mutable-link class includes /blob/main/ links the guard does not cover; @neo-gpt confirmed the catch is valid + non-blocking and asked me to file it as a standalone follow-up rather than fold it into the already-approved PR.
Live latest-open sweep: checked the latest 22 open issues + a targeted blob/main/mutable link/permalink search at 2026-06-08T~09:18Z; no equivalent ticket (#12740's cost-safety subs are a different concern).
V-B-A (premise): git grep 'blob/main/' origin/dev -- resources/content/release-notes/ → 4 links: 3× blob/main/buildScripts/enhanceSeo.mjs + 1× blob/main/buildScripts/updateNeoVersion.mjs. The #12739 guard pattern (/blob\/dev\//) does not match these.
The Problem
#12739 closed the /blob/dev/ subset of the dangling-link-on-indexed-SEO-routes class (#12735), but main is also a mutable branch (it advances on every release), so a /blob/main/<path> link can dangle on a future file move/rename — the same failure mode, lower-frequency. Two gaps:
- 4 unpinned
/blob/main/ links remain in active release notes (3× enhanceSeo.mjs, 1× updateNeoVersion.mjs), now live SEO routes via #12728's recursive glob.
- The guard over-claims.
getMutableReleaseNoteGithubLinks / assertStableReleaseNoteGithubLinks and the spec "active release notes do not publish mutable GitHub dev links" use a /blob/dev/-only pattern, so they assert "no mutable links" while 4 mutable /blob/main/ links pass. Name and contract say "mutable"; behavior covers only dev.
The Architectural Reality
- Owning surface:
buildScripts/docs/seo/generate.mjs — MUTABLE_RELEASE_NOTE_GITHUB_LINK_PATTERN (the /blob/dev/ regex), getMutableReleaseNoteGithubLinks(), assertStableReleaseNoteGithubLinks() (all introduced by #12739).
- Content: the 4
/blob/main/ links live in resources/content/release-notes/** — pin to immutable commit SHAs matching #12739's treatment of the dev-links; verify each resolves via git cat-file -e.
- Test:
test/playwright/unit/ai/buildScripts/docs/seo/Generate.spec.mjs — broaden the fixtures + the recursive-scan assertion to dev|main.
- Forward-compat: #12728's recursive glob makes all chunk-N release notes live SEO routes; the #12739 test already scans
**/*.md, so the broadened guard covers them.
The Fix
- Broaden
MUTABLE_RELEASE_NOTE_GITHUB_LINK_PATTERN from /blob/dev/ to /blob/(dev|main)/, keeping the "mutable" framing now that it is accurate.
- Pin the 4
/blob/main/ links to immutable commit SHAs (the enhanceSeo.mjs ×3 + updateNeoVersion.mjs targets), verifying each with git cat-file -e.
- Update the guard tests: the fixture set + the "active release notes do not publish mutable GitHub links" recursive scan must catch
main as well as dev.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
MUTABLE_RELEASE_NOTE_GITHUB_LINK_PATTERN (generate.mjs) |
#12739 guard + #12735 class |
match /blob/(dev|main)/ |
n/a (pure widening) |
inline JSDoc |
unit fixture: dev + main matched, /blob/<sha>/ not matched |
4 /blob/main/ release-note links |
#12739 dev-link pinning precedent |
repinned to immutable commit SHAs |
n/a |
same file |
git cat-file -e + git grep blob/main/ → 0 |
guard test (Generate.spec.mjs) |
#12739 test |
recursive **/*.md scan flags dev + main |
n/a |
same file |
spec passes with 0 violations post-pin |
Decision Record impact
none — pure widening of an existing #12739 guard + content pinning; no ADR involved.
Acceptance Criteria
Out of Scope
- The
/blob/dev/ links + the base guard — #12739 (merged) owns those.
- Non-release-note
/blob/main/ links elsewhere in the repo (scoped to the release-note SEO-route surface).
- A generic "any mutable ref" guard beyond
dev|main — tags/SHAs are immutable; a branch-vs-tag regex distinction is out of scope (dev + main are the known mutable branches in use).
Related
- #12735 (the parent class ticket, closed by #12739)
- #12739 (the dev-link pin + guard this extends)
- #12728 (recursive glob — makes chunk-N release notes live SEO routes)
- #12732 (the
WORKING_WITH_AGENTS tombstone — a coordinated link target)
- #12696 (v13 release epic)
Origin Session ID: 728442d6-a2a0-4481-a631-a3112ce6d703
Retrieval Hint: "release-note /blob/main mutable link guard broaden dev|main pin commit SHA #12735 #12739"
Authored by Claude Opus 4.8 (neo-opus-vega, Claude Code).
Context
Direct follow-up to #12735 (closed by #12739, merged 2026-06-08T08:59:55Z). #12739 pinned the 31 mutable
/blob/dev/GitHub links in active release notes to immutable commit SHAs and added a fail-at-source generator guard. During the #12739 review I caught that the same mutable-link class includes/blob/main/links the guard does not cover; @neo-gpt confirmed the catch is valid + non-blocking and asked me to file it as a standalone follow-up rather than fold it into the already-approved PR.Live latest-open sweep: checked the latest 22 open issues + a targeted
blob/main/mutable link/permalinksearch at 2026-06-08T~09:18Z; no equivalent ticket (#12740's cost-safety subs are a different concern).V-B-A (premise):
git grep 'blob/main/' origin/dev -- resources/content/release-notes/→ 4 links: 3×blob/main/buildScripts/enhanceSeo.mjs+ 1×blob/main/buildScripts/updateNeoVersion.mjs. The #12739 guard pattern (/blob\/dev\//) does not match these.The Problem
#12739 closed the
/blob/dev/subset of the dangling-link-on-indexed-SEO-routes class (#12735), butmainis also a mutable branch (it advances on every release), so a/blob/main/<path>link can dangle on a future file move/rename — the same failure mode, lower-frequency. Two gaps:/blob/main/links remain in active release notes (3×enhanceSeo.mjs, 1×updateNeoVersion.mjs), now live SEO routes via #12728's recursive glob.getMutableReleaseNoteGithubLinks/assertStableReleaseNoteGithubLinksand the spec "active release notes do not publish mutable GitHub dev links" use a/blob/dev/-only pattern, so they assert "no mutable links" while 4 mutable/blob/main/links pass. Name and contract say "mutable"; behavior covers onlydev.The Architectural Reality
buildScripts/docs/seo/generate.mjs—MUTABLE_RELEASE_NOTE_GITHUB_LINK_PATTERN(the/blob/dev/regex),getMutableReleaseNoteGithubLinks(),assertStableReleaseNoteGithubLinks()(all introduced by #12739)./blob/main/links live inresources/content/release-notes/**— pin to immutable commit SHAs matching #12739's treatment of the dev-links; verify each resolves viagit cat-file -e.test/playwright/unit/ai/buildScripts/docs/seo/Generate.spec.mjs— broaden the fixtures + the recursive-scan assertion todev|main.**/*.md, so the broadened guard covers them.The Fix
MUTABLE_RELEASE_NOTE_GITHUB_LINK_PATTERNfrom/blob/dev/to/blob/(dev|main)/, keeping the "mutable" framing now that it is accurate./blob/main/links to immutable commit SHAs (theenhanceSeo.mjs×3 +updateNeoVersion.mjstargets), verifying each withgit cat-file -e.mainas well asdev.Contract Ledger Matrix
MUTABLE_RELEASE_NOTE_GITHUB_LINK_PATTERN(generate.mjs)/blob/(dev|main)//blob/<sha>/not matched/blob/main/release-note linksgit cat-file -e+git grep blob/main/→ 0Generate.spec.mjs)**/*.mdscan flags dev + mainDecision Record impact
none — pure widening of an existing #12739 guard + content pinning; no ADR involved.
Acceptance Criteria
git grep 'blob/main/' -- resources/content/release-notes/→ 0 (all 4 pinned to commit SHAs that resolve viagit cat-file -e).MUTABLE_RELEASE_NOTE_GITHUB_LINK_PATTERNmatches both/blob/dev/and/blob/main/(and still NOT/blob/<sha>/or/blob/<tag>/).**/*.mdguard test asserts 0 mutable (dev|main) links across active release notes.Out of Scope
/blob/dev/links + the base guard — #12739 (merged) owns those./blob/main/links elsewhere in the repo (scoped to the release-note SEO-route surface).dev|main— tags/SHAs are immutable; a branch-vs-tag regex distinction is out of scope (dev+mainare the known mutable branches in use).Related
WORKING_WITH_AGENTStombstone — a coordinated link target)Origin Session ID: 728442d6-a2a0-4481-a631-a3112ce6d703 Retrieval Hint: "release-note /blob/main mutable link guard broaden dev|main pin commit SHA #12735 #12739"
Authored by Claude Opus 4.8 (neo-opus-vega, Claude Code).