feat(spec)!: element:record_picker.filter converges onto the ViewFilterRule array form — the last record-form filter in ComponentPropsMap (objectui#6206 Option B) - #15456
Conversation
…rray form Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
📓 Docs Drift Check4 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 129 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 23fc1608aee7ffde0764783ada71fcf88c36ce5c && git checkout 23fc1608aee7ffde0764783ada71fcf88c36ce5c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c3b6da4bca58e7d275ee0f81e5bd58c2591df093 87df844749c503bafb2c473ca96fdf690546cefe && git checkout -B drift-repro c3b6da4bca58e7d275ee0f81e5bd58c2591df093 && git merge --no-ff 87df844749c503bafb2c473ca96fdf690546cefe
node scripts/docs-audit/affected-docs.mjs --json c3b6da4bca58e7d275ee0f81e5bd58c2591df093 |
Clause ② in-seat review — verdict ADOPTED VERBATIM from an isolated contract-tier reviewerAdoption record by the Why an isolated reviewer rather than this seat. Downgrade-fuse reading before acting (⛔ a self-asserted tier is not a reading): Transcript tier verification (the precondition for adopting at all):
Reviewed-by: session_01G4138K1EG7kQ81FNba5Kp4 — isolated contract-review subagent (contract-review tier; same parent session as the implementing subagent, so independence here is by withheld context, not by a separate session id) Contract review — PR #15456 (head
|
| # | Change | Where | Verdict |
|---|---|---|---|
| J1 | ComponentPropsMap['element:record_picker'].filter: FilterConditionSchema.optional() → z.array(ViewFilterRuleSchema).optional() |
packages/spec/src/ui/component.zod.ts :2184 (base) → :2213 (head) |
RIGHT |
| J2 | TS type ElementRecordPickerProps['filter']: FilterCondition | undefined → ViewFilterRule[] | undefined (follows J1) |
same | RIGHT |
| J3 | New export ElementRecordPickerPropsParsed (ADR-0122 D5) |
component.zod.ts:2298; ui/index.ts:48 export * from './component.zod' |
RIGHT |
| J4 | FilterConditionSchema import removed from component.zod.ts |
:4 (base) |
RIGHT — no public-surface change (data/filter.zod still exports it) |
| J5 | ADR-0122 pin Iso819 deleted, count 826 → 825 |
type-alias-convention.pin.test.ts |
RIGHT |
| J6 | ADR-0087 D3 semantic entry element-record-picker-filter-rule-array under step 18 + regenerated registry.ts |
migrations/entries/semantic/18.element-record-picker-filter-rule-array.ts; registry.ts:6418-6464 |
RIGHT in shape and registration; WRONG in two statements of fact (C1) |
| J7 | Changeset @objectstack/spec: minor, feat(spec)!:, BREAKING body, <!-- adr-0087: registered … --> |
.changeset/record-picker-filter-rule-array.md |
RIGHT level (§2); WRONG in two statements of fact (C1, C2) |
| J8 | Regenerated followers: content/docs/references/ui/component.mdx, api-surface/ui.json, export-origins/ui.json; docs/protocol-upgrade-guide.md and spec-changes.json unchanged |
— | RIGHT |
| J9 | One fixture re-spelled (component.test.ts:1670) + 5 new pins (acceptance, real ViewFilterRuleSchema, refusal envelope, cross-door parity, whole-map census) |
component.test.ts:1816-1919 |
RIGHT, and not vacuous |
| J10 | .describe() text at the key (published into the reference docs) |
component.zod.ts:2214 |
acceptable; one inherited overclaim noted under C1 (optional) |
Basis, per judgment
J1 — the accept set is exactly the ruled one, no wider and no narrower. The construction is byte-for-byte the sibling construction: no .min(), no .default(), .optional() — compare element:number :1851 and record:related_list :1121. Refusal of the record form is invalid_type, expected: 'array', located at filter (pinned; see J9). Nothing else in the file moved: the head's filter: census is
$ git show origin/pr-15456:packages/spec/src/ui/component.zod.ts | grep -nE '^\s+filter: z\.(array|unknown)'
1121 z.array(ViewFilterRuleSchema) record:related_list
1157 z.array(ViewFilterRuleSchema) its nested Add-affordance picker
1851 z.array(ViewFilterRuleSchema) element:number
2213 z.array(ViewFilterRuleSchema) element:record_picker ← this PR
2456 z.unknown() object-grid
2613 z.unknown() object-metric
2657 z.unknown() object-kanban
2712 z.unknown() object-calendar
The measurement-first clause of the ruling is satisfied, re-derived by symbol at the pin (objectui 00d3f09c):
packages/components/src/renderers/basic/record-picker.tsx:108const filter = composed?.filter ?? props.filter;→:135if (filter) query.$filter = filter;→:138await adapter.find(object, query).readProps(:68) is a spread;filter?: unknown(:82).packages/data-objectstack/src/index.ts:2423async find(…)→:2452this.convertQueryParams(params)→:3846-3851: an array$filtergoes totranslateFilterArray(:348), an object toconvertFiltersToAST.translateFilterArray→isObjectFilterEntryForm(:295, keys onfield) →objectFilterEntriesToAST(:309) →objectFilterEntryToAST(:280) →normalizeFilterOperator(:146) overFILTER_OPERATOR_ALIASES, which maps 18 of the spec's 20VIEW_FILTER_OPERATORS(view.zod.ts:243-248) to server symbols;is_empty/is_not_emptyare absent from the table and fall through?? opraw — the same path everyListView.filterrule array already takes, so no new operator surface is introduced by this PR.- Nothing on that path parses props against the installed spec:
git grep -n -E 'ComponentPropsMap|ElementRecordPickerPropsSchema|ElementRecordPickerProps\b' 00d3f09c -- 'packages/*/src/**' 'apps/*/src/**'minus tests minus comment lines → 0 hits. - Contrast with the
element:numbertrap:aggregate()(:5002) learned array lowering only on objectui#6828;find()has it. The card's fork therefore resolves to converge, and the PR chose correctly.
J2 — readers of the type outside packages/spec at head: scripts/measure-partial-retirement-annotation.mjs:167-169 (three retired keys, not filter); objectui at the pin: comments only. No reader left behind.
J3 — the XParsed is warranted, not a D3 synonym. Compile-time probe, run against the head tree with the pin file's own Eq/Assert helpers (type-alias-convention.pin.test.ts:71-75):
probe-picker.ts Assert<Eq<z.input<typeof ElementRecordPickerPropsSchema>, z.infer<…>>> → error TS2344 (non-isomorphic)
probe-number.ts same on ElementNumberPropsSchema (the precedent) → error TS2344 (positive control)
probe-control.ts same on RecordHighlightsField (Iso820, still pinned) → compiles (negative control)
node scripts/check-spec-parsed-alias.mjs → 1516 bare z.input aliases, 825 pinned isomorphic, 691 paired with an XParsed. OK.
J4 — git show origin/pr-15456:packages/spec/src/ui/component.zod.ts | grep -n FilterConditionSchema → :1840, :2189, both inside comments; negative control (a symbol that must not exist) → 0; base had :4 import, :1841 comment, :2184 entry.
J5 — the count is recomputed from source by the test and asserted (expect(pins).toHaveLength(825)); check:test-typecheck OK (54 files / 261 errors / 145 pinned signatures, unchanged).
J6 — pnpm --filter @objectstack/spec gen:migration-registry on the head → git diff | wc -l = 0 (byte-identical regeneration; 153 semantic, 89 retired-key, 97 retired-def). check:migration-registry OK. node scripts/check-adr-0087-registration.mjs --base be2594e66 --head HEAD → [BREAKING+bang] registered element-record-picker-filter-rule-array (new here). migrations.test.ts green. No concurrent registry change was dropped: git diff --stat be2594e66..origin/main -- packages/spec content/docs/references .changeset shows only .changeset/sandbox-writeback-entry-snapshot-normalised.md (+30). Step-18 placement is consistent with PROTOCOL_VERSION = '17.0.0' (kernel/protocol-version.ts:18) and with the 25 sibling 18.* entries including the precedent. The two false statements are itemised in C1.
J8 — my own build and regeneration on the head:
pnpm --filter @objectstack/spec build → exit 0
pnpm --filter @objectstack/spec check:generated → "All 15 generated artifacts are up to date"
(migration-registry, spec-changes, upgrade-guide, meta-url-spelling, skill-docs, skill-refs,
react-blocks, authorable-surface + JSON schemas, api-surface, export-origins, declaration-map,
docs, strictness-ledger, liveness, test-typecheck)
git status --short | wc -l → 0
"Unchanged by construction" is correct and derivable: migrations/chain.ts:36-44 composeMigrationChain selects m > fromMajor && m <= toMajor with toMajor = PROTOCOL_MAJOR = 17, so no step-18 entry projects into the guide or spec-changes.json (precedent id: 0/0 hits; this id: 0/0 hits).
J9 — the pins fail when the fix is reverted. Ablation in the scratch worktree: git show be2594e66:packages/spec/src/ui/component.zod.ts > …/component.zod.ts (blob 7c12868… → 0fb17a6…, record-form line count 0 → 1), then vitest run src/ui/component.test.ts src/type-alias-convention.pin.test.ts → 6 failed / 244 passed: the five new cases plus should accept full record_picker props. Restore via git checkout HEAD -- <path> → blob 7c12868…, git diff HEAD | wc -l = 0. At head the four named files pass: Test Files 4 passed · Tests 436 passed. The census pin is not vacuous: lazySchema (shared/lazy-schema.ts:21) is a Proxy that forwards .shape, and the test guards its own probe (expect(doors).toEqual(expect.arrayContaining([the three doors]))) before asserting holdouts is empty.
J10 — the precedence sentence "the binding-level dataSource.filter wins outright when both are set" is true at the pin (composed?.filter ?? props.filter, record-picker.tsx:108).
2. Semver levelling
Declared: "@objectstack/spec": minor with feat(spec)!:, a BREAKING body, and the ADR-0087 marker. This is the correct level. The repo forbids major during the launch window — scripts/check-changeset-no-major.mjs (header: the lockstep fixed group means one major promotes ~70 packages; "During the launch window we ship breaking changes as minor"); I ran it against the merge base: ✓ This diff introduces no major bump. ADR-0087's ratified launch-window exemption covers the window only, never the chain, and the chain step exists (J6). Precedent: packages/spec/CHANGELOG.md:1282 — 17.3.0 shipped element-number-filter-rule-array as minor under the same wording. What the diff does to the published surface: one accept-set change at one key (record form out, rule array in), one TS type change at that key, one additive type export. patch would be wrong (accept-set change); major is barred by the guard. Level I would set: minor, as declared.
3. Boundary flags — every flag, caveat, narrowing and open question, answered or escalated
- F1 — Card step 2 fork (converge vs. consumer card first). ANSWERED: converge. Measured independently at the pin (J1); the
find()door lowers rule arrays; no refusal stands between an authored array and the query. - F2 — Triage: does the
FilterConditionSchemaimport leave with the entry? ANSWERED: yes (J4). - F3 — Claim:
registry.tsnext writer after feat(spec,hono-server): retire SessionUser.language — /auth/me/localization is the one read face for the user's language (#14788) #15386, never hand-merged. ANSWERED: regeneration is byte-identical andmainhas not touchedpackages/specsince the merge base (J6). - F4 — Claim:
content/docs/references/**shared with in-flight [finding]@exampleand@categorytag lines render verbatim on 13 published reference pages — a tag WITH a payload needs a rewrite, not the@moduledrop #14455. ANSWERED conditionally:git log --oneline origin/main | grep '#14455'→ not landed. If it lands first,bash scripts/pm/os-regen-merge.shplus regeneration is owed at enqueue (C5). - F5 — PR H3: the four
object-*doors arez.unknown(), filed as finding(spec): fourobject-*filterdoors inComponentPropsMapdeclarez.unknown()— no orthography at all — so they still accept the MongoDB-style record ui#6206-B retired, silently; the card that closed the lastFilterConditionSchemadoor assumed they declared the array #15449. ANSWERED and TURNED INTO A CONDITION: the finding is correct (J1 census), finding(spec): fourobject-*filterdoors inComponentPropsMapdeclarez.unknown()— no orthography at all — so they still accept the MongoDB-style record ui#6206-B retired, silently; the card that closed the lastFilterConditionSchemadoor assumed they declared the array #15449 exists (opened 16:08Z), but the PR's own shipped texts assert the opposite (C1). - F6 — PR:
ElementDataSourceSchema.filter(page.zod.ts:169) is still the record form, filed as finding(spec):ElementDataSourceSchema.filter(page.zod.ts) still declares the MongoDB-styleFilterConditionSchema— the binding-level sibling of #14406 outsideComponentPropsMap, while objectui's composition seam lowers three shapes and its own pins author the tuple array #15442. ANSWERED: after this PR one node carries two orthographies through one??; at the pin the seam types the binding'sfilterasunknownandmergeFilterNodeslowers all three shapes (core/src/data-scope/element-data-source.ts:69-78, :247-251), so it is not a runtime defect; the narrowing toComponentPropsMapis the card's and triage's, not the PR's; finding(spec):ElementDataSourceSchema.filter(page.zod.ts) still declares the MongoDB-styleFilterConditionSchema— the binding-level sibling of #14406 outsideComponentPropsMap, while objectui's composition seam lowers three shapes and its own pins author the tuple array #15442 exists. Not a reason to refuse. ESCALATE to triage as a census item, not to this PR:FilterConditionSchemaalso declaresfilter/runtimeFilteratdashboard.zod.ts:387,:720,dataset.zod.ts:190,:288,report.zod.ts:239,:316— outside the map and outside finding(spec):ElementDataSourceSchema.filter(page.zod.ts) still declares the MongoDB-styleFilterConditionSchema— the binding-level sibling of #14406 outsideComponentPropsMap, while objectui's composition seam lowers three shapes and its own pins author the tuple array #15442. Whether ui#6206-B's "platform-wide" reaches data-scope and report filters, or those are the wireFilterConditionby design, is a fork neither this PR nor I may settle. - F7 — objectui#7663 (registry
inputs.filtertype: 'object', parity pins assert the record form). ANSWERED with a downstream condition (C4). The issue exists (opened 15:58Z) and carries a Blocked-by line. - F8 — "NOT MEASURED, declared for CI" (
check:skill-examples,check:doc-formula-expressions,check:doc-security-posture,check:react-declaration-parity). ANSWERED by CI:Lint & Repo Gatescompleted success at 16:50:58Z.check:react-declaration-paritygenuinely cannot run in this repo (check:generatedprints "Cannot run here — needs MANIFEST=<sdui.manifest.json>"); objectui's own registry comment stateselement:record_pickeris not inPUBLIC_BLOCKS(record-picker.tsx:334-335at the pin). - F9 —
check:docs-audit-scopered, root-caused to.examples-build*residue, filed finding(tooling): a prerequisite-abortedcheck:skill-examplesrun leaves gitignored.examples-build*trees underpackages/specthatcheck:docs-audit-scope's self-test walks and reds — two gates, one box, order-dependent #15457. ANSWERED: finding(tooling): a prerequisite-abortedcheck:skill-examplesrun leaves gitignored.examples-build*trees underpackages/specthatcheck:docs-audit-scope's self-test walks and reds — two gates, one box, order-dependent #15457 exists; not this diff; the CI gate family is green. - F10 — Post-Task Add Changesets and GitHub Actions automation #4 / Console Pin Gate skipped. ANSWERED: the gate's path filter is
.objectui-sha, the build-console scripts andci.yml(ci.yml:105-112), so a spec-only change never triggers it; when it runs it builds the Console SPA at the pinned SHA (scripts/build-console.sh) and does not execute objectui's vitest; objectui's non-test source names the type only in comments and the renderer typesfilterasunknown, so a later pin bump does not go red on this change. - F11 — H2 (pin vs. objectui
main), "EMPTY over six files with a non-empty control leg". ANSWERED WITH A CORRECTION: one of the six paths,packages/core/src/data-scope/useElementDataSource.ts, exists in neither tree (git cat-file -e 00d3f09c:<path>→ does not exist; the six-file diff is empty, so it is not atmaineither) — that leg is vacuous. Re-derived on the real files:packages/react/src/hooks/useElementDataSource.ts,packages/react/src/element-data-source/ElementDataSourceGate.tsx,packages/core/src/utils/filter-converter.ts(definesmergeFilterNodes/toFilterNode) → 0 diff lines each; the other five named files → 0; control legpackages/core/src→ 7 files, +530/−214;git merge-base --is-ancestor 00d3f09c origin/main→ exit 0. The conclusion ("no safe-at-main / refused-at-pin split; chore(console): bump the objectui pin —.objectui-sha(00d3f09c) predates objectui#7477'snormalizeListViewSchemadata.provider: 'object'fold, which #14791 step 3 cannot land without #15422 is not a dependency") holds; the stated evidence did not (C3). - F12 — Docs Drift Check: two changed files yielded no anchor. ANSWERED: both are generated followers (
api-surface/ui.json,export-origins/ui.json); hand-written docs name the element without afiltershape (content/docs/ui/pages.mdx:187), so there is nothing to drift. - F13 — Ruling authority. The ruling's literal subject is
element:number; the card applies the ruling's own stated principle ("one filter orthography platform-wide") to the picker, and triage (2026-09-02) graded that as execution, not a reopening. ANSWERED: within the ruling's words; recorded here so a maintainer can object explicitly rather than by omission. - F14 —
needs:contract-reviewhung on PR and card together. VERIFIED on both label sets. - F15 — Changeset: "
os migrate meta --from 18lists it". WRONG (C2).composeMigrationChain(chain.ts:36-44) selectsm > fromMajor && m <= toMajor: a step-18 entry is listed by--from 17once the protocol major is 18, and by--from 18never (until 19). Also: until the 18 cut nothing lists it at all — true of the precedent and of all 2518.*entries; that is the launch-window state, not this PR's defect. - F16 — Checks at write time. Every completed run is
success(Build Core, Build Docs, Test Core 2–6, Dogfood 1–3 + rollup, Temporal Conformance, Type Check ×5, Lint & Repo Gates, Spec property liveness, Check Changeset, Governed Surface Queue Guard, single-writer and single-claim guards, Part-of guard, Documentation Links);Console Pin Gate,Check PR Size(dup),Packed-tarball smoke(opt-in) skipped by filter;Test Core (1/6)still in progress (C6).
Conditions
- C1 (edit + regenerate, before the PR leaves draft). Correct the false census in shipped text; the head tree has three array-declared
filterdoors other than the picker (record:related_list:1121, its nested picker:1157,element:number:1851— two of them top-level map entries) and fourz.unknown()doors (:2456, :2613, :2657, :2712), which the PR's own finding(spec): fourobject-*filterdoors inComponentPropsMapdeclarez.unknown()— no orthography at all — so they still accept the MongoDB-style record ui#6206-B retired, silently; the card that closed the lastFilterConditionSchemadoor assumed they declared the array #15449 and the census test's comment state correctly:packages/spec/src/migrations/entries/semantic/18.element-record-picker-filter-rule-array.ts:13names "theobject-*blocks" among the array-declaring siblings;:24says "six siblings declared theViewFilterRulearray". Thenpnpm --filter @objectstack/spec gen:migration-registry(projections atregistry.ts:6426,:6437) andcheck:generated.packages/spec/src/ui/component.zod.ts:2187— the same "theobject-*blocks" claim in the docblock..changeset/record-picker-filter-rule-array.md:17-18— "every sibling declaredz.array(ViewFilterRuleSchema)".- Optional in the same pass: the inherited
.describe()phrase "everyfilterinput in this map shares" (:2214; the precedent at:1852says the same and was accepted as-is).
- C2 (edit, before the PR leaves draft).
.changeset/record-picker-filter-rule-array.md:30:--from 18→--from 17(listed once the protocol major is 18), or drop the command claim (F15). - C3 (record). A PR comment correcting the H2 file list: the
useElementDataSourcehook lives atpackages/react/src/hooks/useElementDataSource.ts(+ElementDataSourceGate.tsx), identical pin↔main (F11). No code change. - C4 (downstream, objectui). The objectui PR that installs the spec release carrying this change must carry objectui#7663 —
record-picker.tsx:344-370type: 'object'→'array'with the description rewritten, and the parity pins atrecord-picker-inputs-spec-parity.test.ts:163-171, :187-193re-pointed — because those pins parse against the installed spec and go red at unlock. Nothing in this repo's.objectui-shaflow is affected (F10). - C5 (enqueue-time). If [finding]
@exampleand@categorytag lines render verbatim on 13 published reference pages — a tag WITH a payload needs a rewrite, not the@moduledrop #14455 lands first:bash scripts/pm/os-regen-merge.shand re-regeneration ofregistry.tsandcontent/docs/references/**before enqueue (F4). - C6 (enqueue-time).
Test Core (1/6)must complete green (F16).
PASS WITH CONDITIONS.
Generated by Claude Code
…e; migrate command listed by --from 17 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
…cord-picker-filter-orthography
CI red on
|
|
C3 — correction of the H2 evidence record (no code change). The PR body's H2 line named six read-path files by basename and did not prove, per path, that each exists in BOTH trees. That was the real gap:
Control leg: The conclusion stands as the review re-derived it: no "safe at main, refused at the pin" split on this read path, and #15422 is not a dependency of this card. The Generated by Claude Code |
…; regenerate followers after merging main Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4
Fixes #14406
Draft,
CONTRACT_REVIEW_TIER, Clause ②: this PR changes what the published contract ACCEPTS atelement:record_picker.filter.needs:contract-reviewis hung on this PR and on the card together with this reviewable increment; the seat reviews in-seat first — not flipped ready, not enqueued, not auto-merged.R3 verdict: the read path takes the array form end to end — converge
Maintainer ruling objectui#6206-B (2026-08-25, verbatim 「同意」): one filter orthography platform-wide. After #12039 Key 2 (PR #14404) converged
element:number,ComponentPropsMap['element:record_picker'].filterwas the lastfilterinput in the map still declared as the MongoDB-styleFilterConditionSchemarecord. Triage fixed the sequencing: measure the consumer BEFORE moving the declaration, because theelement:numberruntime refused raw rule arrays until objectui#6828. So step 1 was measured, at the pin and at objectuimain, by symbol not by line — and the array form is taken end to end, so this PR executes the last entry.Step 1 readings (H4) at the pin
00d3f09c500c4a45b5f27aca8af80349412faaf1(=.objectui-shaonorigin/main)Read-only over the sibling checkout with
git show PIN:path/git grep PIN; objectui was not edited.packages/components/src/renderers/basic/record-picker.tsx:108—const filter = composed?.filter ?? props.filter;→:135if (filter) query.$filter = filter;→:138await adapter.find(object, query). The door isfind(), notaggregate().packages/data-objectstack/src/index.ts:3846-3851(ObjectStackAdapter.convertQueryParams, thefind()route): an ARRAY$filteris lowered throughtranslateFilterArray; an object goes throughconvertFiltersToAST.translateFilterArray(:348) recognises[{ field, operator, value }, ...]viaisObjectFilterEntryFormand lowers each rule to an AST tuple (objectFilterEntriesToAST,['and', ...]for several). Contrastaggregate()(:5002), which only learned this on objectui#6828 — the reason theelement:numberconvergence had to be consumer-first;find()has had it since objectui#6302.packages/core/src/data-scope/element-data-source.ts:69-78, :247-251— the composition seam typesfilterasunknownbecause three shapes reach it (record object, AST array,ViewFilterRule[]) andmergeFilterNodeslowers all three;packages/components/src/__tests__/record-picker-element-data-source.test.tsx:35,72,80already ship array-shaped filters through the picker's$filter.propertiesagainst the installed@objectstack/spec:readProps(renderers/basic/readProps.ts) is a spread; non-test source namesElementRecordPickerPropsSchema/ElementRecordPickerPropsonly inside comments (git grep -w, exit 1 on non-comment lines). So the pin's npm spec (^17.0.0, which refuses arrays at this key) never judges the authored value at render time.packages/objectql/src/engine-filter-array-lowering.test.tspins that Door 2 lowers the tuple array to aFilterConditionbefore the driver — the same door every list view's stored rule array already takes.inputs.filterastype: 'object'(record-picker.tsx:344-370), so a JSX page writing the array gets atype-mismatchdiagnostic — severitywarning(sdui-parser/src/validate.ts:246), andrenderers/layout/page.tsx:553acts onerroronly, so the page still renders and filters. That, plus the two parity pins (record-picker-inputs-spec-parity.test.ts:163-171, :187-193) that assert the record form, flip when a released spec reaches the pin — filed as objectui#7663 with aBlocked-by:line.H2 — pin vs objectui
main(f7cf7e8, fetched into an owned ref)git merge-base --is-ancestor PIN mainexit 0 (self-proving direction).git diff --stat PIN mainoverrecord-picker.tsx,useElementDataSource.ts,element-data-source.ts,data-objectstack/src/index.ts,sdui-parser/src/validate.ts,record-picker-inputs-spec-parity.test.ts: EMPTY; control leg overpackages/core/src: 7 files changed, so the empty diff is a real "identical", not a shallow artifact. No "safe at main, refused at the pin" split on this read path; #15422 (pin bump) is not a dependency of this card.H3 — census, re-derived with controls
FilterConditionSchemaincomponent.zod.tsonbe2594e66: the import (:4), a comment in theelement:numberdocblock (:1841), and exactly onefilterentry (:2184, insideElementRecordPickerPropsSchema). Negative control (a symbol that must not exist) exit 1; positive controlViewFilterRuleSchemacount 5. Triage's narrow claim holds. Two corrections to the card's wider prose, filed rather than folded: the fourobject-*doors declarefilter: z.unknown()— no orthography — not the array (#15449); andElementDataSourceSchema.filter(page.zod.ts:169, the other operand of the same??) is still the record form (#15442).plugin-list/data-listhave noComponentPropsMaprow at all.H1 — located by symbol
ElementRecordPickerPropsSchema→ itsfilterkey sat at:2184onbe2594e66(the card measured:2114, triage:2141); the entry, not the line, was edited.What changed
packages/spec/src/ui/component.zod.ts—filter: z.array(ViewFilterRuleSchema).optional()with the measurement recorded in its docblock; theFilterConditionSchemaimport at:4leaves with the entry (R4: its remaining mentions in the file are comments only);ElementRecordPickerPropsParseddeclared (ADR-0122 —ViewFilterRuleSchema'soperatornormalizes on parse, so input and infer diverge).packages/spec/src/type-alias-convention.pin.test.ts— theIso819pin deleted, count 826 → 825, ledger entry written.packages/spec/src/ui/component.test.ts— the one in-repo author of a record-form pickerfilter(a fixture,:1670) re-spelled to the array (fixture triage: it used the record form, it did not pin it); a new block pins acceptance, the realViewFilterRuleSchema(operator normalization, value shape atfilter.0.value), the refusal envelope (invalid_type,expected: 'array', located atfilter; operator-object and$andspellings too), cross-door parity withelement:numberandrecord:related_list, and a whole-map census by shape ("no top-levelfilterdoor refuses the rule array").packages/spec/src/migrations/entries/semantic/18.element-record-picker-filter-rule-array.ts— the ADR-0087 D3 entry;packages/spec/src/migrations/registry.tsregenerated bygen:migration-registryfromentries/(this PR is the next writer after feat(spec,hono-server): retire SessionUser.language — /auth/me/localization is the one read face for the user's language (#14788) #15386; never hand-merged)..changeset/record-picker-filter-rule-array.md—@objectstack/specminor(launch-window convention for a BREAKING accept-set change), FROM → TO mapping, and theadr-0087: registered element-record-picker-filter-rule-arraymarker.content/docs/references/ui/component.mdx,packages/spec/api-surface/ui.json,packages/spec/export-origins/ui.json.docs/protocol-upgrade-guide.mdandspec-changes.jsonare unchanged by construction: the guide renders majors up toPROTOCOL_MAJOR(17) and this is a major-18 entry — theelement-number-filter-rule-arrayprecedent is absent from both for the same reason (grep -c0/0).record_pickerfollowed by afiltervalue within 15 lines acrossexamples/,content/docs/,docs/,skills/,packages/**,apps/**,.changeset/(generated trees excluded) — ONE author, the spec fixture above; zero outside the spec package.skills/**untouched, so no governed surface.Verification (union run at
25633c27d, the final commit)bash scripts/pm/os-verify-lock.sh -c 'pnpm --filter @objectstack/spec build'—VERDICT command-exit 0(twice: before and after the test edits, becausegen:api-surfacerefuses a dist older thansrc).pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ui/component.test.ts src/type-alias-convention.pin.test.ts src/migrations/migrations.test.ts src/ui/page.test.ts—Test Files 4 passed (4) · Tests 436 passed (436),VERDICT command-exit 0.pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-component-props.test.ts src/component-field-specs-liveness.test.ts src/validate-page-field-bindings.test.ts— 37 + 45 passed after building@objectstack/formulaand@objectstack/sdui-parser(closures 2 and 1),VERDICT command-exit 0.pnpm --filter @objectstack/spec typecheck—VERDICT command-exit 0(tsc, scripts, and the test layer undertsconfig.test.json; the debt ledger unchanged at 54 files / 261 errors / 145 pinned signatures — the build config excludes*.test.ts, so coverage of the edited tests is the test-layer program, nottsc --noEmit)../component.zod, nodiston that path). First attempt was a NO-OP — theBASEfile carriedBASE=shaandgit restore --sourcefailed to resolve; the anchored counts did not move (record-form line 0→0, migration-id 2→0 expected, got 2→2) and that green is void. Redo with the bare sha: mutation proven on disk (record-form line 0→1, migration-id mentions 2→0, blob7c12868…→0fb17a6…); direction: RED, 6 failed / 241 passed — exactly the five new cases plus the re-spelled fixture; restore viagit checkout HEAD -- path, proven bygit diff HEAD0 lines, clean status, and blob equal to the HEAD blob7c12868….pnpm --filter @objectstack/spec check:generated—✓ All 15 generated artifacts are up to date; individually exit 0:check:api-surface,check:docs,check:authorable-surface,check:spec-changes,check:upgrade-guide,check:migration-registry,check:export-origins,check:liveness,check:strictness-ledger,check:dual-source-exports,check:exported-any,check:entry-nameability,check:browser-reachable-entries,check:llms-txt,check:skill-refs,check:empty-state,check:variant-docs,check:test-typecheck.node scripts/pm/dispatch-gates.mjs(no paths; from the merge base), exit 0 each:check:nul-bytes,check:spec-parsed-alias,check:adr-0087-registration,check:changeset-gate-self-tests,check:objectui-changeset,check:pm-half-states,check:merge-driver,check:test-source-alias,check:cross-package-test-inputs,check:objectql-double-limit,check:corpus-claim-drift,check:quick-reference-counts,check:page-declaration-shape,check:slot-lookup,check:type-source-resolution,check:published-files,check:docs-single-h1,check:doc-anchors,check:doc-authoring,check:docs-redirects,check:role-word,check:skill-identifier-liveness,check:react-page-adapter-contract,check:logger-receiver-detach,check:query-options-erasure.check:docs-audit-scope— exit 1 on first run, root-caused and NOT this diff: its self-test walkspackages/**and admitted the gitignored.examples-build*trees a failedcheck:skill-examplesrun had left underpackages/spec; with those moved aside the self-test passes (568 cases), with them restored it fails again (control). Residue deleted afterwards.check:skill-examples(needs@objectstack/client-reactdist),check:doc-formula-expressionsandcheck:doc-security-posture(exit 3PREREQUISITE NOT MET:@objectstack/lintdist; its closure is 36 packages),check:react-declaration-parity(EXTERNAL_INPUT_REQUIRED;element:record_pickeris not inPUBLIC_BLOCKS, so it is not insdui.manifest.jsoneither).eslint --no-inline-config --format jsonover the 5 edited TS files → 5 files linted, 0 errors, 0 warnings; the config is not type-aware (eslint.config.mjs:328says so: noparserOptions.project, no typed rules), so this diff cannot move any untouched file's verdict.node scripts/pm/git-history.mjs count --days=14 --ref=origin/main --path=packages/spec/src/ui/component.zod.ts→ 25 (answered, floor 2026-07-29 predates the window).Landing notes
filterasunknown; the Console Pin Gate bundles THIS tree's spec, and nothing it builds types againstElementRecordPickerProps['filter'].merge=os-regenresources:registry.tsregenerated fromentries/on this base;content/docs/references/**regenerated.origin/mainhas moved by twotest(scripts)commits sincebe2594e66with zero overlap on this surface; if [finding]@exampleand@categorytag lines render verbatim on 13 published reference pages — a tag WITH a payload needs a rewrite, not the@moduledrop #14455 lands first,bash scripts/pm/os-regen-merge.shplus re-regeneration is owed before enqueue.packages/spec/src/data/filter-text-conformance.ts/FILTER_TEXT_CASES(driver-memory's reference matcher answers$notContainsNO for every valued NON-STRING row — the live mingo path answers YES #14079) —git diff --statshows nopackages/spec/src/data/**.$notContainsNO for every valued NON-STRING row — the live mingo path answers YES #14079 is not addressed here; finding(spec):ElementDataSourceSchema.filter(page.zod.ts) still declares the MongoDB-styleFilterConditionSchema— the binding-level sibling of #14406 outsideComponentPropsMap, while objectui's composition seam lowers three shapes and its own pins author the tuple array #15442, finding(spec): fourobject-*filterdoors inComponentPropsMapdeclarez.unknown()— no orthography at all — so they still accept the MongoDB-style record ui#6206-B retired, silently; the card that closed the lastFilterConditionSchemadoor assumed they declared the array #15449 and objectui#7663 remain open as the filed follow-ups.Generated by Claude Code