Phase 2 schema completion + RLS + L1 (H1, L5, L1 closed)

↗ view on GitHub · Claude · 2026-05-15 · d384aee8

Migrations 0005-0011 create the remaining PIP application tables.
Every user_id is now uuid REFERENCES public.users(id) ON DELETE
CASCADE, closing audit finding H1. The previous user_id text columns
that had no foreign key allowed orphaned data after user deletion.

Added migrations:

  0005_user_profiles_and_api_keys.sql  user_profiles, user_api_keys
  0006_projects.sql                    projects, project_subfolders
  0007_documents.sql                   documents, document_versions,
                                       document_edits (chat_message_id
                                       FK added later in 0008)
  0008_chats.sql                       chats, chat_messages, document
                                       _edits.chat_message_id FK
  0009_workflows.sql                   workflows, hidden_workflows,
                                       workflow_shares
  0010_tabular_reviews.sql             tabular_reviews, tabular_cells,
                                       tabular_review_chats,
                                       tabular_review_chat_messages
  0011_rls.sql                         ENABLE + FORCE ROW LEVEL SECURITY
                                       on every backend-owned table;
                                       revokes any inherited grants to
                                       anon/authenticated

L5 closed by 0011_rls.sql. No row-level policies are defined; the
application connects via a BYPASSRLS role and is itself the access-
control boundary. RLS sits in place as defence-in-depth so any future
direct grant fails closed. The role provisioning commands are
documented in the migration file and in docs/developer/08-migrations.md.

L1 closed in backend/src/lib/userApiKeys.ts. USER_API_KEYS_ENCRYPTION
_SECRET is now required (minimum 32 characters); the previous fallback
to SUPABASE_SECRET_KEY is removed. Rotating the Supabase key no longer
renders stored model provider API keys undecipherable.

backend/schema.sql is marked deprecated. PIP defines its schema only
through the incremental migration files. The Supabase-flavoured file
is retained as a historical reference only.

End-to-end verification against a fresh Postgres 16 database:
- All 11 migrations apply cleanly in order.
- 20 tables created (19 application tables + pip_migrations).
- RLS enabled on every backend-owned table; pip_migrations excluded.
- org_settings seeded with 10 jurisdictions, 17 PA services, 16 PA
  sectors, 7-year retention, piperalderman.com.au allowlist.
- /health and /ready return ok with db: true.
- POST /auth/register grants admin role to BOOTSTRAP_ADMIN_EMAIL on
  first registration.
- POST /auth/register rejects disallowed domains and short passwords
  with clear messages.
- POST /auth/verify-credentials returns the same 401 for wrong
  password and unknown email (no user enumeration).
- users_ensure_profile trigger auto-creates the user_profiles row.
- audit_events table records user.register and user.login.credentials.
Repository cpatpa/PIP
Author Claude <noreply@anthropic.com>
Authored
Parents b4f7697f
Stats 13 files changed , +677 , -409
Part of Phase 2 - Supabase JS → plain pg cutover across the backend

Capture this commit into my fork

Download a Markdown prompt that tells Claude how to port this exact commit into your working tree. Run it via claude -p < capture-commit-d384aee8.md from inside the repo you want the change in.

⬇ Download capture-commit-d384aee8.md