From cc376b97226d80a2d0579d96840f65b26859e666 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 15:16:57 +0000 Subject: [PATCH 1/2] docs(adr,skills): ADR-0144 Open items 11 and 12 settled, Open item 13 for the record-mode switch; skills name the backfill and the grouped-only aggregate ADR-0144: Open item 11 gets a dated note: platform-objects owns sys_exchange_rate (#27185, PR #27246). Open item 12 gets one too: ruling 6486427200, letter A, refuses both fixed-vs-fixed shapes (PR #27991). New Open item 13 covers switching a stored amount to record mode: ruling 6492000994, letter B, implemented by PR #28128. It records that the multi-organization single-code reach stays open. Skills: field-types.md names `os migrate record-currency-codes`. dashboards.md now says sum/avg/min/max are refused "unless grouped by its code field" (the should-fix from PR #27475's review). Both files stay inside the token ratchet. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01Utp6CbTZrFqRYNgjSaSbN5 --- .../8345-currency-mode-switch-skills.md | 11 +++++++ ...-dated-rates-write-time-home-projection.md | 32 +++++++++++++++++++ skills/objectstack-data/rules/field-types.md | 6 ++-- skills/objectstack-ui/rules/dashboards.md | 12 +++---- 4 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 .changeset/8345-currency-mode-switch-skills.md diff --git a/.changeset/8345-currency-mode-switch-skills.md b/.changeset/8345-currency-mode-switch-skills.md new file mode 100644 index 00000000000..7b674759927 --- /dev/null +++ b/.changeset/8345-currency-mode-switch-skills.md @@ -0,0 +1,11 @@ +--- +'@objectstack/skills': patch +--- + +docs(skills): the field-types guide names the backfill for an amount switched to record mode; the dashboards guide says a record-mode amount aggregate is served only grouped by its code field + +Clause-②: no +Metadata-protocol: none + +- **`objectstack-data/rules/field-types.md`**: an amount field with stored rows that is switched to `currencyMode: 'record'` is backfilled with `os migrate record-currency-codes`. The derived-measure sentence moves out of this guide; it already sits in the dashboards guide, which the line now points to. +- **`objectstack-ui/rules/dashboards.md`**: a record-mode amount refuses `sum` / `avg` / `min` / `max` unless grouped by its code field. The old "ungrouped" read as if any grouping would do. diff --git a/docs/adr/0144-multi-currency-record-currency-dated-rates-write-time-home-projection.md b/docs/adr/0144-multi-currency-record-currency-dated-rates-write-time-home-projection.md index 25b89fed82e..3678ec57f08 100644 --- a/docs/adr/0144-multi-currency-record-currency-dated-rates-write-time-home-projection.md +++ b/docs/adr/0144-multi-currency-record-currency-dated-rates-write-time-home-projection.md @@ -353,6 +353,12 @@ Where a source leans one way, the lean is quoted here, not adopted. This record `packages/platform-objects/src`. ADR-0029 D1, D3 and D6 give a first-party capability ownership of its `sys_*` objects and decompose `platform-objects`, and ADR-0028 (Deferred) records the `sys` namespace design. #27185 settles the owner against them. + *Note (2026-10-30):* settled: `@objectstack/platform-objects` owns it (D3's one owner per object), landed in PR + #27246 (`194e66c0`). The table's one reader is the ObjectQL engine's write path, present in every composition that + can write a record, so it has no on/off capability boundary of its own (ADR-0029 D2) and no service or plugin + package would own it; it registers with the engine-facing infrastructure objects `sys_migration` and `sys_secret`, + and workload row M1-1 names the same package. Settlement: + [`6511509227`](https://github.com/objectstack-ai/objectstack/issues/27185#issuecomment-6511509227) on #27185. 12. **Whether today's cross-currency arithmetic between `fixed` fields of different codes is refused** (epic #8345). Two shapes: a measure `currency` that contradicts a `fixed` field's code, and a derived measure over two different `fixed` codes, which Context §1 counts among today's silently wrong numbers. The ruling on #27119 did not decide @@ -362,6 +368,32 @@ Where a source leans one way, the lean is quoted here, not adopted. This record the tree evidence. The general `fixed`-versus-`fixed` guard stays undecided. The authority is the maintainer's, and epic #8345 carries the question ([`6485374827`](https://github.com/objectstack-ai/objectstack/issues/8345#issuecomment-6485374827)). + *Note (2026-10-30):* settled: the maintainer ruled both shapes refused at dataset compile, letter A + ([`6486427200`](https://github.com/objectstack-ai/objectstack/issues/8345#issuecomment-6486427200) on #8345). + Card #27579 landed it in PR #27991 (`6dc06b2e`): a measure `currency` that differs from its `fixed` field's code, + and a derived measure, whatever its op, over two different `fixed` codes answer `DATASET_INVALID`; a `count` + operand is not compared. A `fixed` code is not judged against `localization.currency`, and `currency` over a + `dynamic` field still wins. +13. **An amount field switched to `currencyMode: 'record'` on an object that already holds rows** (#27577). A + `dynamic` or `fixed` field that turns `record` leaves its stored rows with an amount beside a blank code. No row is + refused for resting (ADR-0113 D1): the D8 rule 4 write guard refuses the next write that touches the pair + (`ERR_CURRENCY_CODE_REQUIRED`), and until the code is written the row shows a plain number, falls into the null + group of a grouped aggregate and sits outside every currency-paired filter. PR #27339's contract review + ([`6490309706`](https://github.com/objectstack-ai/objectstack/pull/27339#issuecomment-6490309706), item ③) + escalated whether the switch is refused while such rows exist or a backfill is owed; this record decided neither. + *Note (2026-10-30):* settled: the maintainer ruled letter B + ([`6492000994`](https://github.com/objectstack-ai/objectstack/issues/8345#issuecomment-6492000994) on #8345): the + switch is allowed and owes an explicit backfill. PR #28128 implements it. The operator runs + `os migrate record-currency-codes --object --field --code `, which writes the given + code into every row that holds an amount beside a blank code (dry run by default, `--apply` writes; the code is + never derived, D8 rule 4), and the metadata save that makes the switch answers the existing 409 + `destructive_change` with the finding `currency_mode_to_record`, lifted by the existing `force`. The finding reads + no row and fires only where a previous version exists, so a package registered at boot is not refused. The ruling + left open how a cloud-managed tenant without a CLI reaches the backfill; open beside it: the run walks every + organization under the unscoped system context and writes the one `--code` into each one's codeless rows (no + organization-scope flag, no per-organization count), so on a multi-organization deployment, where `dynamic` + amounts displayed in each organization's own `localization.currency`, that code can be wrong for some of them + (PR #28128's review should-fix S1, escalated to the epic PM). ## Verification notes diff --git a/skills/objectstack-data/rules/field-types.md b/skills/objectstack-data/rules/field-types.md index 019b2a657d4..0bf72cfac68 100644 --- a/skills/objectstack-data/rules/field-types.md +++ b/skills/objectstack-data/rules/field-types.md @@ -183,9 +183,9 @@ Stored as JSON on the parent row — no separate table / FK: `$`); `'record'`, each record's code in the `text` field `currencyField` names (by convention `currency_code`), with `valueDomain: 'iso_4217_currency'`. A dataset measure's own `currency` wins over a `'dynamic'` field; over a `'fixed'` one -it may only restate the code, else `DATASET_INVALID` (delete it), as is a derived -measure over two different `'fixed'` codes (derive from one currency or drop an -operand). A `'fixed'` code is not judged against the tenant's. +it may only restate the code, else `DATASET_INVALID` (objectstack-ui → +Dashboards). A `'fixed'` code is not judged against the tenant's. Switched a +stored field to `'record'`? Backfill: `os migrate record-currency-codes`. ### Select with Default diff --git a/skills/objectstack-ui/rules/dashboards.md b/skills/objectstack-ui/rules/dashboards.md index 0e937873769..5c20f5ef605 100644 --- a/skills/objectstack-ui/rules/dashboards.md +++ b/skills/objectstack-ui/rules/dashboards.md @@ -295,12 +295,12 @@ defineDataset({ dimensions: [{ name: 'signed_date', field: 'signed_date', type: 'date', dateGranularity: 'month' }], measures: [{ name: 'signed_count', aggregate: 'count' }], }); -// A money measure may set `currency` (ISO 4217), e.g. `currency: 'USD'`. -// Chain: measure `currency` → the field's `defaultCurrency` (`fixed` -// only) → tenant `localization.currency`; omit it for counts. A -// `currencyMode: 'record'` amount refuses `currency` and ungrouped -// sum/avg/min/max; grouped by its code field, the column carries -// `currencyField` (per-row code column); a `count` never does. A `fixed` +// A money measure may set `currency` (ISO 4217, e.g. `'USD'`). Chain: +// measure `currency` → the field's `defaultCurrency` (`fixed` only) → +// tenant `localization.currency`; omit it for counts. A +// `currencyMode: 'record'` amount refuses `currency`, and sum/avg/min/max +// unless grouped by its code field; then the column carries +// `currencyField` (the per-row code column); a `count` never does. A `fixed` // field refuses a different `currency` (delete it; restating is fine), and // a derived measure refuses operands in two `fixed` codes (derive from one // currency or drop one). Not judged: `currency` over a `dynamic` field, a From 23b3eb6cef51f90f8498baf47fba5e5b25049451 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 15:24:43 +0000 Subject: [PATCH 2/2] docs(adr): ADR-0144 Open item 13 cites #28128's merge commit Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01Utp6CbTZrFqRYNgjSaSbN5 --- ...cy-record-currency-dated-rates-write-time-home-projection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/adr/0144-multi-currency-record-currency-dated-rates-write-time-home-projection.md b/docs/adr/0144-multi-currency-record-currency-dated-rates-write-time-home-projection.md index 3678ec57f08..453bc21267c 100644 --- a/docs/adr/0144-multi-currency-record-currency-dated-rates-write-time-home-projection.md +++ b/docs/adr/0144-multi-currency-record-currency-dated-rates-write-time-home-projection.md @@ -383,7 +383,7 @@ Where a source leans one way, the lean is quoted here, not adopted. This record escalated whether the switch is refused while such rows exist or a backfill is owed; this record decided neither. *Note (2026-10-30):* settled: the maintainer ruled letter B ([`6492000994`](https://github.com/objectstack-ai/objectstack/issues/8345#issuecomment-6492000994) on #8345): the - switch is allowed and owes an explicit backfill. PR #28128 implements it. The operator runs + switch is allowed and owes an explicit backfill, landed in PR #28128 (`a3d24c99`). The operator runs `os migrate record-currency-codes --object --field --code `, which writes the given code into every row that holds an amount beside a blank code (dry run by default, `--apply` writes; the code is never derived, D8 rule 4), and the metadata save that makes the switch answers the existing 409