You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Measure the strict authoring face before building it: run catalog fixtures, docs json fences and apps through strict node schemas and count undeclared keys per component (first deliverable of the #5250 ruling) #7581
Filed by the director seat from the #5250 ruling (maintainer, 2026-09-04, decision batch #25: option 2 — a strict authoring face as a programme, measurement first). This card is the measurement. ⛔ It builds nothing that ships: no schema in packages/types changes, no gate is wired, .passthrough() on the rendering face is not touched.
Deliverable: a number per component, not a fix
Derive, in a throwaway (a script under scripts/ or a one-off test that is not left in the tree unless the seat decides it is worth keeping as the future gate's skeleton), a strict variant of every node schema in @object-ui/types/zod — the same declared members, unknown keys refused — and run three corpora through it:
every fixture under examples/schema-catalog/src/schemas/**/*.json;
A component with zero undeclared keys is a result too — the programme needs to know how much of the tree is already strict-clean.
Why a measurement card exists at all
The ruling on #5250 is a multi-week programme touching every node schema and AGENTS.md #0.1. Its appetite cannot be set without knowing how red strict goes on today's corpus, and which side of each red is wrong (schema under-declares, or corpus authors garbage). This card produces that reading; the programme cards (strict face on the spec side, validate + fence gate on the devx side) are priced from it on #5250 afterwards.
⛔ Do not flip .passthrough() on BaseSchemaCore; it is load-bearing for renderer props (see the specFieldsExcept docblock).
The throwaway must be reproducible: pin the main SHA it ran against and keep the script in the PR (or attach it to this card) so the numbers can be re-derived after the declaration repairs land.
Clause-②: no (no published surface moves). pm:queue and domain:devx set by the filing seat from the ruling's named executor; triage corrects if the landing point disagrees.
Claim: PM loop round R44
Session: session_013uAaxiwgYDybsTNV9xwa1M
Branch: claude/issue-7581-strict-authoring-face-measurement
Worktree: objectui-issue-7581
Domain: domain:devx
File surface: a throwaway measurement script under scripts/ (or scripts/__tests__/) + the report on this card / in the PR body; ⛔ nothing under packages/types/src/**, no gate wiring, no .passthrough() change (stop on breach; explain in the report)
Container & model: M, measurement (judgment in corpus classification; nothing ships), mode:subagent, model: opus — objectstack's node scripts/pm/dispatch-gates.mjs --tier REFUSES --repo objectstack-ai/objectui (measured this round: gate families are a property of the tree it runs in), so the tier is the PM's per-card judgment under the script's declared policy floor sonnet · default opus · ceiling fable; no path-derived mandate applies to this surface.
Clause-②: no
Serial constraints cleared: none on files — the deliverable is a throwaway under scripts/ (new file) plus a report; ⚠️#6939 (domain:spec, in flight, declaration repairs in the Zod mirror) moves the very numbers this card measures — the run pins its main SHA so it can be re-derived after #6939 lands; #5250 is pm:blocked on this card (fan-out 1)
Inherits the PM claim 5556352168 (assignee already set; I do not write the assignee field). File surface as claimed: a throwaway measurement script under scripts/ plus the report; nothing under packages/types/src/**, no gate wiring, no .passthrough() change.
{
"issue": 7581,
"status": "done",
"branch": "claude/issue-7581-strict-authoring-face-measurement",
"pr": "https://github.com/objectstack-ai/objectui/pull/7916",
"premise_still_valid": true,
"summary": "Added scripts/measure-strict-authoring-face.mjs, a reproducible throwaway that derives strict twins of every node schema in @object-ui/types/zod in memory and runs the three corpora through them; nothing ships, nothing was repaired, BaseSchemaCore.passthrough() is untouched. Headline at origin/main fa7d66c45 (corpus verified identical to that commit by the script's own pin): 2100 nodes, 196 strict-refused, 137 of them green today and red only under strict, 59 already red under the face as shipped; 58 of 94 component types seen are strict-clean; 13 registered types have no document anywhere. Two premise details on the card are wrong and are reported rather than worked around: (1) the TS index signature the card cites at base.ts 409/441 now sits at 467 (BaseSchema) and 499 (ComponentRendererProps) - located by content; 12 declaration sites repo-wide, 120 interfaces extend BaseSchema directly. (2) All 8 apps/**/*.json files are tooling config (package.json, three tsconfig*.json, manifest.json, vercel.json) and hold ZERO authored node documents, so corpus 3 yields only 2 node documents even after widening it mechanically to .ts modules carrying a type literal; the apps' real authored surface is JSX preview harnesses and spec records, not node documents. Two structural findings the programme needs before pricing: the node recursion point SchemaNodeSchema resolves to BaseSchemaCore, so a naive whole-document .strict() refuses 304/557 documents by unrecognising every child's own props - the recursion point must be redirected to AnyComponentSchema first; and the registry must come from the arms of AnyComponentSchema, not from module exports, because FilterFieldSchema's type enum otherwise captures the text and select node types (that bug inflated an early draft from 0 to 704 refusals on `text` alone). #6939 was NOT merged in; this reading is fa7d66c45's.",
"tests": "No unit tests added - the deliverable is a measurement script, and it is verified by its own controls plus the repo gates. GATES (redirect-then-capture, each quoted from the gate's own verdict line): `pnpm check:control-bytes` exit 0 -> 'check-control-bytes: OK (scanned 6398 tracked text file(s); skipped 85 binary).'; `pnpm check:shell-escape-residue` exit 0 -> 'check-shell-escape-residue: OK (5/5 root(s) resolved ...; 0 occurrence(s) outside a fence (counted, not judged)).'; `pnpm type-check:scripts` exit 0, no diagnostics; `pnpm lint:root` exit 0 -> '33 problems (0 errors, 33 warnings)', 0 of them on the new file (grep -c measure-strict = 0, all 33 pre-existing); `node scripts/check-governed-queue-guard.mjs --test scripts/measure-strict-authoring-face.mjs .changeset/7581-...md` -> 'NOT GOVERNED - 2 path(s) checked against 5 governed surface(s); none matched.'. Build first: `pnpm --filter @object-ui/types build` exit 0 -> 'dist completeness: 1 package(s) complete (124 emitted files verified)' (run under bash /home/user/objectstack/scripts/pm/os-verify-lock.sh, VERDICT command-exit 0, held 7s, waited 0s). Note: `pnpm --filter '@object-ui/types^...' build` exits 1 with ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT - the closure has no buildable workspace dependency; the package itself was built directly. REPRODUCIBILITY (the card requires it): two consecutive runs of both output formats diff byte-identical - markdown 455 lines, --json 63k, `diff` silent both ways. ABLATION of the corpus pin, on the committed tree, with an EXIT/INT/TERM trap and absolute paths: appended one line to content/docs/blocks/authentication.mdx; on-disk landing proven by blob hash 6aa5e59b -> 1d59f6a9 (both non-empty, different) and `grep -c` = 1 on the injected text; the run then printed \"corpus identical to that main commit: **NO - the numbers below are not main's**\" where the clean run prints **yes**. Restore leg: `git checkout HEAD -- content/docs` returned the blob to 6aa5e59b byte-identically and `git diff HEAD -- content/docs` is empty; the re-run prints **yes** again. Direction predicted before the run. PROBE CONTROLS inside the script itself: the renderer-mention probe runs `className` (must be > 0) and `zzzFabricatedControlKey` (must be 0) every run and prints both - it reads 13409 / 0, 'probe is live'. That control earned its keep: the first spelling of the pathspec (`packages/*/src` without `:(glob)`) matched nothing and the second was a TDZ throw swallowed by the catch; both returned 0 for every key, which reads exactly like 'nothing reads any of these'. Both are fixed and both traps are written into the script's comments. A third control: `git grep -n '[key: string]: any'` for a fabricated signature name returns 0 matches.",
"mcp_calls": "1 - a single targeted mcp__github__search_issues for the dedupe of the two filed findings (REST /search/* is 403 through the proxy, re-measured this round at 02:51Z; it returned 15 non-empty results, so no control query was owed). Every other GitHub read and write went over repo-scoped REST curl: 6 issue/comment reads, 1 claim comment, 1 PR create, 1 PR body read-back, 2 issue creates, plus the report comment.",
"open_questions": [
{
"question": "Should scripts/measure-strict-authoring-face.mjs stay in the tree as the skeleton of the future fence/validate gate, or be deleted once #5250's pricing has read the numbers? #7581 leaves this to the seat and the PR currently keeps it (the card also requires the script be kept so the numbers can be re-derived after #6939 lands).",
"options": [
"A - keep it as a plain script, unwired to any gate (the state of this PR). Costs one more file under scripts/ that pnpm lint:root and check-control-bytes already cover; buys the ability to re-run the measurement after #6939 and after each declaration repair, which is exactly what the programme needs to know it is converging.",
"B - keep it AND wire it as a report-only CI job that prints the numbers without failing. Buys a trend line for free; costs a job slot and a thing to keep honest, and #5250 explicitly rules that the fence gate is built ON TOP OF the strict face, not before it.",
"C - delete it after the pricing round, keeping only the numbers in this card and the PR body. Cheapest tree; but every future re-measurement is rebuilt from scratch, and the five decisions this one had to get right (registry source, schema-guided traversal, node boundary, clone-not-rebuild, unfailable key probe) are exactly the kind that get re-litigated wrongly."
],
"recommendation": "A, for now, and explicitly not B. On the anti-AI-error axis, keeping the script is what makes 'declared = enforced' re-checkable rather than re-argued, and the four traps it documents in its own comments (the FilterFieldSchema registry capture, the field-def/node collision, the recursion point, the dead grep probe) are the ones a re-implementation would hit again. On the startup-focus axis, B is capability expansion with no puller: #5250 rules the gate comes after the strict face, so a report-only job today prints numbers nobody is acting on yet, and #7581 forbids leaving a gate wired. C is the honest fallback if the seat judges a scripts/ file with no gate behind it to be dead weight - but the card's own reproducibility clause argues against it while #6939 is still in flight."
},
{
"question": "The whole-document reading (304/557 refused) is dominated by the node recursion point, not by component under-declaration. Does the domain:spec seat want the strict face's first card to be 'redirect SchemaNodeSchema to AnyComponentSchema' rather than a per-component declaration sweep?",
"options": [
"A - redirect the recursion point first, then measure again; the per-component numbers here (196/2100) are what remains after that.",
"B - do the per-component declaration repairs first (the #6939 shape), leaving the recursion point as is; strict then still cannot be switched on at document level."
],
"recommendation": "A. B leaves 304/557 documents red for a reason that has nothing to do with the components, so no per-component repair budget can ever close it; the measurement's job was to tell the programme which red is which, and this is the biggest single answer it produced. Flagged rather than decided: sequencing the programme cards is #5250's, not mine."
}
],
"out_of_scope_findings": [
"filed as #7917: `BreadcrumbSchema` and `ObjectTreeSchema` are declared node schemas (arms of AnyComponentSchema) that @object-ui/types/zod never exports, so 2 of 107 component types cannot be validated on their own - unlabelled, unassigned, deduped against #7502/#7324 which are the same class on packages/fields and packages/plugin-form",
"filed as #7918 (label `finding`): ten z.lazy exports in the zod node face rebuild their schema on every getter call, so the recursion point cannot be compared by identity and the sub-schema is reconstructed per parse - unassigned",
"listed in the PR body, NOT filed (objectui#7581 explicitly permits 'file it ... or list it in the report'): the full undeclared-key worklist - `showSubmit`x76 on FormSchema with 40 renderer mentions (#6939-shaped), the dashboard `widgets[].value/icon/trend/trendValue` cluster, and the seven zero-mention #7077-shaped candidates (`columnSpan`, `fullWidth`, `itemTemplate`, `rowSpan`, `summary_field`, `summary_object`, `totalItems`)",
"listed in the PR body, NOT filed: 59 nodes and 50 whole documents are already refused by the face AS SHIPPED - a superset of #6939's 8 components, which is open and in flight; folding them into a new card would duplicate it"
]
}
The measurement — condensed (full tables in PR #7916)
Pinned to origin/mainfa7d66c4505d0fcc866fbecec1901c4990a80c3a; the script verifies the corpus is byte-identical to that commit before reporting. zod 4.4.3, face read from packages/types/dist/zod/index.zod.js. #6939 was not merged in.
Per corpus
corpus
items
node documents
fragments
not an object
no type
unresolved type
nodes
nodes strict-refused
nodes red today
strict-ONLY
catalog
431
412
0
0
0
19
1899
136
23
113
docs
204
143
25
0
20
16
193
58
35
23
authored
19
2
2
0
6
9
8
2
1
1
Corpus sizes agree with the PM's enumeration on all four counts (431 catalog .json, 204 ```json fence openings, 8 apps/**/*.json, 6 files under packages/*/examples/**) — but all 8 apps JSON files are tooling config and hold zero authored node documents, which is why corpus 3 yields only 2.
Headline
metric
value
node documents measured
557
nodes measured (documents flattened at declared node slots)
2100
nodes refused by the strict twin
196
…of which strict is the ONLY reason (green today)
137
nodes already red under the face as shipped
59
component types seen in the corpora
94
component types strict-CLEAN (zero refusals)
58
registered component types with no document anywhere
13
whole-document strict parse, no node boundary
304 / 557 refused
whole-document parse under the face as shipped
50 / 557 refused
⚠️The 304/557 is not a per-component number. Child slots are z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]) and SchemaNodeSchema resolves to BaseSchemaCore — the ~21 base keys and nothing type-specific — so strict-ifying the recursion point unrecognises every child node's own declared props. The per-component tables therefore judge each node against its own schema and treat the recursion point as a boundary. The recursion point has to be redirected to AnyComponentSchema before strict can be switched on at document level at all; that is this measurement's largest structural result.
Per component type — the 36 with at least one refusal
Top undeclared keys, top level (what BaseSchemaCore.passthrough() admits today)
key
occurrences
renderer-source mentions
showSubmit
76
40
action
9
2724
dataSource
9
1465
actions
8
989
change
3
790
formType
3
76
message
3
2046
sections
3
413
submitButton
3
45
text
3
7680
value
3
8785
currentPage
2
40
defaultViewType
2
13
listViews
2
92
searchPlaceholder
2
93
Mention-probe controls: className = 13409 (must be > 0), zzzFabricatedControlKey = 0 (must be 0) ⇒ probe live. Mentions are a coarse triage hint, not a verdict: a common word (value, title, data) hits unrelated code and a computed accessor hits zero. The actionable end is the zero-mention rows — columnSpan, fullWidth, itemTemplate, rowSpan, summary_field, summary_object, totalItems — which are the #7077-shaped candidates, against showSubmit (76 occurrences, 40 mentions), the largest #6939-shaped one.
Top undeclared keys, nested inside declared members
path
occurrences
widgets.[].value
16
widgets.[].icon
15
widgets.[].trend
15
widgets.[].trendValue
15
widgets.[].options.data
11
widgets.[].options.xField
11
widgets.[].options.yField
11
items.[].description
8
items.[].time
8
items.[].title
8
items.[].variant
8
fields.[].multiple
6
fields.[].rows
5
items.[].icon
4
widgets.[].options.value
4
Walker limits and other reported non-results (⛔ nothing silently skipped)
Shapes strict could not close: only opaque custom / function / transform validators — there is no shape inside them to close. Every reachable object, union, discriminated union, array, tuple, record, intersection, optional, nullable, default, pipe and z.lazy was strict-ified. Registry collisions: 0. Arms without a literal type: 0.
type values resolving in NO node schema (refused by AnyComponentSchema today): 44 at document roots across 28 distinct spellings (ui:calendar×5, sidebar-provider×4, action:button×3, page:tabs, donut-chart, pie-chart, radar-chart, spec-report, unknown-component, …) and 9 at declared child-node slots (footer, h1, header, line-chart).
Registered component types with zero documents in any corpus (13): calendar, chatbot-enhanced, chatbot-floating, crud-dialog, object-calendar, object-chart, object-data-table, object-gallery, object-tree, report, report-builder, report-viewer, span.
Authored modules that could not be imported (recorded, not skipped): apps/console/src/pages/developer/hooks/useApiDiscovery.ts — needs a built @object-ui/app-shell. One module prints on import (packages/types/examples/zod-validation-example.ts); console is muted for the run so stdout stays exactly the report, and the fact is reported.
The TypeScript face: [key: string]: any is declared at 12 sites in packages/types/src, and 120 interfaces extends BaseSchema directly. The card cites base.ts lines 409 / 441; located by content they are now 467 (BaseSchema) and 499 (ComponentRendererProps) — the second is the renderer props type, not an authoring type.
ACCEPT — PR #7916 (head 0044a6021): the measurement is delivered, nothing ships, nothing was repaired. Open question 1 answered here: A, keep the script unwired.
domain:devx @ objectui execution seat, PM session session_013uAaxiwgYDybsTNV9xwa1M, R44, 2026-09-06T03:04Z. Report 5556504179 reviewed against GitHub and the tree, ⛔ not against its narrative.
item
reading (mine)
PR shape
draft ✅ · base main ✅ · first line Fixes #7581 ✅ (this card's deliverable is the measurement; it closes on merge) · no other closing keyword adjacent to any card number ✅ · one platform footer ✅
Path surface (get_files)
exactly 2: scripts/measure-strict-authoring-face.mjs (+1001), .changeset/7581-strict-authoring-face-measurement.md (+9, empty frontmatter). Zero packages/ changes, zero governed paths — the card's ⛔ (no schema, no gate wiring, no .passthrough() change) holds by the file list, not by assertion
Deliverable shape
per-component table (documents / strict-refused / undeclared keys, top-level vs nested) and per-corpus totals with the docs fragment and non-document buckets are in the PR body and in the report comment on this card, as the card asked ("in the PR or on this card" — both)
Reproducibility
the script pins and verifies the corpus against the main commit it ran on; two consecutive runs of both output formats diff byte-identical; the pin's ablation (one appended line → corpus identical … NO) proved on disk by differing blob hash and restored to the HEAD blob with empty git diff HEAD
Controls
the renderer-mention probe carries a must-fire (className 13409) and a must-not (zzzFabricatedControlKey 0) every run — ⭐ and that control caught two dead-probe spellings during development that would otherwise have read "nothing reads any of these"
Gates
type-check:scripts 0 diagnostics · lint:root 33 warnings all pre-existing (0 on the new file) · check:control-bytes OK · check:shell-escape-residue OK · governed-queue-guard NOT GOVERNED — quoted from verdict lines
#7917 (two node schemas never exported from the zod face — bare, for triage) · #7918 (z.lazy rebuild per getter — finding); the undeclared-key worklist and the 59 already-red nodes are listed in the PR body rather than filed, which the card explicitly permits and which avoids a twin of in-flight #6939
Headline the programme will price from (at origin/mainfa7d66c45, #6939 NOT merged in)
2100 nodes · 196 strict-refused, of which 137 are green today and red only under strict, 59 already red under the face as shipped · 58 of 94 seen component types strict-clean · 13 registered types with no document anywhere. ⭐ And the structural result that outranks the per-component numbers: the node recursion point SchemaNodeSchema resolves to BaseSchemaCore, so a naive whole-document .strict() refuses 304 / 557 documents by unrecognising every child's own props — the recursion point must be redirected to AnyComponentSchema before any per-component repair budget can close a whole-document reading.
Two card-premise corrections from the dev, adopted
base.ts index signature lines are 467 (BaseSchema) and 499 (ComponentRendererProps) today, not 409/441; located by content.
apps/**/*.json holds zero authored node documents (all eight are tooling config); corpus 3 is JSX harnesses and spec records, so its node-document yield is 2 even after mechanical widening. The card's corpus-3 definition should be read as "the apps' authored documents" ≈ empty, not as a measurement gap.
Landing plan
Path surface clean ⇒ flipped ready at 2026-09-06T03:04Z; auto-merge armed only when every check is green; enqueue confirmed by the added_to_merge_queue event. On MERGED: this card closes via Fixes; #5250 (Blocked-by: #7581) is discharged by the unlock scan — the stroke that closes this card also posts the pointer on #5250 and returns it to the pricing step its ruling names.
scripts/measure-strict-authoring-face.mjs present on main
1
1
its header marker MEASUREMENT THROWAWAY
> 0
✅
the must-fire probe key zzzFabricatedControlKey in the script
> 0
✅
control — a nonsense phrase
0
0
The card closes via Fixes on merge; pm:dispatched stripped and assignee cleared in this stroke where GitHub left them. #5250 (Blocked-by: #7581) is now discharged — pointer posted there. Open question 1 was answered A (script kept, unwired); question 2 (recursion point first vs per-component repairs) is the domain:spec seat's to propose on #5250.
Activity
claude commentedon Sep 6, 2026
Claim: PM loop round R44⚠️ #6939 (domain:spec, in flight, declaration repairs in the Zod mirror) moves the very numbers this card measures — the run pins its
Session:
session_013uAaxiwgYDybsTNV9xwa1MBranch:
claude/issue-7581-strict-authoring-face-measurementWorktree:
objectui-issue-7581Domain:
domain:devxFile surface: a throwaway measurement script under
scripts/(orscripts/__tests__/) + the report on this card / in the PR body; ⛔ nothing underpackages/types/src/**, no gate wiring, no.passthrough()change (stop on breach; explain in the report)Container & model: M, measurement (judgment in corpus classification; nothing ships),
mode:subagent,model: opus— objectstack'snode scripts/pm/dispatch-gates.mjs --tierREFUSES--repo objectstack-ai/objectui(measured this round: gate families are a property of the tree it runs in), so the tier is the PM's per-card judgment under the script's declared policy floor sonnet · default opus · ceiling fable; no path-derived mandate applies to this surface.Clause-②: no
Serial constraints cleared: none on files — the deliverable is a throwaway under
scripts/(new file) plus a report;mainSHA so it can be re-derived after #6939 lands; #5250 ispm:blockedon this card (fan-out 1)Generated by Claude Code
claude commentedon Sep 6, 2026
Claim: os-dev subagent (dispatched by
domain:devx @ objectuiPM seat, round R44)Session:
session_013uAaxiwgYDybsTNV9xwa1MBranch:
claude/issue-7581-strict-authoring-face-measurementWorktree:
/home/user/objectui-issue-7581Base SHA:
fa7d66c4505d0fcc866fbecec1901c4990a80c3aInherits the PM claim 5556352168 (assignee already set; I do not write the assignee field). File surface as claimed: a throwaway measurement script under
scripts/plus the report; nothing underpackages/types/src/**, no gate wiring, no.passthrough()change.Generated by Claude Code
claude commentedon Sep 6, 2026
os-dev-report
{ "issue": 7581, "status": "done", "branch": "claude/issue-7581-strict-authoring-face-measurement", "pr": "https://github.com/objectstack-ai/objectui/pull/7916", "premise_still_valid": true, "summary": "Added scripts/measure-strict-authoring-face.mjs, a reproducible throwaway that derives strict twins of every node schema in @object-ui/types/zod in memory and runs the three corpora through them; nothing ships, nothing was repaired, BaseSchemaCore.passthrough() is untouched. Headline at origin/main fa7d66c45 (corpus verified identical to that commit by the script's own pin): 2100 nodes, 196 strict-refused, 137 of them green today and red only under strict, 59 already red under the face as shipped; 58 of 94 component types seen are strict-clean; 13 registered types have no document anywhere. Two premise details on the card are wrong and are reported rather than worked around: (1) the TS index signature the card cites at base.ts 409/441 now sits at 467 (BaseSchema) and 499 (ComponentRendererProps) - located by content; 12 declaration sites repo-wide, 120 interfaces extend BaseSchema directly. (2) All 8 apps/**/*.json files are tooling config (package.json, three tsconfig*.json, manifest.json, vercel.json) and hold ZERO authored node documents, so corpus 3 yields only 2 node documents even after widening it mechanically to .ts modules carrying a type literal; the apps' real authored surface is JSX preview harnesses and spec records, not node documents. Two structural findings the programme needs before pricing: the node recursion point SchemaNodeSchema resolves to BaseSchemaCore, so a naive whole-document .strict() refuses 304/557 documents by unrecognising every child's own props - the recursion point must be redirected to AnyComponentSchema first; and the registry must come from the arms of AnyComponentSchema, not from module exports, because FilterFieldSchema's type enum otherwise captures the text and select node types (that bug inflated an early draft from 0 to 704 refusals on `text` alone). #6939 was NOT merged in; this reading is fa7d66c45's.", "tests": "No unit tests added - the deliverable is a measurement script, and it is verified by its own controls plus the repo gates. GATES (redirect-then-capture, each quoted from the gate's own verdict line): `pnpm check:control-bytes` exit 0 -> 'check-control-bytes: OK (scanned 6398 tracked text file(s); skipped 85 binary).'; `pnpm check:shell-escape-residue` exit 0 -> 'check-shell-escape-residue: OK (5/5 root(s) resolved ...; 0 occurrence(s) outside a fence (counted, not judged)).'; `pnpm type-check:scripts` exit 0, no diagnostics; `pnpm lint:root` exit 0 -> '33 problems (0 errors, 33 warnings)', 0 of them on the new file (grep -c measure-strict = 0, all 33 pre-existing); `node scripts/check-governed-queue-guard.mjs --test scripts/measure-strict-authoring-face.mjs .changeset/7581-...md` -> 'NOT GOVERNED - 2 path(s) checked against 5 governed surface(s); none matched.'. Build first: `pnpm --filter @object-ui/types build` exit 0 -> 'dist completeness: 1 package(s) complete (124 emitted files verified)' (run under bash /home/user/objectstack/scripts/pm/os-verify-lock.sh, VERDICT command-exit 0, held 7s, waited 0s). Note: `pnpm --filter '@object-ui/types^...' build` exits 1 with ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT - the closure has no buildable workspace dependency; the package itself was built directly. REPRODUCIBILITY (the card requires it): two consecutive runs of both output formats diff byte-identical - markdown 455 lines, --json 63k, `diff` silent both ways. ABLATION of the corpus pin, on the committed tree, with an EXIT/INT/TERM trap and absolute paths: appended one line to content/docs/blocks/authentication.mdx; on-disk landing proven by blob hash 6aa5e59b -> 1d59f6a9 (both non-empty, different) and `grep -c` = 1 on the injected text; the run then printed \"corpus identical to that main commit: **NO - the numbers below are not main's**\" where the clean run prints **yes**. Restore leg: `git checkout HEAD -- content/docs` returned the blob to 6aa5e59b byte-identically and `git diff HEAD -- content/docs` is empty; the re-run prints **yes** again. Direction predicted before the run. PROBE CONTROLS inside the script itself: the renderer-mention probe runs `className` (must be > 0) and `zzzFabricatedControlKey` (must be 0) every run and prints both - it reads 13409 / 0, 'probe is live'. That control earned its keep: the first spelling of the pathspec (`packages/*/src` without `:(glob)`) matched nothing and the second was a TDZ throw swallowed by the catch; both returned 0 for every key, which reads exactly like 'nothing reads any of these'. Both are fixed and both traps are written into the script's comments. A third control: `git grep -n '[key: string]: any'` for a fabricated signature name returns 0 matches.", "mcp_calls": "1 - a single targeted mcp__github__search_issues for the dedupe of the two filed findings (REST /search/* is 403 through the proxy, re-measured this round at 02:51Z; it returned 15 non-empty results, so no control query was owed). Every other GitHub read and write went over repo-scoped REST curl: 6 issue/comment reads, 1 claim comment, 1 PR create, 1 PR body read-back, 2 issue creates, plus the report comment.", "open_questions": [ { "question": "Should scripts/measure-strict-authoring-face.mjs stay in the tree as the skeleton of the future fence/validate gate, or be deleted once #5250's pricing has read the numbers? #7581 leaves this to the seat and the PR currently keeps it (the card also requires the script be kept so the numbers can be re-derived after #6939 lands).", "options": [ "A - keep it as a plain script, unwired to any gate (the state of this PR). Costs one more file under scripts/ that pnpm lint:root and check-control-bytes already cover; buys the ability to re-run the measurement after #6939 and after each declaration repair, which is exactly what the programme needs to know it is converging.", "B - keep it AND wire it as a report-only CI job that prints the numbers without failing. Buys a trend line for free; costs a job slot and a thing to keep honest, and #5250 explicitly rules that the fence gate is built ON TOP OF the strict face, not before it.", "C - delete it after the pricing round, keeping only the numbers in this card and the PR body. Cheapest tree; but every future re-measurement is rebuilt from scratch, and the five decisions this one had to get right (registry source, schema-guided traversal, node boundary, clone-not-rebuild, unfailable key probe) are exactly the kind that get re-litigated wrongly." ], "recommendation": "A, for now, and explicitly not B. On the anti-AI-error axis, keeping the script is what makes 'declared = enforced' re-checkable rather than re-argued, and the four traps it documents in its own comments (the FilterFieldSchema registry capture, the field-def/node collision, the recursion point, the dead grep probe) are the ones a re-implementation would hit again. On the startup-focus axis, B is capability expansion with no puller: #5250 rules the gate comes after the strict face, so a report-only job today prints numbers nobody is acting on yet, and #7581 forbids leaving a gate wired. C is the honest fallback if the seat judges a scripts/ file with no gate behind it to be dead weight - but the card's own reproducibility clause argues against it while #6939 is still in flight." }, { "question": "The whole-document reading (304/557 refused) is dominated by the node recursion point, not by component under-declaration. Does the domain:spec seat want the strict face's first card to be 'redirect SchemaNodeSchema to AnyComponentSchema' rather than a per-component declaration sweep?", "options": [ "A - redirect the recursion point first, then measure again; the per-component numbers here (196/2100) are what remains after that.", "B - do the per-component declaration repairs first (the #6939 shape), leaving the recursion point as is; strict then still cannot be switched on at document level." ], "recommendation": "A. B leaves 304/557 documents red for a reason that has nothing to do with the components, so no per-component repair budget can ever close it; the measurement's job was to tell the programme which red is which, and this is the biggest single answer it produced. Flagged rather than decided: sequencing the programme cards is #5250's, not mine." } ], "out_of_scope_findings": [ "filed as #7917: `BreadcrumbSchema` and `ObjectTreeSchema` are declared node schemas (arms of AnyComponentSchema) that @object-ui/types/zod never exports, so 2 of 107 component types cannot be validated on their own - unlabelled, unassigned, deduped against #7502/#7324 which are the same class on packages/fields and packages/plugin-form", "filed as #7918 (label `finding`): ten z.lazy exports in the zod node face rebuild their schema on every getter call, so the recursion point cannot be compared by identity and the sub-schema is reconstructed per parse - unassigned", "listed in the PR body, NOT filed (objectui#7581 explicitly permits 'file it ... or list it in the report'): the full undeclared-key worklist - `showSubmit`x76 on FormSchema with 40 renderer mentions (#6939-shaped), the dashboard `widgets[].value/icon/trend/trendValue` cluster, and the seven zero-mention #7077-shaped candidates (`columnSpan`, `fullWidth`, `itemTemplate`, `rowSpan`, `summary_field`, `summary_object`, `totalItems`)", "listed in the PR body, NOT filed: 59 nodes and 50 whole documents are already refused by the face AS SHIPPED - a superset of #6939's 8 components, which is open and in flight; folding them into a new card would duplicate it" ] }The measurement — condensed (full tables in PR #7916)
Pinned to
origin/mainfa7d66c4505d0fcc866fbecec1901c4990a80c3a; the script verifies the corpus is byte-identical to that commit before reporting. zod4.4.3, face read frompackages/types/dist/zod/index.zod.js. #6939 was not merged in.Per corpus
typetypeCorpus sizes agree with the PM's enumeration on all four counts (431 catalog
.json, 204```jsonfence openings, 8apps/**/*.json, 6 files underpackages/*/examples/**) — but all 8 apps JSON files are tooling config and hold zero authored node documents, which is why corpus 3 yields only 2.Headline
z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)])andSchemaNodeSchemaresolves toBaseSchemaCore— the ~21 base keys and nothing type-specific — so strict-ifying the recursion point unrecognises every child node's own declared props. The per-component tables therefore judge each node against its own schema and treat the recursion point as a boundary. The recursion point has to be redirected toAnyComponentSchemabefore strict can be switched on at document level at all; that is this measurement's largest structural result.Per component type — the 36 with at least one refusal
flexstackitems×1buttonaction×5,events×1,fullWidth×1,text×1cardchange×3,value×3,dataSource×2,content×1, +1formshowSubmit×76,submitButton×3,title×1fields.[].multiple×6,fields.[].rows×5,fields.[].columns×3,fields.[].formula×3, +21iconlabelrequired×1badgetext×2inputvalidations×1statisticgridactions×1,pagination×1,rows×1,title×1object-griddataSource×2,operations×1,resizableColumns×1,singleClickEdit×1, +1pagination.enabled×1,selection.enabled×1,selection.mode×1dashboardwidgets.[].value×16,widgets.[].icon×15,widgets.[].trend×15,widgets.[].trendValue×15, +6pageactions×3,breadcrumbs×1regions.[].body×1selectfield×1,optionsFrom×1data-tabledataSource×1columns.[].key×1,columns.[].title×1emptyaction×4,message×2resizablecarouselcomboboxsearchPlaceholder×2,emptyText×1filter-builderinput-otpseparator×2object-formformType×3,sections×3,dataSource×1,defaultTab×1, +1timelineitems.[].description×8,items.[].time×8,items.[].title×8,items.[].variant×8, +2alert-dialogactions×4object-viewdefaultViewType×2,listViews×2,searchableFields×2,defaultListView×1, +3form.drawerSide×1,form.formType×1alertmessage×1listitemTemplate×1items.[].type×3list-viewdataSource×2navigation-menuitems.[].items×2paginationcurrentPage×2,pageSize×1,totalItems×1apppages×1collapsibledetailobject-kanbandataSource×1popoverStrict-CLEAN — 58 component types, zero refusals (a result too, per the card)
text,box,container,separator,toggle,avatar,div,checkbox,radio-group,date-picker,file-upload,header-bar,scroll-area,sonner,spinner,switch,toast,kanban,tree-view,aspect-ratio,button-group,kbd,progress,table,bar-chart,breadcrumb,chart,code-editor,textarea,calendar-view,chatbot,markdown,object-gantt,object-map,sheet,skeleton,tabs,toaster,toggle-group,command,dropdown-menu,image,loading,view-switcher,accordion,action,context-menu,detail-view,dialog,drawer,filter-ui,hover-card,html,menubar,sidebar,slider,sort-ui,tooltipTop undeclared keys, top level (what
BaseSchemaCore.passthrough()admits today)showSubmitactiondataSourceactionschangeformTypemessagesectionssubmitButtontextvaluecurrentPagedefaultViewTypelistViewssearchPlaceholderMention-probe controls:
className= 13409 (must be > 0),zzzFabricatedControlKey= 0 (must be 0) ⇒ probe live. Mentions are a coarse triage hint, not a verdict: a common word (value,title,data) hits unrelated code and a computed accessor hits zero. The actionable end is the zero-mention rows —columnSpan,fullWidth,itemTemplate,rowSpan,summary_field,summary_object,totalItems— which are the #7077-shaped candidates, againstshowSubmit(76 occurrences, 40 mentions), the largest #6939-shaped one.Top undeclared keys, nested inside declared members
widgets.[].valuewidgets.[].iconwidgets.[].trendwidgets.[].trendValuewidgets.[].options.datawidgets.[].options.xFieldwidgets.[].options.yFielditems.[].descriptionitems.[].timeitems.[].titleitems.[].variantfields.[].multiplefields.[].rowsitems.[].iconwidgets.[].options.valueWalker limits and other reported non-results (⛔ nothing silently skipped)
custom/function/transformvalidators — there is no shape inside them to close. Every reachable object, union, discriminated union, array, tuple, record, intersection, optional, nullable, default, pipe andz.lazywas strict-ified. Registry collisions: 0. Arms without a literaltype: 0.typevalues resolving in NO node schema (refused byAnyComponentSchematoday): 44 at document roots across 28 distinct spellings (ui:calendar×5,sidebar-provider×4,action:button×3,page:tabs,donut-chart,pie-chart,radar-chart,spec-report,unknown-component, …) and 9 at declared child-node slots (footer,h1,header,line-chart).calendar,chatbot-enhanced,chatbot-floating,crud-dialog,object-calendar,object-chart,object-data-table,object-gallery,object-tree,report,report-builder,report-viewer,span.apps/console/src/pages/developer/hooks/useApiDiscovery.ts— needs a built@object-ui/app-shell. One module prints on import (packages/types/examples/zod-validation-example.ts); console is muted for the run so stdout stays exactly the report, and the fact is reported.[key: string]: anyis declared at 12 sites inpackages/types/src, and 120 interfacesextends BaseSchemadirectly. The card citesbase.tslines 409 / 441; located by content they are now 467 (BaseSchema) and 499 (ComponentRendererProps) — the second is the renderer props type, not an authoring type.Generated by Claude Code
claude commentedon Sep 6, 2026
ACCEPT — PR #7916 (head
0044a6021): the measurement is delivered, nothing ships, nothing was repaired. Open question 1 answered here: A, keep the script unwired.domain:devx @ objectuiexecution seat, PM sessionsession_013uAaxiwgYDybsTNV9xwa1M, R44, 2026-09-06T03:04Z. Report5556504179reviewed against GitHub and the tree, ⛔ not against its narrative.main✅ · first lineFixes #7581✅ (this card's deliverable is the measurement; it closes on merge) · no other closing keyword adjacent to any card number ✅ · one platform footer ✅get_files)scripts/measure-strict-authoring-face.mjs(+1001),.changeset/7581-strict-authoring-face-measurement.md(+9, empty frontmatter). Zeropackages/changes, zero governed paths — the card's ⛔ (no schema, no gate wiring, no.passthrough()change) holds by the file list, not by assertionmaincommit it ran on; two consecutive runs of both output formats diff byte-identical; the pin's ablation (one appended line →corpus identical … NO) proved on disk by differing blob hash and restored to the HEAD blob with emptygit diff HEADclassName13409) and a must-not (zzzFabricatedControlKey0) every run — ⭐ and that control caught two dead-probe spellings during development that would otherwise have read "nothing reads any of these"type-check:scripts0 diagnostics ·lint:root33 warnings all pre-existing (0 on the new file) ·check:control-bytesOK ·check:shell-escape-residueOK · governed-queue-guard NOT GOVERNED — quoted from verdict linesz.lazyrebuild per getter —finding); the undeclared-key worklist and the 59 already-red nodes are listed in the PR body rather than filed, which the card explicitly permits and which avoids a twin of in-flight #6939Headline the programme will price from (at
origin/mainfa7d66c45, #6939 NOT merged in)2100 nodes · 196 strict-refused, of which 137 are green today and red only under strict, 59 already red under the face as shipped · 58 of 94 seen component types strict-clean · 13 registered types with no document anywhere. ⭐ And the structural result that outranks the per-component numbers: the node recursion point
SchemaNodeSchemaresolves toBaseSchemaCore, so a naive whole-document.strict()refuses 304 / 557 documents by unrecognising every child's own props — the recursion point must be redirected toAnyComponentSchemabefore any per-component repair budget can close a whole-document reading.The two open questions
safeValidateSchemabecause the Zod mirror names a key its renderer never reads (8 components) #6939 lands; the five traps the script documents in its comments (registry captured from module exports, the field-def/node collision, the recursion point, clone-vs-rebuild, the dead grep probe) are exactly what a rewrite would hit again. ⛔ Not B: finding: JSON doc snippets are checked by nothing — the ts/tsx gate cannot see them, andBaseSchema.passthrough()makesobjectui validateaccept arbitrary undeclared keys #5250 rules the fence gate is built on top of the strict face, and a report-only job today prints numbers nobody acts on yet.BaseSchema.passthrough()makesobjectui validateaccept arbitrary undeclared keys #5250's programme, which the ruling reserves to thedomain:specanddomain:devxseats proposing on finding: JSON doc snippets are checked by nothing — the ts/tsx gate cannot see them, andBaseSchema.passthrough()makesobjectui validateaccept arbitrary undeclared keys #5250. Relayed there in this stroke with the dev's recommendation (A) as input, ⛔ not decided here.Two card-premise corrections from the dev, adopted
base.tsindex signature lines are 467 (BaseSchema) and 499 (ComponentRendererProps) today, not 409/441; located by content.apps/**/*.jsonholds zero authored node documents (all eight are tooling config); corpus 3 is JSX harnesses and spec records, so its node-document yield is 2 even after mechanical widening. The card's corpus-3 definition should be read as "the apps' authored documents" ≈ empty, not as a measurement gap.Landing plan
Path surface clean ⇒ flipped ready at 2026-09-06T03:04Z; auto-merge armed only when every check is green; enqueue confirmed by the
added_to_merge_queueevent. On MERGED: this card closes viaFixes; #5250 (Blocked-by: #7581) is discharged by the unlock scan — the stroke that closes this card also posts the pointer on #5250 and returns it to the pricing step its ruling names.Generated by Claude Code
claude commentedon Sep 6, 2026
LANDED — PR #7916 merged 2026-09-06T03:35:09Z (queued 03:19:56Z). Verified on
origin/mainb8e3f8379by CONTENT, ⛔ not by the merge event.domain:devx @ objectuiexecution seat, PM sessionsession_013uAaxiwgYDybsTNV9xwa1M, R44, 2026-09-06T03:36Z.scripts/measure-strict-authoring-face.mjspresent onmainMEASUREMENT THROWAWAYzzzFabricatedControlKeyin the scriptThe card closes via
Fixeson merge;pm:dispatchedstripped and assignee cleared in this stroke where GitHub left them. #5250 (Blocked-by: #7581) is now discharged — pointer posted there. Open question 1 was answered A (script kept, unwired); question 2 (recursion point first vs per-component repairs) is thedomain:specseat's to propose on #5250.Generated by Claude Code