Repository navigation
Commit e744244
fix(plugin-audit): the compliance ledger keeps no copy of secret-store key material (#26683)
Part of #26640 — the writer half; the at-rest rewrite's run entry point
is #26687.
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:审计台账(`sys_audit_log` 与
`sys_activity`)写入时不再复制密钥存储与两张设置表中的加密密钥材料列;另附一个一次性改写模块,可清除已写入副本中的这些列(热表与归档库)。
- 为什么改:台账是第二份长期留存的副本,此前把应仅限平台访问的加密材料一并复制进去(详情按维护者披露裁决不公开)。
- 风险与回滚:只删副本中的列,不改源表、不改读侧规则;台账仍记录每次创建/更新/删除(哪条记录、标签、密钥
id、算法、版本、操作人与时间)。回滚即 revert 本 PR。
-
先看:`packages/plugins/plugin-audit/src/key-material-copy.ts`(规则与名单)、`audit-writers.ts`
中 `ledgerView` 新增的一行调用、`key-material-copy-migration.ts` 的改写器。
- 两分钟验证:`pnpm --filter @objectstack/plugin-audit exec vitest run
--maxWorkers=2 src/audit-internal-field-omission.test.ts
src/key-material-copy-migration.test.ts`,预期 2 个文件全部通过。
- 需要你做的:无。已写入旧副本的执行入口(`packages/cli` 的 CLI 命令)由 #26687 承接。
## What changes
- **Writer.** `ledgerView` (the one view every ledger and activity copy
is taken through) now omits a closed, writer-held list of key-material
columns, by the same disposition as a field declared `internal`:
omitted, not masked. The list (`KEY_MATERIAL_COPY_COLUMNS`,
`key-material-copy.ts`) names the secret store and the two settings
holders, keyed by the platform-object definitions' own names. Neither
existing limb reached these columns: they are plain text columns, not
credential fields, and not `internal`, because their producers read them
back through the generic engine read. `titleFieldOf` refuses a listed
column as a record title, as it refuses masked and `internal` columns.
- **What the ledger still records.** Every create, update and delete of
those rows keeps its ledger and activity row, with the record id,
producer-scoped label, key id, algorithm, version, actor and time. A
write that changes only the material still records its row, with the
material on neither side. I chose this over excluding the store from the
CRUD mirror: the store has three producers, and only the settings
producer writes the settings audit trail, so an exclusion would leave
the other two with no ledger record at all.
- **The settings holders are in the same class.** Their encrypted-value
column holds a handle when a crypto provider is wired and the material
inline on the legacy adapter path, and a copy cannot tell which. The
holder's own header already says the audit log holds only a placeholder
and a digest of an encrypted value. So the copy carries neither (fail
closed).
- **Copies already written.** `key-material-copy-migration.ts` adds pure
planners (`planKeyMaterialAuditRowPatch`,
`planKeyMaterialActivityRowPatch`) and two runners:
`migrateKeyMaterialCopies` (engine, hot tables) and
`migrateArchivedKeyMaterialCopies` (the archive datasource's driver).
They drive the mail-log body rewrite's runner and two stores, which
`mail-log-body-migration.ts` now exports as `runLedgerCopyRewrite`,
`ledgerCopyStoreOverEngine` and `ledgerCopyStoreOverArchiveDriver`. The
mail-log rewrite keeps its behaviour and its log lines. So there is one
ledger-copy rewrite, and the write call sites stay the two that already
existed: the tenant-audit write-site census is unchanged at 244. Both
runs are dry by default and idempotent. They change only the copy
column, keep each row's stored `updated_at`, and report counts only.
Both runners are exported from the package index.
- **Test config.** `vitest.config.ts` gains one anchored alias for
`@objectstack/platform-objects/system`, so the new census resolves the
definitions from source (`check:test-source-alias`).
- **Comment.** `sys-secret.object.ts`: the `trackHistory: false` comment
now says what the ledger records for the store. Comment only.
## Tests
- `audit-internal-field-omission.test.ts` gains a census block over the
REAL definitions. It pins that the writer-held list equals the census
and that every listed column is declared by its object, so a rename
reds. On create, update and delete, the control column is recorded on
every side and no listed column appears on any side or anywhere at rest
(ledger or activity). A write of only the material still records its
row. A same-named column on an unlisted object is still recorded.
- `key-material-copy-migration.test.ts`: the planners keep every other
key, leave unlisted objects and material-free rows alone, and are
idempotent. The engine runner covers dry run, apply, re-run as a no-op,
writes as the platform keeping `updated_at`, and an unreadable table
counted as a failure with no value in any log line. The archive runner
runs against a real SQLite driver, including a never-created archive
table.
## Acceptance notes
- **Remaining half: the run entry point.** The precedents ship their
at-rest rewrite behind an `os migrate …` command in `packages/cli`,
which is outside this card's file surface, so this PR ships the exported
runners without a command. Copies written before this release keep the
material until an entry point runs them: #26687.
- **The mail-log rewrite was restructured, not changed.** Its runner and
stores were lifted into exported functions so the key-material rewrite
drives the same ones. Its existing test file
(`mail-log-body-migration.test.ts`) passes unchanged. The `os migrate
audit-mail-bodies` integration tests in `packages/cli` exercise it
through the built package and are left to CI.
- **Read side unchanged by design.** The ledger's read rules are not
touched.
## Verification
<details><summary>Writer-layer pin, and its ablation</summary>
On the fix (HEAD `21dca99112`): `vitest run
src/audit-internal-field-omission.test.ts
src/key-material-copy-migration.test.ts` gives `Test Files 2 passed
(2)`, `Tests 54 passed (54)`.
Ablation (`scripts/ablation-replace.mjs`, the writer's one new call
replaced by a no-op, anchor 1 to 0, blob `ad8f76be13d4` to
`6ee8dfd95a08`): `Tests 6 failed | 37 passed (43)`. The 6 red are
exactly the new census cases (3 objects × 2). Restored: blob equals HEAD
(`ad8f76be13d4`), `git diff HEAD` empty. The test imports the writer
from source, so no build stood between the mutation and the run.
</details>
<details><summary>Composition check (one-off, not committed)</summary>
A temporary test exercised the real settings producer, its crypto
provider and a real `ObjectQL` engine over the real definitions, with
the writer installed, through two writes of an encrypted value. On the
fix, none of the encrypted values the producer minted was at rest in the
ledger, and every create and delete row of the store was still written.
With the writer call ablated, every minted value was at rest. No
plaintext was at rest in either run. The scratch file was deleted, and
nothing of it is committed.
</details>
<details><summary>Package suites and gates</summary>
All readings on head `c38dcc7257` (the final commit), exit codes
captured before any pipe.
- `pnpm --filter @objectstack/plugin-audit test`: `Test Files 65 passed
(65)`, `Tests 1047 passed (1047)`, lock `VERDICT command-exit 0`.
- `pnpm --filter @objectstack/plugin-audit typecheck`: exit 0. The test
layer is included (`check:test-typecheck: OK`, 0 errors).
- `pnpm --filter @objectstack/platform-objects typecheck` and `test`
(comment-only edit, read at `cd0e7a5d7d`): exit 0, `Test Files 96 passed
(96)`, `Tests 1361 passed (1361)`.
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands`: 64 commands, every one run. `--ran` gives `64 derived, 64
run, 0 NOT-MEASURED, 0 UNRUN`, a DERIVED zero because all 64 carry an
exit code.
- An earlier pass, before the last commit, was red on
`check-tenant-audit-census` (my first runner added two write call sites)
and on `check:test-source-alias` (the `/system` alias was missing). Both
are fixed by the last commit, and both are green above.
- The derivation flagged the tree as 2 commits behind `origin/main`,
because 2 gate scripts changed there. Neither script is in this card's
64 commands, and the two commits touch neither package, so I did not
merge.
```text
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/check-tenant-audit-census.mjs :: exit 0
node scripts/check-tenant-audit-census.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:i18n :: exit 0
pnpm check:i18n-stale-fill :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
pnpm --filter @objectstack/plugin-audit test :: exit 0
pnpm --filter @objectstack/plugin-audit typecheck :: exit 0
```
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_012YjcDon6BvR9MN93ZsFq3U)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent bb91ae8 commit e744244
10 files changed
Lines changed: 816 additions & 61 deletions
File tree
- .changeset
- packages
- platform-objects/src/system
- plugins/plugin-audit
- src
| 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 | |
|---|---|---|---|
| |||
168 | 168 | | |
169 | 169 | | |
170 | 170 | | |
171 | | - | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
172 | 177 | | |
173 | 178 | | |
174 | 179 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| 43 | + | |
43 | 44 | | |
44 | 45 | | |
| 46 | + | |
45 | 47 | | |
46 | 48 | | |
47 | 49 | | |
| |||
353 | 355 | | |
354 | 356 | | |
355 | 357 | | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
| 24 | + | |
23 | 25 | | |
24 | 26 | | |
25 | 27 | | |
| |||
1558 | 1560 | | |
1559 | 1561 | | |
1560 | 1562 | | |
| 1563 | + | |
| 1564 | + | |
1561 | 1565 | | |
1562 | 1566 | | |
1563 | 1567 | | |
| |||
1854 | 1858 | | |
1855 | 1859 | | |
1856 | 1860 | | |
| 1861 | + | |
| 1862 | + | |
| 1863 | + | |
| 1864 | + | |
| 1865 | + | |
| 1866 | + | |
1857 | 1867 | | |
1858 | 1868 | | |
1859 | 1869 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
91 | 114 | | |
92 | 115 | | |
93 | 116 | | |
| |||
0 commit comments