You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 6dc06b2
Browse filesBrowse the repository at this point in the historyBrowse files
fix(service-analytics): a dataset refuses a measure currency that contradicts its field's fixed code, and a derived measure across two different fixed codes (#27991)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D5QoSbzQb2Lqa8sG8mU6Fiz)_Fixes#27579
Clause-②: yes (narrowing)
Metadata-protocol: narrowed
Skills-docs: follow-up owed —
skills/objectstack-data/rules/field-types.md,
skills/objectstack-ui/rules/dashboards.md,
content/docs/data-modeling/currency.mdx, content/docs/api/data-api.mdx
## 维护者速读
- 改了什么:数据集编译时新增两条只针对 `fixed` 币种的拒收。度量写的 `currency` 和被汇总字段的 `fixed`
币种不同,拒收;派生度量(任何运算)的两个金额操作数落在两个不同的 `fixed` 币种,拒收。都返回 `DATASET_INVALID` /
400,并给出改法。
- 为什么改:这两种写法今天会算出不报错的错数(EUR 字段被标成 USD,或 USD 减 EUR 不带币种)。维护者裁决
6486427200(ADR-0144 Open item 12,字母 A)。
- 风险与回滚:这是收窄,以前能编译的这两种写法现在被拒。仓内数据集元数据实测 0
处命中;已部署租户存的数据集无法从仓库调查,它们会在注册与请求时被拒,错误信息写明改法。回滚即撤本 PR。
-
先看:`packages/services/service-analytics/src/measure-currency-coherence.ts`(两个
`fixed` 分支与 `fixed`
标记),`packages/services/service-analytics/src/__tests__/record-currency-analytics.test.ts`(三道门的
pin 与对照)。
- 两分钟验证:`pnpm --filter @objectstack/service-analytics exec vitest run
src/__tests__/record-currency-analytics.test.ts`,预期 42 个用例全绿。
- 需要你做的:无,席位落地。
## What changes
`assertMeasureCurrenciesCoherent` (`measure-currency-coherence.ts`),
called once from `compileDataset`, gains the `fixed` arm of each guard.
The record-mode arms from #27338 are unchanged.
- **Shape 1.** An aggregated measure whose `currency` differs from its
field's `fixed` code refuses with `DATASET_INVALID` / 400. The message
names the measure, the field, the object and both codes, and the fix:
delete the measure's `currency`, and the column takes the field's code.
A `currency` that restates the `fixed` code is accepted.
- **Shape 2.** A derived measure, for every op (`sum`, `difference`,
`ratio`, `product`), whose monetary operands resolve to two different
`fixed` codes refuses with `DATASET_INVALID` / 400, naming both
operands, their codes and the fix (derive it from measures in one
currency, or drop the operand in the other). A derived `sum` /
`difference` over one `fixed` code resolves to that code, so a derived
measure built on it is judged at the next level. A `count` operand is
not compared.
- **What a `fixed` code is.** A resolved code now carries `fixed`: true
when it is the field's `fixed` code, restated by the measure or not. A
derived `sum` / `difference` is `fixed` when every operand is. Shape 2
compares only `fixed` codes.
- **Not judged, unchanged:** a `fixed` code against the tenant currency;
a measure `currency` over a `dynamic` field. Because the ruling lists
the second as legal, an operand whose code is such an explicit one is
not a `fixed` code, and Shape 2 does not compare it (see Acceptance
notes).
- **Moved pins.** Starting from #27338's first draft (`80b98fb8`,
dropped at `507ed8f9`): `currency-mode-relay.test.ts`'s
`m_explicit_fixed` restates the fixed field's `EUR` instead of
contradicting it with `JPY`, and `query-dataset.test.ts`'s chain pin
moves to a `dynamic` field.
- Changeset: `@objectstack/service-analytics` **minor**, with a BREAKING
paragraph, both declaration lines, and the ADR-0087 marker `not-required
(no-migration-prescription)`.
## Tests
`record-currency-analytics.test.ts`, guard block (real engine over
SQLite, through `AnalyticsServicePlugin`):
- Shape 1 refused at registration, naming the fix; restated code
accepted (control); a `currency` over a `dynamic` field not judged
(control).
- Shape 2 refused for each of the four ops, naming the fix; refused one
level up through a derived `sum` of one `fixed` code; a `fixed` operand
restated by its measure code compares by that one code (control); an
explicit code over a `dynamic` field against a `fixed` one is not judged
(control); amount per invoice over each `fixed` code, and a `fixed`
amount with the tenant currency, accepted (controls).
- The request door and the Studio draft preview: `queryDataset`, with
and without `previewDrafts`, refuses both shapes with `DATASET_INVALID`
/ 400 naming the measure; a restated code is served with `currency:
'EUR'` on both (control).
- The old "not judged" controls for the two `fixed` shapes are replaced
by the pins above.
## Acceptance notes
- **The in-tree survey: 0 of either shape.** I searched dataset metadata
in `examples/**`, `packages/platform-objects/**`,
`packages/runtime/src/seed-datasets.ts`, `ui/examples/**` and the skills
evals. 16 files declare `measures:`, and none sets a measure `currency`.
The two derived measures in the tree (`chart-gallery.dataset.ts`,
`revenue-pulse.dataset.ts`) are both ratios of two counts. A deployed
tenant's stored datasets cannot be surveyed from the repository: a
stored dataset with either shape now refuses at registration and at
query, and its message names the edit.
- **One reading the ruling leaves open, for the contract review.** The
ruling says Shape 2 compares operands that "resolve to two different
`fixed` codes", and lists "a measure `currency` over a `dynamic` field"
as legal. #27338's first draft compared any two resolved codes, so it
also refused a derived measure over an explicit `JPY` on a `dynamic`
field and a `fixed` `USD`. This PR follows the ruling's words: only
`fixed` codes are compared, and a CONTROL pins that pair as accepted.
Refusing that pair too would narrow further, and would need its own
ruling.
- **Skills and docs: follow-up owed.**
`skills/objectstack-data/rules/field-types.md` says "Under
`'fixed'`/`'dynamic'` a measure's own `currency` wins", which this PR
makes false for a contradicting `fixed` code.
`skills/objectstack-ui/rules/dashboards.md` (the currency chain
comment), `content/docs/data-modeling/currency.mdx` (Roll-ups and
datasets) and `content/docs/api/data-api.mdx` (the currency chain)
describe the record-mode refusals only. None is in this card's file
surface. Grep: `git grep -n -iE
"measure.{0,40}currency|currency.{0,40}measure" -- 'skills/**'
'content/docs/**'`.
- **`dataset-compiler.ts`.** The comment above the
`assertMeasureCurrenciesCoherent` call names only the record-mode arms.
It is outside the file surface and is incomplete rather than false, so
this PR leaves it.
- The ADR-0144 Open item 12 dated note stays with the epic PM, as the
ruling says.
## Verification
Head `c053f4ca` (base `bacb3e6a`).
<details><summary>Red on the base, ablations, restore</summary>
- The new and moved pins against the unchanged
`measure-currency-coherence.ts` of `bacb3e6a`: 10 red, 83 green over the
three files. Red: Shape 1 at registration, Shape 2 for each of the four
ops, Shape 2 one level up, and both shapes at `queryDataset` and at the
draft preview. Every control was green (restated code, `dynamic` field,
`count` operand, tenant currency, explicit code over a `dynamic` field).
- Through `scripts/ablation-replace.mjs` (anchor hit 1 → 0, restored
blob == HEAD, `git diff HEAD` empty after each). The tests import the
source, so no build was needed between legs.
- Shape 1 arm disabled: 3 red (registration, `queryDataset`, preview),
39 green.
- Shape 2 `fixed` pair disabled: 7 red (four ops, one level up,
`queryDataset`, preview), 35 green.
- An explicit code over a `dynamic` field marked `fixed`: 1 red (the
CONTROL that pins it as not judged), 41 green.
</details>
<details><summary>Package suite, gates, dogfood</summary>
- `pnpm --filter @objectstack/service-analytics test`: 196 files, 4752
passed, 282 skipped. `typecheck` exit 0 (its `include` is `src`, tests
included).
- `node scripts/pm/dispatch-gates.mjs --commands`: 59 derived families,
all run. `--ran` reconciliation: "59 derived, 58 run, 1 NOT-MEASURED, 0
UNRUN". 58 exited 0. NOT MEASURED: `check:dual-build-cjs-loads`, exit 3
(prerequisite: a whole-tree build; this run built only the dogfood
closure and `@objectstack/service-analytics`). CI runs it.
- `pnpm lint`: exit 0.
- Full dogfood battery (the `(narrowing)` arm), `pnpm --filter
@objectstack/dogfood test` under the verify lock at `c053f4ca`: VERDICT
command-exit 0, Test Files 357 passed and 3 skipped (360), Tests 2875
passed and 20 skipped (2895), no failures.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_016WV4cyL63KrU8pu6fycLHW)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016WV4cyL63KrU8pu6fycLHW
Co-authored-by: Claude <noreply@anthropic.com>
fix(service-analytics): a dataset refuses a measure `currency` that contradicts its field's `fixed` code, and a derived measure across two different `fixed` codes (ADR-0144 open item 12)
6
+
7
+
Clause-②: yes (narrowing)
8
+
Metadata-protocol: narrowed
9
+
10
+
**BREAKING** (`@objectstack/service-analytics`): two compile-time refusals, `DATASET_INVALID` / 400, for dataset metadata that compiled before and printed a wrong label or a wrong number. Both reach boot registration, `queryDataset` and the Studio draft preview, which compile through the one `compileDataset` call.
11
+
12
+
- A measure `currency` whose code differs from the aggregated field's `fixed` code. The column printed the field's amounts under the measure's code, such as euros labelled `USD`. The message names the fix: delete the measure's `currency`, and the column takes the field's code. A measure `currency` that restates the `fixed` code is still accepted.
13
+
- A derived measure, for every op, whose monetary operands resolve to two different `fixed` codes, directly or through a derived `sum` / `difference` built on them. The value combined bare numbers in two currencies and carried no currency at all. The message names the fix: derive it from measures in one currency, or drop the operand in the other. A `count` operand is not compared, so an amount per invoice still compiles.
14
+
15
+
Not judged, unchanged: a `fixed` code against the tenant currency, which is not known at compile; a measure `currency` over a `dynamic` field. The record-mode arms of both guards are unchanged.
16
+
17
+
No in-tree dataset declares either shape. A deployed tenant's stored datasets cannot be surveyed from the repository: a stored dataset with either shape now refuses at registration and at query, and its message names the edit.
18
+
19
+
<!-- adr-0087: not-required (no-migration-prescription) the two refusals reject a contradiction in a dataset's own measures at compile and name the fix in their message; there is no stored shape to convert and no key is removed -->
0 commit comments