Skip to content

finding(spec): ElementDataSourceSchema.filter (page.zod.ts) still declares the MongoDB-style FilterConditionSchema — the binding-level sibling of #14406 outside ComponentPropsMap, while objectui's composition seam lowers three shapes and its own pins author the tuple array #15442

Closed

Description

@os-sales

Observed by the os-dev executing #14406 (session session_01G4138K1EG7kQ81FNba5Kp4) while measuring that card's read path. Filed unassigned for triage; ⛔ not folded into #14406 — its census, ruling text and dispatch surface are scoped to ComponentPropsMap, and this key is read by every element that carries a binding.

Measured on origin/main (be2594e66)

packages/spec/src/ui/page.zod.ts:169, inside ElementDataSourceSchema:

filter: FilterConditionSchema.optional().describe('Additional filter criteria'),

After #14406 lands, element:record_picker carries two filter orthographies at two keys that one renderer read resolves through a single ?? (composed?.filter ?? props.filter, record-picker.tsx:108 at the pin): properties.filter is the ViewFilterRule array, dataSource.filter is the MongoDB-style record. The card the ruling ui#6206-B closes says "one filter orthography platform-wide"; this key is the binding-level one the ComponentPropsMap census could not see.

The consumer at the objectui pin 00d3f09c500c4a45b5f27aca8af80349412faaf1 (identical on objectui main f7cf7e8)

  • packages/core/src/data-scope/element-data-source.ts:69-78 types the binding's filter as unknown "because three shapes reach it (a MongoDB-style object, an ObjectQL AST node array, a spec ViewFilterRule[]) and mergeFilterNodes is the single sink that lowers all three"; :247-251 AND-combines it with the saved view's own rule array.
  • packages/components/src/__tests__/record-picker-element-data-source.test.tsx:80 authors dataSource: { object: 'account', view: 'hot', filter: [['amount', '>', 100]] } — an array the spec REFUSES at dataSource.filter (FilterConditionSchema is a z.record(...).and(z.object(...)); an array is invalid_type there). So the consumer's own pin already contradicts the declaration.

