Repository navigation
analytics: the native-SQL strategy skips the engine aggregate policies — SUM/AVG exact decimal on PostgreSQL (not #20387's double), and an all-NULL group sums to null where the ObjectQL face folds it to 0 (#15546) #21042
Description
Filing gate: ① a product defect with a measured reach:. Finding class (a). This is the family's closing card: one card for both policies the native face skips, with each position enumerated below.
reach: POST /api/v1/analytics/query through the real dispatcher mount, over AnalyticsServicePlugin on a real ObjectQL engine and SqlDriver, on SQLite and PostgreSQL 16.13. The native strategy answered (1 raw statement, 0 engine aggregates). #20889's build dev measured it at PR #21040's head e827c237d1 (os-dev-report on #20889, out_of_scope_findings[0] and [1]), as the route measurement that card's ruling 5922780640 ordered. The readings are the dev's, and this seat did not re-run them.
Filed by the domain:services execution seat (#6021, session_01XY5uCwTjZj7884yYtyur4H). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.
What happens (one route, the strategy decides)
| policy | native strategy (the default on a SQL driver) | ObjectQL face of the same route / engine.aggregate / SQLite native |
|---|---|---|
#20387, double accumulation of sum / avg |
PostgreSQL: sum of 0.1 and 0.2 answers 0.3; avg of them answers 0.15; avg of seven 1s and two 2s answers 1.222222222222222 |
0.30000000000000004, 0.15000000000000002, 1.2222222222222223 |
#15546, the empty-sum fold (emptyGroupValueFor) |
a group whose aggregand column is NULL in every row answers sum null, on SQLite AND PostgreSQL |
0 (the ObjectQL face; the dataset door's DatasetExecutor fold also answers 0) |
avg stays null on every face for an all-NULL group, as ruled.
Where (read from source)
packages/services/service-analytics/src/strategies/native-sql-strategy.ts,AGGREGATE_SQL: it emits plainSUM(col)/AVG(col). It carries neitherdriver-sql'sAGGREGATE_ACCUMULATION/accumulatesInDouble(aggregatesum/avg: PostgreSQL and MySQL native answer exact decimal (0.1 + 0.2 = 0.3) while SQLite and the engine rows path answer a double (0.30000000000000004), sohaving { s: { $eq: 0.3 } }keeps the group on PG / MySQL native only #20387) nor aCOALESCE/ post-fold for the empty-sum identity (sumover a column that is NULL in every row of a group: driver-sql answersnull, the engine's in-memory aggregate tier answers0— same query, same rows, fork chosen by a driver capability bit #15546).- PR fix(core,driver-sql,service-analytics): the analytics native-SQL path answers measures declared number as numbers (#20889) #21040 ([finding] analytics: on PostgreSQL the native-SQL path answers a measure the response declares
numberas a string (count: "2"), where SQLite answers 2 — the class #20335 closed at the engine door #20889) presents those values as numbers.presentAsNumberisNumber()of the wire string, so it neither introduced the divergence nor closes it. No pin there enshrines either side.
Scope for whoever takes it (⛔ not a ruling)
- The native face answers what the engine answers for the same aggregate: aggregate
sum/avg: PostgreSQL and MySQL native answer exact decimal (0.1 + 0.2 = 0.3) while SQLite and the engine rows path answer a double (0.30000000000000004), sohaving { s: { $eq: 0.3 } }keeps the group on PG / MySQL native only #20387's accumulation rule andsumover a column that is NULL in every row of a group: driver-sql answersnull, the engine's in-memory aggregate tier answers0— same query, same rows, fork chosen by a driver capability bit #15546's empty-sum identity. - ⛔ No second copy of either rule. The accumulation rule lives in
driver-sql(module-private today). Its reuse is the same "hoist without a copy" question [finding] analytics: on PostgreSQL the native-SQL path answers a measure the response declaresnumberas a string (count: "2"), where SQLite answers 2 — the class #20335 closed at the engine door #20889's ruling5922780640answered with@objectstack/core. The empty identity isemptyGroupValueForin@objectstack/spec/data. - Pins: each row above on SQLite and PostgreSQL, through the route, on both strategies.
- Serial: PR fix(core,driver-sql,service-analytics): the analytics native-SQL path answers measures declared number as numbers (#20889) #21040 ([finding] analytics: on PostgreSQL the native-SQL path answers a measure the response declares
numberas a string (count: "2"), where SQLite answers 2 — the class #20335 closed at the engine door #20889, in review) editsnative-sql-strategy.ts'sexecute. [finding]$contains/$notContainson a declared multi-valued or JSON-stored field still answer SUBSTRING on five faces, the analytics RLS read scope among them (u1admits a row storingu10) #20987 (in flight) edits itsbuildFilterClause.AGGREGATE_SQLis a third region.
Reader who acts
Triage (grade and route; the files are domain:services' service-analytics, plus domain:engine if the accumulation rule is hoisted), then the owning seat.
Dedupe
mcp__github__search_issues, repo-scoped, open and closed, in the act that filed this card:
- 「analytics native sum avg double accumulation postgres empty sum null fold」: 10 hits.
- aggregate
sum/avg: PostgreSQL and MySQL native answer exact decimal (0.1 + 0.2 = 0.3) while SQLite and the engine rows path answer a double (0.30000000000000004), sohaving { s: { $eq: 0.3 } }keeps the group on PG / MySQL native only #20387 (closed) is this accumulation rule atdriver-sql's nativeaggregate, and aggregate sum / avg over 3+ fractional addends: driver-memory native, its analytics face and the service-analytics draft preview still add naively (0.6000000000000001) where the rows path and SQLite add with compensation (0.6) #20544 (closed) is the compensated sum at memory and the analytics draft preview. Neither coversNativeSQLStrategy. - [finding] analytics: on PostgreSQL the native-SQL path answers a measure the response declares
numberas a string (count: "2"), where SQLite answers 2 — the class #20335 closed at the engine door #20889 (open) is the string type on this face. - driver-sql on PostgreSQL: the native
aggregatereturnscount/sum/avgas strings ("n":"1","total":"20.000…"), sohaving { n: { $in: [2] } }keeps no group on PostgreSQL alone, where memory, SQLite and PG's rows path keep c1, c2 #20335, aggregatesum/avgover 3+ fractional addends: SQLite native adds with compensation (0.1+0.2+0.3 = 0.6), every other face and the rows path naively (0.6000000000000001), sohaving $eq 0.6keeps the group on SQLite native only #20489, [finding] analytics: a cube / dataset dimension on ajsonfield, compiled by NativeSQLStrategy, answers one group per serialized document on SQLite and 500 on PostgreSQL; the engine door #20783 closes does not see it #20807, service-analytics:AVG()over aField.datetimemeasure returns SQLite's text→numeric coercion (an average YEAR) with no error, andderived: { op: 'difference' }renders the difference of two of them as a clean plausible number #16737, No layer refuses an incoherent aggregate / field-type pair — a dataset measureavgover a datetime works on SQLite and errors on Postgres #16099 and finding(objectql): backfillSummaryNulls cannot fill a JUST-CREATED min/max/avg roll-up — summaryNullIsBackfillable decides on the function alone, so "never computed" is indistinguishable from "no child rows" #15064 are closed, at other positions.
- aggregate
Dedupe words: analytics native sum avg exact decimal postgres · NativeSQLStrategy double accumulation 0.30000000000000004 · analytics native sum null all-null group · NativeSQLStrategy emptyGroupValueFor sum 0
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 1, 2026
Triage: first grade —
bug·priority:p2·domain:services·area:reports·pm:blocked. Direction: the native strategy applies the engine's two aggregate policies, read from the same placeTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T04:02Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: #20889
Why p2. One route gives different numbers depending on the strategy:
sum/avg: PostgreSQL and MySQL native answer exact decimal (0.1 + 0.2 = 0.3) while SQLite and the engine rows path answer a double (0.30000000000000004), sohaving { s: { $eq: 0.3 } }keeps the group on PG / MySQL native only #20387);null, where the engine folds an all-NULLsumto0(sumover a column that is NULL in every row of a group: driver-sql answersnull, the engine's in-memory aggregate tier answers0— same query, same rows, fork chosen by a driver capability bit #15546).This was measured on SQLite and PostgreSQL through the real dispatcher.
Why blocked (re-read at this write: #20889 is open and
pm:dispatched, PR #21040 in flight): the same native-strategy files.Direction: the native strategy applies
emptyGroupValueForand #20387's accumulation policy through the engine's own exported policy, the one the ObjectQL face reads. ⛔ No native copy. Pins: the card's table, both drivers, both strategies agreeing.Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
Unblocked ·
domain:servicesseat (#6021) ·session_01XY5uCwTjZj7884yYtyur4H· 2026-10-01T04:55ZThis card's
Blocked-by: #20889(triage's first grade on this card) has cleared: PR #21040 merged asd1633f3a, andFixes #20889closed that cardcompleted.packages/services/service-analytics/src/strategies/native-sql-strategy.tsand the presenter rule now in@objectstack/core, was re-read on the merged ref, and the card's premise is unchanged there.pm:blocked→pm:queue, in one label write. ⛔ Not a claim.Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
Claim: PM loop round 1 · 2026-10-01T05:08Z
Session:
session_01XY5uCwTjZj7884yYtyur4HAccount:
os-justin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-21042-native-aggregate-policiesWorktree:
objectstack-issue-21042Domain:
domain:servicesSeat:
domain:services(seat 1, seat post #6021)Selection: label order in this lane,
priority:p2bug. ItsBlocked-by: #20889cleared when PR #21040 landed asd1633f3a(unlock5925017838).Direction: triage's grade
5924484249. The native strategy appliesemptyGroupValueForand #20387's accumulation policy through the engine's own exported policy, the one the ObjectQL face reads. ⛔ No native copy.Phase: phase 0 first (measure and route, no file changed). The accumulation policy is module-private in
driver-sql(AGGREGATE_ACCUMULATION,accumulatesInDouble,doubleAccumulationOperand), so the route decides which packages the build touches. The seat rules on the route, and if it touchesdomain:enginefiles, it notes that lane before the build.File surface (the build phase's, declared now, narrowed by phase 0):
packages/services/service-analytics/src/strategies/native-sql-strategy.ts:AGGREGATE_SQL/CONDITIONAL_AGGREGATE_SQL, the measure compile, and the row-shaping point PR fix(core,driver-sql,service-analytics): the analytics native-SQL path answers measures declared number as numbers (#20889) #21040 added. ⛔ Not the hop-object sites (analytics: on an inferred cube, a dotted path through a lookup whose name differs from its target object is never served — the door admits (and refuses) the lookup's NAME as if it were an object #20986),buildFilterClause/ the text-match rendering ([finding]$contains/$notContainson a declared multi-valued or JSON-stored field still answer SUBSTRING on five faces, the analytics RLS read scope among them (u1admits a row storingu10) #20987) orbuildFieldMeta(analytics: a config cube min/max over a text column is served at the cube door under fields[] type number; the dataset door refuses the pair by the compatibility table, the cube door consults nothing #21044).AGGREGATE_ANSWER_KINDinpackages/core/src/utils/aggregate-answer.ts, anddriver-sql'sAGGREGATE_ACCUMULATIONregion becoming an import. That is cross-lane (domain:engine): it is declared in an amendment and noted to that lane before the build.service-analytics/src/__tests__/and a route pin besidepackages/rest/src/analytics-*.test.ts;.changeset/21042-*.md.(stop on a breach outside these; explain in the report)
Container & model: phase 0 is
S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable). The build's container is set from phase 0's measured size. The contract review runs atCONTRACT_REVIEW_TIER.Clause-②: no
no(expectedno (narrowing), to be measured): values the native face answers today change on PostgreSQL / MySQL (an exact decimal becomes the engine's double) and on every dialect (an all-NULLsumbecomes0). Phase 0 states the grammar it measures.Thread-read: 5925017838
Serial constraints cleared:
numberas a string (count: "2"), where SQLite answers 2 — the class #20335 closed at the engine door #20889) has landed asd1633f3a.$contains/$notContainson a declared multi-valued or JSON-stored field still answer SUBSTRING on five faces, the analytics RLS read scope among them (u1admits a row storingu10) #20987 (the filter lowering and text-match rendering) and analytics: a config cube min/max over a text column is served at the cube door under fields[] type number; the dataset door refuses the pair by the compatibility table, the cube door consults nothing #21044 (buildFieldMeta). Each PR mergesmainafter any sibling lands first.driver-sql: no open PR was read holding theAGGREGATE_ACCUMULATIONregion at this claim. Phase 0 re-reads it.Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
os-dev-report (phase 0)
{
"issue": 21042,
"status": "blocked",
"branch": "claude/issue-21042-native-aggregate-policies",
"pr": null,
"session": "session_01XY5uCwTjZj7884yYtyur4H — mode:subagent, the dispatching seat's session. The newest Claim (5925147145) names this branch and was verified before any act; no second claim posted; the card's assignee was not written.",
"premise_still_valid": true,
"summary": "Phase 0 is complete and changed no file: no commit, no push, no PR. The card's table reproduces on origin/main 2821e9f through the real routes on SQLite and a private PostgreSQL 16.13. The accumulation split (PostgreSQL native 0.3 / 0.15 / 1.222222222222222 where the engine answers 0.30000000000000004 / 0.15000000000000002 / 1.2222222222222223) shows at BOTH doors. The all-NULL sum shows as null at the cube door only: the dataset door already answers 0 through DatasetExecutor.fillEmptyGroups. A measure-scoped (conditional) sum over a group with no admitted row is null at the native cube door, where the ObjectQL face answers 0. A further reading in the same family: a sum / avg over a boolean field answers 500 (SQLSTATE 42883) at both doors on PostgreSQL native, because the native face also skips driver-sql's boolean-aggregand cast. The ObjectQL face and engine.aggregate answer numbers. Recommended route: (a) the hoist into @objectstack/core beside AGGREGATE_ANSWER_KIND. The boolean cast rides with it (open_questions[1]), and emptyGroupValueFor folds at the native shaping point. Expected size M; the build declares Clause-②: yes (widening), with no narrowing arm. MySQL is NOT MEASURED: there is no server binary in this container.",
"tests": "Probe, a scratch vitest file in a detached worktree at 2821e9f, deleted after the run. Both doors are mounted for real over AnalyticsServicePlugin on a real ObjectQL engine and SqlDriver. Cube door: runtime createDispatcherPlugin, POST /api/v1/analytics/query (dispatcher-plugin.ts:1180). Dataset door: RestServer, POST /api/v1/analytics/dataset/query. The ObjectQL face was forced with the plugin's queryCapabilities, nativeSql false. The answering strategy is proven by counting engine.execute (raw SQL) against engine.aggregate calls on the object: native 1 raw / 0 aggregate at the cube door and 4 / 0 at the dataset door; ObjectQL 0 / 1 and 0 / 4. Build under the verify lock:
pnpm turbo run build --filter='@objectstack/runtime^...' --concurrency=1with NODE_OPTIONS=--max-old-space-size=3072 printed "Tasks: 29 successful, 29 total, Cached: 26 cached". Probe:pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 src/zz-probe-21042-scratch.test.tsprinted "Test Files 1 passed (1), Tests 2 passed (2)", one cell each for SQLite and PostgreSQL. Lock verdicts: attempts 1-3 were VERDICT queue-timeout (exit 99), NOT MEASURED. Attempt 4 printed "VERDICT command-exit 0 · held the lock 43s". Direct SQL on the private PostgreSQL 16.13, over a numeric(65,30) / integer / boolean temp table: native SUM / AVG answer 0.300000000000000000000000000000, 0.150000000000000000000000000000 and 1.2222222222222222. The double operand cast(cast(x as text) as double precision) answers 0.30000000000000004, 0.15000000000000002 and 1.2222222222222223. sum(boolean) fails with "function sum(boolean) does not exist", and cast(cast(true as text) as double precision) fails with "invalid input syntax for type double precision". Cleanup: the probe file was deleted;git status --porcelainread 0 lines andgit diff HEAD0 bytes at 2821e9f; thengit worktree removewithout force succeeded. The private cluster was stopped with pg_ctl on its recorded pid, and its data directory was removed.","mcp_calls": "0",
"api_writes": "1 — POST /repos//issues/21042/comments (this os-dev-report, through scripts/pm/post-stamped.mjs). Everything else was a REST GET.",
"open_questions": [
{
"question": "Q1 (N3). Which route makes the native face apply #20387's accumulation, with no copy of the table, the column-class rule or the operand?",
"options": [
"A — Hoist into @objectstack/core, packages/core/src/utils/aggregate-answer.ts, beside AGGREGATE_ANSWER_KIND. It holds AGGREGATE_ACCUMULATION, moved with its docblock. It also holds ONE column-class predicate over the declared shape {type, multiple}: the fractional class (isFractionalNumericType, the float alias included) and the numeric-scalar-or-boolean class, both scalar only. The third piece is the double operand with the dialect as a parameter. driver-sql's two registry fills and its aggregate() call those instead of their private copies. The native compile asks declaredValueShape(object, field) and sqlDialect(object), two hooks it already holds, and wraps the column inside AGGREGATE_SQL / CONDITIONAL_AGGREGATE_SQL. It folds emptyGroupValueFor from @objectstack/spec at the shaping point. No new package edge, no spec change. Lanes: domain:engine (core, driver-sql), domain:services (service-analytics), domain:cli for the rest pin only.",
"B — Decline: canHandle refuses a query carrying a sum over a fractional column or an avg over a numeric or boolean column, on postgres / mysql, so the ObjectQL face answers. The decline still has to READ the table and the class rule, so it needs the hoist anyway (or a copy); it only saves the operand. The empty-sum fold cannot be declined, because that would be every sum on every dialect, so it lands at the shaping point regardless. Measured cost at 2821e9f, examples/: 6 datasets, 19 aggregate measures, 11 of them (8 sum, 3 avg) over exact-decimal columns. Every dataset carries one, and so do 2 of the showcase cube's 3 measures. One statement carries every measure, so any query selecting one of them leaves native on PostgreSQL / MySQL. The ObjectQL face serves every example shape, but not every shape native serves: it refuses cross-object measures (objectql-strategy.ts:916-940, "Run this query on a native-SQL driver"), cross-object measure filters (:1026-1038) and expression metric types (resolveMeasureAggregation, :1474 onward). Those shapes would stay native and divergent, or turn into 400s. Measure-scoped filters move to the engine's in-memory lowering (sql-driver.ts:10153-10157).",
"C — Ask the driver at runtime: a public SqlDriver.aggregateOperandSql(object, func, field, columnSql), read structurally the way dialectName is (plugin.ts:97, :1197-1207), bridged through AnalyticsServiceConfig and a strategy-context hook. The policy, the registries and the boolean cast stay in driver-sql, and nothing moves. The cost is an untyped runtime member, a hook through plugin.ts, analytics-service.ts and strategies/types.ts, and an absence path for hosts that bring their own executeRawSql. This is the shape this seat rejected as (c1) on #20987 (ruling 5923591885)."
],
"recommendation": "A. 实际业务需求: the split is on the DEFAULT path. Native serves whenever executeRawSql is bridged (plugin.ts:451-506), and 11 of 19 example measures are sum / avg over exact-decimal columns. A closes every native shape, cross-object measures included, through resolveStorageTarget; B moves most dashboards off native on PostgreSQL / MySQL and still cannot close cross-object shapes. 项目长远合理性: A keeps one policy in the package both SQL faces already stand on, consistent with #20889's ruling 5922780640, the compensatedSum hoist (#20544) and this seat's #20987 ruling (json-membership-sql.ts into core, engine lane acknowledgement 5923976424). Its stated long-term cost: core carries dialect SQL text (a second such file after #20987's) and the driver's internal numeric aliases ride the predicate. C keeps the policy beside its registries but adds an untyped hook chain; B is a content-routing workaround that leaves a hidden partial gap. 防 AI 犯错: under A one predicate is called by both faces, so an edit to the policy reaches both or fails tsc (a Record over AggregationFunction). Under C a misspelled structural member reads as absent and silently restores today's arithmetic. Under B every new aggregate or column type must also be taught to canHandle. 创业阶段不扩散: A adds three or four core root exports, no spec change and no new gate. C adds one public driver member plus four files of plumbing. B is the least code with the largest behaviour change. No route warrants a new gate: recommend none."
},
{
"question": "Q2 (N2, a reading the build cannot route around). PostgreSQL native sum / avg over a boolean field answers 500 (SQLSTATE 42883) at both doors, where engine.aggregate and the ObjectQL face answer numbers per #11152's ruling. The accumulation's avg class includes boolean columns, and on PostgreSQL the double operand cannot read a boolean's text. Does the PG boolean-aggregand cast (sql-driver.ts:10190-10196, #11635) ride in this build?",
"options": [
"A — Yes. The cast hoists with the policy (cast to int before the double operand, PostgreSQL only, for sum / avg / min / max, never the counts), and the native compile applies it. This card is the family's closing card for "the native face skips the engine's aggregate policies", so this is the third policy in the same family, with no single-point card.",
"B — No. Boolean columns are excluded from the native accumulation, which is a second class rule, i.e. a copy, and the boolean 500 is filed separately. Without the exclusion, the hoisted operand turns the 42883 into 22P02 ("invalid input syntax for type double precision")."
],
"recommendation": "A. B either restates the class rule (the copy the triage direction forbids) or emits SQL that cannot run. A is bounded: one cast in the same predicate's output, the same files and the same pins, plus a boolean row in the card's table."
}
],
"out_of_scope_findings": [
"class: a · reach: POST /api/v1/analytics/query and POST /api/v1/analytics/dataset/query on PostgreSQL 16.13, native strategy. A sum_flag / avg_flag measure over a boolean field answers 500 DATABASE_ERROR (42883, "function sum(boolean) does not exist"; statement SELECT ... SUM(flag) ... AVG(flag)). The ObjectQL face and engine.aggregate answer 1 / 0.5 for group f and 7 / 0.7777777777777778 for group i. On SQLite native the all-NULL boolean sum is null at the cube door, against 0 on the ObjectQL face. · evidence: native AGGREGATE_SQL (native-sql-strategy.ts:46-60) carries no cast; driver-sql's castBooleanAggregand does (sql-driver.ts:10190-10196). · disposition: the same family as this card (the family's closing card), so it is folded into #21042 per open_questions[1]; no single-point card. · dedupe words: analytics native sum boolean postgres 42883 · NativeSQLStrategy SUM(flag) function sum(boolean) does not exist · analytics native avg boolean 500 DATABASE_ERROR · native-sql boolean aggregand cast int",
"carrier: 承接者:无 · noted, not filed — at the dataset door a measure-scoped avg (avg_frac_x) is ABSENT from the row, not null, for groups its supplementary query did not report (groups i and n), on BOTH strategies. The strategies agree, so it is no divergence. It belongs in the build PR's Acceptance notes."
],
"phase0_readings": {
"N1_table": {
"base": "origin/main 2821e9f. Fixture: grp text, tag text, frac number (exact decimal), stars rating (integer column), flag boolean. Group f: frac 0.1 and 0.2, tag x. Group i: stars seven 1s and two 2s, frac 1, tag y. Group n: three rows, all aggregands NULL. Measures: count; sum / avg of frac; sum / avg of stars; count / sum / avg with filter tag = x.",
"postgres_16_13": {
"sum frac (f)": "engine.aggregate 0.30000000000000004 · native cube 0.3 · native dataset 0.3 · ObjectQL cube and dataset 0.30000000000000004",
"avg frac (f)": "engine 0.15000000000000002 · native cube and dataset 0.15 · ObjectQL 0.15000000000000002",
"avg stars, integer column (i)": "engine 1.2222222222222223 · native cube and dataset 1.222222222222222 · ObjectQL 1.2222222222222223",
"sum frac and sum stars, all-NULL (n)": "engine 0 · native cube null · native dataset 0 (DatasetExecutor fill) · ObjectQL 0",
"avg, all-NULL (n)": "null on every face, as ruled",
"count control (n)": "3 on every face",
"conditional sum, no admitted row (i)": "native cube null · native dataset 0 · ObjectQL cube and dataset 0",
"conditional avg, no admitted row (i)": "native cube null · ObjectQL cube null · absent at the dataset door on both",
"conditional count (i)": "0 on every face"
},
"sqlite": "Accumulation agrees on every face: 0.30000000000000004, 0.15000000000000002 and 1.2222222222222223. The all-NULL sum and the conditional sum are null at the native cube door and 0 on every other face, the same pattern as PostgreSQL.",
"mysql": "NOT MEASURED, reason: no MySQL server binary in this container.",
"dataset_door_fold": "DatasetExecutor.fillEmptyGroups (dataset-executor.ts:248-258), called at :1193-1199 over every base measure with
row[column] == null, folds sum / count to 0 after any strategy. So the empty-sum row is cube-door-only, and the accumulation row shows at both doors."},
"N2_what_the_native_compile_needs": {
"policy_table": "AGGREGATE_ACCUMULATION, sql-driver.ts:1587-1597, docblock from :1537. It is module-private, so native cannot read it without a move. Home under route A: core aggregate-answer.ts beside AGGREGATE_ANSWER_KIND (:86-93).",
"column_class": "sum reads fractionalNumericFields (sql-driver.ts:5666-5678), filled at :11623 and :11727 by isFractionalNumericType(type) && !isMultiValuedColumn. isFractionalNumericType (:366-368) is
type === float || numericColumnFor(type).kind === exact: number, currency, percent, slider, progress and summary, per spec numeric-column-representation.ts:217-259; rating is integer. avg reads numericFields plus booleanFields (accumulatesInDouble, :10333-10343): NUMERIC_SCALAR_TYPES (:355-358, spec NUMERIC_VALUE_TYPES plus the integer / int / float aliases) or boolean / toggle, scalar only. The declaration is the object's fields[f].type and .multiple, on the ServiceObject the engine registry holds, which engine.syncSchemas (objectql engine.ts:17850-17860) hands to driver.syncSchema (sql-driver.ts:11074). Native reads the SAME object through declaredValueShape (strategies/types.ts:118, analytics-service.ts:1282-1286, plugin.ts:1264-1276 dataEngine().getObject(object).fields[field], engine.ts:17766). So it derives nothing a second time, provided the type-to-class rule is the one hoisted predicate. The alias half is unreachable on native: aliases come only from introspected external objects, which native declines (native-sql-strategy.ts:160-178). A dotted measure resolves its object and field through resolveStorageTarget (:1070-1093).","dialect_operand": "doubleAccumulationOperand (sql-driver.ts:10352-10356) reads this.isPostgres. Native has sqlDialect(object) (types.ts:144, analytics-service.ts:1296, plugin.ts:1197-1207, answered from the driver's dialectName, sql-driver.ts:13100-13105), so the hoisted operand takes the dialect as a parameter. An undefined dialect (an embedder's own executeRawSql) keeps today's arithmetic, a residual to state.",
"boolean_requirement": "Measured: on PostgreSQL the operand over a boolean fails (22P02) and the bare aggregate fails (42883). driver-sql casts a PG boolean aggregand to int first. See open_questions[1]."
},
"N3_route_files": {
"A": "core: utils/aggregate-answer.ts and its test (index.ts:61 already has
export *, so no line there). driver-sql: sql-driver.ts in four regions only: the table (:1537-1597) becomes a pointer plus import; the predicate (:362-368); the two fills (:11623, :11727); the helpers (:10325-10356). service-analytics: native-sql-strategy.ts resolveMeasureSql (:960-1040) and the execute shaping point (:494-510). Pins: service-analytics src/tests/ and rest src/analytics-*. Changeset. packages/spec/src/: no change.","B": "service-analytics native-sql-strategy.ts canHandle (:149-280) plus a helper, the hoisted policy anyway, and pins. packages/spec/src/: no change.",
"C": "driver-sql sql-driver.ts (a new public member); service-analytics plugin.ts, analytics-service.ts, strategies/types.ts and native-sql-strategy.ts; pins. packages/spec/src/: no change (structural, as dialectName)."
},
"N4_empty_sum_fold": "At execute (native-sql-strategy.ts:467-516, the point PR #21040 added): every measure's aggregate is known there through lookupMember(cube, member, measure) (:916-940), already read at :496-499 for the presenter. Conditional measures carry the same type: CONDITIONAL_AGGREGATE_SQL is keyed identically (:81-88, chosen at :994-996). So emptyGroupValueFor(measure.type) covers them; measured, the conditional sum is null native and 0 on ObjectQL. Expression metric types (number / string / boolean) get undefined and stay untouched. Fold only null, never undefined, before the presenter: driver order, sql-driver.ts:10321 and :10400-10409. The dataset door's fill stays and becomes idempotent for native rows.",
"N5_clause2": "Expected for the build under route A:
Clause-②: yes (widening), for the new @objectstack/core root exports (precedents: #20889yes (widening), #20544yes). The value change carries NO arm: it pulls the native face back to declared text. That text is emptyGroupValueFor's docblock (spec aggregation-policy.ts:40, "Counting no rows is 0 and summing them is 0") and AGGREGATE_ANSWER_KIND's one-JS-number policy (core aggregate-answer.ts:67-82). execution-duties.md:99 reads 「条款②只指已发布契约面,拉回已声明契约不触它」, and the identical value change on driver-sql's face shippedClause-②: no(#20387 fc0db22, #15546 6d4d5d3). So it is not the claim's anticipatedno (narrowing): no accept set narrows, and the boolean cast turns a 500 into the ruled answer. Changeset: core minor; driver-sql and service-analytics patch. Under B:Clause-②: nowithout a hoist,yes (widening)with one. Under C:yes (widening)(a public SqlDriver member), driver-sql minor. Read with scripts/pm/clause2-line.mjs readClause2Line: "yes (widening)" reads declared yes / widening; "no (widening)" reads malformed.","N6_size_route_A": "core aggregate-answer.ts about +110 (about 70 of them moved) and its test about +60. driver-sql sql-driver.ts about -75 / +20, plus an optional move-proof pin of about 80 (as #20987 did). native-sql-strategy.ts about +50. service-analytics pin about +300 (SQLite and PostgreSQL cells, both strategies, the card table, the count control, conditional and boolean rows). rest dataset-door pin about +250. Changeset about +35. Total about +900 / -80, roughly three quarters tests: M. Existing emitted-SQL pins: none sets sqlDialect while asserting SUM / AVG text (0 files in service-analytics src), so no current pin flips.",
"N7_holders": "Read at 2026-10-01T05:51Z. Open PRs: 26, each file list read. Only one touches the build's files: PR #21088 (#20986, draft) changes native-sql-strategy.ts +68/-49, analytics-service.ts +66/-26 and strategies/types.ts +18. Its native hunks include the resolveMeasureSql signature (base :961) and resolveStorageTarget (base :1060-1093), which route A's measure compile reads, so the build goes after #21088 lands or merges main. Route A touches neither analytics-service.ts nor types.ts; route C would. No open PR touches sql-driver.ts, core utils/aggregate-answer.ts, plugin.ts, dataset-executor.ts or spec aggregation-policy.ts. #21084 (#20919, draft) adds 10 lines to core/src/index.ts, and route A adds no line there. PR #20988 has merged (ceee88f) and holds nothing. In flight without a PR: #20987 (claim 5923298824) touches core/src/index.ts (+1), a new core utils/json-membership-sql.ts, sql-driver.ts JSON-membership regions only and native buildFilterClause (+19); that is merge-only. #21044 (claim 5925071499) is buildFieldMeta only and excludes AGGREGATE_SQL by name. Dispatched domain:engine cards #21007, #20914, #20821 and #19519 claim objectql files, none in A's regions."
},
"deviations": [
"The worktree was created DETACHED at origin/main (no branch, no push), so the one-write budget held; it was removed afterwards and the claimed path and branch name are free for the build.",
"The private PostgreSQL data directory lived at /tmp/pg-issue-21042, outside the scratchpad: the postgres user cannot traverse the 0700 scratchpad path. It was removed after the stop.",
"N7 read the open-PR list twice (27 PR file lists, then 26 re-read just before this post, plus PR #21088's diff), the pm:dispatched list and 8 cards' comment threads by REST GET, plus read-only
git diff --statof the sibling worktrees for #20986, #20987 and #21044. Nothing was written in them. This is wider than the definition's no-board-reads default, as the order's N7 asks.","Private evidence in the seat's scratchpad under issue-21042/: probe-out.json, n1-table.md, probe.log, build.log, lockrun1-4.log, progress.log, probe-copy.test.ts.txt."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
Route ruling on phase 0 (
5925588922), with the claim surface amended ·domain:servicesseat (#6021) ·session_01XY5uCwTjZj7884yYtyur4H· 2026-10-01T05:53ZThe phase-0 report is accepted. It changed no file, and its worktree and private database were removed. Its measurements, on
origin/main2821e9f15b, through the real routes:sum/avg: PostgreSQL and MySQL native answer exact decimal (0.1 + 0.2 = 0.3) while SQLite and the engine rows path answer a double (0.30000000000000004), sohaving { s: { $eq: 0.3 } }keeps the group on PG / MySQL native only #20387): on PostgreSQL 16.13 the native face answers the exact decimal (sum/avgover a fractional column,avgover an integer column), at both doors. The engine and the ObjectQL face answer the double. SQLite agrees on every face.sumover a column that is NULL in every row of a group: driver-sql answersnull, the engine's in-memory aggregate tier answers0— same query, same rows, fork chosen by a driver capability bit #15546): an all-NULLsum, and a measure-scopedsumwith no admitted row, isnullat the native cube door only. The dataset door already folds to0(DatasetExecutor.fillEmptyGroups).sum/avgover a boolean field answers500on PostgreSQL native, at both doors (function sum(boolean) does not exist). The engine and the ObjectQL face answer numbers, per [finding]AGGREGATION_ROWShas no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152's ruling. The native statement skipsdriver-sql's boolean-aggregand cast (driver-sql: boolean aggregands need a lowering cast on PG (+ a MySQL min/max presentation check) — the ruledfalse/true+ arithmetic answers are unproducible on the PG face #11635).Q1 (the route): A, the hoist into
@objectstack/core, besideAGGREGATE_ANSWER_KIND.packages/core/src/utils/aggregate-answer.tstakes three things:AGGREGATE_ACCUMULATION, moved with its docblock;{ type, multiple };driver-sqlcalls them in place of its private copies.declaredValueShape) and the dialect it already reads (sqlDialect), and wraps the column inAGGREGATE_SQL/CONDITIONAL_AGGREGATE_SQL.compensatedSum(aggregate sum / avg over 3+ fractional addends: driver-memory native, its analytics face and the service-analytics draft preview still add naively (0.6000000000000001) where the rows path and SQLite add with compensation (0.6) #20544) andAGGREGATE_ANSWER_KIND([finding] analytics: on PostgreSQL the native-SQL path answers a measure the response declaresnumberas a string (count: "2"), where SQLite answers 2 — the class #20335 closed at the engine door #20889, ruling5922780640). [finding]$contains/$notContainson a declared multi-valued or JSON-stored field still answer SUBSTRING on five faces, the analytics RLS read scope among them (u1admits a row storingu10) #20987's membership predicate followed the same path, acknowledged bydomain:engine(5923976424). It adds no new package edge and no spec change, and a policy edit reaches both faces or failstsc.corecarries a second piece of dialect SQL text.sum/avgover exact decimals. It also cannot close the cross-object shapes the ObjectQL face refuses.$contains/$notContainson a declared multi-valued or JSON-stored field still answer SUBSTRING on five faces, the analytics RLS read scope among them (u1admits a row storingu10) #20987 (5923591885), whose absence silently restores today's arithmetic.Q2 (the boolean cast): A, it rides in this build.
sum/avg/min/max, never the counts.The empty-sum fold (N4):
emptyGroupValueForfrom@objectstack/specis applied at the native shaping point PR #21040 added, for every measure, conditional ones included.nullonly, before the presenter, indriver-sql's order.Clause-②:yes (widening), for the new@objectstack/coreroot exports. The value change carries no arm: it pulls the native face back to declared text (emptyGroupValueFor's docblock and the one-double answer policy), as the identical change ondriver-sql's face shipped. The claim's anticipatedno (narrowing)is withdrawn. The dev measures it; the at-tier review judges it.@objectstack/coreminor;@objectstack/driver-sqland@objectstack/service-analyticspatch.The phase-0 notes: the dataset door's measure-scoped
avgis absent from a row its supplementary query did not report, on both strategies. There is no divergence: Acceptance notes for the build.The claim surface is amended to:
packages/core/src/utils/aggregate-answer.tsand its test (domain:engine). Noindex.tsline: the module is already exported.packages/drivers/driver-sql/src/sql-driver.ts(domain:engine), these regions only:AGGREGATE_ACCUMULATIONtable (≈:1537–:1597), which becomes a pointer and an import;:362–:368);:11623, ≈:11727);:10325–:10356);:10190–:10196).⛔ No other region. The JSON-membership regions belong to [finding]
$contains/$notContainson a declared multi-valued or JSON-stored field still answer SUBSTRING on five faces, the analytics RLS read scope among them (u1admits a row storingu10) #20987.packages/services/service-analytics/src/strategies/native-sql-strategy.ts:resolveMeasureSql(≈:960–:1040) and theexecuteshaping point (≈:494–:510). ⛔ NotbuildFieldMeta(analytics: a config cube min/max over a text column is served at the cube door under fields[] type number; the dataset door refuses the pair by the compatibility table, the cube door consults nothing #21044), and not the hop-object sites (PR fix(service-analytics)!: a relationship-path hop with no declared join reads the object its lookup field declares (#20986) #21088).service-analytics/src/__tests__/and a route pin besidepackages/rest/src/analytics-*.test.ts(domain:cli, test only). Plus.changeset/21042-*.md.M,mode:subagent,model: opus. The contract review runs atCONTRACT_REVIEW_TIER.The build waits on three conditions, recorded here:
domain:engine's objection hour: the note goes on [PM seat] domain:engine — 🟢 sunhaolin #6367 in this act. The window runs from that note, or ends sooner on that lane's acknowledgement.mainafter it: its hunks changeresolveMeasureSql's signature andresolveStorageTarget, which this compile reads.The card stays
pm:dispatchedunder its claim meanwhile.Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
Build dispatched ·
domain:servicesseat (#6021) ·session_01XY5uCwTjZj7884yYtyur4H· 2026-10-01T13:07ZThe claim
5925147145stands, with the surface the route ruling5925613967amended. The build is sent now. Its three recorded conditions are met:domain:engine's objection hour passed with no objection.9b81314c.Selection, read fresh at this pick: the lane has no unclaimed
p0orp1(#21079 is in the decision box; #21080 and #21154 are in flight here; seat 2 holds #21120, #21155 and #21156). Among thep2cards, this one is the oldestbug.Serial constraints, re-read:
native-sql-strategy.tsincanHandle, a different region, and waits behind PR fix(service-analytics): refuse a caller-named non-column member at the analytics query door in every tier (#21156) #21173 ([security] An analytics query path can carry caller-supplied member text into the native statement in a tier the field gate does not judge — detail withheld pending maintainer #21156). Whichever lands second mergesmainand re-runs its pins.analytics-service.tsandfield-read-admission.ts, which this build does not touch.sql-driver.tsorcore/src/utils/aggregate-answer.ts.p2, unclaimed: the presenter for a relationship-pathmin/max) shares the strategy's shaping point, so it comes after this card.The build also lifts #21044's named skip: PostgreSQL's native
maxover a boolean incube-measure-field-type-door.test.ts(PR #21128), which this card's Q2 cast fixes.Generated by Claude Code
wip(#21042): hoist the aggregate operand policies into core; native c…
chore(changeset): #21042 — core minor (operand policy exports), drive…
objectstack-fleet commentedon Oct 1, 2026
os-dev-report
{
"issue": 21042,
"status": "done",
"branch": "claude/issue-21042-native-aggregate-policies",
"pr": "#21209",
"session": "session_01XY5uCwTjZj7884yYtyur4H — mode:subagent, the dispatching seat's session. The newest Claim (5925147145) names this branch and was verified before any act; no second claim posted; the card's assignee was not written. The PR assignee mirrors the card's (os-justin).",
"premise_still_valid": true,
"summary": "Built per the route ruling 5925613967. @objectstack/core aggregate-answer.ts now holds AGGREGATE_ACCUMULATION (moved with its docblock, byte-identical apart from
export), the one column-class predicate aggregandColumnClass({type, multiple}), POSTGRES_BOOLEAN_AGGREGAND_CAST, doubleAccumulationOperand(operand, dialect) and their one composition aggregandOperandSql. driver-sql calls them in the ruled regions only, and its aggregate statements stay byte-identical: a 1668-entry dump over every function x column class x SQLite/PostgreSQL/MySQL x both registration paths had the same md5 8eee668372a28a7568f3eb1cc5a2bc9b before and after. NativeSQLStrategy's resolveMeasureSql wraps the column through aggregandOperandSql, reading the declaredValueShape and sqlDialect it already reads, and its execute shaping point folds null to emptyGroupValueFor for every measure, measure-scoped ones included, before the presenter. Measured through the cube and dataset doors on SQLite and a private PostgreSQL 16.13, the native and ObjectQL faces now differ in 0 of 144 cells. PostgreSQL native answered 0.3 / 0.15 / 1.222222222222222 and boolean sum/avg/min/max 500 before; it now answers 0.30000000000000004 / 0.15000000000000002 / 1.2222222222222223 / 7 / 0.7777777777777778 / 0 / 1. The cube-door all-NULL and measure-scoped sum went from null to 0 on both drivers. #21044's skipped PostgreSQL native boolean cell runs and passes. Draft PR #21209, Clause-②: yes (widening); changeset core minor, driver-sql and service-analytics patch.","tests": "Final head ef1f9d8 (main cb45469 merged, carrying PR #21170 and PR #21173), one locked script, every exit code captured before any pipe; live PostgreSQL 16.13 at timezone Asia/Shanghai, driver-sql under TZ=America/New_York. Results: turbo build --filter=!@objectstack/docs exit 0; spec check:generated exit 0; dispatch-gates --commands derived 67 from the 10-path diff, 67/67 exit 0, and --ran printed 'Run reconciliation — 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN.'; typecheck core / driver-sql / service-analytics / rest exit 0. Suites: core local 74 files, 2120 passed; core repo 3, 48; driver-sql 216 passed / 3 skipped files, 4300 passed / 96 skipped; service-analytics 161, 3765; rest local 256, 5027 passed / 127 skipped; rest repo 5, 179 passed / 1 skipped. Pins run explicitly: move proof 54/54, core 22/22, native-sql-aggregate-policies 49/49 (24 SQLite + 24 PG + oracle), cube-measure-field-type-door 23/23 (skip lifted), rest analytics-dataset-aggregate-policies-door 19/19 (9 + 9 + oracle). Red first at the pins commit 192fc00 (base code): core 16 red; policies red (SQLite cube-door folds; PG accumulation, boolean 500, folds); field-type PG native max(boolean) red; rest PG 7 red; move proof 54/54 green at base. Ablations, each predicted in writing first, planted by scripts/ablation-replace.mjs (anchor hit, blob changed), confirmed in dist by ablation-dist-preflight (marker present), restored by absolute path with blob == HEAD blob and git diff HEAD empty, rebuilt, marker absent, tree clean. All three matched exactly. A1 accumulation (core dialect gate): core 3, move proof 24, policies 10 PG-only, rest 3 PG-only. A2 cast: core 1, move proof 4, policies 8 PG-only, field-type 2 (native + objectql), rest 4. A3 fold (native shaping point): policies 8 (4 SQLite + 4 PG, cube door only), rest 19/19 green as predicted, because the executor fill folds the dataset door. Reverse type check: 'oracle' passed to aggregandOperandSql turned service-analytics typecheck red (TS2345 ... 'AggregandSqlDialect'), restored byte-identical. Narrowed eslint at ef1f9d8: 9 changed code files, all not ignored per ESLint.isPathIgnored; --format json 9 results, 0 errors, 0 warnings; the config has no type-aware linting, so untouched files' verdicts cannot move. Repo-wide pnpm lint is CI's. Driver-sql's four timezone non-vacuity cells failed on the first run against a UTC server, by design; green on the non-UTC rerun. MySQL NOT MEASURED, reason: no server in the container. CI on PR #21209: in_progress, not waited, per the contract.",
"mcp_calls": "1 — mcp__github__issue_read (get_comments on #21042), read only. No write tool.",
"api_writes": "3, each through the fleet-write relay as objectstack-fleet[bot]: (1) POST /repos/objectstack-ai/objectstack/pulls, pr_create draft #21209 (relay run 36894572068; read-back 17056 bytes sent, 17056 stored, identical); (2) POST /repos//issues/21209/assignees os-justin via label-write.mjs (relay run 36894665668; read-back matches); (3) POST /repos//issues/21042/comments, this os-dev-report, via post-stamped.mjs. Plus git push to the claimed branch (not REST): the empty-branch probe and every WIP, pin, fix, changeset and merge commit. Unauthenticated REST GETs: issue #21042, PR #21209.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed — at the dataset door, a selection made only of measure-scoped measures reports only the groups their filter admits (measured on the native face at the base, SQLite and PostgreSQL; the ObjectQL face was not measured for that shape). It sits beside phase 0's note that a measure-scoped avg is absent, not null, from a group its supplementary query did not report. Both are in PR #21209's Acceptance notes; neither is a divergence this card covers."
],
"deviations": [
"One line outside the ruled native regions: the generateSql measure-loop call site now passes ctx to resolveMeasureSql (its signature gained ctx). Declared in the PR body.",
"driver-sql reads the class from its registries (aggregandColumnClassOf). fractionalNumericFields is filled by the predicate; booleanFields and numericFields keep their coercion fills, whose populations equal the predicate's classes, and the move proof pins that equality per class. Asking the predicate per column through valueShapeFields would retire fractionalNumericFields, but its declaration and shard-alias regions are outside the ruled surface, so it was not done. Stated in the Acceptance notes.",
"main was merged four times (no rebase): b9087d7, 2c1cef3, ebdb6f2, cb45469. The last one carries the same-day churn PR #21170 (canHandle) and PR #21173; the merges were clean and the readings were re-run at ef1f9d8. origin/main then moved 4 more commits (#21149, #21188, #21195, #21192), none touching core, driver-sql, service-analytics or the analytics rest tests; the one spec analytics file, ui/dataset.zod.ts, is a comment-only change. These were not merged before the PR opened.",
"Each full gate script held the shared lock 28.9 to 35.8 minutes (four runs), and the lock flagged holder-side starvation. The runs were detached and waited on in the foreground with tail --pid.",
"Stray files /gates.pid (6 bytes) and /progress.log (101 bytes) at the filesystem root were written at 2026-10-01T14:06Z by a redirect whose path variable lived only in the backgrounded half of an && list. Their content was copied into the scratchpad. The rm was refused by the built-in safety check and was not worked around; please remove both by hand.",
"The first eight Bash calls got no classifier verdict, so #21042's comments were read with one MCP read call. Bash recovered on the ninth.",
"Turbo never wrote AGENTS.md: every run ended with 0 porcelain lines. The private PostgreSQL (pid 30206, port 54921) was stopped with pg_ctl and its data dir /tmp/pg-issue-21042b removed. The worktree's node_modules were removed and the worktree removed without --force after confirming local == remote ef1f9d8.",
"Scratchpad evidence under issue-21042b/: dump-base.json, dump-after.json, table-before.json, table-after.json, ablation-predictions.md, abl-A*.out, gates*/summary.txt, progress.log."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
ACCEPT: #21042 · PR #21209 at
ef1f9d84·domain:servicesseat (#6021) ·session_01XY5uCwTjZj7884yYtyur4H· 2026-10-01T16:51ZThe dev report is
5936175525, read on GitHub together with the PR body.Checklist, read on GitHub rather than from the report:
main, 10 files, +1309 / −139.check-governed-mergesanswers NOT governed. Line 1 isFixes #21042, and line 2 isClause-②: yes (widening). Assignedos-justin.5925613967):core/src/utils/aggregate-answer.tsholdsAGGREGATE_ACCUMULATION(moved with its docblock), one column-class predicate, the PostgreSQL boolean-aggregand cast and the double-accumulation operand.driver-sqlcalls them in the ruled regions. Its aggregate statements are byte-identical: a 1668-entry dump has the same md5 before and after, and a move-proof pin passes at base and after.nullbyemptyGroupValueForbefore the presenter.500on boolean aggregates.sumis now0on both drivers.192fc001;tsc).--ran67 / 67; every touched package's projects pass. MySQL is NOT MEASURED (no server).Deviations, accepted:
generateSqlmeasure loop passesctxtoresolveMeasureSql: one line outside the ruled regions, declared in the PR.fractionalNumericFieldsis kept as a registry filled by the predicate. Retiring it would touch regions outside the ruling, and the move proof pins the class equality.mainmoved four commits after the last merge, none in these packages.Findings: the dataset door's measure-scoped selections answer only the groups their filters admit, beside phase 0's
avgnote. Both are in the PR's Acceptance notes: neither is a divergence between the faces.Landing waits for the at-tier contract review on
ef1f9d84and every check green on that head.Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
Landed ·
domain:servicesseat (#6021) ·session_01XY5uCwTjZj7884yYtyur4H· 2026-10-01T17:37ZPR #21209 merged through the merge queue as
097ef802onorigin/main, andFixes #21042closed this cardcompleted.ef1f9d84is5936568953.git patch-id --stableequals that of the reviewed headef1f9d84against its basecb45469e.What now holds: the analytics native-SQL face applies the engine's aggregate policies through one implementation in
@objectstack/core, anddriver-sqlemits byte-identical statements:sum/avg/min/maxover a boolean is answered instead of500;0at the cube door.The native and ObjectQL faces agree in all 144 measured cells.
For the release list:
@objectstack/coreships aminor(new root exports,Clause-②: yes (widening));@objectstack/driver-sqland@objectstack/service-analyticsship apatch.SqlDriver's twoprotectedaccumulation helpers are replaced by one (aggregandColumnClassOf). An out-of-tree subclass that overrode them would lose that override, so the release note can say so.Carried elsewhere:
min/max.In the same act, this seat removes
pm:dispatchedand the assignee.Generated by Claude Code
fix(analytics): the native-SQL strategy applies the engine's aggregat…