Skip to content

fix(db): purge private title vocabulary and enforce public scope#868

Merged
BigSimmo merged 3 commits into
mainfrom
codex/private-title-privacy-20260718
Jul 18, 2026
Merged

fix(db): purge private title vocabulary and enforce public scope#868
BigSimmo merged 3 commits into
mainfrom
codex/private-title-privacy-20260718

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • Purge historical document_title_words rows before the table-backed corrector is installed on fresh/pending rollouts, then repeat the purge in a forward migration for environments where that corrector migration was already applied; source documents are untouched and valid public vocabulary is rebuilt.
  • Enforce the public/indexed/title-word invariant on future writes with a fail-closed trigger that takes a FOR SHARE document-row lock to close concurrent owner/status/title races.
  • Reassert RLS, service-role and SECURITY DEFINER privileges, validate the historical lowercase/length constraints, and abort the migration if any out-of-scope row remains.
  • Mirror the function/trigger in the canonical schema, regenerate the drift manifest through disposable local replay, and add focused schema contracts.

Verification

  • Disposable repository-native Postgres schema replay; regenerated drift manifest matches the canonical schema.
  • npm run test -- tests/supabase-schema.test.ts — 66/66 focused tests passed before the final docs-only/main merge.
  • npm run check:function-grants — 28 SECURITY DEFINER functions protected.
  • Prettier on all four changed files.
  • Post-merge manifest SHA/object proof and git diff --check.
  • Scoped ESLint and git diff --check after the rollout-order P1 fix.
  • Exact-head hosted checks are running after commit 77482fc9e.
  • Verification not run: the exact post-merge focused Vitest rerun was blocked twice by another worktree's legitimate repository-wide heavy lock and was not retried a third time. The intervening main merge changed no migration/schema files; exact-head hosted CI is the final gate.
  • UI verification not run: no UI, routing, styling, browser, reduced-motion, or forced-colors behavior changed.
  • Live npm run check:production-readiness, Supabase project checks, migration apply, and provider-backed retrieval/answer gates were not run because they remain separately confirmation-required.

Risk and rollout

  • Risk: High privacy significance, narrowly scoped database change. The migration deletes only derived title vocabulary that should never have been visible to the corrector; it neither deletes nor rewrites source documents. The row-locking trigger prevents a concurrent document scope change from reintroducing stale private vocabulary.
  • Rollback: Forward-only. Preserve the purge and public scope. If the guard requires adjustment, ship a reviewed follow-up migration; never restore the historical private/non-indexed vocabulary or the unscoped corrector.
  • Provider or production effects: None in this workflow. No live Supabase/OpenAI API, production database, migration apply, deployment, or credential was used.

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

Rollout gate

  • Merging this PR does not authorize applying the migration to the live Supabase project.
  • Before any separately approved live apply, confirm the pending migration set and retain a reviewed backup/forward-only rollback plan.
  • After apply, verify the documented zero-row invariant for private, non-indexed, and stale title words before enabling the corrector rollout.

Review disposition

  • The rollout-window P1 is fixed by purging unsafe vocabulary inside 20260717171000_public_title_corrector.sql before its CREATE OR REPLACE FUNCTION, while the new forward migration remains responsible for already-applied environments and long-term enforcement.

Summary by CodeRabbit

  • Security & Data Integrity
    • Strengthened enforcement for title-word visibility so document_title_words stays aligned with the indexed public document title corpus.
    • Added database-side validation for title-word formatting (length and lowercase) and automatic purging/repair of stale or invalid records.
    • Updated permissions around the new enforcement mechanism to better protect write access paths.
  • Bug Fixes
    • Fixed cases where outdated or mismatched title-word entries could persist or be reintroduced.
  • Tests
    • Expanded migration and invariants coverage to ensure “fail-closed” behavior and correct purge/repair ordering.

@supabase

supabase Bot commented Jul 18, 2026

Copy link
Copy Markdown

Updates to Preview Branch (codex/private-title-privacy-20260718) ↗︎

Deployments Status Updated
Database Sat, 18 Jul 2026 14:39:36 UTC
Services Sat, 18 Jul 2026 14:39:36 UTC
APIs Sat, 18 Jul 2026 14:39:36 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Sat, 18 Jul 2026 14:39:37 UTC
Migrations Sat, 18 Jul 2026 14:39:40 UTC
Seeding ⏸️ Sat, 18 Jul 2026 14:39:29 UTC
Edge Functions ⏸️ Sat, 18 Jul 2026 14:39:29 UTC

