Skip to content

Commit 2537aff

Browse files
committed
chore(spec): regenerate artifacts and declare the record-mode refinement in the dropped-refinements ledger
Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f Co-authored-by: Claude <noreply@anthropic.com>
1 parent 59ac9e8 commit 2537aff

7 files changed

Lines changed: 54 additions & 11 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec): `currencyMode: 'record'` + `currencyField` — a record-mode amount points at a sibling ISO 4217 code field (ADR-0144 D2)
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
A currency amount (or a `formula` with `returnType: 'currency'`) can now carry a currency per record. `currencyConfig.currencyMode` gains a third value, `record`, and `currencyConfig.currencyField` names the field on the same object that holds each record's ISO 4217 code. The amount itself stays a bare number (ADR-0104 D1).
11+
12+
```ts
13+
fields: {
14+
currency_code: Field.text({ label: 'Currency', valueDomain: 'iso_4217_currency' }),
15+
amount: Field.currency({ label: 'Amount', currencyConfig: { currencyMode: 'record', currencyField: 'currency_code' } }),
16+
}
17+
```
18+
19+
- `currencyField` is required under `record` and refused under `dynamic` and `fixed`.
20+
- 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.
21+
- `dynamic` and `fixed` configs parse exactly as before; an app that never writes `record` sees no change.
22+
23+
⛔ Not released alone: this ships in the same release as the record-mode aggregate refusals and the per-row display.

‎content/docs/references/data/field.mdx‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ const result = CurrencyConfigSchema.parse(data);
2828

2929
| Property | Type | Required | Description |
3030
| :--- | :--- | :--- | :--- |
31-
| **currencyMode** | `Enum<'dynamic' \| 'fixed'>` | optional (default: `"dynamic"`) | 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. Neither mode is a per-record choice: the value is a bare number either way. |
32-
| **defaultCurrency** | `string` | optional (default: `"CNY"`) | The currency code (ISO 4217, e.g. USD, CNY, EUR) of a `fixed`-mode field: its one currency. Not read under `dynamic` (the default), where amounts display in the tenant default currency. |
31+
| **currencyMode** | `Enum<'dynamic' \| 'fixed' \| 'record'>` | optional (default: `"dynamic"`) | 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). |
32+
| **defaultCurrency** | `string` | optional (default: `"CNY"`) | The currency code (ISO 4217, e.g. USD, CNY, EUR) of a `fixed`-mode field: its one currency. Not read under `dynamic` (the default), where amounts display in the tenant default currency, nor under `record`, where each record names its own. |
33+
| **currencyField** | `string` | optional | `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`. |
3334

3435

3536
---
@@ -101,7 +102,7 @@ const result = CurrencyConfigSchema.parse(data);
101102
| **summaryOperations** | `{ object: string; field: string; function: Enum<'count' \| 'sum' \| 'min' \| 'max' \| 'avg'>; relationshipField?: string; … }` | optional | Roll-up summary definition. The engine recomputes the value when child records are inserted/updated/deleted. |
102103
| **language** | `string` | optional | Programming language for syntax highlighting (e.g., javascript, python, sql) |
103104
| **step** | `number` | optional | Step increment for slider (default: 1) |
104-
| **currencyConfig** | `{ currencyMode?: Enum<'dynamic' \| 'fixed'>; defaultCurrency?: string }` | optional | Which currency an amount is in: for a currency field, and for a formula field whose returnType is currency. Absent: dynamic mode. |
105+
| **currencyConfig** | `{ currencyMode?: Enum<'dynamic' \| 'fixed' \| 'record'>; defaultCurrency?: string; currencyField?: string }` | optional | Which currency an amount is in: for a currency field, and for a formula field whose returnType is currency. Absent: dynamic mode. |
105106
| **dimensions** | `integer` | optional | Vector dimensionality (e.g., 1536 for OpenAI embeddings) |
106107
| **trackHistory** | `boolean` | optional | Render this field's value changes as human-readable entries on the record activity timeline (ADR-0052 §5b). Opt-in per field. |
107108
| **group** | `string` | optional | Field group name for organizing fields in forms and layouts (e.g., "contact_info", "billing", "system") |
@@ -248,8 +249,9 @@ const result = CurrencyConfigSchema.parse(data);
248249

