The audit log is Contacts DB’s built-in record of every significant state change in the system. Every time a task moves from one status to another, a newDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/0m1n3m/contacts-db/llms.txt
Use this file to discover all available pages before exploring further.
AuditLog entry is written automatically — capturing who made the change, when it happened, what the previous state was, and what the new state became. This gives your team a reliable, tamper-evident trail for compliance reviews, retrospectives, and diagnosing bottlenecks in your workflow.
Audit log data model
Theaudit_logs table is designed to be generic: it can record changes to any model via entity_type and entity_id. For now, task status transitions are the primary data source.
| Column | Type | Notes |
|---|---|---|
id | bigint | Auto-incrementing primary key |
actor_id | foreignId → users (nullable) | The user who triggered the change; null for system-initiated transitions |
project_id | foreignId → projects (nullable) | The project the changed entity belongs to; set to null if the project is deleted |
entity_type | string | Fully-qualified class name of the changed model (e.g. App\Models\Task) |
entity_id | unsignedBigInt | Primary key of the changed model instance |
action | string (indexed) | The type of change; task status transitions use status_changed |
before | json (nullable) | State snapshot before the change |
after | json (nullable) | State snapshot after the change |
ip_address | string(45) (nullable) | IPv4 or IPv6 address of the request that triggered the change |
user_agent | string (nullable) | Browser or client user-agent string |
created_at | timestamp | When the log entry was written |
updated_at | timestamp | — |
(entity_type, entity_id), by (project_id, created_at), and by (actor_id, created_at).
What is logged
Every task status transition is recorded automatically. The flow is:- A status change is saved on a
Taskmodel (viaPATCH /tasks/{task}orPATCH /tasks/{task}/change-status). - Laravel’s model observer fires the
updatinghook viaTaskObserver. TaskObserverdetects that thestatusattribute is dirty and callsTimeTrackingService::recordStateChange().TimeTrackingServicecalls its privatelogStateChange()method, which creates theAuditLogrecord with thebeforeandafterstatus values, the authenticated user’s ID, the current IP address, and the user-agent header.
Log entry structure
A typicalstatus_changed entry looks like this:
before and after columns are cast to arrays by the AuditLog model, so you can read them directly as PHP arrays without manual json_decode calls.
Accessing the status history on a task
TheTask model exposes two convenience methods for working with its audit trail.
$task->statusChanges()
Returns a MorphMany query builder scoped to action = 'status_changed' and ordered chronologically by created_at. Use this when you need to apply additional query constraints or eager-load relationships:
$task->getStatusHistory()
Returns a ready-to-display array of status transition records, with actor names resolved:
changed_by value comes from $log->actor?->name, so it is null (not an error) for system-initiated transitions where actor_id is null.
Time tracking integration
TimeTrackingService uses the audit log as its source of truth when recalculating time metrics. The service reads statusChanges() entries in chronological order to recompute dev_time (seconds spent in_progress), review_time (seconds spent in_review), lead_time (total working time from creation to done), and backward_transitions (count of status regressions).
If you manually correct audit log entries, or if you suspect a task’s time metrics have drifted out of sync, you can force a full recalculation:
Task::withoutEvents() to prevent the observer from firing again during the recalculation, and it performs a single $task->save() at the end to persist all updated metrics atomically.
Status order reference
Status order reference
The
Any transition from a higher-order status to a lower-order status increments
TimeTrackingService uses a defined status order to detect backward transitions:| Status | Order |
|---|---|
created | 0 |
accepted | 1 |
in_progress | 2 |
in_review | 3 |
done | 4 |
backward_transitions on the task.Retention
The audit log table grows indefinitely — there is no automatic pruning. In a busy production environment the table can accumulate millions of rows over time. Consider scheduling a periodic cleanup job that deletes entries older than your retention policy (e.g. 12 months):getStatusHistory() and time-tracking recalculation remain accurate.