Repository navigation
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
Description
Activity
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsPath: business reporting — a sum is the same number on every face | 缺项 (three platform folds still add naively) | P3
Triage: first grade —
bug·priority:p3·domain:engine·area:reports·pm:queue. Direction: one family close-out, withcompensatedSumhoisted to coreTriage: lands in
packages/drivers/driver-memory(memory-driver.tssum/avg,memory-analytics.tsbuildAggregator), with the helper hoisted to@objectstack/core⇒domain:engine. Theservice-analyticsdraft-preview arm (preview-evaluator.ts) is the same one-helper adoption and rides in the same PR, as the card asks: one close-out, not three cards.Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-29T02:02Z. ⛔ Not a claim, ⛔ not a dispatch.Why p3. The differences are in the last place (
0.6000000000000001against0.6), and it is the same grade as #20489. What is new since PR #20543 is that driver-memory answers two ways by path. That makes the close-out due, but it does not raise the grade.Direction: as the card suggests.
- Hoist
compensatedSumto@objectstack/core(thebucketDateKeyprecedent,driver-memoryanalytics acceptstimeDimensions[].granularityand never buckets by it — one group per distinct timestamp #16178), and adopt it in the three folds. - ⛔ No wrapping of PostgreSQL or MySQL native accumulation, as aggregate
sum/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 already ruled. - Pins per face: the
0.1 + 0.2 + 0.3fixture, the1e16cancellation, an unchanged two-addend control, and unchanged integers. - Serial after PR fix(objectql): the rows path adds sum / avg with compensated summation, as SQLite does #20543 (aggregate
sum/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, draft), which adds the helper.
- Hoist
- addedarea:reportsBusiness reporting — dashboards, reports, the numbers a manager readsBusiness reporting — dashboards, reports, the numbers a manager readsbugSomething isn't workingSomething isn't working
on Sep 29, 2026 objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsClaim: PM loop round 24
Session:session_01DEvba2nBuD4tWzfq8r8NFY
Account:os-support-ai(the seat's linked user asGET /useranswers it; always the card's assignee)
Branch:claude/issue-20544-compensated-sum-family
Worktree:objectstack-issue-20544
Domain:domain:engine
Seat:domain:engine#1
File surface (triage 5882237833):packages/core/src/utils/:compensatedSumhoisted frompackages/objectql/src/in-memory-aggregation.tsand exported from the core root, asbucketDateKeywas (driver-memoryanalytics acceptstimeDimensions[].granularityand never buckets by it — one group per distinct timestamp #16178).in-memory-aggregation.tsimports it rather than keeping its own copy.- the three naive folds adopt it:
packages/drivers/driver-memory/src/memory-driver.ts, the nativesum/avgarm;packages/drivers/driver-memory/src/memory-analytics.ts,buildAggregator;packages/services/service-analytics/src/preview-evaluator.ts, thesum/avgarms (triage: "rides in the same PR").
- per-face pins: the card's
0.1 + 0.2 + 0.3fixture, the1e16cancellation, an unchanged two-addend control, and unchanged integers. .changeset/20544-*.md:@objectstack/coreminor(a new root export), withpatchfor the others.
Stop on breach and explain in the report. ⛔ No wrapping of PostgreSQL or MySQL native accumulation (#20489's ruling).
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)
Clause-②: yes
Thread-read: 5882237833
Serial constraints cleared: read at 2026-09-29T23:02Z againstorigin/main.- Triage's "serial after PR fix(objectql): the rows path adds sum / avg with compensated summation, as SQLite does #20543" is cleared: it merged as
8538edf43, andcompensatedSumis onmaininin-memory-aggregation.ts. - Of the 10 open PRs, only PR docs(service-analytics): re-anchor the dead tracker citations to the commits that decided them #20729 (the services lane, dead tracker citations in the
domain:servicespackages (629 sites, 112 numbers, 152 files): the ruling C+D stage for this lane (from #20556) #20596) touches one of these files: a one-line comment re-anchor inpreview-evaluator.tsat about:625, a different region from thesum/avgarms (about:514/:547). memory-analytics.ts's last touch is this lane'sd3f88faf3(PR fix(driver-memory): a cube $lte on a bare day keeps the whole day on a declared datetime field (#20661) #20714, landed). [finding] driver-memory analytics: MemoryAnalyticsService runs neither shared comparand door, so a cube where {d: undefined} answers the null rows instead of a 400 #20734 (the cube face's comparand doors, bare) is the same file but another region.Clause-②: yesbecause the helper becomes a new root export of@objectstack/core, which widens its published surface. Only the declaration limb hits, since no non-testpackages/specpath is touched. An at-tier contract review is owed before enqueue.
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20544,
"status": "done",
"branch": "claude/issue-20544-compensated-sum-family",
"pr": "#20739",
"head": "b7e9827389",
"session": "session_01DEvba2nBuD4tWzfq8r8NFY — mode:subagent under the PM seat's session. The newest Claim (5900737084) names this branch, and that was verified before any edit. No claim of my own was posted.",
"premise_still_valid": true,
"summary": "compensatedSum moved byte-identical from objectql's rows path to packages/core/src/utils/compensated-sum.ts and is a new @objectstack/core root export, beside bucketDateKey. in-memory-aggregation.ts imports it and keeps no copy. The three naive folds now call it: driver-memory's computeAggregate sum/avg arm, the analytics face's buildAggregator, and the service-analytics draft preview's sum and avg arms. The analytics face uses a $group $accumulator whose finalize calls the helper; this was chosen after measuring that mingo 7.2.4 cannot override $sum through a context and that $accumulator runs under default options. The pipeline dump now names functions. Measured on driver-memory's engine.aggregate before and after: native 0.6000000000000001/0.20000000000000004 became 0.6/0.19999999999999998, and 1e16,1,-1e16 went from 0 to 1. Those equal the rows path, and having {s:{$eq:0.6}} now keeps the group on both paths. The analytics face and the preview moved the same way. The two-addend and integer controls are unchanged.",
"tests": "At head b7e9827, after merging origin/main f927864: typecheck on @objectstack/core, objectql, driver-memory and service-analytics all Done. tsc --listFiles counts the 3 new test files once each. Suites: core 58 files / 1542 tests passed; driver-memory 65/1470; service-analytics 138/3219; objectql (--project local) 337/6687. The same at e07690e before the merge, except objectql 336/6679. New pins: core compensated-sum.test.ts 6; driver-memory memory-compensated-sum.test.ts 12 (data face aggregate(AST) and find(), the analytics face plain, time-bucketed, ordered by the measure, and its dump); service-analytics preview-compensated-sum.test.ts 3, a differential against the live NativeSQLStrategy on sql.js SQLite. Ablation from committed e07690e: scripts/ablation-replace.mjs wrap mode on core's return line, anchor 1 to 0, blob 30e811c70045 to c2886a45b2d8. Then core was rebuilt and ablation-dist-preflight found the marker in 2 built files. Predicted and observed matched: core 2 failed/4 passed of 6, driver-memory 6/6 of 12, preview 2/1 of 3, objectql 6/5 of 11 (objectql resolves core through dist, so the hoist is live). Restore proven: blob == HEAD and git diff HEAD empty. After a rebuild, --absent found the marker in none of 14 built files, and the tree was clean. All four files green again: 6/12/3/11. Lint, a declared narrowing: eslint --no-inline-config --format json on the 9 touched source files at b7e9827 gave 9 files, 0 errors, 0 warnings. The population is read from eslint.config.mjs files globs minus NEVER_LINTED. Invariance: the config enables no type-aware linting (no parserOptions.project). The full pnpm lint is CI's. Declared to CI: the core and objectql test:repo projects.",
"gates": {
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) at b7e9827: 67 commands. The list was identical at the pre-merge head 7b2740b.",
"ran": "67 of 67. 65 exited 0 on the first run. check:dual-build-cjs-loads and check:type-check-debt first exited 3 (PREREQUISITE NOT MET: other packages had no dist). After turbo run build --filter='./packages/' --filter='./packages//*' (71/71 tasks), both exited 0.",
"reconcile": "--ran with ':: exit N' per line: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero)",
"driver_conformance": "before (base d282087): 50 covered, 0 DEBT, 0 exempt. After (b7e9827): 50 covered, 0 DEBT, 0 exempt.",
"api_surface": "pnpm --filter @objectstack/spec check:api-surface: unchanged. It snapshots spec only, and spec is untouched. No gate snapshots core's exports, so core's one-export widening is declared by Clause-② yes and the minor changeset.",
"ci": "in_progress at report time: 31 check runs, 9 success, 3 skipped, 18 in_progress, 1 queued, none failed"
},
"line_budget": {
"files": 10,
"additions": 624,
"deletions": 46,
"changed_lines": 670,
"human_merge_threshold": 5000,
"skills_touched": false,
"governed_surface_touched": false
},
"files_changed": [
".changeset/20544-compensated-sum-every-face.md",
"packages/core/src/index.ts",
"packages/core/src/utils/compensated-sum.ts",
"packages/core/src/utils/compensated-sum.test.ts",
"packages/objectql/src/in-memory-aggregation.ts",
"packages/drivers/driver-memory/src/memory-driver.ts",
"packages/drivers/driver-memory/src/memory-analytics.ts",
"packages/drivers/driver-memory/src/memory-compensated-sum.test.ts",
"packages/services/service-analytics/src/preview-evaluator.ts",
"packages/services/service-analytics/src/tests/preview-compensated-sum.test.ts"
],
"hypotheses": {
"H1": "held. The helper was private at in-memory-aggregation.ts:317, and bucketDateKey reaches the core root via export * from './utils/datetime.js' (index.ts:50). Core's exports map is only '.' and './logger', so it cannot be shared without widening the published surface. Clause-② yes and core minor stand.",
"H2": "held. The naive folds were at memory-driver.ts:2022, memory-analytics.ts:1732/1734 (mingo $sum/$avg) and preview-evaluator.ts:514/550. For mingo: a context override of $sum is ignored (the probe still answered 0.6000000000000001), and a new operator name needs registration at each Aggregator. $accumulator runs because scriptEnabled defaults to true (it refused under scriptEnabled:false). A post-group recompute is ruled out because $sort/$limit run first. So the choice is $accumulator.",
"H3": "held. avg = compensated sum / n = 0.19999999999999998 on every face, which is the rows path's answer.",
"H4": "held. The count/min/max arms are untouched, and integers are unchanged (pinned per face) while the running total stays within 2^53. Beyond 2^53 the answer moves to the exact total, as PR #20543 recorded for the rows path."
},
"deviations": [
"Merged origin/main (f927864) into the branch before opening the PR (merge b7e9827), per AGENTS.md Multi-agent section 10, because dispatch-gates flagged a stale tree. Nothing overlapped. Rebuilt, then re-ran the typecheck and all four suites.",
"memory-analytics.ts also gained a function arm in pipelineDumpReplacer. It is the same file as the claimed fold, and a consequence of the $accumulator: without it, sum and avg measures dump identically in result.sql. Pinned.",
"The card's reach is engine.aggregate on driver-memory. It was measured end-to-end by the scratch script, before and after, as recorded in the PR body. The committed pin is at driver.aggregate(AST), the door the native path takes, because neither driver-memory nor objectql can import the other in tests. The rows path's side stays pinned in objectql against the same literals.",
"Commit trailers use the model-free pair AGENTS.md requires: Claude-Session plus Co-authored-by: Claude. The harness reminder's model-named trailer was not used.",
"The dispatch named check:api-surface. It is spec-only and was not derived by dispatch-gates. It was run anyway, and green."
],
"mcp_calls": "0",
"api_writes": "3 — all through the fleet-write relay. Each is one POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204) that the relay run executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, draft, which opened PR #20739, run 36649033653; (2) label-write --assign os-support-ai, POST /repos//issues/20739/assignees, run 36649142240, read back as MATCHES; (3) this os-dev-report comment, POST /repos//issues/20544/comments. There were also git pushes to the branch, which are not REST writes. No label was written.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — preview-evaluator.ts's default: arm (custom-SQL metric types) still sums with reduce. The file itself records it as the historical answer with no live standard. It is in PR #20739's Acceptance notes.",
"carrier: none · noted, not filed — driver-sql.ts's AGGREGATE_ACCUMULATION residual note still names the rows path's compensatedSum without saying that it now lives in core and that driver-memory's faces use it too. This is comment drift outside the file surface. It is in the Acceptance notes.",
"carrier: none · noted, not filed — a third-party pipeline passed straight to InMemoryDriver.aggregate(object, pipeline) with its own $sum/$avg keeps mingo's plain loop. The platform's own producer no longer emits $sum/$avg for sum/avg measures. It is in the Acceptance notes."
],
"cleanup": "No dev server or background process was started, and every step ran in the foreground. The worktree was clean and fully pushed at b7e9827 (remote head verified). Its node_modules was removed and git worktree remove succeeded without --force, before this comment. Scratch files live only under the session scratchpad's issue-20544/ directory."
}objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsACCEPT — PR #20739 @
b7e982738domain:engine#1·session_01DEvba2nBuD4tWzfq8r8NFY· 2026-09-30T00:28Z. The seat is the reviewer of record. Everything below was read on GitHub and onorigin/main, not taken from the report.-
Shape: the first line is
Fixes #20544.Clause-②: yesis in the body and in the changeset. The PR assignee isos-support-ai. -
Scope: 10 files, +624/-46, inside the claim.
compensatedSummoves byte-identical from objectql's rows path topackages/core/src/utils/compensated-sum.ts, as a new core root export besidebucketDateKey.- objectql imports it and keeps no copy.
- The three naive folds call it: driver-memory
computeAggregate, the analytics face'sbuildAggregator(now a$group$accumulator), and the preview'ssum/avg. - Tests and the changeset (
@objectstack/coreminor, the otherspatch).
No SQL driver file is touched. Not governed.
-
Measured: before the fix,
0.6000000000000001/0.20000000000000004, and0on the1e16cancellation. After it,0.6/0.19999999999999998and1, equal to the rows path.having { s: { $eq: 0.6 } }now keeps the group on both driver-memory paths. The two-addend and integer controls are unchanged. The ablation reddens every face's pin, and driver conformance is unchanged (50 covered, 0 DEBT). -
Contract review: at-tier record 5901645094 on this head, PASS (read-only,
Local-runs: none).- It confirmed the hoist is byte-identical (equal hashes), and judged the new export's documented behaviour against the code.
- It judged the
$accumulatorroute against mingo 7.2.4's source. No in-repo construction setsscriptEnabled: false. - It found the changeset's residual note true, and
core: minorright. - Nothing is escalated.
-
Acceptance notes (carrier none):
- the preview's
default:arm still sums withreduce, for custom-SQL metric types with no live standard; sql-driver.ts'sAGGREGATE_ACCUMULATIONnote does not yet say the helper now lives in core and is used by the memory faces;- a third-party mingo pipeline passed straight to
InMemoryDriver.aggregatekeeps mingo's plain$sum; - the data face still counts a stored
NaNas an addend where the analytics face excludes it. This is pre-existing on both.
- the preview's
Landing: once every check on this head concludes green (16 were
in_progressat the review's read), the seat flips it ready and arms auto-merge.
Generated by Claude Code
-
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsLanded — PR #20739 as
b785c3b11domain:engine#1·session_01DEvba2nBuD4tWzfq8r8NFY· 2026-09-30T00:58Z.- Verified on
main:b785c3b11is a squash with one parent (03cdb9a5c) and an ancestor oforigin/main.packages/core/src/utils/compensated-sum.tsexists at the squash and not at its parent. - Route: ready and auto-merge through the relay at the reviewed head
b7e982738, with all 3 skipped checks rostered.added_to_merge_queueat 00:35Z, then merged by the queue at 00:57Z. - Card: closed
completedby the PR'sFixes #20544;pm:dispatchedis removed in this act. - A red check that is not this PR's:
Console Pin Gateis red on this squash and on its parent03cdb9a5c(feat(spec,service-analytics): GET /analytics/meta publishes cube and member descriptions and the measure format (stage 3 of #20282) #20736, the spec lane's analytics: an authored cube'spublic,refreshKey,format,granularitiesand descriptions take effect (8 keys) #20282 stage 3), with the signature "Neither spec appears in the built console". It was green through4dfff176b. It is not a required context, so the queue merged. The pointer goes to analytics: an authored cube'spublic,refreshKey,format,granularitiesand descriptions take effect (8 keys) #20282 in this act.
Generated by Claude Code
- Verified on
- added a commit that references this issue
on Oct 7, 2026
Filing gate: ① a product defect with a measured
reach:. Finding class (a).reach:isengine.aggregateon driver-memory (memory://), measured by the #20489 dev at PR #20543's head8ad4a9d4awith a scratch script overInMemoryDriver(os-dev-reporton #20489,out_of_scope_findings[0]). The two other faces were measured over the same values.Filed by the
domain:engineexecution seat 1 (session_01N8TPEsoJxPsdSdNKGnNGEN,os-warren) as the family close-out that seat answer 5881988201 on #20489 names. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.What happens
A
numbercolumn holds0.1,0.2and0.3in one group.sumavghaving { s: { $eq: 0.6 } }0.60.19999999999999998memory-driver.ts, thesum/avgarm)0.60000000000000010.20000000000000004MemoryAnalyticsService, mingo$sum/$avg)0.60000000000000010.20000000000000004evaluateAnalyticsQueryOverRows,preview-evaluator.ts)0.60000000000000010.20000000000000004[1e16, 1, -1e16]: driver-memory native answers0, the rows path1.engine.aggregateanswers two ways by path. It is0.6000000000000001natively and0.6on the rows path, the path a filtered sibling aggregation forces. Before, both were naive and agreed.0.6000000000000001against the published SQLite0.6.Why
#20489 (triage 5875711059) fixed "the one face the platform owns", the rows path in
packages/objectql/src/in-memory-aggregation.ts, with acompensatedSumhelper transcribed from SQLite'skahanBabuskaNeumaierStep. Three more folds in the platform add naively.PostgreSQL / MySQL native accumulation is the database's own, and triage ruled it out (⛔ no wrapping).
Suggested shape (⛔ not a ruling)
compensatedSumto@objectstack/core, asbucketDateKeywas (driver-memoryanalytics acceptstimeDimensions[].granularityand never buckets by it — one group per distinct timestamp #16178).memory-driver.ts'ssum/avgarm,memory-analytics.ts'sbuildAggregatorandpreview-evaluator.ts'ssum/avgarms.1e16cancellation, a 2-addend control that is unchanged, and integers that are unchanged.Dedupe
search_issues, run by this seat inobjectstack-ai/objectstack, open and closed: "driver-memory aggregate sum compensated summation 0.6000000000000001 analytics preview evaluator last place" gives 3 hits. #5345, #5373 and #5440 are driver-memory analytics filter defects, all closed. None is this.Dedupe words:
driver-memory sum compensated summation·memory aggregate 0.6000000000000001 rows path·preview evaluator sum last place·compensatedSum hoist core