Milestone: M9 — Hardening and Compliance | SOW Reference: FR8, NFR3 (also feeds M3-11) | Requirement Clarity: ✅ Clear | Dev Status: 🟡 Partially prototyped Moved from
docs/requirements/tasks/POC/24-logging-tracking-reconciliation.md. This is the audit-logging phased plan (A→B→C→D) that M9-01/M3-11 should build against. Unmodified below.
M9-01b Task 24: Logging & Tracking — Reconciliation of Docs vs Reality¶
Priority: Reference / planning (no code changes in this task) Created: 2026-04-16 Purpose: Single source of truth for what user-activity logging is actually running today, what's announced in the user-story docs but not built, and the phased plan to close the gap without disturbing the live POC.
Why This Task Exists¶
During an end-of-POC audit we discovered three mismatches between what the user-story documents describe and what the codebase actually does:
- Session tracking is more mature than the docs admit — server-side sessions with device fingerprinting and
lastSeenAtall work end-to-end, but the docs describe only the Auth0 side. AuditLogentity exists with no writers — the table is created and migrations run, but zero services write to it. Compliance docs promise audit trails that don't exist.- User-activity monitoring and analytics are aspirational — dashboards and metrics are documented as features but have no backend implementation.
This file lines up the documents with the code and sets a phased plan so we build (or honestly defer) each missing piece.
PDF Requirement Cross-Reference¶
The v3.0 PDF (REC Verifiable Credentialing Platform High-level Requirements v3.0.pdf) explicitly mandates audit logging. This plan tracks directly to those lines:
| PDF Location | Requirement | Phase in this plan |
|---|---|---|
| p.4 — Security, Privacy and Compliance | "Maintain detailed and immutable audit logs, including timestamped records for all data access and changes." | Phases A (writes) + D (immutability) |
| p.4 — Reporting & Analytics | "Generate compliance and audit reports (user access, data changes, consent logs) on demand and scheduled basis." | Phase B (UI + export) |
| p.11 — Platform Admin / RFP ref 9 — "Audit trail of User activity" | "Initial versions of the MVP will allow user activity to be queried by Platform Admins via the back-end support team only." | Phase A satisfies MVP; Phase B is the post-MVP admin UI |
| p.15 — Other / RFP ref 3 — "Log the credentials offers, issuances and verifications" | "User activity such as Credential Offers, Issuance, Presentation requests and Verification will be logged by the system as an audit trail. Initial versions of the MVP will allow this audited activity to be queried by via the back-end support team only." | Phase A (events: credential.issued, credential.revoked; Presentation + Verification events arrive with Drop 2) |
Compliance gap today: the POC is technically non-conformant with the PDF until Phase A ships. The audit_logs table is defined but empty. MVP does not require a UI — just that rows be written and queryable via direct DB access. Phase A is therefore the minimum viable audit scope per the PDF.
Live Today — Do Not Break¶
Treat the following as stable functionality. Any future work must preserve backward compatibility:
| Capability | Schema | Service | API | UI |
|---|---|---|---|---|
| Server-side session persistence | sessions table (userId, auth0SessionId, ipAddress, userAgent, metadata JSONB with device/browser/OS, issuedAt, expiresAt, lastSeenAt) |
SessionService (createSession, extendSession, updateLastSeen, terminateSession, terminateAllOtherSessions) |
GET /auth/sessions, DELETE /auth/sessions/:id, DELETE /auth/sessions |
/account/sessions page with SessionList.tsx |
| Email delivery tracking | email_logs table with EmailStatus lifecycle (QUEUED → SENT → DELIVERED → OPENED / CLICKED / BOUNCED / FAILED) |
EmailService + EmailTrackingService |
GET /email/track/open/:id.png, GET /email/track/click/:id?url=... |
Implicit — statuses feed into operational dashboards (none yet) |
Planned — Not Yet Built¶
| Capability | Schema already present? | Write path? | Status |
|---|---|---|---|
| Generic audit logging (who did what, when, on which resource) | ✅ audit_logs table + AuditLog entity |
❌ Zero call sites | Entity is orphaned |
| Admin-visible audit trail (filtering, export) | — | ❌ No service, no API, no UI | Not started |
| Login / security-event stream | — | ❌ No entity | Not started |
| User-activity analytics (logins over time, active users, session duration, feature usage) | Partial — sessions table has raw data | ❌ No aggregation service, no dashboard | Not started |
| Suspicious-activity alerts | — | ❌ No detection pipeline | Not started |
| Immutability guarantees on audit records (append-only, cryptographic chain) | — | ❌ Not designed yet | Not started |
Documents Affected by This Reconciliation¶
These user-story files have been updated only at the top with a status banner so readers see the reality before the acceptance criteria:
- session-management.md — added "Current POC Implementation Status" banner confirming session persistence is live and flagging which ACs are Drop 1+.
- compliance-audit-reporting.md — added "Not Yet Implemented" banner; explains the
AuditLogentity exists but has no write path. - user-activity-monitoring.md — added "Not Yet Implemented" banner with dependency note on audit logs.
The acceptance criteria below each banner are preserved as the target specification; the banner is the current reality.
Phased Plan to Close the Gap¶
Phase A — Minimum audit write path (MVP-required per PDF p.11 + p.15, ~3-5 days)¶
Goal: satisfy the PDF's MVP audit-trail clause by writing audit_logs rows. No UI needed — "back-end support team only" access is explicitly sufficient for MVP.
PDF-mandated events (p.15 RFP ref 3):
1. credential.issued — wire into ManualCredentialService.createManualCredential
2. credential.revoked — wire into CredentialService.revokeCredential
3. presentation.requested — wire when the disclosure feature lands (Drop 2)
4. presentation.verified — wire when verification feature lands (Drop 2)
High-signal events we're adding beyond the PDF minimum (prepares for Phase B reports):
org.created—OrganizationService.createOrganizationorg.kyb_changed—SuperAdminOrganizationService.updateKybStatusorg.issuer_enabled—SuperAdminOrganizationService.enableIssuerCapabilitiessession.terminated—SessionService.terminateSession(distinguish self-logout vs admin force-terminate)
Row shape (all columns already exist on the audit_logs entity):
- actorUserId, orgId, resourceType, resourceId, before/after JSONB snapshots, ipAddress, userAgent, createdAt.
Delivery mechanism: a NestJS @AuditLog('action.name') decorator + interceptor so services stay clean and every event has a uniform shape. Single AuditLogService.log() dependency in the interceptor, not sprinkled in business services.
Phase B — Admin audit-trail UI (Drop 1 Beta, ~3 days after Phase A)¶
GET /admin/audit-logswith filters (actor, action, resource, orgId, date range) + pagination.- New frontend page
/superadmin/audit-logswith table, filters, CSV export. - Scope: SuperAdmin sees all; ORG_ADMIN sees their org only.
Phase C — Activity analytics (Drop 2, ~1-2 weeks)¶
- Aggregation service computing daily metrics from
sessions+audit_logs+email_logsinto a newactivity_metricsrollup table. - Dashboard UI with charts (logins over time, active users, top actions, session duration).
- CSV/PDF export.
Phase D — Immutability & alerts (Production Hardening, P4)¶
- Append-only enforcement (DB-level triggers, periodic hash chaining).
- Suspicious-activity rules (e.g. many failed logins, new-device login notifications).
- External log shipping to SIEM (optional).
Principles for Anyone Picking This Up¶
- Do not modify
SessionServiceorsessionstable schema without a migration plan — the UI depends on the current shape. - Do not modify
EmailLogstatus enum or tracking controllers — active email flows depend on the current behaviour. AuditLogentity columns are safe to extend additively (new nullable columns) but not to rename or remove — there may already be rows in staging from exploratory testing.- When adding audit writes, prefer a decorator or interceptor over sprinkling
auditLogService.log()calls in every service body. A@AuditLog('credential.issued')decorator on the controller method keeps the services clean and gives us a uniform event shape.
Cross-References¶
- 14-poc-readiness-audit.md — original audit that flagged missing audit-log writers
- M9-00-security-encryption-audit.md — encryption coverage of audit data (needed for Phase D)
- 18-master-execution-plan.md — fold Phase A/B/C/D into P2 (Drop 1 Beta) and P3/P4 rows
- 20-code-cleanup-audit.md — note:
AuditLogentity is not a removal candidate; it's a planned-future-work placeholder and Phase A wires it up