Skip to content

State machines

Every persisted entity in Oversight has a small, explicit state machine. The transitions map directly onto API actions: a PATCH that violates a transition returns 409. UI controls render or hide based on the current state. Every transition writes an audit event.

The navigation flow for principal-admin and principal-compliance-officer. Authentication is the only state transition that can throw the user back to SignedOut; everything else is navigation between persisted views.

stateDiagram-v2
[*] --> Authenticating
Authenticating --> SignedOut: 401
Authenticating --> Dashboard: session ok
SignedOut --> Authenticating: submit credentials
Dashboard --> Register
Dashboard --> BreachQueue
Dashboard --> Reviews
Dashboard --> AnnualReviews
Dashboard --> AuditLog
Dashboard --> TenantSettings: principal-admin only
Register --> ArDetail
ArDetail --> Register
BreachQueue --> BreachDetail
BreachDetail --> NotifyFcaStepUp: principal-admin only
NotifyFcaStepUp --> BreachDetail
Reviews --> ReviewWorkspace
ReviewWorkspace --> Reviews
AnnualReviews --> AnnualReviewPacket
AnnualReviewPacket --> SignOffStepUp: principal-admin only
SignOffStepUp --> AnnualReviewPacket
Dashboard --> SignedOut: log out or expiry

NotifyFcaStepUp and SignOffStepUp are short-lived states gated by step-up auth (re-enter password and TOTP). On success the action proceeds; on cancel or fail the user returns to the prior view.

The navigation flow for ar-user. Scoped to one AR via session.arId. The persona switcher does not exist on this surface in production.

stateDiagram-v2
[*] --> Authenticating
Authenticating --> SignedOut: 401
Authenticating --> Home: session ok
Home --> RequiredActions
Home --> MIReturns
Home --> Breaches
Home --> FileReviews
Home --> Profile
MIReturns --> MIReturnDraft
MIReturnDraft --> MIReturnSubmitted
MIReturnSubmitted --> MIReturns
Breaches --> BreachNew
BreachNew --> BreachSubmitted
BreachSubmitted --> Breaches
FileReviews --> FileReviewDetail
FileReviewDetail --> ChallengeDraft: within 10 working days of complete
ChallengeDraft --> FileReviewDetail
Home --> SignedOut: log out or expiry

Submitted MI returns and breaches are immutable from the AR-user surface; subsequent state transitions happen on the principal-compliance surface.

Triggered when a principal-admin completes the appointment wizard (/demo/principal/register/new). The thirty-day PS22/11 / SUP 12.7 window for FCA objection sits between PendingAppointment and Active; SUP 12.8 termination fires a similar thirty-day window before Terminated.

stateDiagram-v2
[*] --> PendingAppointment: appointment wizard submit
PendingAppointment --> Active: PS22/11 30-day window elapsed, no FCA objection
PendingAppointment --> Withdrawn: principal withdraws before active date
Active --> UnderInvestigation: compliance opens investigation
UnderInvestigation --> Active: investigation closes, no action
UnderInvestigation --> Suspended: compliance suspends pending review
Active --> Suspended: principal-admin suspend (SUP 12.8 notification)
Suspended --> Active: principal-admin reinstates
Suspended --> Terminated: principal-admin terminates
Active --> Terminated: principal-admin terminates (SUP 12.8 30-day window)
UnderInvestigation --> Terminated: terminate-for-cause
Withdrawn --> [*]
Terminated --> [*]

Persisted ArStatus values are pending-appointment, active, suspended, under-investigation, terminated. Withdrawn is a transient pre-active state captured by setting deletedAt on the AR row before appointedOn is reached.

Transitions to Suspended and Terminated require step-up auth and write a SUP 12.8 notification audit record. Termination requires the principal-admin to type the AR’s trading name to confirm (the demo enforces this in the UI; production also requires step-up auth).

The pending-appointment → active transition is automatic and runs on a cron job that checks appointedOn <= now() for each pending-appointment record. No FCA-objection callback is automated in v1; the principal-admin is alerted seven days before the active date and can withdraw if a regulator objection arrives.

Triggered when an AR-user files a breach via POST /api/breaches. The SUP 15 clock starts at awareAt. Material and significant breaches with customer impact actual-low or actual-high route through NotifiableToFca; the rest go directly into InRemediation.

stateDiagram-v2
[*] --> Reported
Reported --> Triaged: principal opens
Triaged --> Investigating: compliance assigns
Investigating --> AssessingMateriality: facts captured
AssessingMateriality --> NotifiableToFca: material or significant + actual customer impact
AssessingMateriality --> InRemediation: not notifiable
NotifiableToFca --> NotifiedFca: SUP 15 notification recorded (step-up)
NotifiedFca --> InRemediation
InRemediation --> Resolved
Resolved --> Closed
Closed --> [*]

Persisted BreachStatus values (open, in-remediation, resolved, closed) are coarser than the diagram’s lifecycle states; the finer states (Triaged, Investigating, AssessingMateriality, NotifiableToFca, NotifiedFca) are derived from notifiedFcaAt, presence of an assignee, presence of a materiality assessment, and severity.

The deadline alerter cron job runs hourly and pages the principal-admin when a NotifiableToFca breach has fewer than 24 hours of notifyByAt remaining, with escalation to email and SMS. Past-due deadlines do not block notification submission; the audit chain captures the late status.

A scheduled review against the AR’s vertical rubric (MCOB, ICOBS, or CONC). Created by compliance, scored against the rubric, completed (terminal for the principal side), optionally challenged by the AR within 10 working days.

stateDiagram-v2
[*] --> Scheduled
Scheduled --> InProgress: reviewer opens
InProgress --> AwaitingEvidence: AR-side evidence requested
AwaitingEvidence --> InProgress: evidence provided
InProgress --> Complete: complete (locks findings, recomputes AR score)
Complete --> Challenged: AR raises within 10 working days
Challenged --> InProgress: reviewer reopens
Complete --> [*]

Complete recomputes the AR’s risk score (see Risk-scoring). Challenged pauses retention; the review remains live until reopened-and-recompleted or the challenge is rejected (returns to Complete with a challenge note).

The PS22/11 annual packet for one AR. Aggregates risk trajectory, breach summaries, file-review summaries, MI returns, conduct events. Sign-off is terminal and requires step-up auth.

stateDiagram-v2
[*] --> Draft
Draft --> InReview: compliance submits for review
InReview --> ChangesRequested: principal-admin requests edits
ChangesRequested --> InReview: compliance resubmits
InReview --> SignedOff: principal-admin signs (step-up)
InReview --> Rejected: principal-admin rejects with notes
Rejected --> Draft: compliance redrafts
SignedOff --> [*]

The cycleYear field anchors the packet to a calendar year. A new Draft is auto-created for each AR each year on a configurable date (default 1 January). SignedOff is immutable; corrections after sign-off go into the next year’s packet with a cross-reference.

Every transition is enforced server-side. The handler:

  1. Loads the row inside the tenant-scoped transaction (RLS filters).
  2. Validates the requested transition against a STATE_MACHINE constant (a map of from -> allowed to).
  3. Validates any preconditions (e.g. step-up token present and unexpired for notify-fca).
  4. Updates the row with optimistic concurrency (If-Match on updatedAt).
  5. Writes an AuditEvent with the action name (e.g. "breach.notify-fca").
  6. Triggers any side effects (risk recompute, deadline alerter, FCA bundle generation).

Disallowed transitions return 409 with the current state in error.details.currentStatus. The UI mirrors the same STATE_MACHINE constant to render or hide controls; the server is the source of truth.