State Machines
On this page
CRAIG uses explicit state machines with validated transitions for all stateful entities.
Invalid transitions return HTTP 400 with a descriptive error message.
Each diagram below is derived directly from the transitions.rs source code in the corresponding service.
Case Management (craig-cases)
Placement (craig-placement)
Placement Status
Placements track a child’s physical location in care.
Creating an active placement increments the foster home’s occupancy counter; ending it decrements.
Terminal facts are immutable (#1112). Placements are born planned or active; the create endpoint refuses ended outright, since a birth-ended row would freeze NULL terminal facts forever and never emit placement.ended, and this closes the previously-unvalidated status vocabulary. ended_at and end_reason are writable only in the request that transitions the placement to ended, and that transition must carry ended_at: an end recorded without its instant could never be repaired under immutability. end_reason stays optional there, since whether a reason is mandatory is jurisdiction policy, not structure. A request that tries to decouple either fact from the ending transition is refused with the typed terminal-facts-end-transition problem, and because ended admits no further transition, not even to itself, the facts recorded at the end are permanent. Consumers that snapshot them, such as the #1070 SG activation’s transfer_on derivation and the financial placement.ended void handler, can no longer be desynchronized through the API. Non-terminal record-keeping (ctw_finding, reasonable_efforts, permanency_goal) stays writable after the end. A post-hoc correction path, audited, re-emitting a typed event, with financial reconciliation, is deliberately not provided; if the domain ever demands one, it is its own designed feature, not an edit. The placement_ended_missing_ended_at blocking invariant backstops raw writes that strip the end instant, but a raw rewrite of the fact values falls outside any invariant’s reach, since the columns carry no history to compare against. The store remains the single legitimate writer.
Data Exchange (craig-exchange)
Reporting (craig-reporting)
AFCARS Submission Status
AFCARS (Adoption and Foster Care Analysis and Reporting System) submissions follow a review and approval workflow before transmission to the federal Administration for Children and Families (ACF).
NCANDS Submission Status
NCANDS (National Child Abuse and Neglect Data System) submissions follow the same review workflow as AFCARS.
Financial (craig-financial)
Payment Status
Foster care maintenance and assistance payments track the forward chain pending → approved → issued → cleared. Each step has been a supervisor-gated REST transition since #978 (PUT …/approve, /issue, /clear), implemented as one atomic UPDATE … WHERE status = <expected> that stages its lifecycle event (financial.payment_approved / payment_issued / payment_cleared) in the same transaction. voided is permitted from every state by the transition predicate, but the production writers only ever void undisbursed (pending/approved) rows: the placement.ended consumer’s placement-scoped set-based void (#776), and, since #1068, the subsidy generator’s reconcile pass, which voids an agreement-month whose derivation went stale (backdated status/terms) and regenerates it under the voided-exclusive unique key (ADR-053). Disbursed rows are frozen instead, and surface through the subsidy_disbursed_derivation_mismatch invariant. A deliberate manual-void path is a tracked follow-up (#1028).
Adjustment Status
Payment adjustments (recoupment, underpayment correction, allocation change) require the same supervisor approval as the originating payment.
Claiming Record Status
Title IV-E claiming records are aggregated into quarterly federal financial participation (FFP) claims (CB-496 / CB-250) for submission to ACF.
Subsidy Agreement Status
Kinship-subsidy agreements (ADR-052, #1067) are governed by the Georgia DFCS Policy Manual’s chapter 22.8, covering the ERR and SG/NRSG programs, and chapter 22.9, the review workflow (ADR-054, #1081). SG (Subsidized Guardianship) pays a relative caregiver after a permanent-guardianship transfer; NRSG is its non-relative counterpart. The statuses below label business-effective intervals (subsidy_agreement_status_intervals), not a mutable row state: subsidy_agreements.current_status is a projection of the interval head, maintained single-writer by craig_financial::subsidy::store under a parent FOR UPDATE and verified by blocking invariants. pending exists because SG/NRSG agreements are signed before the guardianship transfer. Since #1070 (ADR-056) the witnessed activation (POST …/subsidy-agreements/{id}/activate) is the only native pending → active mint: it runs at the derived transfer date, the guardianship-ending date of the agreement’s anchoring placement (the specific placement record whose ACTIVE state justifies the agreement), confirmed by a service-to-service read from craig-placement rather than operator-entered, and under a county-floor proof. The generic transition endpoint refuses that same transition for native SG/NRSG agreements, naming this path instead. Imported agreements never enter native activation, and since #1071/ADR-057 an open-pending import is unrepresentable, so the refusal is belt-only. suspended is distinct from termination: a missed review causes suspension, and three suspended months cause termination, automated by the #1081 knob-consented review-enforcement sweep. terminated → active is reinstatement under chapter 22.8, on the same identity, with cause-specific approval enforced by #1081; a post-closure re-application is instead a new agreement row with a predecessor_agreement_id link. withdrawn and declined are pending-terminal paths.
Security & Compliance (craig-security)
Security Review Status
Biennial security reviews (45 CFR § 95.621(f)) are scheduled and tracked on the security_reviews table with the three-state workflow below.
NIST Control Implementation Status
NIST SP 800-53 controls tracked on nist_controls follow a three-state implementation lifecycle. Each control records last_assessed_at, assessed_by, and free-text implementation notes.
Security Alert Acknowledgement
Detection rules create alerts on security_alerts. An alert begins unacknowledged; an admin (or the system on webhook response) acknowledges with a note.
Invariant (#1156, DB-enforced since #1172): at most one unacknowledged alert exists per rule. The partial unique index uq_security_alerts_standing_rule is the arbiter, so overlapping scans, from the scheduler and on-demand alike, insert exactly one. The standing alert is the operator’s signal for a sustained breach; acknowledging it re-arms the rule, and a still-breached condition fires a fresh alert on the next scan.