Repository navigation
Description
Ruled: 6464202770 · letter Q2 X · 6465431966 · letter Q1 乙-现在 · 2026-10-28T09:33Z
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Filing gate ③ (maintainer-directed). Maintainer, 2026-10-28, verbatim: 「以上币种的所有相关问题,立一张决策卡」, after the triage seat walked the currency surface: the tenant default currency, how fields and dashboards render it, and the paused multi-currency design #8345. ⛔ Not a claim.
维护者速读
- 一句话问题: 平台在「不知道这笔钱是什么币种」时,各处的做法互相打架:字段和仪表盘按组织默认币种显示,但有四处会自己猜一个币种(三处猜美元,一处猜人民币),客户看到的是错的货币符号。另外,真正的多币种(每条记录自带币种、按汇率折成本位币)从 10 月 8 日起暂停,要不要恢复、恢复成什么形状,也一并定。
- 要你裁两件事: Q1 多币种恢复与否、什么形状;Q2 不知道币种时的统一规则。Q3 不用裁,裁后一并做。
- 我的推荐: Q1 乙(C′ 形状),但时序上保持暂停,等有客户拉动再开工。Q2 X:先用组织默认币种,没有就不显示符号,模板里没有就拒发,任何地方都不猜。
Background (measured on origin/main, 2026-10-28)
What already exists and works:
- The tenant default currency
localization.currency(ADR-0053; Setup → Localization;scope: 'tenant'). New organizations are pre-filled from the creator's language or region (localization: a new organization gets its default currency from its creator's language or region (zh-CN → CNY), written as a visible Setup → Localization value; with nothing to derive from, it stays unset (maintainer ruling 乙, 2026-10-23) #25495, ruling 乙), never with a guessed$. - One display rule for fields:
resolveFieldCurrency(ui/packages/i18n/src/currency.ts). The order is: an explicitcurrency→fixed'sdefaultCurrency→ the legacydefaultCurrency→ the tenant default → no symbol. Every field, grid, record drawer and metric widget uses it. - One labelling rule for analytics: the measure
currency→ the field's fixed currency →context.currency(the tenant default) (service-analyticsanalytics-service.ts:3127).
Where the platform still guesses a currency (each one breaks that rule):
| # | Where | What it does | Who sees it |
|---|---|---|---|
| G1 | Studio dataset inspector, ui/packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.tsx:215 |
Picking "currency" format with no currency stores currency: 'USD' on the measure. A measure's own currency outranks the tenant default. |
A CNY organization's dashboard tile shows $ |
| G2 | Template formatter, packages/formula/src/template-engine.ts:82 (arg ?? 'USD'), card #26939 |
{{ x | currency }} with no argument formats in USD, in mail and in-app copy |
An email or a flow notice shows $ for a yuan amount |
| G3 | Spec, packages/spec/src/data/field.zod.ts:559 |
A fixed-mode field authored with no code materializes defaultCurrency: 'CNY'. This is against the ruled principle (#9689): "NEVER materialize a default the schema itself would refuse as authored". |
A USD or EUR app's fixed field shows ¥ |
| G4 | Example, examples/app-crm/src/datasets/opportunity.dataset.ts:32-33 |
currency: 'USD' over a tenant-currency field |
Anyone who copies the example, including AI authors, inherits it |
Docs that claim what does not exist (#8345's measurement; not a decision; done under Q3):
content/docs/protocol/objectql/index.mdx:121: "Money with exchange rates".content/docs/.../types.mdx:30: "Store amount + currency code".
Multi-currency (#8345), paused 2026-10-08: 「8345 多币种暂时不是重点,暂停」. That card's measurement still stands:
- There is no record currency today (a currency value is one number;
dynamicmeans the tenant default, and "Neither mode is a per-record choice"). - There is no exchange-rate conversion.
- Today a mixed sum is meaningless: USD 100 + JPY 15000 answers
15100. - The origin was an overseas port-terminal customer (2026-08): billing in the customer's currency, reconciliation at dated rates, and consolidated reporting in one home currency.
Governing text
- ADR-0053 (the tenant currency as the fallback label); ADR-0104 D1 (a currency value is a scalar number;
CurrencyValueSchemawas retired). field.zod.tsFieldSchemablock, principle FieldSchema acceptsdeleteBehavior: 'set_null'on amaster_detail, and the engine silently resolves it tocascade#9689 (maintainer 2026-08-24): "NEVER materialize a default the schema itself would refuse as authored".packages/formula/src/stdlib.ts:7: a stdlib function may not be handed a host resolver. This is why [Epic] multi-currency (shape C′): record currency, dated exchange rates, write-time home-currency projection — 17 children, R1/R2/R3 release pairing (ruling 6465431966) #8345's option B conflicts.- AGENTS.md Prime Directives 10 and 12: a declaration is enforced; wrong metadata is refused loudly at publish.
Premises (each with a re-check)
- G1:
git grep -n "currency: currency || 'USD'" origin/main -- ui/packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.tsx→ 1 hit. - G2:
git grep -n "arg.trim() : 'USD'" origin/main -- packages/formula/src/template-engine.ts→ 1 hit. - G3:
git grep -n "default('CNY')" origin/main -- packages/spec/src/data/field.zod.ts→ 1 hit. - G4:
git grep -n "currency: 'USD'" origin/main -- examples/app-crm/src/datasets/opportunity.dataset.ts→ 2 hits. - The one display rule:
git grep -n "tenantDefault ||" origin/main -- ui/packages/i18n/src/currency.ts→ 1 hit. - No conversion:
git grep -n -i -E "exchange.?rate|fx_rate|convertCurrency" origin/main -- packages/ ':!*.test.ts'→ 0 hits.
Q1: Multi-currency, resume #8345 and in what shape?
一句话: 客户要「按客户币种开票、按当日汇率对账、统一折成本位币出报表」,平台今天三件都做不到。
| 选项 | 做什么 | 客户可感知后果 | 工作量 |
|---|---|---|---|
| 甲 | 保持暂停 | 只用一种币种的客户不受影响;跨币种业务的客户仍做不了,应用只能自己手搓汇率表 | 0 |
| 乙 = #8345 的 C′ | 金额字段新增「记录币种」模式(指向记录上的 ISO 4217 币种字段)+ sys_exchange_rate 汇率表 + 写入时按单据日期汇率算好本位币金额并存下(同时记下所用汇率),缺汇率拒绝写入;未按币种分组的跨币种原始求和在构建时拒收 |
三件事都能做到;现有汇总、报表、表格合计、指标卡不改读取代码就出本位币合计;改汇率需显式重算 | spec 1 PR + server 2–3 PR + ui 1 PR · 无先例 · 估 1–2 周 |
| 丙 = #8345 的 A′ | 只做「记录币种」模式 + 跨币种求和护栏,不做汇率 | 能按客户币种开票,汇总不再算错,但折不成本位币 | spec 1 PR + server 1 PR · 无先例 |
业务含义直译: 甲:只收一种钱。乙:收银台带换汇柜台,每笔都按当天汇率记下折算值。丙:每张单子写明币种,年底还得手工换算。
推荐: 乙的形状(C′),时序上维持暂停。
- 两年后的终态是「交易币种 + 本位币」双金额,写入时锁定汇率。这和 Salesforce Advanced Currency Management、Dynamics 365 的 transaction / base currency 是同一个模型。
- 只看①选乙;②是否翻转:否,只调时序(今天没有在跑的客户拉动,等拉动回来再开工)。
- 不取丙:它是半量,做完还要再改一次契约。不取甲作终态:跨币种客户会回来。
- 置信缺口:「1–2 周」是估算,不是实测。
Q2: One rule for an amount whose currency is not known
一句话: 同一笔不知道币种的钱,在字段里显示组织币种,在仪表盘某些格子里显示 $,在邮件里显示 $,在固定币种字段里显示 ¥。
| 选项 | 做什么 | 客户可感知后果 | 工作量 |
|---|---|---|---|
| X | 全平台一条规则:先用明确写的币种 → 再用组织默认币种 → 都没有:显示时不带符号,发送(邮件、通知)时拒发。G1 不再存 USD;G2 无参数用组织币种;G3 fixed 不写币种在发布时拒收(不再变成 CNY);G4 删掉 currency: 'USD' |
不会再出现错的货币符号;固定币种字段必须写明币种 | ui 1 PR(G1)+ formula 1 PR(G2,并入 #26939)+ spec 1 PR(G3,收窄,需 ADR-0087 处置与存量调查)+ example 1 行(G4)· 先例 #25495 |
| Y | 一律拒收:任何地方没写币种就在构建时拒收 | 最严;但把「组织默认币种」这个已有能力废掉,作者每个金额都得写币种 | 同上加全量调查,量更大 |
业务含义直译: X:「没写就按公司本位币,公司也没定就不标符号」。Y:「每张单子都必须手写币种」。
推荐: X。
- ①:一条规则住在一处,组织默认币种就是为此而建的(ADR-0053、localization: a new organization gets its default currency from its creator's language or region (zh-CN → CNY), written as a visible Setup → Localization value; with nothing to derive from, it stays unset (maintainer ruling 乙, 2026-10-23) #25495)。Y 等于推翻它。
- ③防 AI 犯错:X 下,作者或 AI 漏写币种时,看到的是正确的组织币种,或者响亮的拒发。今天看到的是一个错误的
$,没有任何报错。 - 只看①选 X;②③④ 是否翻转:否。
Q3: Not a decision; done after the ruling either way
The two docs over-claims are rewritten to what ships ("one number; currency from the field or the tenant default"). The fix is domain:devx, p3. Under the #8345 hold, this was owed separately and never filed.
Four-axis facets
os-decision-facets
- ① 项目长远合理性:Q2 X 把「不知道币种」收成一条规则(明确 → 组织默认 → 无符号/拒发),消掉四处各自猜测的特例;Q1 乙是主流平台的「交易币种 + 本位币、写入时锁汇率」终态,不加公式回调例外。
- ② 实际业务拉动:G1–G4 今天就在把错的符号给到非美元/非人民币组织(任何新建的 CNY 组织在 Studio 里选「货币」格式即中招);多币种的拉动来自 8 月的海外港口客户,10 月 8 日已暂停,当前无在跑拉动。
- ③ 防 AI 犯错:今天漏写币种是静默错符号;X 下要么是正确的组织币种,要么是发布/发送时的响亮拒收;G4 示例是 AI 作者照抄的源头,必须先改。
- ④ 创业阶段不扩散:X 不新增任何键(只删猜测、收窄
fixed);Q1 乙新增一个模式与一个平台对象,是永久义务,故时序上等拉动再做。
Prior rulings read: currency, defaultCurrency, multi-currency, exchange rate, localization.currency → 6 hits; ADR-0053, ADR-0104 D1; thread: [Epic] multi-currency (shape C′): record currency, dated exchange rates, write-time home-currency projection — 17 children, R1/R2/R3 release pairing (ruling 6465431966) #8345 (6051408208 pause), localization: a new organization gets its default currency from its creator's language or region (zh-CN → CNY), written as a visible Setup → Localization value; with nothing to derive from, it stays unset (maintainer ruling 乙, 2026-10-23) #25495 (乙), FieldSchema acceptsdeleteBehavior: 'set_null'on amaster_detail, and the engine silently resolves it tocascade#9689
After the ruling
- Q2 = X:
- Triage files G1 (
domain:ui, p2), G3 (domain:spec, p2, narrowing; it owes ADR-0087 disposition and a stored-metadata survey) and G4 (domain:devx, p3). - spec(formula): a
currencyhole with no argument still formats in US dollars — refuse it at authoring in every template dialect (follow-up of #26660) #26939 is re-scoped to "no argument → the tenant default; no tenant default →TEMPLATE_INVALID", and leavespm:blocked.
- Triage files G1 (
- Q2 = Y: the same four cards, each with "refuse at authoring" as its target; spec(formula): a
currencyhole with no argument still formats in US dollars — refuse it at authoring in every template dialect (follow-up of #26660) #26939 keeps its current target. - Q1 = 甲 or 乙: [Epic] multi-currency (shape C′): record currency, dated exchange rates, write-time home-currency projection — 17 children, R1/R2/R3 release pairing (ruling 6465431966) #8345 records the letter. For 乙 it stays
pm:on-hold, with the shape fixed as C′, and thedomain:specseat re-verifies the premises when the restart condition fires. - Q1 = 丙: [Epic] multi-currency (shape C′): record currency, dated exchange rates, write-time home-currency projection — 17 children, R1/R2/R3 release pairing (ruling 6465431966) #8345 is re-scoped to A′ and returns to
pm:queue. - Q3: triage files the docs correction (
domain:devx, p3) whatever the letters are.
Related
#8345 (multi-currency, on hold), #25495 (new organization default currency, closed), #26939 (template no-argument currency), #26937 / #26660 (currency:@path in mail), #26948 (unknown mail formatter), ADR-0053, ADR-0104.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 28, 2026
Brief-claimed-by: session_01QSeKYo7qYsX6HsAneHBMP2 · batch #421 · 2026-10-28T07:42Z
Director seat, summon #53 (ledger objectstack#12708). Presented to the maintainer in batch #421 as item 2 of 3 — two letters: Q1 (multi-currency, #8345) and Q2 (the one rule for an amount whose currency is not known); Q3 is execution after either letter.
Seat increment (re-read at
origin/maine6e41998, 2026-10-28T07:42Z):defaultCurrencyunderpackages/excluding tests → 95 hits).docs/adr/0053-date-and-datetime-semantics.mdis date and datetime semantics, not the tenant currency.git grep -n -iE "localization\.currency|tenant default currency" origin/main -- docs/adr→ 0 hits (control:CurrencyValueSchema→ ADR-0104 L79, L166). The tenant-currency fallback's governing text is the settings manifest (packages/spec/src/system/settings-manifest*, keycurrency) and the localization: a new organization gets its default currency from its creator's language or region (zh-CN → CNY), written as a visible Setup → Localization value; with nothing to derive from, it stays unset (maintainer ruling 乙, 2026-10-23) #25495 ruling (乙), not an ADR. ADR-0104 D1 (a currency value is a scalar number;CurrencyValueSchemadeleted) verified at L165–166. The letters do not move.field.zod.ts:555–559):currencyModedefaults todynamic, anddefaultCurrency.default('CNY')is materialised on every parse, dynamic configs included — the i18n display rule already has to ignore it (currency.ts:34–36). Under X the fix is "fixedrequires an authoreddefaultCurrency; no default at all" —Metadata-protocol: narrowed,(narrowing)arm, ADR-0087 disposition, plus a stored-metadata survey offixedconfigs without a code (expected 0; the survey is the proof, not the expectation).fixed;乙新增一个模式加一个系统对象,等拉动再开工。pm:on-hold,Restart-when:改写为具名客户拉动)· Q2 X. Not Y: it retires the tenant default the platform built on purpose (localization: a new organization gets its default currency from its creator's language or region (zh-CN → CNY), written as a visible Setup → Localization value; with nothing to derive from, it stays unset (maintainer ruling 乙, 2026-10-23) #25495). Not 丙: half the contract, re-cut later.currencyhole with no argument still formats in US dollars — refuse it at authoring in every template dialect (follow-up of #26660) #26939) · spec 1 PR (G3, narrowing kit) · devx 1 line (G4) — S each, precedent localization: a new organization gets its default currency from its creator's language or region (zh-CN → CNY), written as a visible Setup → Localization value; with nothing to derive from, it stays unset (maintainer ruling 乙, 2026-10-23) #25495; Q1 乙 = 0 now (the shape is recorded on [Epic] multi-currency (shape C′): record currency, dated exchange rates, write-time home-currency projection — 17 children, R1/R2/R3 release pairing (ruling 6465431966) #8345).Prior rulings read: defaultCurrency · multi-currency · exchange rate · localization.currency → 97 hits in AGENTS.md / docs/adr / packages/spec/src (
field.zod.tsdocblock L539,field.test.tsL1686; no ADR rules the fallback); thread: none on this card; #8345 pause 6051408208, #25495 乙, #9689.objectstack-fleet commentedon Oct 28, 2026
Ruling: batch #421 item 2 · letters Q2 X · Q1 held — the maintainer asked for a deeper multi-dimensional analysis before the letter · maintainer 「多币种帮我专题分析,从多个维度深度分析,其他同意。」 2026-10-28T08:22Z
Recorded by the director seat (summon #53,
session_01QSeKYo7qYsX6HsAneHBMP2, ledger objectstack#12708). Brief 6463511172. 「其他同意」 takes Q2 X. Q1 (multi-currency, #8345) is NOT ruled in this act: the maintainer's words ask the seat for a 专题分析 first; it follows on this card as the next comment, and Q1's letter is recorded in a secondRuling:comment when it comes. Q3 is execution and goes with Q2.Q2 = X — one rule for an amount whose currency is not known: an explicit currency → the tenant default (
localization.currency) → with neither, DISPLAY carries no symbol and SEND (mail, notification) refuses. Nothing guesses, anywhere.ui/packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.tsx:215): picking the currency format with no currency stores nocurrencykey; the measure inherits the field → tenant chain.domain:ui, p2 — card filed in this act.packages/formula/src/template-engine.ts:82): no argument → the tenant default; no tenant default →TEMPLATE_INVALIDat send. spec(formula): acurrencyhole with no argument still formats in US dollars — refuse it at authoring in every template dialect (follow-up of #26660) #26939 is re-scoped to this target and returns topm:queue(its written target, "refuse at authoring", was Y).packages/spec/src/data/field.zod.ts:559):fixedrequires an authoreddefaultCurrency; the'CNY'default goes —(narrowing)arm,Metadata-protocol: narrowed, ADR-0087 disposition, a stored-metadata survey offixedconfigs without a code (expected 0; the survey is the proof).domain:spec, p2 — card filed in this act.examples/app-crm/src/datasets/opportunity.dataset.ts:32–33): deletecurrency: 'USD'.domain:devx, p3 — card filed in this act.content/docs/protocol/objectql/index.mdx:121andtypes.mdx:30rewritten to what ships (one number; currency from the field or the tenant default).domain:devx, p3 — card filed in this act.Workload: as the brief — ui S · formula S (via #26939) · spec S · devx XS × 2. Precedent #25495.
Execution: this card STAYS
needs-user-decisionfor Q1;pm:awaiting-maintainer(a half-state beside it with noMaintainer-action:line) is removed in this act.Ruling-ref:on each execution card names this comment.objectstack-fleet commentedon Oct 28, 2026
多币种专题分析(Q1 / #8345)· 总监席 · 第 53 场 · 2026-10-28T08:36Z
应维护者「多币种帮我专题分析,从多个维度深度分析」(批 #421 回字)。输入:#8345 的测量轮(6041378776,两个探针跑在真实 ObjectQL 引擎上)、spec 席的四形状分析(6041482631)、本卡正文、
origin/maine6e41998的现读,以及 hotcrm6c52f22(本席只读克隆)。⛔ 不是裁决;Q1 的字母仍由维护者下。0. 结论先行
$/¥的底座上。1. 今天的平台(实测)
currencyMode: 'dynamic'(默认)= 按组织默认币种显示,'fixed'= 字段有一个币种。没有任何键、列或值承载「这条记录是什么币种」。localization.currency:租户级、无平台默认、ISO 4217 值域、可改且无历史——改一下,所有历史金额就换了标签(探针 B)。exchange.?rate|fx_rate|convertCurrency在packages/0 命中,对照 95)。CURRENCY_FRACTION_DIGITS只剩值域键集,小数位的值零读者——换算会是它的第一个读者。sum答200 "USD";汇总字段 USD 100 + JPY 15000 答15100;跨币种派生度量相减答0无标签。全部静默错数。stdlib.ts:7),objectstack build要逐字节稳定 ⇒ 公式里不能有读汇率表的函数。2. 业务场景维度:谁要、要什么
港口客户三件事对应的最小能力:① 记录币种;② 按日期的汇率 + 写入时快照;③ 存下来的本位币金额让现有汇总直接出合并数。三件缺一,客户还是得自己手搓(而手搓的写法平台看不见、护栏拦不住)。
3. 主流平台怎么建模(对照)
CurrencyIsoCodeconvertCurrency())transactioncurrencyid每条记录_base影子列并存currency_id字段,金额字段currency_field指向它res.currency.rate按日期amount_currency+balance都存公分母:记录币种 + 固定本位币 + 按日期汇率表 + 写入时快照并存储本位币金额。只有 Salesforce 走读时换算,并为此付了 ACM 的代价。
4. 数据模型维度:记录币种放哪(Q0)
currencyMode: 'record'+currencyField指向同对象上一个valueDomain: 'iso_4217_currency'的文本字段(该值域 No declarative way to constrain an object FIELD to a value domain (iana_time_zone), and neither extension point an app can reach can express it #14168 已落地)。值仍是标量,ADR-0104 D1 不动;多个金额共用一个币种字段;AI 能看见它、构建时能校验它。build校验不到。{amount, currency}:推翻 ADR-0104 D1,迁移每一个已存金额列,复活刚退役的CurrencyValueSchema。fixed字段——对开放币种集不可用。5. 汇率维度
sys_exchange_rate { currency, rate(相对本位币), effective_date, source },按 org / currency / date 唯一;交叉汇率经本位币推导(⛔ 不做 N×N 矩阵)。CURRENCY_FRACTION_DIGITS第一次有读者。sys_exchange_rate走系统对象的权限面,写入留审计。6. 换算时机维度:写入 vs 读取(Q3)
rateDate指向记录上的日期字段),缺则构建拒收;⛔ 不默认 created_at——那是猜。7. 汇总与报表语义维度(Q4)
record模式字段的原始sum/avg/min/max在构建时拒收,除非按币种字段分组;分组结果在线上逐行带币种(今天一列一个币种会把分组标错,探针 C)。fixed字段矛盾 ⇒ 构建拒收。8. 公式维度
convertCurrency()公式函数([Epic] multi-currency (shape C′): record currency, dated exchange rates, write-time home-currency projection — 17 children, R1/R2/R3 release pairing (ruling 6465431966) #8345 原 M2、形状 B)与标准库纯度不变量冲突,且公式字段是虚拟的、汇总拒绝它——结果看起来能汇总、实际加不起来,是 AI 最容易踩的坑。⛔ 不做。returnType: 'currency'的公式沿用同一currencyConfig形状(spec: a formula field cannot declare a currency result —returnTypeisnumber | text | boolean | date, so a formula over money renders as a bare number #22727)。9. 防 AI 犯错维度
currencyField必须是 ISO 4217 值域文本字段;rateDate必须是日期字段;本位币影子列目标必须是存储型、fixed且币种 = 本位币。10. 迁移与兼容维度
record是新增模式(Metadata-protocol: added,minor);dynamic/fixed原样保留;存量数据不动。localization.currency,一旦存在汇率行或影子列声明即锁定不可改(Dataverse 规则)。这同时堵住今天「改组织币种就把所有历史金额换标签」的隐患。currency: 'USD'when the currency format is picked with no currency — store nothing and inherit the field / tenant chain (#27119 Q2 X, G1) #27156 / spec(field):fixed-modecurrencyConfigwith nodefaultCurrencymaterialises'CNY'— require the code, drop the default (narrowing; #27119 Q2 X, G3) #27157 / docs+examples(currency):opportunity.dataset.tshard-codescurrency: 'USD'over a tenant-currency field, and two protocol docs claim exchange rates / a stored currency code — delete the key, rewrite the two sentences (#27119 Q2 X G4 + Q3) #27158,spec(formula): acurrencyhole with no argument still formats in US dollars — refuse it at authoring in every template dialect (follow-up of #26660) #26939 改目标)。G3 收窄fixed(spec(field):fixed-modecurrencyConfigwith nodefaultCurrencymaterialises'CNY'— require the code, drop the default (narrowing; #27119 Q2 X, G3) #27157)尤其是前置——多币种的「本位币影子列」正是一个fixed字段,它不能再静默变成 CNY。11. 创业阶段:范围裁剪与工作量
做(C′ 的最小集):1 个枚举成员(
record)· 3 个键(currencyField、rateDate、本位币影子列声明)· 1 个系统对象(sys_exchange_rate)· 1 个重算操作 · 护栏放进既有门(spec 构建校验、analytics 编译腿),⛔ 不新增check:*门禁。不做:行情源 · 个人显示币种 · 期末重估 / 未实现汇兑损益 · N×N 汇率对 · 读时换算 · 一个组织多本位币(多主体)· 按地区的舍入规则 · 公式换算函数。
工作量:spec 1 PR(模式 + 键 + 构建校验与拒收用例)· server 2–3 PR(系统对象 + 写入时折算与缺汇率拒写 + 重算操作)· analytics 1 PR(分组拒收与逐行币种)· ui 1 PR(币种字段显示与影子列只读);估 1–2 周 agent 时间 + 契约复核;无先例。
12. 时序与触发
sys_exchange_rate+ 手工 / 导入 → M2 写入时折算 + 影子列 + 缺汇率拒写 + 重算 → M3 analytics 分组结果逐行带币种与 ui 只读展示。13. 风险清单
record模式字段与影子列,退回单币种要迁移数据。(currency, effective_date)索引,可缓存当日)。opportunity.dataset.tshard-codescurrency: 'USD'over a tenant-currency field, and two protocol docs claim exchange rates / a stored currency code — delete the key, rewrite the two sentences (#27119 Q2 X G4 + Q3) #27158 把两句虚报改掉,避免 AI 作者按文档写出不存在的键。14. 四轴小结与推荐
推荐:Q1 乙 = C′ 形状,维持暂停(#8345 留
pm:on-hold,Restart-when:改写为上面三个开工信号之一),并在本裁决同笔把子问题裁清:Q0 A(兄弟币种字段)· 汇率 A(手工 / 导入、缺则拒写)· 本位币 A(沿用localization.currency,有汇率或影子列即锁定)· 换算时机 A(写入时 + 显式重算)· 汇总 A(未分组拒收、合并走影子列)。15. 裁后执行
Restart-when:;本卡 Q1 录第二条Ruling:,卡随之关闭 completed(Q2 已立卡)。Blocked-by:链),[Epic] multi-currency (shape C′): record currency, dated exchange rates, write-time home-currency projection — 17 children, R1/R2/R3 release pairing (ruling 6465431966) #8345 转pm:blocked指向 epic。pm:queue。objectstack-fleet commentedon Oct 28, 2026
Workload list if C′ starts now (Q1 乙-现在) · director seat · summon #53 · 2026-10-28T09:03Z
Asked by the maintainer after the analysis 6464438764: 「如果启动这个开发,评估具体的工作量清单」. Read on
origin/maine6e41998; precedents named per PR. ⛔ Not a ruling, not a claim. Sizes are the fleet's: S = one subagent run, M = one cloud session (hours), L = a day or more with review loops; Tier = who lands it.Prerequisite already filed: #27157 (
fixedrequires a code; the home field must never fall back to CNY) — on the critical path before M0-1.M0 — record currency + guards (= A′; invoicing in the customer's currency works after this)
CurrencyConfigSchemagainscurrencyMode: 'record'+currencyField(a text field on the same object withvalueDomain: 'iso_4217_currency'); object-levelsuperRefineinobject.zod.ts(precedent L1038/L2706 arms) checks the target exists, is text, carries the domain;formulareturnType: 'currency'shares it (#22727); describe / JSDoc / guidance; generated baselines, forms,packages/spec/docs;skills/**authoring guidance (Skills-docs: updated here); pin tests for the three refusals.Metadata-protocol: added, minor.CONTRACT_REVIEW_TIER(published schema)record-validator.ts: a record-mode amount with a blank currency code refuses loudly (ERR_CURRENCY_CODE_REQUIRED);engine-aggregate-field-type-door:sum/avg/min/maxover a record-mode field refused unless grouped by itscurrencyField;summaryroll-up over a record-mode child field refused at metadata registration (precedent: the aggregate × type door). The design round's probes become fixtures (fx_invoice: USD 100 + EUR 100 must refuse, not answer 200).contracts/analytics-service.ts: grouped results carry a per-rowcurrency;cube-measure-field-type-door.ts(precedent) refuses a raw aggregate over a record-mode field unless the query groups by its code field; the Q6 guards on today's surface: a derived measure across measures whose resolved currencies differ refuses; a measurecurrencycontradicting afixedfield's refuses.CONTRACT_REVIEW_TIER;Seam:card, spec seat owns the keyui/packages/i18n/src/currency.tsresolveFieldCurrencyreads the sibling code field's VALUE for record mode (needs the record in scope); callers:fields/src/widgets/CurrencyField.tsx,GridField.tsx,FormulaField.tsx, record drawer,plugin-dashboardmetric tile (per-group currency), grid footer sum (refuse / group); Studio field editor:recordmode + field picker. 29 files name the resolver (changelogs included); ~8 source files move.content/docs/data-modelingcurrency page (modes, the record mode, what refuses); #27158 lands first.M1 — the rate table (independent of M0; can run in parallel)
sys_exchange_rateobject:currency(text,iso_4217_currency),rate(decimal, high precision, "1 home unit = rate units of currency" documented once),effective_date(date),source(select: manual / import / app); unique (currency, effective_date); tenant scope; admin-only permission set; audit on. Precedent files:audit/sys-*.object.ts(142–294 lines each) + tests; Setup nav entry (apps/setup.app.ts) and a list view.sys_import_jobserves an arbitrary object (if yes: 0 code, one docs line; if no: a CSV importer for this object, S). Provider feeds stay out: an app flow writes rows.localization.currencyrefuses a change once asys_exchange_raterow or ahomeAmountdeclaration exists.service-settingshas no write-side predicate hook today (config-change-audit.tsis after-the-fact) — a new guard with one predicate reading the engine; precedent for the shape: the settings manifestrequired/ value-domain refusals.M2 — write-time home projection (the core; consolidation works after this)
homeAmount: { field, rateDate, rateField? }:fielda storedcurrencyfield indynamicmode (= tenant currency) marked platform-computed;rateDateadate/datetimefield on the same object (⛔ no default);rateFieldoptional decimal.superRefinearms for each; no silent default anywhere.Metadata-protocol: added, minor.CONTRACT_REVIEW_TIEReffective_date≤ rateDate); home = round(amount × rate, minor units of the home currency) —CURRENCY_FRACTION_DIGITSgets its first reader; same-currency-as-home ⇒ rate 1, no lookup; write home + rateField; missing rate refuses the write (ERR_EXCHANGE_RATE_MISSING, naming currency + date); validator: home and rate fields read-only (precedent:summary, L1173–1180); batch writes; driver-agnostic (engine-computed column, assummary-aggregate.ts); SQLite float-storage rounding pinned. Intra-record, so simpler than the cross-record roll-up machinery.os migrate recompute-home-amounts --object X [--since DATE] [--apply]modelled onsummary-backfill.tsandcommands/migrate/activity-changed-fields.ts(report-then-apply); rows whose rate is missing are listed, not silently skipped — the apply/skip rule is a parameter for the maintainer (below).os migrate); backfill = M2-3. Verify, one test.M3 — reporting, UX, dogfood
homeAmount; grid footer for record-mode columns groups or refuses.examples/app-showcaseinvoicegainscurrency_code,amount(record mode),amount_home,invoice_date; seededsys_exchange_raterows; adocs/qa/platform-checklistitem; one browser dogfood run.docs/adr/**, maintainer approves)Totals and path
fixed-modecurrencyConfigwith nodefaultCurrencymaterialises'CNY'— require the code, drop the default (narrowing; #27119 Q2 X, G3) #27157 → M0-1 → M2-1 → M2-2 → M2-3 → M3-3 (five serial PRs, ~2 weeks of it); M1 and M0-2/3/4 run beside it.Parameters the maintainer decides (defaults in bold; a letter on each is enough)
sys_exchange_rate: org admin only / any user with a dedicated permission.rateunits of the foreign currency (Odoo) / the inverse (Dataverse). One line in the ADR either way.rate: 12 significant decimals / other.What is not in this list (and why)
Provider feeds, per-user display currency, period-end revaluation / unrealised FX gains, N×N pair rates, read-time conversion, multiple home currencies per organization, locale rounding rules, a CEL
convertCurrency()— each a capability with no pull in tree, and the last one conflicts withstdlib.ts:7.Confidence gaps
sys_import_jobserving an arbitrary object is unverified (decides M1-2's size).objectstack-fleet commentedon Oct 28, 2026
Ruling: batch #421 item 2 (second record, Q1) · letter 乙-现在 · maintainer 「同意立专题卡」 2026-10-28T09:29Z
Recorded by the director seat (summon #53,
session_01QSeKYo7qYsX6HsAneHBMP2, ledger objectstack#12708). First record 6464202770 (Q2 X). The maintainer read the analysis 6464438764, the workload list 6464936321 and the seat's answers in the summon chat (rate sources, per-object opt-in, no platform switch, author-named fields, UI entry, enabled-currency derivation, risks, staged releases, zero impact on single-currency apps), then answered 「同意立专题卡」 — the epic form the seat proposed for 乙-现在.Q1 = 乙-现在: multi-currency resumes now, shape C′, as an epic on #8345.
valueDomain: 'iso_4217_currency', referenced bycurrencyField; rates A —sys_exchange_rate{ currency, rate, effective_date, source }, manual entry and import in v1, provider feeds by an app flow, a missing rate refuses; home currency A —localization.currency, locked once a rate row or a home-amount declaration exists; conversion timing A — at write, with an explicit recompute operation; aggregation A — ungrouped raw aggregates refused, consolidation through the home field.sys_exchange_rateeditable by organization admins only; 3 rate direction "1 home unit =rateunits of the foreign currency"; 4ratekeeps 12 significant decimals.currencyMode: 'record'is the switch; no injected columns, no fixed field names — every currency-related field is declared by the app author and referenced by pointer, the naming convention (currency_code,AMOUNT_home,AMOUNT_rate) is guidance inskills/**, never a contract; "enabled currencies" is derived (home + currencies with a rate row + codes already used), not a setting; the form pre-fills the organization's currency at the renderer, the stored value is always explicit; metadata never names the home currency (the home field is adynamic-mode currency field).fixed-modecurrencyConfigwith nodefaultCurrencymaterialises'CNY'— require the code, drop the default (narrowing; #27119 Q2 X, G3) #27157 + the ADR + M0-1..M0-4 in ONE Version Packages release (a publishedrecordmode without the refusals and the per-row display is worse than today); R2 = M1 + M2-1..M2-3 (+ M3-2's read-only display) in ONE release (the rate table's only reader is the write-time projection; thehomeAmountkey never ships ahead of the engine); R3 = the rest of M3. M1 is therefore developed in parallel but released with M2.Workload: 16 PRs per 6464936321 plus one XS prerequisite (retire the
@deprecatedCurrencyValueSchema, still exported atfield.zod.ts:581against ADR-0104 D1's "deleted"); L 1, M 10, S 6; about 9–12 agent-days; ~3 weeks calendar at 2–3 lanes; critical path #27157 → M0-1 → M2-1 → M2-2 → M2-3 → M3-3.Execution: #8345 becomes the epic parent (
pm:on-hold→pm:epic, title and body rewritten with the shape, the rulings above, the territory and the child tree); the children are filed in this act asPart of #8345(spec and seam children go to the spec seat's queue withdomain:spec; the rest carrypm:epicfor the epic PM); the maintainer summons the epic PM with/pm-dispatch epic:#8345. This card closescompleted: Q2's execution cards are #27156 / #27157 / #27158 / #26939; Q1's is the epic.objectstack-fleet commentedon Oct 28, 2026
Closed completed · both letters recorded: Q2 X (6464202770) and Q1 乙-现在 (6465431966, maintainer 「同意立专题卡」) · director seat (summon #53,
session_01QSeKYo7qYsX6HsAneHBMP2) · 2026-10-28T09:34ZExecution lives elsewhere: Q2 → #27156 / #27157 / #27158 and #26939 (re-scoped); Q1 → the multi-currency epic on #8345 (shape C′, children filed as
Part of #8345, the maintainer summons/pm-dispatch epic:#8345). The analysis (6464438764) and the workload list (6464936321) stay on this thread as the epic's design record. Nothing is left on this card.docs(adr): ADR-0144 multi-currency — record currency, dated rates, wr…
docs(adr): ADR-0144 takes the three should-fixes of contract review 6…