Audit Trail & Event Logging
Overview
Every state change on a Shrubbery — from creation to completion — is recorded as an immutable event in the Audit Trail. The Trail answers "who did what and when" without requiring anyone to remember; it is the accountability spine of the system. This guide explains what gets recorded, how actor attribution works, and where the Trail surfaces in the product.
Actors
- Lead — initiates most events (created, renegotiated, edited, completed). Their
user_idis theactor_id. - Knight — initiates Handshake events (handshake_accepted, handshake_refused) and may mark a row completed. Their
user_idis theactor_id. - External System — initiates events via the Sync API. The
actor_kindisapi_token;actor_idis the token's ID, not a user ID. - Squire — does not generate audit events. Squire extraction is silent; the
createdevent is attributed to the Lead who saved the Draft.
Flow
- Any actor performs an action that causes a state transition (Lead sends a Draft, Knight Handshakes, Lead renegotiates, etc.).
- System writes an event to
shrubbery_eventssynchronously within the same database transaction as the state update. If the transaction rolls back, the event does not persist. - Event record includes:
event_type,actor_id,actor_kind(userorapi_token),shrubbery_id,created_at, and an optionalmetadataJSON blob for field-level detail. - Lead or Knight can view the Timeline — the per-row render of the Audit Trail — inside the Detail Sheet at any time. The Timeline is newest-first and always up to date.
Event types and when they fire
| Event type | Who triggers | When |
|---|---|---|
created | Lead (or API token) | Shrubbery row inserted (any capture path) |
handshake_accepted | Knight | Pending_Handshake → Active |
handshake_refused | Knight | Pending_Handshake → Refused |
renegotiated | Lead | Substantive edit on Active row → Pending_Handshake |
edited | Lead | Non-substantive edit on Active row |
completed | Lead, Knight, or API token | Active → Completed |
status_synced | API token (Sync API) | External status update applied |
nudge_sent | System (Inngest) | Nudge delivered to Knight |
Edge cases
- API-token attribution — when the Sync API closes a row, the
actor_kind = api_tokenfield identifies the automation rather than a user. The Dossier surfaces this label ("API" next to the completion event) so Leads understand the row was not manually marked done. CFS arithmetic is unchanged. - Renegotiation metadata — the
renegotiatedevent'smetadatablob carries{ oldObjective, newObjective, oldDeadline, newDeadline }. The Timeline renders this as a visible diff, not raw JSON. - Soft-deleted rows — deleting a Shrubbery sets a
deleted_atflag but preserves the row and all its events. The Audit Trail is intact; the row simply no longer appears in active views. - Multiple renegotiations — each renegotiation cycle writes a separate
renegotiatedevent. The Timeline shows the full chain, making it clear how many times terms changed before the final Handshake. - Nudge events — Nudge delivery events are written by Inngest after the fact. A gap between
createdandnudge_sentis normal; delivery failures trigger Inngest retries, each of which writes its own event.
Related concepts
Last updated: 22 June 2026