Skip to main content

The audit trail is not court-grade

The audit trail is the product's core value — a disciplinary process stands or falls on being able to show who did what, when. The PoC's audit trail has four independent weaknesses, each of which alone would disqualify it as evidence.

  1. Client-authored and client-trusted. case_audit_events rows are inserted by the browser (useAuditLog.useLogEvent, useChecklistOperations, useWorkflow) with client-supplied actor_user_id, actor_identifier, event_type, and metadata. A malicious or buggy client can forge, omit, or backdate entries. A real audit log must be server-authored from the authenticated identity, append-only, ideally hash-chained.

  2. Best-effort / fire-and-forget. In useChecklistOperations, the case_audit_events insert is .then()-chained, never awaited, with no error handling. If it fails, the user action still succeeds — silently unlogged.

  3. Conditional on companyId. Those same inserts only run if (companyId). Because the active company is held in memory only (company context is in-memory only), after a page refresh companyId is null and audit logging silently no-ops while the user keeps working.

  4. Truncated. useAuditEvents hard-caps at limit(200) (and limit(50) for entity audit) with no pagination. On a long case the "complete" log is silently cut off. The dossier export caps audit at limit(1000).

There are also two parallel audit systems that must be reconciled: the case-level event stream case_audit_events and the item-level checklist_audit_log (old/new value). See Two audit systems.

What breaks: in a dispute, the log is challengeable on every axis — authenticity, completeness, and reliability. The marketing video promises "Audit trail completo" (intent vs reality); the implementation does not deliver it.

Rebuild implication

Server-authored, awaited, unconditional, paginated, append-only, tamper-evident. One canonical audit stream.