Skip to content

feat: attributed activity log for tasks and projects#133

Draft
FrkAk wants to merge 21 commits into
mainfrom
worktree-task-activity-log
Draft

feat: attributed activity log for tasks and projects#133
FrkAk wants to merge 21 commits into
mainfrom
worktree-task-activity-log

Conversation

@FrkAk

@FrkAk FrkAk commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Summary

Task Reference: [MYMR-240]

Replaces the hardcoded binary agent/user JSONB history blob (tasks.history, projects.history) with a dedicated, RLS-gated activity_events table that records, for every discrete change, who (real human), how (which agent harness), and what (the specific entity touched).

  • Storage: new public.activity_events table (one row per changed scalar / per collection element), gated by the existing projects membership policy; metadata {from,to} populated for status_changed / priority_changed / estimate_changed / moved.
  • Attribution: AuthContext gains a durable-keys-only actor descriptor (source web/mcp/system, userId, OAuth clientId). Web actor from the session; MCP actor from the JWT azp claim. The write path never touches neon_auth.
  • Read-time identity, no service_role: name/avatar/harness are resolved at read time via two SECURITY DEFINER functions modeled on task_assignees_visibleactivity_actors_visible(p_task_id) and oauth_client_name(p_client_id).
  • Read path: getTaskFull* stops shipping history (egress win); new cursor-paginated GET /api/task/[taskId]/events; the rebuilt ActivitySection renders avatar + name + MCP harness badge + relative time (absolute on hover) with "show more".
  • Backfill: one-time idempotent scripts/backfill-activity-events.sql (run with migration creds, not runtime).

Type of change

  • New feature

Testing

  • Tested locally with bun run devnot run in this environment (no dev stack); panel verification is a manual step, see notes.
  • Linting passes (bun run lint)
  • Typecheck passes (bun run typecheck)

Full bun test is green: 788 pass / 0 fail across 90 files, including RLS isolation for activity_events (a non-member reads an empty page), the read-time SDF hydration, edge two-row writes, updateTask discrete-diff splitting, and backfill mapping + idempotency.

Notes for reviewer

  • No service_role anywhere in runtime code. Identity is resolved at read time through the two SDFs above (granted to app_user, membership re-checked inside). The write path stores only durable keys (actor_user_id, source, actor_client_id).
  • RLS belt: activity_events was added to the explicit ENABLE/FORCE ROW LEVEL SECURITY lists and given an explicit grant in docker/*.sql (required by tests/db/rls-coverage.test.ts), beyond the Drizzle .enableRLS().
  • Plan deviation (intentional): listTaskActivity resolves the page and its read-time identity in a single RLS-scoped SQL statement that joins the SDFs inline (the codebase's task+assignees idiom, via a lib/db/raw/ statement builder), rather than the plan's two-step JS hydration — withUserContextRead's build callback is pure/non-awaiting, so the awaiting-callback shape the plan sketched isn't valid here.
  • Edge events write two rows (one per endpoint task), preserving the old appendTaskHistoryMany WHERE task_id = ? semantics.
  • Dormant columns: tasks.history / projects.history are left in place but unused for one release; a follow-up migration drops them once parity is verified (spec §G step 4).
  • Manual rollout steps not in this PR: (1) run scripts/backfill-activity-events.sql once against the target DB with migration credentials; (2) eyeball the panel via bun run dev.
  • Realtime live-prepend and a project-wide activity feed UI are intentionally deferred (the table + indexes already support them).

@FrkAk FrkAk marked this pull request as draft June 13, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant