Skip to content

feat(spec): the example type-check gate now covers content/docs, not just skills/ - #3882

Merged
os-zhuang merged 2 commits into
mainfrom
claude/action-undoable-experimental-j8g24i
Jul 28, 2026
Merged

feat(spec): the example type-check gate now covers content/docs, not just skills/#3882
os-zhuang merged 2 commits into
mainfrom
claude/action-undoable-experimental-j8g24i

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Follow-up to #3876, which is where this gap showed itself.

The gap

check:skill-examples compiles the TypeScript in prose against the built spec, so an example that stops compiling fails CI instead of quietly teaching code that no longer works. It does its job — it caught the broken defineTool example in #3876 when tool.requiresConfirmation was removed.

But it only ever walked skills/:

Tree files with ts blocks compiled
skills/ 9 9
content/docs/ (hand-written) 124 0

The identical break in a docs page ships. Docs examples are copied verbatim by humans and AI exactly like skill examples — same shape as the stale-evidence and orphan-proof warnings fixed in #3857 / #3868: a gate covering a fraction of the surface it appears to cover reads as coverage.

The change

The walker is now a SOURCE_ROOTS list, and this lands the first batch: 164 docs blocks across 63 pages, taking the gate from 32 → 196 checked examples. content/docs/references/ is excluded — build-docs.ts regenerates it from the schemas, so it cannot drift independently.

The marker is now per-format — and this is the part I nearly got wrong

MDX has no HTML comments. <!-- os:check --> in a .mdx doesn't degrade, it fails the fumadocs build outright:

Unexpected character `!` (U+0021) before name … (note: to create a comment in MDX, use `{/* text */}`)

I found this by building the docs site — the first attempt broke 60+ pages. Nothing in the type-check gate would have told me, because the block extraction is regex-level and never parses MDX.

  • skills/**/*.md<!-- os:check -->
  • content/docs/**/*.mdx{/* os:check */}

Both spellings are recognised for orphan detection, so a wrong-format marker fails loudly rather than silently checking nothing. That extends the guard's existing philosophy to the failure mode this change introduces.

The batch was measured, not guessed

Marking all 780 docs blocks and compiling showed which are self-contained. One subtlety worth recording: a block that "passes" inside a 780-file program can be leaning on globals declared by other blocks — a file with no import/export is a global script, so TaskViews declared in one page's example resolves in another's. Selecting on that first pass gave 564 "passing" blocks, of which only 164 survived once compiled as the real, smaller set. The set was converged by recompiling and dropping newly-failing blocks until green.

