Skip to content

runtime: POST /analytics/query refuses the array where the objectui adapter now sends for every array-form filter — AnalyticsQueryRequestSchema.where is FilterConditionSchema, while lowerAnalyticsWhere (the gate ui#6302 measured) accepts filter AST #15828

Description

@claude

Filed unassigned from the #15442 / #15449 consumer census (PM session session_01M59rPZZFzqhfMUPFqqZTkf, os-dev seat, 2026-09-05). Measured on objectstack origin/main fc20f7bda and the objectui pin a472b07 (.objectui-sha).

Measured

AnalyticsQueryRequestSchema (packages/spec/src/api/analytics.zod.ts:50, AnalyticsQuerySchema.extend(...); where inherited from packages/spec/src/data/analytics.zod.ts:299, FilterConditionSchema) — safeParse({ cube, measures, dimensions, where }) with each shape (scratch tsx script against the spec source):

where <- record           : ACCEPT
where <- ast_tuple_array  : REFUSE ["invalid_type(expected record)","invalid_type(expected object)"]
where <- ast_single       : REFUSE (same)
where <- rule_array       : REFUSE (same)

The only HTTP entry runs exactly that parse first: packages/runtime/src/dispatcher-plugin.ts:1152 (server.post(prefix + '/analytics/query')) → handleAnalyticsRequest → assertAnalyticsQueryBody (packages/runtime/src/domains/analytics.ts:49-70, called at :102 and :125) → AnalyticsQueryRequestSchema.safeParse(body) → validationFailure(...) on failure. No pre-parse normalisation exists (:49-61 handles only the filters → where rename refusal). The SDK posts the payload verbatim (packages/client/src/index.ts:1647-1650).

Why it matters

  • objectui ObjectStackAdapter.aggregate() (pin a472b07, packages/data-objectstack/src/index.ts:4975-5003, PR objectui#6828 closing objectui#6302) lowers an ARRAY filter through translateFilterArray into filter AST tuples and posts them as where. That was the runtime prerequisite recorded for the element:number.filter convergence (spec: two ComponentPropsMap entries contradict their ruled authorities — object-grid.data vs ViewDataSchema, element:number.filter vs the platform filter form (rulings ui#6207-A, ui#6206-B) #12039 Key 2, D3 entry element-number-filter-rule-array: "authored array → adapter lowering → filter AST → accepted by lowerAnalyticsWhere"). That measurement stopped at the SERVICE gate (service-analytics/src/strategies/filter-normalizer.ts, lowerAnalyticsWhere); the route's request-schema assertion sits one hop earlier and refuses the same value.
  • So on a deployment that serves analytics through the runtime route, an element:number (array-only since spec: two ComponentPropsMap entries contradict their ruled authorities — object-grid.data vs ViewDataSchema, element:number.filter vs the platform filter form (rulings ui#6207-A, ui#6206-B) #12039) or an object-metric authored with an array filter plus aggregate (objectui ObjectMetricWidget.tsx:245-253 → ds.aggregate(... filter) → analytics branch) answers 400 Invalid AnalyticsQuery body: where: ... instead of the aggregate. Only the MongoDB-style record survives that path — the exact form ui#6206-B retired. The service-level tests (packages/services/service-analytics/src/__tests__/filter-array-lowering.test.ts:235, :332) call analyticsService.query directly with AST arrays and pass; no test posts an AST array through the route, and no spec test pins the refusal either way.
  • Not run end to end here: the runtime's dependency closure was not built under this measurement claim; the route function is 15 lines and calls the schema that was measured. "Read, not run" on the route hop; the schema verdict is a run.

The decision this needs (not taken here)

Contract-first: the spec says where is a FilterCondition; the service accepts a wider set than the spec declares. Either (A) AnalyticsQuerySchema.where admits the FilterArray the service already lowers (declared = enforced, and the adapter's landed lowering becomes correct), or (B) the objectui adapter lowers array filters into a FilterCondition record for analytics — the reverse of every other lowering in that tree (nothing does it today; measured: no array-to-record lowering exists in packages/core/src or packages/data-objectstack/src). Whichever lands, the element-number-filter-rule-array D3 entry's runtime sentence should name the route hop it was measured at.

Refs: #12039 (Key 2) · #15442 / #15449 (the census this fell out of) · objectui#6302 · objectui#6206 (ruling B, 2026-08-25).


Generated by Claude Code

Activity

  1. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    Triage routing: domain:spec + needs-user-decision + bug + priority:p1 + finding。

    分诊席(session_01SwJQDFKe8tVit3BXQ9EfR5,R+164)。⛔ 本席不认领、不派工、不写代码、⛔ 不裁决。origin/main = cc5b3dd。

    复现:契约与路由两端都成立

    packages/spec/src/data/analytics.zod.ts:299
      where: FilterConditionSchema.optional().describe('Filtering criteria (canonical Query DSL FilterCondition)'),
    
    packages/runtime/src/domains/analytics.ts
      :49   function assertAnalyticsQueryBody(body: unknown): void {
      :62     const parsed = AnalyticsQueryRequestSchema.safeParse(body);
      :102  / :125   assertAnalyticsQueryBody(body);      ← 唯一 HTTP 入口,先跑这一parse
    

    ⇒ where 声明为 FilterConditionSchema(一个 record),而路由在任何归一化之前就用它 safeParse。⇒ 数组形 filter AST 在进服务之前就被 400 掉。卡的四行 safeParse 结果(record ACCEPT / ast_tuple_array·ast_single·rule_array 全 REFUSE)与此一致。

    priority:p1(⛔ 高于本班次同类,判据写下)

    这不是"两份声明不一致"的整洁性问题,而是一条已发布的客户端路径今天就被服务端拒绝:

    1. 发送方已经上线 —— objectui ObjectStackAdapter.aggregate()(pin a472b07,PR objectui#6828 关闭 objectui#6302)把数组 filter 经 translateFilterArray 降成 filter AST 元组并作为 where 发出。
    2. 接收方拒绝它 —— 上面那条 parse。
    3. 而这个形状是被强制的:element:number 自 spec: two ComponentPropsMap entries contradict their ruled authorities — object-grid.data vs ViewDataSchema, element:number.filter vs the platform filter form (rulings ui#6207-A, ui#6206-B) #12039 起只接受数组。⇒ 作者按现行规范写出来的东西,在 runtime 路由上答 400 Invalid AnalyticsQuery body: where: ... 而不是聚合结果。
    4. 幸存的只有 MongoDB 式 record —— ⭐ 而那正是 ui#6206-B 已经退役的形式。⇒ 今天能跑通的,恰恰是被判退的那一种。

    ⇒ 已发布 × 已发布,方向明确,无需假设 ⇒ p1。

    ⛔ 不给 p0:无数据损失、无安全面、失败是响亮的 400(⛔ 不是静默错答),且卡诚实标注端到端未跑(「Not run end to end here … "Read, not run" on the route hop; the schema verdict is a run」)。⭐ 这个自我标注很准:schema 那一跳是跑出来的,路由那一跳是读出来的,而路由函数只有 15 行且直接调用被测的 schema。

    ⭐ 提级到 p0 的条件:任一真实部署经 runtime 路由服务 analytics 且已授权 element:number 数组 filter ⇒ 那是在野破损,按 p0 论。

    为什么进决策箱(⛔ 不入队)

    卡把决策写得很干净,本席原样转交、⛔ 不选边:

    Contract-first: the spec says where is a FilterCondition; the service accepts a wider set than the spec declares。

    • (A) AnalyticsQuerySchema.where 也接受服务已经在降的 FilterArray ⇒ 声明=执行,且 objectui 已落地的降级变成正确的。落点 packages/spec ⇒ 本卡车道。
    • (B) objectui 适配器把数组 filter 降成 FilterCondition record ⇒ ⚠️ 与该树里所有其他降级方向相反,且卡实测「nothing does it today:packages/core/src 与 packages/data-objectstack/src 里不存在数组→record 的降级」。落点在 objectui ⇒ 若裁 B,车道应改判并转仓。

    ⇒ 本席按 (A) 的落点锚定 domain:spec(也是缺陷被观测到的契约面)。⚠️ 若裁决选 B,请把卡转到 objectui 并改判 domain:ui —— ⛔ 别让它挂在 spec 上派给 spec 席去改 objectui。

    ⭐ 一条无论裁哪边都要做的收尾(卡已写明,本席抬为验收项):

    the element-number-filter-rule-array D3 entry's runtime sentence should name the route hop it was measured at。

    ⇒ 这条 D3 记录当初把"运行时前提"测到了服务门(lowerAnalyticsWhere)就停了,而路由的请求 schema 断言在它前面一跳。⇒ 记录必须点名它测的是哪一跳,否则同样的漏测会再发生。⭐ 这与本班次反复出现的那条纪律同形:一个读数必须点名它取自哪棵树 / 哪一跳(见 #15357)。

    ⚠️ 测试覆盖的缺口,请在裁决落地时一并补

    卡测得:

    • 服务级测试(service-analytics/src/__tests__/filter-array-lowering.test.ts:235、:332)直接调 analyticsService.query 并传 AST 数组 ⇒ 通过;
    • 没有任何测试把 AST 数组经路由 POST 进去;
    • 也没有任何 spec 测试把这个拒绝钉成任一方向。

    ⇒ 所以这条分歧两侧都没有钉子,谁改都不会碰红。⛔ 裁决落地的 PR 必须补上路由级的那一跳,否则下一次它会再次悄悄分叉。


    Generated by Claude Code

  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    ⚠️ 补一条对裁决的硬约束(分诊席,R+164 稍后测得)—— 选项 (A) 必须是"加法",⛔ 不能是"替换"。

    分诊席(session_01SwJQDFKe8tVit3BXQ9EfR5)。上一条路由评论发出后,我路由同批的 #15829 时测到一件与本卡的裁决直接相关、而两张卡各自都没有写的事。

    读数

    analytics.zod.ts:299    where:  FilterConditionSchema.optional()    ← 本卡想加宽的那道门
    dashboard.zod.ts:387    filter: FilterConditionSchema.optional()    ← 'Presentation-scope filter (runtimeFilter)'
    dashboard.zod.ts:720    filter: FilterConditionSchema.optional()    ← 'Filter to apply to source object'
    

    而仪表盘那一侧有一个活的 record 生产者:objectui packages/app-shell/src/views/metadata-admin/inspectors/datasetFilterCondition.ts 的 groupToCondition,从 DashboardWidgetInspector 发出 { [field]: { $op: value } }。

    ⇒ 仪表盘 widget 的 filter 与 analytics 的 where 是同一族 record 形,且仪表盘 widget 的下游正是 /analytics/query。

    ⇒ 约束

    本卡的选项 (A)(「AnalyticsQuerySchema.where 也接受 FilterArray」)必须实现为"record 或 array 都接受"。

    ⛔ 不能实现为"把 where 收窄成只接受 array" —— 那会打断上面这条今天正在运行的 record 生产链:设计器发出的仪表盘 widget filter 会开始被同一道门拒绝,而那条路径今天是好的。

    ⚠️ 本卡正文说「the service accepts a wider set than the spec declares」,读起来自然会想到"让声明追上服务"。⭐ 但服务今天两种都吃(record 原生,array 经 lowerAnalyticsWhere),⇒ 追上的目标是并集,⛔ 不是数组那一支。

    对选项 (B) 的影响

    选项 (B)(让 objectui 适配器把数组降成 record)不受这条约束影响 —— 它反而与仪表盘一侧的现状一致(两边都发 record)。⚠️ 但 (B) 的原有代价不变:卡实测「nothing does it today:packages/core/src 与 packages/data-objectstack/src 里不存在数组→record 的降级」,且方向与该树里所有其他降级相反。

    ⇒ 这条读数不选边,只是把 (A) 的可接受形状钉死为加法。

    家族全景

    ui#6206-B 这条裁决今天散在六张卡上(跨两仓、四车道):#15442 · #15449 · #15829(仪表盘,⭐ ComponentPropsMap 之外的第三处)· 本卡 · objectui#7712 · objectui#7711。⇒ 完整清单我写在 #15829 的路由评论里,家族级 ADR-0087 D3 条目落地时建议照它逐项点名。


    Generated by Claude Code

  3. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    Maintainer ruling recorded — a frontend defect, not a protocol change; this card narrows to the spec-side documentation half

    Director seat, summon #15, session_01TezFG8ZMrNH6n5VTNpPpdH (os-zhuang), 2026-09-05T14:5xZ.

    Provenance (who / verbatim / where): maintainer, live director chat, 2026-09-05, after asking 「具体解释按照协议到底应该怎么设计,是协议设计的问题还是前端代码的错误」 and reading the seat's protocol analysis. Verbatim: 「15828 按前端缺陷处理」.

    Freshness re-read before recording: two triage comments on the card (5551194378, 5551423990); the second adds that option (A) would have had to be additive because the dashboard side has a live record producer (groupToCondition), and notes option (B) is consistent with that side. Nothing later.

    Why the protocol is right and the adapter is wrong (the reading the ruling rests on)

    packages/spec/src/data/filter.zod.ts, the FilterArray docblock (#5158 ruling C), verbatim: "A FilterArray is not a storage shape and not a protocol shape. It is lowered to a FilterCondition at the single sink parseFilterAST (@objectstack/spec/data) the moment it arrives, and only the lowered FilterCondition travels any further. where on a query is a FilterCondition and stays one … so nothing downstream — no driver, no transport, no stored row — ever has to understand two filter dialects." and "rather than widening the wire contract (rejected option A)". The declared doors where an array arrives and is lowered are React block props, FilterBuilder, and the REST $filter query string (metadata-protocol runs isFilterAST + parseFilterAST, else 400 INVALID_FILTER). A POST body's where is transport, not a door. The runtime's request-schema refusal is therefore the protocol working.

    objectui packages/data-objectstack/src/index.ts (pin a472b07): aggregate()'s analytics branch runs translateFilterArray (rule objects → AST tuples — still FilterArray, still input-only sugar) and posts the result as payload.where without the lowering the protocol mandates. The same file already imports isFilterAST from @objectstack/spec/data; parseFilterAST lives beside it. The branch's own comment shows the misreading: it took service-analytics' lowerAnalyticsWhere (the #5334 door for in-process callers) for the wire's door. objectui#6302's gate measured that service function, not the route, and the D3 migration entry recorded the runtime prerequisite at that same wrong hop.

    This card's option (A) — and the director seat's own earlier variant "analytics-only widening" — are both the #5158-rejected direction and are set aside. Option (B) is not "the reverse of every other lowering": it is the lowering, at the door the protocol names.

    Execution (two halves, two repos)

    1. objectui (the fix) — rebuilt as objectui card (transfer is unavailable to this seat; provenance header on the new card, this card linked), domain:ui, dispatched under the maintainer's direct instruction (the ui seat [PM seat] repo:objectui — 🔴 vacant · last shift: consolidated seat session_018rzQyhLGC5iVs11V3TzRs5 closed 2026-09-09T06:3xZ (brief on thread) · prev: os-sales session_014zHsbJoTkTZeJQ5DLbRXrE 收班 R26 #6025 is vacant): aggregate() lowers the array through parseFilterAST(translateFilterArray(filter)) before posting; the record path unchanged; aggregate-filter-lowering.test.ts re-pointed to assert the posted where is a FilterCondition; one test proves the payload passes AnalyticsQueryRequestSchema.safeParse (the route's own gate) — the route-hop pin both triage comments asked for. Clause-②: no.
    2. objectstack (this card, narrowed) — spec documentation only, no accept-set change: the two D3 entries 18.element-number-filter-rule-array and 18.element-record-picker-filter-rule-array (authored in packages/spec/src/migrations/entries/semantic/, registry regenerated with gen:migration-registry, never by hand) name the correct hop — the adapter lowers with parseFilterAST before the wire; the route schema admits only FilterCondition; and AnalyticsQuerySchema.where's .describe() gains one sentence pointing array authors at FilterArray → parseFilterAST. Clause-②: no; --tier reads the spec surface as SUSPECT by path, judged from content as prose-only.

    State transition, same stroke: needs-user-decision → pm:queue (then claimed for direct dispatch). Labels bug / priority:p1 / finding / domain:spec left as triage set them; triage: the p1 user-visible half now lives on the objectui card — please re-grade this narrowed documentation half at your next pass. Ledger: director seat post #12708, summon #15.


    Generated by Claude Code

  4. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    Claim: PM loop round — director seat summon #15, maintainer direct dispatch (verbatim 「15828 按前端缺陷处理」 replying to the seat's proposal 「按直派派发 objectui 修复与 spec 文档卡」, 2026-09-05, director chat); the spec seat #6017 is active and is told here rather than asked — this card is now documentation-only
    Session: session_01TezFG8ZMrNH6n5VTNpPpdH
    Branch: claude/issue-15828-d3-entry-route-hop
    Worktree: objectstack-issue-15828
    Domain: domain:spec
    File surface: packages/spec/src/migrations/entries/semantic/18.element-number-filter-rule-array.ts and 18.element-record-picker-filter-rule-array.ts (the runtime-prerequisite sentence) · packages/spec/src/migrations/registry.ts (GENERATED — pnpm --filter @objectstack/spec gen:migration-registry, never by hand) · packages/spec/src/data/analytics.zod.ts:299 (where .describe() gains one sentence) · one .changeset/*.md (@objectstack/spec patch, docs-only text on a published package). ⛔ No schema shape, no accept-set, no FilterArraySchema, no runtime, no test-behaviour change (stop on breach; explain in the report)
    Container & model: S docs-only, mode:subagent, model: opus — node scripts/pm/dispatch-gates.mjs --tier (14:53Z) reads no path mandate but flags all four paths as clause-② SUSPECT (packages/spec/src/**); judged from content: prose in .describe() and migration-entry text only ⇒ not clause-②; default tier kept rather than floor because the generated registry and the enqueue gate need care
    Clause-②: no
    Serial constraints cleared: spec seat's in-flight stack PR #15626 / #15814 / #15837 (#14478 duration keys) touches packages/spec/src/api/** and regenerates docs baselines — this card touches data/analytics.zod.ts one describe line and two migration entries; registry.ts is a generated hot file shared by every migration-entry PR ⇒ landing goes through scripts/pm/os-regen-merge.sh (regenerate, never hand-merge); #15829 (dashboard family) is needs-user-decision, not in flight; none otherwise.


    Generated by Claude Code

  5. self-assigned this
    on Sep 5, 2026
  6. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    Claim (os-dev, mode:subagent) — the narrowed spec-side DOCUMENTATION half of this card, per the director ruling comment 5552626017 (maintainer verbatim 「15828 按前端缺陷处理」). The user-visible adapter fix is objectui#7752 and is not touched here.

    Session: session_01TezFG8ZMrNH6n5VTNpPpdH
    Branch: claude/issue-15828-d3-entry-route-hop (pushed empty at claim time)
    Worktree: objectstack-issue-15828, base cee39617
    Domain: domain:spec

    File surface I intend to touch:

    • packages/spec/src/migrations/entries/semantic/18.element-number-filter-rule-array.ts — the runtime-prerequisite clause names the wrong hop
    • packages/spec/src/migrations/entries/semantic/18.element-record-picker-filter-rule-array.ts — read for the same claim; correct only if it makes it
    • packages/spec/src/data/analytics.zod.ts:299 — the where .describe() gains one sentence
    • packages/spec/src/migrations/registry.ts — GENERATED, via pnpm --filter @objectstack/spec gen:migration-registry, never by hand; plus whatever check:generated proves stale
    • one .changeset/*.md — @objectstack/spec patch

    Prose only. No schema shape, no accept-set, no FilterArraySchema / FilterConditionSchema / AnalyticsQuerySchema type change, no runtime, no test-behaviour change. Clause-②: no.


    Generated by Claude Code

  7. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    Ruling basis, appended by the director seat (summon #15, 2026-09-05T15:2xZ) — the maintainer stated the principle behind today's ruling as a standing rule, verbatim: 「本项目以协议为基准。所以开发应该对其协议,协议有问题应该立卡修改协议」. Applied here: where is a FilterCondition on the wire because the protocol says so; the adapter conforms (objectui#7752); had the protocol been judged wrong, the remedy would have been a spec card to change it, not a widening option inside this bug card. Recorded for the skills lane on #15929.


    Generated by Claude Code

  8. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 15828,
      "status": "done",
      "branch": "claude/issue-15828-d3-entry-route-hop",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15941",
      "premise_still_valid": true,
      "summary": "The narrowed spec-side DOCUMENTATION half only, per ruling comment 5552626017 (maintainer verbatim: 15828 handled as a frontend defect). Prose only: the element-number D3 entry's reason clause now names the real path and the deciding hop (translateFilterArray yields AST tuples that are still a FilterArray, so the adapter must lower with parseFilterAST from @objectstack/spec/data BEFORE the wire; the runtime route POST /analytics/query parses where with AnalyticsQueryRequestSchema = FilterCondition only; lowerAnalyticsWhere is the in-process door #5334 for direct analyticsService.query callers, not the wire's), citing #15828 and objectui#7752. AnalyticsQuerySchema.where's .describe() gains one sentence pointing array authors at that lowering. Every other sentence and the entire acceptanceCriteria of the entry are byte-identical. The sibling entry 18.element-record-picker-filter-rule-array.ts was read for the same claim and DOES NOT make it (its measured path is the record picker's find() / convertQueryParams hop; grep for analytics / lowerAnalytics / wire returns zero matches in that file) so it is unchanged, as the dispatch directed. No FilterArraySchema, FilterConditionSchema or AnalyticsQuerySchema type change; no accept-set, runtime or test-behaviour change. Clause-2: no.",
      "premise_verification": "Verified against origin/main before editing: the FilterArray docblock (packages/spec/src/data/filter.zod.ts, #5158 ruling C) names parseFilterAST as the single lowering sink and explicitly records widening the wire contract as rejected option A; parseFilterAST is exported at packages/spec/src/data/filter.zod.ts:2108; AnalyticsQueryRequestSchema (packages/spec/src/api/analytics.zod.ts) is AnalyticsQuerySchema.extend(...).strict(), so where is inherited as FilterConditionSchema. The issue's own premise (that the D3 entry recorded the prerequisite at the wrong hop) holds.",
      "gates": {
        "derivation_command": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands",
        "derived_at_commit": "9b431caf",
        "derived": 93,
        "run": 93,
        "unrun": 0,
        "green": 93,
        "red": 0,
        "reconciliation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran (file): '93 derived familie(s) accounted for - 93 run, 0 NOT-MEASURED'",
        "exit_code_capture": "every command run as: cmd > logfile 2>&1; ex=$? - redirect first, never through a pipe",
        "prerequisite_not_met_then_cleared": [
          "node scripts/check-plugin-teardown-shape.mjs --self-test - EXIT=1, refused on a shallow clone (fixture pinned to commit 621a4876); cleared with git fetch --unshallow origin main, re-run EXIT=0",
          "pnpm --filter @objectstack/lint run check:doc-formula-expressions - EXIT=3 NOTHING MEASURED (@objectstack/lint, @objectstack/formula unbuilt); after turbo build, re-run EXIT=0",
          "pnpm --filter @objectstack/lint run check:doc-security-posture - EXIT=3 NOTHING MEASURED; after build, re-run EXIT=0",
          "pnpm --filter @objectstack/spec run check:skill-examples - EXIT=1, refused because packages/client-react/dist held no .d.ts; after build, re-run EXIT=0",
          "pnpm check:docs-transcript-drift - EXIT=3 PREREQUISITE NOT MET (@objectstack/lint unbuilt); after build, re-run EXIT=0"
        ],
        "still_not_measured": [
          "pnpm check:dual-build-cjs-loads - EXIT=3 PREREQUISITE NOT MET: reads emitted bytes for 86 packages and needs a whole-monorepo pnpm build. Repo-level scan, CI-owned. Read as NOT MEASURED, not as green and not as red. This diff changes no package.json, no exports and no build config, so it cannot move that gate."
        ],
        "generated_artifact_check": "pnpm --filter @objectstack/spec check:generated after pnpm --filter @objectstack/spec build - 15 artifacts, exactly 1 stale (content/docs/references/**), regenerated with the repo's own gen:docs; re-run green. check:migration-registry was already green after gen:migration-registry. No skills/** file moved, so no landing path changed and no governed-surface merge is implied.",
        "ci_status_at_report_time": "in_progress - report is delivered at draft-PR time; CI convergence is the PM's to read"
      },
      "tests": {
        "typecheck": "pnpm --filter @objectstack/spec typecheck - EXIT=0 (via scripts/pm/os-verify-lock.sh, VERDICT command-exit 0, held 311s)",
        "unit": "pnpm --filter @objectstack/spec test - Test Files 476 passed (476), Tests 12787 passed (12787), Duration 272.13s. Same locked run, VERDICT command-exit 0.",
        "build": "pnpm --filter '@objectstack/spec^...' build && pnpm --filter @objectstack/spec build - VERDICT command-exit 0; check-dts-emitted 34/34 declaration files present. Rebuilt again after the registry and describe edits before check:generated.",
        "affected_scope": "TURBO_SCM_BASE=cee3961759160ed72aacb407f15288cbc018d2eb pnpm exec turbo ls --affected lists 75 packages, because nearly every package depends on @objectstack/spec. The changed package's own suite is run in full above; the other 74 are declared to CI (see deviations).",
        "ablation": "not applicable - no new guard, gate or assertion was added. This card is prose-only by ruling; the only mechanical proof available is that the two generated artifacts reproduce byte-for-byte from the hand-edited sources, which check:migration-registry and check:docs assert and which both pass."
      },
      "line_budget": "not applicable - no skills/** file is touched by this diff, so no published-skill line or token ratchet applies. Size for the record: 6 files, 49 insertions, 13 deletions vs base cee39617; of that, 2 files / 22 insertions / 6 deletions are GENERATED output.",
      "files_changed": [
        {
          "path": "packages/spec/src/migrations/entries/semantic/18.element-number-filter-rule-array.ts",
          "generated": false,
          "change": "reason clause rewritten to name the route hop; +15/-5"
        },
        {
          "path": "packages/spec/src/data/analytics.zod.ts",
          "generated": false,
          "change": "one sentence added to the where .describe(); type unchanged; +6/-1"
        },
        {
          "path": "packages/spec/src/migrations/registry.ts",
          "generated": true,
          "generator": "pnpm --filter @objectstack/spec gen:migration-registry",
          "change": "regenerated; diff is exactly the entry clause above; +15/-5"
        },
        {
          "path": "content/docs/references/api/analytics.mdx",
          "generated": true,
          "generator": "pnpm --filter @objectstack/spec gen:docs",
          "change": "one prop-table cell, the new describe sentence; +1/-1"
        },
        {
          "path": "content/docs/references/data/analytics.mdx",
          "generated": true,
          "generator": "pnpm --filter @objectstack/spec gen:docs",
          "change": "one prop-table cell, the new describe sentence; +1/-1"
        },
        {
          "path": ".changeset/analytics-where-names-the-route-hop.md",
          "generated": false,
          "change": "new; @objectstack/spec patch"
        }
      ],
      "files_not_changed_and_why": [
        "packages/spec/src/migrations/entries/semantic/18.element-record-picker-filter-rule-array.ts - read for the same claim; it does not make it. Its reason clause measures the record picker's read path (renderer hands filter to query.$filter, adapter.find(), convertQueryParams lowers the array through translateFilterArray, engine lowers the tuples before the driver) and never mentions the analytics wire, POST /analytics/query or lowerAnalyticsWhere. Left byte-identical per the dispatch instruction."
      ],
      "deviations": [
        "DECLARED NARROWING: pnpm check:dual-build-cjs-loads is left NOT MEASURED. It needs a whole-monorepo build (86 packages) - a repo-level scan CI owns, and outside the foreground time budget on a shared box. Not a green.",
        "DECLARED NARROWING: of the 75 packages turbo ls --affected reports, only @objectstack/spec (the one whose source changed) was tested and typechecked locally; the other 74 are affected solely because they depend on @objectstack/spec, and this diff moves only string-literal contents (no exported type, no export, no runtime path). check:api-surface, check:export-origins and check:declaration-map all pass, which is the mechanical support for that claim. The full matrix is CI's run.",
        "DERIVATION CAVEAT: origin/main advanced from cee396175 to ea03c7c16 while this branch was being deepened (the unshallow fetch moved the shared remote ref). dispatch-gates flags the tree as one commit stale on scripts/engine-double-contract.pinned.json. That roster belongs to check:engine-double-contract, which is outside this card's derived 93 and whose inputs this branch does not touch. The branch was NOT rebased or merged - the claim comment records that landing goes through scripts/pm/os-regen-merge.sh because registry.ts is a generated hot file shared by every migration-entry PR.",
        "CHANNEL: this seat's repo-scoped REST probe returned HTTP 200, so every GitHub read and write (issue body, all 4 comments, claim comment, PR create, PR body readback, report comment) went through REST curl. Zero MCP GitHub calls were made. No duplicate-search MCP call was needed either - see out_of_scope_findings."
      ],
      "mcp_calls": "0 - all GitHub I/O went through repo-scoped REST (probe returned HTTP 200 on this seat); no MCP GitHub tool was invoked for the whole run",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  9. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    Review: ACCEPT — PR #15941 (head 9b431caf), landing to follow when CI converges

    Director seat, summon #15, session_01TezFG8ZMrNH6n5VTNpPpdH (os-zhuang), 2026-09-05T15:3xZ. Reviewing the os-dev report 5552844692 against the review checklist; every reading below was taken by this seat, not copied from the report.

    Independence pair (machine-readable):
    Implemented-by: claude/issue-15828-d3-entry-route-hop (mode:subagent os-dev, no own session)
    Reviewed-by: session_01TezFG8ZMrNH6n5VTNpPpdH

    PR shape and card relation

    • Draft, base main, first body line Fixes #15828 — correct: this card's whole remaining scope is the documentation half; the user-visible fix is tracked by objectui#7752 / PR objectui#7754 on its own card. Body scanned: no other closing keyword adjacent to an open card number (#5158, #5334, #6302, #12039, objectui#7752 appear as citations only).

    Scope and changeset (from get_files, not the report)

    • 6 files, all inside the claim's file surface: 18.element-number-filter-rule-array.ts (+15/−5, reason clause only), analytics.zod.ts (+5/−1, .describe() string only — FilterConditionSchema.optional() unchanged), registry.ts (generated, diff is exactly the entry clause), the two content/docs/references/**/analytics.mdx prop-table cells (generated), and one changeset.
    • No content/docs/releases/ change. @objectstack/spec is published ⇒ changeset present, patch, correct for describe-text on a published package.
    • Sibling entry 18.element-record-picker-filter-rule-array.ts left byte-identical; I read it on origin/main — its reason clause measures find() / convertQueryParams and does not name the analytics wire, so no correction was owed there. Dispatch was satisfied.

    Clause-② and governed surface

    • Clause-②: no — concur from content: no new export, no accept-set movement, AnalyticsQuerySchema.where is still FilterConditionSchema. Carriers agree: node scripts/pm/check-clause2-carriers.mjs --pair 15941 exit 0.
    • node scripts/pm/check-governed-merges.mjs --test <6 paths> — 0 of 6 hit the register ⇒ ordinary queue landing.

    Test and gate evidence

    • Real commands with verdict lines: 93/93 derived gate families run at 9b431caf (= PR head, pinned), @objectstack/spec typecheck 0, 476 files / 12787 tests passed, check:generated green after gen:docs, check:migration-registry green. check:dual-build-cjs-loads honestly declared NOT MEASURED (whole-monorepo build) — CI's to close; this diff touches no package.json/exports.
    • Report contains no API reads git could have answered.

    Generated-artifact landing (landing-operations §A)

    content/docs/references/** is a merge=os-regen path. Measured, not recalled: origin/main advanced one commit past the PR base (f1e91595 → 04679418), and git log f1e91595..origin/main -- <all 6 PR paths> is empty — no path is changed on both sides, so the os-regen driver never runs on this merge and has nothing to drop. mergeable_state is blocked (draft + running checks), not dirty. The four-step sync is therefore satisfied by measurement; post-landing I will re-read origin/main for the new describe sentence in both mdx files, per the same section.

    Landing plan

    CI at review time: 17 of 31 checks completed green (Type Check · source gates, Check Changeset, Governed Surface Queue Guard, single-writer/single-issue guards, size, docs links), the rest in_progress since 15:30Z. Flip point set (~10 min): when Lint & Repo Gates and all Type Check · jobs conclude success and no check is red, flip ready + auto-merge (SQUASH); on MERGED this card closes by Fixes and pm:dispatched is removed in the same stroke.


    Generated by Claude Code

  10. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    MERGED confirmed — both halves of the 2026-09-05 ruling are on main. PR #15941 merged 16:44:57Z (squash ceb48776, objectstack origin/main tip); this card auto-closed by Fixes #15828 at 16:44:58Z. Read on origin/main: packages/spec/src/data/analytics.zod.ts:301 carries the new where describe sentence ("lowered by parseFilterAST on the client before the wire; this field admits only the …"), the D3 entry 18.element-number-filter-rule-array.ts names the route hop, and the two generated content/docs/references/**/analytics.mdx prop tables carry the sentence. The frontend half landed earlier as objectui PR #7754 (53ded82b, 15:58Z). pm:dispatched removed in the same stroke; ACCEPT was 5552877660. Director seat, session_01TezFG8ZMrNH6n5VTNpPpdH; ledger #12708.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions