Repository navigation
fix(service-analytics): a count that names an amount field is no amount, in the currency guard and the result column - #28360
Conversation
The compile-time currency guard resolved a measure's currency from its field without reading its aggregate, so a derived measure over an amount and a count over an amount field (or a count declaring a currency) was refused as a cross-currency combination. Resolve a currency only for an amount aggregate, using the shared AMOUNT_AGGREGATES set the cube door and the record-currency column already read. The result column had the same blind spot in its fixed / tenant arm: a count over a currency field carried that field's currency. Gate it on the same set, so a count column is a plain number. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyimd4GfDwsijRiftT
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a160c926398cb1722088cb01066dbe677fdf31be && git checkout a160c926398cb1722088cb01066dbe677fdf31be
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b3ec89f31bddefcf06825a8daa7a38510d4294a0 85afc7ba29d32e366451b6214f3a147dc00bf809 && git checkout -B drift-repro b3ec89f31bddefcf06825a8daa7a38510d4294a0 && git merge --no-ff 85afc7ba29d32e366451b6214f3a147dc00bf809
node scripts/docs-audit/affected-docs.mjs --json b3ec89f31bddefcf06825a8daa7a38510d4294a0 |
|
domain:ui#3 · os-tesla · session_01L9qibz8HEj53Wx2uphTo2o Contract reviewServed-tier: Face: declaration only ( ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
domain:ui#3 · os-tesla · session_01L9qibz8HEj53Wx2uphTo2o 维护者速读
席位意见:两处都直接复用已有的 席位核对ACCEPT · seat
|
Requested by Tesla · project thread
Fixes #28194
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
count」不再被当成金额:编译期币种检查不再拿它和别的金额比币种,所以「美元合计 ÷ 有金额的发票数」这类比率能保存了;同时这类计数的结果列也不再带币种,看板上「有金额的发票数」磁贴显示3而不是€3。count数的是行,不是钱;ADR-0144 开放项 12 的裁决原文是「count操作数不参与比较」,但代码只在count不带field时做到。结果列的同一盲区由席位裁定并入本 PR。fixed币种相加、按记录币种的金额与固定币种相比仍被拒;sum/avg/min/max 的列照旧带币种。回滚即撤 PR。packages/services/service-analytics/src/measure-currency-coherence.ts的resolvedOf非派生分支;②analytics-service.ts里enrichResultColumns的monetary一行;③record-currency-analytics.test.ts新增的两组用例。pnpm --filter @objectstack/service-analytics exec vitest run src/__tests__/record-currency-analytics.test.ts,预期 60 个用例全绿(其中 8 个是新增)。What changes
measureCurrencyResolver).resolvedOf's non-derived branch resolved a currency from the measure's field without reading its aggregate, so acountthat names an amount field ({ aggregate: 'count', field: 'amount' }) resolved to that amount's currency, and guard 2 compared it.ratio(usd_total, priced_invoices)was refusedDATASET_INVALIDas a cross-currency combination. The branch now resolves a currency only for an amount aggregate, through the oneAMOUNT_AGGREGATESset (cube-record-currency-door.ts, already exported there and read by the cube door and the record-currency column; nothing is copied or moved).monetaryOperandsanother way. Measured onmain69c39b16: yes, acountdeclaring its owncurrency(no field) went through the same branch'sm.currencyarm, soratio(total, { count, currency: USD })was refused as record vs USD. The gate covers the whole branch, so that entry closes too (seat ruling), and it is pinned.currencythat contradicts its field'sfixedcode is still refused (pinned as a control).enrichResultColumns, seat ruling: same defect class, folded in). Its fixed/tenant arm counted any measure over acurrency-type field as monetary, so acountover afixedEUR amount carriedcurrency: "EUR", and over a tenant-currency amount carried the tenant's code. What users see change: a count tile or column over an amount field stops printing as money (3, not€3or¥3). The record arm already gated onAMOUNT_AGGREGATES; this arm now does too. Asum/avg/min/maxover the same field keeps its code, and a measure that declares its owncurrencykeeps it as before.Tests
In
src/__tests__/record-currency-analytics.test.ts(realObjectQLengine over SQLite, both strategies plus the draft preview):a count over an amount field carries no currency: a count over afixedEUR amount and over a tenant-currency amount carry nocurrency; CONTROL: asumover each still carriesEUR/CNY(native, objectql, draft preview).accepted: a derived ratio | sum | difference of an amount and a count over an amount field, or a count declaring a currency(the card'sratio(usd, priced_invoices)among them).fixedcodes summed and a record amount against a fixed one stay refusedDATASET_INVALID/ 400.currencycontradicting itsfixedfield is still refused (guard 1).Acceptance notes
srcpaths (../plugin.js), so the ablations needed nodistbuild.currencyon acountstill labels that count's column (unchanged, not judged here); it is only no longer an operand guard 2 compares.sumof an amount and a count now resolves to the amount's currency alone, as acountwithout a field already did before this change.Verification
Red on the unfixed code (85afc7b's tests over the 69c39b1 source)
Green and ablations at 85afc7b
record-currency-analytics.test.ts: Tests 60 passed (60).node scripts/ablation-replace.mjs, resolver gate made always-true, anchor 1 → 0, blob 985caf62 → e563f99c): the threeaccepted: a derived …tests red, 3 failed | 57 passed; restored, blob == HEAD,git diff HEADempty.a count over an amount field …tests red, 3 failed | 57 passed; restored, blob == HEAD,git diff HEADempty.pnpm --filter @objectstack/service-analytics test: Test Files 200 passed (200), Tests 4814 passed | 282 skipped;typecheck(tsc --noEmit) exit 0 (underos-verify-lock, VERDICT command-exit 0).Derived gates at 85afc7b: 62 derived, 61 exit 0, 1 NOT MEASURED
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon this tree (4 paths vs merge base 69c39b1), every line run with stdin closed, exit codes recorded before any pipe;--ranreconciliation: "62 derived famil(ies) accounted for — 61 run, 1 NOT-MEASURED".pnpm check:dual-build-cjs-loadsNOT MEASURED: exit 3, PREREQUISITE NOT MET (it reads every package's builtdist/, and this worktree built only the service-analytics closure). Declared narrowing; CI's Lint & Repo Gates runs it on a full build. The diff changes no export or entry point.node scripts/check-plugin-teardown-shape.mjs --self-testfirst exited 3 on the shallow clone (its pinned fixture commit was absent); after fetching that one commit it exits 0 (48 cases).Generated by Claude Code