LearnNewsExamplesServices
Frontmatter
id12749
titlePin /blob/main release-note links + broaden the mutable-link guard
stateClosed
labels
bugdocumentationaibuild
assigneesneo-gpt
createdAtJun 8, 2026, 11:19 AM
updatedAtJun 8, 2026, 12:49 PM
githubUrlhttps://github.com/neomjs/neo/issues/12749
authorneo-opus-vega
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 8, 2026, 12:49 PM

Pin /blob/main release-note links + broaden the mutable-link guard

Closed v13.0.0/archive-v13-0-0-chunk-17 bugdocumentationaibuild
neo-opus-vega
neo-opus-vega commented on Jun 8, 2026, 11:19 AM

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:

  1. 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.
  2. 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.mjsMUTABLE_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

  1. Broaden MUTABLE_RELEASE_NOTE_GITHUB_LINK_PATTERN from /blob/dev/ to /blob/(dev|main)/, keeping the "mutable" framing now that it is accurate.
  2. Pin the 4 /blob/main/ links to immutable commit SHAs (the enhanceSeo.mjs ×3 + updateNeoVersion.mjs targets), verifying each with git cat-file -e.
  3. 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

  • git grep 'blob/main/' -- resources/content/release-notes/ → 0 (all 4 pinned to commit SHAs that resolve via git cat-file -e).
  • MUTABLE_RELEASE_NOTE_GITHUB_LINK_PATTERN matches both /blob/dev/ and /blob/main/ (and still NOT /blob/<sha>/ or /blob/<tag>/).
  • The guard helper / test names no longer over-claim — they read "mutable" and genuinely catch the mutable branches.
  • The recursive **/*.md guard test asserts 0 mutable (dev|main) links across active release notes.
  • No regression to #12739's dev-link pinning or the fail-at-source guard behavior.

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).

tobiu referenced in commit 931295f - "fix(seo): pin mutable main release-note links (#12749) (#12751)" on Jun 8, 2026, 12:49 PM
tobiu closed this issue on Jun 8, 2026, 12:49 PM