Promoted from an open question on PR #10736 (#10339), with that dispatch's measurements. No gate is red. Filed unassigned for a maintainer call — the dev who raised it was explicit that it is one.
The recurring defect
A section landing page's ## What's in this module bullet list is hand-kept next to a meta.json that is the real routing source, so it drifts in both directions and nothing checks it. Three known instances:
| section | instance |
|---|
runtime-services | #9604 — chapter list omitted services.sms |
permissions | #10339 — list named 14 of 21 pages; 6 missing, fixed in PR #10736 |
ai | #10737 — omits connect-mcp, which is meta.json's first content page and is linked nowhere else on that page |
The precedent already chose
#9604 did not derive its list. It topped up by hand and added check:runtime-services-index, holding the list to meta.json. So the repo's answer to this class is already "check them, don't generate them" — it has simply only ever been applied to one section.
Why deriving is the wrong shape, measured on permissions
Ruled out on evidence rather than taste, from the #10339 dispatch and re-verified here:
content/docs/permissions/meta.json carries "pages": ["index", "administrator-guide", …] — slugs only, no per-page titles.- So a derived list must synthesize link text from frontmatter, which regresses an existing row today:
access-recipes.mdx is titled "Who can see data / automation / interface" and would render as that instead of the curated "Access Recipes". - The glosses exist in no source file at all — and which pages get one is editorial (3 of 14 on that page, reserved for a title that under-describes its page).
A landing page is an editorial object, not nav config. Generating it loses exactly the parts a reader benefits from.
Population, measured rather than pattern-matched
Exactly three pages carry a hand-written ## What's in this module bullet index: permissions, api, ai.
Two near-misses were checked and dismissed: getting-started is a "Next Steps" pointer list that deliberately links out of the module, and protocol/objectui is a curated "For Implementers" reading list mixing in /docs/references/ links. Nine further sections (ui, automation, capabilities, data-modeling, kernel, protocol, protocol/objectql, protocol/kernel, concepts) use a <Cards> component — a different object, and no claim is made about them.
Shape, if taken
Hold the list's SET and ORDER to its directory's meta.json, both directions, for the three pages above.
⚠️ It cannot be a parameterization of scripts/check-runtime-services-index.mjs — that file is ~1000 lines and bespoke, also checking stability labels, registry slots and canonical-source rows. It wants a new small gate.
⭐ The one stated objection is no longer a blocker. The #10339 report notes such a gate needs "a root package.jsoncheck: entry, which is inside the #9465 epic fence". PR #10698 (#10490), merged 2026-08-21T09:34:38Z, wired a brand-new gate directly into lint.yml as a node scripts/… step with no package.json alias, specifically to stay outside that fence. So the fence does not stand in the way.
The honest argument against
Three pages may not be worth a gate. The counter is that this is the third time it has been found by a human noticing, never by a check, and each instance cost a card. That is a maintainer's call on where the line sits, which is why this is filed rather than dispatched.
If it is declined, the fallback is already in place: PR #10736 adds a source-of-truth comment and drift note beside the permissions list, and that wording can be replicated to api and ai when they are next touched.
Refs
#9604 (the precedent, and its gate) · #10339 / PR #10736 (permissions, where this was measured) · #10737 (the ai instance) · #10698 / #10490 (the fence-free wiring pattern)
Generated by Claude Code
Promoted from an open question on PR #10736 (#10339), with that dispatch's measurements. No gate is red. Filed unassigned for a maintainer call — the dev who raised it was explicit that it is one.
The recurring defect
A section landing page's
## What's in this modulebullet list is hand-kept next to ameta.jsonthat is the real routing source, so it drifts in both directions and nothing checks it. Three known instances:runtime-servicesservices.smspermissionsaiconnect-mcp, which ismeta.json's first content page and is linked nowhere else on that pageThe precedent already chose
#9604 did not derive its list. It topped up by hand and added
check:runtime-services-index, holding the list tometa.json. So the repo's answer to this class is already "check them, don't generate them" — it has simply only ever been applied to one section.Why deriving is the wrong shape, measured on
permissionsRuled out on evidence rather than taste, from the #10339 dispatch and re-verified here:
content/docs/permissions/meta.jsoncarries"pages": ["index", "administrator-guide", …]— slugs only, no per-page titles.access-recipes.mdxis titled "Who can see data / automation / interface" and would render as that instead of the curated "Access Recipes".A landing page is an editorial object, not nav config. Generating it loses exactly the parts a reader benefits from.
Population, measured rather than pattern-matched
Exactly three pages carry a hand-written
## What's in this modulebullet index:permissions,api,ai.Two near-misses were checked and dismissed:
getting-startedis a "Next Steps" pointer list that deliberately links out of the module, andprotocol/objectuiis a curated "For Implementers" reading list mixing in/docs/references/links. Nine further sections (ui,automation,capabilities,data-modeling,kernel,protocol,protocol/objectql,protocol/kernel,concepts) use a<Cards>component — a different object, and no claim is made about them.Shape, if taken
Hold the list's SET and ORDER to its directory's
meta.json, both directions, for the three pages above.scripts/check-runtime-services-index.mjs— that file is ~1000 lines and bespoke, also checking stability labels, registry slots and canonical-source rows. It wants a new small gate.⭐ The one stated objection is no longer a blocker. The #10339 report notes such a gate needs "a root
package.jsoncheck:entry, which is inside the #9465 epic fence". PR #10698 (#10490), merged 2026-08-21T09:34:38Z, wired a brand-new gate directly intolint.ymlas anode scripts/…step with nopackage.jsonalias, specifically to stay outside that fence. So the fence does not stand in the way.The honest argument against
Three pages may not be worth a gate. The counter is that this is the third time it has been found by a human noticing, never by a check, and each instance cost a card. That is a maintainer's call on where the line sits, which is why this is filed rather than dispatched.
If it is declined, the fallback is already in place: PR #10736 adds a source-of-truth comment and drift note beside the
permissionslist, and that wording can be replicated toapiandaiwhen they are next touched.Refs
#9604 (the precedent, and its gate) · #10339 / PR #10736 (permissions, where this was measured) · #10737 (the
aiinstance) · #10698 / #10490 (the fence-free wiring pattern)Generated by Claude Code