fix(db): read migration history through a service-role RPC so live-drift can pass (#316) - #2198
Conversation
…ift can pass (#316) The live-drift workflow's "Align migration history for Supabase Preview" step has never been able to pass on this project. It read supabase_migrations.schema_migrations directly through PostgREST with Accept-Profile, and this project has never exposed that schema to the Data API, so the read returns 406 PGRST106 every time. The defect stayed hidden because the drift comparison ran first and always failed, leaving this step skipped. Phase 6.2 cleared the last drift finding on 2026-08-19, the step ran for the first time ever, and it is now the sole reason the job concludes failure - and therefore the sole reason pinned issue #1963 stays open against a database whose drift block is empty (run 32378402265, 2026-08-20: "Compare live schema drift: success", "No unexpected schema drift"). 20260820120000 adds public.migration_history_versions(): stable, security definer, search_path pinned to '', dynamic read guarded by to_regclass, returning {probe, versions} for every history row, revoked from public/anon/ authenticated and granted to service_role alone - the established schema_drift_snapshot() pattern. Exposing supabase_migrations to the Data API would widen the public PostgREST surface of a clinical project for one weekly read; routing through the management API would put an account-scoped token into CI. Both are recorded as rejected in the forensics file. check-migration-history-alignment.ts now prefers the RPC and falls back to the old read only when the function itself is absent. Every other outcome raises, including probe: no_history_table - a check that reports "aligned" because it could not look is worse than the red job it replaces. The migration is NOT deployed: D4 is off, so db push runs from this branch in an approved window BEFORE merge, the order Phase 4 used. Until it is applied, check:drift would report migration_history_versions as a missing function. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in:40 minutes Limit details: You’ve used the included review currently available. Your 85 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?Wait for the limit to reset, then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
Comment |
Updates to Preview Branch (claude/phase-4-index-restoration-b0f4ea) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
Uh oh!
There was an error while loading. Please reload this page.
…estion (#2201) * docs(ledger): record the PR #2198 migration-history alignment review Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(db): record the 2026-08-20 alignment window and reopen the D4 question The PGRST106 fix (PR #2198, squash a341832) merged before its production window, so the window's pre-flight found 20260820120000 already applied and db push was never run. Verified read-only against sjrfecxgysukkwxsowpy: stmt_count 3 (executed statements, not mark-applied), prosecdef true, provolatile s, search_path="", proacl postgres=X/postgres | service_role=X/postgres, and the function returns probe ok with version_count 211 against 211 history rows and 211 local files. No guard migration is owed. Two open items are recorded rather than absorbed. D4 is unresolved again. created_by and idempotency_key are NULL on every row from 20260818090000 onward, including ones this programme applied by operator db push, so the history table carries no provenance signal. The 2026-08-19 observation recorded as "D4 is OFF" was that migrations sat pending WHILE a PR was open; that never tested deploy-on-merge, and so never contradicted section 3.7's 34-second application after #2106's squash-merge. Until the dashboard is re-checked, assume merging a migration PR deploys it to production. Staging is one version behind (210 rows, function absent, pending set exactly 20260820120000). Both write paths were denied by the session auto-mode classifier under the live-Supabase confirmation rule added by PR #2196, so Phase 4's staging parity is open by one version until an operator applies it. Also lands the owed PR #2198 review-ledger record, whose original push was cut short when the branch was deleted on merge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(db): treat a merged migration PR as a production deploy until D4 is re-verified The 2026-08-20 window update reopened D4 (Supabase GitHub auto-deploy), but the status board's active instructions still told a coordinator the opposite: the "Next dispatches" line, the D4 owner-decision entry, the 2026-08-19 "Resolved" paragraph, and the pre-window forensics section all said D4 is OFF and every migration needs its own explicit `db push`. A coordinator reading those could merge a migration PR outside its approved window — the unapproved production schema change the update exists to prevent. All four now point at the reopened decision: assume merging a migration PR deploys it to production until the dashboard toggle is re-verified. Push-before- merge stays the required order, for the inverted reason. Also lands the PR #2201 review-ledger record. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoyoNN7nCUVDLaE84k6e6c --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Summary
The live-drift workflow's second step,
Align migration history for Supabase Preview, has never been able to pass on this project. It readsupabase_migrations.schema_migrationsdirectly through PostgREST withAccept-Profile: supabase_migrations, and this project has never exposed that schema to the Data API, so the read returns406 PGRST106every time.The defect stayed hidden because the drift comparison ran first and always failed, leaving this step
skipped. Phase 6.2 cleared the last drift finding on 2026-08-19, the step ran for the first time ever, and it is now the sole reason the job concludesfailure— and therefore the sole reason pinned issue #1963 stays open against a database whose drift block is empty.Measured again on
mainthis week, run32378402265(2026-08-20, weekly cron):This PR repairs the read with the least authority that can do the job:
supabase/migrations/20260820120000_migration_history_versions_rpc.sqladdspublic.migration_history_versions()—stable,security definer,set search_path to '', dynamic read guarded byto_regclass, returning{probe, versions}for every history row.revoke execute … from public, anon, authenticatedplusgrant execute … to service_role, exactly the establishedpublic.schema_drift_snapshot()pattern (20260706200000/20260818090000). Read-only: oneselectover the CLI history table.scripts/check-migration-history-alignment.tsnow tries the RPC first and falls back to the old Accept-Profile read only when the function itself is absent (404 /PGRST202). Every other outcome raises, includingprobe: no_history_table— a check that reports "aligned" because it could not look would be worse than the red job it replaces. When neither path works, the failure names the remedy.supabase/schema.sqlmirrors the definition byte-identically;supabase/drift-manifest.jsonregenerated.tests/migration-history-alignment.test.ts(new, 7 tests) pins the preference order, the fallback condition, the honest-failure behaviour, and migration-vs-mirror parity.docs/database-drift-detection.mdgains a section on the alignment step and why this transport was chosen;docs/audit/live-drift-forensics-2026-08.mdgains "Alignment-step repair — 2026-08-20" with the run IDs and pasted output;docs/database-remediation-coordination.mdrecords the state and the required deploy order.Two alternatives were considered and rejected, recorded in the forensics file so they are not re-litigated: exposing
supabase_migrationsto the Data API (widens the public PostgREST surface of a clinical project for one weekly read, and lives in dashboard config rather than in git), and routing the read through the management API withSUPABASE_ACCESS_TOKEN(puts an account-scoped token into CI secrets — far broader authority than the read needs, and still blocked on#183).Deploy order. The Supabase GitHub auto-deploy is off (D4), so merging this PR applies nothing.
supabase db pushruns from this branch in an approved window before merge — the order Phase 4 used. Until the migration is applied,check:driftwould reportmigration_history_versionsas a missing function, so merging first would trade one red for another. Staging needs the same migration by the Phase 2 method to hold the parity Phase 4 restored.Ledger: this is the fix for the queued request
cc60253d(P2, live-drift alignment step); the canonical row is allocated by the next serializednpm run issues:reconcile. Tracking anchor#316.RAG impact: no retrieval behaviour change — this adds a read-only, service-role-only catalog function used solely by CI migration-history alignment, and rewrites one CI script's transport; no retrieval RPC, ranking surface, selection, ordering, or fixture is touched.
Verification
npm run check:migration-role— "Hosted migration-role guard passed: active hosted SQL/tooling uses postgres and immutable applied history is unchanged."npm run drift:manifest— full scratch replay ofsupabase/schema.sqlintosupabase/postgres:17.6.1.127, "Replay complete in 58s". This executes the new function body in a real Postgres, so the SQL is proven rather than reviewed. Manifest now carries 94 functions (was 93), withpublic.migration_history_versions()atacl: ["postgres=X/postgres", "service_role=X/postgres"]— least privilege confirmed by replay, noPUBLICexecute.npx vitest run tests/drift-detection.test.ts tests/migration-history-guards.test.ts tests/search-health-index-coverage.test.ts tests/migration-history-alignment.test.ts— "Test Files 4 passed (4) / Tests 40 passed (40)".npm run format(whole tree) andnpm run verify:pr-local.Not run, with reasons: no provider-backed gate (
check:supabase-project,eval:*,verify:release) — none is needed for a repo-side change, and all require explicit approval. No browser gate — no UI surface is touched. The live proof that this fix works can only come from the deploy window plus the next live-drift dispatch, and is recorded as owed.Clinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy)Notes
supabase_migrations.schema_migrations(version and name). It touches no clinical document, no owner-scoped table, and no patient data, and it is unreachable byanonorauthenticated.schema_drift_snapshot()'smigration_history, which returns only the rows recorded without executed statements. Neither replaces the other.