Skip to the content.

ADR-0034: Report Lifecycle State Machine

Decision: Five-state lifecycle: DRAFT → REVIEW → APPROVED → PUBLISHED → ARCHIVED. Transitions are enforced by ReportService. Direct jumps are not permitted except for explicit administrative archive.

State definitions:

State Meaning Who can set
DRAFT Work in progress; content may be incomplete Author
REVIEW Submitted for peer or management review Author
APPROVED Review complete; approved for dissemination Reviewer
PUBLISHED Disseminated; STIX bundle generated; immutable content Approver
ARCHIVED Superseded or withdrawn; not for distribution Any

Valid transitions:

DRAFT ──► REVIEW ──► APPROVED ──► PUBLISHED
  ▲           │           │            │
  └───────────┘           │            │
  (reject back            │            │
   to DRAFT)              ▼            ▼
                       ARCHIVED    ARCHIVED

DRAFT ↔ REVIEW is the only bidirectional transition (review rejection sends the report back to DRAFT for revision).

Why APPROVED is separate from PUBLISHED: In most CTI teams, the analyst who writes the report is not the same person who approves it for external distribution. Requiring explicit approval before publish enforces a review gate. Teams without a formal review process can configure auto_approve = true in the report template, which collapses REVIEW → APPROVED → PUBLISHED into a single step.

Why no CANCELLED state: Cancelled reports should be ARCHIVED, not deleted. Maintaining the full history (including withdrawn intelligence) is a compliance and audit requirement in most organisations.

Immutability on PUBLISHED: Once a report reaches PUBLISHED, its content fields (body_sections, key_findings, evidence_links) become read-only. Updates produce a new Report version with parent_report_id pointing to the previous published version and version incremented. This mirrors the STIX 2.1 versioning model where modified creates a logical new version rather than mutating the original.

Versioning implementation: ReportService.publish(report_id) increments version, sets published_at, generates the STIX bundle, and marks content as immutable via a is_published flag in storage. A new draft is created with parent_report_id set when an analyst wants to revise a published report.

STIX report SDO generation: Triggered automatically on transition to PUBLISHED. The STIX bundle is stored as stix_bundle_json in the report row and the STIX Report SDO ID is written to stix_report_ref. Downstream dissemination consumers poll for rows where stix_report_ref IS NOT NULL and status is PUBLISHED.


Licensed under the Apache License, Version 2.0