249250
| Property | Type | Required | Description |
250251
| :--- | :--- | :--- | :--- |
251-
| **currencyMode** | `Enum<'dynamic' \| 'fixed'>` | optional (default: `"dynamic"`) | 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. Neither mode is a per-record choice: the value is a bare number either way. |
252-
| **defaultCurrency** | `string` | optional (default: `"CNY"`) | The currency code (ISO 4217, e.g. USD, CNY, EUR) of a `fixed`-mode field: its one currency. Not read under `dynamic` (the default), where amounts display in the tenant default currency. |
252+
| **currencyMode** | `Enum<'dynamic' \| 'fixed' \| 'record'>` | optional (default: `"dynamic"`) | 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). |
253+
| **defaultCurrency** | `string` | optional (default: `"CNY"`) | The currency code (ISO 4217, e.g. USD, CNY, EUR) of a `fixed`-mode field: its one currency. Not read under `dynamic` (the default), where amounts display in the tenant default currency, nor under `record`, where each record names its own. |
254+
| **currencyField** | `string` | optional | `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`. |
253255

254256
### Nested Shape: `Field.conditionalFormatting[number]`
255257

‎content/docs/references/data/object.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ const result = ApiMethod.parse(data);
267267
| **summaryOperations** | `{ object: string; field: string; function: Enum<'count' \| 'sum' \| 'min' \| 'max' \| 'avg'>; relationshipField?: string; … }` | optional | Roll-up summary definition. The engine recomputes the value when child records are inserted/updated/deleted. |
268268
| **language** | `string` | optional | Programming language for syntax highlighting (e.g., javascript, python, sql) |
269269
| **step** | `number` | optional | Step increment for slider (default: 1) |
270-
| **currencyConfig** | `{ currencyMode?: Enum<'dynamic' \| 'fixed'>; defaultCurrency?: string }` | optional | Which currency an amount is in: for a currency field, and for a formula field whose returnType is currency. Absent: dynamic mode. |
270+
| **currencyConfig** | `{ currencyMode?: Enum<'dynamic' \| 'fixed' \| 'record'>; defaultCurrency?: string; currencyField?: string }` | optional | Which currency an amount is in: for a currency field, and for a formula field whose returnType is currency. Absent: dynamic mode. |
271271
| **dimensions** | `integer` | optional | Vector dimensionality (e.g., 1536 for OpenAI embeddings) |
272272
| **trackHistory** | `boolean` | optional | Render this field's value changes as human-readable entries on the record activity timeline (ADR-0052 §5b). Opt-in per field. |
273273
| **group** | `string` | optional | Field group name for organizing fields in forms and layouts (e.g., "contact_info", "billing", "system") |
@@ -629,7 +629,7 @@ const result = ApiMethod.parse(data);
629629
| **summaryOperations** | `{ object: string; field: string; function: Enum<'count' \| 'sum' \| 'min' \| 'max' \| 'avg'>; relationshipField?: string; … }` | optional | Roll-up summary definition. The engine recomputes the value when child records are inserted/updated/deleted. |
630630
| **language** | `string` | optional | Programming language for syntax highlighting (e.g., javascript, python, sql) |
631631
| **step** | `number` | optional | Step increment for slider (default: 1) |
632-
| **currencyConfig** | `{ currencyMode?: Enum<'dynamic' \| 'fixed'>; defaultCurrency?: string }` | optional | Which currency an amount is in: for a currency field, and for a formula field whose returnType is currency. Absent: dynamic mode. |
632+
| **currencyConfig** | `{ currencyMode?: Enum<'dynamic' \| 'fixed' \| 'record'>; defaultCurrency?: string; currencyField?: string }` | optional | Which currency an amount is in: for a currency field, and for a formula field whose returnType is currency. Absent: dynamic mode. |
633633
| **dimensions** | `integer` | optional | Vector dimensionality (e.g., 1536 for OpenAI embeddings) |
634634
| **trackHistory** | `boolean` | optional | Render this field's value changes as human-readable entries on the record activity timeline (ADR-0052 §5b). Opt-in per field. |
635635
| **group** | `string` | optional | Field group name for organizing fields in forms and layouts (e.g., "contact_info", "billing", "system") |

