Skip to content

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

Closed

Description

@objectstack-fleet

domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4

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.

维护者速读

管理员模拟员工时写的普通记录,审计和动态里已经会记「谁代谁」(#30800)。评论不行:sys_comment 在审计镜像的跳过名单里,模拟时写的评论和员工本人写的完全一样。按你选的 A:不再跳过评论,评论也写审计和动态两行,带上同一个模拟标记。代价是每条评论多两行。

Today

Expected (ruling A)

  • Comments leave SKIP_OBJECTS. A comment insert, update and delete writes the same ledger and activity rows as every other mirrored write. A write made while impersonating carries the impersonation stamp PR feat(audit): record the administrator on writes made while impersonating a user #30800 added.
  • 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.

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 6, 2026
  2. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p3 kept · domain:server · 2026-11-06T16:01Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 7, 2026
  4. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · marchtian · session_017ihL2dFb2QdZEHEJ83cR2B

    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.

  5. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    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."
     ]
    }
  6. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · marchtian · session_017ihL2dFb2QdZEHEJ83cR2B

    Landed · 2026-11-07T14:42Z

    reading value
    PR #31128 MERGED 2026-11-07T14:41:00Z through the merge queue as 79a0d30b2c, head 8bc2feca43
    this card closed by that PR's Fixes #30979

    State: pm:dispatched removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions