docs(spec): state at the renderer that section-grammar headings are not stable anchors - #13199
Merged
Merged
Conversation
…ot stable anchors The four unqualified section-grammar headings emitted by the reference-page renderer (### Properties, ### Allowed Values, ### Union Options, #### Option N) repeat by design: one page carries many schema sections and, unlike the per-key ### Nested Shape: / ### Allowed Values: headings, they carry no qualifier by construction. Measured: 1316 excess occurrences across 162 of 214 pages. They do not collide -- the docs site's slugger (remarkHeading + github-slugger@2.0.0) de-duplicates them into properties, properties-1, ... on all three consumer surfaces -- but that suffix is positional, so a section-grammar anchor is unstable even though it is unique. Source-side note only; the emitted pages are byte-unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
…ction-anchor-note
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Aug 29, 2026
os-trump
marked this pull request as ready for review
August 29, 2026 11:09
os-trump
enabled auto-merge
August 29, 2026 11:09
Contributor
This was referenced Aug 29, 2026
This was referenced Aug 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #12862
Source-side note only, in the renderer that emits the section grammar. The emitted reference pages are byte-unchanged — proven rather than assumed:
check:docsprints✅ 231 generated files in sync with packages/spec,check:generatedprints✓ All 14 generated artifacts are up to date, andgit status --porcelainis empty after both runs.What this executes
The card's re-grade picked one of the three dispositions triage had reserved, on the measurement the previous dispatch delivered. Verbatim and untranslated, per the repo's rule on quoting Chinese rulings:
⛔ Not qualify — that rewrites every heading on 162 pages. ⛔ Not demote — that changes the published rendering shape. Both were weighed and rejected in the re-grade: zero correctness harm (no anchor collision exists) plus zero measured demand (no inbound link exists), and the point that decided it, that the de-dup suffix is positional, so a section-grammar anchor is unstable even when unique — only qualification could buy stable addressing, and qualification is the expensive one.
The note, and the measured facts it carries
Nothing here is re-derived; every number comes from the measurement report on the card.
### Properties, the whole-schema### Allowed Values,### Union Options,#### Option N— repeat by design. One page carries many schema sections, and unlike the per-key headings beside them they carry no qualifier by construction. The per-key ones do, precisely so a page cannot mint the same anchor twice, and they stay addressable.### Properties, 145### Allowed Values, 12### Union Options, 26#### Option N.content/docs/references/ui/view.mdxalone carries 45### Properties.fumadocs-mdx/noderegisterconfigured byapps/docs/source.config.ts, ids read out of the compiled module, never inferred from library docs):remarkHeadingplus the workspace's single copy ofgithub-slugger@2.0.0de-duplicates them intoproperties,properties-1,properties-2, …, identically on all three surfaces that consume an anchor — rendered HTMLid, fumadocs TOC url, search index.ui/view.mdxrenders 215 headings with 215 distinct ids and 0 duplicates.properties-17means the 18thPropertiesheading in document order, so inserting a schema section earlier on the same page silently renumbers every anchor below it. ⇒ These are not stable anchors and nothing should link to them.check:doc-anchorsverifies that links RESOLVE, never that anchors are UNIQUE, so its green says nothing about this in either direction.Where the note went — and one correction to the dispatch's file attribution
packages/spec/scripts/lib/schema-section.tscarries the full note, onrenderSchemaSection, plus a four-line pointer at theheading = '### Properties'parameter (the largest population, 1133 of the 1316, and far from the doc block).packages/spec/scripts/lib/format-type.tscarries a short scoped note onformatPropertyType, pointing at the fuller one rather than duplicating it.format-type.tsemits### Allowed Valuesdoes not hold against the tree, and the note was written to the tree instead.format-type.tsemits no heading at all — a grep for a heading emission in it returns nothing outside prose. All four section-grammar headings are emitted fromschema-section.ts(lines 443, 448, 469 and therenderPropertiesheadingdefault). Whatformat-type.tsowns is the decision informatPropertyTypeto relocate a too-wide vocabulary out of its table cell, andschema-section.tsprints that one under a qualified### Allowed Values: OWNER.KEYheading — unique per page by construction, i.e. the addressable population, not the section grammar. So the scoped note there states exactly that distinction: two### Allowed Valuesrenderings, only one of them addressable. The route is unchanged; only the attribution is corrected, and this is reported rather than routed around.Bounds honoured
packages/spec/scripts/build-docs.tsandfile-description.test.tsare untouched — a parked PR owns those, and this diff does not intersect it.content/docs/releases/**untouched.Verification
Gate family derived mechanically with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no hand-fed paths), re-derived after the merge of currentmainand unchanged: 26 matched families over the two changed paths. The union below was run after the final commit, at52e7f2590. Every exit code was captured before any pipe.All 26 matched families exit 0, plus
check:doc-anchors,check:nul-bytes,check:generatedandpnpm --filter @objectstack/spec typecheck. Verdict lines quoted from the gates themselves:✅ 231 generated files in sync with packages/spec—check:docs✓ All 14 generated artifacts are up to date.—check:generated✅ check-doc-anchors: 280 internal #fragment link(s) across 410 source file(s) all resolve to a real headingcheck-nul-bytes: OK (scanned 7310 text file(s) -- 7310 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/**, all 14 recorded and every recorded row still reached✓ check:dual-build-cjs-loads — 102 published require entry point(s) across 66 package(s) load; 610 emitted CommonJS file(s) parse✓ 67 package build artifacts present (existence, not freshness).—check:dev-prereqs, after a fullpnpm buildcheck:test-typecheck: OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.jsonRenderer test suites:
pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 scripts/schema-section.test.ts scripts/format-type.test.ts scripts/nested-shape.test.ts—Test Files 3 passed (3),Tests 161 passed (161).One family is NOT MEASURED locally, by its own instruction rather than by a skip:
node scripts/check-test-completeness.mjsexits 3 withPREREQUISITE NOT MET — this gate grades a saved 'turbo run test' log, and no log was named, and its own text says to record the local reading as NOT MEASURED. CI passes it a log on every invocation, so CI behaviour is unaffected.Changeset
None, deliberately, and the
skip-changesetlabel declares it.packages/spec'sfilesarray is["dist","json-schema","liveness","prompts","llms.txt","README.md","src/**/*.zod.ts","CHANGELOG.md","api-surface","spec-changes.json"]—scriptsis not in it, so nothing in this diff is published from any package. The diff is comment text only: 63 added lines, 0 removed, 0 lines of executable change. The immediately comparable precedent is PR #13183.Generated by Claude Code