‎content/docs/references/system/migration.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Add a new field to an existing object
102102
| **summaryOperations** | `{ object: string; field: string; function: Enum<'count' \| 'sum' \| 'min' \| 'max' \| 'avg'>; relationshipField?: string; … }` | optional | Roll-up summary definition. The engine recomputes the value when child records are inserted/updated/deleted. |
103103
| **language** | `string` | optional | Programming language for syntax highlighting (e.g., javascript, python, sql) |
104104
| **step** | `number` | optional | Step increment for slider (default: 1) |
105-
| **currencyConfig** | `{ currencyMode?: Enum<'dynamic' \| 'fixed'>; defaultCurrency?: string }` | optional | Which currency an amount is in: for a currency field, and for a formula field whose returnType is currency. Absent: dynamic mode. |
105+
| **currencyConfig** | `{ currencyMode?: Enum<'dynamic' \| 'fixed' \| 'record'>; defaultCurrency?: string; currencyField?: string }` | optional | Which currency an amount is in: for a currency field, and for a formula field whose returnType is currency. Absent: dynamic mode. |
106106
| **dimensions** | `integer` | optional | Vector dimensionality (e.g., 1536 for OpenAI embeddings) |
107107
| **trackHistory** | `boolean` | optional | Render this field's value changes as human-readable entries on the record activity timeline (ADR-0052 §5b). Opt-in per field. |
108108
| **group** | `string` | optional | Field group name for organizing fields in forms and layouts (e.g., "contact_info", "billing", "system") |
@@ -532,7 +532,7 @@ Add a new field to an existing object
532532
| **summaryOperations** | `{ object: string; field: string; function: Enum<'count' \| 'sum' \| 'min' \| 'max' \| 'avg'>; relationshipField?: string; … }` | optional | Roll-up summary definition. The engine recomputes the value when child records are inserted/updated/deleted. |
533533
| **language** | `string` | optional | Programming language for syntax highlighting (e.g., javascript, python, sql) |
534534
| **step** | `number` | optional | Step increment for slider (default: 1) |
535-
| **currencyConfig** | `{ currencyMode?: Enum<'dynamic' \| 'fixed'>; defaultCurrency?: string }` | optional | Which currency an amount is in: for a currency field, and for a formula field whose returnType is currency. Absent: dynamic mode. |
535+
| **currencyConfig** | `{ currencyMode?: Enum<'dynamic' \| 'fixed' \| 'record'>; defaultCurrency?: string; currencyField?: string }` | optional | Which currency an amount is in: for a currency field, and for a formula field whose returnType is currency. Absent: dynamic mode. |
536536
| **dimensions** | `integer` | optional | Vector dimensionality (e.g., 1536 for OpenAI embeddings) |
537537
| **trackHistory** | `boolean` | optional | Render this field's value changes as human-readable entries on the record activity timeline (ADR-0052 §5b). Opt-in per field. |
538538
| **group** | `string` | optional | Field group name for organizing fields in forms and layouts (e.g., "contact_info", "billing", "system") |

‎packages/spec/authorable-surface/data.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
"data/Cube:sql",
102102
"data/Cube:title",
103103
"data/CubeJoin:name",
104+
"data/CurrencyConfig:currencyField",
104105
"data/CurrencyConfig:currencyMode",
105106
"data/CurrencyConfig:defaultCurrency",
106107
"data/CurrencyValue:currency",

