Repository navigation
fix(runtime): the /automation definition doors record which user saved a flow - #27538
Conversation
…s user id POST /automation, PUT /automation/:name and POST /automation/:name/clone persist through registerAndSaveFlow, which called saveMetaItem with no actor, so the sys_metadata row and its history named nobody. The save now passes the caller's id, read through the toggle door's activationCaller, and nothing when the context has no user. Claude-Session: https://claude.ai/code/session_017xRBcScm27Q3R5mkYLPWdB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xRBcScm27Q3R5mkYLPWdB Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e72ac0b16fbb079ad593d95b8b0f035aa18e7488 && git checkout e72ac0b16fbb079ad593d95b8b0f035aa18e7488
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aef631b80b3175f2f43287a6e0185c828c356ac8 f1f7ce31ee3adc4e2f74dfb574a35f0db76e4792 && git checkout -B drift-repro aef631b80b3175f2f43287a6e0185c828c356ac8 && git merge --no-ff f1f7ce31ee3adc4e2f74dfb574a35f0db76e4792
node scripts/docs-audit/affected-docs.mjs --json aef631b80b3175f2f43287a6e0185c828c356ac8 |
|
domain:devx#1 · sunhaolin · session_017xRBcScm27Q3R5mkYLPWdB 席位核对Verdict: ACCEPT (partial delivery) at head
Open question from the dev (how to finish the Generated by Claude Code |
|
domain:devx#1 · sunhaolin · session_017xRBcScm27Q3R5mkYLPWdB Ready to land at 2026-10-29T11:54Z, held. CI on head The landing step (relay Generated by Claude Code |
Part of #27414. This PR delivers the
sys_metadatahalf: the row'screated_by/updated_byand its history'srecorded_by. Thesys_audit_loghalf is not in this PR. Its nulluser_idis produced inpackages/metadata-protocol, outside this card's claimed file surface, and the same null appears on the/metadoor (measured below). The report on the card says how to finish it.Clause-②: no
Metadata-protocol: none
维护者速读
POST /api/v1/automation(新建)、PUT /api/v1/automation/:name(更新)、POST /api/v1/automation/:name/clone(克隆)保存 flow 时,sys_metadata行的created_by/updated_by和历史行的recorded_by现在记的是这位管理员,不再是空。registerAndSaveFlow,它调saveMetaItem时没带actor。/meta门一直带着,所以同一个 flow 从/meta存能记人,从/automation存就记不到。ADR-0073 D3 要求归属必须具体。sys_audit_log行user_id仍为空,/meta门也一样,根因在packages/metadata-protocol,不在本卡文件面内。packages/runtime/src/domains/automation.ts的registerAndSaveFlow(saveMetaItem那一行);packages/runtime/src/domains/automation-flow-save-actor.test.ts。pnpm --filter @objectstack/runtime exec vitest run src/domains/automation-flow-save-actor.test.ts,预期 3/3 通过:新建、更新、克隆的行都写着调用者 id,无用户的对照写 null。What changes
registerAndSaveFlowinpackages/runtime/src/domains/automation.tsis the one persistence path forPOST /automation,PUT /automation/:nameandPOST /automation/:name/clone. It now passes the caller's user id assaveMetaItem'sactor. The id is read throughactivationCaller(context), the helper the toggle door uses since PR fix(service-automation): the flow toggle records which admin switched a packaged flow #26605: the signed-in user's id from the execution context and nothing else, never from the body. A context with no user passes noactor, so a system write stays unattributed as before.contextinto the function, so no caller changed.automation-authoring-doors-durable.test.ts: two exact-argument pins onsaveMetaItemnow includeactor: 'u_admin', the id of the context those tests already send.Tests
Measured before the change, on the stock showcase (fresh file DB, signed in as admin@objectos.ai), at base ded67bf
The three
/automationdoors and, as a control,PUT /api/v1/meta/flow/:name. Then each newsys_metadatarow and thesys_audit_logrows onsys_metadata:Create and update show the same null actor as clone, so all three are in scope (same function, same fix). The audit rows are null on all four doors, including the
/metadoor that already passesactor.Measured after the change, same rig, runtime rebuilt (head d325afa)
packages/runtime/src/domains/automation-flow-save-actor.test.ts(3 cases). It uses a real store, the same rig asmeta-save-actor.test.ts: better-sqlite3:memory:, the realsys_metadata*objects and a realObjectStackProtocolImplementation. The door is driven through the realHttpDispatcher, and the rows are read back. Create records the creator, update by another user records that user as updater, clone records the cloning admin. The control is a caller with no user: its rows record null on all three doors.Ablation (one mutation, through scripts/ablation-replace.mjs)
saveMetaItem's argument lost theactorspread. Anchor 1 to 0, blob 14a061fa to 540e6524.git diff HEADempty.automation.tsfromsrc(relative import), so no rebuild was needed to observe the mutation.pnpm --filter @objectstack/runtime typecheckexit 0, including its test-layer check.pnpm --filter @objectstack/runtime test: 472 files passed, 7671 tests passed, 19 skipped.origin/main(head f1f7ce3; incoming commits touchobjectql, so its closure was rebuilt): 6 related files re-run (the new pin, durable, tenant-authored-write, flow-clone, meta-save-actor, toggle-caller), 47/47 passed.Acceptance notes
sys_audit_loghalf of automation: cloning a packaged flow writes the sys_metadata row and its audit row with a null actor, so nothing records which admin created the clone #27414.SysMetadataRepository.putinpackages/metadata-protocol/src/sys-metadata-repository.tswrites thesys_metadatarow under{ ...ctx, isSystem: true }, with nosession.userIdand noprovenance.attributedUserId. The audit writer inplugin-audittakesuser_idfrom exactly those two fields, so the audit row names nobody on every metadata save door. The/metadoor is included, and the live control above shows it. Theactorreachesupdated_by/created_by/recorded_bybut never the engine write's context. That file is outside this card's claimed surface, so it is left to the seat.Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon this diff: 61 commands. Each exit code was captured before any pipe.--ranreconciliation: 61 derived, 59 run, 2 NOT-MEASURED, 0 UNRUN.node scripts/check-plugin-teardown-shape.mjs --self-test(shallow clone, its pinned control commit is unreachable; the scan itself exited 0) andpnpm check:dual-build-cjs-loads(needs a full workspace build).eslint --no-inline-configon the three touched.tsfiles: exit 0. The repo-wide lint is CI's.origin/mainbrought no change topackages/runtimeor to anything these gates derive from.Generated by Claude Code