Skip to content

feat(spec): opt-in os:check-yaml gate validates tagged docs YAML against live spec schemas - #13267

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-13086-yaml-fence-checker
Aug 30, 2026
Merged

feat(spec): opt-in os:check-yaml gate validates tagged docs YAML against live spec schemas#13267
os-project-manager merged 3 commits into
mainfrom
claude/issue-13086-yaml-fence-checker

Conversation

@claude

@claude claude Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Fixes #13086

Implements the 2026-08-29 ruling on that card: option (ii) — an opt-in tagged-YAML checker mirroring the existing os:check mechanism. Corpus-wide tagging stays a follow-up (option (i), per the ruling); moving examples to ts fences (option (iii)) was explicitly not adopted.

What this adds

check:yaml-examples (packages/spec/scripts/check-yaml-examples.ts): every fenced YAML block in skills/ (md) and content/docs/ (mdx, minus generated references/) that carries an os:check-yaml SCHEMA marker on the line directly above its bare yaml fence is parsed and safeParsed against the live spec schema it declares.

  • Resolution: a lowercase declaration (object, view, page, ...) resolves through getMetadataTypeSchema() — the same registry the runtime metadata write door validates with — so the docs' example parsing and the runtime accepting it are one fact. A PascalCase declaration (FormSectionSchema, ...) resolves against the same 15 category namespaces build-schemas.ts walks, because most doc examples are fragments that instantiate a named sub-schema. Ambiguous names are refused with qualified spellings listed.
  • Fragment grammar: a SCHEMA name may take a [] suffix (array of instances, validated element-wise) and a key=WRAPPER token (the single-key pedagogical wrapper many pages use, e.g. sections:). Any other key beside the wrapper is a loud failure.
  • Verbatim rejections: a failure prints the fence's file:line plus every Zod issue path and message untouched — the strict-object rename hints (e.g. visible on a form section naming visibleWhen, ADR-0089) reach the author exactly as a runtime save would print them (ruling point 1).
  • Coverage census (ruling point 3): every run prints the tagged/untagged fence counts and the top untagged files (--coverage for the full table), so opt-in adoption is visible and proddable rather than a permanent excuse. Current reading: 6 tagged / 147 untagged across 17 files.
  • Loud on empty (dispatch non-negotiable): zero YAML fences found, or zero tagged blocks, is a hard red — never a silent no-op. Orphaned markers (wrong spelling for the file type, blank line between marker and fence, fence-meta on the fence, non-yaml fence below) are hard reds too, mirroring the sibling gate's orphan scan.
  • Marker spellings follow the existing os:check convention exactly: HTML-comment form in .md, MDX curly-brace expression-comment form in .mdx (the page being tagged here already carries two os:check markers of that form, so the MDX-safety precedent is on the same page).

Gate obligations in full (ruling point 4): --self-test with 32 fixture-driven cases wired ahead of the production run in the package script (so check:self-test-wired covers it by construction, verified green); classified in check:generated's NO_GENERATOR ledger (--reconcile-only green: 28 check + 17 gen scripts all classified); typechecked by check:scripts-typecheck (file presence in the program verified via listFiles); wired as a step in lint.yml's typecheck-consumers job beside the sibling example gates (adds no required context — a step in an existing job).

Tagged page conversion (ruling point 2): content/docs/protocol/objectui/layout-dsl.mdx — the page that shipped the same phantom-key class through three rounds (8251 / 8306 / 12935) — now carries the first six os:check-yaml markers: two section: wrappers, two sections: lists, the tabbed FormViewSchema example, and the responsiveStyles fragment. All six validate against today's schemas with zero content edits. The 17 fences that remain untagged mostly teach shapes the live schemas refuse (whole undeclared surfaces, not typos — a removed-or-implemented decision per section); the per-fence readings are recorded as #13266, which remains open for docs-content triage rather than being cleaned up inside this mechanism card.

yaml@2.9.0 was added to packages/spec devDependencies — the exact version the workspace root already carries (no new third-party dependency; it makes the script's resolution explicit instead of leaning on the root hoist).

Verification (all at head 4cf14eed, clean tree)

  • check:yaml-examples --self-test: all 32 cases hold (extraction, fence-awareness of nested illustrations, all four orphan channels, resolution refusals including ambiguity, wrapper/array refusals, YAML parse errors with mapped lines, empty-claim refusals, and a pin that the live schema's rejection message reaches the output verbatim — the expectation is read from the live schema at test time, not fossilised).
  • Production run green: 6 tagged blocks validate; census printed.
  • Ablation (measured, both legs confirmed on disk): with the fix committed, injecting the historical phantom visible: breakpoint map into a tagged section block (mutation confirmed by anchored grep count 1) turned the gate red naming layout-dsl.mdx:228 and printing the ADR-0089 rename hint verbatim; restore via git checkout HEAD verified by grep count 0, empty git diff HEAD, and disk blob hash equal to the HEAD blob (58f7edec). No dist is in this gate's read path (tsx over src + docs), so no rebuild leg applies.
  • Reverse-verification of the vacuous-green guard: before the page was tagged, the production run exited 1 with the zero-tagged refusal (measured, not assumed).
  • Spec suite: vitest run — 443 files, 11776 tests, all passing (covers the check-generated.ts ledger edit's own pin suites).
  • Derived gate battery (dispatch-gates.mjs from the merge base, re-derived at 4cf14eed — list unchanged): all matched families green locally, including check:docs, check:skill-examples (260 blocks across 3 surfaces — this PR's page edit is in its population), check:generated full aggregate, both @objectstack/lint doc gates, the workflow-consistency family over the lint.yml edit, check:pm-dispatch-gates (879 cases) and bare-root-worklist --self-test, and check:nul-bytes.
  • ESLint, narrowed with evidence: this repo runs one flat config with type-aware linting never enabled for any file (stated in eslint.config.mjs around line 328, with its own measured positive control), so per-file verdicts are independent of untouched files; eslint --no-inline-config --format json over both touched scripts: 2 files, 0 errors, 0 warnings. The .mdx/YAML/JSON paths in this diff are outside eslint's population and are covered by the docs gates above.
  • Declared narrowings (NOT MEASURED locally, by each gate's own refusal; CI runs them after its full workspace build): check:type-check-debt re-measure leg (refuses on 55 unbuilt closure packages; @objectstack/spec appears in neither DEBT nor TEST_DEBT ledger, and no ledgered package's sources are touched, so the measured numbers cannot move — the self-test and coverage half ran green), check-dev-prereqs (unbuilt-workspace precondition; this diff adds no package entry points), check:dual-build-cjs-loads full leg (explicit PREREQUISITE NOT MET exit 3; its 53-case self-test ran green; this diff changes no bundle or exports condition). check-test-completeness is a CI-attestation consumer and has no local run.

Changeset

None, and skip-changeset is applied deliberately: nothing published changes — packages/spec's files list ships neither scripts/ nor devDependencies, and the docs tree is not a versioned package. Same shape as the merged precedents 13202 (spec gate script) and 13199 (docs + spec), both skip-changeset.

Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N

Generated by Claude Code


Generated by Claude Code

@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 29, 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 74049254d47bd0edd2a2fcd732dcc01c91504f10packageMentionDocs.

@github-actions github-actions Bot added size/l ci/cd dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tooling labels Aug 29, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 29, 2026 20:25
@os-project-manager
os-project-manager added this pull request to the merge queue Aug 29, 2026
Merged via the queue into main with commit 2ebfe7e Aug 30, 2026
39 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-13086-yaml-fence-checker branch August 30, 2026 02:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants