Context
Epic #11372 / ADR 0004 migrated resources/content/release-notes/ to the universal ordinal-100 chunk-N/ shape via the new ReleaseNotesSyncer (#11405). The portal's ticket index generator was updated for the new archive/chunk shape in #11287/#11295, but the sibling release index generator buildScripts/docs/index/release.mjs was never updated. Surfaced during an ADR-0004 Phase-2 consumer audit on 2026-05-29.
The Problem
release.mjs scans release notes with a non-recursive glob and emits flat paths. After #11405 chunked the release-notes directory, both assumptions are now false:
- Files now live at
release-notes/chunk-1/… and release-notes/chunk-2/…, not at the top level.
- The committed
apps/portal/resources/data/releases.json still carries flat paths like resources/content/release-notes/v12.1.0.md, but that file now physically lives at resources/content/release-notes/chunk-2/v12.1.0.md.
Net effect:
- The portal release-notes view fetches
path literally (Component.mjs → await fetch(path)); the flat paths now 404.
- Re-running the generator would produce an empty
releases.json (the non-recursive fg('*.md') matches zero files under chunk-N/).
This is a regression introduced by the otherwise-correct #11405 chunking, because the release-index consumer was out of scope for the #11287 ticket-consumer migration.
The Architectural Reality
buildScripts/docs/index/release.mjs:62 — fg('*.md', { cwd: inputDir, absolute: true }) is non-recursive. The sibling tickets.mjs:64 correctly uses fg('**/*.md', …).
buildScripts/docs/index/release.mjs:101 — emits path: resources/content/release-notes/${path.basename(filePath)} (flat). Must preserve the chunk segment.
- Consumers of
releases.json[].path: apps/portal/view/news/release/* (path-agnostic fetch(path)), and downstream middleware-v2 SSR (Phase-2 item 14, out of scope here).
apps/portal/model/Release.mjs:26 carries a doubly-stale path comment (/.github/RELEASE_NOTES/v11.18.0.md); apps/portal/model/Ticket.mjs:33 carries a stale flat comment too.
The Fix
release.mjs: change the glob to recursive (**/*.md) so it finds files under chunk-N/.
release.mjs: emit the real relative path including the chunk segment (use path.relative(ROOT_DIR, filePath), mirroring tickets.mjs:130), not a reconstructed flat path.
- Regenerate
apps/portal/resources/data/releases.json and commit the corrected output.
- Fix the stale
path field comments in Release.mjs:26 and Ticket.mjs:33.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
releases.json[].path |
ADR 0004 §2.1 + on-disk layout |
resources/content/release-notes/chunk-N/vX.Y.Z.md |
none (path read verbatim by fetch) |
release.mjs JSDoc |
v12.1.0.md verified at chunk-2/ on disk |
Decision Record impact
aligned-with ADR 0004 — completes the Phase-2 consumer migration for the release index (sibling to #11287's ticket-index migration). Does not amend or challenge.
Acceptance Criteria
Out of Scope
- The larger ADR §9 items 11–12 "tree-folder lazy-load index + portal lazy-consume-on-expand" refactor (separate Phase-2 sub).
middleware-v2 SSR / ticketPaths.json rewire (Phase-2 item 14, separate repo).
issue-archive/pr-archive code-ref cleanup (sibling cleanup ticket).
Avoided Traps
- Reconstructing a flat path from the filename (the current bug) — must preserve the chunk segment via
path.relative, not path.basename.
- Hardcoding a single
chunk-1 — the generator must be chunk-count-agnostic (release-notes already spans chunk-1 + chunk-2).
Related
- Parent epic: #11372 (ADR 0004)
- Sibling consumer migration (tickets): #11287, #11295
- Release-notes chunking that surfaced the gap: #11405
- Authority:
learn/agentos/decisions/0004-github-content-architecture.md §2.1, §9 item 11
Retrieval Hint: query_raw_memories("release.mjs chunk-N release-notes index migration ADR 0004 phase 2"); commit anchors: #11405 (release-notes chunking), #11295 (tickets.mjs migration this mirrors).
Context
Epic #11372 / ADR 0004 migrated
resources/content/release-notes/to the universal ordinal-100chunk-N/shape via the newReleaseNotesSyncer(#11405). The portal's ticket index generator was updated for the new archive/chunk shape in #11287/#11295, but the sibling release index generatorbuildScripts/docs/index/release.mjswas never updated. Surfaced during an ADR-0004 Phase-2 consumer audit on 2026-05-29.The Problem
release.mjsscans release notes with a non-recursive glob and emits flat paths. After #11405 chunked the release-notes directory, both assumptions are now false:release-notes/chunk-1/…andrelease-notes/chunk-2/…, not at the top level.apps/portal/resources/data/releases.jsonstill carries flat paths likeresources/content/release-notes/v12.1.0.md, but that file now physically lives atresources/content/release-notes/chunk-2/v12.1.0.md.Net effect:
pathliterally (Component.mjs→await fetch(path)); the flat paths now 404.releases.json(the non-recursivefg('*.md')matches zero files underchunk-N/).This is a regression introduced by the otherwise-correct #11405 chunking, because the release-index consumer was out of scope for the #11287 ticket-consumer migration.
The Architectural Reality
buildScripts/docs/index/release.mjs:62—fg('*.md', { cwd: inputDir, absolute: true })is non-recursive. The siblingtickets.mjs:64correctly usesfg('**/*.md', …).buildScripts/docs/index/release.mjs:101— emitspath: resources/content/release-notes/${path.basename(filePath)}(flat). Must preserve the chunk segment.releases.json[].path:apps/portal/view/news/release/*(path-agnosticfetch(path)), and downstreammiddleware-v2SSR (Phase-2 item 14, out of scope here).apps/portal/model/Release.mjs:26carries a doubly-stale path comment (/.github/RELEASE_NOTES/v11.18.0.md);apps/portal/model/Ticket.mjs:33carries a stale flat comment too.The Fix
release.mjs: change the glob to recursive (**/*.md) so it finds files underchunk-N/.release.mjs: emit the real relative path including the chunk segment (usepath.relative(ROOT_DIR, filePath), mirroringtickets.mjs:130), not a reconstructed flat path.apps/portal/resources/data/releases.jsonand commit the corrected output.pathfield comments inRelease.mjs:26andTicket.mjs:33.Contract Ledger
releases.json[].pathresources/content/release-notes/chunk-N/vX.Y.Z.mdfetch)release.mjsJSDocv12.1.0.mdverified atchunk-2/on diskDecision Record impact
aligned-with ADR 0004— completes the Phase-2 consumer migration for the release index (sibling to #11287's ticket-index migration). Does not amend or challenge.Acceptance Criteria
release.mjsfinds all release-note.mdfiles underrelease-notes/chunk-*/.releases.jsonpaths resolve to files that exist on disk (spot-checkv12.1.0→chunk-2/v12.1.0.md).releases.jsonis non-empty and preserves the major-version tree grouping + descending semver sort.pathcomments corrected inRelease.mjs+Ticket.mjs.Out of Scope
middleware-v2SSR /ticketPaths.jsonrewire (Phase-2 item 14, separate repo).issue-archive/pr-archivecode-ref cleanup (sibling cleanup ticket).Avoided Traps
path.relative, notpath.basename.chunk-1— the generator must be chunk-count-agnostic (release-notes already spanschunk-1+chunk-2).Related
learn/agentos/decisions/0004-github-content-architecture.md§2.1, §9 item 11Retrieval Hint:
query_raw_memories("release.mjs chunk-N release-notes index migration ADR 0004 phase 2"); commit anchors: #11405 (release-notes chunking), #11295 (tickets.mjs migration this mirrors).