Repository navigation
feat(scripts): gate documented SchemaExample ids against the catalog registry - #9009
Merged
Merged
Conversation
…atalog registry `SchemaExample` resolves its `id` prop through `getExample(id)`, which throws on an unknown id rather than degrading. That is the right runtime behaviour — a silently wrong or empty example teaches the wrong thing — but it means a mistyped or stale id is a throw at page render in the published docs, and nothing asked the question before then. Adds `check:doc-example-ids`: it walks every `.mdx`/`.md` page under `content/docs/**`, reads each `<SchemaExample>` tag's id, and resolves it against the ids the generated schema-catalog index carries. Illustrative references are recognised by SHAPE — a value carrying angle-bracket metasyntax or an elision cannot be a filename-derived id — rather than by a list of the two literals the tree happens to contain today, and every exemption is printed on every run. The rule's premise is re-derived from the live registry each run: if a real id ever carries a marker, the gate stops instead of exempting it. The population is `content/docs/**` only. The same reference shape appears in the examples READMEs, the generated index, the catalog smoke test, a script test, the extractor CLI, the Python regenerator and the site's own components; all of those are illustrative prose or the machinery itself, and a gate that failed on a README's teaching id would be its own defect. Gets its own workflow rather than a leg on `ci.yml`/`lint.yml` for the reason `doc-component-types.yml` records: the change that introduces this defect is docs-only, which is exactly the shape those two workflows skip. The population is clean today, so the test carries a firing control — the real script, spawned over a throwaway tree with a deliberately unknown id, asserted to exit 1 — plus controls that an empty walk, an empty reference set and a half-read registry each report "could not run" rather than a clean zero. `getExample` is unchanged, and no fallback entry was added to the registry. Co-authored-by: Claude <noreply@anthropic.com>
Two existing pins require every workflow to be both documented and bucketed,
and both went red on the new one:
- `ci-cd-pipeline-doc.test.ts` requires a section naming the workflow file,
plus a row in the inventory table — a workflow nobody documented is a check
contributors get blocked by without knowing it exists.
- `dependabot-merge-gate.test.ts` requires every check a pull request
produces to be classified exactly once. `Doc Example Id Check` subscribes
`pull_request` with no trigger-level path filter, so every pull request
produces it, which puts it in the required set beside the three
neighbouring doc gates.
The docs edit is to the CI guide, not to a page this gate reads for ids.
Co-authored-by: Claude <noreply@anthropic.com>
`check:entry-guard` refused the hand-typed guard, and it is right to: node leaves `process.argv[1]` as the caller typed it, so a script reached through a symlink compares two different paths, answers false and does nothing — exit 0, no output, which a CI wrapper reading only the status cannot tell from a pass. Routes it through the one shared predicate instead. The baseline is untouched: `KNOWN_HAND_TYPED_GUARDS` is shrink-only and adding a line to it is not a supported way to make this pass. Behaviour is unchanged when the script is invoked normally, and now correct through a symlink: both spellings print the same funnel and exit 0. Co-authored-by: Claude <noreply@anthropic.com>
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 objectui#8623
What this adds
check:doc-example-ids— a static check that everySchemaExamplereference incontent/docs/**names an id the schema catalog registry actually carries, plus itstest and its own workflow.
SchemaExampleresolves itsidprop throughgetExample, and that lookup throwson an unknown id rather than degrading. Per the triage ruling that behaviour is
unchanged here — no fallback in
getExample, and no registry entry added so areference resolves. What was missing is that nothing asked the question before render,
so a mistyped or stale id passed every check in the repository and crashed the page it
was on.
The population — re-measured on this branch point, not inherited
origin/mainhad moved from the sha named in the dispatch (e8b7b078->9b83f479), sothe reading was retaken:
content/docs/**(.mdx+.md)SchemaExamplereferences foundControl: the intersection is non-empty (414 of 416), so the misses are a reading and
not a broken matcher. The registry itself is read twice from the generated index — the
REGISTRYobject's own keys, which is whatgetExampleindexes, and the schema importspecifiers above them — and the two must agree exactly or the gate reports could not
run.
Both exemptions are the syntax placeholders in the schema-catalog authoring guide, exactly
as the dispatch measured: the angle-bracket template in the snippet teaching the tag, and
the ellipsis in the prose sentence describing it. There is not one broken real id in the
docs today.
The exemption is a shape rule, not a list of two literals
A value carrying angle-bracket metasyntax, or an elision (Unicode or ASCII), cannot be a
filename-derived id — so it is illustrative. That rule is argued in the script's header
and it is never silent: every run prints each exemption with its page, its value and the
marker that matched. Two things it deliberately does not do:
typo — all still judged. A pinned test fixtures a well-formed typo and requires it red.
assertMarkersAreImpossiblere-derives from the liveregistry on every run that no real id carries a marker, and exits could not run if that
ever stops holding, rather than letting the exemption swallow a real reference.
Green on landing proves nothing — so here is the gate going red
Firing control, on the real tree. One real id in a layout docs page was mutated to a
typo, the gate was run, and the page was restored. Restoration is proved by hash rather
than by an exit code — pre-mutation blob
36867f58, post-mutationf216a5ca,post-restore
36867f58, andgit diff HEADempty:(The two exempted values are shown with the angle-bracket one written out in words; the
run prints the literal.)
Firing control, in the test.
scripts/__tests__/check-doc-example-ids.test.tsspawnsthe real script as a process over throwaway trees — not a unit test of a helper —
and asserts exit
1for an unknown id, exit1for a tag with no id at all, and exit0once the same fixture's id is corrected, so the red was the id and not the fixture.
Non-empty-population controls. A walk that reads zero pages, a matcher that finds zero
references, and a registry that reads as zero ids each exit
2(could not run), becauseall three otherwise produce the same clean verdict as a clean tree. Each has its own pin.
Verdict on the restored tree, at final HEAD
61dd16b56:Why a fourth
check-doc-*script and not a leg on one of the threeEach neighbour's header was read before this one was named, and none can be extended
without blurring a question it declares it answers alone:
@examplecommentson exported declarations under
packages/*/src/**, against the built.d.ts— adifferent population, a different instrument, a different universe;
a published shared reader owns, and its header rules
content/docs/**out explicitly;derives its universe from source every run, which is why this script borrows both habits
— but its subject is a
typestring literal inside a fenced code block judged against thecomponent registry, and its header states it answers one question only. An example id is a
JSX attribute in the page's prose judged against the example catalog; folding two
vocabularies into one gate makes its verdict line ambiguous for no shared work.
The script name and the
check:doc-example-idsalias collide with none of the three, and apin asserts all four still point at their own files.
Wiring: its own workflow, not a
lint.ymllegThe dispatch asked for a workflow leg. It got its own workflow, for the reason the
doc-component-types workflow header records for the neighbouring gate over these same pages:
the change that introduces this defect is docs-only, and that is precisely the shape
ci.ymlandlint.ymlshort-circuit —ci.yml'stype-checkjob decides with agit diffthat excludes
content/**. Wired intolint.yml, this gate would be blind to every changethat can introduce the defect. It needs no install and no build, so it can afford to run
unfiltered. This also leaves the
lint.ymlseam with objectui#8554 untouched entirely; theonly shared file is the
package.jsonscripts block, one added line.Two files beyond the declared face, both forced by existing pins
Running the whole
scripts/__tests__suite turned two pins red on the new workflow, andboth are wiring the repository already requires:
a workflow nobody documented is a check contributors get blocked by without knowing it
exists;
The new check subscribes
pull_requestwith no trigger-level path filter, so it goes inthe required set beside the three neighbouring doc gates.
not edited to make any check pass — it was edited because an existing pin requires it. No
content/docspage'sSchemaExampleid was touched.Verification
Run under the shared verification lock where it holds cores; gate scripts read the checkout
only.
pnpm exec vitest run scripts/__tests__tsc -p tsconfig.scripts.json--listFilesconfirms both new files are in that program, so it is a measurement rather than an exclusioneslint .repo-wide,--format json--testover this diffcheck-doc-snippet-typesis not owed: the docs edit adds zero fenced blocks (git diff^+fence count is 0), so no snippet population moved. Everything else is CI's.Boundaries honoured
getExample's throwing behaviour is untouched; no fallback, no registry entry added tomake a reference resolve.
content/docs/**.examples/, the READMEs, the smoke test, thescript test, the extractor CLI, the Python regenerator and the site's own components are
all out, and the script's header says why.
content/docs/releases/untouched.scripts/check-eager-closure-budget.mjsuntouched (objectui#8554's file).Premise 2 is false as stated. It said the reference shape outside
content/docs"stilllives in exactly the seven files the card listed". Measured tree-wide on this branch point it
lives in nine: the seven, plus the site's
SchemaExamplecomponent and itsSchemaCatalogIndexsibling. The card's original grep was scoped toscripts/ examples/ packages/, which never containedapps/, so the seven-file reading was correct for thatscope and the dispatch generalised it one step too far. Control: the same command shape
over the same population returns 179 files for a term known to be everywhere, so the reader
was firing. This changes nothing about the ruling — both extra files are the machinery that
consumes ids rather than pages that reference them, and both are outside the population
triage scoped — but the dispatch's count is wrong and is recorded as wrong here.
Premises 1, 3 and 4 all held. The dispatch's population table (415 distinct / 432 registry /
413 resolving / 2 missing, both placeholders in the authoring guide) reproduced exactly on
the newer tip.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Generated by Claude Code