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 f3d286c
Browse filesBrowse the repository at this point in the historyBrowse files
fix(spec): record currency mode refuses an empty pointer and an inert defaultCurrency
Contract-review should-fixes on the record currency mode (ADR-0144 D2):
- `currencyField` is never empty (`.min(1)`), so no record amount
carries a pointer the object door names as nothing.
- `defaultCurrency` is refused under `record`, at the key, with a
prescription. `record` is new, so no stored row needs the tolerance
`dynamic` keeps; the inert key is ADR-0078's shape.
- The object door reads `VALUE_DOMAIN_FIELD_TYPES` instead of repeating
the `'text'` literal.
- ADR-0144 anchors on field.zod.ts and object.zod.ts.
- The changeset lists `@objectstack/platform-objects`, which ships the
regenerated metadata-form bundles.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f
Copy file name to clipboardExpand all lines: .changeset/27184-currency-record-mode.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,7 @@
1
1
---
2
2
'@objectstack/spec': minor
3
3
'@object-ui/i18n': minor
4
+
'@objectstack/platform-objects': minor
4
5
---
5
6
6
7
feat(spec): `currencyMode: 'record'` + `currencyField` — a record-mode amount points at a sibling ISO 4217 code field (ADR-0144 D2)
@@ -17,7 +18,7 @@ fields: {
17
18
}
18
19
```
19
20
20
-
-`currencyField` is required under `record` and refused under `dynamic` and `fixed`.
21
+
-`currencyField` is required under `record`, refused under `dynamic` and `fixed`, and never empty; `defaultCurrency` is refused under `record`.
21
22
- The object refuses a `currencyField` that names no other declared field, a field that is not `text`, or a `text` field without `valueDomain: 'iso_4217_currency'`. Each refusal is located at `fields.FIELD.currencyConfig.currencyField` and lists the object's candidate code fields.
22
23
-`dynamic` and `fixed` configs parse exactly as before; an app that never writes `record` sees no change.
currencyMode: z.enum(['dynamic','fixed','record']).default('dynamic').describe('Currency mode. `fixed`: the field has one currency, `defaultCurrency`. `dynamic` (the default): the field has no currency of its own — amounts display in the tenant default currency (the `localization.currency` setting; a plain number when none is set) and `defaultCurrency` is not read. `record`: each record has its own currency, the ISO 4217 code stored in the sibling field `currencyField` names; `defaultCurrency` is not read. The value is a bare number in every mode (ADR-0104 D1).'),
625
-
defaultCurrency: z.string().length(3).optional().describe('The currency code (ISO 4217, e.g. USD, CNY, EUR) of a `fixed`-mode field: its one currency, required under `fixed`. Not read under `dynamic` (the default), where amounts display in the tenant default currency, nor under `record`, where each record names its own.'),
637
+
currencyMode: z.enum(['dynamic','fixed','record']).default('dynamic').describe('Currency mode. `fixed`: the field has one currency, `defaultCurrency`. `dynamic` (the default): the field has no currency of its own — amounts display in the tenant default currency (the `localization.currency` setting; a plain number when none is set) and `defaultCurrency` is not read. `record`: each record has its own currency, the ISO 4217 code stored in the sibling field `currencyField` names; `defaultCurrency` is refused. The value is a bare number in every mode (ADR-0104 D1).'),
638
+
defaultCurrency: z.string().length(3).optional().describe('The currency code (ISO 4217, e.g. USD, CNY, EUR) of a `fixed`-mode field: its one currency, required under `fixed`. Not read under `dynamic` (the default), where amounts display in the tenant default currency, and refused under `record`, where each record names its own.'),
626
639
// ADR-0144 D2 — the pointer of a `record`-mode amount. Optional at the key
627
-
// level; `refuseCurrencyFieldOutsideRecordMode` below makes it required
628
-
// under `record` and refuses it under the other two modes, and the object's
// what it names, because only the object holds the sibling field map.
631
-
currencyField: z.string().optional().describe('`record` mode only, and required there: the name of the field on the same object that holds each record\'s currency — a `text` field declaring `valueDomain: \'iso_4217_currency\'` (conventionally `currency_code`). Refused under `dynamic` and `fixed`.'),
640
+
// level and never empty; `refuseRecordModeKeyMismatch` below makes it
641
+
// required under `record` and refuses it under the other two modes, and the
// object.zod.ts) checks what it names, because only the object holds the
644
+
// sibling field map.
645
+
currencyField: z.string().min(1,'`currencyField` names a field: an empty name points at nothing — name the field on this object that holds each record\'s ISO 4217 code, conventionally `currency_code`.').optional().describe('`record` mode only, and required there: the name of the field on the same object that holds each record\'s currency — a `text` field declaring `valueDomain: \'iso_4217_currency\'` (conventionally `currency_code`). Refused under `dynamic` and `fixed`, and never empty.'),
632
646
// #19992 — no `.overwrite()` here any more: the #7918 ISO 4217 contradiction
633
647
// check and the #11423 default-materializing `.overwrite()` both existed only
634
648
// for the removed `precision` key (see CURRENCY_CONFIG_DECIMAL_PLACES_GUIDANCE
635
-
// above). #27157 — `fixed` ⇒ a code; ADR-0144 D2 — `record` ⇔ a pointer, both
636
-
// below. Parse output is `{ currencyMode }` plus the `defaultCurrency` and
649
+
// above). #27157 — `fixed` ⇒ a code; ADR-0144 D2 — `record` ⇔ a pointer and
650
+
// no code, both below. Parse output is `{ currencyMode }` plus the `defaultCurrency` and
"invariant": "`UniqueScopeSchema` is the closed scope vocabulary `boolean | 'global' | 'organization'` (ADR-0120 D1). `'organization'` is the explicit synonym of field-level `true` — identical materialization, and `isUniqueDeclared` MUST count it (a word the vocabulary accepts but no driver reads is declarable-but-inert, ADR-0078). `'tenant'` and `'org'` are rejected, never aliased — the parse error names `'organization'` (§Terminology; PD #12: one contract, no dialects). The single-tenant story is D3's NULL-safe COALESCE bucket, NOT the retired 'composite degenerates to single-column' claim #5030 falsified."
7
+
"invariant": "`UniqueScopeSchema` is the closed scope vocabulary `boolean | 'global' | 'organization'` (ADR-0120 D1). `'organization'` is the explicit synonym of field-level `true` — identical materialization, and `isUniqueDeclared` MUST count it (a word the vocabulary accepts but no driver reads is declarable-but-inert, ADR-0078). `'tenant'` and `'org'` are rejected, never aliased — the parse error names `'organization'` (§Terminology; PD #12: one contract, no dialects). The single-tenant story is D3's NULL-safe COALESCE bucket, NOT the retired 'composite degenerates to single-column' claim #5030 falsified.\n\nADR-0144 D2 — `CurrencyConfigSchema` has a third mode, `record`: the amount stays a bare number (ADR-0104 D1) and `currencyField` names the sibling field holding each record's ISO 4217 code. The config door (`refuseRecordModeKeyMismatch`) requires a non-empty `currencyField` under `record`, refuses it under `dynamic` / `fixed`, and refuses `defaultCurrency` under `record`. Each refused key would be declared but read by nothing (ADR-0078). Do NOT give `record` a `defaultCurrency` fallback or a platform-chosen code field name: per D8 rules 2 and 4, the code is the record's own, the field is the author's, and nothing fills either in. What the pointer NAMES is judged on the object (object.zod.ts), not here."
0 commit comments