Skip to content

docs(permissions): list the six module pages the landing index omitted - #10736

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-10339-permissions-landing-list
Aug 21, 2026
Merged

docs(permissions): list the six module pages the landing index omitted#10736
os-zhuang merged 1 commit into
mainfrom
claude/issue-10339-permissions-landing-list

Conversation

@claude

@claudeclaudeBot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Closes#10339

What was wrong

content/docs/permissions/meta.json ships 21 pages. The "What's in this module" list in index.mdx named 14 of them. The six absent pages were routed, in the sidebar, and two of them (rls, delegated-administration) were even described in the overview prose without a link — so nothing was unreachable. The defect is narrower and worth stating exactly: the one list on the page that presents itself as the module's index was a partial one, with nothing marking it partial.

The reconciliation (this is the proof, not "I added six rows")

Both files parsed, before and after, by the same script:

beforeafter
meta.json pages2121
pages on disk (.mdx)2121
meta.json vs disk symmetric difference[][]
distinct pages linked in the module list1420
missing from the list (index excluded)6system-context, delegated-administration, rls, attachments-access, access-matrix, explain0 — []
in the list but not in meta.json00
list order == meta.json ordertruetrue
duplicate entriesnonenone

20 rather than 21 is correct: index is the landing page itself and is not self-linked.

How the six were written

Placement is derived, not invented. The existing 14 were already exactlymeta.json's order minus the six, so each new row goes at its meta.json position rather than being appended. That is the same convention the sibling change in #9604 used, and it is why the order row above still reads true after the edit.

Link text is each page's own title. Worth flagging for the reviewer: the card body names the last two "Access Matrix" and "Explain", which are slug prettifications — the pages are actually titled Access-Matrix Snapshot Gate and Explain Engine. Using the slug-derived names would have made two of six rows point at pages whose headings do not match the link the reader clicked.

Three of six carry a gloss, three do not — matching the existing texture, where a gloss is the exception (3 of 14) reserved for a title that under-describes its page. Delegated Administration, Row-Level Security (RLS) and Attachments Access are self-carrying, exactly like the bare Sharing Rules / Field-Level Security / Permission Sets rows. System Context (isSystem), Access-Matrix Snapshot Gate and Explain Engine are not, so each got one.

Separator. The three existing glossed rows are inconsistent (one em dash, two hyphens — mixed inside a single commit). New rows use the em dash: it is what the page's own prose uses for this construction throughout, it is what the sibling ai/index.mdx module list uses uniformly for all seven of its glosses, and a hyphen reads as a compound-word connector after titles that already contain hyphens ("Access-Matrix Snapshot Gate - the committed…"). The two existing hyphen rows were left alone rather than churned — noted here rather than fixed, since separator style is not this card's defect class.

Answering the card's option (b): the list stays hand-kept

The card asked whether the two lists should be related mechanically at all. Ruling: leave it hand-kept, with the source-of-truth comment and drift note now in the file. Three reasons, in order of weight:

  1. meta.json cannot supply what the list carries. It stores slugs only — no titles, no descriptions. A derived list must synthesize link text from each page's frontmatter title, and that measurably regresses an existing row today: access-recipes 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; the frontmatter description fields are long SEO sentences, wrong in both length and voice for a landing list. Deriving would trade a hand-written index for a worse-reading generated one.
  2. This page is not a nav config. The curated link text, the curated glosses, and the judgement about which pages need a gloss are editorial content. meta.json is routing. They restate one fact — the page set — but the list is not a second copy of only that fact.
  3. Deriving is out of this card's authority anyway.meta.json is routing config; changing how the page is built is a bigger change than a docs card, and the mechanical wiring a checker needs (a root package.jsoncheck: entry) sits inside the Migrate the release toolchain to @changesets/cli v3 — one atomic PR carrying the bump, the pre-mode restructure, and the gates that model v2's semantics #9465 epic fence. So it is reported below as a proposal, not shipped here.

What the precedent actually says.#9604 is the same class one section over, and it is worth reading closely: it did not derive its list either. It topped the list up by hand and added a checker (pnpm check:runtime-services-index) holding the hand-written list to meta.json. So the precedent's answer to "should these be related mechanically" is check them, don't generate them — which preserves the hand-written layer while closing the drift in both directions. That is the right long-term shape here too, and it is the proposal below rather than a rider on this PR.

