Skip to content

Investigations

An investigation is the phase of a report's lifecycle during which an assigned agent works the case. It begins with the investigate action and ends with validate, invalidate, or conclude. The authoritative state machine lives in rpc_report_action (migrations 006 → 034); the application-side rules live in reports_service.perform_report_action and reports_service.conclude_investigation.

Lifecycle

text
Open / Pending ──(investigate)──► Under Investigation

              ┌──────────────────────┼──────────────────────┐
              ▼                      ▼                      ▼
          validate               invalidate             conclude
              ▼                      ▼                      ▼
         Validated              Invalidated              Pending
   (closes contact thread,  (same side effects)   (no bot work, no queue row;
    queues bot action)                              timeline + event only)

Beginning an investigation

  • Endpoint: POST /reports/<id>/action with {"action": "investigate"} — the single application path. POST /reports/<id>/investigation/begin is a legacy adapter over the same operation.
  • Authorization: manage_dockets (clearance 2+), supervisor-report rank check, and the assigned-agent rule (the acting agent must be assigned to the case, or hold Senior Agent rank or above). The bot key may also drive it.
  • Preconditions: the case must not already be Under Investigation (409). Starting from Open or Pending is always allowed. Reopening a concluded case (Closed, Completed, Validated, Invalidated) requires clearance 4+; the timeline event reads "Investigation reopened".
  • Side effects: status → Under Investigation, "Investigation begun" timeline row, canonical report.investigation_started event, and a pending_actions row for the bot (the bot performs Discord-side work such as role changes and logs).

Working the case

While the investigation is in progress, the assigned agent (or supervisor rank) may:

  • add notes (POST /reports/<id>/notes) — required to be in this status for browser sessions;
  • add evidence (POST /reports/<id>/evidence) — same gate;
  • add timeline entries (POST /reports/<id>/timeline) — same gate;
  • message the reporter via the contact thread.

Notes and evidence are recorded through rpc_add_note / rpc_add_evidence, which write the row, the timeline entry, and the canonical event (report.note_added / report.evidence_added) in one transaction.

Completing the investigation

Three terminal paths, all of which first lock the report row and re-check the status inside the database:

PathEndpointResultQueue rowContact thread
ValidatePOST /reports/<id>/action {"action":"validate"}Validatedyesclosed
InvalidatePOST /reports/<id>/action {"action":"invalidate"}Invalidatedyesclosed
ConcludePOST /reports/<id>/investigation/end, or PATCH status="Pending" while under investigationPendingnounchanged

Validate/invalidate require the current status to be exactly Under Investigation — otherwise 409. They write "Report validated/invalidated" and "Investigation concluded" timeline rows plus the matching canonical events, close any open contact thread, and queue the bot action. Conclude performs the status guard, writes "Investigation concluded" + report.investigation_concluded (metadata.to = "Pending"), and inserts no queue row — concluding carries no bot work.

Requeueing

There is no dedicated "requeue investigation" operation. Two recovery paths exist:

  1. A concluded case (Pending) can be re-investigated with the investigate action — the normal reopen path.
  2. Failed Discord-side bot work associated with an action is recovered through the action queue: POST /actions/<id>/requeue (failed → pending, attempts reset) or the admin queue controls. See Operations.

Concurrency behavior

Concurrency is handled inside the database, not in application code:

  • rpc_report_action executes SELECT status, assigned_agent FROM reports WHERE report_id = … FOR UPDATE before any guard. Two agents racing to validate the same case: the first commits, the second finds the row already Validated and raises STATUS_CONFLICT → HTTP 409 with "This action can no longer be performed — the case status changed".
  • The application pre-flight checks are duplicated in the RPC so the database is authoritative even if a route-level guard is bypassed.
  • Claim (action=claim) is guarded the same way: ALREADY_CLAIMED → 409 when the case is already assigned.

Authorization requirements

RequirementDetail
Sign-inActive browser session, or the bot API key for bot-driven actions
Clearancemanage_dockets (2) for action/begin/end; reassign_docket (4 + Head Investigator rank) for assignment changes
Supervisor reportsSenior Agent rank or above (or API key)
AssignmentOnly the assigned agent may action/note/evidence/timeline/contact a case (supervisor ranks bypass)
Reopen concluded caseClearance 4+

Authoritative database operations

OperationRPCWhat it guards
Investigate / validate / invalidate / contact / claimrpc_report_actionRow lock, allowed-status set, duplicate-contact guard, already-claimed guard; writes status/assignment + timeline + event + contact message + queue row atomically
Concluderpc_report_action (action conclude)Status must be Under Investigation; timeline + report.investigation_concluded event, no queue row
Note / evidencerpc_add_note / rpc_add_evidenceRow + timeline + event + updated_at in one transaction
Deletionrpc_delete_reportPre-image capture + delete + forensic audit in one transaction (see Reports)