Client-side enforcement is the whole trust model
Assumption (implicit): that the workflow can only be driven through the React UI, so
guards written in useWorkflow.ts are sufficient to protect legal integrity.
Reality: the browser talks to Postgres directly through the Supabase anon key
(src/integrations/supabase/client.ts). Every workflow rule — allowed transitions,
checklist completion, mandatory gates, the caducidade deadline, the CT/Sindicato
5-day wait — is JavaScript in useWorkflow.executeTransition. Row-Level Security does
not re-check any of it. Confirmed against the migrations: the RLS policy on cases lets
any company member INSERT/UPDATE/SELECT (get_user_company_ids()); only
is_company_admin is blocked from DELETE. There is no per-role, per-status, or
per-checklist write restriction in the database.
What breaks: anyone with a login and the anon key can
supabase.from("cases").update({ status: "CLOSED" }).eq("id", caseId)
…skipping the Nota de Culpa, the employee's response window, every checklist item, and every gate — and the case looks legally closed. The dossier generated afterwards would present a procedurally void process as complete.
Why it matters for a compliance product: the guards are the product. If they are advisory, the software actively produces false assurance.
Rebuild implication
The state machine and all its guards must move server-side and become the only way
to mutate cases.status — a transactional API/RPC (see
Transitions are not atomic) with the client reduced to
calling it. RLS should forbid direct UPDATE of status entirely.