‎packages/spec/dropped-refinements.baseline.json‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
"manifest.objects.element.defaultRowScope",
106106
"manifest.objects.element.fieldGroups",
107107
"manifest.objects.element.fields.out.valueType",
108+
"manifest.objects.element.fields.out.valueType.currencyConfig",
108109
"manifest.objects.element.fields.out.valueType.inlineColumns.element",
109110
"manifest.objects.element.lifecycle",
110111
"manifest.objects.element.membershipKeys",
@@ -234,6 +235,7 @@
234235
"data.options[1].manifest.objects.element.defaultRowScope",
235236
"data.options[1].manifest.objects.element.fieldGroups",
236237
"data.options[1].manifest.objects.element.fields.out.valueType",
238+
"data.options[1].manifest.objects.element.fields.out.valueType.currencyConfig",
237239
"data.options[1].manifest.objects.element.fields.out.valueType.inlineColumns.element",
238240
"data.options[1].manifest.objects.element.lifecycle",
239241
"data.options[1].manifest.objects.element.membershipKeys",
@@ -362,6 +364,7 @@
362364
"options[1].manifest.objects.element.defaultRowScope",
363365
"options[1].manifest.objects.element.fieldGroups",
364366
"options[1].manifest.objects.element.fields.out.valueType",
367+
"options[1].manifest.objects.element.fields.out.valueType.currencyConfig",
365368
"options[1].manifest.objects.element.fields.out.valueType.inlineColumns.element",
366369
"options[1].manifest.objects.element.lifecycle",
367370
"options[1].manifest.objects.element.membershipKeys",
@@ -433,6 +436,7 @@
433436
"data.packages.element.options[1].manifest.objects.element.defaultRowScope",
434437
"data.packages.element.options[1].manifest.objects.element.fieldGroups",
435438
"data.packages.element.options[1].manifest.objects.element.fields.out.valueType",
439+
"data.packages.element.options[1].manifest.objects.element.fields.out.valueType.currencyConfig",
436440
"data.packages.element.options[1].manifest.objects.element.fields.out.valueType.inlineColumns.element",
437441
"data.packages.element.options[1].manifest.objects.element.lifecycle",
438442
"data.packages.element.options[1].manifest.objects.element.membershipKeys",
@@ -485,6 +489,7 @@
485489
"data.defaultRowScope",
486490
"data.fieldGroups",
487491
"data.fields.out.valueType",
492+
"data.fields.out.valueType.currencyConfig",
488493
"data.fields.out.valueType.inlineColumns.element",
489494
"data.fields.out.valueType.relatedListFilter.lazy",
490495
"data.lifecycle",
@@ -756,6 +761,11 @@
756761
"measures.valueType"
757762
]
758763
},
764+
"data/CurrencyConfig": {
765+
"sites": [
766+
""
767+
]
768+
},
759769
"data/DataEngineAggregateOptions": {
760770
"sites": [
761771
"filter.options[1].lazy"
@@ -875,6 +885,7 @@
875885
"data/Field": {
876886
"sites": [
877887
"",
888+
"currencyConfig",
878889
"inlineColumns.element",
879890
"relatedListFilter.lazy"
880891
]
@@ -977,6 +988,7 @@
977988
"defaultRowScope",
978989
"fieldGroups",
979990
"fields.out.valueType",
991+
"fields.out.valueType.currencyConfig",
980992
"fields.out.valueType.inlineColumns.element",
981993
"fields.out.valueType.relatedListFilter.lazy",
982994
"lifecycle",
@@ -996,6 +1008,7 @@
9961008
"sites": [
9971009
"",
9981010
"fields.valueType",
1011+
"fields.valueType.currencyConfig",
9991012
"fields.valueType.inlineColumns.element",
10001013
"fields.valueType.relatedListFilter.lazy"
10011014
]
@@ -1276,6 +1289,7 @@
12761289
"system/AddFieldOperation": {
12771290
"sites": [
12781291
"field",
1292+
"field.currencyConfig",
12791293
"field.inlineColumns.element",
12801294
"field.relatedListFilter.lazy"
12811295
]
@@ -1298,6 +1312,7 @@
12981312
"system/ChangeSet": {
12991313
"sites": [
13001314
"operations.element.options[0].field",
1315+
"operations.element.options[0].field.currencyConfig",
13011316
"operations.element.options[0].field.inlineColumns.element",
13021317
"operations.element.options[0].field.relatedListFilter.lazy",
13031318
"operations.element.options[3].object",
@@ -1331,6 +1346,7 @@
13311346
"object.defaultRowScope",
13321347
"object.fieldGroups",
13331348
"object.fields.out.valueType",
1349+
"object.fields.out.valueType.currencyConfig",
13341350
"object.fields.out.valueType.inlineColumns.element",
13351351
"object.fields.out.valueType.relatedListFilter.lazy",
13361352
"object.lifecycle",
@@ -1370,6 +1386,7 @@
13701386
"system/MigrationOperation": {
13711387
"sites": [
13721388
"options[0].field",
1389+
"options[0].field.currencyConfig",
13731390
"options[0].field.inlineColumns.element",
13741391
"options[0].field.relatedListFilter.lazy",
13751392
"options[3].object",

‎packages/spec/src/data/currency-config-record-mode.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,12 @@ describe('the object judges what `currencyField` names', () => {
115115

116116
it('refuses a pointer at a field that is not `text`', () => {
117117
const issues = objectIssues(invoice({
118-
currency_code: { type: 'select', label: 'Currency', options: [{ label: 'USD', value: 'USD' }] },
118+
currency_code: { type: 'number', label: 'Currency' },
119119
amount: { type: 'currency', label: 'Amount', currencyConfig: RECORD },
120120
}));
121121
expect(issues).toHaveLength(1);
122122
expect(issues[0]!.path).toEqual(['fields', 'amount', 'currencyConfig', 'currencyField']);
123-
expect(issues[0]!.message).toMatch(/^`amount\.currencyConfig\.currencyField` names `currency_code`, a `select` field/);
123+
expect(issues[0]!.message).toMatch(/^`amount\.currencyConfig\.currencyField` names `currency_code`, a `number` field/);
124124
});
125125

126126
it('refuses a pointer at a `text` field without the ISO 4217 value domain', () => {

0 commit comments

Comments
 (0)