Skip to content

Commit 5fa54ea

Browse files
committed
docs(adr): ADR-0144 review fixes — required pointers, scoped citations, ADR-0104 note moved clear of the retirement note
M2: both homeAmount pointers are required (D1 + D8 rule 2); only their spelling is the spec card's. S1/S2: the ADR-0104 note keeps only the multi-currency-keeps-D1 sentences, sits at the end of D1, and leaves the retirement note to its own card. S3: ADR-0112 scoped to runtime refusals. S4: ADR-0028/ADR-0029 cited for the rate table's owner. S5-S8: TL;DR, Metadata-protocol cards, D8 rule 4 reading, settings-guard wording. Claude-Session: https://claude.ai/code/session_01Utp6CbTZrFqRYNgjSaSbN5 Co-authored-by: Claude <noreply@anthropic.com>
1 parent ace180a commit 5fa54ea

2 files changed

Lines changed: 53 additions & 35 deletions

File tree

‎docs/adr/0104-field-runtime-value-shape-contract.md‎

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -170,18 +170,6 @@ either adopted by the contract and enforced, or deleted — it does not remain
170170
exported-but-dead. An exported-but-unconsumed value schema is exactly the
171171
inert metadata ADR-0078 forbids.
172172

173-
> **Note 2026-10-28 (#27183; [ADR-0144](./0144-multi-currency-record-currency-dated-rates-write-time-home-projection.md)).**
174-
> This paragraph's currency sentence needs two corrections.
175-
> First, the deletion it records has not happened yet. At `cdec7473`,
176-
> `CurrencyValueSchema` is still exported and marked `@deprecated`
177-
> (`packages/spec/src/data/field.zod.ts#CurrencyValueSchema`, its type
178-
> `#CurrencyValue`). The retirement is carried by #27182, which records its
179-
> landing here.
180-
> Second, multi-currency keeps this decision. ADR-0144 (ruling `6465431966`
181-
> on #27119) puts a record's currency in a sibling ISO 4217 code field that
182-
> the amount names through `currencyField`, never in the value, so a
183-
> `currency` value stays a bare number.
184-
185173
**Consumers converge on the contract** (each keeps its role, loses its private
186174
type lists):
187175

@@ -208,6 +196,14 @@ assertion that every MATRIX entry `parse`s under `valueSchemaFor` — so the
208196
contract and the executable oracle cannot drift apart, and a contract change
209197
that would break the wire fails a test instead of shipping silently.
210198

199+
> **Note 2026-10-28 (#27183; [ADR-0144](./0144-multi-currency-record-currency-dated-rates-write-time-home-projection.md)) — multi-currency keeps D1's currency value.**
200+
> ADR-0144 (ruling `6465431966` on #27119) puts a record's currency in a
201+
> sibling ISO 4217 code field that the amount names through `currencyField`,
202+
> never in the value. A `currency` value therefore stays a bare number, as
203+
> the "Reality wins" paragraph above decides. The `CurrencyValueSchema`
204+
> retirement that paragraph records belongs to #27182, whose own dated note
205+
> (PR #27233) goes under that paragraph.
206+
211207
### D2 — Typed action handlers: declared params become the enforced, typed handler input
212208

213209
Action params are already fields-lite (`ActionParamSchema` mirrors `FieldType`,

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

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,17 @@
99
in the summon chat. The ruling's own text is the authority this record executes; where the two differ, the ruling
1010
wins and this record is corrected.
1111
- **Builds on**: [ADR-0104](./0104-field-runtime-value-shape-contract.md) D1 (a `currency` value is a bare number,
12-
kept here), [ADR-0112](./0112-error-code-vocabulary-and-ledger.md) (the codes and envelope of every refusal this
13-
record names).
14-
- **Amends**: no decision. ADR-0104 gains a dated note under D1's "Reality wins" paragraph. The note points here and at
15-
#27182, the card that retires `CurrencyValueSchema`.
12+
kept here). [ADR-0112](./0112-error-code-vocabulary-and-ledger.md) governs the runtime refusals only: a blank record
13+
currency code and a missing rate ([Open item](#open-items) 9). It sets their codes and envelope on the wire, and any
14+
new code is registered in ADR-0112's extension ledger. The build-time refusals this record names are authoring
15+
diagnostics, not wire errors.
16+
[ADR-0029](./0029-kernel-object-ownership-and-platform-objects-decomposition.md) D1, D3 and D6 (a first-party
17+
capability owns its `sys_*` objects, one owner per object, and `platform-objects` is being decomposed) and
18+
[ADR-0028](./0028-metadata-naming-and-namespace-isolation.md) (Deferred; the `sys` namespace design it records)
19+
govern where `sys_exchange_rate` may live. #27185 settles its owner against them (D3).
20+
- **Amends**: no decision. ADR-0104 gains a dated note at the end of its D1 section: multi-currency keeps D1's
21+
bare-number value. For the `CurrencyValueSchema` retirement, the note points to #27182 (PR #27233), which carries its
22+
own note under D1's "Reality wins" paragraph.
1623
- **Evidence**: epic [#8345](https://github.com/objectstack-ai/objectstack/issues/8345), with its measurement round
1724
[`6041378776`](https://github.com/objectstack-ai/objectstack/issues/8345#issuecomment-6041378776) and the spec seat's
1825
shape verdict [`6041482631`](https://github.com/objectstack-ai/objectstack/issues/8345#issuecomment-6041482631);
@@ -30,8 +37,9 @@
3037
- `sys_exchange_rate { currency, rate, effective_date, source }` holds dated rates against the organization's home
3138
currency, which is `localization.currency`. In v1, rows are entered by hand or imported. A provider feed is an app flow
3239
that writes rows.
33-
- At write, the engine converts the amount at the document date's rate. It stores the result in a declared home field
34-
(`homeAmount`) together with the rate it used. A missing rate refuses the write. A corrected rate reaches stored
40+
- At write, the engine converts the amount at the document date's rate. The amount field's `homeAmount` declaration
41+
names the stored home field (`homeAmount.field`) that receives the result, and the author-declared field that stores
42+
the rate used. A missing rate refuses the write. A corrected rate reaches stored
3543
rows only through an explicit recompute.
3644
- A raw aggregate over a record-mode field refuses at build unless it groups by the code field. Consolidated totals
3745
read the stored home field.
@@ -88,7 +96,7 @@ aggregation. The ruling names them by topic ("Q0 A", "rates A", "home currency A
8896
`objectstack build` byte-stable, so a formula function cannot read a rate table.
8997
- **`CurrencyValueSchema` is still exported**, marked `@deprecated`
9098
(`packages/spec/src/data/field.zod.ts#CurrencyValueSchema`, its type `#CurrencyValue`), although ADR-0104 D1 says it
91-
"is deleted". #27182 retires it.
99+
"is deleted". #27182 retires it, in PR #27233.
92100
- **Today's numbers are silently wrong** (from the measurement round's probes over a real ObjectQL engine; the scratch
93101
files were run and deleted and are not re-run for this record). The probes model a record currency the way authors
94102
do today, as a sibling text field. `sum` over a USD row of 100 and an EUR row of 100 answers `200 "USD"`. A `summary`
@@ -159,13 +167,18 @@ pairing.
159167
- In v1, rows come from manual entry and import. Provider feeds come "by an app flow", which writes rows; the
160168
platform ships no feed.
161169
- A missing rate refuses (D5).
170+
- Which package owns the object is #27185's to settle against ADR-0029 D1, D3 and D6 and ADR-0028
171+
([Open item](#open-items) 11). Workload row M1-1 and the epic's territory name `packages/platform-objects/src`,
172+
which ADR-0029 D6 is decomposing.
162173

163174
### D4 — Q2: the home currency is `localization.currency`, locked once used (letter A)
164175

165176
- The organization's home currency is the existing `localization.currency` setting. There is no second currency
166177
setting.
167-
- It is locked "once a rate row or a home-amount declaration exists". M1-3 (#27198) builds the lock. `service-settings`
168-
has no write-side predicate hook today (workload row M1-3).
178+
- It is locked "once a rate row or a home-amount declaration exists". M1-3 (#27198) builds the lock.
179+
`service-settings` has no write-side guard today that can refuse a value change on engine state (workload row M1-3).
180+
`config-change-audit.ts` runs after the fact, and the write-path predicate `providesConfidentiality` concerns secret
181+
posture, not a value change.
169182

170183
### D5 — Q3: the conversion runs at write and is stored with its rate (letter A)
171184

@@ -176,9 +189,14 @@ pairing.
176189
counts as missing in the first place is [Open item](#open-items) 2.
177190
- A corrected rate reaches stored rows only through "an explicit recompute operation". Changing a rate row does not, on
178191
its own, rewrite stored home amounts.
179-
- The declaration key is `homeAmount`. The ruling: "the `homeAmount` key never ships ahead of the engine". Workload
180-
row M2-1 drafts its shape as `homeAmount: { field, rateDate, rateField? }`. #27191 fixes the shape under contract
181-
review; see [Open items](#open-items) 1 and 2.
192+
- The declaration key is `homeAmount`. The ruling: "the `homeAmount` key never ships ahead of the engine".
193+
`homeAmount` is the declaration on the amount field, and `homeAmount.field` names the stored home field.
194+
- **Whenever `homeAmount` is declared, both its document-date pointer and its rate pointer are required.** D1 stores
195+
the amount "with the rate used", and D8 rule 2 lets that rate live only in a field "declared by the app author and
196+
referenced by pointer". The conversion runs "from the document date's rate", and under the same rule the document
197+
date is an author-declared field named by a pointer, with no default for the engine to guess. Workload row M2-1 and
198+
#27191's card body draft the rate pointer as optional (`rateField?`). This record corrects that to required. Only the
199+
spelling of the two pointers is #27191's ([Open item](#open-items) 1).
182200

183201
### D6 — Q4: raw aggregates over a record-mode field refuse, and consolidation reads the home field (letter A)
184202

@@ -215,9 +233,10 @@ pairing.
215233
`skills/**`, "never a contract".
216234
3. **"Enabled currencies" are derived, not a setting.** They are the home currency, plus every currency with a rate
217235
row, plus the codes already used.
218-
4. **The form pre-fills the organization's currency at the renderer, and the stored value is always explicit.** Read
219-
plainly, the organization's currency reaches a new record only as a pre-filled value the user submits, and the
220-
server never fills in the code.
236+
4. **The form pre-fills the organization's currency at the renderer, and the stored value is always explicit.** This
237+
record reads the rule to mean that the organization's currency reaches a new record only as a pre-filled value the
238+
user submits, and that the server never fills in the code. #27186's refusal of a blank code (drafted as
239+
`ERR_CURRENCY_CODE_REQUIRED`, workload row M0-2) is the enforcement that makes the reading true.
221240
5. **Metadata never names the home currency.** "the home field is a `dynamic`-mode currency field", so it displays in
222241
`localization.currency`, as every `dynamic` field does.
223242

@@ -271,8 +290,8 @@ D8 already excludes a switch, injected columns, fixed field names and a setting
271290
## Consequences
272291

273292
- **The change is additive.** `record` is a new mode, `dynamic` and `fixed` keep their meaning and stored rows are not
274-
touched. An app that never writes `record` sees no change. The two spec cards that add keys (#27184, #27191) declare
275-
`Metadata-protocol: added`.
293+
touched. An app that never writes `record` sees no change. Three cards declare `Metadata-protocol: added`: the two that add
294+
authorable keys (#27184, #27191) and the seam card that adds the analytics wire key (#27187).
276295
- **Two steps cannot be undone once used** (design record §13). One is the home-currency lock. The other: an object
277296
with record-mode amounts and a home field returns to a single currency only through a data migration.
278297
- **Today's relabel hazard closes where the feature is used** (the measurement round's Q2 recommendation). Once the
@@ -289,12 +308,11 @@ D8 already excludes a switch, injected columns, fixed field names and a setting
289308
The ruling does not decide the items below. Each belongs to the card named, and is settled under that card's review.
290309
Where a source leans one way, the lean is quoted here, not adopted. This record is amended with each answer.
291310

292-
1. **The document-date pointer and the stored-rate pointer: spelling and whether they are required** (#27191). The
293-
ruling fixes that the rate is the document date's and that the rate used is stored. Workload row M2-1 drafts
294-
`homeAmount: { field, rateDate, rateField? }`. In that draft, `rateDate` is "a `date`/`datetime` field on the same
295-
object (⛔ no default)" and `rateField` is optional. The design record §6 says the document date must be declared
296-
explicitly and must not default to `created_at` (「⛔ 不默认 created_at」). An optional `rateField` sits against the
297-
ruling's "STORED with the rate used", and #27191 reconciles the two.
311+
1. **The spelling of the document-date pointer and the rate pointer** (#27191). Only the names are open. Both
312+
pointers are required whenever `homeAmount` is declared (D5, from D1 and D8 rule 2). The optional `rateField?` of
313+
workload row M2-1 and #27191 is corrected to required. Workload row M2-1 drafts the names `rateDate` and
314+
`rateField`; in that draft, `rateDate` is "a `date`/`datetime` field on the same object (⛔ no default)". The design
315+
record §6 says the document date must not default to `created_at` (「⛔ 不默认 created_at」).
298316
2. **Which row counts as "the document date's rate"** (#27192). The ruling says "a platform rate table by effective
299317
date". Workload row M2-2 and the measurement round read this as the row with the latest `effective_date` on or before
300318
the document date. The measurement round also rules out a "nearest-future rate". The design record §5 says
@@ -324,6 +342,10 @@ Where a source leans one way, the lean is quoted here, not adopted. This record
324342
10. **Which aggregates count as "raw"** (#27186, #27187). The sources list `sum`, `avg`, `min` and `max`; `count`
325343
reads no amount. The per-row `currency` on grouped analytics results (workload row M0-3) is the contract half of
326344
#27187.
345+
11. **The owner package of `sys_exchange_rate`** (#27185). Workload row M1-1 and the epic's territory name
346+
`packages/platform-objects/src`. ADR-0029 D1, D3 and D6 give a first-party capability ownership of its `sys_*`
347+
objects and decompose `platform-objects`, and ADR-0028 (Deferred) records the `sys` namespace design. #27185
348+
settles the owner against them.
327349

328350
## Verification notes
329351

0 commit comments

Comments
 (0)