Skip to content

feat(scripts): hold every section landing index to its meta.json, and top up the two drifted ones - #11259

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-10738-section-landing-index-gate
Aug 23, 2026
Merged

feat(scripts): hold every section landing index to its meta.json, and top up the two drifted ones#11259
os-zhuang merged 2 commits into
mainfrom
claude/issue-10738-section-landing-index-gate

Conversation

@claude

@claudeclaudeBot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Fixes#10738

Adds scripts/check-section-landing-index.mjs and repairs the two landing pages it found drifted.

The population is 8, not 3 or 4 — and the discriminator is the heading, not the shape

The card measured 3, corrected itself to 4, and left open whether the Cards-grid sections were covered, noting that api/index.mdx carries both shapes so the two categories "cannot be treated as disjoint". Re-measured on origin/main under the card's own stated parse method, shape is simply the wrong discriminator. The right one is the heading itself: writing ## What's in this module is the author declaring "this block is the index of this module".

Exactly 8 sections declare it, in one spelling (ASCII apostrophe, no in this chapter/in this section variant anywhere under content/docs, and no non-index page carries it):

sectionshapemeta.json pageslinkeddriftorder
aibullets88ok
apibullets1111ok
automationCards99ok
data-modelingCards17152 missingok
kernelCards77ok
permissionsbullets2020ok
pluginsbullets44ok
uiCards15114 missingwrong

The other 26 landing pages under content/docs declare no index block and are not held. That is what keeps the deliberately curated ones curated — protocol/objectui's "For Implementers" reading list mixing in /docs/references/ links, getting-started's "Next Steps" pointing out of its module, and the narrative concepts / protocol / protocol/kernel pages. None can be caught by accident; they would have to write the heading first.

One strength, not two

The dispatch suggested two strengths — set-and-order for the bullet lists, coverage-only for the grids. Measurement beat it: order already holds on 7 of the 8, and the eighth is ui, the page this gate was written for. So set and order holds for all 8, both shapes read identically, and no shape-dependent branch exists to get wrong.

Out-of-section links are ignored entirely, never counted and never ordered. That clause is what lets an editorially curated block stay curated: ai, api, permissions and plugins each close their block with cross-references into /docs/protocol/ and /docs/references/, and all four are green.

Hrefs only. Link text, glosses, bolding and Card descriptions stay hand-written, because meta.json stores slugs only — a generated block would synthesize link text from frontmatter and regress a row that exists today (permissions/access-recipes.mdx is titled "Who can see data / automation / interface" and would render as that instead of the curated "Access Recipes"). Check them, don't generate them — the #9604 precedent, re-measured.

The ui omission is a live customer-visible symptom

content/docs/ui/index.mdx omitted react-pages, actions, reports and translations, and listed views before pages. The react-pages row is the residual half of the P0 #10638 report — "https://objectstack.ai/docs/ui does not show the React Pages entry". That card's deploy half already landed: the page serves and react-pages is in the sidebar payload. What was still missing in production is its row on the landing page, because the hand-kept grid never got one.

Demonstrated red, not a belief

Same command, same real tree, before and after the docs commit:

$ node scripts/check-section-landing-index.mjs # at f5a52e13b (gate only)
✗ check-section-landing-index -- 2 problem(s)
• data-modeling/index.mdx: ... omits 2 page(s) ...: field-type-decision-tree, validation-rules.
• ui/index.mdx: ... omits 4 page(s) ...: react-pages, actions, reports, translations.
exit 1
$ node scripts/check-section-landing-index.mjs # at 28d591a68
✓ check-section-landing-index: 8 section index block(s) enumerate their meta.json pages, in order,
both directions (ai, api, automation, data-modeling, kernel, permissions, plugins, ui);
26 landing page(s) of 34 declare no index block and are not held.
exit 0

The gate is committed before the docs fixes on purpose, so that red is reproducible at f5a52e13b.

--self-test adds 31 assertions over the real judge()/run() path: both shapes in sync, missing page, undeclared row, wrong order, the pre-fix ui grid reproduced, foreign-link curation, ### sub-heading vs same-level heading, fence and MDX-comment masking, index/---Group--- filtering, opt-in skipping, and all seven refusals.

Refusing rather than passing

