You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
QA-source: #27409 · automation.setup-packaged-automation-board · observed beside the clauses
Found by an off-clause probe in the 18.3.0 release-candidate QA run #27409, at framework c4c515ad, on the stock showcase.
#26474 fixed the null actor on the packaged flow and action toggle doors, and this run confirmed that fix holds. When #26474 was closed, the clone door was explicitly left out of its scope with a request for a card of its own. Searching open and closed issues found none, so this is that card.
Reproduction
Boot the stock showcase on a fresh file DB and sign in as admin@objectos.ai.
POST /api/v1/automation/showcase_task_completed/clone with {"name":"qa_task_completed_clone","label":"QA clone"}. It answers 200.
Read the new sys_metadata row for qa_task_completed_clone with GET /api/v1/data/sys_metadata/ID?select=created_by,updated_by. Both fields are null.
GET /api/v1/data/sys_audit_log?top=40&$orderby=created_at desc. The create row on sys_metadata for that id has user_id null and actor null, while the toggle rows beside it carry the admin's id.
It is extracted from the 18.3.0 RC run, so it is listed in wave anchor [sweep] v18.3.0 release test sweep #27377 §5 by the runner, and it is retested on the next wave.
Triage: lands in packages/runtime/src/domains/automation.ts (domain:server); rationale: this concurs with the first touch at 06:03Z (comment 6489286673) on class, domain, area and p2. The landing file is now located. Labels stay as they are (bug, priority:p2, pm:queue, area:automation, domain:server). Read at origin/main ded67bfb, 2026-10-29T09:55Z.
Class (b): it violates a declared contract. ADR-0073 D3 says "Attribution is always concrete". The cloning admin's identity is lost on a governed row and its audit row. Reach was measured on the public REST clone door.
Mechanism located (the card says it was not located): the clone door at line 3209 saves through registerAndSaveFlow (line 1920). At line 1942 that calls store.saveMetaItem({ type: FLOW_METADATA_TYPE, name, item: definition }) with no actor from context.
Scope lead: the same registerAndSaveFlow is the one persistence path for POST /automation (line 2890) and PUT /automation/:name (line 3901) as well as for clone. Reading the code, create and update through the automation domain very likely stamp a null actor too. Only clone was measured. The dev should measure create and update first. If they reproduce, the fix in registerAndSaveFlow covers all three doors, and the pins should cover all three. This is the same card and the same mechanism, not a new one.
Prior rulings read: check-prior-rulings.mjs --card 27414 named ADR-0073 D3 (attribution is always concrete), which supports the direction. The other hits (ADR-0029 D9.7, ADR-0044 D2, ADR-0068 D3, ADR-0094 D5) do not govern this, and nothing needs a decision.
Type: Bug.
Size/model suggestion: S, or S/M if create and update reproduce and get pins too.
Claim: PM loop round 1
Session: session_017xRBcScm27Q3R5mkYLPWdB
Account: sunhaolin
Branch: claude/issue-27414-flow-save-actor
Worktree: objectstack-issue-27414
Domain: domain:server
Seat: domain:devx#1
File surface: packages/runtime/src/domains/automation.ts (registerAndSaveFlow and its callers clone / create / update only), its tests under packages/runtime, one @objectstack/runtime changeset (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: default (dispatch-gates --tier at c4c515a: "no path-derived mandate"), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: runtime automation domain code: registerAndSaveFlow calls saveMetaItem without the caller as actor | no platform path stamps it; #26605 fixed the same omission on the toggle doors | reached by every admin who clones (and very likely creates or updates) a flow; the audit trail is used today
Thread-read: 6494081779
Premise: holds (triage first touch re-verified the mechanism on origin/main ded67bfb; the dev re-measures before editing)
Serial constraints cleared: none — the 10 open domain:server + pm:dispatched claims were read; #27237 owns packages/services/service-automation/**, a different package from this card's packages/runtime file
Maintainer direct dispatch (cross-lane claim authority): the maintainer, in session session_017xRBcScm27Q3R5mkYLPWdB on 2026-10-29, verbatim: 「你应该使用 pm-dispatch 技能处理 issue 和 pr」. This devx-seat session claims these domain:server cards on that instruction; the card stays domain:server.
Scope note from triage: registerAndSaveFlow also serves POST /automation and PUT /automation/:name; the dev measures create and update first and fixes them in this PR only if they show the same null actor (same function, same fix).
os-dev-report
{
"issue": 27414,
"status": "rework",
"branch": "claude/issue-27414-flow-save-actor",
"pr": "#27538",
"session": "session_017xRBcScm27Q3R5mkYLPWdB",
"premise_still_valid": true,
"summary": "Partial delivery, PR body opens 'Part of #27414'. Delivered: registerAndSaveFlow (packages/runtime/src/domains/automation.ts) now passes the caller's userId as saveMetaItem's actor, read through activationCaller(context) (the #26605 helper), so POST /automation, PUT /automation/:name and POST /automation/:name/clone stamp sys_metadata created_by/updated_by and sys_metadata_history.recorded_by. Measured live on a fresh showcase before the change: create and update reproduce the clone null actor, so all three are in scope. NOT delivered: the sys_audit_log user_id/actor half of the card's acceptance. Measured live, it is null on every sys_metadata write door, including the PUT /meta/flow/:name control that already passes actor. Mechanism: SysMetadataRepository.put (packages/metadata-protocol/src/sys-metadata-repository.ts) inserts/updates under { ...ctx, isSystem: true } with no session.userId and no provenance.attributedUserId, the only two fields plugin-audit reads for user_id. That file is outside the claimed surface, so I stopped there (see open_questions).",
"tests": "Live HTTP, stock showcase, fresh file DB, admin signed in. Before (base ded67bf): clone/create/update rows created_by=null updated_by=null; /meta control row = admin; all 4 sys_audit_log rows on sys_metadata user_id=null actor=null. After (head d325afa, runtime rebuilt; dist marker grep 1): clone/create/update rows = admin id; audit rows still null on all 4 doors. New real-store pin automation-flow-save-actor.test.ts: 3/3 (create plus update by another user, clone, and a no-user control on all three doors that records null). Durable pins updated to expect actor. Ablation via scripts/ablation-replace.mjs, fix committed first: the actor spread was removed (anchor 1->0, blob 14a061fa->540e6524). Result 4 failed / 10 passed: 2 durable pins and the create/update and clone row pins failed, the control stayed green. Restored: blob == HEAD 14a061fa, git diff HEAD empty. Runtime imports automation.ts from src, so no rebuild was needed. At 8551475 under the lock: runtime typecheck exit 0 (including check:test-typecheck); runtime test 472/472 files, 7671 passed, 19 skipped, VERDICT command-exit 0. After the origin/main merge (f1f7ce3, incoming objectql change, runtime^... rebuilt): 6 related files 47/47, VERDICT command-exit 0.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 8551475 derived 61. --ran reconciliation exit 0: '61 derived, 59 run, 2 NOT-MEASURED, 0 UNRUN'. NOT MEASURED (exit 3, PREREQUISITE NOT MET, left to CI): node scripts/check-plugin-teardown-shape.mjs --self-test (shallow clone, pinned control commit unreachable; the scan run itself exited 0) and pnpm check:dual-build-cjs-loads (no full workspace build). All other 59 exited 0. eslint --no-inline-config on the 3 touched .ts files: exit 0. PR check-runs: in_progress at report time, not waited on.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "3 REST requests across 2 relay dispatches: pr_create (POST /repos/objectstack-ai/objectstack/pulls, then POST /repos//issues/27538/assignees), and this report (POST /repos//issues/27414/comments). git push is not counted.",
"line_budget": "n/a — no skills/** or ledger file touched",
"files_changed": [
"packages/runtime/src/domains/automation.ts",
"packages/runtime/src/domains/automation-flow-save-actor.test.ts",
"packages/runtime/src/domains/automation-authoring-doors-durable.test.ts",
".changeset/27414-flow-save-actor.md"
],
"deviations": [
"Showcase booted with 'pnpm --filter @objectstack/example-showcase dev -- --fresh -p 38714' instead of 'pnpm dev'. Only the showcase closure was built, and check-dev-prereqs refuses a partial build. The server was stopped by its port afterwards.",
"A stray file /build.pid (the pid of the build process) was written at the filesystem root by a mis-scoped shell variable. Removing it was refused by the harness safety check, so it is still there for a person to delete.",
"The gate runner's while-read loop lost stdin after 57 gates. The remaining 4 were rerun with stdin from /dev/null, and all 4 exited 0. A first exit-3 record for check:type-check-debt came from that loop and was removed from the record before reconciliation.",
"The PR body footer uses the AGENTS.md session-URL form, not the harness reminder's emoji form (AGENTS.md takes precedence)."
],
"open_questions": [
{
"question": "How should the sys_audit_log half of #27414 be finished? The audit row names nobody on every metadata save door, /meta included.",
"options": [
"A: widen this card's surface to packages/metadata-protocol/src/sys-metadata-repository.ts and add a second PR there. The put/delete engine writes would carry opts.actor as provenance.attributedUserId, the existing channel plugin-audit already reads for a human credited on a system-authorized write. One edit covers every metadata door. Cost: a metadata-protocol pin plus a check:system-context-census reading. S/M.",
"B: close this card on the sys_metadata half and file the audit half as its own class (b) card across all metadata doors. Cost: one more card. This card's acceptance text would then need amending."
],
"recommendation": "A, as the card's own acceptance names the audit row. The mechanism is one function in one package. The channel already exists and is the one the audit writer documents for exactly this case, so no second mechanism is needed."
}
],
"out_of_scope_findings": [
"class: b · reach: PUT /api/v1/meta/flow/qa_meta_before as admin on the stock showcase -> sys_audit_log create row on sys_metadata has user_id null, actor null (measured live) · evidence: SysMetadataRepository.put writes under { ...ctx, isSystem: true } with no userId/attributedUserId; ADR-0073 D3 'Attribution is always concrete' · Seam: runtime:SysMetadataRepository.put -> plugin-audit audit-writers user_id · this is the undelivered half of #27414 itself; it widens to every metadata door · dedupe words: sys_audit_log sys_metadata user_id null actor, metadata save audit attribution, attributedUserId SysMetadataRepository"
]
}
ACCEPT → PR #27538 (partial: the sys_metadata item, Part of #27414), reviewed 2026-10-29T11:46Z. The sys_audit_log item is sub-issue #27539, claimed by this seat and dispatched. This card closes when both have landed.
Each PR carried Part of #27414, so nothing closed this card automatically. Nothing is left on it, so it is closed and pm:dispatched is off. The retest is automation.setup-packaged-automation-board in the next checklist wave.
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 真挡得住的权限 | none | P②
Triage: p2 ·
domain:server·area:automation· 2026-10-29T06:03Z⛔ Not a claim.
sys_metadatarow and its audit row with a null actor. automation: switching off a packaged flow or a packaged action writes the activation-ledger row and its audit row with a null actor, so nothing records which admin did it #26474 fixed the toggle doors and left clone out on purpose, asking for this card. An audit trail that cannot name the admin is wrong data on a governed record, so p2.bug,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:devx#1 · sunhaolin · session_017xRBcScm27Q3R5mkYLPWdB
Path: 真挡得住的权限 | none | P②
Triage: lands in
packages/runtime/src/domains/automation.ts(domain:server); rationale: this concurs with the first touch at 06:03Z (comment 6489286673) on class, domain, area and p2. The landing file is now located. Labels stay as they are (bug,priority:p2,pm:queue,area:automation,domain:server). Read at origin/mainded67bfb, 2026-10-29T09:55Z.registerAndSaveFlow(line 1920). At line 1942 that callsstore.saveMetaItem({ type: FLOW_METADATA_TYPE, name, item: definition })with no actor fromcontext.registerAndSaveFlowis the one persistence path forPOST /automation(line 2890) andPUT /automation/:name(line 3901) as well as for clone. Reading the code, create and update through the automation domain very likely stamp a null actor too. Only clone was measured. The dev should measure create and update first. If they reproduce, the fix inregisterAndSaveFlowcovers all three doors, and the pins should cover all three. This is the same card and the same mechanism, not a new one.create_recordunderrunAs:'system'inserts rows withowner_id/organization_id/created_byall NULL — records born untouchable even by admin #5494 (other mechanisms). No duplicate.check-prior-rulings.mjs --card 27414named ADR-0073 D3 (attribution is always concrete), which supports the direction. The other hits (ADR-0029 D9.7, ADR-0044 D2, ADR-0068 D3, ADR-0094 D5) do not govern this, and nothing needs a decision.Size/model suggestion: S, or S/M if create and update reproduce and get pins too.
Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:devx#1 · sunhaolin · session_017xRBcScm27Q3R5mkYLPWdB
Claim: PM loop round 1
Session:
session_017xRBcScm27Q3R5mkYLPWdBAccount:
sunhaolinBranch:
claude/issue-27414-flow-save-actorWorktree:
objectstack-issue-27414Domain:
domain:serverSeat:
domain:devx#1File surface:
packages/runtime/src/domains/automation.ts(registerAndSaveFlowand its callers clone / create / update only), its tests underpackages/runtime, one@objectstack/runtimechangeset (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: default(dispatch-gates --tierat c4c515a: "no path-derived mandate"),effort: highClause-②: no
Metadata-protocol: none
Responsibility: runtime automation domain code:
registerAndSaveFlowcallssaveMetaItemwithout the caller as actor | no platform path stamps it; #26605 fixed the same omission on the toggle doors | reached by every admin who clones (and very likely creates or updates) a flow; the audit trail is used todayThread-read: 6494081779
Premise: holds (triage first touch re-verified the mechanism on origin/main
ded67bfb; the dev re-measures before editing)Serial constraints cleared: none — the 10 open
domain:server+pm:dispatchedclaims were read; #27237 ownspackages/services/service-automation/**, a different package from this card'spackages/runtimefileMaintainer direct dispatch (cross-lane claim authority): the maintainer, in session
session_017xRBcScm27Q3R5mkYLPWdBon 2026-10-29, verbatim: 「你应该使用 pm-dispatch 技能处理 issue 和 pr」. This devx-seat session claims these domain:server cards on that instruction; the card staysdomain:server.Scope note from triage:
registerAndSaveFlowalso servesPOST /automationandPUT /automation/:name; the dev measures create and update first and fixes them in this PR only if they show the same null actor (same function, same fix).Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
os-dev-report
{
"issue": 27414,
"status": "rework",
"branch": "claude/issue-27414-flow-save-actor",
"pr": "#27538",
"session": "session_017xRBcScm27Q3R5mkYLPWdB",
"premise_still_valid": true,
"summary": "Partial delivery, PR body opens 'Part of #27414'. Delivered: registerAndSaveFlow (packages/runtime/src/domains/automation.ts) now passes the caller's userId as saveMetaItem's actor, read through activationCaller(context) (the #26605 helper), so POST /automation, PUT /automation/:name and POST /automation/:name/clone stamp sys_metadata created_by/updated_by and sys_metadata_history.recorded_by. Measured live on a fresh showcase before the change: create and update reproduce the clone null actor, so all three are in scope. NOT delivered: the sys_audit_log user_id/actor half of the card's acceptance. Measured live, it is null on every sys_metadata write door, including the PUT /meta/flow/:name control that already passes actor. Mechanism: SysMetadataRepository.put (packages/metadata-protocol/src/sys-metadata-repository.ts) inserts/updates under { ...ctx, isSystem: true } with no session.userId and no provenance.attributedUserId, the only two fields plugin-audit reads for user_id. That file is outside the claimed surface, so I stopped there (see open_questions).",
"tests": "Live HTTP, stock showcase, fresh file DB, admin signed in. Before (base ded67bf): clone/create/update rows created_by=null updated_by=null; /meta control row = admin; all 4 sys_audit_log rows on sys_metadata user_id=null actor=null. After (head d325afa, runtime rebuilt; dist marker grep 1): clone/create/update rows = admin id; audit rows still null on all 4 doors. New real-store pin automation-flow-save-actor.test.ts: 3/3 (create plus update by another user, clone, and a no-user control on all three doors that records null). Durable pins updated to expect actor. Ablation via scripts/ablation-replace.mjs, fix committed first: the actor spread was removed (anchor 1->0, blob 14a061fa->540e6524). Result 4 failed / 10 passed: 2 durable pins and the create/update and clone row pins failed, the control stayed green. Restored: blob == HEAD 14a061fa, git diff HEAD empty. Runtime imports automation.ts from src, so no rebuild was needed. At 8551475 under the lock: runtime typecheck exit 0 (including check:test-typecheck); runtime test 472/472 files, 7671 passed, 19 skipped, VERDICT command-exit 0. After the origin/main merge (f1f7ce3, incoming objectql change, runtime^... rebuilt): 6 related files 47/47, VERDICT command-exit 0.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 8551475 derived 61. --ran reconciliation exit 0: '61 derived, 59 run, 2 NOT-MEASURED, 0 UNRUN'. NOT MEASURED (exit 3, PREREQUISITE NOT MET, left to CI): node scripts/check-plugin-teardown-shape.mjs --self-test (shallow clone, pinned control commit unreachable; the scan run itself exited 0) and pnpm check:dual-build-cjs-loads (no full workspace build). All other 59 exited 0. eslint --no-inline-config on the 3 touched .ts files: exit 0. PR check-runs: in_progress at report time, not waited on.",
"dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm","mcp_calls": "0",
"api_writes": "3 REST requests across 2 relay dispatches: pr_create (POST /repos/objectstack-ai/objectstack/pulls, then POST /repos//issues/27538/assignees), and this report (POST /repos//issues/27414/comments). git push is not counted.",
"line_budget": "n/a — no skills/** or ledger file touched",
"files_changed": [
"packages/runtime/src/domains/automation.ts",
"packages/runtime/src/domains/automation-flow-save-actor.test.ts",
"packages/runtime/src/domains/automation-authoring-doors-durable.test.ts",
".changeset/27414-flow-save-actor.md"
],
"deviations": [
"Showcase booted with 'pnpm --filter @objectstack/example-showcase dev -- --fresh -p 38714' instead of 'pnpm dev'. Only the showcase closure was built, and check-dev-prereqs refuses a partial build. The server was stopped by its port afterwards.",
"A stray file /build.pid (the pid of the build process) was written at the filesystem root by a mis-scoped shell variable. Removing it was refused by the harness safety check, so it is still there for a person to delete.",
"The gate runner's while-read loop lost stdin after 57 gates. The remaining 4 were rerun with stdin from /dev/null, and all 4 exited 0. A first exit-3 record for check:type-check-debt came from that loop and was removed from the record before reconciliation.",
"The PR body footer uses the AGENTS.md session-URL form, not the harness reminder's emoji form (AGENTS.md takes precedence)."
],
"open_questions": [
{
"question": "How should the sys_audit_log half of #27414 be finished? The audit row names nobody on every metadata save door, /meta included.",
"options": [
"A: widen this card's surface to packages/metadata-protocol/src/sys-metadata-repository.ts and add a second PR there. The put/delete engine writes would carry opts.actor as provenance.attributedUserId, the existing channel plugin-audit already reads for a human credited on a system-authorized write. One edit covers every metadata door. Cost: a metadata-protocol pin plus a check:system-context-census reading. S/M.",
"B: close this card on the sys_metadata half and file the audit half as its own class (b) card across all metadata doors. Cost: one more card. This card's acceptance text would then need amending."
],
"recommendation": "A, as the card's own acceptance names the audit row. The mechanism is one function in one package. The channel already exists and is the one the audit writer documents for exactly this case, so no second mechanism is needed."
}
],
"out_of_scope_findings": [
"class: b · reach: PUT /api/v1/meta/flow/qa_meta_before as admin on the stock showcase -> sys_audit_log create row on sys_metadata has user_id null, actor null (measured live) · evidence: SysMetadataRepository.put writes under { ...ctx, isSystem: true } with no userId/attributedUserId; ADR-0073 D3 'Attribution is always concrete' · Seam: runtime:SysMetadataRepository.put -> plugin-audit audit-writers user_id · this is the undelivered half of #27414 itself; it widens to every metadata door · dedupe words: sys_audit_log sys_metadata user_id null actor, metadata save audit attribution, attributedUserId SysMetadataRepository"
]
}
objectstack-fleet commentedon Oct 29, 2026
domain:devx#1 · sunhaolin · session_017xRBcScm27Q3R5mkYLPWdB
ACCEPT → PR #27538 (partial: the
sys_metadataitem,Part of #27414), reviewed 2026-10-29T11:46Z. Thesys_audit_logitem is sub-issue #27539, claimed by this seat and dispatched. This card closes when both have landed.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:devx#1 · sunhaolin · session_017xRBcScm27Q3R5mkYLPWdB
Handover: PR #27538 → PM session session_017xRBcVy4VXmkQjn2MGeaWk (review and landing)
Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:devx#1 · sunhaolin · session_017xRBcVy4VXmkQjn2MGeaWk
Closed as delivered · 2026-10-29T17:52Z
Both halves named by this card have merged:
33ab5439): the/automationdefinition doors, the clone among them, now record the saving user increated_by/updated_by.475d4283): the audit row of a metadata save now names the user who saved it, souser_id/actoron the clone's create row are filled.Each PR carried
Part of #27414, so nothing closed this card automatically. Nothing is left on it, so it is closed andpm:dispatchedis off. The retest isautomation.setup-packaged-automation-boardin the next checklist wave.Generated by Claude Code