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: sys_activity.actor_avatar_url is declared but never written, so every activity row carries a null avatar even for a user with a profile image #22527
Unblocked: #22510 closed (PR #22526, d1d42ec57 on main), per triage's unlock scan 6095905468.
Filing gate: ① a product defect, class (b) (a declared column with no writer, at a spec → runtime → renderer seam), reach measured once through a public door. Found and measured by the dev of #22510 (PR #22526, report on #22510, out_of_scope_findings[0]). Filed by the domain:services seat 2 (session_01WYYhVJ78u7PhwFViWo1EmQ). ⛔ Not a claim.
Reader: triage routes it. By its file it lands in domain:services (packages/plugins/plugin-audit, the activity writer).
Dedupe (MCP search_issues, open and closed, run just before this card was created): sys_activity actor_avatar_url never written activity avatar null record history avatar → 1 hit, #22510 (the actor_name sibling, which PR #22526 fixes and which does not cover the avatar). None carries this.
The defect
sys_activity declares actor_avatar_url (packages/plugins/plugin-audit/src/objects/sys-activity.object.ts, about :155). No writer in the repository fills it (git grep actor_avatar_url over non-test, non-translation sources finds only the declaration). objectui's renderers read it: record-history.tsx maps user_avatar, and recordActivityFeed.ts maps actorAvatarUrl.
Measured on PR #22526's after-stack (pnpm dev:crm --fresh):
The admin's sys_user.image was set via POST /api/v1/auth/update-user and read back through GET /api/v1/data/sys_user.
The admin's next crm_lead edit row, read via GET /api/v1/data/sys_activity, carries actor_avatar_url null. All 39 rows on the stack are null.
Direction (for triage and the claimant)
This is the same family and the same writer as #22510. ADR-0049 (enforce-or-remove) applies: the column is a declared snapshot field that renderers read, so either the writer fills it at write time, from the same sys_user row PR #22526 already reads for the name, or the declaration is retired. A system write keeps it null (ADR-0118 D1: the system actor is null; display is the renderer's).
Serial: behind PR #22526 (#22510), which edits the same writer (audit-writers.ts's activity row and its per-user memo).
Tests
Pin: a user with sys_user.image set writes an activity row whose actor_avatar_url is that image.
Controls: a user with no image → null; a system write → null.
Triage: first grade, bug · priority:p3 · domain:services · area:records, pm:blocked on #22510 (PR #22526, same writer)
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-09T19:53Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: the activity writer is packages/plugins/plugin-audit/src/audit-writers.ts. That puts it in domain:services, the same writer and family as #22510.
Unlock scan: #22510 closed, with PR #22526 landed as d1d42ec57. pm:blocked → pm:queue
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-10T08:58Z. ⛔ Not a claim, ⛔ not a dispatch.
Blocked-by: none
What landed: the activity writer fills actor_name from the actor's sys_user row, through a per-user memo. That is the read this card's actor_avatar_url rides.
The direction stands (6088195808): the same writer and the same memo fill actor_avatar_url. A user with no image stays null. A system actor stays null (ADR-0118 D1).
Claim: PM loop round 1 · 2026-10-10T09:06Z
Session: session_013j5gkUCpqQiti4GgPqqmnt
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-22527-activity-actor-avatar
Worktree: objectstack-issue-22527
Domain: domain:services
Seat: domain:services#1 (seat post #6021)
Unblocked per triage's scan 6095905468 (#22510 closed, PR #22526 landed as d1d42ec57). The body's stale Blocked-by: line is replaced in this act.
File surface, read on origin/main3d0eeefa4a:
Tests in plugin-audit: the card's pin, both controls and the ablation.
.changeset/22527-*.md:patch for @objectstack/plugin-audit.
⛔ No change to sys-activity.object.ts (the column is already declared). ⛔ No objectui edit. ⛔ No content/docs/releases/.
Stop on breach; explain in the report.
Container & model: S, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; the default tier)
Clause-②: no
A declared column gets its writer (ADR-0049). No accept set or public surface moves.
Responsibility: this repository's plugin-audit: the activity writer never writes sys_activity.actor_avatar_url | the actor_name memo from #22510, which this reuses | every user with a profile image, on every record History entry; measured on PR #22526's after-stack
Thread-read: 6095905468
Serial constraints cleared:
os-dev-report
{
"issue": 22527,
"status": "done",
"branch": "claude/issue-22527-activity-actor-avatar",
"pr": "#22672",
"session": "session_013j5gkUCpqQiti4GgPqqmnt (subagent of the domain:services seat 1 PM; parent's session)",
"premise_still_valid": true,
"summary": "Draft PR #22672 is at head 980e354, which merges origin/main 5fb1746. plugin-audit's writeAudit now writes sys_activity.actor_avatar_url from the acting user's sys_user.image, through the memo d1d42ec added for actor_name; there is no second lookup path. resolveActorName became resolveActorSnapshot: ONE sys_user read projects the title column (titleFieldOf, unchanged) and image, and returns { name, avatarUrl }. It stays promise-cached per user for 30 s. The name rule is now one shared helper, titleTextOf, which resolveLookupTitles uses too, byte-identical to the inline rule it replaced. image is projected only when sys_user declares it. A user with no image stays null. A system write stays null and reads no user (ADR-0118 D1). MEASURED before coding: the column is sys_user.image, a Field.url. ADR-0092 Tier 1 calls it 'avatar URL'; it is not an image-type field, so ADR-0104 sys_file ids do not apply. objectui at the pinned .objectui-sha 20c6d351 writes it with ProfilePage.tsx:115 updateUser({ image: result.url }). Both renderers use the column verbatim as an image src: record-history.tsx:201 to HistoryTimeline.tsx:217-221 AvatarImage src, and recordActivityFeed.ts:807 to RecordActivityTimeline.tsx:553-555 img src. Stored value and renderer expectation match, so there is no fork and no transform. The file surface is exactly what the claim allows: audit-writers.ts, activity-actor-name.test.ts, and .changeset/22527-activity-actor-avatar.md (patch). There is no sys-activity.object.ts edit, no objectui edit and no releases edit. One deviation, stated: the harness reminder asked for a model-named Co-Authored-By trailer and an emoji PR footer. I used AGENTS.md's model-free trailer pair and the session-URL footer instead; the role file says that reminder yields. The worktree and its node_modules are removed. No process I started is still running.",
"tests": "Heavy runs went through os-verify-lock with OS_VERIFY_LOCK_SLOT=issue-22527, NODE_OPTIONS=--max-old-space-size=3072, turbo --concurrency=1 and vitest --maxWorkers=2. (1) pnpm --filter @objectstack/plugin-audit test on 980e354: Test Files 45 passed (45), Tests 714 passed (714), VERDICT command-exit 0. (2) pnpm --filter @objectstack/plugin-audit typecheck on 980e354: VERDICT command-exit 0, and check:test-typecheck OK. A first run had failed on one TS18046 in the new test's query helper; commit 980e354 typed it. (3) New pins in src/activity-actor-name.test.ts, on the real ObjectQL engine, reusing that file's existing counting driver double (no new double): a pin (a user with image writes it verbatim on create, update and delete), two controls (no image gives null while the name still lands; a system write gives null with 0 sys_user reads), and a memo pin (a 3-row predicate update makes exactly 1 sys_user read, whose projection carries both name and image, and a later write by the same user makes 0 reads). 10/10 green. (4) Ablations through scripts/ablation-replace.mjs in WRAP mode, inside a wrapper with trap restore, the fix committed first, writer blob 7e8ede39cac1 (unchanged at the final head). A, drop the actor_avatar_url stamp: anchor 1 to 0, blob 7e8ede39cac1 to 77824c05976f; 'Tests 2 failed | 8 passed (10)'. The pin and the memo pin went red with 'expected undefined to be /api/v1/storage/files/file_grace_avat...'; the controls and the name pins stayed green, the expected direction. B, bypass the memo hit: blob to 1ced12124c1a; 'Tests 2 failed | 8 passed (10)'. The #22510 read-count pin and the new memo pin went red with 'expected 3 to be 1'. Each restore was proven by the tool and again by hand: the blob equals HEAD's 7e8ede39cac1 and git diff HEAD is empty. The subject is imported from src by a relative path, so no dist leg applies. (5) Targeted eslint, a narrowed measurement on 980e354: eslint --no-inline-config --format json over the 2 changed TS files gave files=2 errors=0 warnings=0. The population comes from eslint.config.mjs (its repo-wide TS/JS glob object and its packages TS glob object both match the 2 files). Untouched files cannot change verdict, because the config never enables type-aware linting (no parserOptions.project, no projectService). The repo-wide pnpm lint is CI's.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 980e354 (3 paths vs merge base 5fb1746; 260 changed lines, under 3000) derived 67 commands. All 67 were run, each exit captured before any pipe. 65 exited 0. 2 exited 3 (PREREQUISITE NOT MET): check:i18n and check:dual-build-cjs-loads. The --ran reconcile reads: '67 derived famil(ies) accounted for — 65 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)', exit 0. I then cleared both prerequisites and re-ran both gates on the same head. check:i18n, after its closure was built through turbo (59/59 cached): 'OK (9 package(s) — all bundles in sync, no undeclared authoring keys)', exit 0. check:dual-build-cjs-loads, after a full turbo build (71 of 72 cached; the first attempt was queue-timeout 99 behind a spec test:repo holder, and the retry on the same slot acquired): '107 published require entry point(s) across 66 package(s) load', exit 0. NOT MEASURED locally, CI-owned: the 5 path-scheduled CI jobs (Test Core shards, Temporal Conformance, Dogfood Regression and Verify, Build Core), the 4 workspace type-check lanes, the 6 workflow-valued families, the 11 wide-population and 52 roster families that dispatch-gates lists outside its runnable set. CI was not awaited: one read at report time gave 31 check runs, 12 completed (9 success, 3 skipped, 0 failure) and 19 in_progress. origin/main has moved 8 commits since the merge; none touches plugin-audit.",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "3 — via the fleet-write relay as objectstack-fleet[bot]: (1) pr_create POST /repos/objectstack-ai/objectstack/pulls, draft, relay run 38043775442, body read back 8410 bytes identical; (2) label-write --assign zhuangjianguo, POST /repos//issues/22672/assignees, relay run 38043811271, read back MATCHES; (3) this os-dev-report comment, POST /repos//issues/22527/comments. Plus git push (not REST): the empty-branch probe and 4 content pushes. No labels were written: the dispatch named none, and a changeset is present, so skip-changeset does not apply.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (objectui, no claimant) · noted, not filed — Observation, not measured: sys_comment.author_avatar_url is client-written. objectui at 20c6d351, packages/app-shell/src/views/RecordDetailView.tsx:2085 and :2124, writes author_avatar_url from 'avatar' in currentUser ? currentUser.avatar : undefined. objectui's own ProfilePage.tsx:153 reads the signed-in user's avatar as user.image, so this may always write undefined. No reach was measured, so it is not a class a/b/c filing; recorded in the PR's Acceptance notes. Dedupe words: sys_comment author_avatar_url, comment author avatar, currentUser avatar image",
"carrier: none · noted, not filed — A sys_user.image value stored by bypassing the engine's Field.url validation (a raw SQL edit) would make that user's activity insert refuse, reported at error by the existing durability path, because the copy is validated on insert against the same Field.url shape. No such producer was measured: the console uploader, better-auth update-user and OAuth sign-up all store validated URLs. Recorded in the PR's Acceptance notes."
]
}
ACCEPT (seat review): PR #22672 at head 980e354e40. sys_activity.actor_avatar_url is written from the acting user's profile image
domain:services seat 1 (#6021) · session_013j5gkUCpqQiti4GgPqqmnt · 2026-10-10T10:23Z. Claim 6095960292. Report 6096428776. Read against GitHub and the PR head, not the report.
Review route.Clause-②: no: a declared column gets its writer (ADR-0049), and no accept set or public surface moves. Not governed: check-governed-merges --pr 22672 reports 260 changed lines. So the review is a seat read plus CI.
The change, as read in the diff (audit-writers.ts, its test, a patch changeset):
The name rule. It moves into one shared helper, titleTextOf, which resolveLookupTitles uses too. It behaves exactly as the inline rule it replaces did.
The avatar is copied verbatim. It was measured first: sys_user.image is a Field.url, the console's uploader writes the upload's URL there, and both objectui renderers at the pinned .objectui-sha use the column as an image src. So there is no transform and no fork.
When nothing is written. A user with no image, and a system write, stay null. A system write reads no user (ADR-0118 D1). image is projected only when sys_user declares it.
Evidence read.
The pin, both controls and the memo pin are green.
Ablation A (stamp dropped) turns the pin and the memo pin red. Ablation B (memo bypassed) turns both read-count pins red. Each was restored to a blob equal to HEAD.
67 derived gates: 65 exit 0, and the 2 prerequisite-gated ones exit 0 after their builds.
plugin-audit: 714 passed. Typecheck exits 0.
CI at 980e354e40: 31 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in), all rostered). mergeable_state: clean.
Findings.
sys_comment.author_avatar_url: client-written by objectui from a field its own profile page does not use. Noted, not filed: no reach was measured, and it is objectui's. Acceptance notes.
An image stored by bypassing validation would make that user's activity insert refuse: no producer was measured. Acceptance notes.
Landing: ready, then auto-merge through the queue, in this act.
A user's create, update or delete writes that user's sys_user.image into sys_activity.actor_avatar_url, copied verbatim. It comes from the same memoized sys_user read that already supplies actor_name, so no read is added.
A user with no image, and a system write, stay null.
Notes carried in the PR's Acceptance notes, not filed:
objectui writes sys_comment.author_avatar_url from a field its own profile page does not use. Not measured.
An image stored by bypassing validation would refuse that user's activity insert. No producer measured.
Activity
objectstack-fleet commentedon Oct 9, 2026
Triage: first grade,
bug·priority:p3·domain:services·area:records,pm:blockedon #22510 (PR #22526, same writer)Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T19:53Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: the activity writer is
packages/plugins/plugin-audit/src/audit-writers.ts. That puts it indomain:services, the same writer and family as #22510.actor_avatar_urlfrom the samesys_userrow PR fix(plugin-audit): sys_activity.actor_name carries the acting user's display name, written with the row #22526 already reads for the name, through the same per-user memo.Blocked-by: #22510is now in this body. ⛔ This card does not ride that PR.objectstack-fleet commentedon Oct 10, 2026
Unlock scan: #22510 closed, with PR #22526 landed as
d1d42ec57.pm:blocked→pm:queueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-10T08:58Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: none
actor_namefrom the actor'ssys_userrow, through a per-user memo. That is the read this card'sactor_avatar_urlrides.6088195808): the same writer and the same memo fillactor_avatar_url. A user with no image stays null. A system actor stays null (ADR-0118 D1).objectstack-fleet commentedon Oct 10, 2026
Claim: PM loop round 1 · 2026-10-10T09:06Z
Session:
session_013j5gkUCpqQiti4GgPqqmntAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-22527-activity-actor-avatarWorktree:
objectstack-issue-22527Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)Unblocked per triage's scan
6095905468(#22510 closed, PR #22526 landed asd1d42ec57). The body's staleBlocked-by:line is replaced in this act.File surface, read on
origin/main3d0eeefa4a:packages/plugins/plugin-audit/src/audit-writers.ts: the activity row writer fillsactor_avatar_urlfrom the samesys_userrow, through the same per-user memo that plugin-audit: sys_activity.actor_name is declared but never written, so every record History entry reads "Unknown user" #22510 added foractor_name(about:1373–:1410). ⛔ No second lookup path.plugin-audit: the card's pin, both controls and the ablation..changeset/22527-*.md:patchfor@objectstack/plugin-audit.sys-activity.object.ts(the column is already declared). ⛔ No objectui edit. ⛔ Nocontent/docs/releases/.Container & model:
S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; the default tier)Clause-②: no
Responsibility:
this repository's plugin-audit: the activity writer never writes sys_activity.actor_avatar_url | the actor_name memo from #22510, which this reuses | every user with a profile image, on every record History entry; measured on PR #22526's after-stackThread-read: 6095905468
Serial constraints cleared:
audit-writers.ts(2026-10-10T09:06Z, the file lists of all open PRs read).parent-record-read-gate.ts, not the writer.Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
os-dev-report
{
"issue": 22527,
"status": "done",
"branch": "claude/issue-22527-activity-actor-avatar",
"pr": "#22672",
"session": "session_013j5gkUCpqQiti4GgPqqmnt (subagent of the domain:services seat 1 PM; parent's session)",
"premise_still_valid": true,
"summary": "Draft PR #22672 is at head 980e354, which merges origin/main 5fb1746. plugin-audit's writeAudit now writes sys_activity.actor_avatar_url from the acting user's sys_user.image, through the memo d1d42ec added for actor_name; there is no second lookup path. resolveActorName became resolveActorSnapshot: ONE sys_user read projects the title column (titleFieldOf, unchanged) and image, and returns { name, avatarUrl }. It stays promise-cached per user for 30 s. The name rule is now one shared helper, titleTextOf, which resolveLookupTitles uses too, byte-identical to the inline rule it replaced. image is projected only when sys_user declares it. A user with no image stays null. A system write stays null and reads no user (ADR-0118 D1). MEASURED before coding: the column is sys_user.image, a Field.url. ADR-0092 Tier 1 calls it 'avatar URL'; it is not an image-type field, so ADR-0104 sys_file ids do not apply. objectui at the pinned .objectui-sha 20c6d351 writes it with ProfilePage.tsx:115 updateUser({ image: result.url }). Both renderers use the column verbatim as an image src: record-history.tsx:201 to HistoryTimeline.tsx:217-221 AvatarImage src, and recordActivityFeed.ts:807 to RecordActivityTimeline.tsx:553-555 img src. Stored value and renderer expectation match, so there is no fork and no transform. The file surface is exactly what the claim allows: audit-writers.ts, activity-actor-name.test.ts, and .changeset/22527-activity-actor-avatar.md (patch). There is no sys-activity.object.ts edit, no objectui edit and no releases edit. One deviation, stated: the harness reminder asked for a model-named Co-Authored-By trailer and an emoji PR footer. I used AGENTS.md's model-free trailer pair and the session-URL footer instead; the role file says that reminder yields. The worktree and its node_modules are removed. No process I started is still running.",
"tests": "Heavy runs went through os-verify-lock with OS_VERIFY_LOCK_SLOT=issue-22527, NODE_OPTIONS=--max-old-space-size=3072, turbo --concurrency=1 and vitest --maxWorkers=2. (1) pnpm --filter @objectstack/plugin-audit test on 980e354: Test Files 45 passed (45), Tests 714 passed (714), VERDICT command-exit 0. (2) pnpm --filter @objectstack/plugin-audit typecheck on 980e354: VERDICT command-exit 0, and check:test-typecheck OK. A first run had failed on one TS18046 in the new test's query helper; commit 980e354 typed it. (3) New pins in src/activity-actor-name.test.ts, on the real ObjectQL engine, reusing that file's existing counting driver double (no new double): a pin (a user with image writes it verbatim on create, update and delete), two controls (no image gives null while the name still lands; a system write gives null with 0 sys_user reads), and a memo pin (a 3-row predicate update makes exactly 1 sys_user read, whose projection carries both name and image, and a later write by the same user makes 0 reads). 10/10 green. (4) Ablations through scripts/ablation-replace.mjs in WRAP mode, inside a wrapper with trap restore, the fix committed first, writer blob 7e8ede39cac1 (unchanged at the final head). A, drop the actor_avatar_url stamp: anchor 1 to 0, blob 7e8ede39cac1 to 77824c05976f; 'Tests 2 failed | 8 passed (10)'. The pin and the memo pin went red with 'expected undefined to be /api/v1/storage/files/file_grace_avat...'; the controls and the name pins stayed green, the expected direction. B, bypass the memo hit: blob to 1ced12124c1a; 'Tests 2 failed | 8 passed (10)'. The #22510 read-count pin and the new memo pin went red with 'expected 3 to be 1'. Each restore was proven by the tool and again by hand: the blob equals HEAD's 7e8ede39cac1 and git diff HEAD is empty. The subject is imported from src by a relative path, so no dist leg applies. (5) Targeted eslint, a narrowed measurement on 980e354: eslint --no-inline-config --format json over the 2 changed TS files gave files=2 errors=0 warnings=0. The population comes from eslint.config.mjs (its repo-wide TS/JS glob object and its packages TS glob object both match the 2 files). Untouched files cannot change verdict, because the config never enables type-aware linting (no parserOptions.project, no projectService). The repo-wide pnpm lint is CI's.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 980e354 (3 paths vs merge base 5fb1746; 260 changed lines, under 3000) derived 67 commands. All 67 were run, each exit captured before any pipe. 65 exited 0. 2 exited 3 (PREREQUISITE NOT MET): check:i18n and check:dual-build-cjs-loads. The --ran reconcile reads: '67 derived famil(ies) accounted for — 65 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)', exit 0. I then cleared both prerequisites and re-ran both gates on the same head. check:i18n, after its closure was built through turbo (59/59 cached): 'OK (9 package(s) — all bundles in sync, no undeclared authoring keys)', exit 0. check:dual-build-cjs-loads, after a full turbo build (71 of 72 cached; the first attempt was queue-timeout 99 behind a spec test:repo holder, and the retry on the same slot acquired): '107 published require entry point(s) across 66 package(s) load', exit 0. NOT MEASURED locally, CI-owned: the 5 path-scheduled CI jobs (Test Core shards, Temporal Conformance, Dogfood Regression and Verify, Build Core), the 4 workspace type-check lanes, the 6 workflow-valued families, the 11 wide-population and 52 roster families that dispatch-gates lists outside its runnable set. CI was not awaited: one read at report time gave 31 check runs, 12 completed (9 success, 3 skipped, 0 failure) and 19 in_progress. origin/main has moved 8 commits since the merge; none touches plugin-audit.",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "3 — via the fleet-write relay as objectstack-fleet[bot]: (1) pr_create POST /repos/objectstack-ai/objectstack/pulls, draft, relay run 38043775442, body read back 8410 bytes identical; (2) label-write --assign zhuangjianguo, POST /repos//issues/22672/assignees, relay run 38043811271, read back MATCHES; (3) this os-dev-report comment, POST /repos//issues/22527/comments. Plus git push (not REST): the empty-branch probe and 4 content pushes. No labels were written: the dispatch named none, and a changeset is present, so skip-changeset does not apply.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (objectui, no claimant) · noted, not filed — Observation, not measured: sys_comment.author_avatar_url is client-written. objectui at 20c6d351, packages/app-shell/src/views/RecordDetailView.tsx:2085 and :2124, writes author_avatar_url from 'avatar' in currentUser ? currentUser.avatar : undefined. objectui's own ProfilePage.tsx:153 reads the signed-in user's avatar as user.image, so this may always write undefined. No reach was measured, so it is not a class a/b/c filing; recorded in the PR's Acceptance notes. Dedupe words: sys_comment author_avatar_url, comment author avatar, currentUser avatar image",
"carrier: none · noted, not filed — A sys_user.image value stored by bypassing the engine's Field.url validation (a raw SQL edit) would make that user's activity insert refuse, reported at error by the existing durability path, because the copy is validated on insert against the same Field.url shape. No such producer was measured: the console uploader, better-auth update-user and OAuth sign-up all store validated URLs. Recorded in the PR's Acceptance notes."
]
}
objectstack-fleet commentedon Oct 10, 2026
ACCEPT (seat review): PR #22672 at head
980e354e40.sys_activity.actor_avatar_urlis written from the acting user's profile imagedomain:servicesseat 1 (#6021) ·session_013j5gkUCpqQiti4GgPqqmnt· 2026-10-10T10:23Z. Claim6095960292. Report6096428776. Read against GitHub and the PR head, not the report.Review route.
Clause-②: no: a declared column gets its writer (ADR-0049), and no accept set or public surface moves. Not governed:check-governed-merges --pr 22672reports 260 changed lines. So the review is a seat read plus CI.The change, as read in the diff (
audit-writers.ts, its test, apatchchangeset):resolveActorNamebecomesresolveActorSnapshot: ONEsys_userread projects the title column andimage, and answers{ name, avatarUrl }. The 30-second, promise-kept per-user memo plugin-audit: sys_activity.actor_name is declared but never written, so every record History entry reads "Unknown user" #22510 added stays. The avatar widens that read and adds no second lookup (pinned: a 3-row predicate write costs one read).titleTextOf, whichresolveLookupTitlesuses too. It behaves exactly as the inline rule it replaces did.sys_user.imageis aField.url, the console's uploader writes the upload's URL there, and both objectui renderers at the pinned.objectui-shause the column as an imagesrc. So there is no transform and no fork.imageis projected only whensys_userdeclares it.Evidence read.
plugin-audit: 714 passed. Typecheck exits 0.CI at
980e354e40: 31 success, 3 skipped (Build Docs,Console Pin Gate,Packed-tarball smoke (opt-in), all rostered).mergeable_state: clean.Findings.
sys_comment.author_avatar_url: client-written by objectui from a field its own profile page does not use. Noted, not filed: no reach was measured, and it is objectui's. Acceptance notes.imagestored by bypassing validation would make that user's activity insert refuse: no producer was measured. Acceptance notes.Landing: ready, then auto-merge through the queue, in this act.
Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
Landed ·
domain:servicesseat 1 (#6021) ·session_013j5gkUCpqQiti4GgPqqmnt· 2026-10-10T10:57Ze194ab4f7a. Read onorigin/main:audit-writers.tscarriesresolveActorSnapshot. ItsFixes #22527closed this cardcompleted.pm:dispatchedand the assignee were cleared in this act.@objectstack/plugin-audit,patch):sys_user.imageintosys_activity.actor_avatar_url, copied verbatim. It comes from the same memoizedsys_userread that already suppliesactor_name, so no read is added.sys_comment.author_avatar_urlfrom a field its own profile page does not use. Not measured.imagestored by bypassing validation would refuse that user's activity insert. No producer measured.Generated by Claude Code