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
plugin-audit: a comment written while impersonating writes no audit or activity row, so it can't be told from the employee's own (#30213 comments fork, ruling A) #30979
Filing gate: ③ a task on the maintainer's word. Card #30213 (closed by PR #30800) left one fork with the maintainer: its os-dev-report (6676374251) asked how comments should carry the impersonation stamp. The maintainer chose option A, 「A 评论也记审计和动态」, on the seat's decision card on 2026-11-06.
No new column and no migration. Option B, a column on sys_comment, was not chosen.
Measure first whether a comment activity row duplicates an existing comment feed entry in the console timeline; if it does, say how the timeline shows it, and stop if a ui change is needed.
Read visibility: an audit or activity row for a comment must not show the comment to a reader who cannot read its parent record. Reuse the existing read-visibility rules; don't add a second judgment.
Acceptance
Tests:
an impersonated comment insert writes an audit row and an activity row, both stamped with the administrator;
CONTROL: a non-impersonated comment writes both rows unstamped;
a reader without read access to the parent record does not see the comment's rows.
Suggested grade: p3 · S · domain:server (plugin-audit). Dedupe: this card is the only carrier of the #30213 comments fork.
The card's own stops hold. The seat measures timeline duplication first and stops if it needs a UI change. Read visibility must not show a comment through its audit row to a reader who cannot read the comment.
Claim: PM loop round 1 · 2026-11-07T12:28Z
Session: session_017ihL2dFb2QdZEHEJ83cR2B
Account: marchtian
Branch: claude/project-thread-nbg9qq
Worktree: objectstack-issue-30979 (the card thread's own checkout)
Domain: domain:server
Seat: domain:server#3
File surface: packages/plugins/plugin-audit/** (stop on breach; explain in the report)
Container & model: M, mode:thread (card thread cmsg_01VmuUhXU9SSUzqJYyXSmYSi96aMRMFJf7kCoATPX8nMxf of this account's project), model: TIER_DEFAULT — dispatch-gates --tier: no path-derived mandate, effort: session default — card-thread effort is set by the maintainer only; the seat's reading is high (M; read-visibility edge)
Clause-②: no
Metadata-protocol: none
Responsibility: plugin-audit's own SKIP_OBJECTS (objectstack, packages/plugins/plugin-audit/src/audit-writers.ts) drops every sys_comment write from the ledger | no platform path records an impersonated comment today (PR #30800 stamps every other mirrored write) | any administrator impersonating a person who writes a comment; reached today on every comment write
Thread-read: 6689264771
Premise: holds — read at origin/main 5f00374: SKIP_OBJECTS still lists sys_comment; PR #30800 merged
Serial constraints cleared: no open domain:server claim or open PR names plugin-audit; siblings of this seat touch platform-objects and plugin-auth only
The dispatch order is the card thread's opening message (files server-seat-3/orders/_standing.md + server-seat-3/orders/30979.md in this account's project); slot granted by the project coordinator.
{
"issue": 30979,
"status": "done",
"branch": "claude/project-thread-nbg9qq",
"pr": "https://github.com/objectstack-ai/objectstack/pull/31128",
"session": "session_017ihL2btLZBoQgqLephBLfF",
"premise_still_valid": true,
"summary": "sys_comment leaves plugin-audit's SKIP_OBJECTS (and so AUDIT_EXCLUDED_OBJECTS): a comment insert, update and delete write one sys_audit_log row and one sys_activity row like any mirrored write, with the PR #30800 impersonation stamp when impersonated. Step 1 measured first: no timeline duplication (the record stream reads the parent record's own object_name/record_id pair; a comment's rows name sys_comment and the comment id), so no UI change. Read visibility reuses the comment gate: the activity and ledger parent gates now enumerate sys_comment as a parent, and because sys_comment carries an object-scoped read middleware it is read through the bounded probe as the caller (ADR-0139 D5.2). Readers of AUDIT_EXCLUDED_OBJECTS checked: hook excludeObjects, parent-record-read-gate enumeration, commentThreadGate namedParents (unchanged effect, the gate skips its own object first). No recursion: the writer writes only sys_audit_log/sys_activity, which stay listed.",
"tests": "head 8bc2feca4: os-verify-lock 'pnpm --filter @objectstack/plugin-audit test' :: exit 0, Test Files 76/76, Tests 1201/1201. New pins: activity-actor-name.test.ts (impersonated comment insert → both rows stamped; impersonated edit+delete stamped; CONTROL own comment → both rows unstamped); activity-read-visibility.integration.test.ts (real kernel + AuditPlugin + SQLite: member served the rows of the comment on its own record only, on list, id-scoped find and count, for both sys_activity and sys_audit_log; controls: rows exist, comment gate answers each member its own comment, admin served both). audit-hook-object-scope.test.ts: the two pins of the old exclusion now pin the reverse. Ablation 1 (scripts/ablation-replace.mjs, anchor hit 1, restore blob == HEAD): 'sys_comment' back on SKIP_OBJECTS → 6 failed / 28 passed across the two files; restored green. Ablation 2: comment read gate registration (comment-access-hooks.ts registerMiddleware(gate, { object: COMMENT_OBJECT })) deleted → 2 failed / 14 passed, the visibility pin red with the other member's comment rows served; restored green. Source imports, no dist resolution involved in the ablated modules.",
"gates": "local, head 8bc2feca4: 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/ci/snapshot-next.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: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:vouch-for-reference-callers :: exit 0; pnpm check:watch-hint-literal :: exit 0; pnpm check:where-matcher :: exit 0. dispatch-gates --ran :: exit 0, 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN. Plus: pnpm --filter @objectstack/plugin-audit typecheck :: exit 0; eslint --no-inline-config on the 4 touched files :: exit 0 (package has no lint script; repo-wide pnpm lint is CI's). First-pass exit 3s cleared by their prerequisites: teardown-shape self-test (fetched pinned fixture commit), dual-build-cjs-loads and i18n (full pnpm build). query-options-erasure first exit 1 on one new test options cast, removed in 8bc2feca4, whole list re-run. CI check-runs: in_progress at report time.",
"line_budget": "n/a (no skills/** touched); diff: audit-writers.ts +14/-6, three test files, one changeset",
"deviations": [
"Worktree: the harness-pinned branch was checked out in a dedicated worktree ../objectstack-issue-30979 (main checkout detached), as the claim names.",
"Changeset level patch with Clause-② no / Metadata-protocol none copied from the claim; the diff agrees."
],
"files_changed": [
".changeset/30979-comment-writes-audited.md",
"packages/plugins/plugin-audit/src/audit-writers.ts",
"packages/plugins/plugin-audit/src/activity-actor-name.test.ts",
"packages/plugins/plugin-audit/src/activity-read-visibility.integration.test.ts",
"packages/plugins/plugin-audit/src/audit-hook-object-scope.test.ts"
],
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls via repository_dispatch, run 42377456514) and this os-dev-report comment via post-stamped.mjs",
"open_questions": [
{
"question": "os migrate comment-parent-pair --apply boots AuditPlugin and updates every unstamped comment through the engine as the system, so after this change each stamped comment also leaves one system-actor update ledger row and activity row (one-shot, bounded by stored comments). Keep it, or exempt this migration's bookkeeping writes?",
"options": [
"A keep: a system update of a comment is a recorded write like any other; one-shot cost",
"B exempt: a follow-up card in packages/cli or plugin-audit to skip the backfill's writes from the mirror (outside this card's file surface)"
],
"recommendation": "A, because the rows are attributed to the system, are bounded, and the stored-metadata body migration already records its rewrites the same way; B only if the maintainer wants migrations kept out of the recent-activity feed."
}
],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: none · noted, not filed — identity-objects-feeds-off.test.ts boots a single posture with no Default Organization, so its comment writes now print the writer's one-time 'Audit write FAILED ... ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED' error line; the suite passes and the line is the writer correctly reporting its harness.",
"carrier: none · noted, not filed — activity-read-visibility.integration.test.ts's existing 'count' pins call engine.count(ACTIVITY, {}, { context }) and pass the context as a third argument the signature does not take; observation only, not a measured defect."
]
}
Activity
objectstack-fleet commentedon Nov 6, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p3 kept ·
domain:server· 2026-11-06T16:01Z⛔ Not a claim.
sys_commentleaves plugin-audit'sSKIP_OBJECTS, so a comment write gets the same ledger and activity rows, with PR feat(audit): record the administrator on writes made while impersonating a user #30800's impersonation stamp. No new column.domain:serverandpm:queuekept, as filed.Generated by Claude Code
objectstack-fleet commentedon Nov 7, 2026
domain:server#3 · marchtian · session_017ihL2dFb2QdZEHEJ83cR2B
Claim: PM loop round 1 · 2026-11-07T12:28Z
Session:
session_017ihL2dFb2QdZEHEJ83cR2BAccount:
marchtianBranch:
claude/project-thread-nbg9qqWorktree:
objectstack-issue-30979(the card thread's own checkout)Domain:
domain:serverSeat:
domain:server#3File surface:
packages/plugins/plugin-audit/**(stop on breach; explain in the report)Container & model:
M,mode:thread(card thread cmsg_01VmuUhXU9SSUzqJYyXSmYSi96aMRMFJf7kCoATPX8nMxf of this account's project),model: TIER_DEFAULT — dispatch-gates --tier: no path-derived mandate,effort: session default — card-thread effort is set by the maintainer only; the seat's reading is high (M; read-visibility edge)Clause-②: no
Metadata-protocol: none
Responsibility: plugin-audit's own
SKIP_OBJECTS(objectstack,packages/plugins/plugin-audit/src/audit-writers.ts) drops everysys_commentwrite from the ledger | no platform path records an impersonated comment today (PR #30800 stamps every other mirrored write) | any administrator impersonating a person who writes a comment; reached today on every comment writeThread-read: 6689264771
Premise: holds — read at origin/main 5f00374: SKIP_OBJECTS still lists sys_comment; PR #30800 merged
Serial constraints cleared: no open
domain:serverclaim or open PR names plugin-audit; siblings of this seat touch platform-objects and plugin-auth onlyThe dispatch order is the card thread's opening message (files
server-seat-3/orders/_standing.md+server-seat-3/orders/30979.mdin this account's project); slot granted by the project coordinator.objectstack-fleet commentedon Nov 7, 2026
domain:server#3 · marchtian · session_017ihL2btLZBoQgqLephBLfF
os-dev-report
{ "issue": 30979, "status": "done", "branch": "claude/project-thread-nbg9qq", "pr": "https://github.com/objectstack-ai/objectstack/pull/31128", "session": "session_017ihL2btLZBoQgqLephBLfF", "premise_still_valid": true, "summary": "sys_comment leaves plugin-audit's SKIP_OBJECTS (and so AUDIT_EXCLUDED_OBJECTS): a comment insert, update and delete write one sys_audit_log row and one sys_activity row like any mirrored write, with the PR #30800 impersonation stamp when impersonated. Step 1 measured first: no timeline duplication (the record stream reads the parent record's own object_name/record_id pair; a comment's rows name sys_comment and the comment id), so no UI change. Read visibility reuses the comment gate: the activity and ledger parent gates now enumerate sys_comment as a parent, and because sys_comment carries an object-scoped read middleware it is read through the bounded probe as the caller (ADR-0139 D5.2). Readers of AUDIT_EXCLUDED_OBJECTS checked: hook excludeObjects, parent-record-read-gate enumeration, commentThreadGate namedParents (unchanged effect, the gate skips its own object first). No recursion: the writer writes only sys_audit_log/sys_activity, which stay listed.", "tests": "head 8bc2feca4: os-verify-lock 'pnpm --filter @objectstack/plugin-audit test' :: exit 0, Test Files 76/76, Tests 1201/1201. New pins: activity-actor-name.test.ts (impersonated comment insert → both rows stamped; impersonated edit+delete stamped; CONTROL own comment → both rows unstamped); activity-read-visibility.integration.test.ts (real kernel + AuditPlugin + SQLite: member served the rows of the comment on its own record only, on list, id-scoped find and count, for both sys_activity and sys_audit_log; controls: rows exist, comment gate answers each member its own comment, admin served both). audit-hook-object-scope.test.ts: the two pins of the old exclusion now pin the reverse. Ablation 1 (scripts/ablation-replace.mjs, anchor hit 1, restore blob == HEAD): 'sys_comment' back on SKIP_OBJECTS → 6 failed / 28 passed across the two files; restored green. Ablation 2: comment read gate registration (comment-access-hooks.ts registerMiddleware(gate, { object: COMMENT_OBJECT })) deleted → 2 failed / 14 passed, the visibility pin red with the other member's comment rows served; restored green. Source imports, no dist resolution involved in the ablated modules.", "gates": "local, head 8bc2feca4: 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/ci/snapshot-next.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: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:vouch-for-reference-callers :: exit 0; pnpm check:watch-hint-literal :: exit 0; pnpm check:where-matcher :: exit 0. dispatch-gates --ran :: exit 0, 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN. Plus: pnpm --filter @objectstack/plugin-audit typecheck :: exit 0; eslint --no-inline-config on the 4 touched files :: exit 0 (package has no lint script; repo-wide pnpm lint is CI's). First-pass exit 3s cleared by their prerequisites: teardown-shape self-test (fetched pinned fixture commit), dual-build-cjs-loads and i18n (full pnpm build). query-options-erasure first exit 1 on one new test options cast, removed in 8bc2feca4, whole list re-run. CI check-runs: in_progress at report time.", "line_budget": "n/a (no skills/** touched); diff: audit-writers.ts +14/-6, three test files, one changeset", "deviations": [ "Worktree: the harness-pinned branch was checked out in a dedicated worktree ../objectstack-issue-30979 (main checkout detached), as the claim names.", "Changeset level patch with Clause-② no / Metadata-protocol none copied from the claim; the diff agrees." ], "files_changed": [ ".changeset/30979-comment-writes-audited.md", "packages/plugins/plugin-audit/src/audit-writers.ts", "packages/plugins/plugin-audit/src/activity-actor-name.test.ts", "packages/plugins/plugin-audit/src/activity-read-visibility.integration.test.ts", "packages/plugins/plugin-audit/src/audit-hook-object-scope.test.ts" ], "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls via repository_dispatch, run 42377456514) and this os-dev-report comment via post-stamped.mjs", "open_questions": [ { "question": "os migrate comment-parent-pair --apply boots AuditPlugin and updates every unstamped comment through the engine as the system, so after this change each stamped comment also leaves one system-actor update ledger row and activity row (one-shot, bounded by stored comments). Keep it, or exempt this migration's bookkeeping writes?", "options": [ "A keep: a system update of a comment is a recorded write like any other; one-shot cost", "B exempt: a follow-up card in packages/cli or plugin-audit to skip the backfill's writes from the mirror (outside this card's file surface)" ], "recommendation": "A, because the rows are attributed to the system, are bounded, and the stored-metadata body migration already records its rewrites the same way; B only if the maintainer wants migrations kept out of the recent-activity feed." } ], "security_findings": "none", "out_of_scope_findings": [ "carrier: none · noted, not filed — identity-objects-feeds-off.test.ts boots a single posture with no Default Organization, so its comment writes now print the writer's one-time 'Audit write FAILED ... ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED' error line; the suite passes and the line is the writer correctly reporting its harness.", "carrier: none · noted, not filed — activity-read-visibility.integration.test.ts's existing 'count' pins call engine.count(ACTIVITY, {}, { context }) and pass the context as a third argument the signature does not take; observation only, not a measured defect." ] }objectstack-fleet commentedon Nov 7, 2026
domain:server#3 · marchtian · session_017ihL2dFb2QdZEHEJ83cR2B
Landed · 2026-11-07T14:42Z
79a0d30b2c, head8bc2feca43Fixes #30979State:
pm:dispatchedremoved.