Found while implementing #4971 (the declared-arm ⊆ spec-accepted gate). Filed unassigned, not claiming. Not fixed in that PR — #4971's dispatch is explicit that the accept set and every declaration stay unchanged, so this is carried there as a reasoned, issue-backed exemption pointing here.
The divergence
| authority | what it says about element:number.filter |
|---|
packages/components/src/renderers/basic/elements.tsx:472 (the registration) | { name: 'filter', type: 'array', label: 'Filter' } — declared array |
@objectstack/spec@17.2.0ComponentPropsMap['element:number'].filter | record | object, .describe('Filter criteria') — an array is refused |
elements.tsx:375-451 (the renderer) | opaque passthrough: filter?: unknown, handed to adapter.aggregate({ …, filter }) and to adapter.find(object, { $filter: filter }) — it commits to no shape at all |
Measured on the installed pin, key-scoped so the required siblings do not speak for it:
ElementNumberProps.safeParse({ object: 'a', aggregate: 'count', filter: [] })
→ [{"expected":"record","code":"invalid_type","path":["filter"], …},
{"expected":"object","code":"invalid_type","path":["filter"], …}]
ElementNumberProps.safeParse({ object: 'a', aggregate: 'count', filter: {} }) → success
Why it matters (the #4971 harm, live on this key)
sdui-parser's checkType clears filter={[…]} because the declared arm says array; sdui.manifest.json and the generated sdui-intrinsics.d.ts publish it as legal; os validate / os build is the first layer to refuse it. declared = enforced inverts silently — the platform's own manifest recommends a write the contract rejects (AGENTS.md #0.1's second dialect).
Which side is wrong is a CONTRACT question, not an implementation detail
The repo is uniform in the opposite direction: every other filter input in the tree is declared 'array' — object-grid.filter (plugin-grid/src/index.tsx:218, "Filter criteria in JSON-rules form"), object-metric.filter (plugin-dashboard/src/index.tsx:207), record:related_list.filter (plugin-detail/src/index.tsx:489, spec ViewFilterRule entries), plugin-list, data-list. Their spec entries accept anything (z.any()), so only element:number — whose spec entry is a typed record — collides.
So the two readings are:
- (a) the spec entry is the odd one out —
ElementNumberProps.filter should take the same ViewFilterRule array form every other block's filter takes, and the fix is upstream in @objectstack/spec (this repo does not edit the spec — AGENTS.md #0); - (b) the declaration is wrong on this one block —
element:number really does want a record-shaped filter, and the input should be declared 'object' (and the description should say so), diverging from every sibling filter surface.
Nothing in-tree settles it: the renderer is an opaque passthrough and expresses no preference, and there is no element:number filter fixture either way. Needs a maintainer/upstream decision rather than a guess.
How to re-derive
sed -n '472p' packages/components/src/renderers/basic/elements.tsx
pnpm exec vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts # the #4971 arm gate; entry `element:number.filter:array`
Deleting the exemption entry in registry-inputs-spec-parity.test.ts is what closes this card — the gate's stale-exemption test demands it the moment either side moves.
Found while implementing #4971 (the declared-arm ⊆ spec-accepted gate). Filed unassigned, not claiming. Not fixed in that PR — #4971's dispatch is explicit that the accept set and every declaration stay unchanged, so this is carried there as a reasoned, issue-backed exemption pointing here.
The divergence
element:number.filterpackages/components/src/renderers/basic/elements.tsx:472(the registration){ name: 'filter', type: 'array', label: 'Filter' }— declared array@objectstack/spec@17.2.0ComponentPropsMap['element:number'].filterrecord | object,.describe('Filter criteria')— an array is refusedelements.tsx:375-451(the renderer)filter?: unknown, handed toadapter.aggregate({ …, filter })and toadapter.find(object, { $filter: filter })— it commits to no shape at allMeasured on the installed pin, key-scoped so the required siblings do not speak for it:
Why it matters (the #4971 harm, live on this key)
sdui-parser'scheckTypeclearsfilter={[…]}because the declared arm says array;sdui.manifest.jsonand the generatedsdui-intrinsics.d.tspublish it as legal;os validate/os buildis the first layer to refuse it.declared = enforcedinverts silently — the platform's own manifest recommends a write the contract rejects (AGENTS.md #0.1's second dialect).Which side is wrong is a CONTRACT question, not an implementation detail
The repo is uniform in the opposite direction: every other
filterinput in the tree is declared'array'—object-grid.filter(plugin-grid/src/index.tsx:218, "Filter criteria in JSON-rules form"),object-metric.filter(plugin-dashboard/src/index.tsx:207),record:related_list.filter(plugin-detail/src/index.tsx:489, specViewFilterRuleentries),plugin-list,data-list. Their spec entries accept anything (z.any()), so onlyelement:number— whose spec entry is a typed record — collides.So the two readings are:
ElementNumberProps.filtershould take the sameViewFilterRulearray form every other block's filter takes, and the fix is upstream in@objectstack/spec(this repo does not edit the spec — AGENTS.md #0);element:numberreally does want a record-shaped filter, and the input should be declared'object'(and the description should say so), diverging from every sibling filter surface.Nothing in-tree settles it: the renderer is an opaque passthrough and expresses no preference, and there is no
element:numberfilter fixture either way. Needs a maintainer/upstream decision rather than a guess.How to re-derive
Deleting the exemption entry in
registry-inputs-spec-parity.test.tsis what closes this card — the gate's stale-exemption test demands it the moment either side moves.