Verification

  • pnpm --filter @objectstack/spec check:skill-examples✅ 196 prose examples type-check (32 skills + 164 docs), re-run against merged main after feat(spec)!: remove tool.requiresConfirmation — a safety flag nothing enforced (#3715, ADR-0033 §2) #3876.
  • pnpm --filter @objectstack/docs build — succeeds. This is the check that caught the MDX comment problem; it is the reason the marker is per-format.
  • pnpm check:doc-authoring — 213 files clean. The markers sit on their own line and leave the fence info-string bare, so the sibling scanner that keys on ^```(ts|typescript|tsx)$ still sees every block (the original script's docstring flags this as a hole to avoid; still avoided).

Follow-up

The remaining ~600 blocks are mostly fragments (a columns: [...] subtree), which the gate's opt-in design already anticipates. Whether any are genuine rot rather than fragments is now answerable for the first time, and worth a pass — that is exactly the class of thing this machinery exists to surface.


Generated by Claude Code

claude added 2 commits July 28, 2026 14:07
…t/docs

check:skill-examples compiles the TypeScript in `skills/` against the built spec.
It works — it caught the broken `defineTool` example when
tool.requiresConfirmation was removed (#3715). But it only ever walked `skills/`:

    skills/          9 files with ts blocks,   9 compiled
    content/docs/  124 files with ts blocks,   0 compiled

So the identical break in a docs page ships. Docs examples are copied verbatim by
humans and AI exactly like skill examples; a gate covering a fraction of the
surface it appears to cover reads as coverage.

Generalises the walker to a SOURCE_ROOTS list and opts in the first batch:
164 docs blocks across 63 pages, taking the gate from 32 to 196 checked examples.
content/docs/references/ is excluded — build-docs.ts regenerates it from the
schemas, so it cannot drift independently.

MARKER IS NOW PER-FORMAT. MDX has no HTML comments: `<!-- os:check -->` in a .mdx
fails the fumadocs build outright ("Unexpected character `!`… to create a comment
in MDX, use `{/* text */}`"). Verified by building the docs site — the first
attempt failed on 60+ pages. skills/*.md keeps `<!-- os:check -->`; content/docs
uses `{/* os:check */}`. Both spellings are recognised for ORPHAN detection, so a
wrong-format marker fails loudly instead of silently checking nothing.

Batch selection was measured, not guessed: marking all 780 docs blocks and
compiling showed which are self-contained. Note a subtlety — blocks that "pass"
in a 780-file program can be leaning on globals declared by OTHER blocks (a file
with no import/export is a global script), so the set was converged by
recompiling and dropping newly-failing blocks until green. 164 stand alone.

The remaining ~600 are mostly fragments (a `columns: [...]` subtree), which the
gate's opt-in design already anticipates; whether any are genuine rot is worth a
follow-up pass now that the machinery reaches them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ajwvrmd1hDC9RBofYBhGuR
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 28, 2026 2:09pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling size/m labels Jul 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 28, 2026 14:26
@os-zhuang
os-zhuang merged commit 0fc6219 into main Jul 28, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/action-undoable-experimental-j8g24i branch July 28, 2026 14:26
os-zhuang added a commit that referenced this pull request Jul 28, 2026
…a call that throws (#3890)

#3882 brought content/docs under the example type-check gate and left the open
question: of the ~600 blocks still unmarked, is any GENUINE ROT rather than a
fragment? Swept them. One real one.

deployment/troubleshooting.mdx answered "How do I extend a built-in schema?" —
a direct FAQ, not a sketch — with `FieldSchema.extend({ … })`. FieldSchema carries
a .transform() that lowers author-facing sugar at parse time, so the exported
value is a ZodPipe, not a ZodObject: `.extend` is undefined and the call throws.
The example also used `z` without importing it.

Rewritten to COMPOSE — parse with FieldSchema, validate the additions alongside
it — verified to both type-check and run, and marked so the gate holds it.
Composition also keeps the transform, which .extend() would discard.

A divergence found while fixing it: the first attempt used FieldSchema.in.extend()
and failed in CI with "Property 'in' does not exist on type 'ZodObject<…>'". The
two builds emit DIFFERENT declarations for the same source — locally
ZodPipe<ZodObject<…>>, in CI a plain ZodObject — while the runtime is unambiguous
(bound ZodPipe, .extend undefined, confirmed after rm -rf dist && pnpm build). So
CI's declaration contradicts the value it describes. Likely inference instability
in the DTS bundling of these very large zod types; worth its own investigation.
The final example uses only .parse(), so it is correct under either declaration.

The sweep's METHOD is recorded in the gate docstring, because two traps make a
naive pass report a confident "nothing found":

  1. tsc stops after syntactic diagnostics and never runs the semantic pass.
     Marking all 780 blocks at once let ~200 broken fragments suppress
     type-checking for every other block; that run returned only TS1xxx codes,
     which reads exactly like "no rot" and proves nothing. Caught by injecting a
     deliberate type error and watching it go unreported.
  2. Unimported type names resolve against the DOM lib — Plugin, Event, Response,
     Storage all exist there. A block missing its import reports "'version' does
     not exist in type 'Plugin'" against lib.dom's Plugin: an artefact, not drift.
     Three of the strongest-looking candidates were exactly this.

Everything else is fragments, plus config-resolution.mdx, whose aspirational
snippets a callout on the page already labels as design intent.

Gate: 196 -> 197 checked examples.
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 tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants