Skip to main content

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.