Skip to content

objectql + cli: backfill the currency code when a stored amount switches to record mode, and a 409 acknowledgement at the metadata door (multi-currency, epic #8345) #27577

Open

Description

@objectstack-fleet

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

Filed by the epic PM under maintainer ruling 6492000994 on #8345 (letter B, 2026-10-29T08:39Z, 「B 放行,欠显式回填」). Part of #8345. ⛔ Not a claim. Lands after R1 (#27268, #27339, #27338, #27340 merged 2026-10-29) and ⛔ before R2 (#27191 / #27192 / #27193): the R2 home projection has no code to convert a codeless row, so a consolidated sum would silently omit it.

Ruling-ref: 6492000994

维护者速读

  • 改了什么: 已有数据的 dynamic / fixed 金额字段改成 record 模式后,旧行没有币种码。新增一个显式回填:运维给定币种码(--code USD),把「有金额、无币种码」的行补上;默认只报告,--apply 才写,可重复跑。元数据保存时,这种切换会返回现有的 409 destructive_change,说明三个后果和回填命令,force 可放行。
  • 为什么改: 不回填的话,这些行显示为纯数字、分组落进 null 组、币种筛选里看不到,R2 合并报表也会漏掉它们。
  • 风险与回滚: 回填只写币种码为空的行,从不推断币种码;409 不读数据行,不加启动时拒绝。回滚即撤 PR。
  • 先看: 先例 packages/objectql/src/summary-backfill.ts 与 os migrate summary-nulls;packages/metadata-protocol/src/protocol.ts 的 detectDestructiveObjectChanges(Phase 3a)。
  • 两分钟验证: 对一个有旧行的对象把金额切到 record:保存返回 409 并点名命令;dry run 报出待补行数,--apply 后再跑一次报 0。
  • 需要您做的: 无,已裁 B。

Task

  • 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 (currencyConfig.currencyField) is blank. ⛔ The code is never derived: not from a retired defaultCurrency, not from localization.currency, not from anything else (ADR-0144 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; a code that fails iso_4217_currency is refused before any write. 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. Whether it writes through engine.update or at the driver as summary-backfill.ts does is the card's call: follow the precedent you pick and say why in the PR.
  • 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.
  • Metadata-door acknowledgement: detectDestructiveObjectChanges (packages/metadata-protocol/src/protocol.ts, the saveMetaItem Phase 3a-destructive gate) gains one finding, currency_mode_to_record: the previous currencyConfig.currencyMode is not record, the next is record, and the field is stored. It answers as the existing HTTP 409 destructive_change with the existing force remedy. Its message names the three consequences (codeless rows display as a plain number, fall into the null group, sit outside every currency-paired filter) and the command above. It reads no rows, like its siblings field_type_change and field_required_no_default. ⛔ No boot-time refusal: a code-declared package registered at boot has no previous version to diff.
  • Tests: the backfill (dry run, apply, idempotence, a row with a code untouched, a blank amount skipped, a bad code refused); the 409 finding (fires on dynamic→record and fixed→record over a stored field, not on a new field, lifted by force); a CLI smoke.

Out of this card's diff, kept with the epic PM (governed paths, Tier H, not mixed into a code PR): the one-line skills/** note (switching a stored field to record leaves rows codeless; run the backfill) and the ADR-0144 open item with its dated note. The epic PM also adds to #27193 that blank-code rows are reported as their own class, apart from missing-rate rows.

Lane: server (objectql, metadata-protocol), with the packages/cli command as the cross-lane half, dispatched by the epic PM per 「epic的卡你统一派」 · Size: M · Review: default. Declarations, for the card to confirm against the tree: Clause-②: yes (widening) (a new member of an existing refusal face, the reading of #27339's review 6490309706) · Metadata-protocol: none (what an author can write is unchanged; force lifts the acknowledgement). Left open on purpose: how a cloud tenant without a CLI reaches the backfill (an admin route or Studio action, M3 or later).

Epic: #8345. Dedupe: #8345 thread, ruling 6492000994, #27193 (R2 recompute: a different operation on the home columns); no open card carries this slice.

Activity

  1. added
    enhancementNew feature or request
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Oct 29, 2026
  2. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

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

    Claim: PM loop round 1 · 2026-10-29T13:42Z
    Session: session_01Utp6CbTZrFqRYNgjSaSbN5 (epic PM, epic:#8345)
    Account: os-bill
    Branch: claude/project-thread-mzbmzo (card thread session_01Utp6CbJa6hKdimkAau1nbA; the platform assigns the thread's one push branch — the recorded platform exception)
    Worktree: objectstack-issue-27577
    Domain: domain:server
    Seat: epic:#8345
    File surface: packages/objectql/src/** (the backfill and its tests), packages/cli/src/commands/migrate/** (the thin command, cross-lane half), packages/metadata-protocol/src/protocol.ts and its tests, .changeset/*; ⛔ no governed path (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: opus (TIER_DEFAULT; dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: yes (widening) — to confirm against the tree
    Metadata-protocol: none — to confirm against the tree
    Responsibility: n/a — not a defect card
    Thread-read: none
    Premise: holds
    Serial constraints cleared: yes — R1 (#27268, #27339, #27338, #27340) landed 2026-10-29; ⛔ lands before R2 (#27191–#27193).
    Release pairing: none — ships in the next Version Packages.
    Maintainer authority: Bill, project thread, 2026-10-28 「epic的卡你统一派」, 「并发可以5」, 「你自主完成后续阶段」; ruling 6492000994 (letter B).

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:dispatchedpm:epicpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions