Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/27182-currency-value-schema-retired.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@objectstack/spec': minor
---

feat(spec)!: `CurrencyValueSchema` and its `CurrencyValue` type are removed from `@objectstack/spec/data` (ADR-0104 D1)

Clause-②: no (narrowing)
Metadata-protocol: narrowed

<!-- adr-0087: registered currency-value-schema-retired -->

**BREAKING** for TypeScript code only: `import { CurrencyValueSchema } from '@objectstack/spec/data'` (or from `@objectstack/spec`), and `import type { CurrencyValue }`, no longer compile (TS2305). The schema declared a `{ value, currency }` object that nothing ever parsed or stored, and it contradicted the real contract: a `currency` field's value is a bare number in the record validator, the SQL driver, import coercion and the field-zoo oracle, and the currency code is field configuration. ADR-0104 had already decided the deletion; the export stayed `@deprecated` until now, and it goes before multi-currency work starts so that no author reads it as the shape that records a currency. No metadata document changes, and runtime behaviour is unchanged.

- `CurrencyValueSchema` / `CurrencyValue` (`{ value: 1000.5, currency: 'USD' }`) → none. Write and read the bare number (`1000.5`), and declare the code on the field: `currencyConfig: { currencyMode: 'fixed', defaultCurrency: 'USD' }`. To type or check a value, use `valueSchemaFor(field, 'stored')` from `@objectstack/spec/data`, which answers a number for a currency field.

The fix: delete the import and use `number` for the value. The upgrade guide entry is `currency-value-schema-retired`, and `data/CurrencyValue` is listed in `RETIRED_DEFS_BY_MAJOR[18]`.
4 changes: 2 additions & 2 deletions content/docs/protocol/objectql/types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,8 @@ is field configuration, never stored per value: under `currencyMode: fixed` it i
under `dynamic`, the default, it is the tenant default currency (the
`localization.currency` setting) and `defaultCurrency` is not the displayed
currency. The old per-value object shape (`CurrencyValueSchema`) was
never consumed by the validator, the driver, or import coercion and is
deprecated in the spec.
never consumed by the validator, the driver, or import coercion and has
been removed from the spec.
</Callout>

**Features:**
Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/data/field-value.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ Two canonical forms exist per field (ADR-0104 D1):

