Skip to content

docs(data-modeling): currency is a bare number in both modes; defaultCurrency is the column currency only under fixed - #20120

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20090-currency-mode-docs
Sep 25, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20090-currency-mode-docs

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20090

Clause-②: no

Docs half of the card: the two content/docs/data-modeling pages triage named as the declared cross-domain surface (the card's item 1 and the addendum). The skills/** half lands separately on claude/issue-20090-currency-mode-teaching (Tier H, awaits human approval); #20090 stays open until both have landed and the seat closes it. Not a governed surface; no meta.json change (both pages already exist).

What changed

Text only, re-derived from the spec (packages/spec/src/data/field-value.zod.ts: currency is in NUMERIC_VALUE_TYPES, "currency IS a bare number"; field.zod.ts: currencyMode 'dynamic' | 'fixed' default 'dynamic', defaultCurrency default 'CNY', and the guidance "a fixed currency is declared as currencyConfig: { currencyMode: 'fixed', defaultCurrency: … } … A field without one uses the tenant default at runtime"; the tenant default's producer is the localization.currency setting in packages/services/service-settings/src/manifests/localization.manifest.ts).

content/docs/data-modeling/field-types.mdx (676 → 677 lines)

  • The currency section's lead sentence now says the stored value is a bare number in both modes and the currency code lives in currencyConfig, never in the value.
  • currencyConfig.currencyMode row: "Whether currency is per-record or fixed" → "Whether the column is pinned to defaultCurrency (fixed) or displays in the tenant default currency, the localization.currency setting (dynamic); the value is a bare number either way".
  • currencyConfig.defaultCurrency row: "Default currency code" → "The column's currency code under fixed; not the displayed currency under dynamic" (the row's own default is 'CNY', so the old description read as "every dynamic column shows CNY").

content/docs/data-modeling/fields.mdx (459 → 465 lines)

  • The "Currency Configuration" example authored currencyMode: 'dynamic', defaultCurrency: 'USD' and never said USD is not the displayed currency under dynamic. It now authors 'fixed', so the defaultCurrency it shows is the currency it displays, with the two modes explained in the comments.
  • One short paragraph under the example states the rule: bare number in both modes, never { value, currency }; under dynamic (the default) the amount displays in the tenant's localization.currency setting and defaultCurrency is not the displayed currency; fixed pins the column to defaultCurrency.

The generated reference content/docs/references/data/field.mdx is untouched (auto-gen; its "dynamic (user selectable)" wording is the spec describe's and is reported to the seat, not edited here).

Changeset

skip-changeset: content/docs/** only — docs-only, nothing published moves.

Verification (head c982ebfa)

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack in this worktree, no paths, derives 40 families; every one run in the foreground with the exit captured before any pipe, then reconciled: --ran verdict "40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN", exit 0. All 40 exit 0:

check-ci-filter-parity · check-closing-keyword-parity (+ --self-test) · check-comment-mask-corpus · check-doc-frontmatter (+ --self-test) · check-doc-route-spelling --advisory (+ --self-test) · check-docs-section-name (+ --self-test) · check-section-landing-index (+ --self-test) · @objectstack/lint check:doc-formula-expressions · check:doc-security-posture · @objectstack/spec check:docs (spec built first under the verify lock) · check:empty-state · check:liveness · check:skill-examples ("259 prose examples type-check across 3 surface(s)") · check:strictness-ledger · check:variant-docs · check:yaml-examples · check:corpus-claim-drift · check:cross-package-test-inputs · check:doc-anchors · check:doc-authoring · check:docs-audit-scope · check:docs-redirects · check:docs-single-h1 · check:docs-spec-enumerations · check:docs-transcript-drift · check:driver-memory-census · check:gitlink-declared · check:nul-bytes · check:published-readme-links · check:react-page-adapter-contract · check:refd-timer-probe · check:role-word · check:skill-identifier-liveness · check:vendor-version-stamps · check:watch-hint-literal.

Prerequisite builds (@objectstack/spec, @objectstack/formula + @objectstack/lint, @objectstack/client-react closure) ran under scripts/pm/os-verify-lock.sh, each with VERDICT command-exit 0. No reverse verification or ablation applies: prose with no consumer that parses it.

Acceptance notes


Generated by Claude Code

…efaultCurrency is the column currency only under fixed

`field-types.mdx` described `currencyMode` as "per-record or fixed" and
`defaultCurrency` as a default that applies; `fields.mdx` authored
`currencyMode: 'dynamic', defaultCurrency: 'USD'` without saying USD is not
the displayed currency under `dynamic`. Re-derived from the spec: the stored
value is a bare number in both modes (ADR-0104 D1); `fixed` pins the column to
`defaultCurrency`; `dynamic` displays the tenant `localization.currency`
setting. The example now authors `fixed` so its `defaultCurrency` is the
currency it shows.

Claude-Session: https://claude.ai/code/session_0148fenvVvyQV9HYxgVDQ33q
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Sep 25, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 25, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c982ebfaa8ed256cf386f433472fb2f7d9f0dbfc

① Derived judgments

  • Accept set / public surface: none moves — two hand-written pages under content/docs/data-modeling/ (a review surface: docs shipped to users and agents); no schema, code, generated page or meta.json is touched. The card's Clause-②: no holds; ⛔ no spec change, as the card and triage require.
  • field-types.mdx: the currency section lead now states the value is a bare number in both modes with the code in currencyConfig (= packages/spec/src/data/field-value.zod.ts :23 / :63 and ADR-0104 :60); the currencyMode row now says fixed pins the column to defaultCurrency and dynamic displays the tenant localization.currency setting, the value a bare number either way; the defaultCurrency row (edited beside the named row, same table, same contradiction — the 'CNY' default) says it is the column's currency under fixed and not the displayed currency under dynamic (= field.zod.ts :439–:440 and the FieldSchema guidance the card quotes). Each sentence re-derived from the spec, not the renderer — and the renderer half objectui#10461 is MERGED, so nothing here describes behaviour no renderer has.
  • fields.mdx: the example switched to currencyMode: 'fixed' (so defaultCurrency: 'USD' is true as written) with comments naming both modes, plus one paragraph stating the bare-number shape and the display rule; +6 lines, hand-written tree, no nav change.
  • Docs gates green on the head per the dev's derivation (40 / 40 exit 0 incl. check:docs-single-h1, check-doc-frontmatter, check:doc-anchors, check:docs-spec-enumerations, spec check:docs); skip-changeset correct (docs-only).

② Semver level

  • None owed: nothing published from a package; docs-only.

③ Boundary flags

  • Dev flags: none (open_questions empty). The three out_of_scope_findings (the spec describe 「dynamic (user selectable)」, the same unconditioned chain in skills/objectstack-ui/rules/dashboards.md, the ADR-0053 mis-citations in data-api.mdx and the analytics code) are the seat's to fold into one class-closure card for the currency-mode family — not blockers here.

Implemented-by: claude/issue-20090-currency-mode-docs
Reviewed-by: session_0148fenvVvyQV9HYxgVDQ33q

VERDICT: PASS

Rendered in-seat at 2026-09-25T06:36Z on the fetched head, diff read three-dot against origin/main 7a13e05; landing (not governed) = every check green on this head, then the relay.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 25, 2026 06:41
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 0d73ff6 Sep 25, 2026
37 of 38 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20090-currency-mode-docs branch September 25, 2026 06:54
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
… text states the fixed condition (objectstack-ai#20179)

Part of objectstack-ai#20126
Clause-②: no

This PR covers the non-governed sites of the currency-mode class-closure
card. It does not close the card. The remainder is listed at the end:
the Tier H skills site and the enumerating pin.

## What was wrong

`CurrencyConfigSchema` declares two modes. With `currencyMode: 'fixed'`,
the field has one currency, `defaultCurrency`. With `dynamic`, the
default, the field has no currency of its own and shows the tenant
default currency. Every runtime reader follows that rule:

- `packages/spec/src/data/field.zod.ts`: the precision refinement judges
`defaultCurrency` only under `'fixed'`. The `FieldSchema` guidance says
"A field without one uses the tenant default at runtime".
- `packages/services/service-analytics/src/plugin.ts`
(`sourceFieldMeta`): relays `defaultCurrency` only under `'fixed'`, as
landed by the runtime member of this family (objectstack-ai#20119).
- objectui's `resolveFieldCurrency`, which the field faces call: reads
`defaultCurrency` only under `'fixed'`. Read from objectui's changeset
`10422-currency-mode-dynamic-tenant-default.md` at its `main`
(`a2f361b`). With no tenant currency set, the amount shows as a plain
number.

Four kinds of text still said otherwise:

1. **The spec describe** (card site 1). It called `dynamic` "user
selectable". No key, stored value or runtime path offers a per-record
currency choice. The value is a bare number (ADR-0104 D1), and the code
lives in field config. The describe ships in the published
`@objectstack/spec` and in the generated
`content/docs/references/data/field.mdx` (2 rows).
2. **`content/docs/api/data-api.mdx`** (card site 3). It named "the
ADR-0053 currency chain" with no mode condition. ADR-0053 is the
date/datetime record and never mentions currency.
3. **service-analytics test text.** Two test files still called the
chain "ADR-0053": the `preview-column-enrichment.test.ts` header table
and two `it` titles, and a `query-dataset.test.ts` section comment. The
objectstack-ai#20119 PR noted the test-file labels and left them as they were.
4. **Hand-written `content/docs/**` lines** that taught
`defaultCurrency` without the `fixed` condition. The census is below.

## What changes

- `field.zod.ts`, **`currencyMode` describe only.** It now says what
each mode does. `fixed`: the field has one currency, `defaultCurrency`.
`dynamic` (the default): no field currency, amounts show in the tenant
default (`localization.currency`, or a plain number when that is unset),
and `defaultCurrency` is not read. Neither mode is a per-record choice.
`gen:docs` regenerated the two `field.mdx` rows. No key, value, default
or parse output changes. `.changeset/20126-currency-mode-describe.md` is
an `@objectstack/spec` patch.
- **`data-api.mdx`.** The ADR-0053 citation is gone. The callout now
states the chain as it runs: measure `currency`, then the source field's
`currencyConfig.defaultCurrency` only when that field is `currencyMode:
'fixed'`, then the tenant default. It also says where the chain runs. It
runs on the dataset query (`POST /analytics/dataset/query`). The cube
query this page documents (`POST /analytics/query`) mints no column
`currency`, and `currency-mode-relay.test.ts` pins that cube-face fact.
- **service-analytics, comments and test titles only.** The header table
row, the two `it` titles, one in-test comment ("no field default" now
reads "no fixed field currency") and the `query-dataset.test.ts` section
comment now describe the chain with the fixed condition. The old
ADR-0053 label is gone. No assertion or runtime line changes.
- **Census fixes** (table below): `validation-rules.mdx`,
`common-patterns.mdx` and `protocol/objectql/types.mdx`.

## Census: every hand-written `content/docs` line naming
`defaultCurrency` (at base `84880f9`)

Rule, from the card: the line must also carry `fixed`, or say it is not
the displayed currency under `dynamic`. The census covers 13 lines, the
same per-file counts the dispatch named (3 / 4 / 1 / 2 / 3). `releases/`
is never edited.

| file:line | verdict | why |
|:---|:---|:---|
| `data-modeling/field-types.mdx:156` | pass | the mode row states
`fixed` and `dynamic` |
| `data-modeling/field-types.mdx:157` | pass | "under `fixed`; not the
displayed currency under `dynamic`" |
| `data-modeling/field-types.mdx:160` | pass | the example carries
`currencyMode: 'fixed'` |
| `data-modeling/fields.mdx:77` | pass | `currencyMode: 'fixed'` line |
| `data-modeling/fields.mdx:79` | pass | "the column's currency under
'fixed'" |
| `data-modeling/fields.mdx:86` | pass | "not the displayed currency"
under `dynamic` |
| `data-modeling/fields.mdx:87` | pass | "`fixed` pins the column to
`defaultCurrency`" |
| `data-modeling/validation-rules.mdx:156` | changed | "3-character
currency code" with no mode condition. It now adds "the field's currency
under `fixed`; not the displayed currency under `dynamic`" |
| `getting-started/common-patterns.mdx:53` | changed | `{ precision: 2,
defaultCurrency: 'USD' }` has no mode, so it is `dynamic`, and the
budget shows the tenant currency, never USD. Added `currencyMode:
'fixed'` |
| `getting-started/common-patterns.mdx:82` | changed | same, in the
map-format tab |
| `protocol/objectql/types.mdx:297` | changed (line shape) | The YAML
block already carried `currencyMode: fixed` on :296, but this line did
not carry it. Added the comment "the field's currency under fixed" so
the line meets the pin |
| `protocol/objectql/types.mdx:308` | changed | "The currency code lives
once on the field definition (`currencyConfig.defaultCurrency`)" holds
only under `fixed`. It now states both modes |
| `protocol/objectql/types.mdx:315` | pass | carries `fixed`
(`currencyMode: fixed \| dynamic`). Left as it was, because the callout
above now states the condition |

Two more lines in `validation-rules.mdx` sit in the same `currency`
table and belong to the same class, although neither names
`defaultCurrency`:

- `:155`, the `currencyMode` row, repeated card site 1's "`dynamic`
(user-selectable)" wording. It now matches the describe and adds this
page's validation fact: under `fixed`, an authored `precision` must
agree with the currency's ISO 4217 fraction digits.
- `:158` said "The per-record currency **code**", which contradicts
ADR-0104 D1. It now says the code is never stored per record.

Both sit inside a census-hit file and table, and both are mechanical
rewrites in the wording of the accepted objectstack-ai#20118 / objectstack-ai#20120 pages.

Pin readings after this PR, over the card's own scopes. The census
script ran from the scratchpad and was not committed.

- The `defaultCurrency` line rule over `content/docs/**/*.mdx` (without
`releases/`): 5 failing at base, **0** at head.
- `ADR-0053` within five lines of `currenc`, over `content/docs/**`,
`packages/services/service-analytics/src/**` and `skills/**`: 5 hits at
base (`data-api.mdx:467`,
`preview-column-enrichment.test.ts:51/:282/:288`,
`query-dataset.test.ts:125`) plus the skills site, and **1** at head.
The one left is `skills/objectstack-ui/rules/dashboards.md:308`, which
is the Tier H remainder.

## Tests and gates

All readings below were taken on head `25b085386`. That head is this
branch plus a merge of `main` at `e2c4e125f`, and the merge brought
nothing into this PR's files. Before the gates ran, all 71 package build
tasks passed (`turbo run build --filter='./packages/*'
--filter='./packages/*/*'`, 71/71).

- **Derived gates.** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derived 109 commands. Each was run
and its exit code recorded, and all 109 exit 0. The `--ran`
reconciliation answers: "109 derived, 109 run, 0 NOT-MEASURED, 0 UNRUN".
The derived set includes `check:docs`, `check:generated` (15 of 15
current), `check:api-surface`, `check:authorable-surface`,
`check:doc-authoring`, `check:nul-bytes`, `check:skill-examples`,
`check:dual-build-cjs-loads` and `check:type-check-debt`.
- **Roster gates.** Six roster families keep their roster in a directory
this PR touches, and all six exit 0: `check-changeset-fixed`,
`check:meta-url-spelling`, `check:spec-changes`, `check:authz-resolver`,
`check:error-code-casing` and `check:filter-alias-parity`.
- **This body.** Run against this text, `check-partof-closing-keyword`
exits 0.
- **`@objectstack/spec`.**
- `pnpm --filter @objectstack/spec test`: 540 files, 15820 passed, 2
todo.
  - `pnpm --filter @objectstack/spec run typecheck`: exit 0.
- No spec test pins the old describe text. The phrase "user selectable"
appears only in the describe and its generated page.
- **`@objectstack/service-analytics`.**
- `pnpm --filter @objectstack/service-analytics exec vitest run
--maxWorkers=2` on the two edited files: 2 files, 44 tests passed.
- `pnpm --filter @objectstack/service-analytics run typecheck`: exit 0.
- `tsc --listFiles` includes both edited test files (2 hits; the control
`src/plugin.ts` has 1).
- **Lint, narrowed to the 3 touched TypeScript files** (`pnpm lint` over
the whole repo is CI's run):
- `eslint --no-inline-config --format json` exits 0, and the JSON
reports 3 files, 0 errors and 0 warnings.
- `eslint --print-config` puts the files in the linted population, with
5 rules on.
- `eslint.config.mjs` enables no type-aware linting (no
`parserOptions.project`), so a change to these files cannot move a
verdict on any untouched file.
- **No ablation.** The diff changes text only: a describe, docs,
comments and test titles. No assertion or runtime line changes, so no
negative pin exists to ablate.

## Acceptance notes

- **The `defaultCurrency` describe is left as it was.** It reads
"Default or fixed currency code". The claimed surface is the
`currencyMode` describe only. The generated row meets the card's pin,
because it carries `fixed`, and the rewritten `currencyMode` row beside
it now states the condition. "Default" can still read as a dynamic-mode
default. That belongs to this family and is noted for the closure card,
not changed here.
- **More ADR-0053-for-currency sites exist outside the card's pin
scopes.** They are the same family and are not in this PR's claimed
surface:
- `packages/spec/src/contracts/analytics-service.ts:80`: the published
TSDoc on `AnalyticsResult.fields[].currency` reads "ADR-0053 currency
chain … source-field default → tenant default", with no mode condition.
It ships in the `@objectstack/spec` typings.
- `packages/spec/src/data/percent-scale.ts:14`: a comment saying
"mirrors the ADR-0053 currency chain".
- `packages/spec/liveness/dataset.json` (the `currency` row's evidence
text): "the ADR-0053 chain … the source field's `defaultCurrency`".
- The card's pin scopes (`skills/**`, `content/docs/**`,
`service-analytics/src/**`) do not reach `packages/spec/src/**`. These
are noted for the closure card.
- **A strictly line-based pin would go red on semantically correct skill
lines.** `skills/objectstack-data/rules/field-types.md` lines :55, :183,
:184, :191 and :192 were corrected and accepted with objectstack-ai#20120. Each states
the condition across a wrapped sentence or a two-line comment, but none
carries `fixed` on its own line. Whoever writes the enumerating pin must
judge a sentence or block, not a single line, or reshape those lines in
the same Tier H PR.
- **`data-api.mdx` makes a wider claim than currency.** The callout says
`label`, `format` and `percentScale` are resolved server-side on this
page's `fields[]`. That is a source read and was not measured. Both cube
strategies push only `name` and `type`
(`native-sql-strategy.ts:1247/:1252`,
`objectql-strategy.ts:1960/:1964`), and the ADR-0021 column enrichment
runs only on `queryDataset`. Only the currency sentence was in scope and
was corrected. Carrier: none, noted and not filed.
- **Three `query-dataset.test.ts` titles are left as they were.** They
say "inherits the field defaultCurrency", "a currency field with no
defaultCurrency" and "wins over the field default". They cite no ADR, so
they are outside the claimed surface. The section comment above them now
says that `sourceFieldMeta().defaultCurrency` is the field's fixed
currency.

## Remainder of objectstack-ai#20126 (not in this PR)

- `skills/objectstack-ui/rules/dashboards.md:304`–`:308`: the
unconditioned chain and the ADR-0053 citation. This is Tier H and gets
its own PR, per triage execution note 1.
- The enumerating pin: a test in the owning package, added by whichever
PR closes the last site (no new `check-*` gate). See the line-based
caveat above.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants