Repository navigation
fix(service-analytics): a derived sum or difference over one fixed currency code carries that code on its column - #28122
Conversation
…rrency code carries that code on its column A derived measure has no field to lend a currency code, so enrichResultColumns minted its column monetary only when the measure restated `currency`. A sum of two EUR-fixed measures reached the wire as a bare number, though the currency docs say it carries EUR. The compile guard's resolution of each measure's currency is lifted into measureCurrencyResolver, which both the guard and the column chain read: a derived measure that resolves to one `fixed` code now carries it, on the live query and the draft preview. Every other derived measure keeps its answer. The guard's refusal of a contradicting derived `currency` no longer says the column would carry no code once the `currency` is deleted. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4d1NUiRegpdMBrWq7FZ
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 3 release-owned page(s) name something this change touched. These are read-only:
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 c369804aa6ef15d92e7d4221fd6c7e9a4ee176b3 && git checkout c369804aa6ef15d92e7d4221fd6c7e9a4ee176b3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 91a423ef6b94a86be8874f1ac6449b406a8971c8 670a779093bce77b73cb2fd370e68510c8f69e62 && git checkout -B drift-repro 91a423ef6b94a86be8874f1ac6449b406a8971c8 && git merge --no-ff 670a779093bce77b73cb2fd370e68510c8f69e62
node scripts/docs-audit/affected-docs.mjs --json 91a423ef6b94a86be8874f1ac6449b406a8971c8
|
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract-review-claim: The lane seat writes the |
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract review · DELIBERATE CORRECTION confirmationServed-tier: Owed because this PR rewrites a pending release note it made false and also changes code: the confirmation is a same-head review record that judges each rewritten sentence. ① Rewritten sentences, judged one by oneNote:
Refusal message:
New note: ② Derived judgments
③ Semver level
④ Designed red: gate and cause
⑤ Design judgment
Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Seat ACCEPT · PR #28122 · head
|
Requested by Elon · project thread
Fixes #28049
domain:server#3 · os-elon-musk · session_016WV4d1NUiRegpdMBrWq7FZ
Clause-②: no (the response comes to say what
content/docs/data-modeling/currency.mdxL180–181 already declares: "A derivedsumordifferenceover onefixedcode carries that code")Metadata-protocol: none
Skills-docs: none.
git grep -n -i currency skills/hitsskills/objectstack-ui/rules/dashboards.mdL298–306 andskills/objectstack-data/rules/field-types.mdL180–188; both state the measure / field chain and the refusals, and neither says a derived measure carries no code, so neither becomes false.content/docs/api/data-api.mdxL497–509 (the currency chain) likewise says nothing about derived measures and stays true.维护者速读
derived),如果是sum/difference,且其所有金额操作数都固定为同一个币种(例如两个固定 EUR 的度量相加),查询结果的这一列现在带上该币种(currency: 'EUR')。线上查询和 Studio 草稿预览一致。packages/services/service-analytics/src/measure-currency-coherence.ts(解析抽成measureCurrencyResolver),analytics-service.ts的enrichResultColumns币种链(derivedCode)。pnpm --filter @objectstack/service-analytics exec vitest run src/__tests__/record-currency-analytics.test.ts,预期 52 passed(新增 3 条:两种策略与草稿预览下eur_twice/eur_net列为EUR,6 个对照列无币种)。check-empty-changeset因此按设计保持红,等人工确认)。What changes
measure-currency-coherence.ts. The compile guard's per-measure currency resolution (resolvedOf,monetaryOperands) moves out ofassertMeasureCurrenciesCoherentintomeasureCurrencyResolver(dataset, declaredCurrency), unchanged, with one reader added:fixedCodeOf(m), the onefixedcode a measure resolves to, elseundefined. The guard now destructures the same resolver. Module-internal; not exported from the package entry.currencycontradicting its operands'fixedcode (PR fix(service-analytics): refuse a derived measure currency that contradicts its operands' fixed code #28043) said "restate the operands' EUR as the measure'scurrency, or delete it (the column then carries no currency code)". After this change that parenthetical is false, so it now reads "delete the measure'scurrency(the column takes the operands' EUR), or restate EUR." The field-level refusal beside it already spells its fix this way.analytics-service.ts. ThedeclaredCurrencyadapter oversourceFieldMetathatcompile()handed the compiler becomes one private member, andenrichResultColumnsbuilds ameasureCurrencyResolverover the same adapter. A derived measure's column is monetary whenfixedCodeOfanswers, and the chain is explicit measurecurrency→ fieldfixedcode → derivedfixedcode → tenant. The guard already refuses a derivedcurrencythat contradicts the resolved code, so the first and third links cannot disagree.record-currency-analytics.test.ts. New block "a derived measure column takes the currency its operands resolve to": native and ObjectQL strategies plus the draft preview. PinsEURon asumover a field-fixed and a restated EUR measure, adifference, and a restated derived; pins nocurrencyand nocurrencyFieldon a fixed+tenant sum, a tenant+tenant sum, a sum of explicit JPY over adynamicfield, aratio, aproduct, and a record-mode sum. Values checked too ({ USD: 20, EUR: 180 }). The existing message assertion moves to the new wording..changeset/28049-derived-sum-fixed-currency-column.md(new, patch) and.changeset/28019-derived-measure-fixed-currency.md(one sentence corrected, see below).Tests
Red on main, green after, ablation
origin/main8cb8c7f4, test only): 3 failed / 49 skipped (filtered). The draft preview failedAssertionError: eur_twice: expected undefined to be 'EUR'. That first draft also selected a derived-over-derived measure, which failed both live strategies earlier, in the executor (second acceptance note), so it was dropped from the block. The ablation below is the clean red for the final block on all three paths.670a7790:record-currency-analytics.test.ts52 passed.scripts/ablation-replace.mjsonanalytics-service.ts(const derivedCode = m.derived ? currencies.fixedCodeOf(m) : undefined;→const derivedCode = undefined as string | undefined; // ABLATION; anchor 1 → 0, blob98e0c551→885a2a1f): 3 failed / 49 passed. Restored: blob back to98e0c551= HEAD,git diff HEADempty. The suite imports the service fromsrc(relative imports), so no dist rebuild was in the path.pnpm --filter @objectstack/service-analytics test: 196 files passed, 4762 passed / 282 skipped.pnpm --filter @objectstack/service-analytics typecheck(includessrc/__tests__): exit 0.Verification
Gates at head 670a779
dispatch-gates --commandsderived 62 families; all run with exit codes recorded, afterturbo run build --filter='@objectstack/dogfood^...' --filter='@objectstack/runtime^...'.check:dual-build-cjs-loadsexit 3 (no dist for seven unrelated packages, e.g.@objectstack/studio): NOT MEASURED locally, CI runs it.check-empty-changeset --base origin/mainexit 1: the deliberate correction below, red by design.--ran: 62 derived, 61 run, 1 NOT-MEASURED (derived from the recorded exit 3), 0 unrun.pnpm lint: exit 0.origin/main; none touchesservice-analyticsor the corrected changeset.Acceptance notes
.changeset/28019-derived-measure-fixed-currency.md(PR fix(service-analytics): refuse a derived measure currency that contradicts its operands' fixed code #28043, not yet released) told readers the refusal's fix was "restate the operands' code as the measure'scurrency, or delete it (the column then carries no currency code; a derived column takes no code from its operands on the wire)". This PR makes that parenthetical false, so the sentence now reads "delete the measure'scurrency(the column takes the operands' code), or restate that code." Nothing else in that note changed.check-empty-changesetrefuses any edit to another PR's changeset and stays red until a person confirms; restoring the old sentence would publish a false statement in the same release as this fix.queryDatasetselecting such a measure fails on both strategies: "Measure 'eur_twice' on cube … aggregates field 'eur_twice', which object … does not have". The draft preview serves it. That is the executor, outside this card, and is reported to the seat. The new block does not use the shape.typestaysnumber, as for a basesumover a currency field.currencyis what makes a column monetary on the wire.Human Minimum
packages/specholds no runtime logic. (Not touched.)pnpm lintpasses.main, one pull request.packages/spectouched: not touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4d1NUiRegpdMBrWq7FZ
Generated by Claude Code