"Reality wins": where the deployed stored shape is coherent, the contract
adopts it — deployed data is a wire contract we don't get to rewrite by
editing Zod. This is why `currency` is a bare number (not the never-consumed
`CurrencyValueSchema` object) and `location` is `{lat, lng}` (what field-zoo
editing Zod. This is why `currency` is a bare number (the never-consumed
`CurrencyValueSchema` object is deleted) and `location` is `{lat, lng}` (what field-zoo
stores), not the never-consumed `{latitude, longitude}` shape.

Purity: schemas/constants/derivation only — no runtime logic, no caching
Expand Down
18 changes: 3 additions & 15 deletions content/docs/references/data/field.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Field schema — Data Protocol reference
navTitle: Field
description: "Field schemas of the ObjectStack Data Protocol: CurrencyConfig, CurrencyValue, Field and 7 more — each property with its type, default and a TypeScript example."
description: "Field schemas of the ObjectStack Data Protocol: CurrencyConfig, Field and 7 more — each property with its type, default and a TypeScript example."
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand All @@ -13,8 +13,8 @@ description: "Field schemas of the ObjectStack Data Protocol: CurrencyConfig, Cu
## TypeScript Usage

```typescript
import { CurrencyConfigSchema, CurrencyValueSchema, FieldSchema, FieldMaskingKeepSchema, FieldMaskingRuleSchema, FieldType, InlineGridColumnSchema, LocationCoordinatesSchema, SelectOptionSchema, UniqueScopeSchema } from '@objectstack/spec/data';
import type { CurrencyConfig, CurrencyValue, Field, FieldMaskingKeep, FieldMaskingRule, FieldType, InlineGridColumn, LocationCoordinates, SelectOption, UniqueScope } from '@objectstack/spec/data';
import { CurrencyConfigSchema, FieldSchema, FieldMaskingKeepSchema, FieldMaskingRuleSchema, FieldType, InlineGridColumnSchema, LocationCoordinatesSchema, SelectOptionSchema, UniqueScopeSchema } from '@objectstack/spec/data';
import type { CurrencyConfig, Field, FieldMaskingKeep, FieldMaskingRule, FieldType, InlineGridColumn, LocationCoordinates, SelectOption, UniqueScope } from '@objectstack/spec/data';

// Validate data
const result = CurrencyConfigSchema.parse(data);
Expand All @@ -32,18 +32,6 @@ const result = CurrencyConfigSchema.parse(data);
| **defaultCurrency** | `string` | optional | 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. |


---

## CurrencyValue

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **value** | `number` | ✅ | Monetary amount |
| **currency** | `string` | ✅ | Currency code (ISO 4217) |


---

## Field
Expand Down
10 changes: 5 additions & 5 deletions content/docs/references/index.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Protocol reference — every schema by module
navTitle: Protocol Reference
description: Every schema published by @objectstack/spec — 1560 schemas across 14 protocol modules
description: Every schema published by @objectstack/spec — 1559 schemas across 14 protocol modules
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand All @@ -23,7 +23,7 @@ counts are sums of the rows they head. Regenerate with
| [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. |
| [API Protocol](/docs/references/api) | 32 | 440 | REST contracts, endpoints, routing, realtime, batch, discovery. |
| [Automation Protocol](/docs/references/automation) | 15 | 77 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
| [Data Protocol](/docs/references/data) | 31 | 185 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
| [Data Protocol](/docs/references/data) | 31 | 184 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
| [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. |
| [Integration Protocol](/docs/references/integration) | 1 | 12 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. |
| [Kernel Protocol](/docs/references/kernel) | 29 | 151 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. |
Expand All @@ -34,7 +34,7 @@ counts are sums of the rows they head. Regenerate with
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
| [System Protocol](/docs/references/system) | 37 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
| [UI Protocol](/docs/references/ui) | 16 | 172 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
| **Total** | **202** | **1560** | 14 protocol modules |
| **Total** | **202** | **1559** | 14 protocol modules |

---

Expand Down Expand Up @@ -132,7 +132,7 @@ Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execu

## Data Protocol

**Source:** `packages/spec/src/data/` · **Import:** `@objectstack/spec/data` · **31 pages, 185 schemas**
**Source:** `packages/spec/src/data/` · **Import:** `@objectstack/spec/data` · **31 pages, 184 schemas**

Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer.

Expand All @@ -156,7 +156,7 @@ Objects, fields, queries, filters, datasources and drivers — the ObjectQL laye
| [`driver/turso.zod.ts`](/docs/references/data/driver-turso) | `TursoConfig`, `TursoTransportMode` |
| [`external-catalog.zod.ts`](/docs/references/data/external-catalog) | `ExternalCatalog`, `ExternalColumn`, `ExternalTable` |
| [`feed.zod.ts`](/docs/references/data/feed) | `FeedFilterMode`, `FeedItemType` |
| [`field.zod.ts`](/docs/references/data/field) | `CurrencyConfig`, `CurrencyValue`, `Field`, `FieldMaskingKeep`, `FieldMaskingRule`, `FieldType`, `InlineGridColumn`, `LocationCoordinates`, `SelectOption`, `UniqueScope` |
| [`field.zod.ts`](/docs/references/data/field) | `CurrencyConfig`, `Field`, `FieldMaskingKeep`, `FieldMaskingRule`, `FieldType`, `InlineGridColumn`, `LocationCoordinates`, `SelectOption`, `UniqueScope` |
| [`field-value.zod.ts`](/docs/references/data/field-value) | `Address`, `AddressValue`, `CalendarDateValue`, `ClockTimeValue`, `FileLikeValue`, `FileReferenceIdValue`, `FileRefusedValue`, `FileValue`, `InstantValue`, `LocationValue`, `ReferenceIdValue` |
| [`filter.zod.ts`](/docs/references/data/filter) | `ComparisonOperator`, `EqualityOperator`, `FieldOperators`, `FieldReference`, `FilterArray`, `FilterCondition`, `NormalizedFilter`, `NowAnchor`, `QueryFilter`, `RangeOperator`, `SetOperator`, `SpecialOperator`, `StringOperator` |
| [`hook.zod.ts`](/docs/references/data/hook) | `Hook`, `HookContext`, `HookEvent`, `HookRefusal` |
Expand Down
23 changes: 20 additions & 3 deletions docs/adr/0104-field-runtime-value-shape-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ lists in three packages, or silently corrupting data.
The spec does export three per-type value schemas — and they make things
worse, not better, because **all three are dead and two contradict reality**:

- `CurrencyValueSchema` (`packages/spec/src/data/field.zod.ts#CurrencyValueSchema`) declares `{value, currency}`;
- `CurrencyValueSchema` (in `data/field.zod.ts`; deleted 2026-10-28, see the note under "Reality wins") declares `{value, currency}`;
the validator, the SQL driver, import-coerce, and the field-zoo round-trip
all treat currency as a **bare number**.
- `LocationCoordinatesSchema` (`packages/spec/src/data/field.zod.ts#LocationCoordinatesSchema`) declares
Expand Down Expand Up @@ -163,13 +163,30 @@ Directive #2. It exports:
**Reality wins.** Where the de-facto stored shape is coherent, the contract
adopts it — deployed data is a wire contract we don't get to rewrite by
editing Zod. Concretely: `currency` **is a scalar number** —
`CurrencyValueSchema` is deleted (tombstoned in `UNKNOWN_KEY_GUIDANCE` /
changelog with the FROM → TO note); `location` adopts the stored `{lat, lng}`
`CurrencyValueSchema` is deleted (registered in `RETIRED_DEFS_BY_MAJOR` with a
D3 semantic entry and the changelog carrying the FROM → TO note — see the
2026-10-28 note below); `location` adopts the stored `{lat, lng}`
and `LocationCoordinatesSchema` is rewritten to match; `AddressSchema` is
either adopted by the contract and enforced, or deleted — it does not remain
exported-but-dead. An exported-but-unconsumed value schema is exactly the
inert metadata ADR-0078 forbids.

> **Note (2026-10-28, #27182, PR #27233) — `CurrencyValueSchema` is deleted.**
> The deletion this paragraph decided had not happened: through 18.2.0 the
> schema and its `CurrencyValue` type stayed exported from
> `@objectstack/spec/data`, `@deprecated`, with zero readers. They are now
> removed, ahead of the multi-currency epic #8345 (ruling 6465431966 on
> #27119), so that no author reads `{value, currency}` as the shape that
> records a currency. The schema had no carrier key, so there is no
> `retiredKey()` tombstone and no guidance-map entry: the
> FROM → TO note (a currency value is a bare number; the code is
> `currencyConfig.defaultCurrency`) is carried by `RETIRED_DEFS_BY_MAJOR[18]`
> (`data/CurrencyValue`, entry file
> `packages/spec/src/migrations/entries/retired-defs/18.data__CurrencyValue.ts`),
> the D3 semantic entry `currency-value-schema-retired` (entry file
> `packages/spec/src/migrations/entries/semantic/18.currency-value-schema-retired.ts`)
> and the changelog.

**Consumers converge on the contract** (each keeps its role, loses its private
type lists):

Expand Down
14 changes: 7 additions & 7 deletions docs/audits/2026-07-unknown-key-strictness-ledger.counts/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The `strict` column is the one the campaign schedules against; it counts both th

| Dir | Sites | strict | passthrough | catchall | strip |
|---|---|---|---|---|---|
| `data/` | 165 | 88 | 2 | 0 | 75 |
| `data/` | 164 | 88 | 2 | 0 | 74 |

## `data/` — sites

Expand All @@ -45,7 +45,7 @@ classify and is not listed (it becomes reportable the day it grows its first sit
| `driver/turso.zod.ts` | 2 |
| `external-catalog.zod.ts` | 4 |
| `field-value.zod.ts` | 4 |
| `field.zod.ts` | 14 |
| `field.zod.ts` | 13 |
| `filter.zod.ts` | 13 |
| `hook-body.zod.ts` | 2 |
| `hook.zod.ts` | 8 |
Expand All @@ -57,15 +57,15 @@ classify and is not listed (it becomes reportable the day it grows its first sit
| `seed-loader.zod.ts` | 12 |
| `seed.zod.ts` | 2 |
| `validation.zod.ts` | 6 |
| **total** | **165** |
| **total** | **164** |

## `data/` — open

Per file, how many of its sites still silently discard unknown keys. The `Class`
column that decides the bucket split is hand-written in the ledger; the arithmetic
over it is here.

**75 strip of 165**, in 10 file(s).
**74 strip of 164**, in 10 file(s).

| File | Strip | Sites |
|---|---|---|
Expand All @@ -74,18 +74,18 @@ over it is here.
| `driver-sql.zod.ts` | 2 | 2 |
| `driver.zod.ts` | 9 | 9 |
| `external-catalog.zod.ts` | 4 | 4 |
| `field.zod.ts` | 2 | 14 |
| `field.zod.ts` | 1 | 13 |
| `filter.zod.ts` | 11 | 13 |
| `hook.zod.ts` | 5 | 8 |
| `query.zod.ts` | 4 | 5 |
| `seed-loader.zod.ts` | 12 | 12 |
| **total** | **75** | **165** |
| **total** | **74** | **164** |

| Bucket | Sites |
|---|---|
| authorable — the ruling's forced scope | 0 |
| unresolved — needs a per-schema verdict | 0 |
| wire / open — out of forced scope | 73 |
| no door — no carrier, ADR-0049 territory | 2 |
| no door — no carrier, ADR-0049 territory | 1 |
| no gate — carrier live, no parse | 0 |
| covered — no carrier, no parse, guarded at every consumer | 0 |
4 changes: 2 additions & 2 deletions docs/audits/2026-07-unknown-key-strictness-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -1263,7 +1263,7 @@ triage row record which one was taken.
| `query.zod.ts` | open | ~~⚠️ classification conflict — see #4721~~ **RESOLVED (11:41Z ruling, closed by #4721).** The conflict was real and the answer was that per-FILE classification was the imprecise instrument: `SortNodeSchema` was carved out as `authorable` and closed (`strictObject` + `aliases: { direction: 'order' }`), the other 4 sites keep `open`. Those 4 are the dialect proper — `BaseQuerySchema`, `AggregationNodeSchema`, `FullTextSearchSchema`, `GroupByNodeSchema`'s object arm — and `BaseQuerySchema`'s own top-level strictness is #4001's to schedule, deliberately **not** taken by #4721 |
| `external-catalog.zod.ts` | wire (p) | **out of scope** |
| `hook.zod.ts` | wire | **out of scope** — `HookContextSchema` + `.session`/`.provenance`/`.user` are the runtime shape handed to a handler; verified in the data step |
| `field.zod.ts` | no door | ⛔ **not strictness work — re-verdicted 2026-08-13 (#4001 data batch).** This row's own instruction was "check whether they are record data (→ open) before closing", and the measured answer is the THIRD one: neither authorable nor open. Both remaining strip sites (`LocationCoordinatesSchema`, `CurrencyValueSchema`) are `@deprecated` DEAD EXPORTS that contradict the enforced value contract — `currency` stores a BARE NUMBER everywhere (validator, SQL driver `float` column, import coercion, field-zoo oracle), `location` stores `{lat, lng}` not `{latitude, longitude}`. Carrier: absent — no schema in the tree references either, so unreachability from every authoring root holds by construction and no BFS is needed (the only non-test references are two `type-alias-convention.pin.test.ts` rows). Parse: absent outside their own `field.test.ts` cases. Consumers' vocabulary: absent — zero references in objectui; `field-value.test.ts` pins from the other direction that the enforced contract REJECTS the retired `CurrencyValueSchema` object shape. The ADR-0049 answer this class prescribes already exists: ADR-0104's "Reality wins" section decides both removals ("an exported-but-unconsumed value schema is exactly the inert metadata ADR-0078 forbids"), the JSDoc deprecations are on `main` with "Removal rides the next spec major", and the removal is tracked at **#8562** — this row points there, never at a batch. Closing them instead would be #4583's precisely-validated dead slot, and worse than most instances of it: a `strictObject` `surface` name plus did-you-mean suggestions on a shape authors must NOT use is an invitation dressed as enforcement, on the exact spelling (`{value, currency}` / `{latitude, longitude}`) the real contract rejects. The third named shape the old row carried, `Address`, was never this file's site — `AddressSchema` is DECLARED in `field-value.zod.ts` since #7127 and only re-exported here |
| `field.zod.ts` | no door | ⛔ **not strictness work — re-verdicted 2026-08-13 (#4001 data batch).** *(2026-10-28, #27182: `CurrencyValueSchema` and its `CurrencyValue` type are DELETED — registered as `RETIRED_DEFS_BY_MAJOR[18]` `data/CurrencyValue` with the D3 entry `currency-value-schema-retired`, ADR-0104 D1 carried out ahead of the multi-currency epic #8345. One strip site remains, `LocationCoordinatesSchema`, and the verdict below stands for it.)* This row's own instruction was "check whether they are record data (→ open) before closing", and the measured answer is the THIRD one: neither authorable nor open. Both remaining strip sites (`LocationCoordinatesSchema`, `CurrencyValueSchema`) are `@deprecated` DEAD EXPORTS that contradict the enforced value contract — `currency` stores a BARE NUMBER everywhere (validator, SQL driver `float` column, import coercion, field-zoo oracle), `location` stores `{lat, lng}` not `{latitude, longitude}`. Carrier: absent — no schema in the tree references either, so unreachability from every authoring root holds by construction and no BFS is needed (the only non-test references are two `type-alias-convention.pin.test.ts` rows). Parse: absent outside their own `field.test.ts` cases. Consumers' vocabulary: absent — zero references in objectui; `field-value.test.ts` pins from the other direction that the enforced contract REJECTS the retired `CurrencyValueSchema` object shape. The ADR-0049 answer this class prescribes already exists: ADR-0104's "Reality wins" section decides both removals ("an exported-but-unconsumed value schema is exactly the inert metadata ADR-0078 forbids"), the JSDoc deprecations are on `main` with "Removal rides the next spec major", and the removal is tracked at **#8562** — this row points there, never at a batch. Closing them instead would be #4583's precisely-validated dead slot, and worse than most instances of it: a `strictObject` `surface` name plus did-you-mean suggestions on a shape authors must NOT use is an invitation dressed as enforcement, on the exact spelling (`{value, currency}` / `{latitude, longitude}`) the real contract rejects. The third named shape the old row carried, `Address`, was never this file's site — `AddressSchema` is DECLARED in `field-value.zod.ts` since #7127 and only re-exported here |
| `driver-sql.zod.ts` | wire | **out of scope** |

**Authorable strip in `data/`:**
Expand Down Expand Up @@ -1807,7 +1807,7 @@ from an abandoned one.
121 non-authorable in 21 rows — #13802 closed `field-value.zod.ts`'s two `open` sites by
maintainer ruling; the counts file carries the live number.)* The `no door`
ones carry the only follow-up in the set, and it is a different ratchet:
ADR-0049 removal, tracked at #8562 for `field.zod.ts`'s two.
ADR-0049 removal, tracked at #8562 for `field.zod.ts`'s two *(2026-10-28: one, after #27182 deleted `CurrencyValueSchema`)*.
- **The nine untriaged directories.** They were never in the ruling's forced
scope and are classified coarsely; a future campaign that wants them starts by
giving them per-file rows, at which point this gate begins holding them too.
Expand Down
2 changes: 0 additions & 2 deletions packages/spec/api-surface/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@
"CurrencyConfig (type)",
"CurrencyConfigParsed (type)",
"CurrencyConfigSchema (const)",
"CurrencyValue (type)",
"CurrencyValueSchema (const)",
"CustomPersistenceConfig (type)",
"CustomPersistenceConfigSchema (const)",
"DATABASE_DRIVER_SELECTION_ALIASES (const)",
Expand Down
Loading
Loading