StudyLyon — multi-tenant ERP / School Management API. This package designs the
Audit module client (Flutter, forward-looking spec) against the implemented NestJS
backend. Every endpoint, filter, field, event, and retention rule is derived directly
from src/modules/audit/**, src/events/**, src/modules/ws/**, src/infrastructure/**,
and studylyon-blueprint/03-Database/AUDITING.md + DATA_RETENTION.md. No feature is
invented; gaps are flagged in the Assumptions & Open Questions section.
Audit is StudyLyon's immutable, append-only record of every business action. It is the
system of record for compliance, forensic analysis, dispute resolution, and tenant security
review. Every domain event that crosses the in-process EventBus is captured into
audit_logs by the AuditHandler wildcard subscription and can be read back through a
single read-only endpoint.
Responsibility
Source
Capture every domain event into audit_logs (wildcard onAny subscription)
audit.handler.ts:18-25
Mask secrets (passwordHash, totpSecret, refreshToken, accessToken, token, password) before storage
audit.service.ts:5-12, 14-23
Append-only persistence (create/find/count only; no update/delete repo methods)
audit-log.repository.ts:6, 15-28
Read endpoint with filters: action, entityType, actorId + page/limit
audit.controller.ts:17-34
Tenant isolation on every query (tenantId from request context, never from body)
audit.controller.ts:30, audit.service.ts:45-48
Action vocabulary = emitted eventType strings (PascalCase, e.g. UserCreated, StudentUpdated)
audit.handler.ts:36; emitters e.g. users.service.ts:65,146,177
Realtime delivery of the same events to the tenant room via WS bridge
ws-bridge.service.ts:16-22
Retention policy: 7 years minimum, immutable, no auto-delete (blueprint)
DATA_RETENTION.md:29
Dedicated audit-write BullMQ queue declared; worker is a no-op today
Schools handle children, fees, and credentials; disputes and inspections are routine.
AUDITING.md makes audit a first-class architectural concern: every CUD + auth + config
action is captured, immutable, tenant-scoped, and retained longest of any collection. The
backend implements the capture + read pipeline today; the client must present it without
ever implying entries can be edited, deleted, or fabricated.
No detail endpoint:GET /audit-logs/:id does not exist — detail is rendered from the list payload (OQ-4).
Pagination shape deviation: service returns {data, total}withoutmeta → the envelope interceptor treats it as a non-paginated payload; client must read data.data[] + data.total (OQ-2, 12_API_Mapping).
Empty resource metadata: most emitters do not set entityType/entityId in payloads today → entityType filter matches nothing for most actions (OQ-1).
No date-range filter: blueprint promises "query by date range" (AUDITING.md:86) but no from/to params exist — (planned).
No search/sort:q and sort params from the shared convention (00-shared/07 §5) are not supported; sort is fixed occurredAt desc (audit-log.repository.ts:23).
TTL contradiction:migrate.ts:17-23 creates a 90-day TTL index on occurredAt, contradicting the 7-year retention policy (OQ-6).
Audit reads are unauthenticated-role-broad: controller is JwtAuthGuard only — any logged-in user (even student, permissions: []) can read the full tenant audit log (OQ-5).
Unbounded limit: no max clamp — limit=10000 is accepted (OQ-7).
WS payloads unmasked:WsBridge broadcasts raw payload (may contain secrets before the masked copy is written); clients must not render it verbatim (OQ-8).
Forward-looking client: backend is complete for capture + list-read; this package specs the UI. The PRD puts native mobile out of Phase 1 (read-only companion in Phase 3 — 00-shared/12 A1); these docs specify the full responsive client anyway (A2: same design system serves web/desktop).
Audit is an admin-dense surface — desktop/tablet table layout is the primary target; phone is a compact list.
Actor display names are not in the response (actorId only). Resolving names requires a client-side join against GET /users(proposed) — actorId filter is exact-id matching (audit.controller.ts:24).
"Export" exists in the blueprint (AUDITING.md:87: CSV/PDF, streamed, itself an audited action) but no endpoint exists → export UI is (planned).
Realtime append is possible today because WsBridge forwards every domain event (the same set the handler persists) to the tenant room (ws-bridge.service.ts:16-22).
Emitters rarely populate entityType/entityId/before in payloads (e.g. users.service.ts:70-75 sends userId only). True "resource" filtering and before/after diffs are effectively absent from today's data. Add entityType/entityId/before to emitters?
Filter UX, diff view
OQ-2
GET /audit-logs returns data:{data:[], total} (no meta) — deviates from the shared paginated envelope. Fix service to return meta (buildPaginationMeta)?
Client parsing
OQ-3
Platform/tenantId:null entries and actorType: platform promised in AUDITING.md:70-71 are not implemented (enum is `user
system
OQ-4
No entry-detail endpoint — deep links to a single entry can't refetch. Add GET /audit-logs/:id?
Detail screen architecture
OQ-5
Controller is JwtAuthGuard only — audit.read defined but unenforced (audit.controller.ts:9); RBAC docs flag the same (design-docs/rbac/05:94-96). Phase-5 permission audit (planned) (docs/IMPLEMENTATION_PLAN.md:241). Client gates on audit.read regardless.
Route gating
OQ-6
migrate.ts:23 creates a 90-day TTL on audit_logs.occurredAt — destroys the 7-year compliance retention (DATA_RETENTION.md:29). Confirm: TTL must be removed or replaced with archive job.
Retention UX, data loss risk
OQ-7
limit unbounded (default 50, audit.controller.ts:20); no clamp to shared max 100 (00-shared/07 §5).
List UX, perf
OQ-8
WS broadcast carries raw, unmasked payload (ws-bridge.service.ts:17-21) while stored docs are masked. If UI renders realtime entries, secret-bearing payloads could surface. Mask at bridge or filter client-side.