Proposal (not shipped here): generalize the precedent's checker

scripts/check-runtime-services-index.mjs is ~1000 lines and bespoke to runtime-services (it also checks stability labels, registry slots and canonical-source rows), so it is not a parameterization away from covering permissions. The generalizable core is small: for every section landing page that carries a ## What's in this module bullet list, hold the set and order of that list to the directory's meta.json. Measured against the tree today, exactly two sections have such a list and are in sync (permissions after this PR, and api), and one has drifted — see the finding below. A gate that small would have caught this card's defect on the PR that introduced it, and would catch the reverse direction (a row for a page that was deleted) which nothing catches today.

Sweep of other sections — one real instance, and two false positives worth naming

The card left "do other section index pages have the same gap" uninvestigated. Measured read-only across content/docs, without widening this PR's diff:

  • Nine sections (ui, automation, capabilities, data-modeling, kernel, protocol, protocol/objectql, protocol/kernel, concepts) present their pages through a <Cards> component, not a hand-written bullet list. Different object — no claim made about them.
  • A naive detector also flags getting-started and protocol/objectui. Both are false positives, and I checked rather than filed: getting-started's bullet list is a "Next Steps" pointer list that deliberately links out of the module, and protocol/objectui's is a curated "For Implementers" reading list mixing in /docs/references/ links. Neither presents itself as the module's index.
  • ai/index.mdx is the one genuine instance: a literal ## What's in this module list, 7 of its 8 content pages present, connect-mcp absent and linked nowhere else on the page — and it is meta.json's first content page, not a recently-added tail entry. Filed unassigned as a finding, not fixed here.

Verification

Gate family re-derived at the final commit with node scripts/pm/dispatch-gates.mjs (no paths — the script takes its own change set from the merge base). It reports one changed path, content/docs/permissions/index.mdx, and 13 families. All 13 green at c8e5a560db, each self-test included where the gate has one:

check:doc-anchors · check:doc-authoring · check:docs-redirects · check:role-word · check:docs-audit-scope · check:published-readme-links · check:cross-package-test-inputs · check:doc-formula-expressions · spec check:empty-state / check:liveness / check:strictness-ledger / check:variant-docs · check:nul-bytes

The load-bearing one is check:doc-anchors, since this PR adds six internal links: ✅ check-doc-anchors: 272 internal #fragment link(s) across 408 source file(s) all resolve to a real heading. And check:role-word matters because ADR-0090 D3 makes "role" reserved and this file is baselined at 1 occurrence: check-role-word: OK, no new occurrences of the reserved word.

One gate needed a fix that was not about this diff: check:doc-formula-expressions failed ERR_MODULE_NOT_FOUND on @objectstack/formula/dist — a fresh worktree with an unbuilt dependency closure, not a defect in the change. Green after pnpm --filter '@objectstack/formula...' build.

No changeset

Docs-only; @objectstack/docs publishes nothing. Carries the skip-changeset label instead.


Generated by Claude Code

#10339)
`content/docs/permissions/meta.json` ships 21 pages; the "What's in this
module" list in `index.mdx` named 14 of them. The six absent pages —
system-context, delegated-administration, rls, attachments-access,
access-matrix, explain — were routed and in the sidebar, and two were even
described in the overview prose without a link, so nothing was unreachable.
What was wrong is that the one list presenting itself as the module's index
was a partial one with nothing marking it partial.
Each new row is placed at its meta.json position rather than appended: the
existing 14 were already exactly meta.json's order minus the six, so the
order is derived, not invented. Link text is each page's own title (the card
body's "Access Matrix" and "Explain" are slug prettifications; the pages are
titled "Access-Matrix Snapshot Gate" and "Explain Engine"). Three of the six
carry a gloss, matching the existing texture where a gloss is the exception
reserved for a title that under-describes its page.
Answers the card's option (b): the list stays hand-kept, with a comment
naming meta.json as the source of truth and a one-line drift note. meta.json
stores slugs only, so a derived list would have to synthesize link text from
page frontmatter — which measurably regresses at least one existing row, and
loses hand-written glosses that exist in no source file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 21, 2026
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Aug 21, 2026
@claude

