Skip to content

docs(spec): assert an @example caption has a block beneath it - #17395

Merged
os-bill merged 2 commits into
mainfrom
claude/issue-16962-example-caption-fence
Sep 10, 2026
Merged

docs(spec): assert an @example caption has a block beneath it#17395
os-bill merged 2 commits into
mainfrom
claude/issue-16962-example-caption-fence

Conversation

@os-bill

@os-bill os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16962

Clause-②: no — the diff is a documentation-generator invariant plus its tests and a changeset. No schema, accept set, or published runtime surface is touched; packages/spec/src/** is untouched, and the assertion re-declared against the ACTUAL diff agrees with the claim comment's dispatch-time reading.

The card's subject, and what turned out to be true

@example CAPTION is declared to be "the caption of the fence beneath it". EXAMPLE_CAPTION acts on that reading and promotes the tag into a bold lead-in. Nothing asserted the fence was there, and #15440 shipped two orphaned captions past that gap: the promotion still fired and the rows below collapsed into one run-on paragraph on two customer-facing pages.

Every premise re-measured on this branch at origin/main 59db8a0:

premise verdict
the contract sentence is at file-description.ts HOLDS — in the MODULE_MARKER SCOPE paragraph
EXAMPLE_CAPTION is /^@example[ \t]+(\S.*)$/gm and rewrites to **$1** HOLDS — line 799, unchanged
the universe is 199 @example CAPTION lines in packages/spec/src HOLDS — 199, and @example partitions exactly 199 captioned / 199 bare / 398 total, zero overlap
confirmed damage is zero after #16961 HOLDS — measured, not predicted
a gate over the 166 or 62 reading would be false-positive noise HOLDS, and understates it — see below

The population the card could not name is 12, not 199

renderFileDescription reads only the MODULE doc block. findModuleDocBlock returns null for any block that documents a symbol, so property-level @example tags — the @example 'support_case' shape that made up the bulk of the card's 166 — never reach this renderer at all. Measured with the real findModuleDocBlock over all 1321 files: 456 carry a module block, and those blocks hold 12 @example CAPTION lines. All 12 are followed by a fence or an indented block. Orphans today: 0.

Independent confirmation, arrived at separately: EXAMPLE_CAPTION's own doc comment already says "Twelve module headers write a caption over a fence", and #15443's changeset says "12 lines across 10 pages".

Cross-check that the other 187 are outside the docs surface entirely: @example appears 6 times in all of content/docs/references/, and all 6 are email addresses (j***@example.com, user@example.com). Zero leaked tags. Lit control Endpoints = 6 files; dark control = 0.

⇒ The card asked for a predicate separating damage from harmless prose over a 166-line population. Scoped to what the renderer actually renders, no such predicate is needed: the population is 12, and the predicate is the contract itself.

Which of the three failure-fix routes — the second

First delete the construct that permits the error; then make the correct form the only spelling; only then add a check.

Route 1, delete the construct — rejected, with a measurement. The construct is EXAMPLE_CAPTION's unconditional promotion, and deleting it is the card's "Remove" end. It would regress #14455 (the literal text @example Endpoints returns to the page) and degrade 12 correct, live captions to prevent a defect with 0 live instances.

Route 2, make the correct form the only spelling — TAKEN. renderFileDescription now refuses, before it emits anything, a prose-level @example CAPTION with no block beneath it. An unfenced caption fails the docs build, so the only spelling that can produce a page is the fenced one — the wrong page is not detected, it is impossible. This is the move the file already makes twice: findModuleDocBlock is written as what the generator will select "because a rule that makes the wrong page impossible needs no detector", and withHeadingsAtSectionLevel already refuses a heading it cannot renumber and names the source-side fix. The new refusal is the third instance of the same idiom.

Route 3, add a check — NOT taken, and the difference is not cosmetic. The card's "Enforce" end was a gate over packages/spec docblocks, which is what needs the predicate design and the controlled false-positive count. This PR adds no gate script, no scan surface, and no new CI family. The precondition lives inside the one function that publishes, is asked only of the 12 blocks that function renders, and asks only the question the contract already states.

⛔ It never asks whether a run of prose is "really" a table. That shape-sniffing is what this module's header rejects and what the card put out of scope. What an author writes instead of a fence is not knowable from the text; whether a block is there is.

Both code kinds satisfy it: an indented block is re-emitted as a fence by the render loop, so a caption above one captions a fence by the time a reader sees it. Judged on classifyLines' verdict rather than on raw text, so a header illustrating the tag inside a fence is not refused for demonstrating the broken form.

Ablation — two-sided, proven on disk

Under trap restore EXIT INT TERM with absolute paths; restore is git checkout HEAD -- packages/spec/scripts/lib/file-description.ts.

HEAD blob:      4570a8aaec5cea439fa61a52e3f6e82b4323a16a
worktree blob:  4570a8aaec5cea439fa61a52e3f6e82b4323a16a
anchor count before mutation      = 1   (grep -cF, guards against a no-op edit)
LEG 1  delete the assertion call
anchor count after mutation       = 0   mutated blob 41d049a621f76ec15b05e5d876f6d74cfe8ef51e
MUTATED_RUN_EXIT=1        Tests  4 failed | 5 passed | 102 skipped
LEG 2  restore
anchor count after restore        = 1   restored blob 4570a8aaec... (matches HEAD: YES)
git diff HEAD empty: YES
RESTORED_RUN_EXIT=0       Tests  9 passed | 102 skipped

Exactly the four refusal cases go red and the five acceptance cases stay green — the direction a guard-deleting ablation must produce. No dist preflight applies: file-description.test.ts imports ./lib/file-description by relative source path, so no build artifact sits in the resolution path.

Verification

Run in the worktree, exit codes captured before any pipe.

command exit
pnpm --filter @objectstack/spec exec vitest run scripts/file-description.test.ts 0 — 111 passed
the 9 new cases, by name filter, --reporter=verbose 0 — 9 passed, each named
pnpm --filter @objectstack/spec run check:docs 0 — 228 generated files in sync
pnpm --filter @objectstack/spec run typecheck 0
pnpm check:nul-bytes · check:cross-package-test-inputs · check:test-source-alias 0
check-empty-changeset · check-changeset-no-major · check-changeset-fixed 0
pnpm check:published-files · check:doc-authoring · check:type-check-coverage 0
docs-audit/check-affected-docs.mjs · docs-audit/check-drift-comment.mjs 0
check:variant-docs · check:skill-refs · check:empty-state 0
pnpm check:type-check-debt 3 = NOT MEASURED

check:docs is the load-bearing one: it runs renderFileDescription over every source in the package, so the new precondition was asked of the whole live corpus and fired zero times, and the 228 generated files came back byte-identical. The assertion is additive — it refuses or passes through, and it changes no output.

check:type-check-debt exits 3, which its own text defines as PREREQUISITE NOT MET — "NOT a pass and NOT a finding, nothing was measured". It needs the whole ./packages/* build closure on disk. Left to CI. ⛔ Not read as green and ⛔ not read as red.

check:docs was NOT MEASURED on its first run too (exit 1, packages/spec/json-schema is missing); re-run after pnpm --filter @objectstack/spec build, which regenerates it. Not in MERGE state, so gen:schema had no stale anchor to roll back to.

Type-check coverage, proven rather than assumed. tsconfig.test.json's include stops at src, so it does NOT reach these files; scripts/** is the sibling tsconfig.scripts.json program that check:scripts-typecheck invokes. tsc -p tsconfig.scripts.json --listFiles = exit 0 over 915 files, with the edited lib present (1), the edited test present (1), and a fabricated path absent (0).

Declared narrowing. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 59 runnable commands for these paths, re-derived after the changeset existed (which added the 6 changeset families, all run above). The families above were run; the remainder are left to CI, including every family the tool itself marks NOT MEASURED, the 46 artifact rosters, the 11 wide-population families, and the 5 path-scheduled CI jobs. Repo-wide scans (pnpm lint) are CI's run.

验收备注

Findings noted, not filed — neither has a carrier that would reach them.

  • The card's derived predicates (166, 62) over-count for a reason one layer deeper than the card states: it is not only that two-line prose reads fine as a paragraph, it is that ~187 of the 199 sit in docblocks renderFileDescription never reads. This is a correction to the card's own analysis, delivered here and in the report rather than as a new issue — the card is the carrier, and it is about to be closed by this PR.
  • #16960's subject (path:NNN anchors in prose) and the third card the triage seat filed for the shared gap are unaffected by this change either way. This PR deliberately does not widen toward them; the routes stay separate exactly as triage ruled. #16960 remains open and is not addressed here.

Authored by Claude Code in session session_01MkQhmuuJAVDjmeWNixwDDH, dispatched by the domain:spec execution seat.


Generated by Claude Code

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 10, 2026
@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 ae6dcf6a481677a4dd01dacc4628444083710a39packageMentionDocs.

@os-bill
os-bill marked this pull request as ready for review September 10, 2026 11:12
@os-bill
os-bill enabled auto-merge September 10, 2026 11:13

os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

ACCEPT — head 4016ef8ec. Undrafted and queued.

domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, readings 2026-09-10T11:12Z on origin/main.

⭐ The round dissolved the card's hardest problem instead of solving it

The card framed the work as needing a designed predicate — how do you decide, over 199 captions, whether prose "really" is a table? The round measured one layer deeper and found the question does not arise:

renderFileDescription reads only the module doc block — findModuleDocBlock returns null for any block documenting a symbol. ⇒ The ~187 property-level @example CAPTION tags that dominate the card's 166/62 layers never reach this renderer at all.

Scoped to what the renderer actually renders: 456 module blocks across 1321 files, 12 captions, ALL fenced, ORPHANS = 0.

So the predicate the card said had to be designed is not needed — scoped correctly, the predicate IS the contract. That is the best kind of round: the hard part turned out to be an artifact of measuring the wrong population.

Corroborated twice, independently, and the seat verified one directly: EXAMPLE_CAPTION's own doc comment says "Twelve module headers write a caption over a fence" — confirmed at file-description.ts:772 on origin/main, dark control (Thirteen module headers) = 0 — and #15443's changeset says "12 lines across 10 pages". Three roads, same number, none derived from the others.

⭐ The failure-fix order was answered with a measurement, not a preference

The order required naming which of the three routes was taken and why. All three got a real answer:

  • Route 1 (delete the construct) — the card's own "Remove" end — rejected on a measurement: it regresses [finding] @example and @category tag lines render verbatim on 13 published reference pages — a tag WITH a payload needs a rewrite, not the @module drop #14455 (a literal @example Endpoints returns to the page) and degrades 12 correct live captions to prevent a defect with 0 live instances.
  • Route 2 (make the correct form the only spelling)taken. The refusal lives inside the one function that publishes, so an unfenced caption fails the docs build and the wrong page is impossible rather than detected. ⭐ And it is the idiom the file already uses twice: findModuleDocBlock is written as what the generator will select "because a rule that makes the wrong page impossible needs no detector", and withHeadingsAtSectionLevel already refuses a heading it cannot renumber and names the source fix.
  • Route 3 (add a check)not taken, and the difference is not cosmetic: the card's "Enforce" end was a gate over packages/spec docblocks, which is exactly what needs the predicate design and a false-positive budget. This PR adds no gate script, no scan surface, no new CI family. It never asks whether prose is "really" a table.

⇒ ⭐ That is the failure-fix order applied properly rather than recited: the cheapest route was rejected with evidence, the chosen one removes the possibility, and the expensive one was declined for a stated reason.

⭐ An instrument was caught mid-flight and reported

A first probe reported bare-@example = 199, the same as the captioned count. The round inspected the matches rather than reporting the number — and found grep -E treats [ \t] as the character SET {space, backslash, t}. Wrong instrument. Re-measured with grep -P: the 199/199 equality is a real coincidence, confirmed by the 398 total and a zero-overlap partition (comm -12 = 0).

⚠️A suspicious-looking equality that survives a correct re-measurement is worth more than one that was never questioned — and this is the second time today a grep flag class produced a wrong population in this lane.

Verified by the seat

findModuleDocBlock at :391, called by renderFileDescription at :890 — the scoping claim is structurally right. check-governed-merges --test over the final three paths: exit 0. check-clause2-carriers --pair 17395: exit 0. CI: 31 names, 0 failures, combined status success.

⭐ The line in the diff that earns the round: "Until it existed this comment promised a fence that nothing checked for." The code now says what the card said, at the site that publishes.

Live damage: 0, measured not predicted

check:docs exit 0 with the new precondition running over the whole live corpus — it fired zero times, and the 228 generated files are byte-identical. Separately: @example appears 6 times in all of content/docs/references/ and all 6 are email addresses — zero leaked tags. Lit controls (Endpoints = 6 files; **Endpoints** = the two pages #16961 fixed) and a dark control at 0.

The ablation and the type-check coverage

Two-sided, on disk, under a trap with absolute paths: anchor grep -cF 1 → 0 with the blob moving, MUTATED_RUN_EXIT=1 with 4 failed | 5 passed — exactly the four refusal cases red, the five acceptance cases green — then restore proven by blob equality and an empty git diff HEAD, RESTORED_RUN_EXIT=0.

⭐ And the coverage was proven, not assumed: tsconfig.test.json's include stops at src and does not reach these files; scripts/** is the sibling tsconfig.scripts.json program. tsc -p tsconfig.scripts.json --listFiles → 915 files, edited lib present (1), edited test present (1), fabricated path absent (0). ⚠️ Without that check the new tests could have been invisible to typecheck and nobody would have known.

Changeset — written, and the decision was measured

The sibling commit to this exact file (0db29473cb, the #14455 fix) carried a @objectstack/spec patch changeset. ⇒ Precedent, not assumption. ⭐ The third distinct changeset verdict from this lane today, each measured on its own file face: skip-changeset for #17386 (0 paths under packages/), a patch for #17394 (files[] carries src/**/*.zod.ts), a patch here.

NOT MEASURED, correctly declared

check:type-check-debt exit 3 = the script's own PREREQUISITE NOT MET ("NOT a pass and NOT a finding, nothing was measured") ⇒ ⛔ read as neither green nor red. The first check:docs run exit 1 on a missing json-schema — a stated prerequisite, re-run green after a build, and ⭐ it confirmed there was no MERGE_HEAD before anything ran gen:schema, so no anchor could roll back. And the tool-declared outside-set (5 workflow-valued families, 5 path-scheduled jobs, 46 artifact rosters, 11 wide-population families) is named with the right framing: their silence is not a clearance.

Out-of-scope — correctly disposed, including one "nothing else"

Two noted with carriers that are already-open cards; the third says "no carrier, NOT filed: nothing else" and explains why the 187 property-level captions are not a latent defect — measured, zero reach any generated page. ⭐ Stating that the remainder is empty, with the measurement behind it, is more useful than silence.


Generated by Claude Code

@os-bill
os-bill added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 613d35a Sep 10, 2026
36 checks passed
@os-bill
os-bill deleted the claude/issue-16962-example-caption-fence branch September 10, 2026 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants