Skip to content

test(dogfood): a home field added to an object that already holds rows migrates as add_columns and the recompute fills it (multi-currency M2-4) - #30186

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ublbgc
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ublbgc

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

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

Fixes #27194
Ruling-ref: 6465431966

Clause-②: no — one new test file under packages/qa/dogfood/test/; no export, schema, route or accepted value changes
Metadata-protocol: none

维护者速读

  • 改了什么: 只加一个证明测试(无产品代码):存量对象先以 record 模式写入 4 行,再补一行 homeAmount 指针和两个字段,走 os migrate apply 同一条延迟 DDL 路径加列,然后跑 recomputeHomeAmounts 回填。
  • 为什么改: 证明 R1 期间建的对象升级到 R2 只需加一行指针:加列是普通 add_columns,回填就是 M2-3 的重算,不需要新门。
  • 风险与回滚: 无产品风险,测试卡。回滚即撤 PR。
  • 先看: packages/qa/dogfood/test/home-amount-added-to-existing-object.test.ts。
  • 两分钟验证: OS_TEST_POSTGRES_URL=… npx vitest run test/home-amount-added-to-existing-object.test.ts(在 packages/qa/dogfood,6 条,SQLite×2 + PG);跳过重算或跳过加列,各自变红。
  • 需要您做的: 无。

What changes

One proving test, packages/qa/dogfood/test/home-amount-added-to-existing-object.test.ts, on three cells: better-sqlite3 (file), sqlite-wasm (file), PostgreSQL (one schema, named skip without OS_TEST_POSTGRES_URL). Each boot is its own engine and its own driver over the same database, as two processes are.

  1. Before. An object whose amount is currencyMode: 'record' with no homeAmount and no home field. Four rows written through the engine: USD 10,000 (2026-03-15), USD 1,500 (2026-04-15), CNY 12.345, SEK 500 (no SEK rate row).
  2. The upgrade boot, as os migrate apply runs it. The amount gains currencyConfig.homeAmount: { field: 'amount_home', rateDate: 'invoice_date', rateField: 'amount_rate' } and the two author-declared readonly fields it names (D8 rule 2). The driver boots with setDeferredDdl(true) (the plan/apply deferral). Pinned:
    • previewDeferredSchemaWork() for the object is exactly [{ kind: 'add_columns', columns: ['amount_home', 'amount_rate'] }], and nothing for any other table (no in-place work);
    • detectManagedDrift() has no entry for the object;
    • flushDeferredSchemaDdl() performs that same entry, and a plan after it is [];
    • all four rows are still there with their amount and code unchanged, and an empty home field and rate.
  3. The recompute (recomputeHomeAmounts, behind os migrate recompute-home-amounts, PR feat(objectql,cli): recompute stored home-currency amounts at the rates in force now, report first, rows without a rate listed and skipped (multi-currency M2-3) #30183):
    • dry run: 4 amounts, 3 changed, 1 skipped, nothing written;
    • apply: the column read back holds the ADR's figures (USD 10,000 at 0.1389 → 71994.24; USD 1,500 at 0.15 → 10000; CNY 12.345 at rate 1 → 12.35), each equal to a fresh write of the same row after the upgrade;
    • the SEK row is listed (rate_missing, SEK, 2026-03-15) and skipped, its home field and rate stay blank, the run is not complete (homeAmountRecomputeComplete false), and a fresh write of that row is refused ERR_EXCHANGE_RATE_MISSING;
    • a re-run changes nothing and still lists the one row.

Finding: the add-column path is ordinary. No refusal, no flag, no data loss: the two columns are a plain add_columns entry, as for any field added to an existing object. Nothing in the migrate path changed.

The in-memory driver has no add-column path (it is schemaless), so it has no cell here; the recompute's in-memory face is packages/objectql/src/engine-home-amount-projection.test.ts.

Why

ADR-0144 D5: a home field added to an object that already holds rows is filled by "an explicit recompute operation" (the recompute's own header names this case). #27194's task asks for the proof that the R1-to-R2 upgrade is one pointer line, the ordinary migration and the recompute, with nothing new in the product.

Verification

  • The file: 6/6 passed on all three cells (PostgreSQL 16, local, OS_TEST_POSTGRES_URL set).
  • Ablation, through scripts/ablation-replace.mjs (each mutation landed, anchor 1 → 0, file restored after):
    • recompute skipped (apply forced to false): 3 failed, one per cell, on the column read back: expected { home: 71994.24, rate: 0.1389 }, received { home: null, rate: null };
    • home field declared without the column migration (the flush replaced by a preview and the deferral disarmed): 6 failed. The plan still lists the add_columns entry, and the recompute's apply writes nothing: its report carries one failures entry per row, no such column: amount_home (SQLite).
  • npx tsc --noEmit -p packages/qa/dogfood: 0 errors. eslint on the file: exit 0.
  • dispatch-gates --commands: 51 commands, exit codes captured before any pipe: 49 exit 0; check-plugin-teardown-shape --self-test exited 3 on the shallow clone and exit 0 after fetching its pinned fixture commit; pnpm check:dual-build-cjs-loads exit 3, NOT MEASURED (it reads every package's dist/; this worktree built only the test's closure), left to CI.

No changeset: a test file in a private package releases nothing (skip-changeset).

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (spec not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; eslint passes on the changed file (whole-repo pnpm lint left to CI).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset: n/a, nothing publishes; both declaration lines are filled in here.
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Utp6CaGBD59SYKhNDMRa8h

@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b7075d990802f04fa7422f654e23df5ea6c64ddf → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

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

Seat review · PR #30186 · 31fe289..cf2a4d5 · TIER_DEFAULT
Verdict: PASS

Must-fix

None.

Should-fix (non-blocking)

  1. The second it depends on the first. engine is only assigned inside test 1 (L208-209), so running test 2 on its own (-t "the recompute fills") fails with TypeError: Cannot read properties of undefined (reading 'getObject') (measured, 2 failed). Fix: put the upgrade boot and flush in a nested beforeAll, or guard it with a clear message. The default whole-file order is fine.
  2. Arm order is not the same as the CLI's. The test arms with setDeferredDdl(true) after engine.init(). The CLI's deferral shadow (packages/cli/src/utils/schema-migrate.ts ~L540) arms before registerDriver forwards. I moved the arm to before registerDriver and got 6/6 green, so nothing changes today. Mirroring the CLI order would make the "as os migrate apply runs it" claim exact.
  3. FYI for the seat, not the author: CI never runs this file on the PR or in the queue. The dogfood shards' if: excludes pull_request and merge_group, so it runs on main pushes only. Its PG cell is also a named skip in every CI job, because the dogfood job has no PG service. The sibling home-amount-projection.test.ts is in the same position. The PG evidence is local runs only (the dev's and mine).

Confirmed

  • Claim proven. Rows are written on boot 1 with the BEFORE schema (record mode, no home field), on a file DB for SQLite and one schema for PG. Boot 2 uses a new engine and driver with AFTER (pointer plus two declared readonly fields), the deferral armed, previewDeferredSchemaWork pinned to exactly add_columns [amount_home, amount_rate] and nothing for other tables, no managed drift, then flushDeferredSchemaDdl, and the plan is empty afterwards. There is no hand-written ALTER. These are the driver primitives that os migrate plan/apply call (schema-migrate.ts L1497/L1608).
  • Not self-referential. The home and rate values are pinned to ADR figures (71994.24 / 0.1389, 10000 / 0.15, 12.35 / 1). They are also compared with toEqual against a fresh insert after the upgrade (amount, currency, home, rate). The SEK row is asserted listed (rate_missing, SEK, 2026-03-15, organization id) and skipped, with home and rate null, homeAmountRecomputeComplete false, and a fresh SEK write refused with ERR_EXCHANGE_RATE_MISSING. A re-run is idempotent.
  • It goes red if the add-column path regresses. Skipping the flush gives 6/6 red. A dist mutation of driver-sql (add_columns plan entry losing a column) gives 6/6 red: test 1 fails on the plan pin and test 2 on no such column. dist was restored byte-identical (sha256 OK).
  • Dev claims reproduced: 6/6 on better-sqlite3, sqlite-wasm and PG 16. Skipping the recompute gives 3/6 red, one per cell (home null). Skipping the column migration gives 6/6 red.
  • Hygiene: the SQLite dir is mkdtemp and removed afterwards (no /tmp/os-home-upgrade-* left). The PG schema os_lv_home_amount_added_to_existing_object is dropped (no os_lv% schema left). Running under TZ=Pacific/Kiritimati, Pacific/Pago_Pago and America/New_York gives 6/6 each. All dates are date-only strings. The cell, adminRaw and LIVE_NAMESPACE shapes match the sibling, and the file reuses the sibling's object and rates. It lands in the isolated project (glob), so no SHARED_SHOWCASE entry is needed.
  • skip-changeset is correct. @objectstack/dogfood is private: true, the diff is one test file, and nothing publishes (AGENTS.md "Post-Task" step 3). CI Check Changeset passed. PR body: Clause-②: no, Metadata-protocol: none, both accurate. No product code, nothing out of scope.
  • Guards: eslint 0, live-db-isolation 0, test-source-alias 0, cross-package-test-inputs 0. tsc on dogfood: 0 errors in this file. The 844 errors elsewhere come from unbuilt sibling deps in my partial worktree, and CI's type checks are green.
  • CI on head cf2a4d5: 51 check-runs, 33 success, 18 skipped, 0 failing or pending. check-expected-skips --pr 30186 exit 0: every skip is in main's roster.

Local commands

# Command (worktree at cf2a4d5, packages/qa/dogfood for vitest) Exit
1 pnpm install --frozen-lockfile --prefer-offline 0
2 turbo run build --filter=objectql... driver-sql... driver-sqlite-wasm... platform-objects... 0
3 vitest run test/home-amount-added-to-existing-object.test.ts (SQLite; 4 pass, 2 PG skip) 0
4 same, OS_TEST_POSTGRES_URL set to local PG 16 (6/6) 0
5 #4 under TZ=Pacific/Kiritimati / Pacific/Pago_Pago / America/New_York (6/6 each) 0/0/0
6 ablation-replace: recompute apply: false (3 failed, restored) 1
7 ablation-replace: flush to preview (6 failed, restored) 1
8 dist mutation: driver-sql add_columns drops a column (6 failed, sha restored) 1
9 variant: arm deferral before registerDriver (6/6, file restored) 0
10 vitest run ... -t "the recompute fills" (order dependence) 1
11 eslint <file> 0
12 tsc --noEmit -p packages/qa/dogfood (0 in file; partial build) 2
13 node scripts/check-live-db-isolation.mjs 0
14 node scripts/check-test-source-alias.mjs 0
15 node scripts/check-cross-package-test-inputs.mjs 0
16 node scripts/pm/check-expected-skips.mjs --pr 30186 0

Cleanup: PG stopped (pg_ctl stop 0), data dir /tmp/pg-review-30186 deleted, worktree removed. No GitHub writes.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

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

Seat ACCEPT · PR #30186 · Head-sha: a098315dcb0d4efa2a755da1a5f29bcee744c97f · 2026-11-05T01:16Z

维护者速读

  • 改了什么: 只加一个 dogfood 测试,证明 R1 期间用老办法建的对象升级到 R2 只需加一行 homeAmount 指针:既有行在先、声明影子列后走普通 os migrate 加列(计划恰为 add_columns [amount_home, amount_rate],无漂移),再跑 recompute 把存量行填满,值与新写入完全一致;缺汇率的行(SEK)列出、跳过、留空。产品代码零改动。
  • 为什么改: 客户升级不应需要特殊迁移步骤;本卡把这条路钉成回归测试。
  • 风险与回滚: 极小,纯测试;@objectstack/dogfood 为私有包,skip-changeset。回滚即撤 PR。
  • 先看: packages/qa/dogfood/test/home-amount-added-to-existing-object.test.ts。
  • 两分钟验证: npx vitest run packages/qa/dogfood/test/home-amount-added-to-existing-object.test.ts(SQLite 两格 + 本地 PG 共 6/6)。
  • 需要您做的: 无(非受管面,席位落地)。

席位意见:证明成立,两条建议已改,可落地。

席位核对

  1. 形态: 1 个文件(+283/-0),4 个提交,无 rebase;Clause-②: no · Metadata-protocol: none,skip-changeset。
  2. 前提实测: 复审者本地 better-sqlite3 / sqlite-wasm / PG16 共 6/6;消融:跳过 recompute 3/6 红、跳过加列 6/6 红、改坏 driver-sql 的 add_columns 计划 6/6 红(已复原、sha256 校验);三个偏移时区各 6/6。增量 cf2a4d5..a098315(1 文件 +25/-11)由席位直接核:升级启动移入独立 beforeAll,延迟 DDL 在 registerDriver 之前启用(同 CLI 的 DeferSchemaDdlPlugin);开发者本地两条测试各自单跑均绿。
  3. 门禁 check-runs: head a098315d success 32、skipped 14,无失败。check-governed-merges --pr 30186:NOT governed,283 行 ≤ 3000。
  4. 接受的偏差: 无。复审记录 6646584723(PASS,S1/S2 已在本 head 落实),Served-tier TIER_DEFAULT。
  5. 范围外去向: dogfood 分片不在 PR 与合并队列上跑(只在 main push 上跑),且 CI 的 dogfood 作业无 PG 服务,PG 格仅有本地证据;同级 home-amount-projection.test.ts 情况相同,属 CI 配置议题,不在本卡。Skills-docs: none。
  6. 落地条款: 非受管面:本笔之后 ready + auto-merge;合并后摘 migrate: adding a homeAmount field to an existing object is the normal add-column path; backfill = recompute — one proving test (multi-currency M2-4, epic #8345) #27194 的 pm:dispatched。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 01:17
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 01:17
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit bedb96e Nov 5, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ublbgc branch November 5, 2026 01:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

1 participant