Why this is its own card

  • Different key, different schema file, different reader set: dataSource is consumed by list-view, object-grid, form, kanban, calendar, gantt and the picker (objectui#6953 / objectstack#7121 wired them), so the measurement finding(spec): element:record_picker.filter still declares the MongoDB-style FilterConditionSchema — the last record-form filter in ComponentPropsMap after the ui#6206-B convergence #14406 did for one renderer has to be redone per consumer — plus the spec-side readers that parse dataSource (packages/lint validate-component-props, the console designer's binding inspector).
  • Its own ADR-0087 D3 entry and its own in-repo author sweep (dataSource.filter authors in examples/**, content/docs/**, spec fixtures, check-yaml-examples).
  • Not decided here: whether ui#6206-B reaches the binding key as written (it reads as if it does — "platform-wide"), or whether a binding-level filter that the view's rule array AND-combines with should simply be the same ViewFilterRule[] for that reason alone. Triage decides; the two shapes are Option A (converge to the array, D3 entry, consumer-first measurement per reader) vs Option B (record a reasoned exception on the binding key).

Refs: #14406 (the last ComponentPropsMap entry) · #12039 (Key 2, PR #14404) · objectui#6206 (the ruling) · objectui#6953 (the binding's consumers) · objectui#6828.

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:specDomain lane — seat card indexed by label:pm:seat
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Sep 4, 2026
  2. os-zhuang commented on Sep 4, 2026

    @os-zhuang
    Contributor

    分诊 · domain:spec / priority:p2 / pm:queue

    Anchor read, not guessed. Confirmed on origin/main f1d7872 (2026-09-04T23:50:17Z): packages/spec/src/ui/page.zod.ts:169 — filter: FilterConditionSchema.optional().describe('Additional filter criteria'), imported from ../data/filter.zod at :9. The declaration to change is in packages/spec ⇒ domain:spec. objectui is the consumer here, not the landing site, so this stays an objectstack card and does not get repo:objectui.

    Grade — p2

    The deciding evidence is the one the filer found and it is unusually clean: the consumer's own pin already contradicts the declaration.

    packages/components/src/__tests__/record-picker-element-data-source.test.tsx:80 authors dataSource: { object: 'account', view: 'hot', filter: [['amount','>',100]] } — an array the spec refuses at dataSource.filter (FilterConditionSchema is z.record(...).and(z.object(...)); an array is invalid_type there).

    So this is not "a schema that could drift" — it is a schema that is already wrong about what its consumer writes, pinned on both sides. Add the ?? collapse the filer measured (composed?.filter ?? props.filter, record-picker.tsx:108), and after #14406 lands element:record_picker carries two filter orthographies at two keys resolved through one read. Two spellings meeting at a single ?? is how a silent wrong-filter lands — a filter that is dropped or misread does not error, it returns the wrong rows.

    Not p1: no measured production incident, and the consumer's mergeFilterNodes sink lowers all three shapes today, so nothing is broken right now — element-data-source.ts:69-78 types the key as unknown precisely because it is absorbing the divergence. That absorption is the workaround, and it is load-bearing.

    Boundary test — Option A crosses, Option B does not

    • Option A (converge to ViewFilterRule[]) narrows a published contract on a key every binding-carrying element reads ⇒ contract change, manual floor, Clause-② review, and an ADR-0087 D3 entry as the filer says.
    • Option B (record a reasoned exception on the binding key) changes no accept set — it is a documented divergence, and its cost is that "one filter orthography platform-wide" becomes "platform-wide except here".

    ⇒ ⛔ Triage is not picking. But two inputs the chooser should not have to re-derive, because they cut in opposite directions and the card presents them evenly:

    • For A: the ruling ui#6206-B says platform-wide, and the filer's reading — that a binding-level filter which the view's rule array is AND-combined with (element-data-source.ts:247-251) should be the same type for that reason alone — is the strongest single argument on this card. Two operands of one AND having different orthographies is hard to defend on its own terms.
    • For B: element-data-source.ts:69-78 documents three shapes reaching this key (MongoDB-style object, ObjectQL AST node array, spec ViewFilterRule[]), with mergeFilterNodes as the single sink. Converging the declaration does not by itself retire the other two live shapes — it just makes the spec refuse two of the things that arrive.

    ⚠️ The cost estimate is the part most likely to be underestimated

    The filer says the per-renderer measurement #14406 did for one component has to be redone per consumer, and lists them: list-view, object-grid, form, kanban, calendar, gantt, the picker (objectui#6953 / objectstack#7121), plus the spec-side readers — packages/lint's validate-component-props and the console designer's binding inspector. That is not a one-file change under Option A. ⇒ Whoever costs this should produce the consumer census before choosing, the same way #14406 did; choosing A on the strength of the AND-combination argument and then discovering seven renderers is how a card becomes a month.

    Its own in-repo author sweep is also part of the deliverable: dataSource.filter authors in examples/**, content/docs/**, spec fixtures, and check-yaml-examples.

    Scope fence — endorsed

    ⛔ Not folded into #14406, correctly: that card's census, ruling text and dispatch surface are scoped to ComponentPropsMap, and this key sits outside it — which is precisely why the ComponentPropsMap census could not see it. Nobody should read this as #14406 being incomplete.

    ⚠️ Related but distinct, and routed separately this round: #15449 — four object-* filter doors inside ComponentPropsMap declaring bare z.unknown(). Same ruling, third location. Whoever takes either should read the other first; if all three converge, they may want one ADR-0087 D3 entry rather than three.

    ⛔ Not a claim, not a dispatch — routing only.


    Generated by Claude Code

  3. claude commented on Sep 5, 2026

    @claude
    Contributor

    Claim: PM loop round R2 — domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T08:46Z. Anchor card of a two-card fold: #15442 (this card) + #15449 — one dev, one branch claude/issue-15442-filter-orthography-consumer-census, mode:subagent, tier claude-fable-5-1.

    Clause-②: no

    Why folded, and why measurement-first: both cards are the same ruling (objectui#6206-B, one filter orthography platform-wide) at two more locations — the binding-level ElementDataSourceSchema.filter (page.zod.ts:169, still FilterConditionSchema) and the four object-* filter doors in ComponentPropsMap (component.zod.ts:2463 / :2620 / :2664 / :2719, z.unknown()) — and both triage notes (5547778089, 5547799546) decline to choose between converge to ViewFilterRule[] (a published-contract narrowing, manual floor, one ADR-0087 D3 entry for the family) and a reasoned exception, and both say the cost must be produced BEFORE choosing: the consumer census #14406 ran for one renderer, redone per consumer. So this dispatch is the census, not the change: the dev measures every reader of both keys on the pinned objectui tree and the spec-side readers, every in-repo author, and what each renderer's lowering does with each of the three shapes — and reports; the seat turns the report into one decision card (or a sequencing note) for the maintainer. No schema moves under this claim.

    Seed datum from the seat's own read (origin/main fc20f7bda, 2026-09-05T08:45Z): the repo's own showcase app authors the MongoDB-style RECORD form on object-metric today — examples/app-showcase/src/ui/pages/my-work.page.ts:39-41 (filter: { status: { $ne: 'done' } }, filter: { status: 'in_review' }, filter: { health: 'red' }) and index.ts:78 — i.e. the exact form ui#6206-B retired is live in-repo on one of the four z.unknown() doors. That single fact changes the cost of "converge" for #15449 from a declaration edit to an in-repo author migration, and it is why the census comes first.

    File face: reads only — packages/spec/src/ui/page.zod.ts, component.zod.ts, component.test.ts (the #14406 census pin), packages/lint/src/validate-component-props.ts + validate-page-field-bindings.ts + page-walk.ts, the objectui pinned tree a472b07 (.objectui-sha; packages/core/src/data-scope/element-data-source.ts, packages/core/src/utils/filter-converter.ts (mergeFilterNodes), the plugin-grid / plugin-kanban / plugin-calendar / metric lowering, record-picker.tsx, the designer's binding inspector), examples/**, content/docs/**, spec fixtures / yaml examples. ⛔ No edit to any of them under this claim; a report is the deliverable. Hot-file check: none (no writes). Dedup: the two cards' own dedup lists (#14406 landed as PR #15456; #12039 Key 2; objectui#6206 / #6953 / #6828) — no other open card names these two keys.

    Member card #15449 carries a one-line pointer to this claim; label flips are per card.


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 5, 2026
  5. claude commented on Sep 5, 2026

    @claude
    Contributor

    Claim (os-dev): branch claude/issue-15442-filter-orthography-consumer-census (pushed empty as the claim marker, zero commits beyond main fc20f7bda), PM session session_01M59rPZZFzqhfMUPFqqZTkf (mode:subagent, round R2), 2026-09-05T08:55Z.

    Scope under this claim: the two-card fold #15442 (anchor) + #15449 (member) — a MEASUREMENT dispatch. Deliverable is the consumer census for both keys (ElementDataSourceSchema.filter in page.zod.ts, and the four object-* filter doors in component.zod.ts): the H1 declaration table, the in-repo author sweep, the objectui reader table at the pin a472b07, the spec-side readers, the ruling's reach, and the per-option cost table. No PR; no edit to any schema, lint rule, renderer, example or doc. Gates: none owed (no diff).

    File face: reads only, as listed in the PM claim above. The only writes are this comment, the report comments on both cards, and any out-of-scope finding filed after a dedup read.


    Generated by Claude Code

  6. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 15442,
      "status": "done",
      "branch": "claude/issue-15442-filter-orthography-consumer-census",
      "pr": null,
      "premise_still_valid": true,
      "premise_per_card": {
        "15442": "true — page.zod.ts:169 `filter: FilterConditionSchema.optional().describe('Additional filter criteria')` on origin/main fc20f7bda (read 2026-09-05T09:0xZ); safeParse through the real ElementDataSourceSchema: record ACCEPT, record with $or ACCEPT, tuple array REFUSE (invalid_type expected record/object at filter), rule array REFUSE, AST logical REFUSE; the objectui pin a472b07 test record-picker-element-data-source.test.tsx:80 authors the tuple array at dataSource.filter (sweep row present), so the consumer's own pin still contradicts the declaration.",
        "15449": "true — component.zod.ts :2463 object-grid / :2620 object-metric / :2664 object-kanban / :2719 object-calendar all `z.unknown().optional()`; FilterConditionSchema appears in that file only in comments :1840 and :2196; safeParse of each of the four doors accepts record, record-with-$or, tuple array, rule array and AST logical alike (measured). The three array doors (:1121 record:related_list, :1157 its nested add.source picker, :1851 element:number, :2220 element:record_picker) refuse everything but the rule array."
      },
      "summary": "MEASUREMENT dispatch, two-card fold; no schema/lint/renderer/example/doc moved; the branch is an empty claim marker (zero commits beyond main fc20f7bda). Readings: objectstack origin/main fc20f7bda, objectui pin a472b07 (= .objectui-sha; `git rev-parse HEAD` verified), 2026-09-05T08:55Z–09:40Z UTC. H1 CONFIRMED exactly as the seat read it (lines above); plus an eighth, nested array door at :1157 (record:related_list add.source.filter). The binding's alias spellings (filters/where/criteria) are did-you-mean REFUSALS (unrecognized_keys), not silent acceptance (measured). H2 AUTHOR SWEEP (brace/indent-aware script over every tracked ts/tsx/js/json/yaml/md/mdx file; positive controls my-work.page.ts:39/40/41 and index.ts:78 all fired; control grep over every `dataSource` mention — docs 26, examples 2, spec 55, lint 25 — found nothing the sweep missed): objectstack 11 occurrences. dataSource.filter: exactly 3 authors, ALL spec test fixtures, ALL record form, all on element:number nodes — packages/spec/src/ui/page.test.ts:666, :762, :767; ZERO in examples/**, hand-written content/docs/**, skills/**, yaml fences, lint fixtures. object-* filter: examples/app-showcase/src/ui/pages/my-work.page.ts:39,:40,:41 object-metric RECORD; index.ts:78 object-metric RECORD; my-work.page.ts:59 object-grid AST TUPLE ARRAY ([['owner_id','=','{current_user_id}']]); packages/lint/src/validate-component-props.test.ts:483 object-grid tuple + :488 object-metric record (the clean-corpus control pin, 'stays SILENT'); packages/lint/src/validate-empty-combinators.test.ts:240 object-grid record { $or: [] }. Generated references render both declarations as `any` (content/docs/references/ui/page.mdx:267; component.mdx:314/:384/:459/:510) and regenerate under a converge. PM assumption corrected: `check:yaml-examples` is a packages/spec script reading only os:check-tagged YAML fences — zero YAML authors of either key, so it is unaffected under either option; and validate-component-props is WARNING tier only (validate-component-props.ts:123), the CLI exits 0 on warnings without --strict (cli/src/commands/validate.ts:519) and no CI job validates the showcase with --strict — so under Option A the showcase's own validate WARNS, it does not go red; the mechanical red is the lint's clean-corpus pin. objectui pin sweep: 26 occurrences — dataSource.filter tuple array x15 (all *.elementDataSource / Gate tests), record x2 (plugin-list ListView.elementDataSource.test.tsx:154, ListView.sharedGate.test.tsx:217); object-* filter tuple x4 (grid test :121, kanban :146, calendar :114, metric :110), rule array x2 (plugin-grid README :468/:528), and content/docs/guide/objectos-integration.mdx:280 an `and:`-keyed object no reader lowers (filed objectui#7713). H3 READERS (objectui a472b07; pin→main drift: only an import line moved on ObjectGrid.tsx/ObjectCalendar.tsx via #7660, every other read-path file byte-identical). Composition: composeElementDataSource (core/src/data-scope/element-data-source.ts:235-252) passes a lone filter VERBATIM and AND-combines two sources via mergeFilterNodes; the Gate (react/src/element-data-source/ElementDataSourceGate.tsx:220-226) writes mergeFilterNodes(base.filter, composed.filter) onto schema.filter — MEASURED: even a lone binding filter is lowered there (toFilterNode runs per source). 17 renderers wrap elementDataSourceBlock; 12 map filter:true (calendar, chart, metric, pivot, record:related_list, line-items, gantt, grid, kanban, list-view, map, timeline); object-detail and the three object-form gates map no filter (no collection query); record-picker uses useElementDataSource directly: `composed?.filter ?? props.filter` (record-picker.tsx:108) → `query.$filter = filter` verbatim (:135) → ObjectStackAdapter.convertQueryParams lowers ALL three shapes (data-objectstack/src/index.ts:3832-3851, translateFilterToAST :400). toFilterNode/mergeFilterNodes RUN (verbatim copy of the pin file, blob 2f69f27c matches `git rev-parse a472b07:…`, only the `@objectstack/spec/ui` import re-pointed at the fc20f7bda spec source): record → ['status','!=','done']; record with $or/$and/$not → ['$or','=',[…]] MISREAD (objectui#6948, open); record with an array value → ['status','=',['a','b']] MISREAD; unknown $op → throws FilterOperatorError/INVALID_FILTER; tuple array verbatim; rule array → [['status','equals','in_review']]; unary rule → [['archived_at','is_empty']]; AST logical verbatim; mergeFilterNodes(viewRules, X) → ['and', [[…view…]], X-lowered] and EVERY combined output passes the spec's isFilterAST (measured: raw record false, raw rule array false, raw tuple true; all lowered/combined true). The four doors: object-grid reads schema.filter through toFilterNode (ObjectGrid.tsx:1461-1462 → $filter :1811): record/tuple/rule all lowered (record caveat #6948); object-kanban hands `$filter: schema.filter` verbatim (ObjectKanban.tsx:263) and object-calendar likewise (ObjectCalendar.tsx:454) — on the ObjectStack adapter all three lowered; on a ValueDataSource a rule-object array is refused row-wise by matchesASTFilter ('not an array', ValueDataSource.ts:178-183 → empty result) while the record and AST match; the calendar ALSO reads schema.filter.calendar as its config object (ObjectCalendar.tsx:145, live at :271 — filed objectui#7711, the #4034 class). object-metric: the Gate writes bound.filter (dashboard/src/index.tsx:174-189) → ObjectMetricWidget → resolveFilterPlaceholders (shape-agnostic) → WITH `aggregate` (all five showcase metrics): ds.aggregate({field,function,groupBy:'_all',filter}) → adapter analytics branch (index.ts:4975-5003) `where = Array.isArray(filter) ? translateFilterArray(filter) : filter` → POST /analytics/query → the runtime route asserts AnalyticsQueryRequestSchema (runtime/src/domains/analytics.ts:49-70, called :102) whose `where` is FilterConditionSchema — MEASURED on the spec source: record ACCEPT, AST tuple array REFUSE, single tuple REFUSE, rule array REFUSE (invalid_type expected record). So on the metric's aggregate path ONLY THE RECORD WORKS today; every array shape is a 400 at the route (the objectui#6302 / PR #6828 measurement stopped at the service gate lowerAnalyticsWhere one hop later; no test posts an array where through the route) — filed objectstack#15828. Without `aggregate` the widget's ds.find({$filter}) path lowers all three. 'Read, not run' on the route function itself (the runtime's dependency closure was not built); the schema it calls was run. Adapter (5000-line file) read, not run. H4 SPEC-SIDE READERS: validate-component-props.ts dispatches properties against ComponentPropsMap at WARNING tier and reads dataSource.object only for object inference (:177); a converge on the four doors turns the record/tuple showcase authors into warnings and flips the clean-corpus pin (validate-component-props.test.ts:475-495) red. validate-page-field-bindings.ts:331 reads props.filter only on record:related_list as rule objects (fieldRefsFrom reads .field) — untouched by either key. page-walk.ts:115-117 reads dataSource.object only — untouched. validate-empty-combinators.ts walks EVERY key named filter/filters/runtimeFilter/relatedListFilter (filter-walk.ts:60; surfaces incl. pages :151) and judges record nodes only (isFilterNode excludes arrays) — its fixture :240 authors object-grid { $or: [] } and would need replacing under A (the rule goes silent on arrays: '整个换掉' triage). The #14406 census pin (component.test.ts:1896-1920) defines holdout = 'declares filter AND refuses the rule array'; z.unknown() accepts the array so the four doors are not holdouts by construction; it stays green under both options and cannot catch them — a converge needs the complementary assertion 'every filter door refuses the record'. Designer (console, objectui app-shell/src/views/metadata-admin): PageBlockInspector.tsx curates per-block fields from BLOCK_CONFIG (previews/block-config.ts) — object-grid [objectName, columns, pageSize], object-metric [objectName, label, description, icon, colorVariant, format, prefix, suffix], object-kanban [objectName, groupField, titleField, cardFields], object-calendar NO ENTRY; neither `filter` nor `dataSource` is curated for any of them, so an authored filter surfaces in the shape-agnostic advanced-key InspectorJsonField (JSON.parse of whatever is typed). The designer authors NEITHER form structurally: Option A does not break it, and it offers no guard either. The record-WRITING builder (inspectors/datasetFilterCondition.ts, groupToCondition) belongs to the DashboardWidgetInspector — the dashboard widget.filter family (spec dashboard.zod.ts:387/:720 FilterConditionSchema), a third location filed as objectstack#15829 (finding). objectui parity: registry-inputs-spec-parity.test.ts carries `element:number.filter:array` (deletable when the spec reaches the pin) and `object-grid.data:object`; object-grid/object-metric registry inputs already declare filter `type: 'array'` (plugin-grid/src/index.tsx:218, plugin-dashboard/src/index.tsx:207) so Option A keeps parity trivially; object-kanban/object-calendar registrations declare NO filter input at all while their renderers read it (filed objectui#7712). The html tier's checkType for an `array` arm is Array.isArray (sdui-parser/src/validate.ts:179-180): a record on grid/metric is ALREADY a type-mismatch there while the spec accepts it — declared vs accepted disagree across tiers today. H5 RULING REACH: objectui#6206 comment 5406409590 (2026-08-25), operative sentence verbatim: 'Maintainer ruling (2026-08-25, batch adjudication, batch 4; verbatim: 「同意」): **Option B** — one filter orthography platform-wide: the spec entry `ComponentPropsMap['element:number'].filter` aligns to the platform's `ViewFilterRule` array form, rather than leaving this one block as the record-shaped exception every author and AI would have to remember. **Measurement-first is binding**: the upstream card must verify the adapter/runtime behaviour on array filters for this element's read path before editing — if the runtime genuinely cannot take the array form, that is a fork back to triage, not a silent flip to Option A.' It names ONLY ComponentPropsMap['element:number'].filter; 'platform-wide' is the principle; it names neither element bindings (dataSource) nor the object-* family. The #6206 card body's premise counted object-grid.filter / object-metric.filter as `'array'`-declared on the objectui side with spec entries that 'accept anything (z.any())'. #7751: maintainer ruling 2026-08-12 (comment 5261743573) direction A — the object-* props schemas enter ComponentPropsMap; landed as commit 8f20201f1 (2026-08-13, PR #8524, 'props schemas derived from objectui's renderer read points') — TWELVE DAYS BEFORE ui#6206-B; no sentence in #7751, its ruling or the commit addresses filter's shape. So z.unknown() is a faithful record of read points that accepted three shapes (grid toFilterNode; kanban/calendar verbatim; metric `filter?: any`), written before the orthography ruling existed — an omission relative to a later ruling, not a deliberate exception to it; the .describe() strings ('ObjectQL filter array/AST') record the author's belief. And the wire does NOT 'take whatever': the metric's aggregate wire takes only the record. (The shallow window's oldest commit is 2026-08-15, so `git log -S` misattributed the introduction; REST commits-by-path corrected it.) H6 COST TABLE (numbers from H2–H5). LOCATION 1 dataSource.filter — Option A (z.array(ViewFilterRuleSchema)): 1 declaration line (page.zod.ts:169); 1 ADR-0087 D3 entry (can be the family entry); regenerate authorable-surface/json-schema/api-surface/docs references; spec fixtures to migrate 3 (page.test.ts:666/:762/:767, record→array, '改拼写'); example/doc/skill authors 0; renderers to touch 0 (all 12 gate blocks and the picker lower the rule array today, measured); designer 0; pins flipped: the 3 spec fixtures; objectui follow-up (not spec-parsed, unblocked only at the pin): 15 tuple-array test authors + 2 record test authors of dataSource.filter become off-spec fixtures (a #7663-style card), and ElementDataSourceConfig.filter's 'three shapes' doc. Breaks: nothing at runtime. Option B (recorded exception, keep FilterConditionSchema): 0 schema files; what stays silently wrong: the spec keeps REFUSING the tuple array objectui's own 15 pins author; element:record_picker keeps two orthographies at two keys resolved by one `??`; the record's $and/$or/$not are spec-legal at the binding but misread by every gate block (objectui#6948); two operands of one `and` in two vocabularies. LOCATION 2 the four object-* doors — Option A: 4 declaration lines; the same family D3 entry; regenerate references (component.mdx); in-repo authors to migrate 5 (my-work.page.ts:39/:40/:41 and index.ts:78 records on object-metric; my-work.page.ts:59 tuple on object-grid); lint fixtures 3 (validate-component-props.test.ts:483/:488 → migrate, the pin otherwise reds; validate-empty-combinators.test.ts:240 → replace); spec pins 0 red (census pin stays green; add the 'refuses the record' twin); renderers to touch: object-grid 0, object-kanban 0, object-calendar 0 (adapter lowers rule arrays), object-metric BLOCKED — its aggregate path 400s on every array at the analytics route (#15828), the verbatim ui#6206-B 'fork back to triage' condition; designer 0; objectui registry: grid/metric already `array` (parity holds), kanban/calendar need the declaration (#7712); showcase validate: warnings only (no red), but the 5 authors migrate in the same PR under contract-first. Option B: 0 schema; the exception must rewrite or justify the four .describe() strings that say 'array/AST'; what stays silently wrong: accept-anything doors; the record's logical keys misread on grid/kanban/calendar (#6948); the html tier declares `array` on grid/metric while the spec accepts the record (declared vs enforced split across tiers); on metric the tuple array the objectui pin :110 authors is a 400 in production. MIXED (A for grid/kanban/calendar, metric excepted): 3 declaration lines now, metric later — buys nothing the sequencing does not, and re-creates a per-door exception inside one family.",
      "tests": "No gates owed (no diff; the branch has zero commits beyond main fc20f7bda, `git status --porcelain` empty after the spec build). Measurements, all scratch scripts OUTSIDE both trees (scratchpad issue-15442/), run with the worktree's tsx from the spec SOURCE at fc20f7bda after `pnpm install --frozen-lockfile` (exit 0, 6.9s): (1) probe-safeparse.ts exit 0 — ElementDataSourceSchema.filter: record ACCEPT / record $or ACCEPT / tuple REFUSE / rule array REFUSE / AST logical REFUSE; aliases filters/where/criteria REFUSE unrecognized_keys; z.array(ViewFilterRuleSchema): only the rule array ACCEPTs; the seven map doors: three array doors refuse all but the rule array, four object-* doors accept all five shapes; my-work.page.ts:39 record through object-metric ACCEPT; my-work.page.ts:59 tuple through object-grid ACCEPT, the same tuple through array(ViewFilterRule) REFUSE invalid_type@0. (2) probe-converter.ts exit 0 — objectui pin filter-converter.ts copied verbatim (diff shows exactly the one import line; `git hash-object` 2f69f27c131c… equals `git rev-parse a472b07:packages/core/src/utils/filter-converter.ts`), import re-pointed at the fc20f7bda spec source: toFilterNode and mergeFilterNodes outputs per shape as listed in H3 (record with $or → ['$or','=',…]; array value → '='; $like → throws INVALID_FILTER). (3) probe-isfilterast.ts exit 0 — spec isFilterAST on raw shapes (record false, rule array false, tuple/single/logical true), on lowered shapes (all true), on every mergeFilterNodes(viewRules, X) output (all true); control hand-built AND true. (4) probe-analytics.ts exit 0 — AnalyticsQueryRequestSchema (api/analytics.zod.ts:50) where: record ACCEPT; AST tuple array / single tuple / rule array REFUSE invalid_type(expected record). (5) probe-route.mts — NOT RUN: importing runtime/src/domains/analytics.ts needs @objectstack/types and onward dist (the build closure the dispatch forbids); read instead: assertAnalyticsQueryBody (:49-70) calls exactly the schema in (4), no pre-parse normalisation, single HTTP entry dispatcher-plugin.ts:1152. (6) sweep.py over `git ls-files` of both trees: objectstack 11 rows (controls my-work.page.ts:39/40/41, index.ts:78 present), objectui 26 rows (control record-picker-element-data-source.test.tsx:80 present); control grep of every dataSource mention found no miss. (7) The only heavy step: `bash scripts/pm/os-verify-lock.sh -c 'NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter @objectstack/spec build'` with OS_VERIFY_LOCK_SLOT=issue-15442-census — VERDICT line: `command-exit 0 · held the lock 193s (3m13s) · waited 112s (1m52s)`; it ran gen:schema and left the tree clean (0 porcelain lines). Ablation: none (nothing to ablate; no change under this claim). Reverse verification: not applicable.",
      "mcp_calls": "10 — all mcp__github__search_issues for dedup: objectui control (hit #7663 first), converter misread (hit objectui#6948 → cited, not filed), metric/aggregate array (hits #6302/#6828 → route hop unmeasured there), analytics route refusal (no hit → #15828), dashboard widget filter (no hit → #15829), guide example (no hit → objectui#7713), kanban/calendar undeclared filter (no hit → objectui#7712), calendar filter.calendar (no hit; only the map twin #4034 → objectui#7711), objectstack control (one 422 on `*`/`()` syntax, re-spelled → hit #15449 first). Every other read went git/payload/REST: issue bodies and comments via the public-repo payload channel (both cards hasNextPage false, 2 comments each) cross-checked by REST GET (200, same 2 comments); commit provenance via REST commits-by-path; all writes (2 claim comments, 5 filings, 3 report comments) via REST POST 201.",
      "open_questions": [
        {
          "question": "Disposition of the two locations under ui#6206-B (one filter orthography platform-wide): Option A — converge both `ElementDataSourceSchema.filter` and the four `object-*` `filter` doors to `z.array(ViewFilterRuleSchema)` with ONE ADR-0087 D3 entry for the family; Option B — a recorded, reasoned exception per location; or a mixed answer per location. Four-axis analysis. 实际业务需求 — measured producers: dataSource.filter has ZERO authors outside spec test fixtures (3 record fixtures; no example, doc, skill or designer writes it; objectui's own pins author the tuple array 15 times and the record twice); the object-* doors have 5 in-repo authors (4 records on object-metric, 1 tuple array on object-grid, all showcase) and no designer producer (the block inspector curates neither filter nor dataSource); the record on object-metric is the ONLY shape its aggregate wire takes today (#15828), while the html tier declares `array` for it. 项目长远合理性 — the ruled direction is one orthography; two operands of one `and` (the Gate's mergeFilterNodes) in one vocabulary is the strongest structural argument; the record's extra expressiveness ($and/$or/$not) is not delivered by the consumer anyway (#6948 misreads it), so B preserves a capability the runtime does not honour; A closes the record-picker's two-orthographies-at-one-`??` hazard. 防 AI 写元数据犯错 — z.unknown() is the accept-anything incubator: today an AI following the manifest writes an array on object-metric and gets a 400, an AI following the showcase writes a record and gets an html-tier type-mismatch; both receipts are wrong somewhere. The fix is contract-first: make the wire take what one declaration says (#15828 first), then declare that one shape loudly at every door; B keeps the silent success receipt for the retired form. 创业阶段不扩散 — no staged window and no dual spelling: the record retires at all doors in one PR, the 5 showcase authors and 6 fixtures migrate in the same change; a per-door exception inside one family is the shape 6206-B refused for element:number.",
          "options": [
            "A — converge BOTH locations family-wide, one D3 entry, SEQUENCED: (1) #15828 first — the analytics route must take the array form on object-metric's aggregate path (contract question: widen AnalyticsQuerySchema.where to the FilterArray the service already lowers, or have the adapter lower arrays into a record) — the verbatim ui#6206-B 'fork back to triage' condition is live on that door; (2) then one spec PR: page.zod.ts:169 + the four doors + D3 entry + regen + 3 spec fixtures + 3 lint fixtures + 5 showcase authors + the 'every filter door refuses the record' twin of the census pin; objectui follow-ups at the pin (#7712 declarations; a #7663-style card for the 17 dataSource.filter test authors). Cost: ~5 declaration lines, 11 fixture/author edits, 0 renderer edits on 3 doors + the binding, metric gated on #15828.",
            "B — recorded exception at both locations: 0 schema edits; must rewrite or justify four .describe() strings that already say 'array/AST'; keeps refusing the tuple array objectui's pins author at dataSource.filter, keeps the metric's html-tier `array` arm contradicting the accepted record, keeps #6948's misread inside the declared accept-set, and makes 'platform-wide' read 'except the two places every data-bound block reads'.",
            "Mixed — A for dataSource.filter, object-grid, object-kanban, object-calendar now; object-metric excepted (its wire natively takes the record and refuses arrays): 4 declaration lines now, 1 later; buys nothing over A's sequencing and re-creates a per-door exception inside one family."
          ],
          "recommendation": "A, sequenced behind #15828. It agrees with triage-15442's 'for A' input (the AND-combination argument, now measured: every combined output is wire-legal AST) and with triage-15449's 'measure each read path first' (done: three doors lower the array today, one cannot). It DISAGREES with triage-15449's 'for B' input — z.unknown() was not a deliberate 'whatever the wire takes': it was derived from read points on 2026-08-13, twelve days before the ruling, and the wire does not take whatever — the metric's aggregate wire takes only the record. It also qualifies triage-15442's 'for B' input — three shapes do reach the consumer, but the spec never accepted two of them (tuple and rule arrays are refused at the binding today), so B does not preserve a working contract, it preserves a contradiction between the declaration and the consumer's own pins. If the maintainer rules #15828 in the adapter direction (lower arrays into a record for analytics), A still stands at the declaration; only the sequencing changes."
        }
      ],
      "out_of_scope_findings": [
        "filed as #15828 (objectstack): runtime `POST /analytics/query` refuses the array `where` the objectui adapter now sends for every array-form filter — AnalyticsQueryRequestSchema.where is FilterConditionSchema (api/analytics.zod.ts:50 / data/analytics.zod.ts:299; runtime/src/domains/analytics.ts:49-70, :102; dispatcher-plugin.ts:1152) while the service gate lowerAnalyticsWhere accepts filter AST; blocks Option A on object-metric and undercuts the element:number D3 entry's runtime claim (measured at the service gate, not the route).",
        "filed as #15829 (objectstack, finding): dashboard widget.filter (dashboard.zod.ts:387, :720) still declares FilterConditionSchema — a further ui#6206-B location with a live designer PRODUCER of the record form (objectui app-shell inspectors/datasetFilterCondition.ts).",
        "filed as objectui#7711: ObjectCalendar.getCalendarConfig reads schema.filter.calendar as the config container (ObjectCalendar.tsx:145, live at :271) — the #4034 map defect class, still on the calendar.",
        "filed as objectui#7712: object-kanban / object-calendar registrations declare no `filter` input (plugin-kanban/src/index.tsx registrations :208/:313/:420/:434; plugin-calendar/src/index.tsx :293/:303) while both renderers read schema.filter (ObjectKanban.tsx:263, ObjectCalendar.tsx:454) and the spec declares it — html-tier unknown-prop on a spec-declared key (#6678 class).",
        "filed as objectui#7713: content/docs/guide/objectos-integration.mdx:280 authors object-grid.filter as an `and:`-keyed object of rule entries — a fourth shape no reader lowers (convertFiltersToAST's equality branch, measured on the $or twin).",
        "NOT filed (already on file, cited): objectui#6948 (open) — convertFiltersToAST has no $and/$or/$not branch; this census adds the measured datum that an array VALUE ({ status: ['a','b'] }) lowers to '=' rather than 'in' on the same branch, and that both misreads sit inside FilterConditionSchema's accept-set at dataSource.filter today. objectui#7663 (open, pm:blocked) already tracks the record-picker registry description that still says record."
      ]
    }

    Generated by Claude Code

  7. claude commented on Sep 5, 2026

    @claude
    Contributor

    Census tables — #15442 (anchor) + #15449 (member), read on objectstack fc20f7bda and objectui pin a472b07, 2026-09-05T08:55Z–09:40Z UTC

    The JSON os-dev-report above is the record; these are the same readings as tables. No file moved under this claim.

    H1 — the declarations (fc20f7bda)

    entry props const declaration line describe
    ElementDataSourceSchema.filter (ui/page.zod.ts) — FilterConditionSchema.optional() :169 Additional filter criteria
    record:related_list RecordRelatedListProps z.array(ViewFilterRuleSchema) :1121 Additional filter criteria for related records
    record:related_list → add.source.filter (nested) same z.array(ViewFilterRuleSchema) :1157 Restrict which records the picker offers.
    element:number ElementNumberPropsSchema z.array(ViewFilterRuleSchema) :1851 …the one filter orthography every filter input in this map shares. The MongoDB-style record form is refused…
    element:record_picker ElementRecordPickerPropsSchema z.array(ViewFilterRuleSchema) :2220 …The binding-level dataSource.filter wins outright when both are set
    object-grid ObjectGridPropsSchema z.unknown() :2463 Base query filter (ObjectQL filter array/AST) — lowered to the wire $filter. THE key, singular…
    object-metric ObjectMetricPropsSchema z.unknown() :2620 Filter the aggregation is scoped by
    object-kanban ObjectKanbanPropsSchema z.unknown() :2664 Base query filter, handed to the wire $filter
    object-calendar ObjectCalendarPropsSchema z.unknown() :2719 Base query filter

    FilterConditionSchema in component.zod.ts: comments only (:1840, :2196). Map rows: :2901 / :3000 / :3017 / :3023–:3026. Binding aliases filters / where / criteria are did-you-mean refusals (unrecognized_keys), measured.

    H2 — in-repo author sweep (objectstack fc20f7bda; brace/indent-aware script over every tracked ts/tsx/js/json/yaml/md/mdx; controls fired)

    file:line key shape
    packages/spec/src/ui/page.test.ts:666 dataSource.filter record
    packages/spec/src/ui/page.test.ts:762 dataSource.filter record
    packages/spec/src/ui/page.test.ts:767 dataSource.filter record
    examples/app-showcase/src/ui/pages/my-work.page.ts:39 object-metric.filter record
    examples/app-showcase/src/ui/pages/my-work.page.ts:40 object-metric.filter record
    examples/app-showcase/src/ui/pages/my-work.page.ts:41 object-metric.filter record
    examples/app-showcase/src/ui/pages/index.ts:78 object-metric.filter record
    examples/app-showcase/src/ui/pages/my-work.page.ts:59 object-grid.filter AST tuple array
    packages/lint/src/validate-component-props.test.ts:483 object-grid.filter AST tuple array (clean-corpus control pin)
    packages/lint/src/validate-component-props.test.ts:488 object-metric.filter record (clean-corpus control pin)
    packages/lint/src/validate-empty-combinators.test.ts:240 object-grid.filter record ({ $or: [] })

    Zeros, each with its control: dataSource.filter in examples/** 0, hand-written content/docs/** 0, skills/** 0, YAML fences 0 (control: every dataSource mention — docs 26, examples 2, spec 55, lint 25 — re-grepped for a nearby filter; nothing beyond the rows above). check:yaml-examples reads only os:check-tagged YAML fences → unaffected either way. Generated references render both declarations as any (references/ui/page.mdx:267; component.mdx:314/:384/:459/:510).

    objectui pin a472b07 (26 rows): dataSource.filter AST tuple array ×15 (every *.elementDataSource / Gate test, e.g. record-picker-element-data-source.test.tsx:80, :126), record ×2 (plugin-list/src/__tests__/ListView.elementDataSource.test.tsx:154, ListView.sharedGate.test.tsx:217); object-* filter: tuple ×4 (ObjectGrid.elementDataSource.test.tsx:121, ObjectKanban…:146, ObjectCalendar…:114, ObjectMetric…:110), rule array ×2 (plugin-grid/README.md:468, :528), and:-keyed object ×1 (content/docs/guide/objectos-integration.mdx:280 → objectui#7713).

    H3 — the readers at the pin, per shape (production ObjectStackAdapter unless noted)

    reader file:line record tuple array (AST) rule array (ViewFilterRule[])
    composition seam core/src/data-scope/element-data-source.ts:235-252 lone: verbatim; with a view: AND via mergeFilterNodes same same
    Gate (12 blocks map filter: true: calendar, chart, metric, pivot, related-list, line-items, gantt, grid, kanban, list-view, map, timeline) react/src/element-data-source/ElementDataSourceGate.tsx:220-226 lowered to ['status','!=','done'] (measured); $and/$or/$not keys MISREAD to ['$or','=',…] (objectui#6948); array value → = verbatim lowered to tuples; unary rules kept
    AND-combined output mergeFilterNodes(viewRules, X) ['and',[[…]],X] — isFilterAST true true true
    object-detail, object-form ×3 gates plugin-detail/src/index.tsx:251, plugin-form/src/index.tsx:160/:284/:349 not read (no collection query) — —
    element:record_picker record-picker.tsx:108 composed?.filter ?? props.filter → :135 $filter verbatim → adapter convertQueryParams :3832-3851 lowered (convertFiltersToAST) passthrough lowered (translateFilterArray)
    object-grid ObjectGrid.tsx:1461-1462 toFilterNode(schema.filter) → :1811 $filter lowered (#6948 caveat) verbatim lowered
    object-kanban ObjectKanban.tsx:263 $filter: schema.filter verbatim adapter lowers; ValueDataSource matches adapter/ValueDataSource match adapter lowers; ValueDataSource REFUSES row-wise ('not an array' → empty)
    object-calendar ObjectCalendar.tsx:454 verbatim; and :145 reads filter.calendar as config (objectui#7711) as kanban (+ config misread) as kanban as kanban
    object-metric with aggregate (all 5 showcase metrics) ObjectMetricWidget.tsx:245-253 → adapter analytics branch index.ts:4975-5003 → POST /analytics/query → AnalyticsQueryRequestSchema (runtime/src/domains/analytics.ts:49-70) works (native where) 400 — where refuses every array (measured on the schema; route read, not run) → #15828 400 (same)
    object-metric without aggregate ObjectMetricWidget.tsx:269 ds.find({ $filter }) lowered passthrough lowered
    designer block inspector app-shell/.../PageBlockInspector.tsx + previews/block-config.ts not curated — advanced-key JSON editor, shape-agnostic same same

    Pin → objectui main (51eb515): only an import line moved on ObjectGrid.tsx / ObjectCalendar.tsx (#7660); every other read-path file byte-identical.

    H4 — spec-side readers

    reader reads under Option A
    packages/lint/src/validate-component-props.ts (WARNING tier, :123) properties vs ComponentPropsMap; dataSource.object (:177) only for object inference showcase record/tuple authors become warnings; clean-corpus pin validate-component-props.test.ts:475-495 reds
    validate-page-field-bindings.ts:331 props.filter on record:related_list only, as rule objects untouched
    page-walk.ts:115-117 dataSource.object only untouched
    validate-empty-combinators.ts via filter-walk.ts:60 every key named filter/filters/runtimeFilter/relatedListFilter, record nodes only fixture :240 (object-grid { $or: [] }) needs replacing
    #14406 census pin component.test.ts:1896-1920 holdout = "declares filter AND refuses the rule array" stays green either way; cannot see z.unknown() by construction — needs the "refuses the record" twin
    designer (PageBlockInspector) curates no filter/dataSource for any object-* block (calendar has no entry) not broken; no guard
    objectui parity registry-inputs-spec-parity.test.ts grid/metric declare filter type: 'array' (plugin-grid/src/index.tsx:218, plugin-dashboard/src/index.tsx:207); kanban/calendar declare none (objectui#7712) parity holds trivially

    H5 — the ruling's reach (verbatim)

    objectui#6206 comment 5406409590 (2026-08-25): "Maintainer ruling (2026-08-25, batch adjudication, batch 4; verbatim: 「同意」): Option B — one filter orthography platform-wide: the spec entry ComponentPropsMap['element:number'].filter aligns to the platform's ViewFilterRule array form, rather than leaving this one block as the record-shaped exception every author and AI would have to remember. Measurement-first is binding: the upstream card must verify the adapter/runtime behaviour on array filters for this element's read path before editing — if the runtime genuinely cannot take the array form, that is a fork back to triage, not a silent flip to Option A." — names element:number.filter only; neither element bindings nor the object-* family. #7751 (ruling 2026-08-12, direction A; landed 8f20201f1 2026-08-13, PR #8524, "derived from objectui's renderer read points") — twelve days before ui#6206-B; nothing in it addresses filter's shape ⇒ z.unknown() is a read-point record, not a deliberate exception.

    H6 — cost per option per location

    dataSource.filter — A dataSource.filter — B four object-* doors — A four object-* doors — B
    declaration lines 1 (page.zod.ts:169) 0 4 0 (+ rewrite/justify 4 describes)
    ADR-0087 one family D3 entry recorded exception same family D3 entry recorded exception
    generated artifacts regen (authorable-surface, json-schema, api-surface, page.mdx) 0 regen (component.mdx) 0
    in-repo authors to migrate 0 examples/docs; 3 spec fixtures (page.test.ts:666/:762/:767) 0 5 showcase (my-work.page.ts:39/:40/:41/:59, index.ts:78); 3 lint fixtures (validate-component-props.test.ts:483/:488, validate-empty-combinators.test.ts:240) 0
    renderers to touch 0 (12 gate blocks + picker lower the array today) 0 grid 0 · kanban 0 · calendar 0 · metric blocked by #15828 (aggregate route refuses every array) 0
    pins that flip the 3 spec fixtures none lint clean-corpus pin; empty-combinators fixture; census pin stays green (add the record-refusal twin) none
    designer 0 0 0 0
    showcase / docs nothing red — showcase validate WARNS (warning tier, no --strict in CI); references regenerate —
    objectui at the pin 15 tuple + 2 record test authors become off-spec fixtures (a #7663-style card); ElementDataSourceConfig doc spec keeps refusing the tuple array the pins author grid/metric registry already array; kanban/calendar need the declaration (#7712) html tier already type-mismatches a record on grid/metric
    stays silently wrong under B — two orthographies at one ?? on the picker; $and/$or/$not legal at the binding but misread by every gate block (#6948) — accept-anything doors; tuple array on metric+aggregate is a production 400 while pin :110 authors it; declared vs enforced split across tiers

    Recommendation (four-axis block applied in the JSON open_questions): A, family-wide, one D3 entry, sequenced behind #15828 — the analytics route must take the array form on object-metric's aggregate path first (the ruling's own "fork back to triage" condition, live on that door); then one spec PR for both locations.


    Generated by Claude Code

  8. claude commented on Sep 5, 2026

    @claude
    Contributor

    Decision requested — the consumer census is in; the disposition of two more ui#6206-B locations is a contract ruling (domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T11:45Z)

    Provenance: the folded measurement dispatch for #15442 (anchor) + #15449 (member) — anchor report 5550899105, readable H1/H2/H3/H6 tables 5550899184, member report 5550899288; all readings on origin/main fc20f7bda and the objectui pin a472b07 (= .objectui-sha) between 2026-09-05T08:55Z and 09:40Z; nothing edited (the branch claude/issue-15442-filter-orthography-consumer-census is an empty claim marker at fc20f7bda, zero commits). Per the claim (5550679316) the census was the deliverable and the seat turns it into one decision card: this seat moves the anchor into the decision inbox in the same stroke (pm:dispatched → needs-user-decision, assignee cleared) and blocks the member #15449 on it (pm:blocked + Blocked-by: #15442) — one ruling covers both locations. The seat does not rule: the options differ by a published-contract narrowing at five doors (ADR-0087 D3, manual floor), the dev's recommendation disagrees with two of the three triage inputs, and the sequencing gate (#15828) is itself a contract question.

    Measured state (from the census; re-check commands are in the anchor report)

    Options × real cost

    option what real cost
    A — converge both locations, family-wide, sequenced page.zod.ts:169 + the four doors → z.array(ViewFilterRuleSchema); ONE ADR-0087 D3 entry for the family; regen (authorable-surface, json-schema, api-surface, docs references); migrate 3 spec fixtures + 3 lint fixtures + 5 showcase authors; add the "refuses the record" twin of the census pin ~5 declaration lines, 11 fixture/author edits, 0 renderer edits on the binding and three doors; object-metric gated on #15828 (the analytics route must take the array on the aggregate path first — or the adapter lowers arrays into a record); objectui follow-ups at the pin (#7712 declarations; a #7663-style card for the 17 dataSource.filter test authors)
    B — recorded exception at both locations 0 schema edits; rewrite or justify the four .describe() strings that already say "array/AST" keeps refusing the tuple array the consumer's 15 pins author; keeps the picker's two orthographies resolved by one ??; keeps #6948's misread inside the declared accept-set; keeps the html-tier array arm contradicting the accepted record on grid / metric; "platform-wide" comes to mean "except the two places every data-bound block reads"
    Mixed — A everywhere except object-metric 4 declaration lines now, 1 later buys nothing over A's sequencing and re-creates a per-door exception inside one family — the shape ui#6206-B refused for element:number

    Coupled question (sequencing gate) — #15828's direction: (i) widen AnalyticsQueryRequestSchema.where in spec to the filter array the service gate lowerAnalyticsWhere already lowers (a spec-lane card, lands before the converge PR), or (ii) have the objectui adapter lower arrays into a record for the analytics branch (an objectui-lane card; cross-repo Blocked-by + pin lag before the converge). Under either answer A stands at the declaration; only the sequencing changes. The seat recommends ruling #15828's direction in the same stroke so the converge dispatch is not blocked twice.

    四维分析(从业务角度)

    一句话问题:element 绑定层的 dataSource.filter 与四个 object-* 块的 filter 门,是按 ui#6206-B「全平台一种过滤器拼写」收敛到 ViewFilterRule[](一条家族 D3 条目,object-metric 先等 #15828),还是在这两处登记「有理由的例外」保留 record 形?

    推荐:A,家族整体,排在 #15828 之后。四轴同向。需要维护者拍板的点:① 五个门的已发布契约收窄(D3 手工地板);② #15828 的方向(spec 放宽 analytics where,还是 objectui 适配器降型)决定先后与车道;③ dev 的推荐与分诊两条「倾向 B」的输入相反 —— dev 的反驳:z.unknown() 早于裁定十二天、记录的是读点不是决定;而「线路什么都吃」不成立,metric 线路只吃 record。本席不代裁。裁 A ⇒ 先按 #15828 的方向立/派前置卡,再一次 spec PR 关掉 #15442 + #15449;裁 B ⇒ 两卡各留一条例外记录评论并关闭,四条 .describe() 改写为独立小卡;裁 Mixed ⇒ 本席按 A 的形状派发但把 metric 门写成显式例外行。

    置信缺口:analytics 路由函数本身「读了没跑」(运行时依赖闭包未构建,它调用的 schema 跑了);适配器 5000 行文件读了没跑;objectui 主干相对 pin 只漂了一行 import;#15829(dashboard widget.filter,同家族第三处,但有活的设计器 record 生产者)未折进本决策 —— 成本形状不同,由分诊单独定级。


    Generated by Claude Code

  9. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 5, 2026
  10. 7 remaining items

  11. os-bill commented on Sep 9, 2026

    @os-bill
    Collaborator

    Released pm:blocked → pm:queue — the pin criterion this card was parked behind is MET

    domain:spec seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-09T13:0xZ. Surfaced by the half-state patrol anchor #9857 (07:50Z run) as an H19 row, "every target it names is closed". H19 is report-only — it reports a candidate, it does not decide it — so the release was run against the state model's two mechanical double-checks, not against the row's sentence.

    ① The condition carried by the MOST RECENT conversion comment

    reading value
    conversion comment 5571612810, 2026-09-07T13:49:44Z, paired with labeled pm:blocked at 13:50:19Z
    condition it carries Blocked-by: #16626, with an executable criterion written out: git merge-base --is-ancestor 53ded82b "$(git show origin/main:.objectui-sha)"
    #16626 closed completed 2026-09-08T07:35:37Z

    ⚠️ The criterion is what was checked, ⛔ not the blocker's closed state — a closed pin-bump card is not by itself proof the pin moved. This lane's own standing rule is that the cross-repo unlock criterion is the consumer can install it, not that upstream merged it, and that rule is exactly why this card was parked in the first place.

    leg reading
    git show origin/main:.objectui-sha (at ce7bae8b4) 53ded82bf7a494f54e344e19099dbf00854b8694
    objectui compare 53ded82b…53ded82bf7a4 identical, ahead 0 / behind 0
    ⭐ lit control — the OLD pin a472b07167a3 vs the new one ahead_by: 34

    The pin is now the fix commit itself, so ancestry is trivial. The control is there because a compare API that returned "identical" for everything would produce the same green: the old pin reads 34 commits behind, so the endpoint is answering, and the pin genuinely moved. ⛔ Ancestry was taken through REST, not git merge-base locally — this checkout can be shallow and a shallow clone reports false non-ancestry.

    ② No merged PR newer than that conversion comment DELIVERS this card

    Three merged PRs cross-reference the card after 2026-09-07T13:49Z, so ② has to be asked seriously rather than waved past. It was asked with the repo's own predicate, ⛔ not a hand-rolled reading of "is there a merged PR on the card":

    PR merged deliveryEvidence(pr, 15442) prDeliversCard
    #16697 2026-09-08T02:13:02Z null false
    #16777 2026-09-08T14:53:20Z null false
    #16788 2026-09-08T07:35:35Z null false
    ⭐ lit control #17068 vs its own card #16185 — "closing-keyword" true

    All three are passing mentions; none carries a closing keyword or a Part of for this card. The control fires on a PR known to deliver, so the three nulls are a reading and not a dead predicate. ⇒ ② does not refuse. The card did not move on past its own condition.

    Premise re-derived on the tree, because a released block is never a reflexive release

    git show origin/main:packages/spec/src/ui/page.zod.ts at ce7bae8b4 — ElementDataSourceSchema opens at :156 and still carries, at :169:

    filter: FilterConditionSchema.optional().describe('Additional filter criteria'),

    The work is undone and the ruling (5559671308, option A) stands. ⇒ Released.

    ⛔ Three constraints the re-dispatch inherits — these are measured, not guesses

    The flight that parked this card left findings that cost a dev's run to obtain. They are carried here so the next dispatch does not re-buy them:

    1. The ruling's "3 spec fixtures" is 4. page.test.ts:633 calls ElementDataSourceSchema.parse({ … filter: { status: 'pending' } }) directly — a record-form author that a dataSource-keyed sweep cannot see. The other three are :665-666, :762, :767.
    2. A fifth accept-anything filter-shaped door the ruling does not name: object-grid.defaultFilters (component.zod.ts, z.unknown(), "Legacy base-filter fallback, read only when filter is absent"). ⛔ Out of scope for the re-dispatch, and explicitly so rather than silently. The ruling names dataSource.filter plus the four object-* filter doors; defaultFilters is a different key and is not among them. Whether it belongs to the family is the ruler's call, ⛔ not the dev's — see the separate routing note below.
    3. Author-sweep counts otherwise confirmed: 3 lint fixtures and 5 showcase authors, and hand-written docs and skills author neither key.

    Housekeeping, flagged rather than done

    The card still carries finding alongside a full grading (bug / priority:p2 / domain:spec). finding means awaiting first grading, and leaves the moment grading happens, so it is stale here. ⛔ This seat does not grade and does not strip grading labels — that is the triage seat's write. Flagged for the next triage fire.

    Its fold partner #15449 stays pm:blocked: its condition is two targets, and while #16626 has cleared, this card is its other target and is still open.


    Generated by Claude Code

  12. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 9, 2026
  13. self-assigned this
    on Sep 9, 2026
  14. os-bill commented on Sep 9, 2026

    @os-bill
    Collaborator

    Claim: PM loop — domain:spec seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-09T23:4xZ. Dispatching os-dev on branch claude/issue-15442-filter-orthography-converge. Container & model: mode:subagent, claude-fable-5-1.

    File surface: packages/spec/src/ui/page.zod.ts (the ElementDataSourceSchema.filter binding-level door) and packages/spec/src/ui/component.zod.ts (the four object-* filter doors), plus their fixtures and the changeset. ⛔ object-grid.defaultFilters is fenced OUT — see below.

    Clause-②: yes
    

    ⚠️ Declared yes conservatively and provisionally. The converge is two-directional: the MongoDB-style record stops being accepted (narrowing) while the ViewFilterRule tuple array starts being accepted where it was not (widening). A widening in either direction forces yes. ⛔ But do not inherit this — measure it on your finished diff and report. If it measures no, say so and I will correct the claim; the declaration is the seat's to move and I now know the mechanism.

    ⭐ New standing discipline, and this card is the first to carry it: run node scripts/pm/check-clause2-carriers.mjs --pair <PR> yourself, at your first push, and report the exit code. Five of my defects today came from declaring at claim time and only discovering the truth at landing time. It cannot run before a PR exists, so your first push is the earliest possible moment.

    Three constraints inherited from the flight that parked this card — ⛔ do not re-buy them

    These cost a dev's round to obtain and are on record in the release comment 5602085198:

    1. The ruling's "3 spec fixtures" is 4. page.test.ts:633 calls ElementDataSourceSchema.parse({ … filter: { status: 'pending' } }) directly — a record-form author a dataSource-keyed sweep cannot see. The other three are :665-666, :762, :767.
    2. ⛔ object-grid.defaultFilters is OUT OF SCOPE, explicitly rather than silently. It is z.unknown(), described as "Legacy base-filter fallback, read only when filter is absent" — a different key from the five the ruling names (dataSource.filter plus four object-* filter doors). Whether it belongs to the family is the ruler's call, ⛔ not yours. It is recorded on the family's direction card [Direction · v18] Retire the flat object/filter data-binding family — dataSource as the single data-binding door (re-anchor of the deleted #6590 tracker) #11509. If you touch it, you have widened the PR.
    3. Author-sweep counts otherwise confirmed: 3 lint fixtures, 5 showcase authors; hand-written docs and skills author neither key.

    Premise re-check before you touch anything

    The ruling is comment 5559671308 (option A: converge the binding-level dataSource.filter and the four object-* filter doors to ViewFilterRule). Its sequencing gate — the objectui pin — was the reason this card was parked, and it is discharged: .objectui-sha on origin/main is now 53ded82bf7a4…, identical to the fix commit, verified by REST compare with a lit control (the old pin reads 34 behind).

    ⛔ Re-derive both halves yourself on origin/main before implementing:

    • ElementDataSourceSchema.filter still declares FilterConditionSchema (it was at page.zod.ts:169, and origin/main has moved many times today);
    • the four object-* filter doors in ComponentPropsMap still declare what the card says they do.

    ⚠️ And look at what your controls match, not just their counts. My worst defect today was a correctly-built pattern returning three hits I never read — they were a rename comment and a tombstone, and I reported the field as live.

    Standing constraints

    • ⛔ One dedicated worktree; never edit /home/user/objectstack; never git stash; never kill a process by name.
    • ⛔ Never touch content/docs/releases/.
    • Draft PR, body line 1 Fixes #15442, needs:contract-review on the PR carrier (the card has it). ⛔ Do not flip ready or enqueue.
    • Merge origin/main through bash scripts/pm/os-regen-merge.sh; regenerate with the repo tooling, ⛔ never hand-edit a generated file.
    • Derive gates with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (⛔ no hand-fed path list), run every family on the final head, land exit codes to disk before reading, reconcile with --ran. PREREQUISITE NOT MET is NOT MEASURED — never green, never red.
    • ⛔ Run the full package suite, never a hand-picked subset. A round today reported green while CI was red because it tested selected files; two more rounds then had to clean it up.
    • ⛔ Read the PR's own CI before reporting done — newest run per check name, not the raw run list, not the required subset.
    • Heavy verification under bash scripts/pm/os-verify-lock.sh; ⛔ a queue-timeout is never a pass. Two sibling agents contend for it.
    • Judge the changeset yourself. The card's own acceptance expects minor with a BREAKING banner; ⛔ re-derive rather than inherit — an inherited grade broke Check Changeset twice on this lane today.

    Report in the usual os-dev-report JSON on this card, stating the --pair exit code, the measured clause-② direction, and the four fixtures' disposition.


    Generated by Claude Code

  15. os-bill commented on Sep 10, 2026

    @os-bill
    Collaborator

    os-dev-report

    {
      "issue": 15442,
      "status": "done",
      "branch": "claude/issue-15442-filter-orthography-converge",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17257",
      "premise_still_valid": true,
      "premise_per_card": {
        "15442": "TRUE on origin/main ae19f5edb (branch base, 2026-09-09T22:30Z): page.zod.ts:169 read `filter: FilterConditionSchema.optional().describe('Additional filter criteria')`, import at :9 — the only two occurrences in the file, both read. Sequencing gate discharged: .objectui-sha = 53ded82bf7a494f54e344e19099dbf00854b8694, the objectui#7754 fix commit itself.",
        "15449": "TRUE on ae19f5edb: `filter: z.unknown().optional()` at component.zod.ts :2466 object-grid / :2624 object-metric / :2672 object-kanban / :2763 object-calendar (lines moved from the card; describe strings matched). `defaultFilters` at :2468 untouched (out of scope, #11509)."
      },
      "summary": "Implemented ruling 5559671308 option A family-wide: ElementDataSourceSchema.filter and the four object-* filter doors now declare z.array(ViewFilterRuleSchema); one ADR-0087 D3 semantic entry `element-data-source-and-object-block-filter-rule-array` (protocol major 18), registry/spec-changes/upgrade-guide regenerated by the tooling; FilterConditionSchema import leaves page.zod.ts; ADR-0122 aliases ElementDataSourceParsed / ObjectMetricPropsParsed / ObjectKanbanPropsParsed / ObjectCalendarPropsParsed declared and the four isomorphic pins deleted (815 to 811); docs references, api-surface and export-origins regenerated via check:generated --fix (authorable-surface unchanged). Changeset: @objectstack/spec minor with the literal **BREAKING** banner and the `adr-0087: registered` marker (gate exit 0; PR CI `Check Changeset` success). FOUR spec fixtures disposition, all 改拼写 to the rule array: page.test.ts:633 (direct ElementDataSourceSchema.parse, the one a dataSource-keyed sweep misses), :665-666, :762, :767. Two further spec fixtures at the object-* doors that no count named (component.test.ts #7751 block: the corrected #7750 grid node with the AST tuple, and the three showcase metric records in the clean-corpus control) 改拼写 — found by the full-suite run, not by the sweep. Lint fixtures: validate-component-props.test.ts:483 and :488 改拼写; validate-empty-combinators.test.ts:240 整个换掉 (page row dropped from the covers-case; `pages` stays in EMPTY_COMBINATOR_SURFACES). Showcase authors: my-work.page.ts:39/:40/:41 + index.ts:78 records and :59 AST tuple migrated to rule arrays, plus the showcase pin test/my-work-visibility.test.ts:190 which authored the tuple. New pins: binding accept/normalize/refuse-record-at-filter/refuse-tuple-at-filter.0/two-keys-same-verdict; per-door it.each over the four object-* doors; and the ruled twin of the #14406 census pin — every filter door in ComponentPropsMap refuses the record, asserted over the whole map by shape. Draft PR #17257 body line 1 `Fixes #15442`, line 2 `Fixes #15449` (the ruling folds the member into this PR), a line-start `Clause-②: yes`, needs:contract-review on the PR; not flipped ready, not enqueued, gate label not cleared. CLAUSE-② MEASURED on the finished diff: yes — two-directional at the binding (the record leaves the accept set; the ViewFilterRule OBJECT array enters it, refused before as invalid_type expected record), pure narrowing at the four z.unknown() doors; the AST TUPLE array is newly accepted nowhere — it was refused at the binding before and stays refused one hop deeper (filter.0), and leaves the four doors' accept set. The claim's 'tuple array starts being accepted' reads as the rule array. --pair 17257 exit codes: first run exit 4 (C1: the PR did not yet carry needs:contract-review — that run raced my additive label POST), second run exit 4 (C1 only on the fold member #15449: it declares Clause-②: yes in its os-trump claim and is delivered by this PR via Fixes, but carried no gate label), third run exit 0 for BOTH pairs after I added needs:contract-review to #15449 additively (POST .../issues/15449/labels, read-back shows the union; a write on the fold member the seat may undo — reported, not hidden). DEVIATIONS: (1) the REST PATCH of the PR body (to fill the Verification section after gates) stored my session-URL footer AND a platform-appended bare footer under it — measured, not re-sent (AGENTS.md footer rule); the rest of the body is byte-identical to what was sent. (2) The first spec full-suite run was red 4/13240: two pre-existing object-* fixtures and two defects in my own new pins (issuesUnder matched filter.0; PageComponentSchema does not dispatch properties through ComponentPropsMap) — fixed in commit 6274af365 and the whole suite re-run green. (3) Both long verification calls exceeded the 10-minute foreground cap and were backgrounded by the harness; I waited in the foreground (tail --pid on the runner's own PID) and read exits from disk. mcp_calls 0: every read via public REST GET / git, every write via REST with GITHUB_TOKEN (PR create 201, labels 200, body PATCH 200, this comment).",
      "tests": "All exit codes redirected to disk before reading. Head 6274af365 (the PR head, no later commit). Builds under scripts/pm/os-verify-lock.sh slot issue-15442-converge: spec build VERDICT command-exit 0 (held 151s, waited 374s); turbo build of lint^... and example-showcase^... command-exit 0, Tasks 61 successful/61 (7m13s); post-sweep spec rebuild exit 0, tree clean. FULL package suites, never a subset, one lock acquisition, six exit files all 0: @objectstack/spec test `Test Files 469 passed | 1 skipped (470)`, `Tests 13238 passed | 1 skipped (13239)`, 385s; spec typecheck exit 0 (`check:test-typecheck: OK … 54 file(s) / 261 error(s) / 145 pinned signature(s)`, pre-existing ledger); @objectstack/lint test `Test Files 103 passed (103)`, `Tests 3680 passed (3680)`, typecheck exit 0; @objectstack/example-showcase test `Test Files 28 passed (28)`, `Tests 379 passed (379)`, typecheck exit 0. REVERSE VERIFICATION (trap EXIT INT TERM, absolute paths, on the committed state, under the lock) — Leg A runtime cross-package (lint test resolving @objectstack/spec through its exports to dist; dist preflight `marker present in 18 built files`): mutated validate-component-props.test.ts:488 back to the record (on-disk anchors injected 1 / removed 0) → vitest exit 1, `1 failed | 44 passed`, failing case `stays SILENT on the corrected #7750 node and the corpus metric shapes`; restore via `git checkout HEAD -- PATH` → git diff HEAD empty exit 0, hash-object 776b8b641dfc… equals HEAD blob, re-run 45/45. Leg B type cross-package (lint tsc against the rebuilt .d.ts): probe with record at ElementDataSource, record at ObjectMetricProps, tuple at ObjectGridProps, rule array at ElementDataSource → exit 1, exactly 3 errors on the 3 off-spec lines (TS2353 ×2, TS2322), 0 on the rule line; probe removed → exit 0; porcelain 0 lines. Direction observed: red as expected on both legs. GATES: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (stderr line 1 names objectstack-ai/objectstack at 6274af365; --repo holds) → 108 commands, each run to its own log with its exit in a ledger; `--ran` reconcile: `108 derived famil(ies) accounted for — 108 run, 0 NOT-MEASURED`, exit 0. Readings: 100/108 exit 0 first pass; six spec dist-reading gates (api-surface, dual-source-exports, entry-nameability, exported-any, generated, skill-examples) refused on their own prerequisite (dist/.build-input-hash-dts recorded the pre-test-edit src digest) = NOT MEASURED first pass; after the rebuild all six exit 0 with own verdict lines (`public API surface + factory signatures unchanged ✓`; `no new dual-source exports`; `0 new unnameable`; `no exported type resolves to any`; `All 15 generated artifacts are up to date`; `258 prose examples type-check`). `pnpm check:dual-build-cjs-loads` exit 3 PREREQUISITE NOT MET (seven untouched packages have no dist here: studio, client-react, embedder-openai, knowledge-memory, knowledge-ragflow, organizations, service-cluster-redis) = NOT MEASURED. `check:react-declaration-parity` exit 1 `MANIFEST is not set` — the gate's own text calls that a reading; it is the on-demand pin-bump gate needing objectui's sdui.manifest.json from a browser run, not produced here, never a CI job — reported as exit 1 with that reason. Outside the derivation: check-adr-0087-registration exit 0 (registered element-data-source-and-object-block-filter-rule-array), check:nul-bytes exit 0. Ablation not applicable beyond the two legs above (no guard deleted). PR CI on 6274af365, newest run per check name, read at report time: 33 names — success: Build Core, Build Docs, Check Changeset, Check Documentation Links, Dogfood Regression Gate (+1/3, 2/3, 3/3), Dogfood Verify CLI, Flag docs affected by code changes, Governed Surface Queue Guard, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Spec property liveness, Temporal Conformance (live PG + MySQL), Test Core 1/6 2/6 4/6 5/6 6/6, Type Check · consumer gates / debt ledger / source gates / workspace, TypeScript Type Check, filter; skipped: Auto Label, Check PR Size, Console Pin Gate, Packed-tarball smoke; in_progress at read time: Lint & Repo Gates, Test Core (3/6). Nothing red.",
      "gates": {
        "derived": 108,
        "run": 108,
        "reconcile_exit": 0,
        "not_measured": [
          "pnpm check:dual-build-cjs-loads (exit 3 PREREQUISITE NOT MET, seven untouched packages unbuilt)"
        ],
        "red_by_reading": [
          "check:react-declaration-parity exit 1 — MANIFEST is not set (on-demand pin-bump gate, no manifest in this container)"
        ],
        "first_pass_prerequisite_refusals_rerun_green": [
          "check:api-surface",
          "check:dual-source-exports",
          "check:entry-nameability",
          "check:exported-any",
          "check:generated",
          "check:skill-examples"
        ]
      },
      "clause_2": {
        "declared_by_claim": "yes (provisional)",
        "measured": "yes",
        "direction": "two-directional at ElementDataSourceSchema.filter (record leaves, ViewFilterRule object array enters); narrowing-only at the four object-* doors (z.unknown accepted everything); the AST tuple array is newly accepted nowhere",
        "pair_exit_codes": {
          "run1_before_pr_label": 4,
          "run2_after_pr_label_member_unlabelled": 4,
          "run3_after_member_label": 0
        }
      },
      "fixtures": {
        "page.test.ts:633": "改拼写 → rule array (direct parse; the fourth)",
        "page.test.ts:665-666": "改拼写",
        "page.test.ts:762": "改拼写",
        "page.test.ts:767": "改拼写",
        "component.test.ts #7751 grid tuple + metric records": "改拼写 (uncounted by every sweep; found by the suite)",
        "validate-component-props.test.ts:483/:488": "改拼写",
        "validate-empty-combinators.test.ts:240": "整个换掉 — page row dropped, surface kept",
        "showcase my-work.page.ts:39/40/41/59 + index.ts:78 + test/my-work-visibility.test.ts:190": "改拼写"
      },
      "line_budget": "not applicable — no skills/** file touched (gen:skill-docs / gen:skill-refs both reported up to date)",
      "mcp_calls": "0 — issue/PR reads via public REST GET and the tool-results files; objectui pin reads via git show 53ded82b in the sibling checkout (never edited); writes via REST with the container token: PR create 201, labels POST 200 (PR, #15449), PR body PATCH 200, this comment POST",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: `pages` in EMPTY_COMBINATOR_SURFACES (packages/lint/src/validate-empty-combinators.ts) now has no spec-legal record filter to judge on a page (dataSource.filter and the four object-* doors are arrays); the walker is key-name based so the retired record form still gets the located error beside the schema refusal — dead-by-spec coverage, not a defect. 承接者:无.",
        "noted, not filed: the generated content/docs/references/ui/page.mdx renders the nested rule's `operator` as optional in the PageComponent.dataSource row and required in the ElementDataSource row — how build-docs prints a z.preprocess in a nested position; pre-existing for every other rule-array door (component.mdx shows the same for element:number). 承接者:无.",
        "noted, not filed: two object-* fixtures in packages/spec/src/ui/component.test.ts (the #7751 block) mirrored the showcase shapes and were counted by no sweep in the thread (the ruling's '3 spec fixtures' counted dataSource.filter authors only) — migrated here; a future sweep for this family should include properties.filter authors in spec tests. 承接者:the seat's sweep notes.",
        "NOT filed (ruling assigns to the seat): the seventeen objectui dataSource.filter test authors at pin 53ded82b (fifteen tuple arrays, two records) and #7712's kanban/calendar registry declarations — objectui cards to be filed by the seat.",
        "NOT touched, per dispatch: object-grid.defaultFilters (z.unknown, different key) — the ruler's call on #11509."
      ]
    }

    Generated by Claude Code

  16. os-bill commented on Sep 10, 2026

    @os-bill
    Collaborator

    os-dev-report

    {
      "issue": 15442,
      "status": "done",
      "branch": "claude/issue-15442-filter-orthography-converge",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17257",
      "premise_still_valid": true,
      "summary": "Patch round on the existing draft PR #17257 for exactly the two findings the seat held it for; no fresh implementation, no new branch, no force-push. Commit 3d7f4eda0 on top of 6274af365, 3 files / 35 insertions / 13 deletions. F1: one at-rest sentence added to the reason of the D3 entry 18.element-data-source-and-object-block-filter-rule-array, matching the register of the precedent entry 17.view-filter-rule-value-shaped-by-operator (its at-rest passage runs :38-50, not :38-47 as the order says — a 3-line offset, the passage itself is exactly where the order points). All three of the review's legs re-verified independently before writing: SemanticMigration converts nothing by its own type (types.ts:30-41, so the order's :29-40 is off by one), this branch adds no D2 conversion (packages/spec/src/conversions/ is absent from the 17-path diff), and applyConversionsToStoredItem (database-loader.ts:831) replays the full chain with includeRetired:true and never validates, by its own contract in conversions/stored.ts. registry.ts follows by gen:migration-registry and that one sentence is its entire delta. F2 and the partial-D2 direction question deliberately untouched: the new sentence says this disposition adds no D2 conversion, and does NOT claim none is possible, so it does not pre-empt the ruler. F3: the pages row restored byte-identical to the base tree (git diff against the merge-base now shows only the added explanatory comment for that file), plus page \"home\" back in the expected list and pages back in the it() title. Nothing weakened. Labels read back after CI: needs:contract-review still on the PR and on both cards, PR still draft.",
      "tests": "All exit codes landed to disk before reading (cmd > log 2>&1; EXIT=$?), never through a pipe; heavy runs via scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=os-dev-15442, verdict read from the wrapper's own VERDICT command-exit line. FULL PACKAGE SUITES (not hand-picked subsets): pnpm --filter @objectstack/lint test -> exit 0, 103 files / 3680 tests passed; pnpm --filter @objectstack/spec test -> exit 0, 469 passed | 1 skipped (470 files), 13238 passed | 1 skipped. TYPECHECK: pnpm --filter @objectstack/spec --filter @objectstack/lint typecheck -> exit 0 (lint's check:test-typecheck also OK, 2 files / 6 errors held in its shrink-only debt ledger, unchanged). F3 EITHER SIDE OF THE RESTORE, same command both times (vitest run src/validate-empty-combinators.test.ts): BEFORE the restore exit 0, 21 passed; AFTER the restore exit 0, 21 passed (the count is unchanged because the row lives inside an existing case; the restored assertion is strictly stronger, 6 expected where-values instead of 5). ABLATION proving the restored row is a real measurement and not a vacuous pass: deleting { key: 'pages', kind: 'page' } from EMPTY_COMBINATOR_SURFACES in validate-empty-combinators.ts turned it RED, exit 1, 1 failed | 20 passed, and the assertion diff names exactly the one lost value, page \"home\" (expected Array(5) to deeply equal Array(6)). Mutation proved on disk before running (anchor grep -c 1 -> 0; git hash-object 8d84ed07 differs from the HEAD blob 7b6eee5a) and the restore leg proved after (hash back to 7b6eee5a, identical to git rev-parse HEAD:path; git diff HEAD for that path empty; git status shows only my two intended files). The script carried trap on a RESTORE function for EXIT INT TERM with an absolute REPO_ROOT path and git checkout HEAD -- path, never a bare git checkout. No dist/build leg was owed: the test imports the rule relatively as ./validate-empty-combinators.js, so vitest resolves it straight from src. MIGRATION AND GENERATED GATES: check:migration-registry exit 0 (self-test ok, registry current at 193 semantic / 165 retired-key / 116 retired-def), check:spec-changes exit 0, check:upgrade-guide exit 0, check:generated exit 0 with all 15 artifacts up to date. First check:generated run was exit 1 on check:api-surface, read correctly as the gate's own documented phantom (a .build-input-hash-dts mismatch because my src edits post-dated the build, NOT a reported export removal) and cured by pnpm --filter @objectstack/spec build, NOT by gen:api-surface. OTHER DERIVED FAMILIES, all exit 0: check-adr-0087-registration --base origin/main, check-changeset-no-major --base origin/main, check-empty-changeset --base origin/main, check:changeset-gate-self-tests, check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias, check:type-check-coverage, check-comment-mask-adoption, check-comment-mask-corpus, check-spec-docblock-symbol-anchors, check-keyed-text-bounds, check:pm-widening-tells, check:doc-anchors, packages/lint check-reference-carrier-shape, check-undeclared-dep-imports. Plus a manual control-byte self-scan over all three touched files (grep -naP over the C0/DEL classes): no hits. GATE DERIVATION: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run twice (before and after git fetch origin main), identical both times, 108 commands over the same 17 paths. It flags STALE TREE, 12 commits behind origin/main, with the staleness confined to scripts/pm/check-half-states.mjs and scripts/pm/check-skill-line-ratchet.mjs. Neither is in the 108 and neither reads anything in my diff (no skills/** path, no half-state path), and the order forbids rebase/merge, so HEAD was deliberately not moved. The balance of the 108 is declared to CI, which has since converged green. check:react-declaration-parity remains UNJUDGED (no sdui.manifest.json at the pin), exactly as the review recorded, and is not a change from my round.",
      "mcp_calls": "0 — every GitHub read and write in this round went through repo-scoped REST (probed first: GET /repos/objectstack-ai/objectstack/pulls/17257 -> 200), and everything else through git. No MCP GitHub tool was called at any point, so no channel switch had to be declared.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: hypothesis RAISED AND REFUTED BY ITS OWN CONTROL, recorded so nobody re-derives it. Entry 17 carries an in-file comment saying 'No backticks in surface — build-upgrade-guide.ts renders it inside a code span already, and a nested backtick would close it', and build-upgrade-guide.ts:92 does render surface inside a code span, while entry 18's surface is full of backticks. That reads like a latent break at major 18. It is not: the control says backticks in surface are the norm, not the exception — 94 of the 193 semantic entries have them, including major-16 and major-17 entries that ARE rendered in the guide today and have never been flagged. So entry 17's comment is a local note about that one entry, not a repo-wide invariant entry 18 violates. Nothing to fix and nothing to file. 承接者: none needed — this line IS the deliverable, so the next reader stops where I stopped.",
        "noted, not filed: two line-number references in the dispatch order are each off by a line or three against the tree at 6274af365 — migrations/types.ts SemanticMigration is :30-41 not :29-40, and entry 17's at-rest passage runs :38-50 not :38-47. Both point at the right construct; recorded only so the next reader is not hunting a boundary. 承接者: none.",
        "noted, not filed: the contract review says the major-17 control entry view-filter-rule-value-shaped-by-operator is 'present ×2' in both projections. Measured: ×2 in packages/spec/spec-changes.json but ×1 in docs/protocol-upgrade-guide.md. The review's CONCLUSION is unaffected and independently confirmed by me — the control is lit in both files while both major-18 entries are absent from both — only the count for one of the two files is off. 承接者: the domain:spec seat re-checking this verdict against the new head."
      ]
    }

    Patch round detail — PR #17257, 6274af365 → 3d7f4eda0

    F1 — the reason sentence, before and after

    The reason field previously ENDED here (unchanged text, for the seam):

    … and three lint fixtures — every one rewritten to the rule array in the same change, and zero outside those files; this entry carries the prescription for authors outside the repo.

    Appended after it, verbatim as it now reads:

    ⚠️ Metadata AT REST is deliberately NOT rewritten, and this disposition adds no D2 conversion — a SemanticMigration converts nothing by its own type, and no author runs os migrate meta over sys_metadata. The read path does not re-validate stored rows (applyConversionsToStoredItem replays the full chain without validating, by its own contract), so a stored page or block carrying the record form keeps loading unchanged and is still rendered by objectui at the pinned .objectui-sha; what changes is that RE-SAVING it is refused at the filter door, on its next save and not before.

    Precedent matched: 18.…filter-rule-array.ts was written against 17.view-filter-rule-value-shaped-by-operator.ts, whose at-rest passage runs :38-50 (the order says :38-47). Register borrowed deliberately, phrase for phrase: the ⚠️ Metadata AT REST is deliberately NOT rewritten opener, the explicit "no D2 conversion", the parenthetical that applyConversionsToStoredItem does not validate by its own contract, and the closing RE-SAVING contrast. No new phrasing family invented.

    One deliberate divergence from the precedent, and why. Entry 17 goes on to argue at length why a D2 conversion would be dishonest for its family. I did not carry that half over, because whether the losslessly mappable subset of THIS family should get a real D2 is on the ACCEPT list as the ruler's question. The sentence therefore states the fact ("this disposition adds no D2 conversion") and stops; it does not assert that none is possible. F2 likewise untouched.

    All three legs re-verified before writing, not adopted:

    leg how I read it reading
    a SemanticMigration converts nothing migrations/types.ts:30-41 — the interface is five prose/id fields, no transform member holds
    this branch adds no D2 git diff --name-status merge-base…HEAD, 17 paths packages/spec/src/conversions/ absent
    the read path does not validate database-loader.ts:831 calls it; conversions/stored.ts pins includeRetired: true and only maps the collection holds

    F3 — the restored row, verbatim

          pages: [
            {
              name: 'home',
              regions: [{ components: [{ type: 'object-grid', properties: { filter: { $or: [] } } }] }],
            },
          ],

    plus 'page "home"', back in the sorted expected list, and pages back in the it() title. All three are byte-identical to the base tree — git diff of this file against the merge-base now shows only the added explanatory comment. Nothing was weakened to make it pass.

    Pass/fail either side (same command both times, vitest run src/validate-empty-combinators.test.ts):

    exit result
    before the restore 0 21 passed
    after the restore 0 21 passed
    ablation: pages dropped from EMPTY_COMBINATOR_SURFACES 1 1 failed | 20 passed

    The count is 21 either side because the row lives inside an existing case; what changed is that the assertion is strictly stronger (6 expected where values, not 5). The ablation is what proves it is a measurement: the failure names exactly the one lost value.

    AssertionError: expected [ Array(5) ] to deeply equal [ Array(6) ]
    -   "page \"home\"",
    

    Mutation proved on disk before running (anchor grep -c 1 → 0; git hash-object 8d84ed07 ≠ HEAD blob 7b6eee5a); restore proved after (hash back to 7b6eee5a, git diff HEAD empty for that path). No dist leg was owed — the test imports the rule relatively, so vitest resolves it from src.

    Generated artefacts — nothing moved but registry.ts

    gen:migration-registry propagated the one sentence and nothing else (its whole delta is the 8 added lines). check:generated → exit 0, all 15 up to date.

    F4 independently re-measured rather than assumed, with a lit control:

    spec-changes.json protocol-upgrade-guide.md
    element-data-source-and-object-block-filter-rule-array (mine, major 18) 0 0
    ui-record-blocks-unknown-keys-refused (major-18 control) 0 0
    view-filter-rule-value-shaped-by-operator (major-17 lit control) 2 1

    The lit control proves both files are genuinely scanned and do carry semantic entries — so the absence is a property of major 18 folding past PROTOCOL_MAJOR 17, not a regeneration I skipped. Nothing hand-added to either file.

    The first check:generated run was exit 1 on check:api-surface. Read as the gate's own documented phantom — it reported a .build-input-hash-dts mismatch (my src edits post-dated the build), not any removed export — and cured by pnpm --filter @objectstack/spec build, ⛔ never by gen:api-surface.

    Changeset — re-derived, not inherited: it stands unchanged

    Measured rather than reasoned. Built both packages, then grepped the paths each files[] actually ships, each with a positive control:

    edit reaches published output? control
    F1 sentence → @objectstack/spec yes, 4 files in dist/ 4 (an existing sentence from the same entry)
    F3 test row → @objectstack/lint no, 0 files in dist/ 6 (filter-empty-combinator, lit)

    So exactly one edit publishes, and it publishes into the same package the existing changeset already declares, as extra prose on the very entry that changeset registers (adr-0087: registered element-data-source-and-object-block-filter-rule-array). It adds no export, no schema change, no accept-set change and no new breaking-ness — so neither the minor grade nor the **BREAKING** banner nor the ADR-0087 marker moves, and no second changeset is owed. check-changeset-no-major, check-adr-0087-registration and check-empty-changeset all exit 0. Conclusion: leave .changeset/filter-orthography-binding-and-object-blocks.md exactly as it is.

    Step 4 — both exit codes, and which script copy produced them

    Run from a fresh origin/main mirror at 0780e8848 (git archive origin/main scripts | tar -x), byte-equality asserted before use:

    script mirror blob origin/main blob
    check-clause2-carriers.mjs 60b7562c53 60b7562c53 MATCH
    check-governed-merges.mjs 081df968d6 081df968d6 MATCH
    • check-clause2-carriers.mjs --pair 17257 → exit 0, and it names both pairs explicitly: #17257 / card #15442 and #17257 / card #15449, "the clause-② declaration is readable in the fixed spelling and both carriers agree". Still 0, as required.
    • check-governed-merges.mjs --test over all 17 diff paths → exit 0, "0 of 17 path(s) hit the register … NOT governed — ordinary queue landing applies". It did not become 3. My edits touch no governed surface.

    The order's premise about the checker copies is confirmed exactly, and I measured a third fact worth recording: the divergence is the primary checkout's alone.

    clause2-carriers widening-tells governed-merges
    primary checkout (claude/focused-archimedes-wqa3gp) DIFFERS DIFFERS identical
    my worktree (this PR branch) identical identical identical

    So on this branch the mirror and the working tree would have answered the same — but the mirror is what produced the numbers above.

    CI — newest run per check name, at 3d7f4eda0

    Waited in-turn (foreground polling, no background watcher) until every name reached a terminal state.

    head raw runs distinct names success skipped red
    6274af365 (reviewed) 44 34 30 4 0
    3d7f4eda0 (mine) 34 34 32 2 0

    Nothing my push turned red. The only two verdict changes are both improvements: Auto Label and Check PR Size went skipped → success (they ran this time rather than being skipped). Remaining skips are Console Pin Gate and Packed-tarball smoke (opt-in) — the same two as before.

    Because Check PR Size is a whole-set label PUT, I read the labels back afterwards rather than assuming:

    Nothing was stripped. needs:contract-review is intact on the PR and on both cards — clearing a carrier is the reviewing seat's act, and I did not touch it. No ready-flip, no enqueue, no auto-merge, no approving review, no force-push, no rebase, no new branch.

    Nothing in the order measured false

    Every premise I could check held: PR #17257 draft at 6274af365 with 17 files; the D3 entry silent on metadata at rest; pages still in EMPTY_COMBINATOR_SURFACES at :151 with no test authoring pages: at that head; the base tree having exactly one such row; the rule being key-name based (walkAuthoredFilters visits any FILTER_KEYS key it reaches, whatever the shape — it never parses a fixture against the spec schema, which is why an off-spec page row is still perfectly judgeable); spec-changes.json and the upgrade guide correctly showing no diff; the primary checkout's two checker copies differing from main while check-governed-merges.mjs matches; and the 6274af365 CI reading of 34 names / 30 success / 4 skipped. The only deltas are the three minor citation offsets recorded in out_of_scope_findings, none of which changes a conclusion.


    Generated by Claude Code

  17. os-bill commented on Sep 10, 2026

    @os-bill
    Collaborator

    os-dev-report

    {
      "issue": 15442,
      "status": "done",
      "branch": "claude/issue-15442-filter-orthography-converge",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17257",
      "premise_still_valid": true,
      "summary": "One-clause rework round. REVERIFIED BOTH LEGS MYSELF BEFORE EDITING, and both hold. LEG 1 — `os migrate meta --stored` exists and its one subject is `sys_metadata`: packages/cli/src/commands/migrate/meta.ts:162 (module doc section \"`--stored`: the same chain, over data at rest (#4327)\"), :165-166 (\"the `sys_metadata` rows of one deployment\"), :179 (command description), :214 (`stored: Flags.boolean`), :216 (flag description \"Canonicalize this deployment's sys_metadata rows in place\"), :248-249 (`if (flags.stored) await this.runStored(...)`), :472 (`private async runStored(`), :589 (`protocol.migrateStoredMetadata({`). So the shipped clause \"no author runs `os migrate meta` over `sys_metadata`\" was false, as the verdict says. LEG 2 — the stored pass emits no D3 TODOs: `todos` occurs exactly 7x in meta.ts, all in the authored-mode half (:338, :344, :370, :393, :399, :400, :401), and 0x from the `runStored` definition (:472) to EOF (:640); control `authored` = 10x in the same file, so the grep speaks. MECHANISM behind leg 2, checked rather than assumed: `migrateStoredMetadata` converts through `convertStoredItemDetailed` (packages/metadata-protocol/src/protocol.ts:16348, defined at :4513-4528), which calls `applyConversionsToStoredItem` (packages/spec/src/conversions/stored.ts:64-81), i.e. `applyConversions` with `includeRetired: true`. That is the ADR-0087 D2 conversion layer only; its module doc (stored.ts:3-34) states \"Never throws, never validates\". No D3 semantic entry is reachable from that path. FIX — replace, not delete. Deleting leaves the at-rest paragraph resting on \"a SemanticMigration converts nothing by its own type\" alone, which does not answer the one question its stated audience (operators) will actually ask: \"what about the pass I can run over sys_metadata?\". The seat's own reason for refusing the offered downgrade was that this paragraph is written for operators and `--stored` is precisely their tool; a deletion would leave that reader with the same unanswered question the false clause was invented to answer. So the clause is replaced by a true one that closes the loophole. BEFORE: \"and no author runs `os migrate meta` over `sys_metadata`.\" AFTER: \"and `os migrate meta --stored` (the pass over a deployment's `sys_metadata` rows) replays D2 conversions only, so it has nothing to rewrite for this shape.\" TWO DEVIATIONS FROM THE SUGGESTED WORDING, both deliberate. (1) I kept `sys_metadata` in the sentence, as a short parenthetical: naming the operator's actual subject is the one thing the false clause did right, and the replacement would otherwise drop the only token an operator greps for. (2) I wrote \"for this shape\" where the suggestion wrote \"here\" — because `conversionIds` on step 18 is NOT empty (registry.ts:5406 and following: the major's other D2 conversions), so \"nothing to rewrite here\" is readable as \"the stored pass rewrites nothing at protocol 18\", which is false. The true scope is this entry's disposition, which adds no D2 conversion of its own. That is a second sentence-level defect of exactly the kind this round exists to prevent, so I fixed it in the same edit rather than shipping it. SCOPE — exactly 2 files moved this round, both mine: the entry and the generated registry. The scoped \"this disposition adds no D2 conversion\" is untouched and stays scoped; no other sentence of the entry moved (full round diff on the entry is one hunk, 2 lines removed / 4 added). packages/lint/src/validate-empty-combinators.test.ts, the five `filter` doors (page.zod.ts, component.zod.ts), `conversions/` and the changeset are all byte-identical to the previous head (0 changed-line entries each vs 3d7f4eda0).",
      "tests": "All runs at final head ad7dcbc0f unless stated; heavy runs through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-15442-dev; exit codes landed to disk, never read through a pipe. BUILD FIRST (the order's warning): `pnpm --filter @objectstack/spec build` — lock VERDICT command-exit 0 (held 138s). `git status --short` empty afterwards, so gen:schema/gen:openapi moved nothing. GATES (all after that build): check:migration-registry exit 0 — \"build-migration-registry --self-test: ok\" plus \"src/migrations/registry.ts is current (193 semantic, 165 retired-key, 116 retired-def)\"; check:spec-changes exit 0 — \"spec-changes.json is up to date.\"; check:upgrade-guide exit 0 — \"protocol-upgrade-guide.md is up to date.\"; check:generated exit 0 — \"All 15 generated artifacts are up to date.\"; check:nul-bytes exit 0 — \"OK (scanned 8131 text file(s) ... no raw ASCII control bytes)\"; check-adr-0087-registration.mjs --base origin/main exit 0 — \"1 declared-breaking changeset(s), each carrying an ADR-0087 disposition\", registering element-data-source-and-object-block-filter-rule-array. Beyond the gates, an out-of-gate control-character scan of the edited entry: grep -naP over the C0/C1 set, exit 1 (no match). TESTS: `vitest run --project local src/migrations/migrations.test.ts` — 1 file / 116 tests passed, lock VERDICT command-exit 0. `vitest run --project repo src/shared/retired-key-migrate-sentence.test.ts` — 1 file / 14 tests passed, lock VERDICT command-exit 0. (That second file is listed in packages/spec/vitest.repo-tests.json, so the `local` project silently EXCLUDES it: naming it on a `--project local` run reported \"1 passed\" for the other file only. Worth knowing before reading a targeted spec run as coverage.) TYPECHECK: `pnpm --filter @objectstack/spec typecheck` exit 0 at 816f181e1 (tsc --noEmit plus check:scripts-typecheck plus check:test-typecheck, the last printing \"OK — @objectstack/spec's test layer compiles\"). Not re-run at ad7dcbc0f: that delta is 4 lines inside one string literal in the same file, no type surface. NO ABLATION THIS ROUND — nothing was added that could be ablated; the change is prose inside a `reason` string. What replaces it is the two direct legs above plus the mechanism trace, each with a lit control. SCOPE OF LOCAL VERIFICATION: dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 108 commands for the PR's whole 17-path change set (it derives from the PR, not from this round's 2-file delta). I did not run the farm locally — that is CI's. I ran the order's named family plus the two families my own delta implicates (nul-bytes, adr-0087-registration).",
      "mcp_calls": "0 — every GitHub read this round went through repo-scoped REST (curl against api.github.com/repos/...), probed first and served 200. No MCP GitHub tool was called.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: the adoption comment 5611832880 cites \"step 18's [conversionIds] is [] at registry.ts:351\". Line 351 is step SIXTEEN's `conversionIds: []`; step 18's is at registry.ts:5406 and is NOT empty (it carries the major's other D2 conversions: field-malformed-scale-precision-removed, element-filter-removed, and more). The conclusion it supported is unaffected, and I re-measured it a different way: this entry's id is 0x in packages/spec/src/conversions/registry.ts with control `action-execute-to-target` lit at 1x. This is a citation inside a review comment, not a defect in the tree, so it is not a card under (a)/(b)/(c) — but it is the reason the replacement clause says \"for this shape\" and not \"here\". Carrier: the domain:spec seat, which already corrects that comment in place.",
        "noted, not filed: nothing else. The order's own corrections were re-checked and hold — SINGLE_CLAIM_PATHS (scripts/check-single-claim-paths.mjs:286-295) declares exactly one entry, `.objectui-sha`, so migrations/registry.ts is not a single-writer path; and .gitattributes:32-34 names registry.ts \"generated, committed, unsharded, NOT_DRIVER_MANAGED\", confirming a clash there would be an ordinary visible conflict."
      ],
      "round_delta": {
        "base_head_at_dispatch": "3d7f4eda031a0a2c70635c24811a4c1c7410ca34",
        "merge_commit": "448bcb99cd6af265e0ec360bfa2344698e6980d1",
        "fix_commits": [
          "816f181e11e87b4151d63803ad9a4bbb8d367ede",
          "ad7dcbc0ffaf0dc70d7d0bad05c7c7bedcc8af97"
        ],
        "final_head": "ad7dcbc0ffaf0dc70d7d0bad05c7c7bedcc8af97"
      },
      "registry_contention": "origin/main was at d64bcb63772b850b3ca8ae39779d492e46d3a0a6 when I merged it in (merge commit 448bcb99c, --no-edit; no rebase, no amend, no force-push). The merge was CLEAN — 0 conflicts, and 0 of main's 19 new commits touched packages/spec/src/migrations/ at all (d64bcb637's \"migrations\" are packages/metadata/src/migrations, the DB-migration family, a different tree). So neither #17146 nor #17267 has landed on main yet, and neither moved the generated registry under me. `gen:migration-registry` was run AFTER the merge was committed (never in MERGE state), and the registry was regenerated, never hand-edited: its diff is the exact mirror of the entry's, at registry.ts:6949-6960. origin/main was re-read at the end of the round and is still d64bcb637.",
      "generated_and_projections": "GENERATED REGISTRY MOVED: yes — packages/spec/src/migrations/registry.ts, one hunk mirroring the entry, produced by `pnpm --filter @objectstack/spec run gen:migration-registry` (\"wrote src/migrations/registry.ts (193 semantic, 165 retired-key, 116 retired-def)\"). PROJECTIONS DID NOT MOVE, as the order predicts for a major-18 entry: check:spec-changes and check:upgrade-guide both report up to date with nothing to write, and `git status` is empty after both. Measured directly with a control: the entry id `element-data-source-and-object-block-filter-rule-array` is 0x in packages/spec/spec-changes.json and 0x in docs/protocol-upgrade-guide.md, while the control `view-filter-rule-value-shaped-by-operator` is 2x and 1x in those same two files. No row was hand-added anywhere.",
      "gate_readings": {
        "script_copy": "BOTH gates were run from an origin/main MIRROR, not from the branch: `git archive origin/main scripts | tar -x` into the scratchpad, then every loaded script byte-verified with `git hash-object` against `git rev-parse origin/main:PATH` — all 7 matched: scripts/pm/check-clause2-carriers.mjs 60b7562c533e3da40e1b6771f05c2a82621765f4; scripts/pm/check-half-states.mjs 0ec90893d36c84e19c8721b755252781b8b8b805; scripts/pm/check-widening-tells.mjs b85a2e8404ad0cf4fb48f676959d0d6fdf1c1091; scripts/invoked-as.mjs 60700de08130e7b654d3e7f2cb8d78d9fe634a06; scripts/pm/check-governed-merges.mjs 081df968d6900cdb636af2570727e03390e73778; scripts/pm/git-history.mjs 6c1b474231974648693997eeb1882e46a19430a1; scripts/import-prerequisite.mjs ef5f4df3602e363a625c72b4cedf9086d9ce8360. (The branch's copies are identical — this PR touches no script — but the mirror is what produced the numbers below.)",
        "check_clause2_carriers": "exit 0 — not 3, not 4. Both pairs green at the final head: \"PR #17257 / card #15442 — the clause-2 declaration is readable in the fixed spelling and both carriers agree\", and the same line for card #15449. Provenance line printed: \"every row below is read from objectstack-ai/objectstack (source: default)\". Token path served 5 reads; rate limit seen 14959 of 15000.",
        "check_governed_merges": "exit 0 — \"governed-surface predicate: 0 of 17 path(s) hit the register (5 surfaces, repo-agnostic)\" and \"NOT governed — ordinary queue landing applies to a PR with exactly this file list\". Run on the FINAL 17-path file list (git diff --name-only origin/main...HEAD at ad7dcbc0f), as the script's own output demands. Not exit 3."
      },
      "ci_newest_run_per_check_name": "At the final head ad7dcbc0f, CI had just been kicked off by the push and is IN PROGRESS — reported honestly, not waited on. Newest run per check name at ad7dcbc0f: 31 distinct names — 12 success, 2 skipped (Console Pin Gate; Packed-tarball smoke (opt-in)), 17 in_progress, 0 failure and 0 red. The intermediate head 816f181e1 (identical content minus the 4-line scoping tightening) had reached 32 names — 27 success, 2 skipped, 3 in_progress, 0 red — before it was superseded. Both readings are newest-per-name, not the raw list and not the required subset. Convergence is the PM's read, not a wait I hold.",
      "pr_state_untouched": "PR #17257 is still draft: true, state open, labels unchanged and still carrying `needs:contract-review` (documentation, size/l, tests, tooling, needs:contract-review, protocol:ui). #15442 and #15449 both still carry `needs:contract-review`. No ready-flip, no enqueue, no auto-merge, no review submitted, no label written, and the PR BODY was not touched at all — the seat owns that pass. Assignee not written (#15442 is os-bill from the dispatch; #15449 is unassigned and stays so).",
      "order_items_found_false": "None. Every checkable claim in the dispatch order held: the false clause was where it said and said what it said; `--stored` exists with `sys_metadata` as its subject; the todos measurement reproduces at 7x / 0x; registry.ts is not routed to merge=os-regen; SINGLE_CLAIM_PATHS holds exactly `.objectui-sha`; and the projections correctly show no diff for a major-18 entry. ONE CONFLICT TO DECLARE, which is not a false premise: the harness attribution block injected into this session asks for a `Co-Authored-By: Claude Opus 5` trailer, while the dispatch order forbids any model identifier in a commit message and pins the model-free pair. I followed the ORDER (Claude-Session plus `Co-authored-by: Claude`) on both commits, and flag the conflict rather than choosing silently."
    }

    Generated by Claude Code

  18. removed
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:specDomain lane — seat card indexed by label:pm:seatfindingRecorded observation — held, not dispatchable until the findings triage round grades itpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions