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

Open
4 of 4 issues completed

Description

@os-zhuang

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

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 seat: ⚪ vacant · last seat os-bill · session_01Utp6CZWjnSeed8H4dMjt9d signed off 2026-11-05T22:08Z on the maintainer's word 「server 处理完当前任务,合并后就下班」/「开发中的尽快收工」 (project chat 2026-11-05); patrol routine deleted, mailbox PR #30098 closed. Predecessor ledger (brief 6540325320) is superseded by the one below.

Seat ledger at sign-off (2026-11-05):

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)

R2.1 — ruling 6670707546 (Open item 13: organization-scoped backfill)

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
    pm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold comment
    on Aug 13, 2026
  2. added theissue type on Aug 13, 2026
  3. 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

  4. 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

  5. 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

  6. 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

  7. 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

  8. 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

  9. 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

  10. 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

  11. 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

  12. 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

  13. 26 remaining items

  14. 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.

  15. 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

  16. 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

  17. os-bill commented on Oct 30, 2026

    @os-bill
    Collaborator

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

    Ruling confirmed: R2 development starts now; only the R2 release waits for the customer's consolidation terms · letter A · maintainer chose 「A 现在启动 R2 开发」 on the decision card in the project review thread 「查清多币种 #27579 为何排在发版后」 2026-10-30T12:15Z

    Recorded by the review thread's session session_01Utp6CZQpM1MNKd4cQai54c (project thread). The brief is kept in the project files (epic-8345/post-r1-status-2026-10-30.md). ⛔ Not a claim; this comment implements nothing.

    What the maintainer asked (2026-10-30T11:26Z): why the epic's follow-ups wait for the 18.3.0 release. Findings: ruling 6486427200 required only that #27579 come after R1 lands (「ships in whichever Version Packages release it is ready for」); the "after the release" hold was the epic PM seat's own scheduling, with no technical or process ground, and it was overtaken by the lane — #27579 landed as PR #27991 (2026-10-30T07:17Z) with docs follow-up PR #28015. The pairing rule in ruling 6465431966 constrains releases, not development; the R2 gate in that ruling's commitment line is the customer's terms (home currency, rate source, which date drives the rate).

    A — build R2 now, release R2 when the customer confirms the terms.

    Ruling-ref: this comment.


    Generated by Claude Code

  18. os-bill commented on Oct 31, 2026

    @os-bill
    Collaborator

    Ruling: 18.3.0 (Version Packages #26517) and the R2 core · Q1 letter A′ · Q2 letter B · maintainer 「同意」 (os-bill, project thread 「决定 18.3.0 是否带出半成品 R2」, 2026-10-31T05:03Z, on the v2 brief recommending A′ and B) · 2026-10-31T05:1xZ

    Question 1 — how 18.3.0 ships against R2. The brief's v1 premise (the homeAmount key on main without its engine, ADR-0144 D9's half-state) was overtaken on 2026-10-31: the home-currency lock PR #28297 is ready with auto-merge (contract review PASS 6538875208, seat ACCEPT 6538881146) and the write-time projection PR #28302 passed its contract review with no must-fix (6539117549). Ruled A′:

    Question 2 — the rate day of a datetime document date (#28302 open question 2, escalated by the contract review). Ruled B: a datetime rateDate value is read as the calendar day in the organization's localization.timezone (default UTC, so an organization on the default is unchanged), not the UTC day. Execution: HomeAmountServing gains timezone(organizationId) beside homeCurrency(organizationId), read through the same settings bridge (getMany('localization', …)), never from the request context, so REST, import and flow writes pick the same day; documentDateOf(value, timezone) formats with Intl.DateTimeFormat('en-CA', { timeZone }) (the reading autonumber-format.ts already uses); a date value is its own day. Reason: localization.timezone is already what today() and analytics date bucketing resolve against (its manifest, lines 9–14), and under the UTC reading an organization on UTC+8 takes the previous day's rate for every document stamped 00:00–08:00 local while reports bucket it into the local day; the day is stored semantics, cheaper settled before release than recomputed after. Lands in #28302's fix round; the ADR amendment (review S4) records it. Not chosen: A (UTC now, organization timezone as a later card — a semantic change over stored rows once released). The skills/docs steer toward a date field is no longer needed.

    一句话:18.3.0 等引擎 #28302 与币种锁 #28297 落地后一起发出(R2 核心随 R1 发,#27193/#27190/#27196 为 R2.1);datetime 单据日期按组织 localization.timezone 的日历日取汇率。维护者原话「同意」(两问均按呈批推荐:A′ / B)。Prior rulings read: 6465431966(C′ 与 D1–D9)、6522614678(R2 开发现在、发布等客户——本裁决改其发布门一条)、6469382727(scale 12)、6486427200、6492000994。呈批全文:/mnt/project-files/decisions/26517-发版带出R2半成品-呈批.md(项目文件)。

    Execution after the ruling. The epic PM (session_01Utp6CbTZrFqRYNgjSaSbN5) carries both into #28302's fix round and the ADR-0144 amendment PR; #28297 may merge now; once #28302 is on main, dispatch refresh_version_pr and tell the maintainer #26517 is ready to merge. Ledger row in the project's rulings file.


    Generated by Claude Code

  19. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    Contributor

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

    Sign-off brief: epic PM seat · 2026-10-31T06:55Z

    The seat is vacant. The ledger is in this card's body (the "Seat ledger at sign-off" section). check-handover exited 0: no unreleased claim, and no decision outside the inbox.

  20. objectstack-fleet commented on Nov 4, 2026

    @objectstack-fleet
    Contributor

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

    Seat online: epic PM · 2026-11-04T13:47Z · round 1 open

  21. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    Contributor

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

    Ruling: ADR-0144 Open item 13, second half (PR #28128 review 6525881524 should-fix S1) · letter C · maintainer 「同意」 2026-11-05T22:02Z

    Marker: maintainer-confirmed (decision thread 「决定云租户和多组织回填」, brief decisions/28128-S1-云租户与多组织回填-呈批.md; the maintainer answered 「同意」 on the recommended letter C at 2026-11-05T21:57Z).

    Question. The explicit backfill ruled in 6492000994 (os migrate record-currency-codes) has one entry point, a CLI that needs shell access to the deployment's database; a cloud-managed tenant has none. And the run walks every organization under the unscoped system context and writes the one --code into each organization's codeless rows, while dynamic amounts displayed in each organization's own localization.currency, so on a multi-organization deployment that code is wrong for some organizations, silently.

    Governing text. packages/rest/src/meta-collection-routes.ts (POST /meta/_migrate-stored): "The server-side form of os migrate meta --stored. The CLI form needs shell access to the deployment's database, which a hosted operator does not have". ADR-0134 D2: "no shape in which a cloud-managed deployment and a self-hosted one differ on this: the mechanism is identical, and it is in this repository". ADR-0144: the home currency is each organization's localization.currency; sys_exchange_rate is tenant-scoped; D8 rule 4, the code is never derived. engine-driver-read-scope.test.ts: { isSystem: true, tenantId } reaches the driver as an organization predicate, so an organization-scoped walk needs no new mechanism.

    Ruling (C = A now + B as its own card).

    A — narrow the CLI (server lane, S, may ride the S2/S3 follow-up card):

    1. backfillRecordCurrencyCodes gains an organizationId option; the walk and every engine.update run under { isSystem: true, tenantId: organizationId }. The report counts per organization.
    2. os migrate record-currency-codes gains --org <id>. On a database holding more than one organization, a run without --org is refused before any write; the refusal lists each organization's codeless row count and MAY show that organization's current localization.currency beside it as a reference only — ⛔ it is never written (D8 rule 4). --all-organizations is the explicit way to write one code everywhere. A single-organization database behaves as today.
    3. 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 (os migrate recompute-home-amounts) takes the same --org shape from its first PR, with the logic in an engine function so the door below can reuse it.

    B — a tenant-admin door (server lane, own card, M):
    4. A REST twin of the CLI command, shaped on POST /meta/_migrate-stored: one route taking { object, field, code, apply? }, calling the same engine function, ALWAYS scoped to the caller's current organization (no environment-wide form), preview unless apply: true, answering the same report; behind the maintenance-door switch; SDK method and route-ledger row. Gate: setup.write in the caller's current organization is the recommendation (the capability that already says what the organization's currency is); the maintainer did not name one, so the card fixes it, manage_metadata being the alternative. The 409 destructive_change remedy text names this door beside the CLI command once it lands. A Setup/Studio button is a separate domain:ui card, blocked until the ui lane resumes.
    5. Interim, until B lands: a cloud-managed tenant's backfill is run by platform operations on request, with --org. Recorded in ADR-0144 Open item 13 as the interim, not the answer.

    Not chosen: A alone (the cloud tenant still reads a remedy it cannot run); B alone (self-hosted multi-organization CLI still writes one code everywhere); D (operations run it forever — contrary to the _migrate-stored rationale and ADR-0134 D2); deriving the code from localization.currency (D8 rule 4; a dynamic tenant currency has no history).

    • 长远(≥50%):仓内 _migrate-stored 存在的理由就是「托管运维没有 shell」,本题是同一句话的第二次出现;ADR-0134 D2 云托管与自托管不分两套机制。本位币、汇率、dynamic 显示币种全按组织,无组织范围的回填是错的默认值。
    • 业务拉动:18.3.0 已带本位币折算上线,切了模式的云租户今天就在静默漏算,且 409 给的处方他跑不了。
    • 防错:只能看不能跑的处方,下一步就是 force 或「推一个码」;A 把静默写错改成拒,B 把跑不了改成只能写自己组织。
    • 不扩散:A 零新面(tenantId 引擎既有);B 一条路由 + 一个 SDK 方法,照抄 _migrate-stored,无新键、新门禁种类、新 wire 码。
    • Prior rulings read: 6492000994(B,显式回填)、6539345871(A′/B,18.3.0 带 R2 核心)、PR feat(objectql,cli,metadata-protocol): backfill the currency code of rows stored before an amount switched to record mode, and acknowledge the switch with a 409 at the save #28128 复审 6525881524 S1。

    Execution after the ruling. The epic PM seat files the A card and the B card with Ruling-ref: <this comment>, and the ADR/skills close-out as its own Tier H card. Two facts on the tree since the brief was written: #27193 merged as PR #30183 (31fe289f) without --org, so item 3 rides the A card (the engine function recomputeHomeAmounts already exists for a door to reuse); and #30102 merged as PR #30182 (b7075d99), so recording this ruling and the item-5 interim in Open item 13, plus the skills/** line, goes in the new Tier H close-out card.

  22. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    Contributor

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

    Seat offline: epic PM · 2026-11-05T22:09Z

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:specDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:epicReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queuepriority:p2Medium: important, M3

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions