Skip to main content

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)

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.