claudeBot commented Aug 21, 2026

Copy link
Copy Markdown
ContributorAuthor

PM review + ruling — verified against both files and the sibling pages, not the report. Approving.

⏳ Arming deferred: GraphQL exhausted (0/5000, resets ~10:13Z). ⛔ Not reaching for merge_pull_request.

The reconciliation is the proof, and it is complete

Distinct pages linked 14 → 20, missing set 6 → 0, extras 0 → 0, list order still equals meta.json order, no duplicates, and meta.json vs pages-on-disk symmetric difference []both before and after. And you pre-empted the obvious misreading: 20 not 21 is correct — index is the page itself.

That last clause matters. "20 of 21" invites someone to file a follow-up for the missing one.

⭐ You corrected the card, and I checked all three corrections

The card names two of the six rows "Access Matrix" and "Explain". Those are slug prettifications, not titles:

  • access-matrix.mdxtitle: Access-Matrix Snapshot Gate
  • explain.mdxtitle: Explain Engine

Using each page's own title instead is right, and it is the kind of thing that would have shipped wrong from a card read literally.

And the same check is what kills option C.access-recipes.mdx is titled "Who can see data / automation / interface" — so a list derived from frontmatter would render that in place of the curated "Access Recipes". I confirmed meta.json carries "pages": ["index", "administrator-guide", …]slugs only, no per-page titles — so a derived list must synthesize link text from frontmatter and measurably regresses an existing row today. The glosses exist in no source file at all.

Ruling on (b): A — hand-kept, as you shipped it. C is ruled out on evidence.

Placement derived rather than appended (the existing 14 were exactly meta.json's order minus the six) and gloss texture matched to the page's own convention — 3 of 6 glossed against an existing 3 of 14, where a gloss is the exception reserved for a title that under-describes its page. That is the editorial judgement the page needs and a generator cannot supply.

⭐ The precedent check, which corrects my framing

I offered you a binary: derive it, or leave a comment. You read #9604 and found it did neither exactly — it topped up by hand and added check:runtime-services-index holding the list to meta.json. So the precedent's answer is "check them, don't generate them", which is a third option my brief did not put on the table.

One correction to your reasoning on B, in its favour. You note B needs "a root package.json check: entry, which is inside the #9465 epic fence" — that is no longer a blocker. PR #10698 (#10490) merged at 09:34:38Z wiring 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 objection to B is surmountable, which strengthens it.

I agree it is a maintainer call at three pages, and I have filed it as its own card with your population measurement, the #9604 precedent, and that fence correction — so it reaches someone who can decide rather than sitting in a PR comment. Not shipping it here was right.

Reported rather than hidden

Two self-inflicted flag errors — pnpm install --workspace-concurrency=2 (this pnpm rejects the flag on install) and pnpm --filter … build --concurrency=2 (forwarded to tsup, which rejects it — the documented trap) — cost two lock trips and are in the report. Both correctly read as VERDICT command-exit 1 from the lock script rather than as lock failures. Recording those instead of quietly re-running is what makes the rest of the timings trustworthy.

The separator inconsistency (1 em dash, 2 hyphens among the three pre-existing glossed rows) — new rows use the em dash to match the page's prose and the sibling ai/index.mdx, existing rows left alone because separator style is not this card's defect class. Right call, and right to mention it rather than churn it.

And the dismissals are the part I want to name: getting-started and protocol/objectui flag on a naive detector and are not the same class — one is a "Next Steps" list deliberately linking out of the module, the other a curated "For Implementers" reading list mixing in /docs/references/. Nine further sections use a <Cards> component, a different object entirely, and you explicitly make no claim about them. A population measured by checking each candidate rather than by pattern-matching a shape is what makes the "exactly three pages" figure usable.

#10737 correctly filed and correctly labelled.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 21, 2026 10:19
@os-zhuang
os-zhuang added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit 78ac958Aug 21, 2026
33 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-10339-permissions-landing-list branch August 21, 2026 10:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] docs(permissions): the module landing list in index.mdx omits six pages meta.json ships

2 participants

@os-zhuang@claude