# 13 — Audit Event Catalogue

Format: `domain.action` · outcome (`success|denied|failure`) · resource · notes. Every event carries tenant, actor, correlation ID, IP (keyed hash plus truncated address), user agent hash, timestamp (UTC). `change_redacted` stores field names and old/new values only for C0/C1 fields; C2+ stores field names and a `changed` marker; C3/C4 never store values.

| Domain | Events |
|---|---|
| auth | `auth.login` (success/failure/throttled) · `auth.logout` · `auth.session.rotate` · `auth.session.revoke` · `auth.password.change` · `auth.password.reset.request` · `auth.password.reset.complete` · `auth.email.verify` · `auth.mfa.enable` · `auth.mfa.disable` · `auth.mfa.challenge` (success/failure) · `auth.recovery_code.use` · `auth.tenant.switch` · `auth.handoff.issue/consume` |
| authz | `authz.denied` (rate-limited) · `role.create/update/delete` · `role.permission.change` · `membership.role.assign/revoke` · `user.invite/accept/suspend/reactivate/remove` |
| tenant | `tenant.create/suspend/reactivate/close` · `tenant.setting.change` · `tenant.preset.apply` · `tenant.module.enable/disable` · `theme.draft.save` · `theme.publish` · `theme.rollback` · `brand.asset.upload/remove` · `support.grant.request/approve/use/expire/revoke` |
| client | `client.create/update/archive/restore` · `client.sensitive.view` (health, ID number, documents) · `client.sensitive.update` · `client.note.create/update` · `client.tag.change` · `client.consent.grant/withdraw` · `client.agreement.sign` · `client.document.upload/view/download/delete` · `client.merge` · `data_request.create/advance/complete/refuse` · `client.erase` |
| membership | `plan.create/update/archive` · `plan.version.publish` · `contract.create/sign/activate` · `contract.freeze/unfreeze/cancel/renew/expire` · `contract.override` |
| attendance | `checkin.allow` · `checkin.deny` · `checkin.override` · `checkin.offline_reconcile` (reserved) · `access.policy.change` |
| trainers | `assignment.create/end` · `programme.create/update/publish` · `assessment.record/update` · `package.grant/consume/adjust` |
| booking | `class.create/update/cancel` · `booking.create/cancel/attend/no_show` · `waitlist.promote` · `appointment.create/update/cancel` |
| finance | `invoice.create/issue/void/credit` · `invoice.send` · `payment.record/succeed/fail/unknown/reverse` · `payment.allocate/deallocate` · `refund.request/approve/reject/complete/fail` · `credit_note.issue` · `adjustment.create/approve` · `expense.create/update/approve` · `reconcile.start/match/close` · `provider.event.receive/process/reject` · `recurring.create/pause/resume/cancel/charge` · `provider.config.change` |
| commerce | `sale.complete/void/refund` · `register.open/close` · `product.create/update/price.change` · `stock.adjust/transfer/receive` · `po.create/send/receive/cancel` · `supplier.create/update` |
| staff/facility | `shift.create/update` · `commission.record/adjust` · `equipment.create/update/retire` · `maintenance.open/close` |
| comms | `email.queue` · `email.send` (sent/failed/unknown) · `email.template.change` · `email.transport.change` · `email.transport.test` · `announcement.send` |
| reports | `report.run` (sensitive reports only) · `export.request/generate/download/expire` |
| security | `security.anomaly` · `security.csrf.fail` · `security.upload.reject` · `security.crypto.fail` (authentication failure of ciphertext) · `security.key.rotate` · `security.hash_chain.verify` (success/failure) |
| system | `backup.run` · `backup.verify` · `restore.test` · `restore.execute` · `retention.purge` · `migration.run` · `settings.platform.change` · `maintenance.mode` · `cron.heartbeat.missed` |

## Rules
1. No audit event is written outside the audit service; modules call `Audit::record(event, resource, context)`.
2. Audit writes happen in the same transaction as the change they describe where the DB is shared, so no change is committed without its record. Failures of the audit write fail the operation for C3 and financial actions; for low-risk reads the failure is logged and retried.
3. Denied operations are recorded with outcome `denied` and the permission checked.
4. The correlation ID ties audit, application logs, queue jobs and emails together.
5. Chain: `row_hash = SHA-256(prev_hash || canonical(row))`, appended under a row lock on `audit_chain_heads`. A scheduled job verifies chains, writes checkpoints and exports the latest root hash off-site so an attacker with app access still cannot rewrite history unnoticed.
6. Tenant-visible audit view shows tenant events; platform events about a tenant (support access) are mirrored into the tenant view.

