Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
428 changes: 428 additions & 0 deletions docs/audit/live-drift-forensics-2026-08.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
| 2026-08-18 | claude/db-remediation-phase4-indexes-a1661a | 5d3dca4dd7a0593a35e6d639144092dee6609256 | Phase 4 index restoration: 20 concurrent index builds + 2 concurrent drops on production, 3 fail-fast guard migrations, search_schema_health required_indexes 22->30, schema.sql mirror, regenerated drift manifest, staging parity, forensics evidence | PASS — 20/20 indexes rebuilt indisvalid+indisready with canonical definitions, 2 orphans dropped per the repo chain, live-drift 32171070287 shows missing_live 20->0 and unexpected_live 2->0, staging drift green (was 19). Two escalations recorded not absorbed: PITR is not enabled on production, and no migration_history allowlist entry was earned (empty intersection with the 15 no-statements versions) | check:migration-role; vitest supabase-schema + search-health-index-coverage + migration-history-guards + drift-detection + migration-history-placeholders + hosted-migration-role-guard (6 files, 109 tests); drift:manifest; check:rag:fixtures (36 golden cases); check:medication-interactions; check:medication-lexicon-report; verify:pr-local all stages pass except two load-induced timeouts (codex-cloud-setup, document-viewer-page-virtualization) that pass in isolation and are unrelated to this diff |
55 changes: 36 additions & 19 deletions docs/database-remediation-coordination.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": 2,
"id": "07867a60-274a-4350-a564-2fed480ff997",
"createdOn": "2026-08-18",
"action": "add",
"payload": {
"pri": "P2",
"type": "issue",
"summary": "Point-in-time recovery is OFF on the live Supabase project, so the remediation plan's 'restore point before any mutating phase' rule cannot be met",
"detail": "Measured 2026-08-19 during the Phase 4 production window (forensics section 'Phase 4 completion', step 1). `supabase backups list --project-ref sjrfecxgysukkwxsowpy` returns pitr_enabled false, walg_enabled true, and seven retained daily physical backups, most recent COMPLETED 2026-08-17T20:33:28Z -- roughly 38 hours before that window opened. CONSEQUENCE: the recovery-point objective for the live clinical corpus (2851 documents, 70120 chunks) is up to ~24 hours, and the database-remediation plan's standing rule 'PITR/backup restore point captured before any mutating phase' CANNOT be satisfied on this project as currently configured. Phase 4 proceeded anyway and said so: every statement in it was index-only with an exact one-statement inverse (CREATE INDEX CONCURRENTLY <-> DROP INDEX CONCURRENTLY) and no data-loss surface, the same reasoning the 2026-08-14 incident window recorded. That reasoning does NOT generalise. Any future window that mutates DATA rather than indexes -- #022's BMJ attestation hosted apply, #036's public_corpus marker migration, #191's ACL consolidation, #057's restore/soak drill, or any reindex or backfill -- must not proceed on this precedent while the only restore point is a day-old physical backup. DECISION REQUIRED FROM THE OWNER, and it is dashboard plus billing work no agent can do: either enable PITR on the Supabase project (Database settings -> Add-ons -> Point in Time Recovery; it is a paid add-on, so this is a cost decision as well as a safety one), or deliberately accept the ~24h RPO and amend the plan's standing rule to say so, so that future sessions stop treating an unsatisfiable checklist item as if it had been met. Whichever is chosen, record it, because the current state is that the rule reads as satisfied by default when nobody checks. Consider re-grading this row to P1 if a ~24-hour worst-case data loss on the clinical corpus is judged unacceptable; it is filed P2 because the exposure is a deliberate platform configuration rather than a regression, and no data loss has occurred. Related: #057 (restore/rollback drill -- its value is limited while PITR is off), #188 and #196-#200 (DR codification).",
"source": "Phase 4 production window 2026-08-19; docs/audit/live-drift-forensics-2026-08.md 'Phase 4 completion' step 1; PR #2151",
"issueUlid": "01M0B4V6S21K6T35KEB130CXJB"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 2,
"id": "2c95ad4c-bbc7-4043-9c79-8afd529fa4c2",
"createdOn": "2026-08-18",
"action": "cancel",
"payload": {
"requestId": "7a93a5a2-d3b9-4f3d-9747-22049300cd64",
"reason": "Superseded and partly stale. Queued by the 2026-08-18 coordination session before Phase 4 ran; it records D4 as 'owner decision pending' and lists Phase 4 as REMAINING, both of which are now wrong (D4 is decided OFF, Phase 4 completed 2026-08-19). Its evidence is preserved in full in docs/audit/live-drift-forensics-2026-08.md section 3.7 and on the coordination board, and its still-current facts (the RPC-divergence track closing, the D4 platform finding) are carried forward verbatim into the replacement update queued in this same batch. Cancelled together with b048dbdd so exactly one mutation lands on #316."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 2,
"id": "51673c73-b0ea-4848-bae4-83a269dfa833",
"createdOn": "2026-08-18",
"action": "update",
"payload": {
"id": "#316",
"detail": "PHASE 4 COMPLETE 2026-08-19; the RPC track of this row closed 2026-08-18. BOTH HALVES OF THIS ROW ARE NOW CLOSED. (A) RPC divergence, from the 2026-08-18 window (PR #2123, forensics 3.7): the authorised window's pre-flight found the pending set EMPTY -- all five 20260818 migrations were already applied with executed statements (3/11/12/5/4, the CLI db push shape, not mark-applied) -- so db push was never run, no migration repair, no vault reads, zero production writes. Manifest def_hash equals live for all ten match_* functions, and live-drift 32131517648 showed 0 function mismatches. CAUSE, platform finding since resolved: the Supabase GitHub integration (Branching, production bound to git main, branch record 2026-06-27) was auto-applying every migration merged to main -- push-triggered live-drift bracketed 110000-112000 to 34 s after #2106's squash-merge. D4 IS NOW DECIDED: auto-deploy is OFF, confirmed empirically on 2026-08-19 when the four new 20260819 migrations sat pending on production after the branch existed and reached it only via an explicit db push. Not established: who enabled the integration or when, or whether the July mark-applied rows trace to it. (B) Index restoration, 2026-08-19 owner-authorized off-peak window: all 20 missing_live indexes rebuilt with CREATE INDEX CONCURRENTLY from canonical definitions cross-read against their defining migrations -- Batch A 14/14, Batch B 6/6, every one indisvalid AND indisready with normalized pg_get_indexdef matching canonical, zero invalid builds, zero retries, zero skips, zero lock waits (pg_locks read between every Batch B build). No transactional build was ever attempted; #102's bare-column indexes held out. Both unexpected_live indexes were DROPPED CONCURRENTLY rather than codified, because the chain already commands both drops and each is a strict leading-column subset of a present canonical index: document_table_facts_document_id_idx (superseded per 20260620000000) and storage_cleanup_jobs_owner_id_idx (superseded per 20260703030000/20260708000000). Live now reports 210 public indexes against the manifest's 210, zero invalid anywhere. Codified in five migrations applied by real supabase db push (never migration repair; every history row carries executed statements): 20260819100000/100100 guard Batch A/B, 20260819100200 discharges the plan 4.4 debt by guarding the two trigram indexes restored 2026-08-14 that 20260804110240 never checked, 20260819100300 takes search_schema_health() required_indexes 22->30 adopting all 8 Phase 6.3 monitor-candidates (unmonitored list 44->36, no monitor-candidate left; production ok true), and 20260819100150 repairs a chain defect the guard itself caught -- see below. Live-drift 32171070287: UNEXPECTED DRIFT 37->16, missing_live 20->ZERO, unexpected_live 2->ZERO. Staging brought to full parity in the same task; its drift comparison is GREEN with ZERO unexpected drift (was 19), corpus untouched, --prune-stale correctly not used. THE GUARD EARNED ITS KEEP: 20260819100200 failed the Supabase Preview check on PR #2151 because a preview branch builds from the migration chain alone, and the chain permanently produced the WRONG document_chunks_content_trgm_idx -- 20260606000000 creates it first without coalesce(content,''), and both later correct creators use IF NOT EXISTS so they no-op, with no migration ever dropping it. Forensics 3.3(d) had scoped this as staging-only and hand-repaired it there; it was never staging-only (db reset, DR replay, CI migration replay, preview branches all get the wrong index, which is NULL for rows with NULL content and so silently omits those chunks). Fixed by 20260819100150, conditional so it no-ops when canonical, rebuilds only on an empty table, and raises rather than run a write-blocking build on a populated one; proven by replaying the whole chain into a scratch Postgres (fails without it exactly as CI did, 204/204 with it) and the no-op path proven on production itself (index OID unchanged at 1491258 across the push). TWO ESCALATIONS FOR THE OWNER, neither absorbed. (1) PITR IS NOT ENABLED on production (pitr_enabled false, walg_enabled true, daily physical backups only, latest 2026-08-17T20:33:28Z), so the plan's standing 'restore point before any mutating phase' rule cannot be met; Phase 4 proceeded only because every statement was index-only with an exact one-statement inverse, and no future window that mutates DATA should proceed on that precedent. Queued separately as its own P2. (2) The migration_history block did NOT drop and no allowlist entry was written -- measured, not skipped: of the 15 no-statements versions, 6 are index-shaped and the intersection between the objects they create and the 22 these guards validate is EMPTY (near-misses are distinct objects, e.g. audit_logs_owner_id_idx vs audit_logs_owner_created_idx). The 15 stay unallowlisted and remain #Q5JHBJ's work. REMAINING FOR THIS ROW: nothing on the index or RPC tracks. Phase 5 measurement (after-EXPLAIN set, #231 re-test on healthy latency, check:production-readiness) is the only follow-on. Full evidence with dates, run IDs and pasted output in docs/audit/live-drift-forensics-2026-08.md sections 3.7 and 'Phase 4 completion'. Session traps still current: the main checkout D:\\Repos\\Database is linked to STAGING, so link a dedicated worktree for production and unlink after; supabase db query --linked --project-ref <ref> works read-only via the management API without a DB password; db query parses a leading -- as a flag, so pass SQL that starts with a comment via --file; production has no track_commit_timestamp.",
"source": "PR #2151 (Phase 4) and PR #2123 (window 3.7); live-drift runs 32171070287 and 32131517648; forensics sections 3.7 and 'Phase 4 completion'",
"baseRowFingerprint": "de004897b857158a00169721415caf512defac916bbff0466da5d7d3da4049c4"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 2,
"id": "946d0cce-549d-4caf-b74d-af5593e0911b",
"createdOn": "2026-08-18",
"action": "cancel",
"payload": {
"requestId": "b048dbdd-7672-4e47-82e2-2211495e40c6",
"reason": "Cancelled by its own author to resolve a two-pending-mutations collision on #316. Its content is carried forward unchanged into the merged replacement queued in this same batch, which additionally carries the RPC-track closure and D4 finding from the concurrently queued 7a93a5a2 so neither session's result is lost."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 2,
"id": "b048dbdd-7672-4e47-82e2-2211495e40c6",
"createdOn": "2026-08-18",
"action": "update",
"payload": {
"id": "#316",
"detail": "PHASE 4 COMPLETE 2026-08-19 in an owner-authorized off-peak production window (D4 auto-deploy OFF, so nothing reached production on merge; every hosted change was made by an explicit named step). The index track of this row is CLOSED. All 20 missing_live indexes were rebuilt with CREATE INDEX CONCURRENTLY using canonical definitions cross-read against their defining migrations: Batch A 14/14 and Batch B 6/6, every one indisvalid AND indisready with normalized pg_get_indexdef matching canonical, zero invalid builds, zero retries, zero skips, zero lock waits (pg_locks read between every Batch B build, waiting stayed 0). No transactional build was ever attempted and #102's bare-column indexes were held out entirely. Both unexpected_live indexes were DROPPED CONCURRENTLY rather than codified, because the repo chain already commands both drops and each is a strict leading-column subset of a present canonical index: document_table_facts_document_id_idx (superseded by document_table_facts_document_idx per 20260620000000) and storage_cleanup_jobs_owner_id_idx (superseded by storage_cleanup_jobs_owner_status_idx per 20260703030000/20260708000000). Live now reports 210 public indexes against the manifest's 210 repo-defined, with zero invalid-or-not-ready anywhere. Codified in four migrations pushed to production by real supabase db push (never migration repair --status applied; all four history rows carry executed statements, stmt_count 4, so none can ever appear in the migration_history probe): 20260819100000 and 20260819100100 guard Batch A and Batch B, 20260819100200 discharges the plan 4.4 debt by guarding the two trigram indexes restored 2026-08-14 that 20260804110240 never checked, and 20260819100300 extends search_schema_health() required_indexes from 22 to 30. All three guards were dry-run green against production BEFORE the push. required_indexes decision: all 8 Phase 6.3 monitor-candidates are now monitored (the 3 formerly-absent ones only after their builds were confirmed), leaving search-health-unmonitored-indexes.json at 36 entries with no monitor-candidate remaining; production search_schema_health() reports ok true, missing []. Live-drift dispatch on main, Actions run 32171070287: UNEXPECTED DRIFT fell from 37 to 16 — missing_live 20 to ZERO, unexpected_live 2 to ZERO. The single remaining function mismatch is search_schema_health() and is merge-pending, proven not assumed: live's def_hash 85df52de66e4e89d4a328b81a3a87c90 is byte-identical to this branch's regenerated manifest, so it clears when the PR lands. Staging (ikoiolksxqxfxgiyqpnu) was brought to full parity in the same task: document_chunks_content_trgm_idx was dropped and rebuilt into the canonical coalesce(content,'') form per forensics 3.3(d), the four migrations were applied by the Phase 2 method with all four md5s matching their repo files, and the drift comparison against staging is now GREEN with ZERO unexpected drift (was 19), corpus untouched at 0 documents and --prune-stale correctly not used. TWO ESCALATIONS FOR THE OWNER, neither absorbed. (1) PITR IS NOT ENABLED on production: backups list reports pitr_enabled false, walg_enabled true, and only daily physical backups, latest 2026-08-17T20:33:28Z. Step 1 of the authorized procedure could not be satisfied. This window proceeded because every statement was index-only with an exact one-statement inverse and no data-loss surface, but the plan's standing 'restore point before any mutating phase' rule cannot be met on this project as configured, and no future phase that mutates DATA should proceed on this precedent. (2) The migration_history block did NOT drop and no allowlist entry was written, which is a measured result: of the 15 no-statements versions, 6 are index-shaped, and the intersection between the objects they create and the 22 objects these guards validate is EMPTY (near-misses are distinct objects, e.g. audit_logs_owner_id_idx vs audit_logs_owner_created_idx). No honest validation-class entry existed, so the 15 stay unallowlisted and remain #Q5JHBJ's work. Full evidence with dates, run IDs and pasted output in docs/audit/live-drift-forensics-2026-08.md section Phase 4 completion.",
"baseRowFingerprint": "de004897b857158a00169721415caf512defac916bbff0466da5d7d3da4049c4"
}
}
8 changes: 4 additions & 4 deletions supabase/drift-manifest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"generated_at": "2026-08-18T08:30:22.062Z",
"generated_at": "2026-08-18T18:15:50.121Z",
"generator": "scripts/generate-drift-manifest.ts",
"postgres_image": "supabase/postgres:17.6.1.127@sha256:be60aee15997daca475b710b734bc6bfe52cd544dcd7e9fd2ff58210b6747d83",
"schema_sha256": "87ac9fc4849ee9cf0404a7260ef1b8bf08a14067573f3a87574a14bbc10469cf",
"replay_seconds": 75,
"schema_sha256": "328677d1c6f3e0136ca108e8117a73051553a2387b078836001e8cdc96f4c2d3",
"replay_seconds": 21,
"snapshot": {
"views": [
{
Expand Down Expand Up @@ -7336,7 +7336,7 @@
"postgres=X/postgres",
"service_role=X/postgres"
],
"def_hash": "f4f5f536026c4dd27d506a8e40b8c6d7",
"def_hash": "85df52de66e4e89d4a328b81a3a87c90",
"signature": "public.search_schema_health()"
},
{
Expand Down
Loading
Loading