❌ Branch Error • Sat, 18 Jul 2026 14:39:41 UTC

ERROR: Unsafe supabase_admin default privileges; migration blocked. (SQLSTATE 42501)
{"safe": false, "entries": ["function:PUBLIC:execute", "function:anon:execute", "function:authenticated:execute", "function:postgres:execute", "function:service_role:execute", "function:supabase_admin:execute", "sequence:anon:select", "sequence:anon:update", "sequence:anon:usage", "sequence:authenticated:select", "sequence:authenticated:update", "sequence:authenticated:usage", "sequence:postgres:select", "sequence:postgres:update", "sequence:postgres:usage", "sequence:service_role:select", "sequence:service_role:update", "sequence:service_role:usage", "sequence:supabase_admin:usage", "table:anon:delete", "table:anon:insert", "table:anon:maintain", "table:anon:references", "table:anon:select", "table:anon:trigger", "table:anon:truncate", "table:anon:update", "table:authenticated:delete", "table:authenticated:insert", "table:authenticated:maintain", "table:authenticated:references", "table:authenticated:select", "table:authenticated:trigger", "table:authenticated:truncate", "table:authenticated:update", "table:postgres:delete", "table:postgres:insert", "table:postgres:maintain", "table:postgres:references", "table:postgres:select", "table:postgres:trigger", "table:postgres:truncate", "table:postgres:update", "table:service_role:delete", "table:service_role:insert", "table:service_role:maintain", "table:service_role:references", "table:service_role:select", "table:service_role:trigger", "table:service_role:truncate", "table:service_role:update", "table:supabase_admin:delete", "table:supabase_admin:insert", "table:supabase_admin:maintain", "table:supabase_admin:references", "table:supabase_admin:select", "table:supabase_admin:trigger", "table:supabase_admin:truncate", "table:supabase_admin:update"], "role_exists": true, "schema_exists": true}
At statement: 3
do $$
declare
  v_status jsonb;
begin
  if not exists (select 1 from pg_catalog.pg_roles where rolname = 'supabase_admin') then
    raise notice 'role supabase_admin does not exist; default-privilege assertion is not applicable';
    return;
  end if;

  begin
    -- Local/Superuser-capable environments can assume the target role even
    -- when the migration role cannot use ALTER DEFAULT PRIVILEGES FOR ROLE
    -- directly. Hosted environments that cannot assume it fall through to the
    -- catalog assertion and block with operator instructions.
    execute 'set local role supabase_admin';
    -- Revokes must be global: per-schema ACLs cannot subtract privileges from
    -- built-in or previously granted global defaults.
    alter default privileges for role supabase_admin
      revoke all privileges on tables from public, anon, authenticated, service_role;
    alter default privileges for role supabase_admin in schema public
      revoke all privileges on tables from public, anon, authenticated, service_role;
    alter default privileges for role supabase_admin
      revoke all privileges on sequences from public, anon, authenticated, service_role;
    alter default privileges for role supabase_admin in schema public
      revoke all privileges on sequences from public, anon, authenticated, service_role;
    alter default privileges for role supabase_admin
      revoke execute on functions from public, anon, authenticated, service_role;
    alter default privileges for role supabase_admin in schema public
      revoke execute on functions from public, anon, authenticated, service_role;
    alter default privileges for role supabase_admin in schema public
      grant select, insert, update, delete on tables to service_role;
    alter default privileges for role supabase_admin in schema public
      grant usage, select on sequences to service_role;
    alter default privileges for role supabase_admin in schema public
      grant execute on functions to service_role;
    execute 'reset role';
  exception when insufficient_privilege then
    begin execute 'reset role'; exception when others then null; end;
    raise notice 'current role % cannot remediate supabase_admin default privileges; asserting the catalog postcondition', current_user;
  end;

  v_status := public.default_privileges_status('supabase_admin', 'public');
  if not coalesce((v_status->>'safe')::boolean, false) then
    raise exception using
      errcode = '42501',
      message = 'Unsafe supabase_admin default privileges; migration blocked.',
      detail = v_status::text,
      hint = E'Run these six statements as supabase_admin, then retry the migration:\n'
        'DO $remediate$ BEGIN ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin REVOKE ALL PRIVILEGES ON TABLES FROM PUBLIC, anon, authenticated, service_role; ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL PRIVILEGES ON TABLES FROM PUBLIC, anon, authenticated, service_role; END $remediate$;\n'
        'DO $remediate$ BEGIN ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin REVOKE ALL PRIVILEGES ON SEQUENCES FROM PUBLIC, anon, authenticated, service_role; ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL PRIVILEGES ON SEQUENCES FROM PUBLIC, anon, authenticated, service_role; END $remediate$;\n'
        'DO $remediate$ BEGIN ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC, anon, authenticated, service_role; ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC, anon, authenticated, service_role; END $remediate$;\n'
        'ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO service_role;\n'
        'ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO service_role;\n'
        'ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT EXECUTE ON FUNCTIONS TO service_role;';
  end if;