## Events emitted by the code today (2026-10-23)
Found by searching for literal Audit::record('...') calls, so events whose names are built from a variable are not listed (for example some illing.schedule_* and ooking.* variants). The table above is the planned scheme; where the two differ, this list is what actually exists. Public enquiry events (lead.*) are recorded with actor type nonymous.

| Domain | Events |
|---|---|
| agreement | `agreement.publish` |
| announcement | `announcement.approve` · `announcement.cancel` · `announcement.create` · `announcement.send` · `announcement.update` |
| assessment | `assessment.create` · `assessment.view` |
| assignment | `assignment.create` · `assignment.end` |
| attendance | `attendance.block` · `attendance.checkin` · `attendance.checkout` · `attendance.qr_rejected` · `attendance.unblock` |
| auth | `auth.email.verify` · `auth.login` · `auth.logout` · `auth.mfa.challenge` · `auth.mfa.disable` · `auth.mfa.enable` · `auth.password.change` · `auth.password.reset.complete` · `auth.password.reset.request` · `auth.recovery_code.use` · `auth.tenant.switch` |
| authz | `authz.denied` |
| backup | `backup.download` · `backup.run` · `backup.verify` |
| booking | `booking.cancel` · `booking.create` |
| brand | `brand.asset.remove` · `brand.asset.upload` |
| class | `class.cancel` · `class.schedule` |
| class_type | `class_type.create` · `class_type.update` |
| client | `client.agreement.sign` · `client.consent.withdraw` · `client.create` · `client.document.delete` · `client.document.download` · `client.document.upload` · `client.erase` · `client.note.create` · `client.note.update` · `client.sensitive.update` · `client.sensitive.view` · `client.tag.change` · `client.update` |
| commission | `commission.export` · `commission.reverse` · `commission.rule_create` |
| contract | `contract.cancel` · `contract.credits_adjusted` · `contract.freeze` · `contract.notice_withdrawn` · `contract.sell` · `contract.unfreeze` |
| data_request | `data_request.advance` · `data_request.create` |
| equipment | `equipment.create` · `equipment.fault` · `equipment.resolve` · `equipment.retire` · `equipment.service` |
| exercise | `exercise.create` |
| expense | `expense.approve` · `expense.create` · `expense.delete` · `expense.export` · `expense.paid` · `expense.reject` · `expense.submit` · `expense.update` |
| export | `export.generate` |
| facility | `facility.archive` · `facility.create` |
| invoice | `invoice.create` · `invoice.credit` · `invoice.discard` · `invoice.issue` · `invoice.update` · `invoice.void` |
| lead | `lead.capture` · `lead.duplicate` · `lead.rejected` |
| membership | `membership.role.assign` |
| payment | `payment.allocate` · `payment.failed` · `payment.provider` · `payment.record` · `payment.unallocate` · `payment.void` |
| plan | `plan.archive` · `plan.create` · `plan.update` |
| platform | `platform.admin.create` |
| po | `po.approve` · `po.cancel` · `po.create` · `po.receive` |
| portal | `portal.invite` |
| pos | `pos.return` · `pos.sale` |
| product | `product.archive` · `product.create` · `product.update` |
| programme | `programme.create` · `programme.status` |
| provider | `provider.create` · `provider.rotate` |
| recon | `recon.approve` · `recon.auto_match` · `recon.ignore` · `recon.import` · `recon.match` · `recon.unmatch` |
| refund | `refund.approve` · `refund.cancel` · `refund.paid` · `refund.reject` · `refund.request` |
| report | `report.export` |
| retention | `retention.purge` |
| role | `role.create` · `role.delete` · `role.permission.change` |
| security | `security.csrf.fail` |
| shift | `shift.cancel` · `shift.clock_in` · `shift.clock_out` · `shift.create` · `shift.export` |
| stock | `stock.adjust` |
| supplier | `supplier.create` · `supplier.update` |
| support | `support.grant.request` · `support.grant.revoke` · `support.grant.use` |
| template | `template.reset` · `template.save` |
| tenant | `tenant.create` · `tenant.preset.apply` · `tenant.setting.change` |
| theme | `theme.draft.save` · `theme.publish` · `theme.rollback` |
| trainer | `trainer.profile` |
| user | `user.invite` · `user.invite.accept` · `user.invite.revoke` |
| webhook | `webhook.rejected` |

