Case lifecycle & concurrency
A case in Ticketra moves through a small, explicit status machine and is protected by optimistic concurrency so two analysts can't silently overwrite each other.
The status machine
| State | Meaning |
|---|---|
OPEN | Created, not yet being worked. |
IN_PROGRESS | Actively under investigation. |
RESOLVED | Investigation finished; pending closure. |
CLOSED | Done. |
REOPENED | Re-activated after closure (new evidence appeared). |
Transitions are named actions: open, start, resolve, close, reopen. The status machine is enforced server-side — you cannot jump arbitrarily between states.
An earlier NEW status was removed; cases begin at OPEN.
Who can edit a case
Edit rights depend on role and relationship to the case:
| Role | Can edit |
|---|---|
SUPER_ADMIN / ADMIN | Any case within their tenant scope. |
USER | A case only if they created it OR are assigned to it. |
This rule is enforced in both the backend (the case update route) and the frontend (the canEdit check), so the UI and the API agree.
If a case is reassigned to a USER, that user can edit it as the assignee, even though they didn't create it. Both created_by and assignee_id are checked.
Optimistic concurrency
Two analysts may open the same case at once. To prevent one silently clobbering the other, case updates use optimistic concurrency:
The client sends the expected_updated_at it last saw. The update only applies if the row still matches; otherwise the server returns 409 Conflict and the client re-fetches. There is no read-then-write race — the check is atomic in the UPDATE statement.
PostgreSQL stores microseconds while JavaScript Date has millisecond precision. Ticketra compares timestamps with millisecond truncation so a value that round-trips through JSON still matches — otherwise every concurrency check would falsely conflict.
Real-time updates
While you work a case, changes from other analysts arrive live over Server-Sent Events, and you can see who else is present. See Audit & events for how that works.