Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[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

Closed

Description

@os-tesla

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:

  1. The mirror follows the dropdown (camelCase). Matches what renders and what onChange emits back to the host — but forks from @objectstack/spec's VIEW_FILTER_OPERATORS, which the component itself imports and folds through.
  2. The dropdown follows the mirror (canonical snake_case). Contract-first — but rewrites the ids every stored filter carries and every consumer of foldFilterGroupToSpecRules reads.
  3. 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

  1. The three directions above, with their costs.
  2. 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/after onto less_than/greater_than, exists/notExists onto is_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.
  3. ⭐⭐ objectui#6349's two parked name-authority rows. FilterBuilderCondition and FilterGroup are declared in both @object-ui/types and @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 retypes operator — 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).
  4. objectui#7993's input, which points at direction 2: the spec treats the camelCase spellings as the DEPRECATED alias form, with less_than canonical.

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.

⚠️ NOT MEASURED, and it prices direction 2: how many stored filters in real tenants carry camelCase ids. Nobody in this container can read that. Direction 2 rewrites every one of them, so whoever rules should want that number or should say explicitly that they are ruling without it.

⛔ 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 (needsValueInput reads 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

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 12, 2026
  2. os-tesla commented on Sep 13, 2026

    @os-tesla
    CollaboratorAuthor

    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-403 the 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 gain between / is_empty / icontains was redundant: the protocol is the answer; a capability it does not name is not offered.

    What is ruled

    1. defaultOperators in packages/components/src/custom/filter-builder.tsx emits the FilterOperatorSchema ids (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.
    2. Stored filters carrying camelCase ids keep loading: normalizeFilterOperator accepts 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.
    3. The eight dropdown ids with no protocol twin: before / after → less_than / greater_than and exists / notExists → is_not_null / is_null (same semantics, folded on read); between, isEmpty, isNotEmpty, containsCaseInsensitive are removed from the dropdown — zero measured pull, and objectui#7379's semantic boundary forbids folding a case-insensitive contains onto contains. ⛔ No spec card. If a customer ever needs one of them, the card is filed then, with the pull attached.
    4. objectui#6349's two parked name-authority rows converge on the @object-ui/types declaration with operator typed as the protocol set — an acceptance criterion of this card.
    5. objectui#9302 (needsValueInput reads the raw operator) is unchanged and lands after 1.

    State

    needs-user-decision → pm:queue; domain:ui kept; priority:p3 added (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

  3. os-tesla commented on Sep 13, 2026

    @os-tesla
    CollaboratorAuthor

    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 FilterOperatorSchema is 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:243 VIEW_FILTER_OPERATORS is 20 members, ⛔ not 14:

    equals not_equals contains not_contains icontains starts_with ends_with
    greater_than less_than greater_than_or_equal less_than_or_equal in not_in
    is_empty is_not_empty is_null is_not_null before after between
    

    The 14-member FilterOperatorSchema this card quotes lives in objectui's packages/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 "remove between / 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: contains and icontains stay 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)

    1. @object-ui/types' FilterOperatorSchema becomes the protocol's set — import VIEW_FILTER_OPERATORS from @objectstack/spec/ui rather than a local literal, so the mirror cannot fall behind again (the same reasoning the protocol's own docblock at :257-268 gives for isMultiOperator).
    2. defaultOperators emits the 20 protocol ids; the camelCase spellings remain readable as deprecated aliases (item 2, unchanged).

    pm:queue / domain:ui / priority:p3 unchanged.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1 — domain:ui execution seat (one dispatch with objectui#9559, as ratification 5814267184 orders: 「the two dispatch together」)
    Session: session_01BA3nKVUwKQJf8DBxrSVtNC
    Branch: claude/issue-9306-filter-builder-protocol-ids (amended; the joint branch claude/issue-9559-filter-operators-protocol-set merged as PR objectui#10465)
    Worktree: objectui-issue-9306
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/types/src/zod/complex.zod.ts (FilterOperatorSchema derived from @objectstack/spec/ui VIEW_FILTER_OPERATORS with the spec's alias normalisation, and the TS mirror type that follows it), packages/components/src/custom/filter-builder.tsx (defaultOperators emits 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 ratified
    Clause-②: 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 touches complex.zod.ts or filter-builder.tsx. Live pm:dispatched claims read at the same time ⇒ none names them. objectui#9695 and objectui#9302 are closed.

    Scope

    The rulings 5651809976 and 5651829933 (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.
    • The seat, not the dev, files the one spelling the protocol's alias table lacks (containsCaseInsensitive → icontains) as a bare card in objectstack. ⛔ It is not fixed in objectui.
    • The acceptance criterion of objectui#6349's two parked name-authority rows (operator typed as the protocol set) is carried.

    ⇒ closed by the same PR (Fixes #9306 in 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_OPERATORS and its docblock, and the builder's own FilterBuilderOperator export) and its filterBuilder.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) and packages/app-shell/src/views/metadata-admin/widgets.tsx (SPEC_TO_FB);
    • packages/plugin-view/src/config/view-config-utils.ts (CANONICAL_TO_BUILDER);
    • the parity and census pins that read these tables in components, fields, plugin-list, plugin-view and app-shell;
    • changesets for each released package.

    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 / notExists follow ruling B (5748934939, ratified): they stay opt-in with no fold (seat state record 5826294523).

    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_FILTER label keys) and its four pins;
    • packages/app-shell/src/views/ObjectView.tsx sanitizeViewOverride ONLY (the operator fold before the value-less lookup), with ObjectView.overlayPairValue. This is hunk-disjoint from objectui#10035's remount region;
    • the stale prose in viewFilterFold.ts, core filter-converter.ts, filter-icontains-alignment-8976.test.ts, types filter-operator-protocol-set-9559.test.ts and examples/schema-catalog filter-builder-operator-alias-trigger-7561.test.tsx;
    • a changeset for @object-ui/i18n if its packs change.

    ⛔ Not plugin-list ListView.tsx (PR objectui#10510, the objectui#10326 claim) and ⛔ not filter-builder.mdx (PR objectui#10515). icontains leaves OPT_IN_OPERATORS (the seat decision on this card).

    domain:ui seat #1 · session_01BA3nKVUwKQJf8DBxrSVtNC · claim · amended 2026-09-25T06:20Z

  5. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    State record from the domain:ui seat #1 (session_01BA3nKVUwKQJf8DBxrSVtNC), 2026-09-25T03:41Z. The claim 5825621947 stays: 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 #9559 only) carries this card's item 1 (the mirror accepts the protocol set and its aliases) and the types half of item 4 (FilterBuilderOperator is the spec's ViewFilterOperator). 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/fields FilterConditionField.condToMongo: its default arm would store a canonical greater_than_or_equal row as an equality in sharing-rule criteria, silently.
    • @object-ui/app-shell datasetFilterCondition OP_TO_MONGO: it drops unmapped rows.
    • @object-ui/plugin-view CANONICAL_TO_BUILDER and app-shell SPEC_TO_FB map 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 components filter-builder.tsx and its operator label keys, fields FilterConditionField.tsx, app-shell datasetFilterCondition.ts and widgets.tsx, plugin-view view-config-utils.ts, and their pins, each with a changeset.

    exists / notExists: ruling B governs. It is the later text and maintainer-ratified (5748934939, ratified 5814267184): 「exists / notExists remain opt-in with their named upstream blocker」. So they stay opt-in builder ids, offered only to FilterConditionField, and ⛔ are not folded to is_not_null / is_null. The fold in the amendment 5651829933 is superseded on this point.

    • ⚠️ The seat's own dispatch order told the dev to implement that fold. That was the seat's error, and the dev caught it before any edit.
    • A fold would change what a stored sharing rule matches on the key-presence drivers (objectstack#5499). That is a security-boundary semantic, left to the maintainer if anyone wants it.

    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:ui seat #1 · state record · 2026-09-25T03:41Z

  6. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    Serial wait, recorded by the domain:ui seat #1 (session_01BA3nKVUwKQJf8DBxrSVtNC), 2026-09-25T04:26Z. The claim 5825621947 stays. ⛔ No label, assignee or grade changes.

    PR objectui#10465 merged at 2026-09-25T04:13Z (1779e8dee, verified on main), which clears this card's first serial constraint. The dropdown half's surface then meets two more holders:

    • PR objectui#10455 (card objectui#10257), which held datasetFilterCondition.ts, has merged. Clear.
    • PR objectui#10450 (card objectui#10367, domain:ui seat Add default props to all components to prevent collapse in designer #4, draft) holds packages/app-shell/src/views/metadata-admin/widgets.tsx. The dropdown half must rewrite that file's SPEC_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:ui seat #1 · serial wait · 2026-09-25T04:26Z

  7. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    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.ts reuses the builder's label keys by literal: NULL_FILTER.labelKey is filterBuilder.operators.isNull and notLabelKey is filterBuilder.operators.isNotNull. ObjectDataPage renders them through t(textKey) on the drill chip. The dropdown's labels ARE keyed by operator id (t of filterBuilder.operators. plus op.value), so Zone 2 item 5 applies. Re-keying the family in all ten packs makes that chip render the raw key, and ObjectDataPage.filterChipI18n-9159.test.tsx goes red: it receives the key string itself. pnpm check:i18n-keys stays green (exit 0) because it does not read object-literal keys, so only that render pin sees it. (2) app-shell ObjectView.tsx sanitizeViewOverride asks VALUELESS_FILTER_OPERATORS.has(String(operator)) RAW. That set (in viewFilterFold.ts) is the builder's VALUELESS_FILTER_BUILDER_OPERATORS plus four canonical spellings. Once the builder's set is canonical, the deprecated isEmpty / isNotEmpty / isNull / isNotNull drop 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.ts goes red. Neither file was edited. The in-surface SOURCE half is done and pushed as a labelled checkpoint, 37a2e607f on the claim branch; no PR was opened. What it changes: components defaultOperators emits the 20 VIEW_FILTER_OPERATORS ids plus the opt-in exists / notExists (ruling B, not folded). The builder folds a stored spelling at its read boundary through the spec's normalizeFilterOperator plus ONE local row, and never emits on read. fields condToMongo / kvToCondition / TEXT_COMPARAND_OPERATORS / FILTER_CONDITION_EXTRA_OPERATORS and app-shell OP_TO_MONGO / MONGO_TO_OP / VALUELESS_TO_MONGO are keyed on the protocol ids. widgets SPEC_TO_FB and plugin-view CANONICAL_TO_BUILDER are removed: one had become the spec's own fold, the other an identity. The stale OPT_IN_OPERATORS docblock 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/spec 17.4.0: normalizeFilterOperator('containsCaseInsensitive') returns its input unchanged, and the control greaterOrEqual gives greater_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 spelled icontains, 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 in OPT_IN_OPERATORS' own last paragraph: drop the entry. I also bridged icontains to $icontains in the dataset inspector (in surface). Other notes: the card carries priority:p3 where the dispatch says p2. The assignee arrived as os-bill and was left untouched. The claim 5825621947 names 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=dot over 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-keys exit 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-bytes OK. 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) and src/views/ObjectView.tsx sanitizeViewOverride (raw value-less lookup), or src/views/viewFilterFold.ts VALUELESS_FILTER_OPERATORS in 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 / notLabelKey become filterBuilder.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 was containsCaseInsensitive the spec's alias table did not fold it, so the gate could not see this. $icontains is in FILTER_OPERATORS, TEXT_OPERATOR_DOOR_CASES and FILTER_TEXT_CASES.",
    "options": [
    "A (in the checkpoint) - Drop icontains from 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 $icontains like 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 icontains opt-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

  8. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    Seat decisions on the dropdown half's two open questions, from the domain:ui seat #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's labelKey / notLabelKey follow the re-keyed filterBuilder.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, sanitizeViewOverride ONLY: the row's operator is folded through the spec's normalizeFilterOperator before 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 pin ObjectView.overlayPairValue. ⚠️ objectui#10035's live claim names this file for its remount region. sanitizeViewOverride is a separate exported function, so the two are hunk-disjoint.
    • The stale prose this change falsifies, in files free of other holders: the VALUELESS_FILTER_OPERATORS comments in app-shell viewFilterFold.ts; the containsCaseInsensitive builder-row mention in core filter-converter.ts and filter-icontains-alignment-8976.test.ts; the FILTER_BUILDER_IDS literal in types filter-operator-protocol-set-9559.test.ts; and the CORRECTION table in examples/schema-catalog filter-builder-operator-alias-trigger-7561.test.tsx.
    • ⛔ NOT added, because other holders have them: plugin-list ListView.tsx (open PR objectui#10510 and the objectui#10326 claim) and content/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 from OPT_IN_OPERATORS. This is not a new product call. It executes the plan OPT_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_OPERATORS and VALID_AST_OPERATORS both carry icontains, and both list dialects execute it. The parity spine now reds on "expressible but withheld", which is exactly the flip that sentence predicted. FilterConditionField keeps writing $icontains, and the dataset inspector's icontains → $icontains bridge row stays in this PR. It touches no stored predicate and no security boundary.

    domain:ui seat #1 · decision · 2026-09-25T06:21Z

  9. 16 remaining items

  10. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Ruling: maintainer · letter A · 「A 撤掉嵌套声明(推荐)」 · 2026-09-27T15:43Z

    Recorded by the domain:ui seat #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 in 5857198401 (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/types FilterGroup.conditions becomes flat condition rows; FilterGroupSchema refuses a sub-group by name; allowGroups / maxDepth are retired from the types face, the zod mirror and content/docs/components/complex/filter-builder.mdx; @object-ui/components then deletes its hand-written FilterBuilderCondition / FilterGroup and derives both from @object-ui/types, with operator (the protocol set plus the opt-in exists / notExists) as the named extension and FilterGroup.id optionality aligned (premise 2 held). Ruling 5852274286's other terms stand unchanged.

    Execution parameters carried into the dispatch (from the box, overturnable only by a later ruling):

    • The value member: the seat's decision (i) in 5857198401 — operator and value are both named extensions in components; value keeps the component's narrower union.
    • Surface: packages/components/src/custom/filter-builder.tsx, packages/types/src/complex.ts, packages/types/src/zod/complex.zod.ts, the allowGroups / maxDepth rows of filter-builder.mdx, the source-text pin packages/types/src/__tests__/filter-builder-condition-id-8415.test.ts, tests, minor changesets for @object-ui/types and @object-ui/components.
    • Clause-②: yes ⇒ a contract review record before enqueue.
    • The objectui#6349 note stays with the landing seat.

    State: needs-user-decision → pm:dispatched in the next act, with a new claim. Blocked-by: none.

    domain:ui seat #1 · ruling record · 2026-09-27T15:43Z


    Generated by Claude Code

  11. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1
    Session: session_01DuWo5bdP9SdVebamn99GGk
    Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-9306-filter-builder-name-authority
    Worktree: objectui-issue-9306
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/components/src/custom/filter-builder.tsx (the hand-written FilterBuilderCondition / FilterGroup and the exported FilterBuilderOperator); packages/types/src/complex.ts (FilterGroup.conditions → flat rows; FilterBuilderSchema.allowGroups / maxDepth retired); packages/types/src/zod/complex.zod.ts (the same, with the sub-group refused by name); the allowGroups / maxDepth rows of content/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; minor changesets for @object-ui/types and @object-ui/components (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (TIER_DEFAULT, objectstack scripts/pm/dispatch-gates.mjs:12534 at 0d3ec47137; --tier refuses 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.ts or filter-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). Why Clause-②: yes: a published @object-ui/types type and its authoring mirror narrow (a sub-group becomes refused), and a published @object-ui/components type narrows.

    domain:ui seat #1 · claim · 2026-09-27T15:44Z


    Generated by Claude Code

  12. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    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 custom issue at the entry path, message == the published describe of conditions); FilterBuilderSchema.allowGroups / maxDepth are ?: never tombstones 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-check at 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-check exit 0; tsc -p tsconfig.test.json exit 0 at bf18964. Consumers (downstream of components, the packages that name the changed symbols): builds turbo 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 json on 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 major bump.",
    "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 value is a bare CONDITION { id, field, operator, value }, while the TS face declares value?: FilterGroup and the component's isValidGroup gate needs conditions + logic (the render half, falling back to the empty group, is a source reading, NOT MEASURED). Named producer: @object-ui/types FilterBuilderSchema value / defaultValue in 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.value and 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 FilterCondition with operator: 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

  13. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    ACCEPT — PR objectui#10823 · domain:ui seat #1 · 2026-09-27T17:49Z

    session_01DuWo5bdP9SdVebamn99GGk (os-steve), the dispatching seat and reviewer of record. Checked against GitHub and origin/main, ⛔ not against the report.

    Checklist

    • Shape: draft → main; body opens Fixes #9306, the only closing keyword; Clause-②: yes at line start; PR assignee os-steve. No closing keyword touches objectui#6349.
    • Scope: 17 files, +786 / −84 — packages/types/src/complex.ts and zod/complex.zod.ts (flat conditions; a sub-group refused by name; allowGroups / maxDepth tombstoned), packages/components/src/custom/filter-builder.tsx (the hand-written pair deleted and derived from types; operator = the protocol set plus exists / notExists; value keeps the narrow union per decision (i); group id optional), the docs page, and pins re-decided (8415, 7918, 10478, mirror-parity), not deleted. Two changesets (@object-ui/types minor; @object-ui/components minor + @object-ui/plugin-view patch). Append-only 「Superseded」 paragraphs on three pending changesets, frontmatter unchanged.
    • Surface additions, accepted: packages/plugin-view/src/config/view-config-utils.ts (an operator assertion at an untyped boundary; the review read parseSpecFilter / specToBuilderOperator and 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.
    • Governed-surface predicate on the 17 paths: NOT governed. Checks on head 71124f208: 43 check-runs, 40 success, 3 skipped, 0 failure. Main is ahead by commits that touch none of these files.
    • Contract review: record 5858274408 — Served-tier: CONTRACT_REVIEW_TIER, head 71124f208089b796bf9f6ad48e421dd8fc501638 (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).
    • The dev's ablations: restoring the mirror union turned 6 pins red; restoring allowGroups as boolean turned 3 red; a cross-package reverse check pasting an authority-typed sub-group gave TS2353. Restores proven.

    Out-of-scope findings, one line each

    • FilterBuilderSchema.value / defaultValue accept a bare condition while the TS face says FilterGroup — filed objectui#10825 (not made worse here).
    • defaultValue may have no reader — carried as the first step of objectui#10825.
    • Docs page's FilterCondition { operator: string } block is stale (pre-existing) — Acceptance notes.
    • fields / app-shell keep local flat BuilderGroup copies behind pre-existing as any casts — Acceptance notes; nothing false today.
    • Stored tenant metadata with nested groups: NOT MEASURED by anyone; the PR's risk section says so.

    Landing: not governed, green, review PASS ⇒ pr_ready then automerge_enable through the relay, in this act. On merge, the seat posts the settled-rows note on objectui#6349, as ruling 5852274286 assigns it.

    domain:ui seat #1 · ACCEPT · 2026-09-27T17:49Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions