Repository navigation
Commit a5df094
feat(cli): os migrate audit-key-material removes key-material columns from older ledger copies (#26772)
Fixes #26687
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:新增 `os migrate audit-key-material`,驱动 #26683 已合入的
`migrateKeyMaterialCopies`(热表)与
`migrateArchivedKeyMaterialCopies`(归档数据源),从旧版写入的 `sys_audit_log` /
`sys_activity` 账本副本里删除加密密钥材料列;默认 dry run,`--apply` 才写。
- **为什么改**:#26683 只让新写入的副本不再带这些列,旧副本需要运维跑一次命令清理;此前没有入口。
- **风险与回滚**:新命令默认只读,`--apply` 只改副本列、保留 `updated_at`、幂等;另把
`audit-mail-bodies` 的归档判定抽成共享 util(行为与 JSON 输出逐字节不变,其归档集成测试 8/8 绿)。回滚 =
revert 本 PR。
- **先看**:`packages/cli/src/utils/ledger-copy-archive.ts`(从
`audit-mail-bodies.ts` 原样搬出的归档判定,风险点在 `examineArchiveTargets` 的
not-connected / unknown 分类)→
`packages/cli/src/commands/migrate/audit-key-material.ts` →
`packages/cli/src/commands/migrate/audit-mail-bodies.ts` 的删减 hunk。
- **两分钟验证**:`pnpm --filter @objectstack/dogfood exec vitest run
test/audit-key-material.dogfood.test.ts`,预期 4/4 绿(dry run 计数、`--apply` 后
0 份副本带列、重跑无变化)。
- **需要你做的**:无,席位落地。
## What changes
- **`os migrate audit-key-material`**
(`packages/cli/src/commands/migrate/audit-key-material.ts`): a new
one-shot subcommand that removes the encrypted key-material columns from
`sys_audit_log` / `sys_activity` ledger copies 18.2.0 and earlier wrote,
then runs the same rewrite over the archive datasource `sys_audit_log`
declares. It mirrors `os migrate audit-mail-bodies` flag for flag
(`--database-url`, `--apply`, `--yes`, `--force`, `--json`): dry run by
default with the read-only boot, the confirmation gate (exit 1 without
`--yes` when non-interactive), JSON-only stdout under `--json`, the
absent-table read view, and exit 1 when the report counts a failure or
an archive target could not be examined.
- **One archive classification for both commands**
(`packages/cli/src/utils/ledger-copy-archive.ts`): the archive-target
lookup, the declared-connect-failure read and the runtime-datasource-row
probe moved out of `audit-mail-bodies.ts` unchanged, so `examined` /
`none` / `not-connected` / `unknown` mean the same thing in both
commands. `audit-mail-bodies.ts` now calls it; its `--json` document
keeps the same keys in the same order.
- **Registered everywhere `audit-mail-bodies` is**: the one-shot family
table, the absent-database roster (with two control rows), the `--json`
stdout-purity family, the confirmation-exit roster, and the
`composeDeclaredDatasources` docblock in `schema-migrate.ts`.
- **Docs**: a command-table row and a `#### os migrate
audit-key-material` section in `content/docs/deployment/cli.mdx`, and
one line in `content/docs/upgrading.mdx` for operators upgrading from
18.2.0 or earlier.
- **Changeset**: `@objectstack/cli` patch (`Metadata-protocol: none`, so
`patch` under the v18 grading).
## Tests
- `audit-key-material.archive.integration.test.ts` (new, 8): the twin of
the mail-bodies archive test, over the real Archiver. Dry run counts hot
and archived copies and writes nothing; `--apply` leaves 0 copies
carrying the columns in either store, keeps every other key and column,
and leaves an unlisted object's same-named column alone; a re-run
rewrites nothing; a copy still hot at run time is archived without the
columns; no run prints a sentinel; `none` / `not-connected`
classification and exit codes.
- `audit-key-material.dogfood.test.ts` (new, 4): the real `os` binary
over the 18.0 upgrade-harness fixture, which holds 4 `sys_audit_log` and
4 `sys_activity` copies carrying the columns. Dry run counts them
byte-identically, `--apply` removes exactly those keys and nothing else,
and a re-run is a no-op.
- Roster rows added in
`data-commands.absent-database.integration.test.ts`,
`schema-migrate.one-shot-family.integration.test.ts`,
`json-stdout-purity.e2e.test.ts` and
`migrate-confirmation-exit.pin.test.ts`.
<details><summary>Local runs (head f75fee2)</summary>
- `pnpm --filter @objectstack/cli typecheck` (tsc plus
`check:test-typecheck`) and `build`: lock VERDICT command-exit 0.
- CLI unit tier, `vitest run --project unit --maxWorkers=2`: Test Files
339 passed (339), Tests 4927 passed (4927).
- CLI integration tier, the four touched files
(`audit-key-material.archive`, `audit-mail-bodies.archive`,
`data-commands.absent-database`, `schema-migrate.one-shot-family`): Test
Files 4 passed (4), Tests 180 passed (180).
- `json-stdout-purity.e2e.test.ts` (nightly tier, run with
`OS_TEST_TIERS=nightly`): Tests 71 passed (71).
- `migrate-confirmation-exit.pin.test.ts`: Tests 36 passed (36), both
`audit-key-material` cases included.
- Dogfood `audit-key-material.dogfood.test.ts`: Tests 4 passed (4), 9.6
s. Archive integration: Tests 8 passed (8), 36.9 s (shared agent box).
</details>
<details><summary>Ablation (fix committed first; restore proven by blob
hash)</summary>
- **Runner swapped** (`scripts/ablation-replace.mjs`, run mode with its
own restore trap): the command's destructuring of the key-material
runners was pointed at the mail-body runners, so the command boots and
reports but removes no key material. Anchor 1 to 0, blob `370c0585` to
`d3752c2d`.
- archive integration: 4 failed / 8 (cases 1 to 4; 0, 5, 6, 7 pin the
fixture, output hygiene and classification and stay green);
- CLI rebuilt, `ablation-dist-preflight.mjs` found the marker in
`dist/commands/migrate/audit-key-material.js`; dogfood: 2 failed / 4
(dry-run count, removal after apply).
- **Unregistered** (default export removed):
`migrate-confirmation-exit.pin.test.ts` 3 failed / 36 (roster equality
and both new cases). A first attempt was refused by the tool before
running, because its replacement was a substring of the original line
and could not be counted; re-run with a countable marker.
- Restore: blob back to `370c0585` == HEAD, `git status --porcelain`
empty; CLI rebuilt, `--absent` preflight clean over 704 built files;
dogfood 4/4 and archive 8/8 green again.
</details>
## Acceptance notes
- `docs/qa/platform-checklist/areas/cli.json` item
`cli.migrate-plan-apply-json` enumerates the `os migrate` subcommands as
variants and its source note says 19; with this command there are 20.
Left unedited as dispatched; no checklist gate reads the count
(`check-platform-checklist.mjs` and the watchdog both exit 0).
- The upgrade line names "18.2.0 or earlier" because the writer change
ships in the release after 18.2.0; it does not guess that release's
number.
## Verification
<details><summary>Gate derivation (dispatch-gates, head
f75fee2)</summary>
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands`: 93 commands. All 93 run; final exit 0 each.
- Three first refused with exit 3 (`PREREQUISITE NOT MET`, nothing
measured: `check:skill-examples`, `check:dual-build-cjs-loads`,
`check:i18n-coverage` read `dist/` of packages outside the CLI closure).
After those packages were built they re-ran with exit 0.
- `dispatch-gates --ran` over the exit-coded record: 93 derived, 93 run,
0 NOT-MEASURED (a derived zero).
- Families CI runs over the whole workspace are left to the PR's
check-runs.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_012YjcDon6BvR9MN93ZsFq3U)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 76a1275 commit a5df094
13 files changed
Lines changed: 1178 additions & 108 deletions
File tree
- .changeset
- content/docs
- deployment
- packages
- cli
- src
- commands/migrate
- utils
- test
- qa/dogfood/test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1293 | 1293 | | |
1294 | 1294 | | |
1295 | 1295 | | |
| 1296 | + | |
1296 | 1297 | | |
1297 | 1298 | | |
1298 | 1299 | | |
| |||
1311 | 1312 | | |
1312 | 1313 | | |
1313 | 1314 | | |
1314 | | - | |
| 1315 | + | |
1315 | 1316 | | |
1316 | 1317 | | |
1317 | 1318 | | |
| |||
1639 | 1640 | | |
1640 | 1641 | | |
1641 | 1642 | | |
| 1643 | + | |
| 1644 | + | |
| 1645 | + | |
| 1646 | + | |
| 1647 | + | |
| 1648 | + | |
| 1649 | + | |
| 1650 | + | |
| 1651 | + | |
| 1652 | + | |
| 1653 | + | |
| 1654 | + | |
| 1655 | + | |
| 1656 | + | |
| 1657 | + | |
| 1658 | + | |
| 1659 | + | |
| 1660 | + | |
| 1661 | + | |
| 1662 | + | |
| 1663 | + | |
| 1664 | + | |
| 1665 | + | |
| 1666 | + | |
| 1667 | + | |
| 1668 | + | |
| 1669 | + | |
| 1670 | + | |
| 1671 | + | |
| 1672 | + | |
| 1673 | + | |
| 1674 | + | |
| 1675 | + | |
| 1676 | + | |
1642 | 1677 | | |
1643 | 1678 | | |
1644 | 1679 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
480 | 480 | | |
481 | 481 | | |
482 | 482 | | |
| 483 | + | |
| 484 | + | |
483 | 485 | | |
484 | 486 | | |
485 | 487 | | |
| |||
0 commit comments