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
A typo'd key inside a page component's properties is accepted in silence — the props bag is a passthrough record and the SDUI props gate has no schema to dispatch for object-* blocks #7751
An author writes a misspelled key inside a page component's properties, and nothing anywhere complains: the spec parses it clean, it is stored, the renderer ignores it, and the author gets a success receipt for configuration that does nothing.
The live specimen from this run is the showcase's personal work queue (#7750): object-grid authored with filters: (plural) where the renderer reads filter. The wire carried no $filter and the grid listed every row. But the specimen is incidental — this card is about the class.
Scope, stated precisely
Carried verbatim from the run, because the specimen is easy to over-escalate:
Scope, stated precisely: this is not an authorization bypass. The unfiltered read is still RLS-constrained, so the caller sees only rows they may see (admin sees all because admin may see all). The defect is that a declared personal-scope filter silently never applies — and that a typo'd key is accepted without complaint. The second half is the more general problem.
Root cause — two layers, both open for this block family
1. The schema does not judge the bag.PageComponentSchema.properties is z.record(z.string(), z.unknown()). PageComponentSchema itself has been .strict() since ADR-0089 D3a, but strictness does not recurse: it closes the component node's own keys and leaves everything under properties unjudged. This is documented, not accidental — see packages/spec/src/stack.zod.ts:817 ("Containers nest through a loose record") and the header of the lint below.
2. The gate that exists cannot see this block. ADR-0078 / #5068 added packages/lint/src/validate-component-props.ts, which dispatches on the component's type and judges properties against that type's props schema. Two reasons it is silent here:
It is warning-only by design, pending an inventory — its own header explains why ("Wiring the parse is the precondition for enforcement, not the enforcement").
More decisively, it skips unregistered types silently:
const schema = PROPS_SCHEMAS[type];
if (!schema) continue; // "Unregistered type — skipped silently … the majority
// of authored nodes are SDUI blocks this map does not carry."
And ComponentPropsMap (packages/spec/src/ui/component.zod.ts:987-1033) carries 31 entries, none of them an object-bound block: page:*, record:*, nav:*, global:*, ai:* and element:* are declared; object-grid, object-metric, object-kanban, object-calendar and the rest of the object-* family are absent. The string object-grid does not appear anywhere in packages/spec.
So for the block family that carries the platform's data-bound authoring surface, both layers are open: the schema passes the bag through, and the lint has nothing to dispatch.
Stale-premise check: re-verified on objectstackorigin/main (00e9196) — the passthrough record, the silent-skip branch, and the 31-entry map without any object-* key are all present as described.
Why this is filed as a finding rather than a fix
The direction is a real choice and it touches a published surface:
A — declare the object-* blocks in ComponentPropsMap. Gives the existing lint something to dispatch and gets did-you-mean suggestions for free (filters → filter is exactly the rename shape the walker already emits). Cost: the spec takes on props schemas for blocks whose implementation lives in objectui, and the two must then be kept in parity — the same coupling check:react-declaration-parity already manages for other surfaces.
B — leave the map as-is and close the loop on the objectui side, where the block's inputs are declared. objectui#4041 already did this for object-grid specifically (the plural spelling was retired and a spelling pin added), but that is per-block work with no gate making it exhaustive.
Neither should be picked by an implementer without a ruling — recorded here so the decision is made once for the whole block family rather than one typo at a time.
Reproduction
Author any page whose component is an object-* block and add an undeclared key inside properties (e.g. filters: on object-grid).
Build / publish — no error, no warning: PageComponentSchema parses the bag, and validateComponentProps skips the type.
Render the page — the key is spread onto the renderer and ignored there.
Control: put the same undeclared key one level out (on the component node rather than inside properties) — ADR-0089 D3a strictness rejects it, proving the gate exists and stops at the bag's edge.
Source
Extracted from the QA run #7695 (framework 92f26f7, console 09987b680).
Symptom
An author writes a misspelled key inside a page component's
properties, and nothing anywhere complains: the spec parses it clean, it is stored, the renderer ignores it, and the author gets a success receipt for configuration that does nothing.The live specimen from this run is the showcase's personal work queue (#7750):
object-gridauthored withfilters:(plural) where the renderer readsfilter. The wire carried no$filterand the grid listed every row. But the specimen is incidental — this card is about the class.Scope, stated precisely
Carried verbatim from the run, because the specimen is easy to over-escalate:
Root cause — two layers, both open for this block family
1. The schema does not judge the bag.
PageComponentSchema.propertiesisz.record(z.string(), z.unknown()).PageComponentSchemaitself has been.strict()since ADR-0089 D3a, but strictness does not recurse: it closes the component node's own keys and leaves everything underpropertiesunjudged. This is documented, not accidental — seepackages/spec/src/stack.zod.ts:817("Containers nest through a loose record") and the header of the lint below.2. The gate that exists cannot see this block. ADR-0078 / #5068 added
packages/lint/src/validate-component-props.ts, which dispatches on the component'stypeand judgespropertiesagainst that type's props schema. Two reasons it is silent here:And
ComponentPropsMap(packages/spec/src/ui/component.zod.ts:987-1033) carries 31 entries, none of them an object-bound block:page:*,record:*,nav:*,global:*,ai:*andelement:*are declared;object-grid,object-metric,object-kanban,object-calendarand the rest of theobject-*family are absent. The stringobject-griddoes not appear anywhere inpackages/spec.So for the block family that carries the platform's data-bound authoring surface, both layers are open: the schema passes the bag through, and the lint has nothing to dispatch.
Stale-premise check: re-verified on
objectstackorigin/main(00e9196) — the passthrough record, the silent-skip branch, and the 31-entry map without anyobject-*key are all present as described.Why this is filed as a finding rather than a fix
The direction is a real choice and it touches a published surface:
object-*blocks inComponentPropsMap. Gives the existing lint something to dispatch and gets did-you-mean suggestions for free (filters→filteris exactly the rename shape the walker already emits). Cost: the spec takes on props schemas for blocks whose implementation lives in objectui, and the two must then be kept in parity — the same couplingcheck:react-declaration-parityalready manages for other surfaces.inputsare declared. objectui#4041 already did this forobject-gridspecifically (the plural spelling was retired and a spelling pin added), but that is per-block work with no gate making it exhaustive.component-props-unknown-keyremains gated on the inventory SDUI 组件 props 没有解析闸门:PageComponent.properties是开放 record,ComponentPropsMap的 29 个站点从不被 parse(#4001 批 17 的 no gate 判定) #5068 describes.Neither should be picked by an implementer without a ruling — recorded here so the decision is made once for the whole block family rather than one typo at a time.
Reproduction
object-*block and add an undeclared key insideproperties(e.g.filters:onobject-grid).PageComponentSchemaparses the bag, andvalidateComponentPropsskips the type.properties) — ADR-0089 D3a strictness rejects it, proving the gate exists and stops at the bag's edge.Source
Extracted from the QA run #7695 (framework 92f26f7, console 09987b680).