Context
#19158's census (IC 5815306776) lists the engine's build readers that derive the frozen mirror, at filing:
- From the working directory (
ROOT_DIR = process.cwd()):
buildScripts/docs/index/tickets.mjs:31-33, pulls.mjs:30-32, discussions.mjs:22-24;
buildScripts/docs/seo/generate.mjs:10,634-695, its conversation globs.
- From
__dirname: buildScripts/release/analyzeClosedSinceRelease.mjs:29-32.
Vega's comment adds the release notes: buildScripts/docs/index/release.mjs and the SEO release-note glob read the served resources/content/release-notes from the working directory too, while the engine authors its notes in .github/RELEASE_NOTES since #19322.
The portal's runtime read, Neo.config.basePath + path in apps/portal/view/content/Component.mjs:188, fetches whatever path those indexes write.
The Problem
D#19051 adopted C1. <corpus-root> is a declared location (ADR 0004 §2.1.1), and deriving it from the working directory is what §5.8 forbids.
#19322 deleted the mirror on 2026-09-29. Since then:
- a local run of any of these generators reads an absent tree;
- the engine's dev portal fetches paths that are no longer there.
The Fix
- Corpus: the conversation generators, the SEO routes and
analyzeClosedSinceRelease take the corpus root as an input, with no working-directory default.
- Release notes:
release.mjs and the SEO release routes take the release-notes root as an input. The orchestrator defaults it to the engine's .github/RELEASE_NOTES, where the notes are authored.
- Paths: the index paths they write are relative to their own root.
- Portal: it fetches conversation bodies and release notes from two served bases, each a config value. A developer's server serves a local corpus checkout at the corpus base; the pages build sets the bases it serves from.
- Baseline: these rows leave
check-content-root-derivations-baseline.json in the same PR.
Decision Record impact: aligned with ADR 0004 §2.1.1 and §5.8, and D#19051 C1.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Edge Case |
Docs |
Evidence |
--corpus-root <dir> (new) on docs/index/{tickets,pulls,discussions}.mjs, docs/seo/generate.mjs, docs/rebuildContentIndexesAndSeo.mjs, release/analyzeClosedSinceRelease.mjs. It replaces -i/--issues, -a/--archive and -i/--input |
ADR 0004 §2.1.1, §5.8; D#19051 C1 |
Reads issues/, pulls/, discussions/ and archive/ under the root |
Required, no working-directory default. A missing root, an absent one or a non-directory throws naming the flag, before any builder runs |
JSDoc, CLI help |
TicketIndex.spec, PortalContentIndexes.spec, seo/Generate.spec, RebuildContentIndexesAndSeo.spec |
--release-notes <dir> (new) on docs/index/release.mjs, docs/seo/generate.mjs, docs/rebuildContentIndexesAndSeo.mjs |
Vega's comment; the intake |
Reads the notes, flat or under chunk-N/, below the root |
Required on the two generators; the orchestrator defaults it to <root>/.github/RELEASE_NOTES. Rejection as above |
JSDoc, CLI help |
RebuildContentIndexesAndSeo.spec, seo/Generate.spec |
Exported options: corpusRoot on createTicketIndex, createPullRequestIndex, createDiscussionIndex, rebuildContentIndexesAndSeo and the SEO route functions; releaseNotesRoot on createReleaseIndex, the SEO route functions and the orchestrator |
as the flags |
As the flags |
As the flags. The issuesDir, archiveDir and inputDir options are removed |
JSDoc |
as the flags |
Index paths: contentDir (tickets, pulls, discussions) and the release path |
the intake |
Relative to their own root, with forward slashes (issues/chunk-2, v13.1.0.md) |
none |
Pull, Ticket, Release model comments |
old-vs-new equivalence on one corpus; committed-index arms |
corpusBasePath, releaseNotesBasePath (new) in apps/portal/neo-config.json |
the intake |
Fetch is Neo.config.basePath + base + path (Portal.view.content.Component, Portal.view.news.release.Component) |
Defaults: resources/content/ and .github/RELEASE_NOTES/. A deploy sets its own |
JSDoc on getContentPath |
dev-server browser render (L3) |
buildScripts/release/prepare.mjs |
#19408 |
Rebuilds the release and label indexes only |
The conversation indexes, sitemap.xml and llms.txt are rebuilt where a corpus is declared |
@file |
unit suite |
Dev-server static entry for /.github/RELEASE_NOTES |
#19408 |
Serves that one directory at its own path |
No entry without the directory (a workspace). Dotfiles stay unserved elsewhere |
JSDoc |
developmentThemeAssets.spec; browser 404 → 200 |
| Adoption: the pages build |
neomjs/pages#13 |
Step 7 passes both flags; step 5 points releaseNotesBasePath at step 4.1's copy |
Engines before this change ignore both |
none |
neomjs/pages#14 dry run; post-deploy render |
Adoption: the Brain's SyncService#rebuildContentIndexesAndSeo |
neomjs/neo-agent-brain#459 |
Passes corpusRoot once its engine pin moves past this change |
Until then it throws naming --corpus-root. Brain CI stubs that call |
none |
neomjs/neo-agent-brain#459 |
Acceptance Criteria
Out of Scope
- Origin-qualified routes for non-
neo origins (D#19051 acknowledgment AC 3), which is its own leaf.
- The release notes'
publishedAt stamp, which is #19409.
- Adoption outside the engine: neomjs/pages#13 and neomjs/neo-agent-brain#459.
Related
#17416 (parent) · #19158 · #19159 · #19322 · #19408 · #19409 · neomjs/pages#10 · neomjs/pages#13 · neomjs/neo-agent-brain#459 · D#19051
Sweeps at 14:09Z:
- Live latest-open: the latest 20 open issues; none equivalent.
- Org search (
content root generators, served content base, declared corpus root): only #19047 and #17416, neither equivalent.
- MC: as in the #19158 sweep.
- A2A: no claim.
Retrieval Hint: "portal generators corpus root input served content base C1"
Origin Session ID: 1f7129c9-c0f7-42e0-ba47-7a42e5ac57c2
Authored by Grace (Claude Opus 5.5, Claude Code) 🖖
Context
#19158's census (IC 5815306776) lists the engine's build readers that derive the frozen mirror, at filing:
ROOT_DIR = process.cwd()):buildScripts/docs/index/tickets.mjs:31-33,pulls.mjs:30-32,discussions.mjs:22-24;buildScripts/docs/seo/generate.mjs:10,634-695, its conversation globs.__dirname:buildScripts/release/analyzeClosedSinceRelease.mjs:29-32.Vega's comment adds the release notes:
buildScripts/docs/index/release.mjsand the SEO release-note glob read the servedresources/content/release-notesfrom the working directory too, while the engine authors its notes in.github/RELEASE_NOTESsince #19322.The portal's runtime read,
Neo.config.basePath + pathinapps/portal/view/content/Component.mjs:188, fetches whateverpaththose indexes write.The Problem
D#19051 adopted C1.
<corpus-root>is a declared location (ADR 0004 §2.1.1), and deriving it from the working directory is what §5.8 forbids.#19322 deleted the mirror on 2026-09-29. Since then:
The Fix
analyzeClosedSinceReleasetake the corpus root as an input, with no working-directory default.release.mjsand the SEO release routes take the release-notes root as an input. The orchestrator defaults it to the engine's.github/RELEASE_NOTES, where the notes are authored.check-content-root-derivations-baseline.jsonin the same PR.Decision Record impact: aligned with ADR 0004 §2.1.1 and §5.8, and D#19051 C1.
Contract Ledger
--corpus-root <dir>(new) ondocs/index/{tickets,pulls,discussions}.mjs,docs/seo/generate.mjs,docs/rebuildContentIndexesAndSeo.mjs,release/analyzeClosedSinceRelease.mjs. It replaces-i/--issues,-a/--archiveand-i/--inputissues/,pulls/,discussions/andarchive/under the rootTicketIndex.spec,PortalContentIndexes.spec,seo/Generate.spec,RebuildContentIndexesAndSeo.spec--release-notes <dir>(new) ondocs/index/release.mjs,docs/seo/generate.mjs,docs/rebuildContentIndexesAndSeo.mjschunk-N/, below the root<root>/.github/RELEASE_NOTES. Rejection as aboveRebuildContentIndexesAndSeo.spec,seo/Generate.speccorpusRootoncreateTicketIndex,createPullRequestIndex,createDiscussionIndex,rebuildContentIndexesAndSeoand the SEO route functions;releaseNotesRootoncreateReleaseIndex, the SEO route functions and the orchestratorissuesDir,archiveDirandinputDiroptions are removedcontentDir(tickets, pulls, discussions) and the releasepathissues/chunk-2,v13.1.0.md)Pull,Ticket,Releasemodel commentscorpusBasePath,releaseNotesBasePath(new) inapps/portal/neo-config.jsonNeo.config.basePath + base + path(Portal.view.content.Component,Portal.view.news.release.Component)resources/content/and.github/RELEASE_NOTES/. A deploy sets its owngetContentPathbuildScripts/release/prepare.mjssitemap.xmlandllms.txtare rebuilt where a corpus is declared@file/.github/RELEASE_NOTESdevelopmentThemeAssets.spec; browser 404 → 200releaseNotesBasePathat step 4.1's copySyncService#rebuildContentIndexesAndSeocorpusRootonce its engine pin moves past this change--corpus-root. Brain CI stubs that callAcceptance Criteria
--corpus-root, or--release-noteswhere it has no default) fails and names the input, as does a root that is absent or not a directory. With the inputs, it writes the same index as today from the same corpus commit.Out of Scope
neoorigins (D#19051 acknowledgment AC 3), which is its own leaf.publishedAtstamp, which is #19409.Related
#17416 (parent) · #19158 · #19159 · #19322 · #19408 · #19409 · neomjs/pages#10 · neomjs/pages#13 · neomjs/neo-agent-brain#459 · D#19051
Sweeps at 14:09Z:
content root generators,served content base,declared corpus root): only #19047 and #17416, neither equivalent.Retrieval Hint: "portal generators corpus root input served content base C1"
Origin Session ID: 1f7129c9-c0f7-42e0-ba47-7a42e5ac57c2
Authored by Grace (Claude Opus 5.5, Claude Code) 🖖