The workflow engine
The state machine (the obvious, expressive part)
src/lib/workflow-config.ts defines a clean, self-documenting FSM: eight operational
macrophases plus terminal states, with ALLOWED_TRANSITIONS, per-phase DEFAULT_CHECKLISTS,
GATE_REQUIRED_TRANSITIONS, and PHASE_DEADLINES. Phases (PT labels):
INTAKE (Abertura) → PRELIMINARY_INQUIRY (Inquérito Prévio) → CHARGES_ISSUED
(Nota de Culpa) → CONSULTATION_PORTAL (Consulta do Processo) → RESPONSE_WINDOW
(Resposta do Arguido) → EVIDENCE_PHASE (Fase de Prova) → BODIES_OPINION
(Pareceres) → FINAL_REPORT (Relatório Final e Decisão) → CLOSED. Transversal:
SUSPENDED, ARCHIVED.
This representation is good and should be preserved — it is not where the risk lives.
Where it runs (the part that is the risk)
The engine is entirely client-side: useWorkflow.executeTransition (browser, anon key).
On each transition it: validates via canTransition (allowed set + checklist approved +
required gate), applies domain guards (caducidade,
bodies-opinion 5-day), then issues a sequence of
non-transactional writes (transition row, case update + base-date stamps, complete old
deadlines, create next checklist, auto-create gates, upsert next deadlines, audit event).
Key tables: cases.status, workflow_transitions (append-only history), case_gates,
case_checklist_items, case_deadlines, case_audit_events.
Gates
Only one gate is actually enforced: FINAL_REPORT → CLOSED requires a
decision_approval gate approved by role decider. GATE_LABELS also names
charges_review and external_comm, which are defined but never enforced (not in
GATE_REQUIRED_TRANSITIONS) — don't mistake them for active gates.
The cross-cutting problems (see their own notes)
- Enforcement is client-side only
- Transitions aren't atomic
- Three status generations coexist
workflow_rules(per-tenant config) is ignored- Conditional branching on flags
Rebuild implication
Keep the FSM shape; move the engine server-side as one transactional command per transition; make gates and the branch table data-driven and tested.