Repository navigation
fix(service-analytics): refuse a derived measure currency that contradicts its operands' fixed code - #28043
Conversation
…dicts its operands' fixed code Guard 1 of measure-currency-coherence skipped derived measures for the fixed-code comparison, so a derived sum of two EUR-fixed measures that declared `currency: 'USD'` compiled and its column printed euros labelled USD. A derived measure whose operands resolve to one `fixed` code now refuses a different declared `currency` with DATASET_INVALID / 400, naming the operands' code and the fix (delete the measure's `currency`). Restating the same code stays legal. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
📓 Docs Drift Check1 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 b0b9c1fe3a2002542d3742f7cb3cc4338698146b && git checkout b0b9c1fe3a2002542d3742f7cb3cc4338698146b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bc496bf7d7c034690353352de308bed3c5a5c4c2 dc86f2a26809015e45c948c0c1086d6e2418feb3 && git checkout -B drift-repro bc496bf7d7c034690353352de308bed3c5a5c4c2 && git merge --no-ff dc86f2a26809015e45c948c0c1086d6e2418feb3
node scripts/docs-audit/affected-docs.mjs --json bc496bf7d7c034690353352de308bed3c5a5c4c2 |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head 890ec13 · 2026-10-30T10:30Z |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract reviewServed-tier: Inputs read: card #28019 (body, triage 6518908209, claim 6519052139 as amended, withdrawal 6519075426, os-dev-report 6520514172), PR #28043 body, file list and net diff against ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: FAIL One must-fix: the derived-arm prescription ("the column takes the operands' EUR") is false against |
…he column really carries A derived column takes no currency code from its operands on the wire, so deleting the measure's currency leaves it unlabelled. The message now prescribes restating the operands' code, or deleting it with that consequence named. Changeset Clause-② reads no (narrowing). Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head dc86f2a · 2026-10-30T10:39Z |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract reviewServed-tier: Delta review: one commit on top of ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS No must-fix. Should-fix, carried by the seat's ACCEPT and not by a further push: the body's |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL 维护者速读
席位意见:ACCEPT。 席位核对Head-sha:
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL
Fixes #28019
维护者速读
currency与其操作数统一解析出的fixed币种不同,直接拒绝(DATASET_INVALID/ 400),报错写明改法;重述同一币种仍合法。currency: 'USD'能编译通过,结果列把欧元金额标成美元,没有任何提示(analytics(dataset): a derived measure that declares its own currency over operands resolving to one fixed code compiles and prints that code's amounts labelled as the declared currency #28019)。Clause-②: yes (narrowing)、Metadata-protocol: narrowed、minor;此前能编译的这类数据集现在被拒,仓内没有数据集用到该形态,已部署租户若有,报错会点名要删的currency。回滚即还原本 PR。packages/services/service-analytics/src/measure-currency-coherence.ts守卫 1 末尾新增的 fixed 判断,src/__tests__/record-currency-analytics.test.ts新增的固定测试。pnpm --filter @objectstack/service-analytics exec vitest run src/__tests__/record-currency-analytics.test.ts,49 例全绿;修复前其中 5 例红。Clause-②: yes (narrowing) — a dataset that declared a derived measure's
currencyagainst its operands' singlefixedcode compiled before and is refused nowMetadata-protocol: narrowed
What changes
measure-currency-coherence.ts, Guard 1: a derived measure that declares acurrencyis now also compared with its resolved currency (resolvedOf). When every monetary operand resolves to onefixedcode and the declared code differs, compile refuses withDATASET_INVALID/ 400. The sentence follows the existing fixed-field refusal: it names the dataset, the derived measure, its operands, the operands' code, and the fix (delete the measure'scurrency; the column takes the operands' code). Operands reached through a fixed-code derived sum are covered too.fixedcode; a derived measure whose operands resolve to no onefixedcode (afixedamount with a tenant-currency one, aratio/product); the record-mode refusal on derived measures; Guard 2.minorchangeset for@objectstack/service-analytics(a dataset value is refused, soMetadata-protocol: narrowed, as in service-analytics: refuse fixed-versus-fixed cross-currency measures at dataset compile (multi-currency, epic #8345) #27579's changeset for the sibling refusals), with the BREAKING note and the ADR-0087not-required (no-migration-prescription)disposition.Why
{ derived: { op: 'sum', of: ['eur_a', 'eur_b'] }, currency: 'USD' }over two EUR-fixed measures compiled, andenrichResultColumnslet the declaredUSDlabel a column of euros. Guard 1 skipped derived measures for the fixed comparison (const field = m.derived ? undefined : fieldOf(m)), althoughresolvedOfalready knew the operands' commonfixedcode. Triage (6518908209) ruled that Guard 1 judges derived measures too and that restating the same code stays legal.Verification
record-currency-analytics.test.tsgives 5 failed / 44 passed. The 5 are the derived sum and difference pins, the fixed-code derived sum pin, and thequeryDatasetand draft-preview door pins; the controls (restated code, no one fixed code) stay green.false, the same 5 go red; restored, the blob equals HEAD's andgit diff HEADis empty.pnpm --filter @objectstack/service-analytics test: 196 files, 4759 passed / 282 skipped.typecheckexits 0. eslint on both changed files exits 0.dispatch-gates --commands: 59 derived, 58 run green, 1 not measured locally (check:dual-build-cjs-loadsneeds a whole-tree build), 0 unrun. Dogfood battery: 357 files / 2875 tests passed, 0 failures.no (narrowing)/none/patchtoyes (narrowing)/narrowed/minorbefore the push (claim amended); the gates above ran on the same code.🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL