Repository navigation
Commit 475d428
fix(metadata-protocol): a metadata save's audit row names the user who saved it (#27568)
Fixes #27539
Part of #27414
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:元数据保存(`/meta`、`/automation`、删除)写 `sys_audit_log` 时,审计行的 `user_id`
/ `actor` 现在记下保存人,不再是空。
- 为什么改:ADR-0073 D3 要求归属必须具体;此前管理员每次保存元数据,审计日志都显示"无人"。
- 风险与回滚:只改 `SysMetadataRepository.put` / `delete`
写入时带的上下文,权限判定仍按系统身份;回滚即还原本 PR 一个文件。
- 先看:`packages/metadata-protocol/src/sys-metadata-repository.ts` 的
`writeContext` 两处(put 与 delete)。
- 两分钟验证:`pnpm --filter @objectstack/metadata-protocol exec vitest run
src/sys-metadata-repository.recorded-by.test.ts`,预期 15/15 通过。
- 需要你做的:无,席位落地。
## What changes
`SysMetadataRepository.put` and `delete` ran every `sys_metadata` /
`sys_metadata_history` write under `{ ...ctx, isSystem: true }`. That
context carries no `session.userId` and no `attributedUserId`, the only
two sources the audit writer (`plugin-audit` `audit-writers.ts`) reads
for `sys_audit_log.user_id`. So every metadata save's audit row named
nobody, on every door, `/meta` included, even though the save's `actor`
already reached `created_by` / `updated_by` / `recorded_by`.
Both verbs now build one `writeContext` per transaction: `isSystem:
true` as before, plus `attributedUserId: opts.actor` when the save names
an actor. `attributedUserId` is the existing attribution channel
(`ExecutionContext.attributedUserId`, copied by the engine into
`HookContext.provenance.attributedUserId`). The write is still
authorized as the system, and the actor is never placed in `userId`, so
no caller-gating hook reads it as the caller. With no actor the key is
absent, so engine self-writes and seeds stay unattributed, as before.
Reads are unchanged. No new mechanism, no spec change.
## Tests
- New pins in `sys-metadata-repository.recorded-by.test.ts` (the file's
existing engine double now records each write verb's context; no new
double):
- actor case: create, update and delete with three different actors.
Every write (`sys_metadata` insert/update/delete plus the history
insert) carries `isSystem: true`, `attributedUserId` set to that write's
actor, and no `userId`.
- control: with no actor, all six writes carry `isSystem: true` and no
`attributedUserId` key.
- A real-store pin with the audit writer installed cannot live in this
package. `@objectstack/metadata-protocol` has no driver, objectql or
plugin-audit dependency, and objectql depends on this package, so
importing it would create a cycle. The engine-to-audit-writer leg was
measured end to end instead (Verification below).
<details><summary>Package suite and typecheck at 9bc0b58</summary>
`pnpm --filter @objectstack/metadata-protocol typecheck && pnpm --filter
@objectstack/metadata-protocol test` under the verify lock: `VERDICT
command-exit 0`, Test Files 279 passed, 4 skipped (283). Tests 21783
passed, 26 skipped.
</details>
<details><summary>Ablation (fix committed first, src import so no
rebuild needed)</summary>
`scripts/ablation-replace.mjs` removed the `attributedUserId` spread
from `put`'s `writeContext`. Anchor 1 -> 0, blob 5b8de92 ->
3ee7c3a8b2eb. Result: 1 failed, 14 passed. The failure was the actor
pin; the control stayed green. Restore: blob == HEAD 5b8de92, `git
diff HEAD` empty.
</details>
## Acceptance notes
- The `/automation` doors pass no `actor` until #27538 lands. Before
that PR, their audit rows stay null for the same reason their
`created_by` does.
- `pnpm check:system-context-census` passes with no register edit. The
change spreads `isSystem: true` exactly as before, and the census does
not count that as a new elevation read site.
## Verification
<details><summary>Live, stock showcase, fresh DB, signed in as the
seeded admin</summary>
Branch build at this PR's head, plus #27538's one-file runtime change
applied locally and not committed. I read `GET
/api/v1/data/sys_audit_log` filtered to `object_name = sys_metadata`.
Every door answered 200:
- `PUT /api/v1/meta/flow/qa_meta_27539` gave a create row with `user_id`
and `actor` set to the admin id.
- `POST /api/v1/automation` gave a create row with `user_id` and `actor`
set to the admin id.
- `PUT /api/v1/automation/qa_auto_27539` gave an update row with
`user_id` and `actor` set to the admin id.
- `DELETE /api/v1/meta/flow/qa_meta_27539` gave a delete row with
`user_id` and `actor` set to the admin id.
Earlier run without #27538's change: the `/meta` create and delete rows
held the admin id. The `POST /api/v1/automation` create row had
`user_id` null and `actor` null. That door passes no actor yet, so this
is the no-actor control.
</details>
<details><summary>Gates at 9bc0b58</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 60 families, and all 60 exited 0.
Reconciliation `--ran`: "60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN".
`test:repo` and repo-wide lint are left to CI.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_017xRBcScm27Q3R5mkYLPWdB)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 33ab543 commit 475d428
3 files changed
Lines changed: 95 additions & 8 deletions
File tree
- .changeset
- packages/metadata-protocol/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
40 | 43 | | |
41 | 44 | | |
42 | 45 | | |
| |||
60 | 63 | | |
61 | 64 | | |
62 | 65 | | |
| 66 | + | |
63 | 67 | | |
64 | 68 | | |
65 | 69 | | |
| |||
74 | 78 | | |
75 | 79 | | |
76 | 80 | | |
77 | | - | |
| 81 | + | |
| 82 | + | |
78 | 83 | | |
79 | 84 | | |
80 | 85 | | |
| |||
86 | 91 | | |
87 | 92 | | |
88 | 93 | | |
89 | | - | |
| 94 | + | |
90 | 95 | | |
| 96 | + | |
91 | 97 | | |
92 | 98 | | |
93 | 99 | | |
94 | 100 | | |
95 | 101 | | |
96 | | - | |
| 102 | + | |
97 | 103 | | |
| 104 | + | |
98 | 105 | | |
99 | 106 | | |
100 | 107 | | |
| |||
260 | 267 | | |
261 | 268 | | |
262 | 269 | | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1147 | 1147 | | |
1148 | 1148 | | |
1149 | 1149 | | |
| 1150 | + | |
| 1151 | + | |
| 1152 | + | |
| 1153 | + | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
1150 | 1161 | | |
1151 | 1162 | | |
1152 | 1163 | | |
| |||
1203 | 1214 | | |
1204 | 1215 | | |
1205 | 1216 | | |
1206 | | - | |
| 1217 | + | |
1207 | 1218 | | |
1208 | 1219 | | |
1209 | 1220 | | |
1210 | 1221 | | |
1211 | 1222 | | |
1212 | 1223 | | |
1213 | | - | |
| 1224 | + | |
1214 | 1225 | | |
1215 | 1226 | | |
1216 | 1227 | | |
| |||
1235 | 1246 | | |
1236 | 1247 | | |
1237 | 1248 | | |
1238 | | - | |
| 1249 | + | |
1239 | 1250 | | |
1240 | 1251 | | |
1241 | 1252 | | |
| |||
1356 | 1367 | | |
1357 | 1368 | | |
1358 | 1369 | | |
| 1370 | + | |
| 1371 | + | |
| 1372 | + | |
| 1373 | + | |
| 1374 | + | |
| 1375 | + | |
| 1376 | + | |
1359 | 1377 | | |
1360 | 1378 | | |
1361 | | - | |
| 1379 | + | |
1362 | 1380 | | |
1363 | 1381 | | |
1364 | 1382 | | |
| |||
1383 | 1401 | | |
1384 | 1402 | | |
1385 | 1403 | | |
1386 | | - | |
| 1404 | + | |
1387 | 1405 | | |
1388 | 1406 | | |
1389 | 1407 | | |
| |||
0 commit comments