This gate computes its own population, so a parser that stopped matching would print a confident green over a tree it never read (#4690's family). Seven refusals close that: empty index block, a meta.json page with no file on disk (reported as a meta.json defect, not an index defect — the set rule would otherwise order an author to link a dead route), unparseable meta.json, no pages array, unreadable section, empty census, and a census below EXPECTED_MIN_SECTIONS.

⚠️ Declared surface deviation: content/docs/data-modeling/index.mdx

The dispatch surface named content/docs/{permissions,api,ai,plugins,ui}/index.mdx. data-modeling was not on it, because at dispatch time nobody knew it was in the population — it was one of the nine Cards sections the card had set aside. The gate found it drifted by two real pages (field-type-decision-tree, validation-rules, both real .mdx files), so leaving it would have shipped a gate red on main.

Taken as a bounded in-place fix: same defect class as this card, mechanical with the correct form pinned by meta.json, no other claim on the file (7 open PRs checked by file list — only #11244 touches content/docs, at ui/pages.mdx, no index.mdx and no meta.json), and the same gate family with no new verification surface. Weakening the gate to exclude it was the alternative and was rejected: a baseline written to make CI green is the defect this class is about.

content/docs/permissions/index.mdx also loses its drift note, which said "nothing checks the two against each other in either direction" — no longer true, so it now names the gate instead.

Wiring

A direct node scripts/… step in lint.yml with no root package.json alias, on the #9465 fence precedent set by PR #10698. dispatch-gates.mjs derives gate families from either spelling and discovers it: node scripts/check-section-landing-index.mjs [lint.yml]. Self-test first, then the gate — the dominant sanctioned shape, and explicitly not the multi-script pattern check:step-collectors flags.

No changeset: the diff touches scripts/, .github/workflows/lint.yml and content/docs/** only. @objectstack/docs is private: true and content/docs sits in no package, so nothing publishes. Labelled skip-changeset.

Verification

All gates below run at 28d591a68, the final commit, and every verdict is the gate's own printed line.

gateverdict
check:entry-guard · check:parse-guardpass
check:doc-anchors · check:doc-authoring · check:doc-frontmatterpass
check:docs-audit-scope · check:docs-redirects · check:published-readme-links · check:role-wordpass
check:node-version · check:required-contexts · check:shard-attestation · check:pnpm-filter-targets · check:workflow-status-functionspass
check:cross-package-test-inputs · check:aggregator-roster · check:ci-filter-parity · check:step-collectors (+ self-test)pass
spec liveness: check:empty-state · check:liveness · check:strictness-ledger · check:variant-docspass
lint: check:doc-formula-expressions · check:doc-security-posturepass
check:nul-bytesOK (scanned 6528 text file(s) … no raw ASCII control bytes)
check:type-check-coverageOK — 65/78 workspace packages type-checked
check:type-check-debtmeasured, not refused: 33 ledger entr(ies) re-measured in 225.0s, 1897 raw tsc error(s) total, none above its recorded number (workspace closure built first: 72 successful, 72 total)
node scripts/check-section-landing-index.mjs (+ self-test)pass — 31 assertions; red/green transcript above

pnpm turbo run build also compiled @objectstack/docs: ✓ Compiled successfully in 71s, ✓ Generating static pages (1220/1220) — the edited MDX parses and its routes resolve.

ESLint — declared narrowing, measured not skipped. Full-repo pnpm lint was attempted under the shared verify lock and died exit 134, a V8 heap OOM at --max-old-space-size=4096 with two sibling agents holding heavy work; that is a resource verdict, not a lint verdict. The narrowing is proven on three readings:

  1. Population, from ESLint's own config (ESLint#isPathIgnored over git ls-files, not a guess): 4899 of 6533 tracked files.
  2. Files linted, from --format json: 5 changed files, errorCount: 0. Exactly one — scripts/check-section-landing-index.mjs — is in the population (E0 W0); the other four are config-ignored, their single warning apiece being ESLint's own "file ignored" notice, not a finding.
  3. Config invariance for untouched files: this repo runs one eslint.config.mjs which "never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file" — its own words, backed by its own planted positive control — and this diff changes no ESLint config. Each file's verdict therefore depends only on that file plus config, so nothing in this diff can move the verdict of any of the 4898 untouched in-population files.

CI runs the full farm regardless.


Generated by Claude Code

#10738)
Adds check-section-landing-index.mjs. A section's meta.json is the routing
source fumadocs builds the sidebar from; the landing page's
"## What's in this module" block is hand-kept beside it and nothing read the
two against each other, so it drifted in both directions one edit at a time
with every gate green (#9604 runtime-services, #10339 permissions,
#10737 ai).
The heading is the opt-in, not the rendering shape: 8 sections declare it,
4 as bullet lists and 4 as Card grids, and the gate reads both identically.
26 landing pages that declare no index block are not held.
Committed before the docs fixes so the red is reproducible at this commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPBxLsqQGCVL5z5UXvgipH
…gate into lint.yml (#10738)
content/docs/ui/index.mdx omitted react-pages, actions, reports and
translations, and listed views before pages. The react-pages omission is the
residual half of the P0 #10638 symptom: the deploy fix restored the page and
the sidebar, but the hand-kept grid never got its row, so
objectstack.ai/docs/ui still does not show React Pages.
content/docs/data-modeling/index.mdx omitted field-type-decision-tree and
validation-rules — a second real drift, found by the gate, outside the
population the card had measured.
permissions/index.mdx's drift note said nothing checks the two against each
other; that is no longer true, so it now names the gate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPBxLsqQGCVL5z5UXvgipH
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddocumentationImprovements or additions to documentationsize/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hold every section landing list to its meta.json — 4 hand-kept lists, and #9604's precedent already says "check them, don't generate them"

2 participants

@os-zhuang@claude