Legal state lives in untyped JSONB
Every investigative action (diligência) stores all of its structured content,
validation flags, document status, and uploads inside a single inquiry_actions.step_data
JSONB column. Nothing is relational.
Examples of what lives only in JSON keys:
step_data.<stepKey>._validated— whether a sub-step is legally validatedstep_data.<deliverableKey>._status—draft/completed/approvedof the formal documentstep_data.preparation.witness_name,step_data.questions[],step_data.info.*— the substantive record of a witness hearing, employee hearing, document analysis, etc.step_data.hearing_minutes._status— approval of the Ata da Inquirição
Two consequences:
-
Unqueryable and unconstrained. You cannot ask the database "which actions are validated?" or "which hearings lack approved minutes?" without scanning JSON. There is no
CHECK, no foreign key, noNOT NULL— a typo in a key silently loses data. A phase's legal completeness is buried in untyped JSON. -
Two status vocabularies coexist and are easy to conflate: the action-level status (
draft/in_progress/completed/validated) and the deliverable-level status (draft/completed/approved). They mean different things.
The witness path (InquiryActionDrawer → HearingMinutesDrawer, hardcoded 5 steps / 7
sections) and the generic path (GenericActionDrawer → ActionDeliverableDrawer,
config-driven from inquiry-action-types.ts) both write into the same JSONB contract but
duplicate the logic.
The tacit assumption: the shape of step_data is stable and correct because the only
writer is the matching drawer. There is no schema to enforce it.
Rebuild implication
Promote the load-bearing fields (validation state, deliverable status, key facts) to typed, queryable columns/tables with constraints. Keep JSON only for genuinely free-form notes. Unify the two deliverable subsystems behind one contract.