Repository navigation
feat(cli): objectui validate and objectui check judge through the strict authoring face (objectui#5250, slice A) - #11069
Conversation
… strict authoring face (objectui#5250) Both commands called `safeValidateSchema` — the tolerant `AnyComponentSchema`, whose `BaseSchemaCore.passthrough()` kept any undeclared key unjudged. They now share one door, `validateAuthoredDocument` in `utils/authoring-face.ts`, which parses through `StrictAnyComponentSchema` (objectui#8345), per the objectui#5250 ruling (option 2): the authoring verdict is strict, the rendering face keeps its passthrough. A refused undeclared key is named with its path and a prescription by `findUndeclaredKeys` (union-arm-diagnostics), which reads keys out of viable union arms, so a nested child's refusal is named even though the issue list carries it only inside an `Invalid input` at the child slot. Part of objectui#5250 (slice A). Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh Co-authored-by: Claude <noreply@anthropic.com>
…jectui check` (objectui#5250) The card's probe (`validation` on an `input`) is refused at the root with its key, path and prescription; a nested child's undeclared key is named at the child's path; a key one viable widget arm declares is not named; declared-only documents stay green. `check` lists a leaf with an undeclared key by name and names the key under it. The three CLI pins that stood in for "the parse `check` / `validate` run" now read `validateAuthoredDocument`, the door both commands call. Part of objectui#5250 (slice A). Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh Co-authored-by: Claude <noreply@anthropic.com>
…or the strict wiring (objectui#5250) The CLI README and utilities/cli.mdx say what the strict authoring face does for `validate` and `check`. Four guide/component pages said nothing refuses an undeclared key, or that `objectui validate` runs `safeValidateSchema`; both went false with the wiring, so they now name the render path and the authoring verdict apart. Changeset: `@object-ui/cli` minor, breaking semantics stated in the body (the fixed group never declares major). Part of objectui#5250 (slice A). Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs read: card objectui#5250 (body and all 15 comments, the ruling ① Derived judgments
② Semver level
③ Boundary flagsDeviations (7, from the os-dev-report):
open_questions (1) — who repairs the 40 class (i) documents, and when. ESCALATED to the dispatching seat on the card; this record does not settle ownership. The reviewer's reading: option A is the one the ruling's sequencing already implies (the fence gate is built on top of the face and surfaces the docs half; the catalog half belongs to the declaration owners, with runtime proof before any deletion). Two documents deserve to be named in the slice B pricing rather than left in the advisory list: out_of_scope_findings (5):
Not flagged by the dev, found here:
Check-runs on this head (read 2026-09-29T08:56Z, 43 runs): 40 Implemented-by: VERDICT: PASS Generated by Claude Code |
…bjectui#5250) Un-park step 1 of objectui#5250. Two textual conflicts, resolved as the measurement round priced them: - packages/cli/src/commands/check.ts imports: both sides kept (the strict door's findUndeclaredKeys and objectui#4795's unbindable-text-expression readers). - check-validity-recogniser.test.ts precondition: validateAuthoredDocument, the strict door, over main's plugin-dashboard:metric-card fixture. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
… on files the strict face refuses (objectui#5250)
Un-park step 1 of objectui#5250, after merging main:
- check: the objectui#4795 judgement of a `${…}` on a closed text key now
runs on the registered-type files the strict authoring face refuses, as
well as on recognised files. On the merged tree a document carrying one
undeclared key was listed and then skipped, so its expression refusal and
the exit code it sets disappeared. Both findings are now reported.
- New pin: a file with an undeclared key and a refused expression is listed
with its key named, refused for the expression, and fails the run.
- The objectui#4795 fixture's test name no longer says the validity arm
admits it (the strict face refuses its `title`).
- registered-types-validate-ratchet-10859: main's report-wrapper case now
reads validateAuthoredDocument, the door the PR removed safeValidateSchema
from.
- validate-strict-authoring-face-5250: the nested probe's issue shape is
re-pinned: the strict face reports `unrecognized_keys` at `children → 0`,
and findUndeclaredKeys names it there.
Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU
Co-authored-by: Claude <noreply@anthropic.com>
…tui#5250) - A nested child's undeclared key is no longer reported only as an `Invalid input` at the child slot: a slot only one union arm fits reports `unrecognized_keys` at the child. The `Invalid input` that names no key is now the shape of a union the node fits more than one arm of, such as a dashboard widget. Corrected in the changeset, the findUndeclaredKeys and describeUndeclaredKey docblocks, the validate and check comments, and the CLI page. - `objectui check` does not exit non-zero only on unreadable JSON: since objectui#4795 a refused expression fails the run, now in a listed file too. Corrected in the changeset, the CLI page and the schema-rendering guide. - The changeset states that it supersedes any entry in the release that calls `safeValidateSchema` what `objectui validate` runs. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Un-park build round: the body sections this round supersedesSeat Head 1. The park note (the ⏸ blockquote at the top)
2. "What changed": three statements replaced
3. The M1 tables are replacedRe-measured on the merged head
The pair list equals the reading on 4. The M2 self-check row is replacedThe built CLI at this head runs
The park-time row read 172 · 167 · 94 · 46. 5. The M3 table is replaced
6. M4 probe (b) is supersededThe nested probe no longer reports 7. "Tests, reverse verification, gates" is replaced at head
|
…e objectui#6318 pin at check's door (objectui#5250) Un-park step 2 of objectui#5250, the statements slice A makes false: - strict-authoring-face.ts and zod/index.zod.ts said nothing in this repository consumes the strict face. `objectui validate` and `objectui check` consume it now. - safe-validate-corpus-6318: the pin is about `objectui check`'s bucket, so it reads the door `check` reads, validateAuthoredDocument. Its seven fixtures are strict-clean, so it stays green. Its counter-probes about the union's declarations stay on safeValidateSchema. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract-tier review of record — PR objectui#11069 (objectui#5250, slice A) · PASS on head
|
Part of #5250 — slice A only:
objectui validateandobjectui checkparse through the strict authoring face. objectui#5250 stays open for slice B (the JSON-fence gate), which is priced from the numbers below.Clause-②: no — narrows the accept set of
objectui validate/objectui checkto the already-exportedStrictAnyComponentSchema; adds no key, export or flagWhat changed
packages/cli/src/utils/authoring-face.tsaddsvalidateAuthoredDocument, which runsStrictAnyComponentSchema.safeParse(objectui#8345).validate.tsandcheck.tsboth call it instead ofsafeValidateSchema, so the two commands cannot drift into two verdicts on one document. No flag chooses the face, in either direction.findUndeclaredKeys(inunion-arm-diagnostics.ts, next to the existing arm selection) reads everyunrecognized_keysout of the issue tree, including the ones nested inside union arms. A nested child's refusal is reported only asInvalid inputat the child slot, because the slot isSchemaNode | SchemaNode[]. The reader reads only viable arms: an arm whosetypemismatched, or that failed on shape alone, is skipped. When several arms are viable, a key counts only if every viable arm refuses it.validateprints a block after the numbered issues:checkprints the same line under each file it lists, after that file's first-issue line.checkstays advisory: it still exits non-zero only on unreadable JSON.FormFieldSchema.fieldsits in theUnmirroredDeclaredledger, so strict refuses the 表单字段簇:spec↔runtime 是双层词汇——枢纽补缺、覆盖闸门、边界响亮化(objectstack#4115) #3090 mixed entry (nameplusfield). Before,validateaccepted it with a warning. The same explanation now prints with the refusal.utilities/cli.mdxdescribe the behaviour. Four guide and component pages said, in effect, that nothing refuses an undeclared key, or thatobjectui validaterunssafeValidateSchema. This change made both statements false. The pages areguide/architecture.md,guide/layout.md,components/basic/text.mdxandguide/schema-rendering.md; they now keep the render path and the authoring verdict apart..changeset/5250-strict-validate-wiring.mddeclares@object-ui/climinor, not major, per AGENTS.md's version policy: the fixed group follows the@objectstackmajor, so a breaking change is marked minor and described in the body. Narrowing a published CLI's accept set is user-visible and breaking for documents that carry an undeclared key, and the body says so.packages/types/**. ⛔ Noscripts/check-doc-snippet-types.mjsor JSON-fence gate. ⛔ The rendering face's.passthrough()is untouched. No catalog or doc document was repaired; the reason is under M3.M1 — the strict face re-measured on this base
Base
f6ae5e22d(origin/main, 2026-09-29). zod 4.4.3.@object-ui/typesbuilt first. Instrument:scripts/measure-strict-authoring-face.mjs, whose own report says the corpus is identical to thatmaincommit. No figure is inherited from the card's thread.typetypeHeadline from the script: 588 node documents; 2208 nodes; 168 nodes strict-refused (128 strict-only, 40 already red); 104 component types seen, 76 of them strict-clean. Whole documents: 163 of 588 refused by the script's strict twin, 31 of 588 refused by the face as shipped.
declareRegisteredInputs), so it refusesmetric-cardwidget inputs thatStrictAnyComponentSchemaaccepts. Example:examples/schema-catalog/src/schemas/plugin-dashboard/basic-dashboard.jsonis accepted by the shipped face. The script still charges its widgets with undeclaredvalue/icon/trend/trendValue(15 to 18 occurrences each in its dashboard row). So M1 is also read through the shipped face, on the same corpora and the same commit, via a scratch copy of the script with one extra reading:Script per-component rows with a strict refusal: component · nodes strict-refused · strict-only · red today.
formdashboard⚠ over-reported, see abovebuttoncardresizableobject-gridpagecarouselobject-formselectgrid,data-table,object-kanban,list-viewempty,combobox,input-otp,navigation-menu,paginationlistlabel,input,object-view,app,page:tabsicon,tabs,detailM2 — where this repository itself runs
validate/check, and what strict does thereThe PM's clue ("no CI step runs bare
pnpm check") is falsified..github/workflows/lint.ymlhas the step "Verify the CLI's own check command passes on this repository", which runspnpm check, the root scriptnode packages/cli/dist/cli.js check, after "Build the CLI the self-check runs".lint.yml→pnpm checkover the repositorynode_modules/dist/.git(633 analysed)examples/schema-catalog. 0 files move into "skipped".check)objectui validatewould now reportcheckdoes not parse thesevalidate/checkover fixtures (7 files, pluscli-binspawning the built binary)distwas deleted and rebuiltregistered-types-validate-ratchet-10859(plugin-ai README document throughvalidate)objectui init(templates simple/form/dashboard) andobjectui generate pageM3 — every strict refusal, classified
The pairs come from the shipped face over the M1 corpora and M2's extra refusal. Keys are read with the same
findUndeclaredKeysthis PR ships; its output equals the scratch prototype's over all 158 refused documents. Each of the 120 strict-only refusals names at least one key. The 22 refused documents that name no key are all refused by the tolerant face too, for a value reason.Class (iii), the
UnmirroredDeclaredfamily (objectui#6152):object-form·formType(3 documents),sections(3),defaultTab(1),showStepIndicator(1);object-grid·operations(1),resizableColumns(1),singleClickEdit(1);pagination·currentPage(2);object-view·form.formType(1),form.drawerSide(1). Thevalidatebehaviour change for the mixed-vocabularyFormFieldSchema.fieldis the same class; no corpus document carries it. The ledger inzod-mirror-parity.test.ts(theUnmirroredDeclaredinterface) reads 12 entries and 84 keys on this base, counted from the interface itself rather than from its docblock.Class (ii), by component, with the read site:
form·showSubmitin 76 documents.form.tsxdestructuresshowSubmitfromschema. This is the single largest group, and all 76 are catalogfields-*fixtures.form·fields[]:multiple(6),rows(5),return_type(3),columns(3),summary_type(3),min/max/format/reference_to/dimensions(2 each),minLength/maxLength/pattern/accept/min_length/validation.message(1 each). Each has a named read in@object-ui/fieldswidgets or inform.tsx.dashboard·widgets[].options:data(7),xField(6),yField(6),value(5), read explicitly in DashboardGridLayout / DashboardRenderer. Alsodescriptionandtrend(1 each), which reach the child through{ ...widget, ...options }. Andwidgets[].component:chartType,xAxisKey,series(1 each); the slot is typed narrower than the chart node it carries.dataSourceonobject-grid(2),list-view(2),object-form(1) andobject-kanban(1). It reaches the component as a React prop through SchemaRenderer's spread;object-gridalso maps the spec binding.page·regions[].children(1) andpage:tabs·items(1).Class (i), by document. The documents are named per the dispatch. Docs fences are cited by file and key, never by line.
content/docs/api/schema-reference.md—selection.enabled,selection.mode,pagination.enabled,striped(object-grid)content/docs/blocks/authentication.mdx,forms.mdx,marketing.mdx—action(button)content/docs/blocks/dashboard.mdx,ecommerce.mdx—dataSource(card),action(button)content/docs/guide/dashboard-filters.md—field,optionsFrom(select)content/docs/guide/expressions.md—itemTemplate(list),message(empty),validations(input)content/docs/guide/layout.md—maxWidth,padding(page),tabs(tabs),object(object-grid)content/docs/guide/schema-playground.md—icon(card),titleandfields[].defaultValue(form),title/rows/actions/pagination(grid)content/docs/guide/schema-rendering.md—message(empty)content/docs/utilities/runner.mdx—value,change(card)content/docs/utilities/vscode-extension.mdx—dataSource,columns[].key,columns[].title(data-table),pages(app)examples/schema-catalog/src/schemas/:components-basic-navigation-menu/documentation-nav.jsonandsite-navigation.json—items[].itemscomponents-basic-pagination/with-item-count.json—pageSize,totalItemscomponents-data-display-list/basic-list.json—items[].typecomponents-form-button/full-width-button.json—fullWidthcomponents-form-combobox/country-selector.json—searchPlaceholdercomponents-form-combobox/searchable-combobox.json—searchPlaceholder,emptyTextcomponents-form-form/contact-form.json—submitButton,fields[].columnSpancomponents-form-form/login-form.jsonandregistration-form.json—submitButtoncomponents-form-input-otp/verification-form.jsonandwith-visual-separator.json—separatorcomponents-form-label/required-label.json—requiredfields-currency/euro-currency.jsonandusd-currency.json—fields[].currencyfields-formula/date-calculation.json,numeric-formula.jsonandtext-concatenation.json—fields[].formulafields-number/decimal-numbers.json,fields-percent/required-percent.jsonandwith-decimal-precision.json—fields[].precision; the widgets readscalefields-object/structured-configuration.json—fields[].schemafields-summary/average-of-field-values, count-of-related-records and sum-of-field-values —fields[].summary_object,fields[].summary_fieldpackages/types/examples/dashboard.ts(dashboardSchema) —content(card)packages/types/examples/login-form.ts(loginFormSchema) —fields[].defaultCheckedexamples/hello-world/schema.json—contenton abutton(button.tsxreadslabel; the likely intended key islabel, which is a rename, not a deletion)No class (i) document was repaired in this PR. The dispatch authorises the repair but does not require it. Three reasons:
pnpm checkstays exit 0, no CLI test fixture changes verdict, and no other test runs the strict door over the catalog.examples/schema-catalog/test.The list is the input for slice B and the repair owners. Classes (ii)/(iii) are not touched;
packages/types/**is out of scope for this card. No class (ii)/(iii) site is red in CI.M4 — root and nested probes
Predicted in writing before the run (2026-09-29T08:14Z):
inputwithvalidationunrecognized_keysat(root), keysvalidation; tolerant acceptsdiv→children[0]inputwithnonsenseinvalid_unionatchildren;unrecognized_keysatchildren → 0inside an arm; tolerant acceptschildren → 0 → children → 0) nests again.⇒
StrictAnyComponentSchemajudges nested children too, per depth. That is why the wiring needsfindUndeclaredKeys: the top-level issue at a child slot names no key.Tests, reverse verification, gates
New:
packages/cli/src/__tests__/validate-strict-authoring-face-5250.test.ts, 12 tests.valueonmetric-card) is not named.typemismatched is not read.checklists a leaf with an undeclared key and names it, and names a nested key the first-issue line cannot.Each refusal pin also asserts that the tolerant face accepts the same document.
Pin sweep, repository-wide: no existing test asserted that
validateorcheckaccepts an undeclared key. Three CLI pins that stood in for "the parsecheck/validateruns" now readvalidateAuthoredDocument:check-first-issue-11007,check-validity-recogniserand the ratchetregistered-types-validate-ratchet-10859. The ratchet's counts are unchanged at 73 / 397.Ablations. Predictions were written before running; each mutation went through objectstack's
scripts/ablation-replace.mjsin wrap mode, which checks the anchor hit and the blob change, and proves the restore by blob equal to HEAD with an emptygit diff HEAD. The fix and the pins were committed first.AnyComponentSchemametric-cardcase)validate.tsalone revertedGates, at HEAD
a11f733. The exit code is captured before any pipe, and each tool's own verdict line is quoted.pnpm --filter @object-ui/types build(before M1)dist completeness: 1 package(s) completepnpm --filter @object-ui/cli buildpnpm --filter @object-ui/cli testTest Files 23 passed (23)·Tests 314 passed (314)(base: 22 / 302)pnpm --filter @object-ui/cli type-checktsc --noEmitcleanpnpm --filter @object-ui/cli lintTest Files 27 passed (27)·Tests 1271 passed (1271)pnpm check(thelint.ymlself-check)172 validated, 167 recognised but not validated, 94 did not validatenode scripts/check-changeset-presence.mjs.changeset/5250-strict-validate-wiring.mdpnpm changeset:checkNo changeset declares a major bumppnpm check:changeset-claims·pnpm check:pending-changeset-literalspnpm check:control-bytes(objectui's NUL/control-byte gate)OK (scanned 9372 tracked text file(s))pnpm docs:check-links·pnpm check:doc-fences·pnpm check:doc-typespnpm check:new-line-citations0 new citation(s)pnpm check:test-path-rootsnode scripts/check-governed-queue-guard.mjs --testover the 15 pathsNOT GOVERNEDNOT MEASURED locally:
pnpm check:doc-snippetsexited 2 withPRECONDITION NOT MET, andpnpm check:readme-exportsexited 1 withpopulation COLLAPSED. Both need 34 unbuilt packages built, and neither exit is a verdict. The only fence this diff adds is atextblock, and the diff adds no import/export line, so neither gate has anything of this PR to judge. Both are declared to CI.Acceptance notes
scripts/measure-strict-authoring-face.mjsreads redder than the face it measures. Its walker has no counterpart of objectui#11022's registered inputs; see M1 for the measured difference. Its header only warns about the opposite direction (a mirror that falls behind reads cleaner). This matters for slice B's pricing, which reads that script. It is reported to the seat, not filed from here.Some prose in
packages/typestests now names the wrong door. Several tests callsafeValidateSchema"the doorobjectui validate/objectui checkrun", or "the tolerant oneobjectui validateruns":object-chart-axis-config-10518, twiceobject-chart-react-tier-node-10770timeline-readme-schema-example-10824form-field-widget-namespaceelement-number-arm-10872'sFACESdocblockTheir assertions stay true.
packages/types/**is ⛔ for this card; owner: none.examples/schema-catalog/test/safe-validate-corpus-6318.test.tspins "stays out ofobjectui check's bucket" through the tolerantsafeValidateSchema. Its seven fixtures are strict-clean, so the pin holds. Its instrument is no longercheck's door, though: a fixture that gained an undeclared key would rejoin the bucket with that pin green. Owner: none.AGENTS.md §5 Add default props to all components to prevent collapse in designer #4 and
skills/objectui/SKILL.mdstill say an authoredeventsbag is "kept, judged by nothing".objectui validatenow refuses it by name. Both files are governed surface and are not touched here; the natural owner is objectui#8347, which the programme gives the AGENTS.md #0.1 amendment.Nested child-slot refusals list every arm under the numbered issue, including the primitive arms that failed on shape. This is
explainUnionIssue's existing capped fallback for undiscriminated unions, and it is not changed here. The "Undeclared keys" block is the readable summary. Pruning shape-mismatch arms from that fallback would be a separate change to ruled arm-selection behaviour.Generated by Claude Code