Skip to content

docs(spec): state at the renderer that section-grammar headings are not stable anchors - #13199

Merged
os-trump merged 2 commits into
mainfrom
claude/issue-12862-section-anchor-note
Aug 29, 2026
Merged

docs(spec): state at the renderer that section-grammar headings are not stable anchors#13199
os-trump merged 2 commits into
mainfrom
claude/issue-12862-section-anchor-note

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

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:docs prints ✅ 231 generated files in sync with packages/spec, check:generated prints ✓ All 14 generated artifacts are up to date, and git status --porcelain is 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:

重新定级(pm:retriage 已摘):范围 = accept + 在渲染器里写明

⇒ 落地内容:在渲染器处写明「section-grammar 标题按设计重复,其 id 由 slugger 位置性去重,不是稳定锚点」,让下一个想链过去的人当场知道。

「accept + 写明」是不改变任何已发布行为的那一条 —— 是空动作加一条说明。

⛔ 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.

  • The four section-grammar headings — ### Properties, the whole-schema ### Allowed Values, ### Union Options, #### Option Nrepeat 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.
  • Measured scale: 1316 excess section-grammar headings across 162 of the 214 reference pages — 1133 ### Properties, 145 ### Allowed Values, 12 ### Union Options, 26 #### Option N. content/docs/references/ui/view.mdx alone carries 45 ### Properties.
  • They do not collide. Driven on the docs site's own compile path (fumadocs-mdx/noderegister configured by apps/docs/source.config.ts, ids read out of the compiled module, never inferred from library docs): remarkHeading plus the workspace's single copy of github-slugger@2.0.0 de-duplicates them into properties, properties-1, properties-2, …, identically on all three surfaces that consume an anchor — rendered HTML id, fumadocs TOC url, search index. ui/view.mdx renders 215 headings with 215 distinct ids and 0 duplicates.
  • That suffix is positional: properties-17 means the 18th Properties heading 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.
  • Nothing does today: 0 hits across all 7296 tracked files, with a positive control and a pattern control. check:doc-anchors verifies that links RESOLVE, never that anchors are UNIQUE, so its green says nothing about this in either direction.
  • The disposition is recorded as decided, not overlooked, together with the reservation from the re-grade: should stable addressing of the section grammar ever become a product requirement, qualification returns to the table and these counts are what price it.

Where the note went — and one correction to the dispatch's file attribution

packages/spec/scripts/lib/schema-section.ts carries the full note, on renderSchemaSection, plus a four-line pointer at the heading = '### Properties' parameter (the largest population, 1133 of the 1316, and far from the doc block).

packages/spec/scripts/lib/format-type.ts carries a short scoped note on formatPropertyType, pointing at the fuller one rather than duplicating it.

⚠️The dispatch premise that format-type.ts emits ### Allowed Values does not hold against the tree, and the note was written to the tree instead.format-type.ts emits no heading at all — a grep for a heading emission in it returns nothing outside prose. All four section-grammar headings are emitted from schema-section.ts (lines 443, 448, 469 and the renderPropertiesheading default). What format-type.ts owns is the decision in formatPropertyType to relocate a too-wide vocabulary out of its table cell, and schema-section.ts prints that one under a qualified### Allowed Values: OWNER.KEY heading — 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 Values renderings, 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.ts and file-description.test.ts are untouched — a parked PR owns those, and this diff does not intersect it.
  • No change to what any renderer emits. No page regenerated, no ratchet moved.
  • content/docs/releases/** untouched.
  • The slugger measurement and the link scan were not re-run; they are done and accepted.

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 current main and unchanged: 26 matched families over the two changed paths. The union below was run after the final commit, at 52e7f2590. Every exit code was captured before any pipe.

All 26 matched families exit 0, plus check:doc-anchors, check:nul-bytes, check:generated and pnpm --filter @objectstack/spec typecheck. Verdict lines quoted from the gates themselves:

  • ✅ 231 generated files in sync with packages/speccheck: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 heading
  • check-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 full pnpm build
  • check:test-typecheck: OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json

Renderer 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.tsTest 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.mjs exits 3 with PREREQUISITE 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-changeset label declares it. packages/spec's files array is ["dist","json-schema","liveness","prompts","llms.txt","README.md","src/**/*.zod.ts","CHANGELOG.md","api-surface","spec-changes.json"]scripts is 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

…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
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing 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
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 33184fd29289cab2527dc295df4a67d6e17c013apackageMentionDocs.

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33249792607 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL test/serve-publishes-bound-port.e2e.test.ts > #13062 the non-zero half — nothing an ordinary boot publishes may move > follows the DEV AUTO-SHIFT onto the port it really ↳ 失败原因: @objectstack/cli:test: Error: ENOENT: no such file or directory, open '/tmp/os-bound-port-home-nzBEeo/runtime.env_local.json'
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 19 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

2 participants

@os-trump@claude