Skip to content

Commit 180ef90

Browse files
docs(skills): currency is a bare number in both modes; the resolution order is mode-conditioned (field-types.md) (#20118)
Part of #20090 Clause-②: no Skill half of the card (the `skills/**` governed surface, Tier H). The two `content/docs` pages land separately on `claude/issue-20090-currency-mode-docs` so the docs correction does not wait on this approval; #20090 stays open until both have landed and the seat closes it. ## What changed One file: `skills/objectstack-data/rules/field-types.md`, the "Currency with Precision" example and the paragraph under it. Text only; no spec change, no example shape change (the block stays a valid `Field` literal — it carries no `os:check` marker, so `check:skill-examples` type-checks nothing in this file; the block is a fragment like every other block there). - The example comment taught `'dynamic'` as a per-record `{ value, currency }`. It now reads: `'fixed'` = the column is pinned to `defaultCurrency`; `'dynamic'` = tenant default. - The paragraph "Currency resolution (ADR-0053)" read `currencyConfig.defaultCurrency` unconditionally, then the tenant default. It now says: the value is a bare number in both modes (ADR-0104 D1), never `{ value, currency }`; the displayed symbol is `currencyConfig.defaultCurrency` only under `'fixed'`; under `'dynamic'` (the default) it is the tenant `localization.currency` setting, not `defaultCurrency`; with neither, a plain grouped number; a measure's own `currency` wins. Every sentence is re-derived from the spec, not from any renderer: - `packages/spec/src/data/field-value.zod.ts` — `NUMERIC_VALUE_TYPES` carries `currency` with the comment "`currency` IS a bare number", and the module header explains `CurrencyValueSchema` was "never-consumed". - `packages/spec/src/data/field.zod.ts` — `currencyMode: z.enum(['dynamic','fixed']).default('dynamic')`; `defaultCurrency` "Default or fixed currency code" with default `'CNY'`; the `FieldSchema` guidance "a fixed currency is declared as `currencyConfig: { currencyMode: 'fixed', defaultCurrency: 'JPY' }`. A field without one uses the tenant default at runtime."; and the precision-check comment "The currency is statically known only under `currencyConfig.currencyMode: 'fixed'` (`dynamic` is out of reach BY DESIGN; a field with no `currencyConfig` has only the runtime tenant default, which is not static)". - The tenant default's producer: `packages/services/service-settings/src/manifests/localization.manifest.ts`, key `currency` — "ISO 4217 code applied when a currency field omits its own. Leave unset to render code-less amounts as plain numbers." ## The ADR citation (mechanism assumption b, measured) `docs/adr/0053-*` on `origin/main` is `0053-date-and-datetime-semantics.md`. It introduces the tenant `localization` settings manifest for the timezone ladder and never names currency (`git grep -ic currency` on it: 0). `git grep -il currency origin/main -- docs/adr/` returns 14 files; the only one that governs a currency rule is ADR-0104 (D1: "`currency` is a scalar number — `CurrencyValueSchema` is deleted"). No ADR in this registry, and no `cloud ADR-NNNN` cited from this repo, governs the display-resolution order. So the paragraph now cites ADR-0104 D1 for the value shape and drops the ADR-0053 citation rather than keep a wrong one; the tenant default is named by its setting, `localization.currency`, which is its actual producer. ## "dynamic (user selectable)" (mechanism assumption c, measured) The spec describe reads "dynamic (user selectable)". ADR-0104 says the currency code "lives in field config, not per value"; ADR-0053 is silent; no ADR, schema key or runtime seam names a place where a per-record currency is selected (`resolveExecutionContext` reads `localization.currency` onto the context; the analytics chain reads field config and the context). The skill therefore teaches only what the spec guarantees — bare number; tenant default — and names no selection mechanism. The gap is recorded below. ## Budget readings (the seat's `skills/**` budget: net at most +2 lines in this file) | reading | before (`fa00ebf4`) | after (`7bf1503b`) | delta | |:---|:---|:---|:---| | `skills/objectstack-data/rules/field-types.md` lines | 328 | 328 | 0 | | `skills/objectstack-data/**/*.md` package lines (15 files) | 3717 | 3717 | 0 | | `field-types.md` tokens, `check-skills-token-ratchet.mjs` (ceiling 3158) | 3158 (headroom 0) | 3157 (headroom 1) | −1 | | bundle total tokens (whole shipped tree) | 153973 | 153972 | −1 | | `field-types.md` bytes (the ratchet counts ceil(bytes / 4)) | 12629 | 12625 | −4 | The file sat at zero token headroom, so the rewrite is paid for inside the same sentences (no re-wrap credit claimed): 7 lines replaced by 7 lines. ## Changeset `skip-changeset` (measured, not assumed): no package's `files[]` names a `skills` path (positive control: `packages/spec` `files[]` names `dist`), and `create-objectstack` installs the catalog at scaffold time with `npx skills add objectstack-ai/objectstack/skills`, from the repository, never from its tarball. Nothing published moves. ## Verification (head `7bf1503b`) `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` in this worktree, no paths, derives 23 families; every one run in the foreground with the exit captured before any pipe, then reconciled: `--ran` verdict "23 derived, 23 run, 0 NOT-MEASURED, 0 UNRUN", exit 0. All 23 exit 0: `check-ci-filter-parity` · `check-closing-keyword-parity` (+ `--self-test`) · `check-comment-mask-corpus` · `check-doc-route-spelling --advisory` (+ `--self-test`) · `check-skills-token-ratchet` (+ `--self-test`) · `@objectstack/lint check:doc-formula-expressions` · `check:agent-test-spelling` · `check:corpus-claim-drift` · `check:cross-package-test-inputs` · `check:doc-authoring` · `check:driver-memory-census` · `check:gitlink-declared` · `check:nul-bytes` · `check:pm-governed-merges` · `check:refd-timer-probe` · `check:role-word` · `check:skill-compatibility` · `check:skill-frame-sync` · `check:skill-identifier-liveness` · `check:watch-hint-literal`. Two prerequisite refusals (exit 3, "nothing was measured") were cleared by building the named packages under the verify lock and re-running: `check:doc-formula-expressions` (needed `@objectstack/formula` + `@objectstack/lint` built) and `check:skill-examples` (needed `@objectstack/client-react` built). `check:skill-examples` is outside this diff's derivation and was run anyway because it reads `skills/**`: "259 prose examples type-check across 3 surface(s)", exit 0. No reverse verification or ablation applies: the change is prose with no consumer that parses it. ## Acceptance notes - The spec describe "dynamic (user selectable)" (`field.zod.ts` `currencyMode`) surfaces verbatim in the generated reference `content/docs/references/data/field.mdx` (2 rows). No mechanism selects a per-record currency anywhere in the tree, so that word over-claims against ADR-0104 D1's own sentence that the code lives in field config. Out of scope here (this card forbids a spec change; the reference page is auto-generated). carrier: spec lane (a one-word describe change plus `gen:docs`); reported in the os-dev report as a class-b candidate with dedupe words. - The same unconditioned chain with the same wrong ADR number appears in `skills/objectstack-ui/rules/dashboards.md` (the measure comment, "resolves measure `currency` → the aggregated field's `currencyConfig.defaultCurrency` → the tenant `localization.currency` default (ADR-0053)"), in `content/docs/api/data-api.mdx` ("the ADR-0053 currency chain") and in `packages/services/service-analytics/src/plugin.ts` and `analytics-service.ts` comments ("ADR-0053 currency chain"). Those are #20091's runtime half and its teaching surfaces; #20091 remains open and is not touched here. carrier: #20091. ## 维护者速读(草稿) **改了什么** — 只改一份对外发布的技能文件 `skills/objectstack-data/rules/field-types.md` 里关于货币字段的两处教学文字:示例注释和其下的"货币解析"段落。行数 328 → 328,token 3158 → 3157(上限 3158),整包 3717 行 → 3717 行。不改 spec,不改任何代码。 **为什么改** — 原文教 AI 把 `dynamic` 模式的货币值写成 `{ value, currency }` 对象,而运行时(校验器、SQL 驱动的数值列、导入)只接受裸数字;照抄的作者会把对象写进数值列。原文还把 `defaultCurrency` 当成无条件的显示币种,而它默认是 `'CNY'`,这样每个 `dynamic` 字段都会显示人民币;按 spec,它只在 `fixed` 模式下是该列的币种,否则显示租户的默认币种。段落引用的 ADR-0053 其实是日期语义的记录,与货币无关,改为引用 ADR-0104 D1(值形状)并去掉错误引用。 **风险与代价(含回滚)** — 纯文字改动,零运行时影响;技能包 token 净减 1,不触及棘轮上限。回滚即 revert 这一个 commit。相关的运行时半边(analytics 无条件读 `defaultCurrency`)归 #20091,不在本 PR。 **席位意见** — **你要做的** — 这是 Tier H 受管面(`skills/**`),需要您(或授权审批账号)的一次 APPROVED review;审批后由席位落地。请顺带看一眼"dynamic (user selectable)"这个 spec 描述词是否要另立一张卡改掉。 --- _Generated by [Claude Code](https://claude.ai/code/session_0148fenvVvyQV9HYxgVDQ33q)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 536f2d5 commit 180ef90

1 file changed

Lines changed: 7 additions & 7 deletions

File tree

‎skills/objectstack-data/rules/field-types.md‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,18 +179,18 @@ Stored as JSON on the parent row — no separate table / FK:
179179
type: 'currency',
180180
currencyConfig: {
181181
precision: 2,
182-
currencyMode: 'fixed', // 'fixed' = one currency for the column;
183-
// 'dynamic' = per-record `{ value, currency }`
182+
currencyMode: 'fixed', // 'fixed' = the column is pinned to
183+
// defaultCurrency; 'dynamic' = tenant default
184184
defaultCurrency: 'USD', // ISO 4217
185185
},
186186
}
187187
```
188188

189-
**Currency resolution (ADR-0053).** A displayed amount resolves its symbol
190-
through: the field's own `currencyConfig.defaultCurrency` → the tenant
191-
`localization.currency` default. With neither set, renderers show a plain
192-
grouped number (never a hardcoded `$`). The same chain backs analytics measures
193-
(a measure's explicit `currency` wins over the field/tenant default).
189+
**Currency resolution.** The value is a bare number in both modes (ADR-0104
190+
D1), never `{ value, currency }`. The displayed symbol: under `'fixed'`,
191+
`currencyConfig.defaultCurrency`; under `'dynamic'` (default), the tenant
192+
`localization.currency` setting, not `defaultCurrency`; with neither, a plain
193+
grouped number (never a hardcoded `$`). A measure's own `currency` wins.
194194

195195
### Select with Default
196196

0 commit comments

Comments
 (0)