end;
$$

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The database restricts document_title_words to lowercase tokens from indexed public document titles. A trigger enforces inserts and updates, migration logic purges stale rows and restores valid rows, schema metadata is updated, and tests cover ACL safeguards and migration correctness.

Public title-word enforcement

Layer / File(s) Summary
Migration access and integrity posture
supabase/migrations/...enforce_public_title_word_scope.sql
RLS, privileges, word constraints, related function ACLs, and default-ACL validation are added.
Scope trigger and data reconciliation
supabase/migrations/...enforce_public_title_word_scope.sql
A security-definer trigger validates indexed public title tokens; stale rows are purged, missing rows restored, and the invariant checked.
Schema and manifest representation
supabase/schema.sql, supabase/drift-manifest.json
The canonical schema and drift manifest declare the function, trigger, synchronization cleanup, and metadata.
Migration replay and invariant assertions
tests/supabase-schema.test.ts, supabase/migrations/...public_title_corrector.sql
Tests cover migration loading, ACL failure behavior, trigger enforcement, reconciliation ordering, constraints, privileges, and the corrector cleanup.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Migration
  participant document_title_words
  participant enforce_document_title_word_scope
  participant documents
  Migration->>document_title_words: purge stale rows
  Migration->>document_title_words: restore missing valid rows
  document_title_words->>enforce_document_title_word_scope: validate insert or update
  enforce_document_title_word_scope->>documents: check indexed public title tokens
  enforce_document_title_word_scope-->>document_title_words: allow valid word or raise 23514
Loading

Possibly related PRs

Suggested labels: codex

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately reflects the main database purge and public-scope enforcement changes.
Description check ✅ Passed The description covers the required summary, verification, risk, and governance sections with enough implementation detail.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/private-title-privacy-20260718

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92a9bf77fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@BigSimmo

Copy link
Copy Markdown
Owner Author

@codex resolve actionable Codex review findings for this pull request and current head using the repository instructions. This is the pull request's single automatic repair pass: do not perform a fresh review, create new standalone findings, or request another review. Work only the existing unresolved Codex threads on the current head. Always fix P0 and P1 findings. For P2 and lower findings, fix only clear, scoped, low-risk issues; otherwise disposition them with a concise reason. After fixing or dispositioning a thread, reply in that thread with as the first line, followed by a concise summary; that marker authorizes the workflow to close that exact thread. If human input or new authorization is required, do not use the marker and leave the thread open with the blocker. Finish only after every actionable thread is fixed or dispositioned and closed, or explicitly left open for a human decision. Do not update the branch from main, address unrelated reviews, broaden scope, or create more than one scoped fix commit. Do not use external APIs, paid services, credentials, dependency changes, or broad refactors unless explicitly authorized. Add targeted tests where behavior changes and run the narrowest relevant validation.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 92a9bf77fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@BigSimmo
BigSimmo enabled auto-merge (squash) July 18, 2026 14:42
@BigSimmo

Copy link
Copy Markdown
Owner Author

Codex please resolve this supabase branch error

@BigSimmo
BigSimmo merged commit 0df01d8 into main Jul 18, 2026
20 of 21 checks passed
@BigSimmo
BigSimmo deleted the codex/private-title-privacy-20260718 branch July 18, 2026 14:42
@BigSimmo

Copy link
Copy Markdown
Owner Author

Codex @codex resolve the supabase branch error please

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

Testing

  • npm run workflow:flightplan -- --write-evidence
  • npm run workflow:clinical-proof -- --write-evidence
  • git diff --check
  • ⚠️ npm run test -- tests/supabase-schema.test.ts — could not run because node_modules/vitest/vitest.mjs is missing in this environment.
  • ⚠️ npm ci — could not install dependencies because the environment has Node v20.20.2, while the repo requires Node 24.x.
  • ⚠️ Provider-backed Supabase branch replay / npm run check:supabase-project / production-readiness checks were not run because repository instructions require explicit confirmation for hosted Supabase or provider-backed workflows.

View task →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant