Skip to content

finding: element:number.filter declares the arm 'array', but ElementNumberProps.filter accepts only a record/object — the manifest gate clears a value spec refuses #6206

Description

@yinlianghui-tw

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

authoritywhat 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'].filterrecord | 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 outElementNumberProps.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 blockelement: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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepm:blocked

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions