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
metadata-protocol: SysMetadataRepository writes the sys_audit_log row for a metadata save with no user, on every metadata door #27539
Carries the sys_audit_log item of #27414; #27414 keeps the sys_metadatacreated_by / updated_by / history recorded_by item, delivered by PR #27538 (Part of #27414). In-flight sub-issue of #27414: it inherits the parent's domain:server and priority:p2 and is claimed by the parent's seat.
QA-source: #27409 · automation.setup-packaged-automation-board · observed beside the clauses
What is wrong
ADR-0073 D3: "Attribution is always concrete." A signed-in administrator's metadata save writes a sys_audit_log row on sys_metadata whose user_id and actor are null. This holds on every metadata save door, not only the /automation doors #27414 names.
Reproduction (measured live by the #27414 dev on a stock showcase, fresh file DB, admin signed in)
PUT /api/v1/meta/flow/qa_meta_before with a valid flow definition, as admin. It answers 200, and the sys_metadata row's created_by is the admin. This door already passes actor.
GET /api/v1/data/sys_audit_log?$orderby=created_at desc&top=20. The create row on sys_metadata for that id has user_id null and actor null.
Mechanism (from the dev's reading; the implementer re-measures)
SysMetadataRepository.put (packages/metadata-protocol/src/sys-metadata-repository.ts) inserts or updates under { ...ctx, isSystem: true }. That context carries no session.userId and no provenance.attributedUserId.
Those are the only two fields plugin-audit reads for user_id.
The audit writer already documents provenance.attributedUserId as the channel for a human credited on a system-authorized write. So the save's opts.actor has an existing channel to reach the audit row, and no second mechanism is needed.
Acceptance
A metadata save with an actor writes its sys_audit_log row with user_id (and actor) equal to that actor, on put and on delete.
A save with no actor (engine self-writes, seeds) stays unattributed, as before.
A pin on a real store in packages/metadata-protocol covers both, and the /meta door and an /automation door each read the admin id end to end.
pnpm check:system-context-census stays green. If the change touches the system-context register, the register row is updated in the same PR.
Claim: PM loop round 1
Session: session_017xRBcScm27Q3R5mkYLPWdB
Account: sunhaolin
Branch: claude/issue-27539-metadata-save-audit-actor
Worktree: objectstack-issue-27539
Domain: domain:server
Seat: domain:devx#1
File surface: packages/metadata-protocol/src/sys-metadata-repository.ts (put and delete only), its tests under packages/metadata-protocol, the system-context register row if the census asks for it, one @objectstack/metadata-protocol changeset (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: default (dispatch-gates --tier: no path-derived mandate for this path at c4c515a), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: metadata-protocol's SysMetadataRepository writes under a system context with no user or attribution | the audit writer already reads provenance.attributedUserId for a human credited on a system-authorized write | reached by every admin metadata save; the audit trail is read today
Thread-read: none
Premise: holds (measured live by the #27414 dev; the implementer re-measures before editing)
Serial constraints cleared: #25467 owns packages/metadata-protocol/src/protocol.ts and a new protocol-commit-items.ts, not this file; no other open domain:server claim names metadata-protocol; PR #27538 (runtime, Part of #27414) is the sibling and does not touch this package
In-flight sub-issue of #27414 (the parent's seat claims it; domain and priority inherited). Cross-lane authority as on the parent: the maintainer, in session session_017xRBcScm27Q3R5mkYLPWdB on 2026-10-29, verbatim: 「你应该使用 pm-dispatch 技能处理 issue 和 pr」.
os-dev-report
{
"issue": 27539,
"status": "done",
"branch": "claude/issue-27539-metadata-save-audit-actor",
"pr": "#27568",
"session": "session_017xRBcScm27Q3R5mkYLPWdB",
"premise_still_valid": true,
"summary": "Premise re-measured and holds. SysMetadataRepository.put and delete wrote under { ...ctx, isSystem: true }, and plugin-audit audit-writers.ts reads user_id only from session.userId ?? provenance.attributedUserId. The engine's buildProvenance copies ExecutionContext.attributedUserId into provenance. Both verbs now build one writeContext per transaction: isSystem true, plus attributedUserId = opts.actor only when an actor is named. It is used for every write (sys_metadata insert/update/delete and the history insert); reads are unchanged and the actor is never put in userId. Live on a stock showcase as admin, the sys_audit_log rows for PUT /meta/flow, DELETE /meta/flow, POST /automation and PUT /automation/:name now carry user_id = actor = the admin id. The two /automation doors were measured with #27538's one-file runtime change applied locally and not committed.",
"tests": "At 9bc0b58 under the lock: pnpm --filter @objectstack/metadata-protocol typecheck && test gave VERDICT command-exit 0, Test Files 279 passed | 4 skipped (283), Tests 21783 passed | 26 skipped. New pins in sys-metadata-repository.recorded-by.test.ts reuse the file's existing double, which now records each write verb's context. Actor case: create/update/delete with 3 actors, 6 writes, each isSystem true with attributedUserId = that actor and no userId. Control: no actor, 6 writes, isSystem true, no attributedUserId key. Ablation via scripts/ablation-replace.mjs with the fix committed first (src import, no dist): the put spread was removed, anchor 1->0, blob 5b8de92d7583->3ee7c3a8b2eb. Result 1 failed / 14 passed: the actor pin failed and the control stayed green. Restored to blob == HEAD with git diff HEAD empty. Live e2e leg: 4 doors gave admin id on user_id and actor. Earlier run without #27538: the /automation create row was null/null (no-actor control).",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 9bc0b58 (after merging origin/main) derived 60 families; all 60 exit 0, captured before any pipe. --ran gave 'Run reconciliation — 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. The pre-merge pass at 5f5e89f/a92804f3 had the same 60. Three of those (dts-closure, dual-build-cjs-loads, lean-entry-closure) first read 1/3/3 against a half-built tree and were rerun after the full build: exit 0. pnpm check:system-context-census exit 0, no register edit needed. PR check-runs at report time: in_progress (Lint, Type Check, Check Changeset, Governed Surface Queue Guard, PR-body guards and others), 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/27568/assignees), and this report (POST /repos//issues/27539/comments). git push not counted.",
"line_budget": "n/a — no skills/** or ledger file touched",
"files_changed": [
"packages/metadata-protocol/src/sys-metadata-repository.ts",
"packages/metadata-protocol/src/sys-metadata-repository.recorded-by.test.ts",
".changeset/27539-metadata-save-audit-actor.md"
],
"deviations": [
"The suggested real-store pin (better-sqlite3 + real sys_metadata objects + the audit writer) cannot live under the claimed surface. @objectstack/metadata-protocol has no driver, objectql or plugin-audit dependency, and objectql depends on it, so the import would be a cycle. I narrowed it: the in-package pin proves the repository hands the engine attributedUserId, and the engine-to-audit-writer leg was measured live end to end once. A permanent cross-package pin, if wanted, would go in packages/plugins/plugin-audit or packages/runtime; that is outside this surface and not written.",
"The live leg applied #27538's automation.ts diff to the worktree without committing it and rebuilt runtime. It was then restored with git checkout HEAD (blob == HEAD) and runtime was rebuilt clean.",
"The showcase was booted with 'pnpm --filter @objectstack/example-showcase dev --fresh -p 38917', not 'pnpm dev'. Only the showcase closure was built. It was stopped by its port.",
"One gate in the first pass (check:type-check-debt) ran a whole-workspace turbo build under the lock. That is the gate's own behaviour; it is noted only because it held the shared lock for about 6 minutes.",
"origin/main was merged before the PR opened (incoming protocol.ts extraction in the same package). The closure was rebuilt and the package suite and the 60 derived gates were rerun on the merged head.",
"The PR body footer uses the AGENTS.md session-URL form, not the harness reminder's emoji form (AGENTS.md takes precedence)."
],
"open_questions": [],
"out_of_scope_findings": []
}
Activity
objectstack-fleet commentedon Oct 29, 2026
domain:devx#1 · sunhaolin · session_017xRBcScm27Q3R5mkYLPWdB
Claim: PM loop round 1
Session:
session_017xRBcScm27Q3R5mkYLPWdBAccount:
sunhaolinBranch:
claude/issue-27539-metadata-save-audit-actorWorktree:
objectstack-issue-27539Domain:
domain:serverSeat:
domain:devx#1File surface:
packages/metadata-protocol/src/sys-metadata-repository.ts(putanddeleteonly), its tests underpackages/metadata-protocol, the system-context register row if the census asks for it, one@objectstack/metadata-protocolchangeset (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate for this path at c4c515a),effort: highClause-②: no
Metadata-protocol: none
Responsibility: metadata-protocol's
SysMetadataRepositorywrites under a system context with no user or attribution | the audit writer already readsprovenance.attributedUserIdfor a human credited on a system-authorized write | reached by every admin metadata save; the audit trail is read todayThread-read: none
Premise: holds (measured live by the #27414 dev; the implementer re-measures before editing)
Serial constraints cleared: #25467 owns
packages/metadata-protocol/src/protocol.tsand a newprotocol-commit-items.ts, not this file; no other opendomain:serverclaim namesmetadata-protocol; PR #27538 (runtime,Part of #27414) is the sibling and does not touch this packageIn-flight sub-issue of #27414 (the parent's seat claims it; domain and priority inherited). Cross-lane authority as on the parent: the maintainer, in session
session_017xRBcScm27Q3R5mkYLPWdBon 2026-10-29, verbatim: 「你应该使用 pm-dispatch 技能处理 issue 和 pr」.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
os-dev-report
{
"issue": 27539,
"status": "done",
"branch": "claude/issue-27539-metadata-save-audit-actor",
"pr": "#27568",
"session": "session_017xRBcScm27Q3R5mkYLPWdB",
"premise_still_valid": true,
"summary": "Premise re-measured and holds. SysMetadataRepository.put and delete wrote under { ...ctx, isSystem: true }, and plugin-audit audit-writers.ts reads user_id only from session.userId ?? provenance.attributedUserId. The engine's buildProvenance copies ExecutionContext.attributedUserId into provenance. Both verbs now build one writeContext per transaction: isSystem true, plus attributedUserId = opts.actor only when an actor is named. It is used for every write (sys_metadata insert/update/delete and the history insert); reads are unchanged and the actor is never put in userId. Live on a stock showcase as admin, the sys_audit_log rows for PUT /meta/flow, DELETE /meta/flow, POST /automation and PUT /automation/:name now carry user_id = actor = the admin id. The two /automation doors were measured with #27538's one-file runtime change applied locally and not committed.",
"tests": "At 9bc0b58 under the lock: pnpm --filter @objectstack/metadata-protocol typecheck && test gave VERDICT command-exit 0, Test Files 279 passed | 4 skipped (283), Tests 21783 passed | 26 skipped. New pins in sys-metadata-repository.recorded-by.test.ts reuse the file's existing double, which now records each write verb's context. Actor case: create/update/delete with 3 actors, 6 writes, each isSystem true with attributedUserId = that actor and no userId. Control: no actor, 6 writes, isSystem true, no attributedUserId key. Ablation via scripts/ablation-replace.mjs with the fix committed first (src import, no dist): the put spread was removed, anchor 1->0, blob 5b8de92d7583->3ee7c3a8b2eb. Result 1 failed / 14 passed: the actor pin failed and the control stayed green. Restored to blob == HEAD with git diff HEAD empty. Live e2e leg: 4 doors gave admin id on user_id and actor. Earlier run without #27538: the /automation create row was null/null (no-actor control).",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 9bc0b58 (after merging origin/main) derived 60 families; all 60 exit 0, captured before any pipe. --ran gave 'Run reconciliation — 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. The pre-merge pass at 5f5e89f/a92804f3 had the same 60. Three of those (dts-closure, dual-build-cjs-loads, lean-entry-closure) first read 1/3/3 against a half-built tree and were rerun after the full build: exit 0. pnpm check:system-context-census exit 0, no register edit needed. PR check-runs at report time: in_progress (Lint, Type Check, Check Changeset, Governed Surface Queue Guard, PR-body guards and others), 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/27568/assignees), and this report (POST /repos//issues/27539/comments). git push not counted.",
"line_budget": "n/a — no skills/** or ledger file touched",
"files_changed": [
"packages/metadata-protocol/src/sys-metadata-repository.ts",
"packages/metadata-protocol/src/sys-metadata-repository.recorded-by.test.ts",
".changeset/27539-metadata-save-audit-actor.md"
],
"deviations": [
"The suggested real-store pin (better-sqlite3 + real sys_metadata objects + the audit writer) cannot live under the claimed surface. @objectstack/metadata-protocol has no driver, objectql or plugin-audit dependency, and objectql depends on it, so the import would be a cycle. I narrowed it: the in-package pin proves the repository hands the engine attributedUserId, and the engine-to-audit-writer leg was measured live end to end once. A permanent cross-package pin, if wanted, would go in packages/plugins/plugin-audit or packages/runtime; that is outside this surface and not written.",
"The live leg applied #27538's automation.ts diff to the worktree without committing it and rebuilt runtime. It was then restored with git checkout HEAD (blob == HEAD) and runtime was rebuilt clean.",
"The showcase was booted with 'pnpm --filter @objectstack/example-showcase dev --fresh -p 38917', not 'pnpm dev'. Only the showcase closure was built. It was stopped by its port.",
"One gate in the first pass (check:type-check-debt) ran a whole-workspace turbo build under the lock. That is the gate's own behaviour; it is noted only because it held the shared lock for about 6 minutes.",
"origin/main was merged before the PR opened (incoming protocol.ts extraction in the same package). The closure was rebuilt and the package suite and the 60 derived gates were rerun on the merged head.",
"The PR body footer uses the AGENTS.md session-URL form, not the harness reminder's emoji form (AGENTS.md takes precedence)."
],
"open_questions": [],
"out_of_scope_findings": []
}
objectstack-fleet commentedon Oct 29, 2026
domain:devx#1 · sunhaolin · session_017xRBcScm27Q3R5mkYLPWdB
Handover: PR #27568 → PM session session_017xRBcVy4VXmkQjn2MGeaWk (review and landing)
Dev report 6497936005.
Fixes #27539; #27414 closes only after #27538 and #27568 have both landed.Generated by Claude Code