Skip to content

[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

Description

@os-zhuang

Epic: multi-currency (shape C′) · pm:epic · opened by the director seat (summon #53, session_01QSeKYo7qYsX6HsAneHBMP2) on ruling 6465431966 (#27119 Q1 = 乙-现在, maintainer 「同意立专题卡」 2026-10-28).

Epic PM session: session_01Utp6CbTZrFqRYNgjSaSbN5 (parent opened 2026-10-28T09:46Z; account os-bill, project seat; registered 2026-10-28T10:42Z on the maintainer's summon 「使用 pm-dispatch 派发 epic #8345」).

Territory (declared): packages/spec/src/data/field.zod.ts (the currency block: CurrencyConfigSchema, CurrencyValueSchema retirement), packages/spec/src/data/object.zod.ts (currency pointer arms), packages/spec/src/contracts/analytics-service.ts (per-row currency key), packages/objectql/src (record-validator currency arms, aggregate door, the write-time home projection, recompute), packages/platform-objects/src (sys_exchange_rate, Setup nav), packages/services/service-settings/src (home-currency lock), packages/services/service-analytics/src (cube door, grouped currency), packages/cli/src/commands/migrate (recompute command), ui/packages/{i18n,fields,plugin-dashboard,app-shell} (currency readers, form, Studio editor), examples/app-showcase (invoice), content/docs/data-modeling, one new docs/adr/* record. Spec and seam children go to the domain:spec seat's queue; the rest are dispatched by the epic PM.

维护者速读

Rulings this epic executes

Children (17), by release

R1 — record currency + guards (one Version Packages release; ⛔ never ship #27184 alone)

R2 — rates + write-time home projection (one release; the rate table never ships without its reader)

R3 — proof and polish

Critical path: #27157 → #27184 → #27191 → #27192 → #27193 → #27197. Commitment: R1 (about 4–5 agent-days); R2 starts after the customer confirms the consolidation terms (home currency, rate source, which date drives the rate).

Release pairing rule (the only half-state risk)

A published record mode without M0-2/M0-3/M0-4 is worse than today (declared, not refused, wrong symbol); a published homeAmount key without M2-2 is a declaration the runtime never honours. The epic PM marks each R1 / R2 PR body ⛔ not released alone — pairs with …, and the maintainer merges Version Packages only when a pair is complete.

History

The 2026-08 filing (overseas port / terminal customer), the 2026-08-13 v18 scheduling, the 2026-10-07 measurement round and the 2026-10-08 pause are this card's earlier revisions and comments; the hold ended with ruling 6465431966.

Activity

  1. added theissue type on Aug 13, 2026
  2. hotlong commented on Aug 13, 2026

    @hotlong
    Contributor

    Triage: confirming the filer's domain:spec — M1/M2 both start from packages/spec/src/data/field.zod.ts and currency-fraction-digits.ts (new sys_exchange_rate shape, CEL stdlib surface declared there), and the guard-rail item explicitly asks for a schema-level refusal. M3 (service-analytics aggregation) is a downstream consumer, not the anchor.

    Hold (2026-08-13): maintainer already scheduled this for v18 (per issue body) — adding pm:on-hold (the label was missing; domain:spec/target:v18 were already set by the filer).

    Restart trigger files:

    • packages/spec/src/data/field.zod.ts (currencyConfig shape)
    • packages/spec/src/data/currency-fraction-digits.ts
    • packages/services/service-analytics/src/analytics-service.ts (ADR-0053 currency labeling chain)

    Generated by Claude Code

  3. os-zhuang commented on Aug 19, 2026

    @os-zhuang
    ContributorAuthor

    Hold-condition completion (triage seat, session session_014qTKTqjme5Fp5BH9iRmy6t, on-hold weak-hit audit): the v18 park is sound and stands, but it had no executable boundary wake — the repo emits no v18 signal today (zero milestones, no v18 refs, spec at 17.0.0, no .changeset/pre.json, releases end at v17), so the only wake was the trigger-file list, which over-fires on incidental churn and never fires on "v18 opened". Adding the boundary leg; the existing trigger-file list stays for re-pricing.

    Restart-when: the v18 cycle opens — first true of: .changeset/pre.json exists on origin/main, packages/spec/package.json version matches ^18., a milestone or ref matching v18 exists (git ls-remote origin 'refs/*v18*'), or content/docs/releases/v18.mdx is created — or a maintainer re-schedules the card


    Generated by Claude Code

  4. os-warren commented on Aug 24, 2026

    @os-warren
    Collaborator

    H17 trigger-file notice (spec seat, session_01Rxnd8cyFnoU8V5y21PaTsy, post-landing patrol): this hold's declared trigger file packages/spec/src/data/field.zod.ts was touched by PR #11406 (merged 9086761ed, 2026-08-24 ~15:0xZ). Scope of the touch: the #9689 deleteBehavior family only — the authored-set_null-on-master_detail parse rejection plus the ruled idempotent-materialization conditional in the .overwrite(). Zero currency surface moved: currencyConfig shape, currency-fraction-digits.ts and the analytics chain are untouched, and no v18 boundary signal exists (this hold's Restart-when: legs all still read false). Posting per the opportunistic-restart mechanism so the hold owner can judge; from this seat's reading the restart does not fire — no action taken on the hold's state. One forward pointer worth having on this card: the merged PR's ADR draft records the principle "never materialize a default the schema itself would refuse as authored", which will constrain how this card's future currency precision/fractionDigits shapes declare their defaults (#11423 is the near-term instance, now queued).


    Generated by Claude Code

  5. os-warren commented on Aug 24, 2026

    @os-warren
    Collaborator

    H17 trigger-file notice (spec seat, session_01Rxnd8cyFnoU8V5y21PaTsy, merge-time per the #11423 claim's declaration): this hold's declared trigger file packages/spec/src/data/field.zod.ts was touched by PR #11766 (Fixes #11423, merged 21:55Z) — and unlike the three prior comment-only touches on record here, this one moves currency surface: CurrencyConfigSchema's .overwrite() gains a one-conditional guard so the precision default 2 is no longer materialized onto a bare fixed config whose currency's ISO 4217/CLDR fraction digits contradict it (JPY/KRW/KWD class) — parse output omits the key and parse(parse(x)) holds. The superRefine rejection of an AUTHORED contradictory precision is unchanged; currency-fraction-digits.ts and the analytics chain are untouched.

    Restart determination: this hold's Restart-when: legs (v18 boundary signals / maintainer re-schedule) all still read false — the hold stays parked, no state change made. Forward note for whoever prices this card at v18: the merged guard is the second instance of the recorded principle "never materialize a default the schema itself would refuse as authored" (with #11406's master_detail conditional), and any future precision/fractionDigits default this card's M1–M3 shapes declare must be designed under it from the start.


    Generated by Claude Code

  6. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    H17 trigger-file notification (spec seat R5, session_01K93P8PbH7kVjsAXkqzH1zY, 2026-08-25): this wave's dispatch for #11566 (maxLength shape + applicability tightening, ruled 2026-08-24) touches packages/spec/src/data/field.zod.ts, a declared Restart-touch trigger file of this hold. Notification only, per the on-hold trigger-file protocol — no rider work from this hold is taken into that dispatch, and the hold stands unchanged (v18 Restart-when unaffected).


    Generated by Claude Code

  7. os-litant commented on Aug 25, 2026

    @os-litant
    Contributor

    H17 trigger-file notification (spec seat R6, session_01NDGG54XF5gbTLdQzCtnaVV, 2026-08-25): this wave's dispatch for #11949 (+#11950 family-folded — minLength shape/applicability tightening per the maintainer's ruling B of 2026-08-25, plus the deferred maxLength ledger registration) touches packages/spec/src/data/field.zod.ts, a declared Restart-touch trigger file of this hold. Notification only, per the on-hold trigger-file protocol and this card's own precedent (the R5 #11566 dispatch notice above): no rider work from this hold is taken into the dispatch — the touch is the bounded-string minLength constraint only, zero currency surface — and the hold stands unchanged (all v18 Restart-when: legs still read false).


    Generated by Claude Code

  8. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    H17 trigger-file notification (domain:spec seat, session_01PBjwYLS6BciTQW3c9xQiD2, 2026-08-31): this wave's dispatch for the objectui#6140 + objectui#6153 spec half (declare rows?: number on markdown/html field metadata and description?: string on select options — maintainer ruling A of 2026-08-25) touches packages/spec/src/data/field.zod.ts, a declared Restart-touch trigger file of this hold. Notification only, per this card's own R5/R6 precedent: no rider work from this hold is taken into the dispatch — the touch is field-metadata key declaration, zero currency surface — and the hold stands unchanged (the v18 Restart-when: legs still read false).


    Generated by Claude Code

  9. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    H17 trigger-file notification (domain:spec seat, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T18:15Z): this round's dispatch for #13916 (rewrite the false sentence "the canonical spelling depends_on already exists at the field level" in the option-description docblock and its two echoes in the sibling test file — fact layer only, per triage 5486900363) touches packages/spec/src/data/field.zod.ts, a declared Restart-touch trigger file of this hold. Notification only, per this card's R5/R6 precedent: no rider work from this hold is taken into the dispatch — the touch is comment text, zero currency surface — and the hold stands unchanged. The Restart-when: legs re-read on origin/main at 18:14Z all still read false: no refs/*v18* on origin, packages/spec at 17.2.0, no .changeset/pre.json, no content/docs/releases/v18.mdx.


    Generated by Claude Code

  10. claude commented on Sep 4, 2026

    @claude
    Contributor

    H17 trigger-file notification (domain:spec seat, session session_0174WZTU6XcFcS7g2kykC53i, 2026-09-04T00:43Z): this round's dispatch for #14168 (maintainer ruling A of 2026-09-02 — FieldSchema gains a valueDomain slot whose vocabulary is exactly SpecifierValueDomainSchema's three members, shared by reference) touches packages/spec/src/data/field.zod.ts, a declared Restart-touch trigger file of this hold. Notification only, per this card's R5/R6 precedent: no rider work from this hold is taken into the dispatch. Honest characterisation of the touch: a constraint slot on string-storing fields whose vocabulary includes iso_4217_currency as a value domain (membership of a currency CODE) — currencyConfig, the currency field type, currency-fraction-digits.ts and the analytics chain are untouched, and no exchange-rate or home-currency surface moves. The Restart-when: legs re-read on origin/main 97a22639 at 2026-09-04T00:43Z all still read false: no refs/*v18* on origin, packages/spec at 17.2.0, no .changeset/pre.json, no content/docs/releases/v18.mdx. The hold stands unchanged.


    Generated by Claude Code

  11. claude commented on Sep 4, 2026

    @claude
    Contributor

    H17 landing notification (domain:spec seat, session session_0174WZTU6XcFcS7g2kykC53i, 2026-09-04T04:09Z): the dispatch notified at 00:43Z (comment 5534029200) has landed — PR #15133 merged at 04:05:20Z, merge commit 1d7e76a6, touching packages/spec/src/data/field.zod.ts, a declared Restart-touch trigger file of this hold. Landed shape, characterised honestly: FieldSchema.valueDomain, a write-time membership constraint on text fields whose vocabulary includes iso_4217_currency (membership of a currency CODE); currencyConfig, the currency field type, currency-fraction-digits.ts and the analytics chain are untouched, and no exchange-rate or home-currency surface moves. Notification only: no rider work from this hold was taken. The Restart-when: legs re-read on origin/main 5c584231a at 04:06Z all still read false: no refs/*v18* on origin, packages/spec at 17.2.0, no .changeset/pre.json, no content/docs/releases/v18.mdx. The hold stands unchanged.


    Generated by Claude Code

  12. 16 remaining items

  13. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    Contributor

    Related: the default-currency half is filed separately · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-23T15:52Z

    On 2026-10-23 the maintainer kept this card's multi-currency scope on hold, and separately ruled that a new environment gets a default currency from its creator's language or region, in objectstack: 「同意乙」. That is #25495, pm:queue. This card stays pm:on-hold.


    Generated by Claude Code

  14. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    Contributor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Pointer: the shape question is now on decision card #27119 (Q1) · 2026-10-28T06:44Z

    ⛔ Not a claim. Hold unchanged.


    Generated by Claude Code

  15. changed the title [-]currency: no exchange-rate / home-currency conversion — dynamic multi-currency amounts cannot be reconciled or aggregated across currencies[/-] [+][Epic] multi-currency (shape C′): record currency, dated exchange rates, write-time home-currency projection — 17 children, R1/R2/R3 release pairing (ruling 6465431966)[/+] on Oct 28, 2026
  16. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    Contributor

    Epic opened · pm:on-hold → pm:epic · director seat · summon #53 · session_01QSeKYo7qYsX6HsAneHBMP2 · 2026-10-28T09:48Z

    Ruling 6465431966 on #27119 (Q1 = 乙-现在, maintainer 「同意立专题卡」). The body is rewritten as the epic parent (shape C′, the rulings executed, the declared territory, the 17 children by release, the pairing rule); the earlier text stays in the body's revision history. Title rewritten in the same act. Thread-read: 6462572182.

    Children filed in this act: R1 #27182 #27183 #27184 #27186 #27187 #27188 #27189 · R2 #27185 #27190 #27198 #27191 #27192 #27193 #27194 #27196 · R3 #27195 #27197. Spec and seam children (#27182 pm:queue; #27184 #27187 #27191 pm:blocked) carry domain:spec for the spec seat; the rest carry pm:epic for the epic PM, pm:blocked where a Blocked-by: line stands. Prerequisite outside the tree: #27157.

    Next: the maintainer summons /pm-dispatch epic:#8345; that session registers its ID in the body and dispatches from #27183 and #27182.

  17. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    Contributor

    Epic PM · subtree triage and plan · 2026-10-28T11:20Z

    Session session_01Utp6CbTZrFqRYNgjSaSbN5 (account os-bill, seat epic:#8345). Authority: Bill (maintainer), project thread, 2026-10-28 — 「其他同意你的建议,开始派发」, 「你可以自己打domain」, 「epic的卡你统一派」, 「并发可以5」, 「你自主完成后续阶段」, and for the ADR 「写 adr 你现在没有 fable,先用 opus 写,10 小时后用 fable 复审。」

    Domain (the landing package; pm:epic stays on all): #27183 domain:skills (governed ADR) · #27185 #27186 #27190 #27192 #27193 #27194 #27195 #27198 domain:server · #27188 #27196 domain:ui · #27189 #27197 domain:devx.

    Spec children moved into the subtree: #27184 #27187 #27191 gain pm:epic (keep domain:spec, pm:blocked, their Blocked-by:); the epic PM dispatches them. #27182 stays with the spec seat that claimed it (claim 6467480691); the epic follows it to merge.

    Staging (each stage releasable on its own): stage 0 #27157 (other seat), #27182 (spec seat), #27183 (claimed here, 6467611450). R1: #27184 first; once its PR is up, #27186 #27187 #27188 build on its branch in parallel, #27189 alongside; all R1 PRs merge in one window, then one release. R2 waits for the customer's consolidation terms; #27191 and #27185 may be drafted early. R3: #27195 #27197. Concurrency cap 5.

  18. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    Contributor

    epic:#8345#1 · os-bill · session_01Utp6CbTZrFqRYNgjSaSbN5

    Open question for the maintainer: ADR-0144 Open item 12 (epic PM, 2026-10-29T01:30Z)

    Should today's cross-currency arithmetic between fixed fields of different codes be refused? There are two shapes:

    • a dataset measure whose currency contradicts a fixed field's code, and
    • a derived measure over two different fixed codes (e.g. a fixed USD amount minus a fixed EUR amount), which ADR-0144 Context §1 counts among today's silently wrong numbers.

    Ruling 6465431966 (#27119) did not decide this, because its C′ sentence names record-mode fields only. For R1 the epic PM confined #27187 / PR #27338's compile guards to record-mode amounts (decision 6485365183 on #27187; tree evidence #27338 507ed8f9). Refusing the fixed-only shapes would be a narrowing of what compiles today (BREAKING for @objectstack/service-analytics), so it needs the maintainer's word. #27338's first draft (fea128ce) leaned toward refusing both. That lean is quoted here, not adopted. Nothing in R1 waits on the answer.

  19. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    Contributor

    epic:#8345#1 · os-bill · session_01Utp6CYKQd1fNzSJRDLNwnW

    Ruling confirmed: ADR-0144 Open item 12 (fixed-versus-fixed cross-currency arithmetic in dataset measures) · letter A · maintainer chose 「A 拒收,R1 之后单独立卡」 on the decision card in the project review thread 「决定 fixed 字段跨币种计算」 2026-10-29T02:42Z

    Recorded by the review thread's session session_01Utp6CYKQd1fNzSJRDLNwnW (project thread), answering the open question 6485374827 above. The maintainer read the six-section brief (options A / A′ / B / C / D, four axes, confidence gaps) and answered with the letter; the brief is kept in the project files (decisions/fixed-fixed-跨币种-呈批.md). ⛔ Not a claim; this comment implements nothing.

    A — both fixed-only shapes are refused at dataset compile, as their own card after R1.

    Execution parameters, fixed in this ruling:

    os-decision-facets


    Generated by Claude Code

  20. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    Contributor

    epic:#8345#1 · os-bill · session_01Utp6CbQvr4k5jUAV39NCn1

    Ruling confirmed: ADR-0144 new open item (an existing dynamic / fixed amount field switched to record mode on an object that already holds rows) · letter B · maintainer chose 「B 放行,欠显式回填(荐)」 on the decision card in the project review thread 「决定切换 record 模式时的旧数据」 2026-10-29T08:39Z

    Recorded by the review thread's session session_01Utp6CbQvr4k5jUAV39NCn1 (project thread), answering the escalation in PR #27339's contract review 6490309706 ③ ("Escalated to the epic PM … Whether that mode switch is refused while such rows exist, or a backfill operation is owed (the D7 parameter 1 shape), is decided nowhere in the ADR"). The maintainer read the six-section brief (options A / B / B′ / C / D, four axes, confidence gaps) and answered with the letter; the brief is kept in the project files (decisions/record-模式切换存量行-呈批.md). ⛔ Not a claim; this comment implements nothing.

    B — the switch is allowed; it owes an explicit, audited backfill of the code; no row is ever refused for resting.

    What stays as PR #27339 / #27340 / #27338 have it at cf5be508 (the write side is the ADR-0113 D1 non-regression invariant applied to the record pair):

    • A row stored with an amount and no code before the switch is not refused for an edit that leaves the pair alone; it is refused (ERR_CURRENCY_CODE_REQUIRED / 400, prescription first) on the next write that touches the amount or the code.
    • Such a row displays as a plain number, falls into the null group of a grouped aggregate, and is outside every currency-paired filter — until it is backfilled.

    What the ruling adds, as its own card after R1, ⛔ landed before R2 (the R2 home projection has no code to convert such a row, and a consolidated sum over the home field would silently omit it):

    1. Backfill function in @objectstack/objectql, modelled on summary-backfill.ts: for one object and one record-mode amount field, write an operator-supplied ISO 4217 code into every row whose amount is not blank and whose code field is blank. The code is given explicitly (--code USD); ⛔ it is never derived from the retired defaultCurrency, the tenant's localization.currency or anything else (D8 rule 4, [Decision] Currency, all of it: resume multi-currency (#8345) and in what shape, and one rule for an amount whose currency is not known (Studio's USD, the template's USD, fixed's CNY, the example's USD) #27119 Q2 X). A row that already holds a code is never touched. Dry run by default, --apply to write, idempotent (a second run finds nothing), per-row counts (written / skipped: code present / skipped: amount blank). No sys_migration flag: nothing gates on the run.
    2. CLI face os migrate record-currency-codes --object <o> --field <amount> --code <ISO> [--apply] in @objectstack/cli, the same split as summary-backfill.ts / os migrate summary-nulls. packages/cli is the other lane: filed as a Seam: cross-lane half of the card.
    3. Metadata-door acknowledgement: detectDestructiveObjectChanges (packages/metadata-protocol/src/protocol.ts, the saveMetaItem Phase 3a-destructive gate) gains one finding, currency_mode_to_record — previous currencyConfig.currencyMode not record, next record, field stored — answered as the existing HTTP 409 destructive_change with the existing force remedy; the message names the three consequences above and the command in 2. It reads no rows, like its siblings field_type_change and field_required_no_default. It fires only where a previous version exists (REST PUT /meta, Studio, package publish); a code-declared package registered at boot has no previous version to diff, and ⛔ no boot-time refusal is added — a registry that refuses on data state takes the application down after a deploy.
    4. skills/** (Tier H, one line): switching a stored field to record leaves existing rows codeless; run the backfill.
    5. cli: os migrate recompute-home-amounts --object X [--since DATE] [--apply] — report then apply; rows with a missing rate are listed and skipped (multi-currency M2-3, epic #8345) #27193 (R2 recompute): rows with a blank code are reported as their own class, apart from rows with a missing rate (parameter 1).
    6. ADR-0144: a new open item, with a dated note recording this letter (Tier H, one sentence), by the card or the epic PM.

    Declarations (the card's to confirm against the tree): the engine function is additive; the 409 finding is a new member of an existing refusal face — the Clause-② reading of #27339's review (registering a refusal member widens the face) applies, Clause-②: yes (widening), Metadata-protocol: none (what an author can write is unchanged; force lifts the acknowledgement). Not judged, left open: how a cloud-managed tenant without a CLI reaches the backfill (an admin route or Studio action, M3 or later); whether the backfill writes through engine.update or at the driver as summary-backfill.ts does (the card follows the precedent it picks and says why).

    Not chosen: A (refuse while rows exist — no reliable refusal point on the code-package path; a Studio-only refusal is a double standard and pushes authors to the invisible copy-by-hand workaround); B′ (backfill without the 409); C (status quo — R2 consolidation silently low); D (derive the code at the switch — D8 rule 4).

    os-decision-facets


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions