Skip to main content

Audit log viewer

Outcome

Auditors pull a focused 30-day access history for a specific patient (or any other entity) and export it as CSV without needing SQL access.

Prerequisites

  • audit.read permission. Tenant migration 109 grants this to CONFIG_MANAGER, OPERATIONS_ANALYST, and EMR_CLIENT_ADMIN. Grant via RBAC matrix for any custom role that needs the viewer.

Surfaces

PathWhat it shows
/admin/auditaudit.access_log rows — every PHI / config access.
Same page, drawerLinked rows from audit.legal_hold + audit.document_reference for context.

Backend: apps/rcm-core/src/modules/audit/ — read-only routes at /api/v1/audit/access-log, /access-log/:id, /access-log/export, /legal-holds, /document-references.

30-day patient access pull (canonical workflow)

  1. Open /admin/audit.

  2. Set Entity type = MEMBER (or whatever entity matters).

  3. Set Entity id = the patient UUID. Use the full UUID — the filter is exact-match.

  4. Set From / To to the 30-day window. The filter inputs accept YYYY-MM-DDTHH:mm and we tag UTC on the way out.

  5. Page through results; click any row to open the detail drawer with the actor, source IP, request id, and the tamper-evidence chain hashes (prev_log_hash / log_hash).

  6. Click Export CSV. The endpoint refuses requests without a bounded date range or with a window wider than 90 days — narrow the range and retry.

The CSV header carries the same column set as the table plus the hash-chain fields, suitable for forensic review.

Rows whose accessed entity matches an active hold in audit.legal_hold carry a red Hold badge inline. The detail drawer also lists the hold in a banner above the metadata grid. The join is computed at request time — a hold released after the row was written stops surfacing immediately on the next refresh.

Retention chip

When the accessed entity has matching rows in audit.document_reference (matched by related_entity_type + related_entity_id), the drawer renders a retention chip per document:

ClassRange
Short≤ 3 yr
Medium4 – 10 yr
Long> 10 yr or permanent

Unknown retention strings render the raw class as a neutral chip so future retention classes don't break the UI.

Pitfalls

  • PHI access-log viewer is per-tenant only. Cross-tenant platform events live in identity.tenant_audit (master DB). They surface in the platform admin viewer (see Platform impersonation + audit).
  • /admin/audit-log is a different surface (UI-18 RBAC change audit). It is gated on config.read and only emits role / role_permission / user_role events. Do not redirect operators to it for HIPAA pulls — they will miss every other entity type.
  • CSV export uses keyset pagination over (accessed_at, access_log_id) and streams in 500-row batches — a 90-day export of a busy tenant returns without buffering the full set. Larger windows are rejected at the route layer.
  • Hash chain is read-only. Verifying the chain is a separate back-office job (not built yet); the drawer just displays the fields so an investigator can copy them into the verifier when it ships.

Validation

CheckExpected
30-day patient pullReturns rows + hash fields
CSV export ≤ 90 daysStreams successfully
Legal-hold rowsDisplay the badge
Retention chip on documentsRenders the right class

Cross-references

Next

8.4 — PHI encryption at rest + key rotation