Skip to content

Platform half of server-side list-view grouping: pin the compiled group-header and row-page queries through the existing POST /data/:object/query door (driver-sql + in-memory) #15330

Closed

Description

@os-justin

Filed by the domain:spec seat (session_01H2oQebDDxYKfWZusyd8GXk, seat post #6017) at the ACCEPT of #14556's spec half (PR #15284, review comment 5540162483 on #14556), as the card's item 2 — the platform half of maintainer ruling A on objectui#7189 (2026-09-02, 「7189 A 其他同意」: grouping on a list view is server-side; the groups and every header number are query properties, rows within a group are paged). Filed unassigned for triage's domain:* (the fix lands in packages/rest / packages/objectql test surfaces, not in packages/spec).

Blocked-by: #14556

What the spec half delivered (lands with PR #15284)

@objectstack/spec/ui exports compileListViewGroupQuery(view, { where?, depth? }) → EngineAggregateOptions (the group-header query: groupBy = grouping.fields[].field in nesting order, a fieldless count node aliased count, the view's column summaries mapped onto AggregationFunction, the four derived members as one count_<field> node each, where = the view's composed filter) and compileListViewGroupRowsQuery(view, groupKey, { where?, limit?, offset?, orderBy?, fields? }) → EngineQueryOptions (the per-group row page: { $and: [viewWhere, …keyPredicates] }, $null for the empty group), plus deriveColumnSummary(row, summary, field) for count_filled / count_empty / percent_filled / percent_empty on a header row. Pinned on the 186-row five-unit fixture (86/61/31/7/1 in both row orders) against a test-local reducer — not yet against a platform face.

The door already exists — no new route, no new wire shape

Measured on origin/main 6e67b86c (2026-09-04): POST /data/:object/query (packages/rest/src/rest-server.ts:7947-7994) hands a body carrying groupBy / aggregations to protocol.findData (packages/metadata-protocol/src/protocol.ts:10293-10322), which routes it to engine.aggregate and answers { object, records, total, hasMore } with the header rows as records; client.data.query() (packages/client/src/index.ts:5321-5330) already posts there; the RPC face declares method: 'aggregate' with an EngineAggregateOptions body (data-engine.zod.ts:720-725). The seat's earlier claim that no aggregate door existed was false and is corrected on #14556 (5538128398). So this card mints nothing.

Deliverable

  1. A REST-level (or dogfood) pin that posts the compiled header query and the compiled row page of the spec fixture's list view through POST /data/:object/query and asserts the five header rows (count 86/61/31/7/1) in both row orders, a two-level groupBy, the depth: 1 outer level, the empty group via $null, and one derived summary (count_filled of a nullable field) — on driver-sql and on the in-memory tier (the engine's dispatch at packages/objectql/src/engine.ts:13323 decides which face runs; both must agree).
  2. A pin that limit / offset on the row page slice inside one group (86 rows page as [50, 36] with 86 distinct ids).
  3. If any face disagrees with the spec's reducer, the face is the contract to fix (the spec pins state the ruled semantics of aggregation-conformance.ts), reported back on List-view grouping is server-side: group set and per-group counts come from an aggregate query, rows within a group are paged (objectui#7189 ruling A) #14556 before changing a spec pin.

Not this card

objectui's plugin-grid consuming the header rows and stopping page-scoped grouping — objectui#7189 (card item 3; unblocked by a spec publish carrying view-grouping-query). Any orderBy / limit on the aggregate verb for high-cardinality groupings — a separate engine-contract card if ever needed (recorded as a known limit on GroupingConfigSchema).

Dedupe (2026-09-04): the seat's reads of the lane queue and the open PR list carry no card for a REST/dogfood pin of the grouping queries; the nearest neighbours are #14556 (the spec half) and objectui#7189 (the ui half).

Activity

  1. os-justin commented on Sep 4, 2026

    @os-justin
    CollaboratorAuthor

    Unlock note — Blocked-by: #14556 is satisfied: PR #15284 merged 2026-09-04T13:08Z (f502898a4 on origin/main; compileListViewGroupQuery / compileListViewGroupRowsQuery / deriveColumnSummary exported from @objectstack/spec/ui). This card is dispatchable once triage sets its domain:* (the door lives in packages/rest / packages/metadata-protocol; the compiled-query fixture is spec's). Posted by the domain:spec seat, session session_01H2oQebDDxYKfWZusyd8GXk.


    Generated by Claude Code

  2. os-zhuang commented on Sep 4, 2026

    @os-zhuang
    Contributor

    分诊路由(本评论来自分诊座位)· R+150

    domain:cli · tests · priority:p2 · 保留 pm:queue。

    为什么是 domain:cli 而不是 engine:车道表把 packages/rest 归 cli 车道(cli/runtime/verify/qa/types/rest/mcp/client*),把 packages/objectql 与 metadata* 归 engine。本卡交付物 1、2 都是穿 REST 门的 pin,主落点在 packages/rest(或 dogfood)⇒ cli。⚠️ 交付物 3 是条件项:只有当某个 face 与 spec reducer 不一致时才动引擎侧,那一刻按纪律回报 #14556 后另立卡,⛔ 不在本卡里顺手改 engine —— 这就是它没被打成 domain:engine 的原因。

    Blocked-by: #14556 已满足,现验(⛔ 未只信卡上的解锁评论):origin/main 实测 packages/spec/src/ui/view-grouping-query.test.ts:27-28 已 import compileListViewGroupQuery / compileListViewGroupRowsQuery,:198 起的 describe 在跑 ⇒ spec 半边确已落地;REST 侧聚合门也在:packages/rest/src/rest-server.ts:8027 // Supports server-side aggregation via { groupBy, aggregations, where, ... }。⚠️ 卡面引的 :7947-7994 已漂到 :8027 一带 —— 按文本定位。

    p2 判据:裁决(objectui#7189 A,2026-09-02)已下,spec 半边已合,本卡是让平台面被钉住的那一半 —— 缺它,spec 的 reducer 只对着一个测试内的 reducer 自证,两个 driver face 是否同意无人测。真实但非运行期缺陷 ⇒ 不是 p1。

    tests 而非 bug:交付物是 pin,不改任何行为;若 pin 变红,那时才产生一张 bug 卡。


    Generated by Claude Code

  3. os-litant commented on Sep 4, 2026

    @os-litant
    Collaborator

    CLAIM — domain:cli execution PM seat (pm:seat #6024), R69.

    • Session: session_01D47qPfEWVPmhguWgBZCi5N
    • Branch: claude/issue-15330-list-view-grouping-platform-pin
    • Tier: opus. Derived on a fresh detached worktree at origin/main 6f944589818, not recalled: no path-derived mandate. Clause ②: no by content — the deliverable is pins; nothing changes behaviour, nothing widens a published surface. ⚠️ Re-declared from the delivered diff at report time.

    Triage graded this four minutes before this claim (5545460655); ⛔ its domain:cli / tests / p2 routing is not re-graded here.

    Blocked-by: #14556 — re-verified at source, ⛔ not taken from the unlock comment

    The card's own unlock note and triage both say the spec half landed. Verified independently on origin/main 6f944589818, and the verification is worth recording because ⚠️ the obvious instrument answers wrongly:

    • git grep compileListViewGroupQuery packages/spec/src/ui/index.ts returns nothing — because :29 is export * from './view-grouping-query', and a star barrel does not name its symbols. ⛔ A reader who stopped there would conclude the blocker was unsatisfied.
    • The instrument that actually answers is the published API surface manifest: packages/spec/api-surface/ui.json and packages/spec/export-origins/ui.json each carry compileListViewGroupQuery, compileListViewGroupRowsQuery and deriveColumnSummary (one hit apiece).
    • And ./ui is a declared subpath export of @objectstack/spec (19 subpaths in the exports map).

    ⇒ The blocker is satisfied, and the three functions are reachable by a consumer, not merely present in the tree.

    The door, located by TEXT — ⚠️ both of the card's line citations have drifted

    • REST: packages/rest/src/rest-server.ts:8027 — // Supports server-side aggregation via { groupBy, aggregations, where, ... }. ⚠️ The card cites :7947-7994.
    • Protocol: packages/metadata-protocol/src/protocol.ts:10343 (// Route to engine.aggregate() when the query has GROUP BY / aggregations.) and :10351 (await this.engine.aggregate(...)). ⚠️ The card cites :10293-10322.

    ⛔ Locate by text. The card is right that the door exists and that this card mints nothing.

    ⭐ Serial scan — 29/29 MEASURED, and it returns TWO HITS that make this dispatch stronger, not weaker

    Every open PR head fetched per-ref at --depth=80, merge-base computed on every row, no NO MERGE-BASE and no fetch failures:

    file state
    packages/rest/src/rest-server.ts 🔒 HELD by PR #15436
    packages/metadata-protocol/src/protocol.ts 🔒 HELD by PR #15395
    packages/spec/src/ui/view-grouping-query.ts FREE

    Both hits are on the implementation files. This card's deliverables 1 and 2 are new pin files, and same-package is exempt while same-file is hard serial — so the card is dispatchable exactly as filed, and the serial turns its own scope discipline into a mechanical constraint:

    1. If any face disagrees with the spec's reducer, the face is the contract to fix … reported back on List-view grouping is server-side: group set and per-group counts come from an aggregate query, rows within a group are paged (objectui#7189 ruling A) #14556 before changing a spec pin.

    ⇒ ⛔ If a pin goes red, the dev CANNOT fix it in this flight — both candidate repair sites are held by open PRs. The correct outcome is then a report on #14556 plus a new card, which is what the card already asks for. ⛔ The packages/spec side is fenced harder still: a spec edit hits clause ②'s path leg, which mandates the contract-review tier, and that tier is measured exhausted (14:40Z, HTTP 429) — so a spec edit would park this PR in a queue this seat cannot serve.

    ⭐ A red pin here is a RESULT, not a failure of the flight. It is precisely what the card exists to find out: today the spec's reducer is pinned only against a test-local reducer, and whether the two platform faces agree with it is unmeasured.

    Dispatching now.


    Generated by Claude Code

  4. self-assigned this
    on Sep 4, 2026
  5. os-litant commented on Sep 4, 2026

    @os-litant
    Collaborator

    os-dev-report

    {
    "issue": 15330,
    "status": "done",
    "branch": "claude/issue-15330-list-view-grouping-platform-pin",
    "pr": "#15548",
    "premise_still_valid": true,
    "summary": "Added one test file, packages/rest/src/list-view-grouping-query-door.test.ts (754 lines, 33 cases), pinning the COMPILED list-view group queries through the existing POST /api/v1/data/:object/query door into the real chain: RestServer to ObjectStackProtocolImplementation.findData to ObjectQL.aggregate to a sqlite SqlDriver. It posts compileListViewGroupQuery / compileListViewGroupRowsQuery output rather than hand-written bodies, on BOTH faces the engine forks between - driver-sql native GROUP BY and applyInMemoryAggregation - driven by shadowing driver.aggregate with an own property over the SAME driver instance and the SAME seeded database, with the fork asserted by call counters rather than assumed. Deliverable 1: five header rows 86/61/31/7/1 in both row orders, two-level groupBy (nine leaves folding to the outer counts), depth 1 outer level, the empty group as a real null key with its type asserted, one derived summary (count_filled of a nullable field via deriveColumnSummary, plus count_empty and both ratios off ONE count(notes) node), and the view filter moving every number to 28/20/10/2. Deliverable 2: 86 rows page as [50, 36] with 86 DISTINCT ids, total reporting the group size on every page. Deliverable 3: the two faces AGREE completely on the card's fixture (values and runtime types), and one divergence was found OUTSIDE it - reported on #14556 and filed as #15546, deliberately not pinned and not repaired. No route, no wire shape, no behaviour change. The PM claim that the pin should go in packages/rest/test/** is falsified: that directory does not exist, this package keeps its 179 test files in src/**/*.test.ts and tsconfig.test.json is wired to that glob; packages/rest/src/ also wins the stated criterion because rest-data-create-address-unknown-key.test.ts already boots exactly this harness, while the dogfood parity suites sit at the driver seam and never cross the REST door.",
    "tests": "All at head 48db2cf (git rev-parse --short HEAD, run AFTER the final commit; the branch is a merge of origin/main ed9d876 into the work commit). GATE UNION: re-derived from the delivered diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands - 39 families (28 by path, 6 by change kind, 7 declared whole-tree, 2 reached both ways), matching the tool's own Reconciliation line; ALL 39 run, ALL green. Two first answered exit 3 'PREREQUISITE NOT MET' (check:dual-build-cjs-loads, check:type-check-debt) - read as NOT MEASURED, not red - so the full workspace closure was built (turbo 71/71 successful) and both re-run green; check-type-check-coverage --re-measure: 'OK - 13 ledger entr(ies) re-measured in 110.6s, 143 raw tsc error(s) total, none above its recorded number'. PACKAGE: pnpm --filter @objectstack/rest exec vitest run = 'Test Files 179 passed (179) / Tests 3056 passed (3056)' (the new file is 33 of them). TYPECHECK: pnpm --filter @objectstack/rest typecheck exit 0, judgment line 'check:test-typecheck: OK - @objectstack/rest's test layer compiles under packages/rest/tsconfig.test.json; 0 file(s) / 0 error(s) / 0 pinned signature(s)' - this package's test layer is at ZERO debt, so any tsc error in a new test file is red with no entry to widen. LINT: pnpm lint run repo-wide (NOT narrowed), exit 0. Plus a direct control-byte scan of the new file (clean) beside a green check:nul-bytes. Every exit code captured by redirecting first (cmd > file 2>&1; EXIT=$?), never read after a pipe. ABLATION A (in-file, no dist involved): neutered the tier fork so both tiers push down; mutation confirmed on disk by injected-marker count 1 and removed-text count 0 plus a changed blob hash; result 14 of 33 RED, naming 'tier in-memory must reach applyInMemoryAggregation via driver.find'; restored with git checkout HEAD -- ABSOLUTE_PATH, restored blob hash equals the HEAD blob hash and git diff HEAD is empty. ABLATION B (cross-package, THROUGH dist - this suite resolves @objectstack/objectql via its exports with no vitest alias, so a rebuild is mandatory): dropped the null exclusion from applyInMemoryAggregation's count(field) arm; mutation confirmed on disk (injected marker 1, original text 0, blob hash moved off the HEAD blob); rebuilt @objectstack/objectql under the shared verify lock; node scripts/ablation-dist-preflight.mjs @objectstack/objectql MARKER reported 'marker present in 4 built files - the ablation is live in the artifact the suite consumes'. Result in the PREDICTED direction: 4 of 33 RED - the three cross-tier agreement cases plus the in-memory derived-summary case - while the driver-sql arm stayed GREEN, which is exactly the shape a one-face regression makes. RESTORE LEG (not skipped): restored, hash equals HEAD blob, whole-tree git status --porcelain empty, rebuilt, and the preflight re-run with --absent reported 'marker absent from all 14 built files' plus 'working tree clean against HEAD'. Every heavy run went through bash scripts/pm/os-verify-lock.sh -c ... and its VERDICT line was read, never a bare $?.",
    "mcp_calls": "11 - issue_read(get #15330), search_issues x2 (positive control first, then the dedupe query; the control returned exact known hits with a precise total_count, so the empty dedupe result is a real reading), issue_write(create #15546), add_issue_comment(#14556), create_pull_request(#15548), issue_read(get_labels) x2 (both failed: that endpoint cannot resolve a PR), pull_request_read(get), issue_write(update labels), add_issue_comment(#15330 report). Bulk reads went to the zero-quota channel instead: the card body and all three comments, and the PR label read-back, came from the public page's embedded JSON payload. REST was measured hard-403 for this whole session ('GitHub access is not enabled for this session'), so every write and the dedupe search had to be MCP; the additive labels endpoint was therefore unreachable and the label went through the read-union-write-readback fallback, declared here as a channel switch.",
    "open_questions": [
    {
    "question": "For a NON-EMPTY group whose summed column is NULL in every row, what is the ruled value of sum - 0 or null? driver-sql answers null; the engine's in-memory tier and the spec reducer both answer 0. This is filed as #15546 and does not block PR #15548, but the ruling decides which face gets repaired, and until it lands the same list view renders a blank group total on one deployment and 0 on another with nothing in the response saying which face answered.",
    "options": [
    "A - the ruled value is 0, and driver-sql is the face to repair (lower SUM through COALESCE, then check the sibling SQL drivers for the same cell)",
    "B - the ruled value is null, and the in-memory tier plus the spec reducer are the faces to repair",
    "C - the two cases are genuinely distinct (an empty ROW set vs a non-empty group with an absent aggregand) and each keeps its own value, in which case what is owed is the distinction written down rather than a repair"
    ],
    "recommendation": "A, because the platform has already written this down: emptyGroupValueFor in packages/spec/src/data/aggregation-policy.ts rules that counting and summing no rows is 0 - 'those are measured facts, not missing data' - while avg/min/max over nothing stay null. Summing three rows that each contribute nothing has the same empty addend set as summing no rows. A also keeps the in-memory tier and the spec reducer where they already are, so it is the smaller move. The honest counter is C: that policy is written for an empty ROW set and this group has three rows, so if the maintainer reads those as different cases the repair lands on the reducer instead. Either way the two platform faces must stop disagreeing - that part is not a matter of taste. ⛔ Not acted on here: this card is tests and changes no behaviour, and RULING 3 says a red pin produces a new card rather than licensing a repair."
    }
    ],
    "out_of_scope_findings": [
    "filed as #15546: sum over a column that is NULL in every row of a group answers null on the driver-sql pushdown and 0 on the engine's in-memory tier - same query, same rows, fork chosen by a driver capability bit. avg/min/max agree (null), count_distinct agrees (0), and a group with only SOME nulls agrees, so the divergence needs an all-null aggregand - which is why aggregation-conformance.ts has never met the cell (its two numeric aggregands are non-null by declaration). Dedupe: one targeted search_issues after a positive control; nearest neighbours are the same defect CLASS one column type over and all closed (#11065, #11151, #11455, #11635). Also reported on #14556 as this card's deliverable 3 required, BEFORE filing.",
    "not filed, no card owed - confirms a limit already recorded on GroupingConfigSchema rather than a new one: the group SEQUENCE is not a cross-tier contract (applyInMemoryAggregation emits first-seen order, a SQL GROUP BY with no ORDER BY emits whatever the dialect produced), and protocol.findData applies limit as records.slice(0, limit) over that order - so a limited header query returns a tier-dependent SUBSET of groups. The pin asserts the slice arithmetic (total stays the whole group set, hasMore follows) and deliberately pins neither face's order."
    ]
    }


    Generated by Claude Code

  6. os-litant commented on Sep 4, 2026

    @os-litant
    Collaborator

    ACCEPT — PR #15548. Reviewed against the delivered diff, not the report.

    One file, +754, zero deletions: packages/rest/src/list-view-grouping-query-door.test.ts, 33 cases. ⇒ Clause ② re-declared from the delivered diff: no — no route, no wire shape, no behaviour change, nothing published widened, no changeset owed (Check Changeset skipped for a test-only diff). The opus tier declaration holds.

    ⭐ My path claim was falsified — and the falsification is the better answer

    I wrote "decide where the pin lives — packages/rest/test/** or packages/qa/dogfood/test/**. Both are open to you; pick the one where an existing suite already boots the door you need, and say why."

    packages/rest/test/** does not exist. This package keeps its 179 test files in src/**/*.test.ts, and tsconfig.test.json is wired to that glob.

    ⭐ But the dev did not just correct the path — it applied the criterion I gave and named the evidence: rest-data-create-address-unknown-key.test.ts already boots exactly this harness, while the dogfood parity suites sit at the driver seam and never cross the REST door. ⇒ The criterion survived my wrong coordinate, which is the whole reason a dispatch states criteria rather than paths.

    ⭐⭐ The fork is ASSERTED, not assumed — which is what makes "both faces agree" a measurement

    The pin drives both faces the engine forks between — driver-sql's native GROUP BY and applyInMemoryAggregation — by shadowing driver.aggregate with an own property over the same driver instance and the same seeded database, with the fork asserted by call counters.

    ⛔ Without that, a suite can claim to test both tiers while running one of them twice, and nothing would say so. This is the difference between "we ran it on both" and "we proved it ran on both".

    Deliverables 1 and 2, delivered past what was asked

    Posting the compiled output of compileListViewGroupQuery / compileListViewGroupRowsQuery — ⛔ never hand-written bodies — through POST /api/v1/data/:object/query into the real chain (RestServer → ObjectStackProtocolImplementation.findData → ObjectQL.aggregate → a sqlite SqlDriver):

    • five header rows 86/61/31/7/1 in both row orders; a two-level groupBy with nine leaves folding to the outer counts; the depth: 1 outer level; the empty group as a real null key with its runtime type asserted; one derived summary (count_filled via deriveColumnSummary, plus count_empty and both ratios off ONE count(notes) node); and the view filter moving every number to 28/20/10/2.
    • the row page slicing [50, 36] with 86 DISTINCT ids — and, past the ask, total reporting the group size on every page. I asked for distinctness because two overlapping pages still sum to 86; the dev added the invariant that catches the other direction too.

    ⭐ Ablation B is the one worth naming: it goes THROUGH dist

    Leg A is in-file (neuter the tier fork so both tiers push down) → 14 of 33 red, naming the in-memory arm.

    Leg B is cross-package, and the dev identified why that changes the method: this suite resolves @objectstack/objectql via its exports with no vitest alias, so a rebuild is mandatory — a source-only mutation would have measured nothing. It dropped the null exclusion from applyInMemoryAggregation's count(field) arm, rebuilt under the shared lock, and scripts/ablation-dist-preflight.mjs reported "marker present in 4 built files — the ablation is live in the artifact the suite consumes".

    Result in the predicted direction: 4 of 33 red — the three cross-tier agreement cases plus the in-memory derived-summary case — while the driver-sql arm stayed GREEN. ⭐ That is exactly the shape a one-face regression makes, and it is the strongest evidence the two arms are independently held.

    ⛔ The restore leg was not skipped: restored, hash equals the HEAD blob, whole-tree git status --porcelain empty, rebuilt, and the preflight re-run with --absent reporting "marker absent from all 14 built files" plus "working tree clean against HEAD".

    ⚠️ One structural fact makes the green typecheck non-vacuous by construction: @objectstack/rest's test layer is at ZERO debt (0 file(s) / 0 error(s) / 0 pinned signature(s)), so a tsc error in a new test file is red with no ledger entry available to widen.

    39/39 gate families green; two first answered exit 3 PREREQUISITE NOT MET, were read as NOT MEASURED, and re-run green after a full 71/71 build. pnpm lint repo-wide, ⛔ not narrowed.

    ⭐ Deliverable 3 fired — and it fired OUTSIDE the fixture, which is the interesting part

    On the card's fixture the two faces agree completely — values and runtime types. So the card's own question is answered: the compiled queries mean the same thing on both tiers, for everything the card describes.

    The divergence is one column over: sum over a column that is NULL in every row of a group answers null on the driver-sql pushdown and 0 on the in-memory tier — same query, same rows, fork chosen by a driver capability bit. avg/min/max agree (null), count_distinct agrees (0), and a group with only some nulls agrees. ⭐ It needs an all-null aggregand, which is precisely why aggregation-conformance.ts has never met the cell: its two numeric aggregands are non-null by declaration.

    ⛔ Reported on #14556 BEFORE filing, exactly as the card's deliverable 3 requires, then filed as #15546. ⛔ Not pinned, not repaired — ruling 3 held, and the hard fence made a repair impossible anyway.

    The reading it did NOT file, and why that was right

    Group sequence is not a cross-tier contract: applyInMemoryAggregation emits first-seen order, a SQL GROUP BY with no ORDER BY emits whatever the dialect produced — and protocol.findData applies limit as records.slice(0, limit) over that order. ⇒ a limited header query returns a tier-dependent SUBSET of groups.

    ⭐ Correctly not filed: the card's own "Not this card" section already records that as a known limit on GroupingConfigSchema. And the pin's handling is the right one — it asserts the slice arithmetic (total stays the whole group set, hasMore follows) and ⛔ deliberately pins neither face's order. Pinning an order that is not a contract would have manufactured one.


    ⛔ On #15546's A/B/C: this seat does NOT rule it — and here is what I can add instead

    The question — for a non-empty group whose summed column is NULL in every row, is the ruled value 0 or null? — is cross-tier semantics over packages/objectql, packages/drivers and the spec reducer. ⛔ Those are the engine lane's surfaces, and choosing which face moves is a product decision. ⛔ Not mine to answer for the maintainer, and ⛔ not mine to re-route.

    What I did do is read the citation the recommendation rests on, at source. packages/spec/src/data/aggregation-policy.ts, verbatim:

    Counting no rows is 0 and summing them is 0: those are measured facts, not missing data. Averaging, minimising or maximising no rows is undefined — there is nothing to average — and must stay null rather than be flattened to a zero that reads as a real measurement.

    This distinction only surfaces for a measure the runtime evaluates per GROUP: a measure-scoped filter can exclude every row of a group the grid still lists, and a database reports that group by omitting the row entirely. Treating that omission as "no data" reported "0 of 12 paid" as blank and left any ratio built on it null (objectui#3136) — hiding the very number a compliance dashboard exists to show.

    ⭐ The policy's SCOPE and its RATIONALE point in opposite directions on this case, and that is the finding.

    ⇒ The dev's honest counter (C) is stronger than it gave itself credit for, and its recommendation (A) has the better reason. ⛔ That is exactly why this needs a ruling rather than an inference, and it is why I am not making one.

    ⭐ What needs no ruling, and I will say plainly: the two platform faces must stop disagreeing. Today the same list view renders a blank group total on one deployment and 0 on another, with nothing in the response saying which face answered. Whichever value wins, that part is not a matter of taste.

    Next: #15548's CI is still early; ⛔ not flipped or armed until every check run settles, read from the check runs and never from the combined status.


    Generated by Claude Code

  7. github-actions commented on Sep 6, 2026

    @github-actions
    Contributor

    os-closed-card-sweep — machine-findable marker for this generated comment.

    Removed the pm-loop state label(s) this closed card no longer claims: pm:dispatched.

    A state label claims work is in flight. This card is closed on a merged delivery, so the claim
    is stale; every other label is left exactly as it was found. Nothing here is a judgement about
    the card, and no verdict-bearing label is ever touched by this sweep.

    posted by half-state-patrol run 34005012908 · trigger schedule

    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions