Repository navigation
[decision] filter-builder's two operator vocabularies: which one is the truth — and it must settle objectui#6349's two parked name-authority rows #9306
Description
Ruled: 5852274286 · letter A · 2026-09-27T03:24Z
Decision needed. Ruled: item 4's components half executes as A (comment 5852274286); this card is the execution card in pm:queue. Filed by the domain:ui PM seat (os-tesla) at R32, to keep four records alive that would otherwise go quiet.
Why this card exists at all
objectui#7561 was narrowed by triage to a single-site repair (the Select's identity comparison going through normalizeFilterOperator). That repair is delivered in PR objectui#9305 and it carries Fixes #7561 — so merging it closes the only card where the vocabulary question is written down.
⭐ The delivering seat spotted this and raised it rather than merging over it. It is right, and the lossy part is specific: a closed card appears in no open-queue census, so the records below stop being discoverable by anyone sweeping for open work.
⇒ this card is the vocabulary question's home. PR objectui#9305 keeps Fixes #7561 — that repair really is complete, and calling a finished deliverable "Part of" would misrepresent it.
The question
filter-builder has two operator vocabularies and they are very nearly disjoint — the overlap is three members (equals, contains, in).
FilterOperatorSchema(packages/types/src/zod/complex.zod.ts) — the spec's canonical snake_case set:equals,not_equals,contains,not_contains,starts_with,ends_with,greater_than,greater_than_or_equal,less_than,less_than_or_equal,in,not_in,is_null,is_not_null.- The dropdown (
packages/components/src/custom/filter-builder.tsx,defaultOperators) — camelCase plus capabilities the spec set has no name for:equals,notEquals,contains,containsCaseInsensitive,notContains,isEmpty,isNotEmpty,greaterThan,lessThan,greaterOrEqual,lessOrEqual,before,after,between,in,notIn,startsWith,endsWith,isNull,isNotNull,exists,notExists.
Three candidate directions, none local:
- The mirror follows the dropdown (camelCase). Matches what renders and what
onChangeemits back to the host — but forks from@objectstack/spec'sVIEW_FILTER_OPERATORS, which the component itself imports and folds through. - The dropdown follows the mirror (canonical snake_case). Contract-first — but rewrites the ids every stored filter carries and every consumer of
foldFilterGroupToSpecRulesreads. - The mirror accepts both dialects, the way the component's own arity fold already does.
⛔ The hard constraint on direction 3, recorded twice and still binding
Direction 3 conflicts directly with this repo's cited spec ruling on objectui#7379 (contains vs icontains). AST_OPERATOR_MAP's comment says an operator "must never be folded onto" its sibling — "That is a semantic boundary, not two spellings of one thing."
⇒ whoever rules the vocabulary must read these two together, or the repo ends up with opposite rulings on adjacent surfaces.
⭐ Four records this card carries forward
- The three directions above, with their costs.
- The capability-bearing dropdown ids with no spec twin —
containsCaseInsensitive,isEmpty,isNotEmpty,before,after,between,exists,notExists.⚠️ The delivering seat counted seven; a naive set difference gives eight, because some of these arguably fold (before/afterontoless_than/greater_than,exists/notExistsontois_not_null/is_null). ⛔ I am not resolving that here — whoever rules must restate the membership explicitly, because "which of these are real capabilities" is half the decision. Under direction 2 these ids lose their names. - ⭐⭐ objectui#6349's two parked name-authority rows.
FilterBuilderConditionandFilterGroupare declared in both@object-ui/typesand@object-ui/components; 46 exported type names carry a second authority — 42 of them are named by no family card #6349 batch 3 (PR fix(components,plugin-grid,app-shell): one authority for ComboboxOption, NamedActionDef and OrgTranslate (objectui#6349 batch 3) #7691) tried to converge them, stopped and reported instead, and the rows were parked on objectui#7561 "because converging the name retypesoperator— so whatever is ruled must settle those two rows too." ⇒ this is an acceptance criterion of the vocabulary ruling, not a related note. Verified at source in two independent comments on objectui#7561 (5548770124,5560863302). - objectui#7993's input, which points at direction 2: the spec treats the camelCase spellings as the DEPRECATED alias form, with
less_thancanonical.
Measured, so the decision is not made in the abstract
The blank-trigger symptom is already fixed by PR objectui#9305 — ⛔ do not treat urgency as an input here. What remains is which vocabulary is the truth.
⭐ MUST-CHECK answered: objectstack #15442 / #15449's filter-converge ruling does NOT cover this surface. Triage asked for a check rather than a conclusion and the seat reported a reading rather than a verdict. ⇒ no upstream ruling pre-empts this one.
⛔ What this card is not
- ⛔ Not a re-opening of PR objectui#9305's narrowed repair. That is complete, correct and reviewed separately.
- ⛔ Not objectui#9302 (
needsValueInputreads the operator raw against the exported valueless set) — same class, different site, its own card. - ⛔ Not a claim that any direction is cheap. All three are contract-shaped.
Refs: objectui#7561 (the home this replaces) · PR objectui#9305 (the delivered narrowing) · objectui#7379 (the must never be folded onto constraint) · objectui#7993 (camelCase is the deprecated alias form) · objectui#6349 (the two parked name-authority rows) · objectui#6939 / PR objectui#7560 · objectui#9302 · objectstack#15442 / #15449 (checked, not covering)
Generated by Claude Code
Activity
os-tesla commentedon Sep 13, 2026
Ruling recorded — 2, as a director-seat class-one adjudication: the filter-builder dropdown speaks the protocol's operator set; camelCase is the deprecated alias accepted on read and rewritten on write; no spec card is filed for the ids the protocol does not name (director seat, decision batch #129 item 2, 2026-09-13)
Maintainer, verbatim (live PM chat, reply received 2026-09-13T07:0xZ), to decision batch #129 presented as
1A·2(2)·3A·4(1)·5S: 「你是项目总监,9306 这种为什么你不直接决策呢?而且不是说要以协议为准吗?还需要立卡问 spec 多次一举啊。这个也需要更新skills。其他同意。」The correction is taken as written, twice over. First, this card should not have been presented: route 2 pulls a consumer surface back to the protocol's declared set, which is the class-one side of the charter's mechanical boundary (
SKILL.md:395「拉回已声明契约 ⇒ 代裁车道」;:400-403the three criteria — ① the standing ruling 「以 objectstack 协议为准」 and objectui#7993's recorded reading that the camelCase spellings are the DEPRECATED alias form decide the direction; ② the failure direction is a refused / normalised id, one revert away; ③ no floor moves). Second, the seat's proposed spec card asking whether the protocol should gainbetween/is_empty/icontainswas redundant: the protocol is the answer; a capability it does not name is not offered.What is ruled
defaultOperatorsinpackages/components/src/custom/filter-builder.tsxemits theFilterOperatorSchemaids (equals,not_equals,contains,not_contains,starts_with,ends_with,greater_than,greater_than_or_equal,less_than,less_than_or_equal,in,not_in,is_null,is_not_null) and nothing else. The mirror type follows.normalizeFilterOperatoraccepts the camelCase spellings as deprecated aliases (objectui#7993) and the builder writes the canonical id back — a lossless read-side conversion, ⛔ not a second vocabulary.before/after→less_than/greater_thanandexists/notExists→is_not_null/is_null(same semantics, folded on read);between,isEmpty,isNotEmpty,containsCaseInsensitiveare removed from the dropdown — zero measured pull, and objectui#7379's semantic boundary forbids folding a case-insensitive contains ontocontains. ⛔ No spec card. If a customer ever needs one of them, the card is filed then, with the pull attached.@object-ui/typesdeclaration withoperatortyped as the protocol set — an acceptance criterion of this card.needsValueInputreads the raw operator) is unchanged and lands after 1.State
needs-user-decision→pm:queue;domain:uikept;priority:p3added (the user-visible symptom is already fixed by PR objectui#9305; this is contract alignment). Recorded for the maintainer's 追认表 as a class-one adjudication.Generated by Claude Code
os-tesla commentedon Sep 13, 2026
Ruling amended — item 3 of 5651809976 was wrong: the protocol ALREADY names every capability the dropdown offers; nothing is removed, and objectui's own 14-member
FilterOperatorSchemais the stale mirror (director seat, 2026-09-13T07:03Z)Maintainer, verbatim (live PM chat, 2026-09-13T07:0xZ), minutes after the ruling above: 「协议就是答案,但是协议是否正确也是需要你考虑的,从平台长远合理性的角度考虑,并参考主流相关平台」
Acting on that, the seat read the protocol instead of the card's copy of it.
packages/spec/src/ui/view.zod.ts:243VIEW_FILTER_OPERATORSis 20 members, ⛔ not 14:The 14-member
FilterOperatorSchemathis card quotes lives in objectui'spackages/types/src/zod/complex.zod.ts— it is objectui's mirror, six members behind the protocol. ⇒ Every dropdown id has a protocol twin, and the seat's "removebetween/isEmpty/isNotEmpty/containsCaseInsensitive" was a mistake made by not reading the protocol before ruling on it. Item 3 is replaced as follows; items 1, 2, 4, 5 stand with the set corrected to the 20.Corrected mapping (22 dropdown ids → protocol)
equals→equals·notEquals→not_equals·contains→contains·containsCaseInsensitive→icontains·notContains→not_contains·isEmpty→is_empty·isNotEmpty→is_not_empty·greaterThan→greater_than·lessThan→less_than·greaterOrEqual→greater_than_or_equal·lessOrEqual→less_than_or_equal·before→before·after→after·between→between·in→in·notIn→not_in·startsWith→starts_with·endsWith→ends_with·isNull→is_null·isNotNull→is_not_null·exists→is_not_null·notExists→is_null(the last two are the only ids with no twin of their own; they fold to the null checks). objectui#7379's boundary is honoured by construction:containsandicontainsstay two operators.The protocol-correctness check the maintainer asked for
Judged against mainstream filter UIs (director-seat memory pointers, ⛔ not measured): Airtable and Notion offer is empty / is not empty and a case-insensitive contains as the default; Dataverse FetchXML has
between,begins-with,ends-with,null/not-null,in; Salesforce list-view filters have equals / not equal / contains / starts with / less or greater than. The protocol's 20 cover that set and keep the null-vs-empty and case-sensitive-vs-insensitive distinctions explicit rather than collapsed. ⇒ The protocol is right as it stands; nothing to add, nothing to remove.Execution (replaces item 1 and 3)
@object-ui/types'FilterOperatorSchemabecomes the protocol's set — importVIEW_FILTER_OPERATORSfrom@objectstack/spec/uirather than a local literal, so the mirror cannot fall behind again (the same reasoning the protocol's own docblock at:257-268gives forisMultiOperator).defaultOperatorsemits the 20 protocol ids; the camelCase spellings remain readable as deprecated aliases (item 2, unchanged).pm:queue/domain:ui/priority:p3unchanged.Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
Claim: PM loop round 1 —
domain:uiexecution seat (one dispatch with objectui#9559, as ratification5814267184orders: 「the two dispatch together」)Session:
session_01BA3nKVUwKQJf8DBxrSVtNCBranch:
claude/issue-9306-filter-builder-protocol-ids(amended; the joint branchclaude/issue-9559-filter-operators-protocol-setmerged as PR objectui#10465)Worktree:
objectui-issue-9306Domain:
domain:uiSeat:
domain:ui#1File surface:
packages/types/src/zod/complex.zod.ts(FilterOperatorSchemaderived from@objectstack/spec/uiVIEW_FILTER_OPERATORSwith the spec's alias normalisation, and the TS mirror type that follows it),packages/components/src/custom/filter-builder.tsx(defaultOperatorsemits the protocol ids; camelCase read as a deprecated alias and written back canonical), the operator-label i18n keys only if the dropdown's labels are keyed by operator id, tests beside each, and the changesets (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(default judgement tier) —priority:p3: a consumer mirror pulled back to the protocol, ruled and ratifiedClause-②: yes
Thread-read: 5651829933
Serial constraints cleared: objectui#7715's PR objectui#10421, the last holder of
complex.zod.ts, merged at 2026-09-25T02:07Z. open-PR file lists read 2026-09-25T02:17Z ⇒ none touchescomplex.zod.tsorfilter-builder.tsx. Livepm:dispatchedclaims read at the same time ⇒ none names them. objectui#9695 and objectui#9302 are closed.Scope
The rulings
5651809976and5651829933(amended; items 1, 2, 4 and 5 stand with the set corrected to the protocol's 20): the dropdown speaks the protocol's operator set, and camelCase is the deprecated alias accepted on read and rewritten on write.Clause-②: yes: the mirror's accept set widens toward the protocol (14 → 20 plus aliases). A review-tier contract review runs before enqueue.containsCaseInsensitive→icontains) as a bare card in objectstack. ⛔ It is not fixed in objectui.operatortyped as the protocol set) is carried.⇒ closed by the same PR (
Fixes #9306in its body).Amended by the seat (the dropdown half, dispatched after PR objectui#10465 merged)
PR objectui#10465 (
1779e8dee) delivered item 1's mirror and item 4's types half. The dropdown half's file surface is now:packages/components/src/custom/filter-builder.tsx(defaultOperators,OPT_IN_OPERATORSand its docblock, and the builder's ownFilterBuilderOperatorexport) and itsfilterBuilder.operators.*label keys in every locale;packages/fields/src/widgets/FilterConditionField.tsx(condToMongo,kvToCondition,FILTER_CONDITION_EXTRA_OPERATORS,TEXT_COMPARAND_OPERATORS);packages/app-shell/src/views/metadata-admin/inspectors/datasetFilterCondition.ts(OP_TO_MONGO,MONGO_TO_OP,VALUELESS_TO_MONGO) andpackages/app-shell/src/views/metadata-admin/widgets.tsx(SPEC_TO_FB);packages/plugin-view/src/config/view-config-utils.ts(CANONICAL_TO_BUILDER);components,fields,plugin-list,plugin-viewandapp-shell;Serial constraints re-cleared: PR objectui#10455 (
datasetFilterCondition.ts) and PR objectui#10450 (widgets.tsx) have both merged. The open-PR file lists and live claims read at the time of this amendment name none of these files.exists/notExistsfollow ruling B (5748934939, ratified): they stay opt-in with no fold (seat state record5826294523).Amended again by the seat (the surface breach, answered with A)
The dropdown half's surface now also names:
packages/app-shell/src/views/drillUrlFilters.ts(NULL_FILTERlabel keys) and its four pins;packages/app-shell/src/views/ObjectView.tsxsanitizeViewOverrideONLY (the operator fold before the value-less lookup), withObjectView.overlayPairValue. This is hunk-disjoint from objectui#10035's remount region;viewFilterFold.ts,corefilter-converter.ts,filter-icontains-alignment-8976.test.ts,typesfilter-operator-protocol-set-9559.test.tsandexamples/schema-catalogfilter-builder-operator-alias-trigger-7561.test.tsx;@object-ui/i18nif its packs change.⛔ Not
plugin-listListView.tsx(PR objectui#10510, the objectui#10326 claim) and ⛔ notfilter-builder.mdx(PR objectui#10515).icontainsleavesOPT_IN_OPERATORS(the seat decision on this card).domain:uiseat #1 ·session_01BA3nKVUwKQJf8DBxrSVtNC· claim · amended 2026-09-25T06:20Zobjectstack-fleet commentedon Sep 25, 2026
State record from the
domain:uiseat #1 (session_01BA3nKVUwKQJf8DBxrSVtNC), 2026-09-25T03:41Z. The claim5825621947stays: this card is still in flight under it. ⛔ No label, assignee or grade changes.What landed from the joint dispatch. PR objectui#10465 (card objectui#9559,
Fixes #9559only) carries this card's item 1 (the mirror accepts the protocol set and its aliases) and the types half of item 4 (FilterBuilderOperatoris the spec'sViewFilterOperator). It is in contract review, and this card stays open.Item 2 (the dropdown emits the protocol ids) stopped, correctly, on a file-surface breach. The dev measured before editing. The builder's camelCase ids are read literally by consumer tables outside the claim's surface:
@object-ui/fieldsFilterConditionField.condToMongo: its default arm would store a canonicalgreater_than_or_equalrow as an equality in sharing-rule criteria, silently.@object-ui/app-shelldatasetFilterConditionOP_TO_MONGO: it drops unmapped rows.@object-ui/plugin-viewCANONICAL_TO_BUILDERand app-shellSPEC_TO_FBmap onto camelCase ids, and parity pins read them.The seat's answer (a sequencing and file-surface call, not a product ruling): option A. Item 2 is dispatched again under this claim after PR objectui#10465 merges. The file surface will be amended here first, naming
componentsfilter-builder.tsxand its operator label keys,fieldsFilterConditionField.tsx,app-shelldatasetFilterCondition.tsandwidgets.tsx,plugin-viewview-config-utils.ts, and their pins, each with a changeset.exists/notExists: ruling B governs. It is the later text and maintainer-ratified (5748934939, ratified5814267184): 「exists/notExistsremain opt-in with their named upstream blocker」. So they stay opt-in builder ids, offered only toFilterConditionField, and ⛔ are not folded tois_not_null/is_null. The fold in the amendment5651829933is superseded on this point.containsCaseInsensitive→icontains: filed as objectstack-ai/objectstack#20092, as the ruling orders. It carries the spec table's own "measured legacy spelling" rule as a premise to check first.domain:uiseat #1 · state record · 2026-09-25T03:41Zobjectstack-fleet commentedon Sep 25, 2026
Serial wait, recorded by the
domain:uiseat #1 (session_01BA3nKVUwKQJf8DBxrSVtNC), 2026-09-25T04:26Z. The claim5825621947stays. ⛔ No label, assignee or grade changes.PR objectui#10465 merged at 2026-09-25T04:13Z (
1779e8dee, verified onmain), which clears this card's first serial constraint. The dropdown half's surface then meets two more holders:datasetFilterCondition.ts, has merged. Clear.domain:uiseat Add default props to all components to prevent collapse in designer #4, draft) holdspackages/app-shell/src/views/metadata-admin/widgets.tsx. The dropdown half must rewrite that file'sSPEC_TO_FB. Waiting on it.When objectui#10450 merges or releases that file, the seat amends this claim's file surface (as listed in
5826294523) and dispatches the dropdown half.domain:uiseat #1 · serial wait · 2026-09-25T04:26Zobjectstack-fleet commentedon Sep 25, 2026
os-dev-report
{
"issue": 9306,
"status": "blocked",
"branch": "claude/issue-9306-filter-builder-protocol-ids",
"pr": null,
"session": "session_01BA3nKVUwKQJf8DBxrSVtNC (subagent: the parent PM session, as the harness stamps it)",
"premise_still_valid": true,
"summary": "STOPPED ON A FILE-SURFACE BREACH, as the dispatch orders. Two consumers OUTSIDE the claim's amended surface need edits, and both are measured red. (1) app-shell
drillUrlFilters.tsreuses the builder's label keys by literal:NULL_FILTER.labelKeyisfilterBuilder.operators.isNullandnotLabelKeyisfilterBuilder.operators.isNotNull. ObjectDataPage renders them throught(textKey)on the drill chip. The dropdown's labels ARE keyed by operator id (toffilterBuilder.operators.plusop.value), so Zone 2 item 5 applies. Re-keying the family in all ten packs makes that chip render the raw key, andObjectDataPage.filterChipI18n-9159.test.tsxgoes red: it receives the key string itself.pnpm check:i18n-keysstays green (exit 0) because it does not read object-literal keys, so only that render pin sees it. (2) app-shellObjectView.tsxsanitizeViewOverrideasksVALUELESS_FILTER_OPERATORS.has(String(operator))RAW. That set (inviewFilterFold.ts) is the builder'sVALUELESS_FILTER_BUILDER_OPERATORSplus four canonical spellings. Once the builder's set is canonical, the deprecatedisEmpty/isNotEmpty/isNull/isNotNulldrop out of it. A STORED view override row{ operator: 'isEmpty', value: '' }is then dropped by the recovery pass as incomplete, so a stored filter loses a row, which breaks ruling item 2 (stored camelCase keeps loading).ObjectView.overlayPairValue.test.tsgoes red. Neither file was edited. The in-surface SOURCE half is done and pushed as a labelled checkpoint,37a2e607fon the claim branch; no PR was opened. What it changes: componentsdefaultOperatorsemits the 20VIEW_FILTER_OPERATORSids plus the opt-inexists/notExists(ruling B, not folded). The builder folds a stored spelling at its read boundary through the spec'snormalizeFilterOperatorplus ONE local row, and never emits on read. fieldscondToMongo/kvToCondition/TEXT_COMPARAND_OPERATORS/FILTER_CONDITION_EXTRA_OPERATORSand app-shellOP_TO_MONGO/MONGO_TO_OP/VALUELESS_TO_MONGOare keyed on the protocol ids. widgetsSPEC_TO_FBand plugin-viewCANONICAL_TO_BUILDERare removed: one had become the spec's own fold, the other an identity. The staleOPT_IN_OPERATORSdocblock sentence is corrected, and the label keys are re-keyed in all ten packs with every value untouched. NOT done: the in-surface pin updates, the 22-id census, the load-and-save-back pin, the ablations and the changesets. Zone 2 item 2, measured on the installed@objectstack/spec17.4.0:normalizeFilterOperator('containsCaseInsensitive')returns its input unchanged, and the controlgreaterOrEqualgivesgreater_than_or_equal. So the one local row is needed; the code names objectstack#20092 beside it. A route choice for the PM to see (open question 2): with the dropdown's id spelledicontains, plugin-list's offered-equals-expressible spine measures it expressible on BOTH list dialects and withheld, so the spine goes red. I took the in-surface arm the gate names, which is also the plan recorded inOPT_IN_OPERATORS' own last paragraph: drop the entry. I also bridgedicontainsto$icontainsin the dataset inspector (in surface). Other notes: the card carriespriority:p3where the dispatch says p2. The assignee arrived as os-bill and was left untouched. The claim5825621947names this branch.","tests": "Measured on the checkpoint tree (source changed, pins not yet updated), from the repo root through os-verify-lock (slot objectui-9306-dev). I ran
pnpm exec vitest run --reporter=dotover the 66 test files that name a builder operator id, a builder export, a consumer table or the drill label (git grep derived the list). Result:Test Files 19 failed | 47 passed (66),Tests 77 failed | 1766 passed (1843), VERDICT command-exit 1, which is expected on a checkpoint. The 19 red files, in surface (17): components filter-builder-opt-in-operators (3), -operator-alias-trigger-7561 (2), -field-switch-value (2), -field-switch-operator (2) and -valueless-canonical-spelling-9302 (1); fields FilterConditionField.operators (30) and .betweenBlankBound-9914 (1); plugin-list list-offered-operator-expressible-parity (3: its probeValue is keyed on 'notIn' and one pin names isNull literally, while the icontains spine itself holds), convertFilterGroupToAST.canonicalSpelling (2) and convertFilterGroupToAST (1); plugin-view view-operator-builder-parity (8); app-shell datasetFilterCondition .nullOperators-9363 (7), .dateRoundTrip-9382 (4), .unmappedInert-9372 (3), .test (3) and .readHalfHolds-10257 (1); app-shell viewFilterFold.emptyValue (1: it pins that the builder's valueless set holds no canonical spelling). Out of surface (2 tests, 2 source files behind them): ObjectDataPage.filterChipI18n-9159 (2) behind drillUrlFilters.ts, and ObjectView.overlayPairValue (1) behind ObjectView.tsx sanitizeViewOverride. The 47 green files include filter-builder-operator-locale-parity, both schema-catalog operator pins, the types mirror pins, the core and data-objectstack pins, all-locales-key-parity and en-zh-key-parity.pnpm check:i18n-keysexit 0: 2813/2813 literal keys resolve, and the dynamic family is read from defaultOperators' value field, so the re-key is seen.pnpm check:control-bytesOK. A control-byte grep over the 15 changed files found no hits. tsc --noEmit on components, fields and plugin-view src WITHOUT the dependency closure built: every error is TS2307 module-not-found or its knock-on, and none sits on a line this change wrote. That is NOT MEASURED as a type-check, because the closure was not built. Also NOT MEASURED, all stopped at the breach: the 22-id census pin, the camelCase load-and-save-back pin, ablations (a) and (b), type-check of any package, eslint per-rule counts, check:new-line-citations, check-changeset-presence and check:changeset-claims.","mcp_calls": "0 - no MCP GitHub tool was called, read or write. Reads went through REST curl via the proxy.",
"api_writes": "1 REST write: this os-dev-report comment, POST /repos//issues/9306/comments, sent via post-stamped.mjs --repo=objectstack-ai/objectui. Plus 2 git pushes of the claim branch (the empty write probe, then checkpoint 37a2e60), which are not REST writes. No pr_create (no PR at a breach), zero label writes, no PATCH.",
"open_questions": [
{
"question": "Surface amendment (a surface and sequencing call, PM discretion). The dropdown half cannot land green without edits in two app-shell files the claim does not name:
src/views/drillUrlFilters.ts(the NULL_FILTER label keys) andsrc/views/ObjectView.tsxsanitizeViewOverride(raw value-less lookup), orsrc/views/viewFilterFold.tsVALUELESS_FILTER_OPERATORSin its place. How should it proceed?","options": [
"A - Amend the claim's surface and re-dispatch on the same branch from 37a2e60. Add drillUrlFilters.ts:
labelKey/notLabelKeybecomefilterBuilder.operators.is_null/is_not_null, with their pins ObjectDataPage.filterChipI18n-9159, drillUrlFilters.test, drillEmptyBucketNavHost-9085 and drillEmptyBucketEscapeHatch-9159 (the last three assert the literal and stay green, so they are stale rather than red). Add ObjectView.tsx: sanitizeViewOverride folds the row's spelling through the spec's normalizeFilterOperator before the value-less lookup, the same both-sides fold objectui#9302 / #9359 used, with its pin ObjectView.overlayPairValue. The remaining work is then the in-surface pin updates, the census, the ablations, the changesets (minor on components, fields, app-shell, plugin-view and i18n) and the PR.","B - Do not re-key the labels: keep camelCase message keys and map each protocol id to its old label key inside the builder. That is a second copy of the camelCase vocabulary. It also needs scripts/check-i18n-call-site-keys.mjs's
filterBuilder.operators.vocabulary declaration changed (out of surface too). It does not remove breach (2).","C - Keep the deprecated camelCase spellings inside VALUELESS_FILTER_BUILDER_OPERATORS so ObjectView's raw lookup still matches. The set would then state ids the dropdown does not draw (the in-surface pin filter-builder-valueless-operators refuses that), and it is a second vocabulary in a published export."
],
"recommendation": "A. Both edits are two to three lines each, forced by ruled work: item 2 requires stored camelCase to keep loading, and item 5 (as dispatched) requires the labels re-keyed. B and C each keep a second vocabulary alive, against ruling item 2's 'not a second vocabulary'."
},
{
"question": "Route choice taken at the checkpoint, open to PM veto: is the case-insensitive contains an ordinary operator now, or still opt-in? Measured: once the dropdown's id is the protocol's
icontains, plugin-list's list-offered-operator-expressible-parity spine finds it expressible on BOTH list dialects. VALID_AST_OPERATORS has it (isFilterAST of the field / icontains / x triple is true), ViewFilterRuleSchema accepts it, and mapOperator has an arm for it. The spine therefore reds on 'expressible but withheld'. While the id wascontainsCaseInsensitivethe spec's alias table did not fold it, so the gate could not see this.$icontainsis in FILTER_OPERATORS, TEXT_OPERATOR_DOOR_CASES and FILTER_TEXT_CASES.","options": [
"A (in the checkpoint) - Drop
icontainsfrom OPT_IN_OPERATORS. That entry's own last paragraph records this as the planned retirement: 'the entry below is deleted, the parity test flips it back on by itself'. It is then offered on the text bucket to every consumer: the list toolbar, the Studio view filter, the plugin-view config panel, the dataset inspector (bridged to$icontainslike the three text operators objectui#9372 bridged) and the generic renderer. FilterConditionField stops listing it as an extra and still writes$icontains. Four axes. Business: measured pull is FilterConditionField (sharing rules) only; list and view pull is zero measured, but the protocol declares it and both list dialects execute it. Long-term: it executes the recorded self-retirement, and the opt-in's stated reason is false for every list and view consumer. AI-error: one rule, protocol ids offered where the dialect carries them. Non-expansion: it widens the UI offering on four surfaces, with no new spec surface and one dataset bridge row.","B - Keep
icontainsopt-in and grant it only where the gate forces it:LIST_VIEW_EXTRA_OPERATORS = ['icontains']in plugin-list ListView.tsx, which is a third out-of-surface file. The Studio, plugin-view and dataset surfaces keep not offering it, with no dialect reason, which is a withdrawal nothing justifies but also no gate there to say so. Narrower on the non-expansion axis.","C - Keep it opt-in everywhere and exempt it in the list parity spine. Refused: that weakens a gate."
],
"recommendation": "A. The long-term axis leads: the opt-in's premise was measured false, and the file already records deleting the entry as the planned outcome. It stays in surface, and it touches no stored predicate and no security boundary (sharing rules already offer it). B is the narrower fallback if the PM prefers no new offerings; it would add ListView.tsx to the amended surface."
}
],
"out_of_scope_findings": [
"noted, not filed (stale prose, none of class a/b/c; all still green) · the id change makes these sentences false, in files outside the surface. plugin-list ListView.tsx: the LIST_VIEW_EXTRA_OPERATORS docblock says neither list dialect has a case-insensitive contains, which was already false before this change (spec 17.1.0), and the VALUELESS_FILTER_BUILDER_OPERATORS_CANONICAL docblock calls the builder set camelCase. app-shell viewFilterFold.ts: the VALUELESS_FILTER_OPERATORS comments. core filter-converter.ts and filter-icontains-alignment-8976.test.ts name the containsCaseInsensitive builder row. types filter-operator-protocol-set-9559.test.ts: its FILTER_BUILDER_IDS literal claims to spell the builder ids 'today'. carrier: the objectui#9306 continuation, for whichever of these files an amended surface names; the rest 承接者:无.",
"noted, not filed · examples/schema-catalog filter-builder-operator-alias-trigger-7561.test.tsx: its CORRECTION table maps canonical ids onto camelCase 'dropdown ids'. It stays green because the builder folds both spellings, but its 'corrected' arm now carries the deprecated spelling. carrier: the objectui#9306 continuation if its surface is amended to it; otherwise 承接者:无.",
"noted · content/docs/components/complex/filter-builder.mdx lists operators by English label only, so this change forces no doc edit. AGENTS.md #2 would still want the protocol-id vocabulary and the read-side alias rule stated there. carrier: the continuation (content/docs is not in the surface)."
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
Seat decisions on the dropdown half's two open questions, from the
domain:uiseat #1 (session_01BA3nKVUwKQJf8DBxrSVtNC), 2026-09-25T06:21Z. Both are file-surface and sequencing calls inside the non-escalation classes, and both follow text already recorded on this card. ⛔ No label, assignee or grade changes.1. The surface breach: A. The claim's surface is amended in place (
5825621947) to add:packages/app-shell/src/views/drillUrlFilters.ts:NULL_FILTER'slabelKey/notLabelKeyfollow the re-keyedfilterBuilder.operators.is_null/is_not_null, with their pins (ObjectDataPage.filterChipI18n-9159,drillUrlFilters.test,drillEmptyBucketNavHost-9085,drillEmptyBucketEscapeHatch-9159).packages/app-shell/src/views/ObjectView.tsx,sanitizeViewOverrideONLY: the row's operator is folded through the spec'snormalizeFilterOperatorbefore the value-less lookup, the same both-sides fold objectui#9302 / finding(plugin-list): convertFilterGroupToAST reads the operator RAW, so a saved view spelled is_null QUERIES with no filter at all and returns every record #9359 used, with its pinObjectView.overlayPairValue.sanitizeViewOverrideis a separate exported function, so the two are hunk-disjoint.VALUELESS_FILTER_OPERATORScomments inapp-shellviewFilterFold.ts; thecontainsCaseInsensitivebuilder-row mention incorefilter-converter.tsandfilter-icontains-alignment-8976.test.ts; theFILTER_BUILDER_IDSliteral intypesfilter-operator-protocol-set-9559.test.ts; and the CORRECTION table inexamples/schema-catalogfilter-builder-operator-alias-trigger-7561.test.tsx.plugin-listListView.tsx(open PR objectui#10510 and the objectui#10326 claim) andcontent/docs/components/complex/filter-builder.mdx(draft PR objectui#10515). Their stale docblock and vocabulary lines are carried by the seat after both holders land.⇒ Why A, not B or C: B keeps a second camelCase label vocabulary, and C keeps deprecated spellings in a published set. Both contradict item 2's 「⛔ not a second vocabulary」.
2.
icontains: A, drop it fromOPT_IN_OPERATORS. This is not a new product call. It executes the planOPT_IN_OPERATORS' own docblock records: 「The lasting fix … is upstream — the view and AST vocabularies gaining the operator — at which point the entry below is deleted, the parity test flips it back on by itself, and the operator becomes ordinary.」 Upstream has it:VIEW_FILTER_OPERATORSandVALID_AST_OPERATORSboth carryicontains, and both list dialects execute it. The parity spine now reds on "expressible but withheld", which is exactly the flip that sentence predicted.FilterConditionFieldkeeps writing$icontains, and the dataset inspector'sicontains→$icontainsbridge row stays in this PR. It touches no stored predicate and no security boundary.domain:uiseat #1 · decision · 2026-09-25T06:21Z16 remaining items
objectstack-fleet commentedon Sep 27, 2026
Ruling: maintainer · letter A · 「A 撤掉嵌套声明(推荐)」 · 2026-09-27T15:43Z
Recorded by the
domain:uiseat #1 (session_01DuWo5bdP9SdVebamn99GGk,os-steve). ⛔ Not this seat's adjudication: the maintainer answered the box question put to them in this session's chat, choosing the option whose label is quoted above, verbatim. That question carried the fork recorded in5857198401(its options A–D, recommendation A, fallback B).Freshness: the thread was re-read in this act. The newest comment is the box
5857198401; nothing newer exists.Ruled: A — retire nesting at the authority, in one PR.
@object-ui/typesFilterGroup.conditionsbecomes flat condition rows;FilterGroupSchemarefuses a sub-group by name;allowGroups/maxDepthare retired from the types face, the zod mirror andcontent/docs/components/complex/filter-builder.mdx;@object-ui/componentsthen deletes its hand-writtenFilterBuilderCondition/FilterGroupand derives both from@object-ui/types, withoperator(the protocol set plus the opt-inexists/notExists) as the named extension andFilterGroup.idoptionality aligned (premise 2 held). Ruling5852274286's other terms stand unchanged.Execution parameters carried into the dispatch (from the box, overturnable only by a later ruling):
5857198401—operatorandvalueare both named extensions in components;valuekeeps the component's narrower union.packages/components/src/custom/filter-builder.tsx,packages/types/src/complex.ts,packages/types/src/zod/complex.zod.ts, theallowGroups/maxDepthrows offilter-builder.mdx, the source-text pinpackages/types/src/__tests__/filter-builder-condition-id-8415.test.ts, tests,minorchangesets for@object-ui/typesand@object-ui/components.Clause-②: yes⇒ a contract review record before enqueue.State:
needs-user-decision→pm:dispatchedin the next act, with a new claim.Blocked-by:none.domain:uiseat #1 · ruling record · 2026-09-27T15:43ZGenerated by Claude Code
objectstack-fleet commentedon Sep 27, 2026
Claim: PM loop round 1
Session:
session_01DuWo5bdP9SdVebamn99GGkAccount:
os-steve(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-9306-filter-builder-name-authorityWorktree:
objectui-issue-9306Domain:
domain:uiSeat:
domain:ui#1File surface:
packages/components/src/custom/filter-builder.tsx(the hand-writtenFilterBuilderCondition/FilterGroupand the exportedFilterBuilderOperator);packages/types/src/complex.ts(FilterGroup.conditions→ flat rows;FilterBuilderSchema.allowGroups/maxDepthretired);packages/types/src/zod/complex.zod.ts(the same, with the sub-group refused by name); theallowGroups/maxDepthrows ofcontent/docs/components/complex/filter-builder.mdx;packages/types/src/__tests__/filter-builder-condition-id-8415.test.ts; dependant call sites the narrowing breaks; tests beside them;minorchangesets for@object-ui/typesand@object-ui/components(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(TIER_DEFAULT, objectstackscripts/pm/dispatch-gates.mjs:12534at0d3ec47137;--tierrefuses objectui paths)Clause-②: yes
Thread-read: 5857342228
Serial constraints cleared: read 2026-09-27T15:44Z. No open PR touches
filter-builder.tsx,complex.ts,complex.zod.tsorfilter-builder.mdx(open-PR file list re-read in this act); no in-flight claim in this lane names them. The branch holds no commits beyond its base (the first dev stopped before any edit); the dev continues on it.Re-claim after the maintainer's ruling
5857342228(letter A). WhyClause-②: yes: a published@object-ui/typestype and its authoring mirror narrow (a sub-group becomes refused), and a published@object-ui/componentstype narrows.domain:uiseat #1 · claim · 2026-09-27T15:44ZGenerated by Claude Code
objectstack-fleet commentedon Sep 27, 2026
os-dev-report
{
"issue": 9306,
"status": "done",
"branch": "claude/issue-9306-filter-builder-name-authority",
"pr": "#10823",
"session": "session_01DuWo5bdP9SdVebamn99GGk (subagent: the parent PM session, as the harness stamps it)",
"premise_still_valid": true,
"summary": "Ruling 5857342228 (letter A) executed in draft PR objectui#10823, head 71124f2. @object-ui/types: FilterGroup.conditions is flat FilterBuilderCondition[]; FilterGroupSchema refuses a nested sub-group BY NAME (one
customissue at the entry path, message == the published describe ofconditions); FilterBuilderSchema.allowGroups / maxDepth are?: nevertombstones on the TS face and retirementTombstone() on the mirror (the objectui#10611 house pattern); FilterGroupSchema is now memoised (7918 ledger moved). @object-ui/components deletes its hand-written FilterBuilderCondition / FilterGroup and derives both from @object-ui/types (interfaces extending the authority with operator/value resp. conditions omitted), with operator = FilterBuilderOperator (protocol set plus exists/notExists) and value = its own required narrow union (execution parameter i), group id optional (premise 2 held and is now pinned at runtime). The narrowing broke exactly two dependants, found by type-check: plugin-view toFilterGroup (operator asserted at the untyped boundary, output unchanged) and two legacy-spelling casts in a plugin-list test. Docs page, the flipped 8415 source pin, the 10478 and 7918 pins, the parity exclusion reason, two new changesets (types minor; components minor + plugin-view patch) and three append-only Superseded paragraphs on pending changesets whose in-release claims this falsifies (7918, 6349 batch 3, 8415). Zone 2 items 1-3 re-measured and held; item 4 (stored tenant nested groups) NOT MEASURED and said so in the PR risk section. objectui#6349's two parked rows are settled by this PR; no comment was written on objectui#6349.","tests": "All heavy runs through os-verify-lock (slot objectui-9306-dev2); VERDICT lines quoted. types:
vitest run packages/types/at 71124f2 → Test Files 248 passed (248), Tests 5363 passed (5363);pnpm --filter @object-ui/types type-checkat 71124f2 exit 0. components:vitest run packages/components/→ 317 passed | 1 skipped (318), Tests 3113 passed | 17 skipped, VERDICT command-exit 0 (components source identical to HEAD);pnpm --filter @object-ui/components type-checkexit 0;tsc -p tsconfig.test.jsonexit 0 at bf18964. Consumers (downstream of components, the packages that name the changed symbols): buildsturbo run build --filter='@object-ui/components^...'7/7, plugin-list^.../plugin-view^... 15/15, app-shell^... 28/28; type-check fields / plugin-list / plugin-view (first run red: plugin-view toFilterGroup TS2322, plugin-list test TS2352 x2; fixed) then exit 0; app-shell type-check exit 0.vitest run packages/plugin-view/+ 10478 pin + 3 schema-catalog filter-builder tests → 55 files / 578 tests passed;vitest run packages/plugin-list/+ 15 consumer filter tests (fields FilterConditionField*, app-shell datasetFilterCondition*, 9306 census, viewFilterFold*) at bf18964 → 117 files / 1545 tests passed. Ablations via objectstack scripts/ablation-replace.mjs wrap mode (anchor hit x1, blob changed, restore proven blob == HEAD and empty git diff HEAD): A1 mirror union restored → pin 6 failed / 12 passed (5 sub-group refusals + the lit-control half that pins a bad operator at conditions.0.operator, i.e. no union around a row); A2 allowGroups back to boolean → 3 failed / 15 passed; A3 cross-package reverse check, a nested sub-group typed as the AUTHORITY FilterGroup pasted into the components test (its project resolves @object-ui/types through the rebuilt dist .d.ts, paths empty) → tsc TS2353 'logic' does not exist in type 'FilterBuilderCondition', exit 1; restored → exit 0. First A3 attempt was a no-op (replacement re-contained the anchor; tool refused, anchor 1 → 1) and is not cited. Pin after restores 18/18, tree clean. Lint narrowing:eslint --format jsonon the 11 touched TS files → 11 files, 0 errors, 45 pre-existing-class warnings; population read from eslint's own output (none ignored), count from the JSON, invariance: eslint.config.js is not type-aware (no parserOptions.project/projectService) and no custom rule in eslint-rules/ reads the disk. Probe (lock-free, plain node over the built @object-ui/types/zod dist): id-less row in a group → single invalid_union at [value], arm 1 = invalid_type at [conditions,0,id] (the 8415 changeset note).","mcp_calls": "0 — no MCP GitHub tool was called, read or write. Card and PR reads went through REST GET via the proxy.",
"api_writes": "3 REST writes, all through the objectstack scripts/pm tools on the fleet relay (each is one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]): (1) pr_create via fleet-write/dispatch.mjs → POST /repos/objectstack-ai/objectui/pulls (draft) = PR 10823; (2) label-write.mjs --issue 10823 --assign os-steve → POST /repos//issues/10823/assignees (read-back MATCHES; zero label writes, the four path labels are labeler.yml's); (3) this os-dev-report comment via post-stamped.mjs → POST /repos//issues/9306/comments. Plus 4 git pushes of the claim branch (not REST). No PATCH, no card assignee or label write.",
"gates": [
{
"command": "pnpm install (worktree)",
"verdict": "Done in 21.4s using pnpm v10.31.0",
"exit": 0
},
{
"command": "os-verify-lock: turbo run build --filter='@object-ui/components^...' --concurrency=2",
"verdict": "Tasks: 7 successful, 7 total · VERDICT command-exit 0",
"exit": 0
},
{
"command": "os-verify-lock: pnpm --filter @object-ui/types type-check (tsc --noEmit + examples + tsconfig.test.json) at 71124f2",
"verdict": "VERDICT command-exit 0",
"exit": 0
},
{
"command": "os-verify-lock: pnpm --filter @object-ui/components type-check",
"verdict": "VERDICT command-exit 0",
"exit": 0
},
{
"command": "os-verify-lock: turbo run build --filter='@object-ui/plugin-list^...' --filter='@object-ui/plugin-view^...' --concurrency=2",
"verdict": "Tasks: 15 successful, 15 total · VERDICT command-exit 0",
"exit": 0
},
{
"command": "os-verify-lock: pnpm --no-bail --filter @object-ui/fields --filter @object-ui/plugin-list --filter @object-ui/plugin-view run type-check (after the two dependant fixes)",
"verdict": "all Done · VERDICT command-exit 0 (first run: exit 2 plugin-view TS2322 in toFilterGroup, then exit 1 plugin-list TS2352 x2 in a test; both fixed)",
"exit": 0
},
{
"command": "os-verify-lock: turbo run build --filter='@object-ui/app-shell^...' then pnpm --filter @object-ui/app-shell run type-check",
"verdict": "Tasks: 28 successful (15 cached) · type-check VERDICT command-exit 0",
"exit": 0
},
{
"command": "os-verify-lock: pnpm exec vitest run --maxWorkers=2 packages/types/ at 71124f2",
"verdict": "Test Files 248 passed (248), Tests 5363 passed (5363) · VERDICT command-exit 0",
"exit": 0
},
{
"command": "os-verify-lock: pnpm exec vitest run --maxWorkers=2 packages/components/",
"verdict": "Test Files 317 passed | 1 skipped (318), Tests 3113 passed | 17 skipped · VERDICT command-exit 0",
"exit": 0
},
{
"command": "os-verify-lock: vitest run packages/plugin-view/ + filter-condition-value-protocol-rule-10478 + examples/schema-catalog filter-builder-mirror-6939 / operator-vocabulary-6939 / operator-alias-trigger-7561",
"verdict": "Test Files 55 passed (55), Tests 578 passed (578) · VERDICT command-exit 0",
"exit": 0
},
{
"command": "os-verify-lock: vitest run packages/plugin-list/ + 15 consumer filter tests at bf18964",
"verdict": "Test Files 117 passed (117), Tests 1545 passed (1545) · VERDICT command-exit 0",
"exit": 0
},
{
"command": "ablation A1 (FilterGroupSchema conditions back to the union) → vitest the 9306 pin",
"verdict": "6 failed | 12 passed (expected red); restored blob == HEAD, git diff HEAD empty",
"exit": 1
},
{
"command": "ablation A2 (allowGroups back to z.boolean) → vitest the 9306 pin",
"verdict": "3 failed | 15 passed (expected red); restored blob == HEAD",
"exit": 1
},
{
"command": "reverse check A3 (authority-typed nested sub-group pasted in components test) → tsc -p tsconfig.test.json",
"verdict": "TS2353 at the pasted line (expected red); restored; control tsc exit 0",
"exit": 1
},
{
"command": "node scripts/check-changeset-presence.mjs at 71124f2",
"verdict": "11 source file(s) of 4 released package(s) changed, and this change declares 2 changeset(s)",
"exit": 0
},
{
"command": "node scripts/check-changeset-no-major.mjs",
"verdict": "No changeset declares a
majorbump.","exit": 0
},
{
"command": "node scripts/check-changeset-overwrite.mjs",
"verdict": "2 added, 3 modified (append-only Superseded paragraphs, frontmatter unchanged) · report-only case 2",
"exit": 0
},
{
"command": "node scripts/check-changeset-claims.mjs",
"verdict": "report-only; filter-related paragraphs read, the three it made false corrected",
"exit": 0
},
{
"command": "node scripts/check-pending-changeset-literals.mjs",
"verdict": "No test source names a pending changeset.",
"exit": 0
},
{
"command": "node scripts/check-new-cross-file-line-citations.mjs at 71124f2",
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s)",
"exit": 0
},
{
"command": "node scripts/check-control-bytes.mjs + grep -naP control-byte self-scan of the 17 changed files",
"verdict": "check-control-bytes: OK; self-scan 0 hits",
"exit": 0
},
{
"command": "node scripts/check-doc-links.mjs / check-doc-component-types / check-doc-example-ids / check-doc-fence-languages / check-shell-escape-residue / check-test-path-roots",
"verdict": "all OK",
"exit": 0
},
{
"command": "node scripts/check-handler-key-read-sites / check-i18n-call-site-keys / check-spec-symbol-derivation / check-component-surface-parity / check-designer-field-key-parity",
"verdict": "all OK",
"exit": 0
},
{
"command": "node scripts/check-governed-queue-guard.mjs --test (17 changed paths)",
"verdict": "NOT GOVERNED",
"exit": 0
},
{
"command": "eslint --format json on the 11 touched TS files",
"verdict": "11 files, 0 errors, 45 warnings (pre-existing classes)",
"exit": 0
},
{
"command": "node scripts/check-sdui-registration-pins.mjs",
"verdict": "NOT MEASURED: prerequisite not met (needs the console build); no registration changed",
"exit": 2
},
{
"command": "check-doc-snippet-types / check-doc-example-types",
"verdict": "NOT MEASURED: need a build of ~35 packages; population disjoint (no ts/tsx doc snippet names the changed symbols; the edited page's only fence is plaintext); CI runs both",
"exit": null
},
{
"command": "pnpm check (CLI catalog check), apps/console type-check, repo-wide pnpm lint, full ...@object-ui/components sweep (31 packages)",
"verdict": "NOT MEASURED locally, declared narrowing: the 6939 pin validates the 5 catalog entries green; console reads only toFilterGroup().conditions[0] id/field; the sweep was narrowed to the packages that name the changed symbols (by grep); CI runs the rest",
"exit": null
},
{
"command": "PR CI at 71124f2 (one read at report time)",
"verdict": "in_progress: 42 check runs, 20 success, 3 skipped, 19 in progress, 0 failed",
"exit": null
}
],
"line_budget": "n/a: no skills/** diff.",
"deviations": [
"File surface, declared: three EXISTING pending changesets (7918-zod-lazy-getter-identity, 6349-name-authority-batch-3, 8415-filter-builder-condition-id) each gained one appended 'Superseded in this release by objectui#9306' paragraph, frontmatter and history untouched. Not named in the claim surface; done because this change makes their in-release present-tense claims false (release text that solidifies on publish) and PR objectui#10534's contract review blocked on exactly this class. If the seat prefers them split out, they are the last commits f151a4c / bf18964 (changesets only).",
"File surface, within 'dependant call sites the narrowing breaks': packages/plugin-view/src/config/view-config-utils.ts (toFilterGroup operator asserted at the untyped boundary; output unchanged) and packages/plugin-list/src/tests/convertFilterGroupToAST.test.ts (two casts). Both found by type-check. The components changeset therefore also declares
@object-ui/plugin-view: patch(the brief named minor for types and components only).","File surface, docs: beyond the allowGroups / maxDepth rows, filter-builder.mdx's intro sentence ('support for nested conditions') and its FilterGroup block (conditions union, required id) were also changed, because this change makes them false; a short 'Retired' section was added per the objectui#10611 precedent.",
"File surface, pins beyond the named 8415 file: 7918 ledger, 10478 nested-row test, zod-mirror-parity FilterGroupSchema exclusion reason — each pinned the branch this change deletes or stated a reason it falsifies; replaced, not deleted.",
"Comment-only repair (AGENTS.md #11, repair-when-touching): the cross-file line addresses in the FilterGroup / FilterField / wrapperClass docblocks of the two touched types files (commit 71124f2; no describe() string moved).",
"The first local commit carried the harness reminder's model-named Co-Authored-By trailer; amended to the model-free form BEFORE any push (never on the remote). No card trailer in any commit.",
"Lock contention: another seat's verify (issue-10594) held the lock ~31 min; two queue-timeouts (exit 99) on this slot. One reading (the 8415 union chain) was taken lock-free with a sub-second plain node import of the built @object-ui/types/zod dist instead of a vitest run; declared here.",
"Throwaway probes: two zz-probe-9306 test files and one .mjs were written into the worktree for measurements and deleted uncommitted (git status clean each time). The first reverse-check attempt was a no-op (tool refused), re-run with a new anchor; only the second is cited.",
"A python census globbing examples/** overran 120s and was auto-backgrounded; stopped with TaskStop (by its own task id) and re-run over git ls-files.",
"Worktree removed after the PR opened (node_modules deleted, git worktree remove without --force succeeded)."
],
"files_changed": [
".changeset/6349-name-authority-batch-3.md",
".changeset/7918-zod-lazy-getter-identity.md",
".changeset/8415-filter-builder-condition-id.md",
".changeset/9306-filter-builder-name-authority.md",
".changeset/9306-filter-builder-nested-groups-retired.md",
"content/docs/components/complex/filter-builder.mdx",
"packages/components/src/tests/filter-builder-name-authority-9306.test.tsx",
"packages/components/src/custom/filter-builder.tsx",
"packages/plugin-list/src/tests/convertFilterGroupToAST.test.ts",
"packages/plugin-view/src/config/view-config-utils.ts",
"packages/types/src/tests/filter-builder-condition-id-8415.test.ts",
"packages/types/src/tests/filter-builder-nested-group-retired-9306.test.ts",
"packages/types/src/tests/filter-condition-value-protocol-rule-10478.test.ts",
"packages/types/src/tests/zod-lazy-getter-identity-7918.test.ts",
"packages/types/src/tests/zod-mirror-parity.test.ts",
"packages/types/src/complex.ts",
"packages/types/src/zod/complex.zod.ts"
],
"open_questions": [],
"out_of_scope_findings": [
"class: c · reach: public door, measured — safeValidateSchema answers success on a filter-builder node whose
valueis a bare CONDITION{ id, field, operator, value }, while the TS face declaresvalue?: FilterGroupand the component's isValidGroup gate needsconditions+logic(the render half, falling back to the empty group, is a source reading, NOT MEASURED). Named producer: @object-ui/types FilterBuilderSchemavalue/defaultValuein zod/complex.zod.ts, each the union of FilterBuilderConditionSchema and FilterGroupSchema · evidence: a throwaway probe through safeValidateSchema on this branch (bareCond: OK) · dedupe words: filter-builder value bare condition, FilterBuilderSchema value union, isValidGroup EMPTY_GROUP, filter-builder defaultValue","carrier: 承接者:无 · noted, not filed: FilterBuilderSchema.defaultValue is declared on both faces, but the filter-builder renderer passes
schema.value || props.valueand FilterBuilder has no defaultValue prop — source reading only, not measured (AGENTS.md: a grep zero does not answer a reader question).","carrier: 承接者:无 · noted, not filed: the docs page's Schema block still names the row
FilterConditionwithoperator: string; the TS name is FilterBuilderCondition with the protocol's operator ids. Polish.","carrier: 承接者:无 · noted, not filed: fields FilterConditionField and app-shell datasetFilterCondition keep local flat BuilderGroup copies with operator typed string, and app-shell viewFilterFold still refuses a nested group at runtime (now a shape the types refuse; a stored-data defence). Nothing false today."
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 27, 2026
ACCEPT — PR objectui#10823 ·
domain:uiseat #1 · 2026-09-27T17:49Zsession_01DuWo5bdP9SdVebamn99GGk(os-steve), the dispatching seat and reviewer of record. Checked against GitHub andorigin/main, ⛔ not against the report.Checklist
main; body opensFixes #9306, the only closing keyword;Clause-②: yesat line start; PR assigneeos-steve. No closing keyword touches objectui#6349.packages/types/src/complex.tsandzod/complex.zod.ts(flatconditions; a sub-group refused by name;allowGroups/maxDepthtombstoned),packages/components/src/custom/filter-builder.tsx(the hand-written pair deleted and derived from types;operator= the protocol set plusexists/notExists;valuekeeps the narrow union per decision (i); groupidoptional), the docs page, and pins re-decided (8415, 7918, 10478, mirror-parity), not deleted. Two changesets (@object-ui/typesminor;@object-ui/componentsminor +@object-ui/plugin-viewpatch). Append-only 「Superseded」 paragraphs on three pending changesets, frontmatter unchanged.packages/plugin-view/src/config/view-config-utils.ts(an operator assertion at an untyped boundary; the review readparseSpecFilter/specToBuilderOperatorand found output unchanged),packages/plugin-list/src/__tests__/convertFilterGroupToAST.test.ts(two casts), and the three pending changesets. All three are "dependants the narrowing breaks" or release text the change made false.NOT governed. Checks on head71124f208: 43 check-runs, 40 success, 3 skipped, 0 failure. Main is ahead by commits that touch none of these files.5858274408—Served-tier: CONTRACT_REVIEW_TIER, head71124f208089b796bf9f6ad48e421dd8fc501638(the current head), independence pair present, PASS. Every change on the TS and tolerant faces is a narrowing the ruling orders; the widening search found nothing newly accepted; the sub-group refusal is a single named issue (checked on zod 4.4.3 source); the strict face refuses it by the same issue (reasoned, not pinned — the 10611 precedent pins the same way).allowGroupsas boolean turned 3 red; a cross-package reverse check pasting an authority-typed sub-group gaveTS2353. Restores proven.Out-of-scope findings, one line each
FilterBuilderSchema.value/defaultValueaccept a bare condition while the TS face saysFilterGroup— filed objectui#10825 (not made worse here).defaultValuemay have no reader — carried as the first step of objectui#10825.FilterCondition { operator: string }block is stale (pre-existing) — Acceptance notes.BuilderGroupcopies behind pre-existingas anycasts — Acceptance notes; nothing false today.Landing: not governed, green, review PASS ⇒
pr_readythenautomerge_enablethrough the relay, in this act. On merge, the seat posts the settled-rows note on objectui#6349, as ruling5852274286assigns it.domain:uiseat #1 · ACCEPT · 2026-09-27T17:49ZGenerated by Claude Code
feat(filter-builder): the dropdown speaks the protocol's operator ids…
feat(types,components)!: retire filter-builder nesting at the name au…