Task Breakdown: Activity Log
Feature ID: activity-log
Status: Done (Retrospective)
Last updated: 2026-05-08
Phase 1 — Schema
- T1.
activity_logentity + repository (packages/agent/src/entities/activity-log.entity.ts,packages/agent/src/database/repositories/activity-log.repository.ts). - T2.
ActivityActionType+ActivityStatusenums (packages/agent/src/entities/activity-log.types.ts). - T3. Indexes:
(userId, createdAt),(userId, actionType),(userId, workId),(userId, status)— anduserIdFK cascade,workIdFKON DELETE SET NULL.
Phase 2 — Service
- T4.
ActivityLogService.log(entry)— persists row, fire-and-forget analytics dispatch, debug log. - T5.
ActivityLogService.updateStatus(id, status, details?, updates?)— partial update, re-dispatches the updated row, null-when-missing semantics. - T6.
findAll/findById/findByIdAndUserId/countRunning/summarizeStatuses/findLatestByUserWorkActionStatusquery helpers. - T7.
exportCsv(query)— 10 000-row cap, six-column header,"-escape, work-name + summary quoting. - T8.
reconcileStaleGenerationActivities(userId)— bulk-fetch works, skip liveGENERATINGrows, terminal-status mapping, per-row try/catch, outer try/catch, debug log on success. - T9.
formatGenerationCompletionSummary/resolveGenerationActivityStatus— keep summary text consistent across listener and reconcile pass. - T10. Optional
ACTIVITY_LOG_ANALYTICS_DISPATCHERtoken wired via@Optional() @Inject(...).
Phase 3 — API
- T11.
ActivityLogController— six endpoints behindAuthSessionGuard:GET /api/activity-log(list,Math.min(limit, 100)cap).GET /api/activity-log/running-count.GET /api/activity-log/summary.GET /api/activity-log/export(CSV, attachment header).GET /api/activity-log/:id(cross-user 404,liveLogsenrichment).
- T12. Reconcile front-step on every endpoint via
reconcileActivities(userId)—reconcileInFlightMap +reconcileCompletedAtMap +ACTIVITY_RECONCILE_TTL_MS = 5_000. - T13. ParseInt / DefaultValue pipes on
limit(default 25) andoffset(default 0); date-string →Dateparsing fordateFrom/dateTo. - T14. CSV response shape:
Content-Type: text/csv+Content-Disposition: attachment; filename=activity-log.csv+res.send(body).
Phase 4 — Event Listeners
- T15.
ActivityLogListenerwith nine@OnEventhandlers:WorkCreatedEvent→WORK_CREATED/work.created.WorkGenerationCompletedEvent→ update existing in-progress row viafindLatestByUserWorkActionStatuselse create freshGENERATION/generation.completedrow; details from latestWorkGenerationHistory.WorksConfigSyncFailedEvent→WORKS_CONFIG_SYNC/works_config.sync_failed.UserCreatedEvent→USER_SIGNUP/user.signup.UserConfirmedEvent→USER_LOGIN/user.confirmedw/via <provider | email>summary.UserPasswordChangedEvent→PASSWORD_CHANGED/user.password_changed(forwardsipAddressfrom event payload).MemberInvitedEvent→MEMBER_INVITED/member.invitedw/inviteeEmail+roledetails.DeploymentDispatchedEvent→DEPLOYMENT/deployment.dispatchedw/ statusIN_PROGRESS.DeploymentCompletedEvent→DEPLOYMENT/deployment.succeededw/ optionalDeployed … to <url>summary.DeploymentFailedEvent→DEPLOYMENT/deployment.cancelled|failedw/terminalState=CANCELED→CANCELLEDstatus mapping.
- T16. Per-handler
try/catch+logger.errorso audit gaps do NOT propagate back to the event emitter.
Phase 5 — Analytics Dispatcher
- T17.
ActivityLogAnalyticsDispatcherinjection token (packages/agent/src/activity-log/activity-log-analytics-dispatcher.ts). - T18.
JitsuServiceenv-driven adapter (apps/api/src/activity-log/jitsu.service.ts): - Disabled at construction whenJITSU_HOSTorJITSU_WRITE_KEYis missing (single info-level log line). -track(activity)merges plain-object metadata only, otherwise treats metadata as{}. - ForwardsactivityId / userId / workId / actionType / action / status / summary / details / createdAtplus the metadata properties to the Jitsu client withactionas the event name. - T19. Dispatcher binding wired in
apps/api/src/activity-log/jitsu.module.ts.
Phase 6 — Web
- T20. History tab consumes
GET /api/activity-logwith the filter UI mapped to theactionTypetaxonomy. - T21. Sidebar badge polls
GET /api/activity-log/running-count. - T22. Activity drawer reads
GET /api/activity-log/:idand rendersdetails.liveLogsfor in-progress generations. - T23. CSV export button triggers
GET /api/activity-log/export?...with the active filters.
Phase 7 — Tests
- T24.
activity-log.service.spec.ts(packages/agent) — coverslog/updateStatus/findAll/countRunning/summarizeStatuses/findById/findByIdAndUserId/findLatestByUserWorkActionStatus/exportCsv/reconcileStaleGenerationActivities/formatGenerationCompletionSummary/resolveGenerationActivityStatus. - T25.
activity-log-summary.spec.ts(packages/agent) — coversformatGenerationCountsSummaryandformatStoredActivitySummary. - T26.
activity-log.listener.spec.ts(apps/api, 25 tests) — every@OnEventhandler, both happy and error paths (#482). - T27.
jitsu.service.spec.ts(apps/api, 9 tests) — env-driven enable/disable, plain-object metadata gate, optionalworkId/details, ISO timestamp forwarding (#482). - T28. Follow-up:
activity-log.controller.spec.ts(apps/api) — currently only the agent-package service spec and the listener / Jitsu adapter unit suites cover this area. The controller's reconcile-debounce (in-flight Map + 5-second TTL), CSV-export response shape,liveLogsenrichment branch, andMath.min(limit, 100)cap have no dedicated controller-level test. Pattern lives inapps/api/src/notifications/notifications.controller.spec.ts— replicate it. - T29. Follow-up: integration tests in
packages/agent(Jest) hitting a real Postgres test container to pin (a) the four composite indexes, (b) the(userId, workId)ON DELETE SET NULLbehaviour, and (c) the reconcile pass against a seeded orphan-in-progress row. Currently only unit tests cover the reconciliation path.
Phase 8 — Docs
- T30. Architecture spec (
docs/specs/architecture/activity-log.md). - T31. This Spec Kit folder (spec / plan / tasks).
- T32. Follow-up: user-facing doc at
docs/features/activity-log.mddescribing the History tab, filter taxonomy, CSV export workflow, and how to interpret the differentActivityActionTyperows. - T33. Follow-up: API reference cross-link from
docs/api/activity-log.mdonce the per-endpoint REST docs land (currently the@nestjs/swaggerdecorators are the canonical source of truth).
Definition of Done
- Service is implemented and unit-tested at the agent layer
(
activity-log.service.spec.ts+activity-log-summary.spec.ts). - All controller endpoints sit behind
AuthSessionGuardand filter every query byuserId. - Lazy reconciliation rewrites orphaned
GENERATIONin_progressrows on every read. - Analytics dispatch is optional, env-gated, and fire-and-forget;
dispatcher failure does NOT throw out of
log()/updateStatus(). - Listener side handles errors with
try/catch + logger.errorso audit gaps do NOT propagate to the event emitter. - Architecture spec + Spec Kit folder authored.
Follow-ups discovered
- T28 — controller-level unit suite is the only API-side gap.
Add an
apps/api/src/activity-log/activity-log.controller.spec.tsmodelled after the notifications controller spec; cover reconcile-debounce, CSV envelope,liveLogsenrichment, the 100-row clamp, and 404-on-cross-user branches. - T29 — DB integration tests against a Postgres test container
to pin schema (composite indexes, FK
ON DELETE SET NULL) and the reconcile pass. - T32 / T33 — user-facing docs and API-reference docs.
- Reconciliation cron — if the lazy per-request approach ever
pressures the read path, consider promoting the reconcile pass to
a
DistributedTaskLockService.runExclusive('activity-log:reconcile', …)cron job, similar to the notifications cleanup worker. - Storage partitioning — the architecture spec calls out per-work partitioning as a future change at the 1M-row scale; this is the spec to amend if that work lands.