Skip to content

Commit cc376b9

Browse files
committed
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Utp6CbTZrFqRYNgjSaSbN5
1 parent 10fc9bf commit cc376b9

4 files changed

Lines changed: 52 additions & 9 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
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
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **`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.
11+
- **`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.

‎docs/adr/0144-multi-currency-record-currency-dated-rates-write-time-home-projection.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,12 @@ Where a source leans one way, the lean is quoted here, not adopted. This record
353353
`packages/platform-objects/src`. ADR-0029 D1, D3 and D6 give a first-party capability ownership of its `sys_*`
354354
objects and decompose `platform-objects`, and ADR-0028 (Deferred) records the `sys` namespace design. #27185
355355
settles the owner against them.
356+
*Note (2026-10-30):* settled: `@objectstack/platform-objects` owns it (D3's one owner per object), landed in PR
357+
#27246 (`194e66c0`). The table's one reader is the ObjectQL engine's write path, present in every composition that
358+
can write a record, so it has no on/off capability boundary of its own (ADR-0029 D2) and no service or plugin
359+
package would own it; it registers with the engine-facing infrastructure objects `sys_migration` and `sys_secret`,
360+
and workload row M1-1 names the same package. Settlement:
361+
[`6511509227`](https://github.com/objectstack-ai/objectstack/issues/27185#issuecomment-6511509227) on #27185.
356362
12. **Whether today's cross-currency arithmetic between `fixed` fields of different codes is refused** (epic #8345).
357363
Two shapes: a measure `currency` that contradicts a `fixed` field's code, and a derived measure over two different
358364
`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
362368
the tree evidence. The general `fixed`-versus-`fixed` guard stays undecided. The authority is the maintainer's, and
363369
epic #8345 carries the question
364370
([`6485374827`](https://github.com/objectstack-ai/objectstack/issues/8345#issuecomment-6485374827)).
371+
*Note (2026-10-30):* settled: the maintainer ruled both shapes refused at dataset compile, letter A
372+
([`6486427200`](https://github.com/objectstack-ai/objectstack/issues/8345#issuecomment-6486427200) on #8345).
373+
Card #27579 landed it in PR #27991 (`6dc06b2e`): a measure `currency` that differs from its `fixed` field's code,
374+
and a derived measure, whatever its op, over two different `fixed` codes answer `DATASET_INVALID`; a `count`
375+
operand is not compared. A `fixed` code is not judged against `localization.currency`, and `currency` over a
376+
`dynamic` field still wins.
377+
13. **An amount field switched to `currencyMode: 'record'` on an object that already holds rows** (#27577). A
378+
`dynamic` or `fixed` field that turns `record` leaves its stored rows with an amount beside a blank code. No row is
379+
refused for resting (ADR-0113 D1): the D8 rule 4 write guard refuses the next write that touches the pair
380+
(`ERR_CURRENCY_CODE_REQUIRED`), and until the code is written the row shows a plain number, falls into the null
381+
group of a grouped aggregate and sits outside every currency-paired filter. PR #27339's contract review
382+
([`6490309706`](https://github.com/objectstack-ai/objectstack/pull/27339#issuecomment-6490309706), item ③)
383+
escalated whether the switch is refused while such rows exist or a backfill is owed; this record decided neither.
384+
*Note (2026-10-30):* settled: the maintainer ruled letter B
385+
([`6492000994`](https://github.com/objectstack-ai/objectstack/issues/8345#issuecomment-6492000994) on #8345): the
386+
switch is allowed and owes an explicit backfill. PR #28128 implements it. The operator runs
387+
`os migrate record-currency-codes --object <object> --field <amount> --code <ISO 4217>`, which writes the given
388+
code into every row that holds an amount beside a blank code (dry run by default, `--apply` writes; the code is
389+
never derived, D8 rule 4), and the metadata save that makes the switch answers the existing 409
390+
`destructive_change` with the finding `currency_mode_to_record`, lifted by the existing `force`. The finding reads
391+
no row and fires only where a previous version exists, so a package registered at boot is not refused. The ruling
392+
left open how a cloud-managed tenant without a CLI reaches the backfill; open beside it: the run walks every
393+
organization under the unscoped system context and writes the one `--code` into each one's codeless rows (no
394+
organization-scope flag, no per-organization count), so on a multi-organization deployment, where `dynamic`
395+
amounts displayed in each organization's own `localization.currency`, that code can be wrong for some of them
396+
(PR #28128's review should-fix S1, escalated to the epic PM).
365397

366398
## Verification notes
367399

‎skills/objectstack-data/rules/field-types.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,9 @@ Stored as JSON on the parent row — no separate table / FK:
183183
`$`); `'record'`, each record's code in the `text` field `currencyField`
184184
names (by convention `currency_code`), with `valueDomain: 'iso_4217_currency'`. A
185185
dataset measure's own `currency` wins over a `'dynamic'` field; over a `'fixed'` one
186-
it may only restate the code, else `DATASET_INVALID` (delete it), as is a derived
187-
measure over two different `'fixed'` codes (derive from one currency or drop an
188-
operand). A `'fixed'` code is not judged against the tenant's.
186+
it may only restate the code, else `DATASET_INVALID` (objectstack-ui →
187+
Dashboards). A `'fixed'` code is not judged against the tenant's. Switched a
188+
stored field to `'record'`? Backfill: `os migrate record-currency-codes`.
189189

190190
### Select with Default
191191

‎skills/objectstack-ui/rules/dashboards.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -295,12 +295,12 @@ defineDataset({
295295
dimensions: [{ name: 'signed_date', field: 'signed_date', type: 'date', dateGranularity: 'month' }],
296296
measures: [{ name: 'signed_count', aggregate: 'count' }],
297297
});
298-
// A money measure may set `currency` (ISO 4217), e.g. `currency: 'USD'`.
299-
// Chain: measure `currency` → the field's `defaultCurrency` (`fixed`
300-
// only) → tenant `localization.currency`; omit it for counts. A
301-
// `currencyMode: 'record'` amount refuses `currency` and ungrouped
302-
// sum/avg/min/max; grouped by its code field, the column carries
303-
// `currencyField` (per-row code column); a `count` never does. A `fixed`
298+
// A money measure may set `currency` (ISO 4217, e.g. `'USD'`). Chain:
299+
// measure `currency` → the field's `defaultCurrency` (`fixed` only) →
300+
// tenant `localization.currency`; omit it for counts. A
301+
// `currencyMode: 'record'` amount refuses `currency`, and sum/avg/min/max
302+
// unless grouped by its code field; then the column carries
303+
// `currencyField` (the per-row code column); a `count` never does. A `fixed`
304304
// field refuses a different `currency` (delete it; restating is fine), and
305305
// a derived measure refuses operands in two `fixed` codes (derive from one
306306
// currency or drop one). Not judged: `currency` over a `dynamic` field, a

0 commit comments

Comments
 (0)