Audit logs — what's recorded and where to read it
Use Clineso audit history to understand who changed important patient, prescribing, payment, and practice records and when.
The audit_logs table is append-only. Once written, a row
cannot be edited or deleted via any UI or RPC.
What gets audited
Every action that has clinical, financial, or compliance significance:
Clinical decisions
case_approved/case_declined/case_more_infoprescription_issued/prescription_cancelledcase_product_set
Fulfilment
order_labeled/order_manifested/order_dispatchedorder_cancelledinventory_consumed(per batch decrement from FEFO)inventory_adjusted(manual)
Comms
comm_event_sent/comm_event_failedcomm_template_updated(with old + new wording)
Patient / staff
patient_created(auto on payment)user_role_granted/user_role_revokedconsent_signed(with wording snapshot)requirement_verified/requirement_rejected
Compliance
gdpr_export_generatedpatient_deletion_requested/_approved/_refusedtenant_suspended/tenant_unsuspended/tenant_anonymiseddpa_processor_acknowledged
Anti-spam
funnel_submission_rejected(with reason + IP)
Schema
Each audit row:
id— uuidtenant_id— scopeaction— slug (e.g.case_approved)actor_user_id— who did it (NULL for system actions)subject_table— table of the subject (e.g.prescription_requests)subject_id— primary key of the subject rowmetadata— JSONB with action-specific detailcreated_at— timestamp
How to read it
From the UI
/practice/audit — filterable audit explorer per tenant. Filter
by action / actor / subject. Chips link directly to the subject
row (e.g. a case_approved chip opens the case detail).
From SQL
select *
from public.audit_logs
where tenant_id = '<tenant-uuid>'
and action = 'case_approved'
order by created_at desc
limit 100;
Across tenants (platform admin only)
Platform admins can query across tenants. Same query without the tenant filter, but RLS will only return rows their grants permit.
Why it's append-only
UK GDPR + MHRA expect every clinical decision to be attributable, dated, and immutable. The table:
- Has no UPDATE or DELETE policy
- Is enforced by RLS — only
service_roleand SECURITY DEFINER RPCs can insert - All inserts go through the
write_audit()helper that validatesactor_user_idagainst the current session
What's NOT audited
- Reads — looking at a record doesn't write to audit_logs. (Read-audit is a separate, much larger problem — out of scope for v1.)
- UI navigation — clicking around without taking an action doesn't audit.
- Failed actions — RLS-blocked actions don't write audit (the row never gets created in the first place). Server errors are in observability logs separately.
Retention
Audit logs are retained for the platform's UK clinical-record retention period (currently 10 years from the relevant patient's last interaction). Anonymisation strips actor PII from audit rows but never deletes the row.
When to investigate
Audit logs are your first stop for:
- "Who approved this case?"
- "When was this template changed?"
- "Did the patient actually consent to this version?"
- "Which batches did this prescription consume?"
If you can't find an answer in audit_logs that should be there, escalate — it's either the wrong query or a regression we need to look at.