docs(gates): record the package-README census and defer check:doc-types' fourth leg (objectui#7896) - #8111
Conversation
…es' fourth leg (objectui#7896) `check:doc-types` does not walk `packages/NAME/README.md` while its two sibling doc gates both do, so a package README's `type` literals are read twice and judged never. The `domain:ui` ruling on objectui#7896 orders the move census-first: the widening may land in the same pull request only if the census reads zero. It does not. All 39 files walked with this gate's own extractor and registry read 26 unregistered `type` literals across 12 files, plus 4 blind spots. So the leg does not land here. This commit records the measurement, the one real defect it found (`packages/plugin-detail/README.md:168` teaches a detail tab whose `content.type` is `activity-timeline`, rendered through `SchemaRenderer` and registered by nothing), the 25 other-vocabulary candidates, and the two couplings the implementing change will owe — the leg's slot in `scanDocs` and the expression-carriage census's surface-equality pin. Documentation only: no walk, no counter and no verdict moves. The header's ⛔ against treating this gate's widenings as a precedent stays verbatim. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
|
Standing down on
Every other check on Generated by Claude Code |
|
Armed — squash auto-merge enabled and the PR is in the merge queue,
On MERGED: content probe on re-fetched Generated by Claude Code |
|
LANDED — merged at 2026-09-06T17:19:18Z as Content probe on re-fetched
#7896 stays open ( Generated by Claude Code |
Refs #7896
Census-only. The widening does NOT land here, because the census does not read zero.
The
domain:uiruling on objectui#7896 (comment5556586208) orders this move census-first: walk all 39packages/NAME/README.mdwithcheck:doc-types' own extractor and own registry, report a blind-spot count, and land the widening in the same pull request only if the census reads zero. It reads 26. So this pull request carries the measurement and a proposal per hit, and the fourth leg is left for the follow-up card the PM opens.STEP 0 — the card's two-way mutation, re-taken on
59a3a233dAssumption A1 holds. Today's counters have moved from the card's
af05c88bbreading (889literals,772registered, was887/770); everything else reproduces exactly.check:doc-typesresult"type": "action:button"toaction:buttonZZinpackages/app-shell/README.md0393b247b70563082c817d8d36b4841bf2170571EXIT=0, counters byte-identical to baseline (188 files / 1105 blocks / 889 literals / 772 registered)"type": "object-grid"toobject-gridZZin the rootREADME.md7d01e3072e8e0d7f78a6a48218f28f78c0a3af50EXIT=1,README.md:272 [unregistered-doc-type] type 'object-gridZZ' (json), registered 772 to 771Both mutations proven on disk by counting the injected and the removed text before reading any verdict. Both restores proven by STATE, not by exit code:
HEADblobgit diff HEAD --name-onlypackages/app-shell/README.md1304531be8aec7684dbccebda403077ee2914f3e1304531be8aec7684dbccebda403077ee2914f3eREADME.mdb6c8467fdfe8e5102ceafff83922d9766f6f4f39b6c8467fdfe8e5102ceafff83922d9766f6f4f39The control proves the instrument is alive; the byte-identical subject counters prove the package README is not in the scan population at all, rather than judged and forgiven.
STEP 1 — the census (the deliverable)
Instrument. The gate's own
packageReadmes/scanDocs/deriveRegistryKeys/analyze, exercised through a thin harness that only groups the gate's own site list per file. To makescanDocsreach the files at all, the fourth leg was applied to a working copy, the census taken, and the leg then reverted — the reverted state is proven below. No hand-written regex judged anything.Assumption A2 falsified in the useful direction. The extractor is fence-language-AGNOSTIC:
scanDocsrecognises any```fence, records its info string only as a label, and applies the sametypematcher inside all of them. So atypescript-fenced literal in a package README is a census HIT, not a blind spot — confirmed by the fence-language mix of the 237 literals read:typescript149,tsx44,json21,ts13,jsonc10.Population control. The widened walk reads 227 documents.
check:doc-fences, an independent instrument, reports227 document(s)and itslistDocumentsreturns the same 39 package READMEs. The two walks agree on the population from different code.typeliteralsBlind spots — 4
A zero without a blind-spot reading is not a measured zero, so the blind spots were measured by a deliberately DIFFERENT instrument (the gate's own regexes cannot report what they cannot see). Four categories were counted over the same 39 files:
~~~fences)type:keys inside a gate-recognised fence whose value is not a same-line quoted string literaltype:keys in 4-space indented (unfenced) code blocksAll four are the same site,
packages/data-objectstack/README.md:603-606, inside a```yamlfence:Unquoted YAML scalars naming
@objectstack/specobject FIELD types, not SDUI component keys. Explained with evidence, and not a candidate for the exemption table either — the extractor never produces a site for them, so there is nothing to exempt. They do not change the STEP 2 decision, which the 26 hits settle on their own.STEP 2 — the decision: the widening does NOT land
Unregistered = 26, not 0. The ruling's branch is unambiguous, and the cost is concrete: landing the leg today turns
mainred on 26 sites this card is explicitly not authorised to touch (⛔ no package README content edits). Per the ruling, ⛔ nothing was added toDOC_TYPE_EXEMPTIONS— that table records rulings, not a switch for turning a first run green, and stuffing 25 entries into it would bury the one real defect among them.What lands instead: a documentation-only note in
scripts/check-doc-component-types.mjsrecording the measurement, the ⛔ ruling, the one real defect, the 25 candidates, and the two couplings the implementing change will owe. No walk, no counter, no verdict moves —pnpm check:doc-typesprints the same line before and after (188 / 1105 / 889 / 772 / 117).The 26 hits, one proposal each
1 real defect — a card of its own, ⛔ not repaired here.
packages/plugin-detail/README.md:168activity-timelinecontent.type.DetailTabs.tsx:72renders tab content throughSchemaRenderer schema={toRenderableSchema(tab.content)}, so this IS an SDUI node position. Nothing registersactivity-timeline—plugin-detailregistersactivity(namespacedrecord:activity). A reader copying the example gets the registry'sOBJUI-001"Unknown component type" panel. Same shape as theline-chartinstance objectui#7896 recorded inpackages/plugin-dashboard/README.md, corrected on the docs side by PR #7951.25 candidate
DOC_TYPE_EXEMPTIONSentries — each a genuine non-component vocabulary with a declaration site in source, each owed a written reason naming that vocabulary:packages/app-shell/README.md:392,393,395,396start,decision,action,endapp-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.tsxpackages/mobile/README.md:138,142,167swipe-left,swipe-right,pinchpackages/mobile/src/useGesture.ts,useSpecGesture.tspackages/plugin-dashboard/README.md:226,235,341,344line,pie,barDashboardRenderer/DashboardGridLayout(dispatch.family === 'series'), not byComponentRegistryplugin-dashboard/src/DashboardRenderer.tsx:117,WidgetConfigPanel.tsx:91packages/plugin-gantt/README.md:608milestoneplugin-gantt/src/GanttView.tsxpackages/plugin-gantt/README.md:713,714,715,716fs,ss,ff,sfplugin-gantt/src/GanttView.tsxpackages/plugin-grid/README.md:177,380,551multipleselection.type)plugin-grid/src/ObjectGrid.tsxpackages/plugin-grid/README.md:253count_uniquesummary.type)plugin-grid/src/useColumnSummaryfamilypackages/plugin-report/README.md:168,189,247matrix,joined,bar'matrix'as one of the seventypevocabularies)plugin-report/src/ReportRenderer.tsx,DatasetReportRenderer.tsxpackages/plugin-view/README.md:171sharepackages/types/src/views.tspackages/plugin-view/README.md:298date-rangeplugin-view/src/FilterUI.tsx,packages/types/src/views.tsNote the mixed-vocabulary hazard these make concrete:
plugin-dashboard's sibling widget kindsmetric-cardandmetricpass today only because some package in the generous union registers those names — the same block holds passing and failing members of one vocabulary, which is exactly why the exemption table is keyed by (file, value) and not by file.Ruling 3 — the header's ⛔ is untouched
scripts/check-doc-component-types.mjs:244still reads, byte-identical toHEAD:The sentence the ruling asks for sits beside it in the new note, in the deferred form this pull request is in: that ⛔ refuses widening onto an ARBITRARY unscanned tree, and
packages/NAME/README.mdis not one — it is the surface this gate's own two siblings already walk, so the move aligns the third gate to its family rather than reaching into a new tree. Cited to objectui#7896. The ⛔ stays, and this card is not licence for the next widening.Ruling 5 — the surface-equality pin
Neither (a) nor (b) is needed in this pull request, because no leg lands.
scripts/check-doc-expression-carriage.mjsimportsAPP_DOCS/appDocsDirs/ROOT_PAGESfrom this gate; none of those constants moved, so the pin's rebuilt walk and the carriage census's walk are still the same three legs. Both were run anyway and are green: the pin suite passes, andnode scripts/check-doc-expression-carriage.mjsexits 0.The divergence hazard is real but is the follow-up card's to carry, so it is written into the gate's note rather than left to be rediscovered: that pin compares against the CONSTANTS, not against
check:doc-types' actual walk, so a fourth leg added to this gate alone would leave the pin GREEN while the two surfaces silently diverge — objectui#7115's shape again. The recorded default is (a): the carriage census imports the new leg constant and walks it too, the pin's rebuilt walk gains the same leg, andSURFACE_LABELplus its enumerating test gain the leg by name. The note also records the leg's required SLOT inscanDocs(before the root pages, which is where both sibling walks append it) — that slot is what keeps the three lists comparable element by element, and the census run confirmed it produces the same 227-document populationcheck:doc-fencesreports.Positive control
Ruling 4 scopes the injected-literal positive control to "only when the widening lands", and it does not land. The equivalent evidence for the census instrument is above and is stronger than an injection, because it is not synthetic: the widened walk named 26 real sites with file and line across 12 of the 39 files, and the control leg of STEP 0 shows the same gate producing
EXIT=1with anunregistered-doc-typediagnostic on a file that IS in its walk. The instrument is alive and it reaches the files.Revert of the census-time leg, proven by STATE:
HEADblobgit diff HEAD --name-onlyscripts/check-doc-component-types.mjs(before the note)ddba2f99b570066c4515901fa37951ea2694ca7dddba2f99b570066c4515901fa37951ea2694ca7dGates — all pinned to
1d05e86a1pnpm check:doc-types188 doc file(s) … 1105 code block(s), 889 type literal(s) … 772 registered, 117 exempted— identical to the pre-change baselinepnpm exec vitest run scripts/__tests__/check-doc-component-types.test.ts check-doc-expression-carriage.test.ts check-doc-fence-languages.test.tsTest Files 3 passed (3) / Tests 121 passed (121)node scripts/check-doc-expression-carriage.mjspnpm check:doc-fencesevery TypeScript block in 227 document(s) …(sibling control, unmoved)pnpm exec vitest run scripts/__tests__/Test Files 112 passed (112) / Tests 3360 passed (3360)pnpm type-check:scriptspnpm lint:root32 problems (0 errors, 32 warnings)— warnings pre-existing, none in the changed filepnpm check:control-bytesscanned 6495 tracked text file(s); skipped 85 binarygrep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'on the changed pathnode scripts/check-changeset-presence.mjs1 file(s) changed, 0 of them published source … no changeset is owednode scripts/check-governed-queue-guard.mjs --test scripts/check-doc-component-types.mjsNOT GOVERNED — 1 path(s) checked against 5 governed surface(s)pnpm check:entry-guard70 scripts/ file(s) — no entry guard outside the baselineOne declared narrowing.
pnpm check:doc-snippetsexits 2 withunbuilt-packagefor 31 packages — a PREREQUISITE NOT MET, not a red gate: it needs a scopeddist/build this diff has no bearing on. It is left to CI. The reason the narrowing is safe here: that gate readsscripts/check-doc-snippet-types.mjs, which this pull request does not touch, and the only changed file is a block comment.Files changed
scripts/check-doc-component-types.mjs— 61 added lines, all inside one block comment. Nothing else.🤖 Generated with Claude Code
https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Generated by Claude Code