Database inventory, for the DBA review
Generated 2026-10-08 by scripts/db/gen_dba_pack.py from the live database. Row counts and sizes are a reading on that date and move whenever the demo is used; structure moves only when a migration lands. Do not edit by hand.
Server
| Fact | Value |
|---|---|
| Engine | PostgreSQL 17.6 on x86_64-pc-linux-gnu |
| Database size | 333 MB |
| Migrations applied | 109 (latest 20261007120000) |
| Extensions | pg_stat_statements 1.11, pgcrypto 1.3, supabase_vault 0.3.1, uuid-ossp 1.1 |
pgcrypto, uuid-ossp and pg_stat_statements are standard on every managed PostgreSQL service. supabase_vault is Supabase's own; it holds 0 secrets and nothing in the migrations uses it.
The hard-coded date
app.today() returns 2026-09-28, a constant, not the clock. The data set is synthetic and anchored to that date, so every overdue, expiring and status flag in SQL is computed against it. Three more copies of the same date live outside the database (scripts/window.py, packages/core/src/config.ts, packages/core/src/cofi/facts.ts). Any real deployment has to change all four together.
Schemas
| Schema | Tables | Views | Functions | Table size | Signed-out role can enter | Signed-in role can enter | What it is |
|---|---|---|---|---|---|---|---|
app |
2 | 0 | 71 | 80 kB | no | yes | Helper functions every policy calls (who is signed in, which sites they see), and the login-to-person link. |
cofi |
0 | 1 | 3 | 0 bytes | no | yes | One view and three functions behind the in-app copy of the client's Power BI report. |
demo_baseline |
28 | 0 | 0 | 61 MB | no | no | Not product: a frozen copy of the demo's writable tables, for resetting the demo. Left out of schema.sql. |
public |
72 | 2 | 83 | 252 MB | yes | yes | The product's tables, and the functions the app calls. |
sales |
4 | 0 | 0 | 136 kB | no | no | Not product: the internal sales lead tracker. |
Tables
Every table the app owns, in the ERD's groups. Rows are exact counts. A table marked not product is in the database for the demo or for internal use; the definitions are the same ones docs/ERD.md carries.
| Table | Group | Rows | Size | Indexes | RLS | Policies | Note |
|---|---|---|---|---|---|---|---|
asset |
Core platform | 396 | 232 kB | 5 | on | 1 | |
asset_type_requirement |
Core platform | 19 | 32 kB | 1 | on | 1 | |
person |
Core platform | 601 | 352 kB | 6 | on | 2 | |
qualification |
Core platform | 2,006 | 888 kB | 7 | on | 1 | |
requirement |
Core platform | 29 | 32 kB | 1 | on | 1 | |
requirement_job_title |
Core platform | 67 | 32 kB | 1 | on | 1 | |
role |
Core platform | 728 | 248 kB | 2 | on | 1 | |
site |
Core platform | 10 | 48 kB | 2 | on | 1 | |
site_type |
Core platform | 4 | 32 kB | 1 | on | 1 | |
contributing_factor |
Event capture | 24 | 32 kB | 1 | on | 1 | |
incident |
Event capture | 1,490 | 1888 kB | 16 | on | 3 | |
incident_attachment |
Event capture | 0 | 40 kB | 4 | on | 4 | |
incident_contributing_factor |
Event capture | 3,309 | 480 kB | 2 | on | 3 | |
incident_incident_type |
Event capture | 1,714 | 264 kB | 2 | on | 2 | |
incident_type |
Event capture | 12 | 32 kB | 1 | on | 1 | |
observation |
Event capture | 10,810 | 4608 kB | 6 | on | 2 | |
observation_item |
Event capture | 0 | 104 kB | 6 | on | 2 | |
telematics_event |
Event capture | 13,267 | 5584 kB | 7 | on | 1 | |
telematics_event_type |
Event capture | 10 | 32 kB | 1 | on | 1 | |
incident_event |
Investigation and audit trail | 4,393 | 1528 kB | 2 | on | 1 | |
investigation_request |
Investigation and audit trail | 4 | 112 kB | 6 | on | 1 | |
investigation_request_event |
Investigation and audit trail | 10 | 64 kB | 3 | on | 1 | |
investigation_request_type |
Investigation and audit trail | 8 | 64 kB | 3 | on | 1 | |
action |
Follow-through | 11,421 | 5648 kB | 10 | on | 4 | |
inspection |
Follow-through | 233,246 | 79 MB | 9 | on | 2 | |
inspection_defect |
Follow-through | 41,031 | 11 MB | 4 | on | 2 | |
task |
Follow-through | 16,258 | 4976 kB | 6 | on | 2 | |
training |
Follow-through | 3,111 | 2680 kB | 12 | on | 2 | |
training_observation_item |
Follow-through | 0 | 40 kB | 2 | on | 2 | |
document_request |
Training catalog and DQ file | 1 | 64 kB | 3 | on | 1 | |
operator_release |
Training catalog and DQ file | 0 | 80 kB | 4 | on | 1 | |
person_document |
Training catalog and DQ file | 2 | 104 kB | 6 | on | 2 | |
training_course |
Training catalog and DQ file | 95 | 88 kB | 2 | on | 1 | |
training_course_focus_area |
Training catalog and DQ file | 119 | 48 kB | 2 | on | 1 | |
training_material |
Training catalog and DQ file | 0 | 56 kB | 3 | on | 1 | |
external_training |
Training sessions and rules | 0 | 40 kB | 4 | on | 1 | |
training_assignment_rule |
Training sessions and rules | 0 | 24 kB | 2 | on | 1 | |
training_event |
Training sessions and rules | 0 | 48 kB | 5 | on | 1 | |
training_session |
Training sessions and rules | 0 | 48 kB | 5 | on | 1 | |
training_session_attendee |
Training sessions and rules | 0 | 32 kB | 3 | on | 1 | |
focus_area |
Observation catalog | 14 | 32 kB | 1 | on | 1 | |
observation_behavior |
Observation catalog | 74 | 96 kB | 3 | on | 1 | |
observation_cadence_rule |
Observation catalog | 5 | 32 kB | 1 | on | 1 | |
observation_risk_rule |
Observation catalog | 13 | 48 kB | 2 | on | 1 | |
observation_setting |
Observation catalog | 2 | 32 kB | 1 | on | 1 | |
observation_type |
Observation catalog | 5 | 32 kB | 1 | on | 1 | |
observation_type_focus_area |
Observation catalog | 20 | 32 kB | 1 | on | 1 | |
observation_behavior_policy |
Policy library | 152 | 152 kB | 3 | on | 1 | |
policy_clause |
Policy library | 2,288 | 2040 kB | 4 | on | 1 | |
policy_document |
Policy library | 1 | 32 kB | 1 | on | 1 | |
policy_section |
Policy library | 95 | 104 kB | 3 | on | 1 | |
asset_downtime |
Route operations | 2,426 | 784 kB | 3 | on | 1 | |
labor_month |
Route operations | 1,008 | 288 kB | 1 | on | 1 | |
route |
Route operations | 241 | 88 kB | 2 | on | 1 | |
route_day |
Route operations | 131,641 | 55 MB | 6 | on | 1 | |
route_stop_plan |
Route operations | 4,357 | 680 kB | 2 | on | 1 | |
aging_bucket |
Financial layer | 5 | 32 kB | 1 | on | 1 | |
ar_invoice |
Financial layer | 102,970 | 20 MB | 4 | on | 1 | |
customer |
Financial layer | 4,296 | 568 kB | 1 | on | 1 | |
customer_service |
Financial layer | 4,357 | 768 kB | 2 | on | 1 | |
customer_site |
Financial layer | 4,296 | 576 kB | 1 | on | 1 | |
invoice_line |
Financial layer | 208,868 | 46 MB | 4 | on | 1 | |
pnl_line |
Financial layer | 9 | 32 kB | 1 | on | 1 | |
revenue_budget_daily |
Financial layer | 23,296 | 2808 kB | 1 | on | 1 | |
route_pnl_month |
Financial layer | 5,776 | 1208 kB | 2 | on | 1 | |
truck_cost_month |
Financial layer | 7,307 | 1112 kB | 1 | on | 1 | |
ask_log |
App support | 0 | 32 kB | 3 | on | 2 | not product: Ask, a question box that is built but switched off |
benchmark |
App support | 2 | 64 kB | 3 | on | 4 | |
capture_draft |
App support | 1 | 56 kB | 3 | on | 1 | |
hub_demo_removed |
App support | 0 | 16 kB | 1 | on | 3 | not product: the internal hub's demo cards |
hub_prospect |
App support | 5 | 144 kB | 1 | on | 4 | not product: the internal hub's demo cards |
hub_question_answer |
App support | 12 | 64 kB | 1 | on | 3 | not product: the internal hub's questions board |
app.user_account |
Auth link (schema app) | 6 | 48 kB | 2 | on | 1 | |
app.user_site |
Auth link (schema app) | 6 | 32 kB | 1 | on | 1 | |
sales.checklist_answer |
Sales (schema sales) | 2 | 32 kB | 1 | off | 0 | not product: the internal sales lead tracker |
sales.lead |
Sales (schema sales) | 1 | 32 kB | 1 | off | 0 | not product: the internal sales lead tracker |
sales.lead_event |
Sales (schema sales) | 0 | 48 kB | 2 | off | 0 | not product: the internal sales lead tracker |
sales.team_member |
Sales (schema sales) | 1 | 24 kB | 1 | off | 0 | not product: the internal sales lead tracker |
Foreign keys with no index on their leading column
49 of them. Rows is the referencing table's count, which is what decides whether a missing index matters: a delete or key change on the referenced row scans the referencing table.
| Table | Rows | Column(s) | References |
|---|---|---|---|
invoice_line |
208,868 | customer_service_id | customer_service |
telematics_event |
13,267 | event_type | telematics_event_type |
action |
11,421 | verifier_person_id | person |
action |
11,421 | work_done_by_person_id | person |
observation |
10,810 | focus_area | focus_area |
observation |
10,810 | observation_type | observation_type |
truck_cost_month |
7,307 | site_id | site |
incident_event |
4,393 | actor_person_id | person |
customer_service |
4,357 | customer_site_id | customer_site |
route_stop_plan |
4,357 | site_id | site |
customer |
4,296 | site_id | site |
customer_site |
4,296 | customer_id | customer |
training |
3,111 | evaluator_person_id | person |
training |
3,111 | renews_training_id | training |
asset_downtime |
2,426 | inspection_id | inspection |
incident |
1,490 | signed_off_by_person_id | person |
observation_behavior_policy |
152 | clause_id, section_id | policy_clause |
policy_section |
95 | parent_section_id | policy_section |
training_course |
95 | created_by_person_id | person |
training_course |
95 | requirement_id | requirement |
observation_type_focus_area |
20 | focus_area | focus_area |
asset_type_requirement |
19 | requirement_id | requirement |
observation_risk_rule |
13 | behavior_id | observation_behavior |
investigation_request_event |
10 | actor_person_id | person |
site |
10 | site_type | site_type |
app.user_site |
6 | site_id | site |
investigation_request |
4 | request_type_id | investigation_request_type |
investigation_request |
4 | requested_by_person_id | person |
person_document |
2 | requirement_id | requirement |
person_document |
2 | submitted_by_person_id | person |
person_document |
2 | verified_by_person_id | person |
capture_draft |
1 | subject_person_id | person |
document_request |
1 | fulfilled_by_document_id | person_document |
document_request |
1 | qualification_id | qualification |
document_request |
1 | requested_by_person_id | person |
document_request |
1 | requirement_id | requirement |
external_training |
0 | course_id | training_course |
external_training |
0 | reviewer_person_id | person |
external_training |
0 | submitted_by_person_id | person |
incident_attachment |
0 | uploaded_by_person_id | person |
operator_release |
0 | asset_id | asset |
operator_release |
0 | released_by_person_id | person |
training_assignment_rule |
0 | created_by_person_id | person |
training_material |
0 | added_by_person_id | person |
training_material |
0 | superseded_by | training_material |
training_session |
0 | cancelled_by_person_id | person |
training_session |
0 | created_by_person_id | person |
training_session |
0 | signed_by_person_id | person |
training_session_attendee |
0 | added_by_person_id | person |
Functions
| Schema | Functions | SECURITY DEFINER | Definer without a pinned search_path | Trigger functions |
|---|---|---|---|---|
| app | 71 | 50 | 0 | 19 |
| cofi | 3 | 1 | 0 | 0 |
| public | 83 | 52 | 0 | 0 |
SECURITY DEFINER functions are the write paths: the app calls them and they check the caller themselves. docs/ERD.md lists which functions write each table.
Triggers (20)
| Table | Trigger | Function |
|---|---|---|
action |
action_release_hold | app.release_hold_on_repair |
action |
action_set_verifier | app.set_action_verifier |
ask_log |
ask_log_append_only | app.refuse_ask_log_change |
external_training |
external_training_log_event | app.log_external_row |
incident |
incident_drops_its_draft | app.drop_filed_draft |
incident |
incident_log_event | app.log_incident_event |
incident |
incident_no_live_requests | app.assert_no_live_requests |
incident |
incident_owner_is_supervisory | app.assert_investigation_owner_supervisory |
incident_attachment |
incident_attachment_log_event | app.log_incident_attachment_event |
incident_event |
incident_event_no_change | app.refuse_incident_event_change |
investigation_request_event |
investigation_request_event_append_only | app.refuse_request_event_change |
observation |
observation_drops_its_draft | app.drop_filed_draft |
training |
training_evaluation_gate | app.training_evaluation_gate |
training |
training_fill_source | app.training_fill_source |
training |
training_log_event | app.log_training_row |
training_event |
training_event_no_change | app.refuse_training_event_change |
training_session |
training_session_lock | app.lock_training_session |
training_session |
training_session_log_event | app.log_session_row |
training_session_attendee |
training_attendee_lock | app.lock_training_attendee |
training_session_attendee |
training_attendee_log_event | app.log_attendee_row |
What is Supabase rather than PostgreSQL
Everything in this section has to be replaced or re-pointed on any other PostgreSQL host. Everything not in it is plain PostgreSQL.
Login (Supabase Auth). app.user_account has a foreign key to auth.users; sales.team_member has a foreign key to auth.users. 8 functions read the signed-in user from the login token directly: app.access_level, app.can_see_actual_cost, app.can_see_cost, app.current_person_id, app.demo_snapshot, app.is_sales_team, app.site_scope, public.current_person. 2 policies outside Storage do the same (app.user_account user_account_self, app.user_site user_site_self); every other policy calls the app.* helpers, so changing where identity comes from is a change to those few functions, not to every policy.
Files (Supabase Storage). 4 buckets, all private: incident-attachments, person-documents, training-evidence, training-material. 11 policies on storage.objects decide who may read and write a file by asking the database, and public.demo_attachment_paths refers to Storage from SQL.
Roles and grants. The web API (PostgREST) connects as anon before sign-in and authenticated after. service_role bypasses row-level security; nothing in the repository uses it (our scripts connect with the database URL instead). Supabase grants all three every privilege on new tables by default, so on most tables row-level security, not the grant, is what stops a write.
| Role | Public tables with any grant (of 72) | With a write grant |
|---|---|---|
| anon | 27 | 27 |
| authenticated | 72 | 49 |
| service_role | 72 | 72 |
105 policies on our own tables. By the role they apply to (a policy can name more than one): authenticated 105, anon 3.
Migration history. Kept by the Supabase CLI in supabase_migrations.schema_migrations; the files are supabase/migrations/*.sql.