From cc46d8164537306436a42fdfcf4116c0b56e0449 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Tue, 18 Aug 2026 16:48:22 +0800 Subject: [PATCH 1/5] feat(db): codify live SET work_mem on the ten match_* RPCs, the eight schema-only objects, and three chain-stale columns (#316) Database remediation Phase 3, reframed as repo-side codification (owner decisions D1 codify-as-live, D2 work_mem canary exemption). No function body changed; no hosted value changed; no production access. - supabase/schema.sql: `set work_mem = ''` on all ten match_* definitions (128MB on chunks_hybrid, embedding_fields_hybrid, index_units_hybrid, index_units_hybrid_v2; 64MB on the other six), including the effective uppercase blocks for chunks_hybrid and table_facts_text. - 20260818110000_codify_live_rpc_work_mem: ALTER FUNCTION ... SET work_mem per function, versioned after every recreate. Regenerated drift-manifest def_hash for all ten equals the live production def_hash (issue #1963, run 32051068106) byte-for-byte. - 20260818111000_codify_schema_only_indexes_and_triggers: the five document_embedding_fields indexes, documents_status_idx, and the documents_updated_at / ingestion_jobs_updated_at triggers, verbatim from schema.sql (forensics 2.3 b); unmonitored-list reasons refreshed. - 20260818112000_reconcile_chain_stale_table_columns: document_chunks .token_estimate (never in any migration) and the rag_visual_eval_cases/runs id defaults (20260705230000 bound extensions.gen_random_uuid via search_path order) - all idempotent on production. - forensics section Phase 3 written; #316 update + #183 rider queued as inbox requests. Staging proof deliberately not run: Phase 2 re-measure absent on main. RAG impact: no retrieval behaviour change - codifying already-live SET work_mem attributes and mirror-only objects; no function body changed Co-Authored-By: Claude Fable 5 --- docs/audit/live-drift-forensics-2026-08.md | 166 +++++++++++++++++- .../41576279-d570-436b-a80b-d23b555845af.json | 12 ++ .../4dbcdcce-3645-4e08-8813-e85ab1fe7bcc.json | 12 ++ supabase/drift-manifest.json | 26 +-- ...0260818110000_codify_live_rpc_work_mem.sql | 54 ++++++ ...odify_schema_only_indexes_and_triggers.sql | 60 +++++++ ...00_reconcile_chain_stale_table_columns.sql | 42 +++++ supabase/schema.sql | 12 ++ .../search-health-unmonitored-indexes.json | 12 +- 9 files changed, 371 insertions(+), 25 deletions(-) create mode 100644 docs/outstanding-issues-inbox/41576279-d570-436b-a80b-d23b555845af.json create mode 100644 docs/outstanding-issues-inbox/4dbcdcce-3645-4e08-8813-e85ab1fe7bcc.json create mode 100644 supabase/migrations/20260818110000_codify_live_rpc_work_mem.sql create mode 100644 supabase/migrations/20260818111000_codify_schema_only_indexes_and_triggers.sql create mode 100644 supabase/migrations/20260818112000_reconcile_chain_stale_table_columns.sql diff --git a/docs/audit/live-drift-forensics-2026-08.md b/docs/audit/live-drift-forensics-2026-08.md index f74f62af40..6bc86901ec 100644 --- a/docs/audit/live-drift-forensics-2026-08.md +++ b/docs/audit/live-drift-forensics-2026-08.md @@ -674,14 +674,168 @@ error, and no migration file was edited.** bodies, which Postgres does not resolve at `CREATE FUNCTION` time, so both applied without `pg_net` installed. They fail only when called. -## Phase 3 — RPC reconciliation +## Phase 3 — RPC reconciliation (reframed: repo-side codification, no production deploy) + +_2026-08-18 (repo-side session; owner decisions D1 codify-as-live and D2 canary exemption in force; +no production access; two read-only `SELECT`s against staging `ikoiolksxqxfxgiyqpnu` for the (c) +triage only, authorised in-session, `list_projects` verified the ref first). This supersedes the +playbook's Phase 3 prompt, which assumed repo-ahead canaries the §1.2 dossier ruled out. **No +function body changed.** `#292` open-PR check before starting: no open PR touched `schema.sql`, +`supabase/migrations/**`, `drift-manifest.json` or `scripts/check-drift.ts`._ + +### 3.1 `SET work_mem` codified on all ten RPCs — zero function mismatches, proven offline + +`supabase/schema.sql` now carries `set work_mem = ''` as the last `set` clause of each +of the ten definitions (both the legacy lowercase and the effective uppercase blocks for +`match_document_chunks_hybrid` and `match_document_table_facts_text`, which `schema.sql` defines +twice; the later block wins on replay). New migration +`20260818110000_codify_live_rpc_work_mem.sql` runs one `ALTER FUNCTION … SET work_mem = '…'` per +function, versioned after every `create or replace` of each (newest bodies `20260701140631`, +`20260714110000`, `20260717162000`, `20260724120000`), so a clean chain replay ends with the +attribute present in live's `[search_path, (plan_cache_mode), work_mem]` proconfig order. The four +duplicate migration pairs (§2.4 finding 2) do not touch the ten. Idempotent on production. + +**Decisive proof.** `npm run drift:manifest` (Docker replay of the edited `schema.sql`, 75 s, +`generated_at 2026-08-18T08:30:22Z`) yields a `def_hash` for every one of the ten that is +**byte-identical to the live production hash** captured in issue #1963's findings block (Actions +run `32051068106`, 2026-08-17 — the same values §1.2 Query 1 recorded): + +| Function | `work_mem` | Migration setting it | `schema.sql` lines (post-edit) | new manifest `def_hash` = live `def_hash` | +| ---------------------------------------- | ---------- | ----------------------------------------------------------- | ------------------------------- | ----------------------------------------- | +| `match_document_chunks_hybrid` | 128MB | `20260818110000` (was 64MB in `20260724000000`) | 2772 (legacy), 6588 (effective) | `5902c39286335c07714e498ea31513a0` | +| `match_document_embedding_fields_hybrid` | 128MB | `20260818110000` (was 64MB in `20260724000000`) | 4292 | `bb975485ee3a5776bce4abdc2e3a3cbd` | +| `match_document_index_units_hybrid` | 128MB | `20260818110000` (was 64MB in `20260724000000`) | 5501 | `d0e277a2f3067f49463b85ac84b33276` | +| `match_document_index_units_hybrid_v2` | 128MB | `20260818110000` (never set before) | 8006 | `05ddb8f73fac7751a2256aa15c1122e2` | +| `match_document_chunks_text` | 64MB | `20260724000000`, re-asserted by `20260818110000` | 3845 | `d135c628720cb8a4d86c2ade4cd3b26a` | +| `match_document_chunks_text_v2` | 64MB | `20260818110000` (never set before) | 7822 | `3639b2442bac7b2e7b18ad322a395acb` | +| `match_document_lookup_chunks_text` | 64MB | `20260724000000`, re-asserted by `20260818110000` | 4002 | `c1ede773fc0498e32bc3b4aa7262b32c` | +| `match_document_memory_cards_hybrid` | 64MB | `20260724000000`, re-asserted by `20260818110000` | 3096 | `9079b928ee56fd7846e280d10ba1d27c` | +| `match_document_memory_cards_hybrid_v2` | 64MB | `20260724000000`, re-asserted by `20260818110000` | 2982 | `ab87a18bea57612db83428c24c425825` | +| `match_document_table_facts_text` | 64MB | `20260818110000` (dropped by the `20260724120000` recreate) | 4190 (legacy), 6676 (effective) | `0ef9a5dfbde03fe6d48d9223e245aa69` | + +10/10 equal. Because `check:drift` compares functions on `def_hash` + `acl` and ACLs were already +equal (§1.2), the next production live-drift run will report **zero `match_*` function mismatches** +without any hosted change — the migration only needs to be marked applied in a production window. +Eval-canary: none dispatched (D2 — planner memory, latency-only; no live eval in this task). + +### 3.2 Eight never-created objects — codified verbatim (`20260818111000`) + +`20260818111000_codify_schema_only_indexes_and_triggers.sql` creates, verbatim from `schema.sql` +(lines 678, 763–776, 1073–1081) with `create index if not exists` / `drop trigger if exists` + +`create trigger` semantics, the five `document_embedding_fields_*` indexes +(`owner_id_idx`, `owner_document_created_idx`, `source_chunk_id_idx`, +`meta_rag_indexing_version_idx`, `search_tsv_chunk_gin_idx`), `documents_status_idx`, and the +`documents_updated_at` / `ingestion_jobs_updated_at` triggers on `public.set_updated_at()`. +Verified against `#102`'s list (`documents_title_bare_trgm_idx`, `documents_file_name_bare_trgm_idx`, +`documents_status_id_idx`): **disjoint** — `documents_status_idx` is the existing single-column index +`#102` proposes to supplement, not one of its objects. All eight already exist on production +(the 2026-08-14 live-drift run listed none of them as `missing_live` and reported no trigger drift), +so the migration is a no-op there. + +**Monitoring decision (forced by `tests/search-health-index-coverage.test.ts`):** all six indexes +were already on `supabase/search-health-unmonitored-indexes.json` (five `accepted-unmonitored`, +`document_embedding_fields_search_tsv_chunk_gin_idx` a `monitor-candidate`); they **stay on the +unmonitored list** and `search_schema_health()` `required_indexes` is not changed here — that is a +runtime-probe redefinition with its own migration and belongs to Phase 4.4. Their `reason` strings +were refreshed to cite `20260818111000` (they had said "never by a migration"). The test's +migration-vs-manifest disagreement count drops by six. + +### 3.3 Triage of the four remaining staging findings + +**(c) `document_chunks` — CHAIN-stale, one column.** Read-only staging +`schema_drift_snapshot()->'tables'` for the three tables, diffed per column against the manifest +offline (`check:drift`'s own 240-char clip hides the column; see note below): -_Not yet run. Requires an approved production window, plus a separate canary approval per -repo-ahead RPC._ +``` +== document_chunks + column only in manifest: token_estimate {"default":"0","not_null":true,"type":"integer"} +``` + +`token_estimate integer not null default 0` is `schema.sql:309`, is written by +`src/lib/chunking.ts:624` and `src/lib/registry-corpus.ts:224`, required by +`src/lib/supabase/database.types.ts:305`, and present on production (2026-08-14 live-drift: no table +drift ⇒ production = manifest = `schema.sql`); `grep -rn token_estimate supabase/migrations` returns +**zero** hits. Fixed in the chain by `20260818112000_reconcile_chain_stale_table_columns.sql` +(`add column if not exists`, catalog-only on PG 11+, no-op on production). Owner-approved in-session. + +**(c) `rag_visual_eval_cases` / `rag_visual_eval_runs` — CHAIN-stale, `id` default binding.** +`schema.sql:5909–5942` is byte-identical to `20260705230000:231–264`, so the mismatch is not +textual. The staging diff is exactly: + +``` +== rag_visual_eval_cases column differs: id manifest default "gen_random_uuid()" staging default "extensions.gen_random_uuid()" +== rag_visual_eval_runs column differs: id manifest default "gen_random_uuid()" staging default "extensions.gen_random_uuid()" +``` -_Pending._ Per-RPC outcome against the Phase 1.2 classification, the migration that codified each -live-ahead body, and eval-canary evidence (36/36, recall 1.0, zero per-case rr regressions) for any -behaviour-changing deploy. +Decisive hunk: `20260705230000_reconcile_live_database_drift.sql:6` opens with +`set search_path = public, extensions, pg_catalog;` — `pg_catalog` **last** — so its bare +`default gen_random_uuid()` bound to pgcrypto's `extensions.gen_random_uuid()`, whereas +`schema.sql:10` (`set search_path = public, extensions;`, `pg_catalog` implicitly first) and +production bind the core `pg_catalog.gen_random_uuid()`. Both generate v4 UUIDs (no behavioural +difference) but it is a real OID mismatch in the column set. `schema.sql` is the correct side and is +unchanged; `20260818112000` rebinds both defaults to `pg_catalog.gen_random_uuid()` (no-op on +production). `indexing_v3_agent_jobs.id` also renders `extensions.gen_random_uuid()` on staging but +`schema.sql:5653` declares it that way explicitly, so chain, mirror and live already agree — not +touched. Nothing from (c) remains UNCLASSIFIED. + +**(d) `document_chunks_content_trgm_idx` — production's restored definition IS canonical; no +escalation.** Three definitions exist in the repo; the drift `def_hash` is +`md5(regexp_replace(pg_get_indexdef(oid), '\s+', '', 'g'))` (`20260706200000:105`), computed +offline for both renderings: + +| Source | Expression | normalized `pg_get_indexdef` md5 | +| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------- | +| `20260606000000:11` (original creator, `if not exists`) | `lower(coalesce(section_heading, '') \|\| ' ' \|\| content)` | `c3db29603d9760b6a50f5ea23d58e4a8` = **staging** | +| `schema.sql:743` = `20260622000000:13` = `20260705180000:11` = production restore (§Phase 4) | `lower(coalesce(section_heading, '') \|\| ' ' \|\| coalesce(content, ''))` | `8499c3d35fc205ab0f7237031eee7926` = **manifest** | + +Canonical = the `coalesce(content, '')` form, which is what production carries since 2026-08-14 +(§Phase 4 pasted the post-build `pg_indexes` definition verbatim). Staging carries the 2026-06-06 +form because every later creator uses `if not exists` and no migration drops it — a chain-stale +residual on staging only. **Not fixed here**: the repair on any populated database is a concurrent +rebuild plus the Phase 4.4 fail-fast guard migration (the `20260804110240` pattern still names +four other indexes only); on staging it is a drop-and-recreate in the next staging window. Named as +the one expected residual for the staging proof. + +**Tooling note recorded for `#316`.** `scripts/check-drift.ts:192` clips each side of a +`columns` diff to 240 characters of an alphabetised multi-kB JSON array, so a single late-alphabet +column (`token_estimate`) never appears in the message and the two prefixes print identical. The +finding fires but does not name the column; per-column expansion needed the raw snapshot. + +### 3.4 Gates + +- `npm run drift:manifest` — `Replay complete in 75s`, `Wrote supabase/drift-manifest.json`; the + ten `def_hash` values above. +- `npm run check:migration-role` — `Hosted migration-role guard passed: active hosted SQL/tooling uses postgres and immutable applied history is unchanged.` +- `npx vitest run tests/supabase-schema.test.ts tests/drift-detection.test.ts tests/migration-history-guards.test.ts tests/search-health-index-coverage.test.ts tests/retrieval-access-scope.test.ts tests/migration-history-placeholders.test.ts tests/hosted-migration-role-guard.test.ts tests/guard-push.test.ts` — `Test Files 8 passed (8) · Tests 149 passed (149)`. +- `npm run verify:pr-local` — see the PR body for the pasted line (the two Windows-environmental + failures `tests/session-start-hook.test.ts` / `tests/worker-observability.test.ts` are the only + expected red set on this host). + +### 3.5 Staging proof — BLOCKED (not run) + +The staging apply of `20260818110000`/`111000`/`112000` was gated on the Phase 2 re-measure +(`main` ≥ 195 migrations, `20260818090000` applied to staging). At `main` `4551b6e4d` this file's §2 +carries **no "Re-measure" subsection** and no open PR adds one, so per instruction the staging step +was **not** started: no `execute_sql` DDL, no `schema_migrations` row, no `check:drift` run against +staging in this session. When unblocked, the method is Phase 2's: `execute_sql` running each file +verbatim in one implicit transaction plus an explicit md5-matched history row carrying the repo +version and name (never `apply_migration`), ref verified before every call; expected result: zero +`match_*` function mismatches, zero never-created objects, zero table column-set mismatches, and +exactly one named residual — `document_chunks_content_trgm_idx` (`c3db2960…` vs `8499c3d3…`, 3.3 d). + +### 3.6 Production window (NOT authorised in this task — for the coordinator) + +Live state already matches for everything in this phase, so the window is mark-applied-by-CLI / +`db push` of already-true statements, plus the one real change from Phase 6.1: + +| Migration | Effect on production | +| -------------------------------------------------------- | ---------------------------------------------------------- | +| `20260818090000_schema_drift_snapshot_history_probe` | **real change** — `schema_drift_snapshot()` v2 (Phase 6.1) | +| `20260818110000_codify_live_rpc_work_mem` | no-op — the ten `work_mem` values already match (3.1) | +| `20260818111000_codify_schema_only_indexes_and_triggers` | no-op — all eight objects already exist (3.2) | +| `20260818112000_reconcile_chain_stale_table_columns` | no-op — column and both defaults already as declared (3.3) | + +One window covers all four; none needs a canary (D2) and none builds an index. ## Phase 4 — Index restoration diff --git a/docs/outstanding-issues-inbox/41576279-d570-436b-a80b-d23b555845af.json b/docs/outstanding-issues-inbox/41576279-d570-436b-a80b-d23b555845af.json new file mode 100644 index 0000000000..14cd0a5797 --- /dev/null +++ b/docs/outstanding-issues-inbox/41576279-d570-436b-a80b-d23b555845af.json @@ -0,0 +1,12 @@ +{ + "version": 2, + "id": "41576279-d570-436b-a80b-d23b555845af", + "createdOn": "2026-08-18", + "action": "update", + "payload": { + "id": "#183", + "detail": "RIDER 2026-08-18 (recorded on the Phase 3 product PR, not a ledger-only branch): the Supabase CLI is now authenticated and this repository is linked to the staging project Clinical KB Staging (ikoiolksxqxfxgiyqpnu) — owner action 2026-08-18. That unblocks CLI repair paths (supabase db push --linked, migration repair with a guard migration) for staging; SUPABASE_ACCESS_TOKEN as a repository/environment secret for CI/live-drift remains outstanding, and the Sentry metric-alert part of this row (SENTRY_AUTH_TOKEN, p95 span.op:db > 500ms alert, deprioritised 2026-08-12) is unchanged.", + "source": "session 2026-08-18 Phase 3 repo-side codification (branch claude/schema-work-mem-codify-6200f1)", + "baseRowFingerprint": "35940a5d1e7fd7c0025901d36235a4620234f28c6c55abbed3381c57c814dd83" + } +} diff --git a/docs/outstanding-issues-inbox/4dbcdcce-3645-4e08-8813-e85ab1fe7bcc.json b/docs/outstanding-issues-inbox/4dbcdcce-3645-4e08-8813-e85ab1fe7bcc.json new file mode 100644 index 0000000000..42412535c3 --- /dev/null +++ b/docs/outstanding-issues-inbox/4dbcdcce-3645-4e08-8813-e85ab1fe7bcc.json @@ -0,0 +1,12 @@ +{ + "version": 2, + "id": "4dbcdcce-3645-4e08-8813-e85ab1fe7bcc", + "createdOn": "2026-08-18", + "action": "update", + "payload": { + "id": "#316", + "detail": "PHASE 3 (reframed) BUILT REPO-SIDE 2026-08-18 — no production access, no function body changed, no hosted value change; owner decisions D1 codify-as-live and D2 canary exemption applied. (1) SET work_mem codified on all ten match_* RPCs: schema.sql now carries the clause on every definition (both blocks for chunks_hybrid and table_facts_text) and migration 20260818110000_codify_live_rpc_work_mem runs ALTER FUNCTION ... SET work_mem per function, versioned after every recreate incl. 20260724120000. Per-function value: 128MB chunks_hybrid, embedding_fields_hybrid, index_units_hybrid, index_units_hybrid_v2; 64MB chunks_text, chunks_text_v2, lookup_chunks_text, memory_cards_hybrid, memory_cards_hybrid_v2, table_facts_text. PROOF: the regenerated drift-manifest def_hash for all ten equals the live production def_hash in issue #1963 (run 32051068106) byte-for-byte, e.g. chunks_hybrid 5902c39286335c07714e498ea31513a0, table_facts_text 0ef9a5dfbde03fe6d48d9223e245aa69 — the next production live-drift run reports zero match_* mismatches once the migration is marked applied. (2) Eight never-created objects codified verbatim by 20260818111000_codify_schema_only_indexes_and_triggers (five document_embedding_fields indexes, documents_status_idx, documents_updated_at and ingestion_jobs_updated_at triggers); disjoint from #102; all six indexes stay on search-health-unmonitored-indexes.json (5 accepted-unmonitored, search_tsv_chunk_gin_idx monitor-candidate), required_indexes untouched — Phase 4.4 decides by migration. (3) Triage: document_chunks CHAIN-stale (token_estimate integer not null default 0 in schema.sql/live/app, zero migrations); rag_visual_eval_cases/runs CHAIN-stale (id default bound to extensions.gen_random_uuid because 20260705230000 sets search_path with pg_catalog last; schema.sql and production bind pg_catalog) — both fixed idempotently by 20260818112000_reconcile_chain_stale_table_columns; document_chunks_content_trgm_idx: production's restored definition (coalesce(content,'') form, md5 8499c3d3...) IS canonical = schema.sql = 20260705180000; staging carries the 20260606000000 bare-content form (c3db2960...) because later creators use if-not-exists — staging-only residual for Phase 4.4 guard + staging rebuild, no escalation. Nothing UNCLASSIFIED. Tooling note: check-drift.ts:192 clips columns diffs to 240 chars so a wide-table column drift never names the column. (4) Gates: check:migration-role passed; 8 focused test files 149 passed; drift:manifest replay 75s. STAGING PROOF NOT RUN — blocked because main 4551b6e4d forensics §2 has no Re-measure subsection (owner confirmed stop). PRODUCTION WINDOW NEEDED (not authorised in this task): 20260818090000 (real change, probe v2) + 20260818110000/111000/112000 (all no-ops, state already matches) — one window, no canary, no index build. Evidence: forensics §Phase 3.", + "source": "session 2026-08-18 Phase 3 repo-side codification (branch claude/schema-work-mem-codify-6200f1)", + "baseRowFingerprint": "e03db7a376a6074d0ae25b648139caf82129f019627b20096058dd731a83a8db" + } +} diff --git a/supabase/drift-manifest.json b/supabase/drift-manifest.json index 20cb6ea453..e914ca130b 100644 --- a/supabase/drift-manifest.json +++ b/supabase/drift-manifest.json @@ -1,9 +1,9 @@ { - "generated_at": "2026-08-17T16:38:39.818Z", + "generated_at": "2026-08-18T08:30:22.062Z", "generator": "scripts/generate-drift-manifest.ts", "postgres_image": "supabase/postgres:17.6.1.127@sha256:be60aee15997daca475b710b734bc6bfe52cd544dcd7e9fd2ff58210b6747d83", - "schema_sha256": "a6fb923400f8566966bc3387f7e58c0b322aed4ae8ff7daa20f9eb5b53567b42", - "replay_seconds": 46, + "schema_sha256": "87ac9fc4849ee9cf0404a7260ef1b8bf08a14067573f3a87574a14bbc10469cf", + "replay_seconds": 75, "snapshot": { "views": [ { @@ -6976,7 +6976,7 @@ "postgres=X/postgres", "service_role=X/postgres" ], - "def_hash": "b5dfaa5e0d6b27ad0c7cfc89711953cb", + "def_hash": "5902c39286335c07714e498ea31513a0", "signature": "public.match_document_chunks_hybrid(extensions.vector,text,integer,double precision,uuid[],uuid)" }, { @@ -6992,7 +6992,7 @@ "postgres=X/postgres", "service_role=X/postgres" ], - "def_hash": "3d99483e01a5c93374408b9e585d3962", + "def_hash": "3639b2442bac7b2e7b18ad322a395acb", "signature": "public.match_document_chunks_text_v2(text,integer,uuid[],uuid,boolean)" }, { @@ -7000,7 +7000,7 @@ "postgres=X/postgres", "service_role=X/postgres" ], - "def_hash": "0e662039807813b400e685d7307d7929", + "def_hash": "d135c628720cb8a4d86c2ade4cd3b26a", "signature": "public.match_document_chunks_text(text,integer,uuid[],uuid)" }, { @@ -7032,7 +7032,7 @@ "postgres=X/postgres", "service_role=X/postgres" ], - "def_hash": "a2d97503e95af88097557029e0ea7836", + "def_hash": "bb975485ee3a5776bce4abdc2e3a3cbd", "signature": "public.match_document_embedding_fields_hybrid(extensions.vector,text,integer,double precision,uuid[],uuid)" }, { @@ -7056,7 +7056,7 @@ "postgres=X/postgres", "service_role=X/postgres" ], - "def_hash": "b72c524f3be13ec1a950cc30e922ec78", + "def_hash": "05ddb8f73fac7751a2256aa15c1122e2", "signature": "public.match_document_index_units_hybrid_v2(extensions.vector,text,integer,double precision,uuid[],uuid,boolean)" }, { @@ -7064,7 +7064,7 @@ "postgres=X/postgres", "service_role=X/postgres" ], - "def_hash": "2e8810a1ec9927aba7c1f04fd18287d1", + "def_hash": "d0e277a2f3067f49463b85ac84b33276", "signature": "public.match_document_index_units_hybrid(extensions.vector,text,integer,double precision,uuid[],uuid)" }, { @@ -7080,7 +7080,7 @@ "postgres=X/postgres", "service_role=X/postgres" ], - "def_hash": "989281557ff4877f8eae5c9a32a3ef8c", + "def_hash": "c1ede773fc0498e32bc3b4aa7262b32c", "signature": "public.match_document_lookup_chunks_text(text,uuid[],integer,uuid)" }, { @@ -7088,7 +7088,7 @@ "postgres=X/postgres", "service_role=X/postgres" ], - "def_hash": "0534ad140950e83128b3434caa5ffd32", + "def_hash": "ab87a18bea57612db83428c24c425825", "signature": "public.match_document_memory_cards_hybrid_v2(extensions.vector,text,integer,double precision,uuid[],uuid)" }, { @@ -7104,7 +7104,7 @@ "postgres=X/postgres", "service_role=X/postgres" ], - "def_hash": "1a937f133f5cb6e6d59a5b00311ec685", + "def_hash": "9079b928ee56fd7846e280d10ba1d27c", "signature": "public.match_document_memory_cards_hybrid(extensions.vector,text,integer,double precision,uuid[],uuid)" }, { @@ -7120,7 +7120,7 @@ "postgres=X/postgres", "service_role=X/postgres" ], - "def_hash": "f68e03ca96f8403d171509a59a769682", + "def_hash": "0ef9a5dfbde03fe6d48d9223e245aa69", "signature": "public.match_document_table_facts_text(text,integer,uuid[],uuid)" }, { diff --git a/supabase/migrations/20260818110000_codify_live_rpc_work_mem.sql b/supabase/migrations/20260818110000_codify_live_rpc_work_mem.sql new file mode 100644 index 0000000000..e8fb216e01 --- /dev/null +++ b/supabase/migrations/20260818110000_codify_live_rpc_work_mem.sql @@ -0,0 +1,54 @@ +-- Codify the SET work_mem attribute already live on ten match_* retrieval RPCs. +-- +-- Plan of record: docs/database-remediation-plan.md Phase 3 (reframed 2026-08-18); +-- evidence: docs/audit/live-drift-forensics-2026-08.md section 1.2 (per-function +-- dossier) and section 2.3/2.4 (staging replay). Ledger anchor #316. +-- +-- WHY: every one of the ten match_* def_hash mismatches reported by the live-drift +-- check is attribute-only. The live pg_get_functiondef carries `SET work_mem TO +-- '...'`, and stripping exactly that line reproduces the repo hash byte-for-byte for +-- 10/10 (forensics 1.2, Query 3). Bodies, signatures, return shapes, volatility, +-- search_path/plan_cache_mode clauses and ACLs are identical to the repo. Zero body +-- divergences, zero repo-ahead. This migration records the live attribute in the +-- chain and supabase/schema.sql now mirrors it, so the drift manifest hashes match. +-- +-- Owner decisions in force (docs/database-remediation-coordination.md): +-- D1 codify-as-live — each function keeps exactly the value recorded live: +-- 128MB on the four hybrids named below, 64MB on the other six. No hosted +-- value changes. +-- D2 eval-canary exemption — work_mem is planner/executor memory (hash vs sort, +-- spill vs in-memory): latency only, result set determined by each RPC's +-- ORDER BY ... LIMIT. RAG impact: no retrieval behaviour change. +-- +-- Why a new migration and not an edit of 20260724000000_optimize_rpc_work_mem: +-- * 20260724120000_table_facts_plpgsql_execute re-creates +-- match_document_table_facts_text with CREATE OR REPLACE, which resets the +-- config-item set, so a clean chain replay leaves it WITHOUT work_mem +-- (measured on staging: 7 of 8 carry it, forensics 2.4 finding 3). +-- * match_document_chunks_text_v2 and match_document_index_units_hybrid_v2 were +-- never given work_mem by any migration; live carries it. +-- * The four hybrids are 128MB live; no recorded migration sets 128MB. +-- Versioned after every create-or-replace of each function (newest bodies: +-- 20260701140631, 20260714110000, 20260717162000, 20260724120000), so a clean +-- replay ends with the attribute present in the same [search_path, +-- (plan_cache_mode), work_mem] proconfig order live shows. +-- +-- Idempotent on production (sjrfecxgysukkwxsowpy): the ten values already match, so +-- ALTER FUNCTION ... SET is a no-op there. Applying it needs only the ordinary +-- production migration window; it does not change hosted state. + +set search_path = public, extensions, pg_temp; + +-- 128MB — live value on the four hybrids (D1: keep as-is) +ALTER FUNCTION public.match_document_chunks_hybrid(extensions.vector, text, integer, double precision, uuid[], uuid) SET work_mem = '128MB'; +ALTER FUNCTION public.match_document_embedding_fields_hybrid(extensions.vector, text, integer, double precision, uuid[], uuid) SET work_mem = '128MB'; +ALTER FUNCTION public.match_document_index_units_hybrid(extensions.vector, text, integer, double precision, uuid[], uuid) SET work_mem = '128MB'; +ALTER FUNCTION public.match_document_index_units_hybrid_v2(extensions.vector, text, integer, double precision, uuid[], uuid, boolean) SET work_mem = '128MB'; + +-- 64MB — live value on the other six +ALTER FUNCTION public.match_document_chunks_text(text, integer, uuid[], uuid) SET work_mem = '64MB'; +ALTER FUNCTION public.match_document_chunks_text_v2(text, integer, uuid[], uuid, boolean) SET work_mem = '64MB'; +ALTER FUNCTION public.match_document_lookup_chunks_text(text, uuid[], integer, uuid) SET work_mem = '64MB'; +ALTER FUNCTION public.match_document_memory_cards_hybrid(extensions.vector, text, integer, double precision, uuid[], uuid) SET work_mem = '64MB'; +ALTER FUNCTION public.match_document_memory_cards_hybrid_v2(extensions.vector, text, integer, double precision, uuid[], uuid) SET work_mem = '64MB'; +ALTER FUNCTION public.match_document_table_facts_text(text, integer, uuid[], uuid) SET work_mem = '64MB'; diff --git a/supabase/migrations/20260818111000_codify_schema_only_indexes_and_triggers.sql b/supabase/migrations/20260818111000_codify_schema_only_indexes_and_triggers.sql new file mode 100644 index 0000000000..29365265de --- /dev/null +++ b/supabase/migrations/20260818111000_codify_schema_only_indexes_and_triggers.sql @@ -0,0 +1,60 @@ +-- Codify eight objects that supabase/schema.sql declares but no migration creates. +-- +-- Plan of record: docs/database-remediation-plan.md Phase 3 (reframed 2026-08-18); +-- evidence: docs/audit/live-drift-forensics-2026-08.md section 2.3 (b) — the +-- staging replay of the full committed chain (194/194, byte-verified) reported +-- these eight as declared in schema.sql with no creating and no dropping migration +-- anywhere in supabase/migrations/**. Ledger anchor #316. +-- +-- WHY: any environment built from migrations alone (staging, disaster-recovery +-- replay, `supabase db reset`) lacks them. Five are btree/GIN indexes on the +-- retrieval-path table document_embedding_fields, one is the plain documents.status +-- index, and two are set_updated_at() triggers whose absence silently stops +-- updated_at maintenance on documents and ingestion_jobs. +-- +-- Provenance: 20260702014803_drop_legacy_vector_indexes.sql dropped the legacy +-- embedding-fields btrees and named these five as the "live-kept equivalents ... +-- declared instead" in schema.sql — declared, but never given a migration. +-- +-- Definitions are verbatim from supabase/schema.sql (lines 678, 763-776, +-- 1073-1081) so the drift manifest, which replays schema.sql, is unchanged. +-- Not #102: that item's bare-column indexes (documents_title_bare_trgm_idx, +-- documents_file_name_bare_trgm_idx, documents_status_id_idx) are different +-- objects and stay canary-gated in their own work. +-- +-- Monitoring: all six indexes remain on supabase/search-health-unmonitored-indexes.json +-- (five accepted-unmonitored, document_embedding_fields_search_tsv_chunk_gin_idx a +-- monitor-candidate). search_schema_health() required_indexes is not changed here; +-- that decision travels with Phase 4.4 by its own migration. +-- +-- Dependencies: public.document_embedding_fields and all referenced columns from +-- 20260608001000; documents.status from 20260527000000; public.set_updated_at() +-- newest at 20260528009000. Ordered after 20260702014803. +-- +-- Idempotent on production (sjrfecxgysukkwxsowpy): all eight already exist there +-- (2026-08-14 live-drift reported no missing_live for these names, and no +-- unexpected trigger drift), so every statement is a no-op in the production window. + +create index if not exists documents_status_idx on public.documents(status); + +create index if not exists document_embedding_fields_owner_id_idx + on public.document_embedding_fields(owner_id); +create index if not exists document_embedding_fields_owner_document_created_idx + on public.document_embedding_fields(owner_id, document_id, created_at desc); +create index if not exists document_embedding_fields_source_chunk_id_idx + on public.document_embedding_fields(source_chunk_id); +create index if not exists document_embedding_fields_meta_rag_indexing_version_idx + on public.document_embedding_fields((metadata->>'rag_indexing_version')); +create index if not exists document_embedding_fields_search_tsv_chunk_gin_idx + on public.document_embedding_fields using gin(search_tsv) + where source_chunk_id is not null; + +drop trigger if exists documents_updated_at on public.documents; +create trigger documents_updated_at +before update on public.documents +for each row execute function public.set_updated_at(); + +drop trigger if exists ingestion_jobs_updated_at on public.ingestion_jobs; +create trigger ingestion_jobs_updated_at +before update on public.ingestion_jobs +for each row execute function public.set_updated_at(); diff --git a/supabase/migrations/20260818112000_reconcile_chain_stale_table_columns.sql b/supabase/migrations/20260818112000_reconcile_chain_stale_table_columns.sql new file mode 100644 index 0000000000..23f157ce20 --- /dev/null +++ b/supabase/migrations/20260818112000_reconcile_chain_stale_table_columns.sql @@ -0,0 +1,42 @@ +-- Reconcile three chain-stale table columns so a clean migration replay matches +-- supabase/schema.sql (and production) column-for-column. +-- +-- Plan of record: docs/database-remediation-plan.md Phase 3 (reframed 2026-08-18); +-- evidence: docs/audit/live-drift-forensics-2026-08.md section 2.3 (c), expanded per +-- column in section Phase 3 against a read-only staging snapshot. Ledger anchor #316. +-- In every case schema.sql is the correct side (it matches production: the +-- 2026-08-14 live-drift run reported no table drift) and is left unchanged; the +-- migration chain is what disagrees. Each statement is idempotent on production. +-- +-- 1. document_chunks.token_estimate — the chain builds 18 columns, schema.sql +-- declares 19: `token_estimate integer not null default 0` (schema.sql line 309) +-- is mentioned by zero migrations, yet the application writes it +-- (src/lib/chunking.ts, src/lib/registry-corpus.ts), the generated types require +-- it (src/lib/supabase/database.types.ts) and production carries it. A database +-- built from migrations alone would reject every chunk insert. On PostgreSQL 11+ +-- adding a column with a constant default is catalog-only (no table rewrite); +-- on production the column exists with this exact shape, so this is a no-op. +-- +-- 2/3. rag_visual_eval_cases.id and rag_visual_eval_runs.id defaults — +-- 20260705230000_reconcile_live_database_drift.sql opens with +-- `set search_path = public, extensions, pg_catalog;` (pg_catalog LAST), so its +-- bare `default gen_random_uuid()` bound to pgcrypto's +-- extensions.gen_random_uuid() rather than the core pg_catalog function. +-- schema.sql (search_path `public, extensions`, pg_catalog implicitly first) +-- and production bind the core function, which the drift snapshot renders as +-- `gen_random_uuid()`; the staging chain replay renders +-- `extensions.gen_random_uuid()`. Both generate v4 UUIDs — no behavioural +-- difference — but the column-set comparison is a real OID mismatch. Rebinding +-- to the schema-qualified core function is a no-op on production and makes the +-- chain agree with schema.sql. (indexing_v3_agent_jobs.id is NOT touched: +-- schema.sql itself declares extensions.gen_random_uuid() there, so chain, +-- mirror and live already agree.) + +alter table public.document_chunks + add column if not exists token_estimate integer not null default 0; + +alter table public.rag_visual_eval_cases + alter column id set default pg_catalog.gen_random_uuid(); + +alter table public.rag_visual_eval_runs + alter column id set default pg_catalog.gen_random_uuid(); diff --git a/supabase/schema.sql b/supabase/schema.sql index 4d53cfb9f0..035fa14a89 100644 --- a/supabase/schema.sql +++ b/supabase/schema.sql @@ -2769,6 +2769,7 @@ returns table ( language sql stable set search_path = public, extensions, pg_temp +set work_mem = '128MB' as $$ with query as ( select websearch_to_tsquery('english', coalesce(query_text, '')) as tsq @@ -2978,6 +2979,7 @@ returns table ( language sql stable set search_path = public, extensions, pg_temp +set work_mem = '64MB' as $$ with query as ( select websearch_to_tsquery('english', coalesce(query_text, '')) as tsq @@ -3091,6 +3093,7 @@ language plpgsql stable set search_path = public, extensions, pg_temp set plan_cache_mode = 'force_custom_plan' +set work_mem = '64MB' as $$ BEGIN PERFORM set_config('hnsw.ef_search', '100', true); @@ -3839,6 +3842,7 @@ returns table ( language sql stable set search_path = public, extensions, pg_temp +set work_mem = '64MB' as $$ with query as ( select websearch_to_tsquery('english', coalesce(query_text, '')) as tsq @@ -3995,6 +3999,7 @@ returns table ( language sql stable set search_path = public, extensions, pg_temp +set work_mem = '64MB' as $$ with query as ( select websearch_to_tsquery('english', coalesce(query_text, '')) as tsq @@ -4182,6 +4187,7 @@ language sql stable set search_path = public, extensions, pg_temp set plan_cache_mode = 'force_custom_plan' +set work_mem = '64MB' as $$ with query as ( select @@ -4283,6 +4289,7 @@ language sql stable set search_path = public, extensions, pg_temp set plan_cache_mode = 'force_custom_plan' +set work_mem = '128MB' as $$ with query as ( select websearch_to_tsquery('english', coalesce(query_text, '')) as tsq @@ -5491,6 +5498,7 @@ language sql stable set search_path = public, extensions, pg_temp set plan_cache_mode = 'force_custom_plan' +set work_mem = '128MB' as $$ with query as ( select websearch_to_tsquery('english', coalesce(query_text, '')) as tsq, @@ -6577,6 +6585,7 @@ CREATE OR REPLACE FUNCTION public.match_document_chunks_hybrid(query_embedding v LANGUAGE sql STABLE SET search_path TO 'public', 'extensions', 'pg_temp' + SET work_mem TO '128MB' AS $function$ with query as (select websearch_to_tsquery('english', coalesce(query_text, '')) as tsq), vector_ranked as ( @@ -6664,6 +6673,7 @@ CREATE OR REPLACE FUNCTION public.match_document_table_facts_text(query_text tex STABLE SET search_path TO 'public', 'extensions', 'pg_temp' SET plan_cache_mode TO 'force_custom_plan' + SET work_mem TO '64MB' AS $function$ begin return query execute $body$ @@ -7809,6 +7819,7 @@ returns table ( language sql stable set search_path = public, extensions, pg_temp +set work_mem = '64MB' as $$ select * from public.match_document_chunks_text_scoped( @@ -7992,6 +8003,7 @@ returns table ( language sql stable set search_path = public, extensions, pg_temp +set work_mem = '128MB' as $$ select * from public.match_document_index_units_hybrid_scoped( diff --git a/supabase/search-health-unmonitored-indexes.json b/supabase/search-health-unmonitored-indexes.json index b0d9b5e1dc..41d8acb554 100644 --- a/supabase/search-health-unmonitored-indexes.json +++ b/supabase/search-health-unmonitored-indexes.json @@ -59,7 +59,7 @@ "index": "documents_status_idx", "table": "documents", "disposition": "accepted-unmonitored", - "reason": "Plain status index declared only in schema.sql (never by a migration — drift backlog item 10, migration-chain fidelity); operational status scans" + "reason": "Plain status index, codified into the chain by 20260818111000 (was schema.sql-only — drift backlog item 10, forensics 2.3 b); operational status scans" }, { "index": "document_chunks_anchor_idx", @@ -119,31 +119,31 @@ "index": "document_embedding_fields_meta_rag_indexing_version_idx", "table": "document_embedding_fields", "disposition": "accepted-unmonitored", - "reason": "Schema.sql-only version-tag scan index for reindex campaigns (drift backlog item 10); operational" + "reason": "Version-tag scan index for reindex campaigns, codified into the chain by 20260818111000 (was schema.sql-only — drift backlog item 10); operational" }, { "index": "document_embedding_fields_owner_document_created_idx", "table": "document_embedding_fields", "disposition": "accepted-unmonitored", - "reason": "Schema.sql-only owner+document+created listing index (drift backlog item 10); the monitored document_embedding_fields_owner_chunk_idx covers the retrieval owner predicate" + "reason": "Owner+document+created listing index, codified into the chain by 20260818111000 (was schema.sql-only — drift backlog item 10); the monitored document_embedding_fields_owner_chunk_idx covers the retrieval owner predicate" }, { "index": "document_embedding_fields_owner_id_idx", "table": "document_embedding_fields", "disposition": "accepted-unmonitored", - "reason": "Schema.sql-only rename target of the dropped document_embedding_fields_owner_idx (drift backlog item 10); owner FK support, superseded for retrieval by document_embedding_fields_owner_chunk_idx" + "reason": "Rename target of the dropped document_embedding_fields_owner_idx, codified into the chain by 20260818111000 (was schema.sql-only — drift backlog item 10); owner FK support, superseded for retrieval by document_embedding_fields_owner_chunk_idx" }, { "index": "document_embedding_fields_search_tsv_chunk_gin_idx", "table": "document_embedding_fields", "disposition": "monitor-candidate", - "reason": "Schema.sql-only GIN over the embedding-field search tsvector (drift backlog item 10); lexical field retrieval path — decide required_indexes membership by migration once its live status is confirmed" + "reason": "GIN over the embedding-field search tsvector, codified into the chain by 20260818111000 (was schema.sql-only — drift backlog item 10); lexical field retrieval path — decide required_indexes membership by migration in Phase 4.4 (present on production per the 2026-08-14 live-drift run)" }, { "index": "document_embedding_fields_source_chunk_id_idx", "table": "document_embedding_fields", "disposition": "accepted-unmonitored", - "reason": "Schema.sql-only source_chunk_id FK support (drift backlog item 10); join support for cleanup, not retrieval matching" + "reason": "source_chunk_id FK support, codified into the chain by 20260818111000 (was schema.sql-only — drift backlog item 10); join support for cleanup, not retrieval matching" }, { "index": "document_index_units_chunk_idx", From 7806ebada0e6f51d96351514f9f5057a2faedb08 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:21:27 +0800 Subject: [PATCH 2/5] docs(ledger): review record for PR #2106 (Phase 3 codification) Co-Authored-By: Claude Fable 5 --- ...4eb5d39a6292bfe60f94f940fdc0c272877227a7de151077d5a.record.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/branch-review-records/e7988c8069a294eb5d39a6292bfe60f94f940fdc0c272877227a7de151077d5a.record.md diff --git a/docs/branch-review-records/e7988c8069a294eb5d39a6292bfe60f94f940fdc0c272877227a7de151077d5a.record.md b/docs/branch-review-records/e7988c8069a294eb5d39a6292bfe60f94f940fdc0c272877227a7de151077d5a.record.md new file mode 100644 index 0000000000..1191fb13a9 --- /dev/null +++ b/docs/branch-review-records/e7988c8069a294eb5d39a6292bfe60f94f940fdc0c272877227a7de151077d5a.record.md @@ -0,0 +1 @@ +| 2026-08-18 | claude/schema-work-mem-codify-6200f1 | cc46d8164537306436a42fdfcf4116c0b56e0449 | db remediation Phase 3 (reframed): codify live SET work_mem on ten match_* RPCs, eight schema-only objects, three chain-stale columns; forensics section Phase 3; #316 + #183 inbox requests (PR #2106) | Reviewed and handed off; no function body changed; ten manifest def_hash equal live production hashes 10/10; staging proof deliberately not run (Phase 2 re-measure absent on main); production window listed in PR body | drift:manifest 75s; check:migration-role passed; vitest 8 schema/drift files 149 passed; verify:pr-local all stages green except test (3 red: session-start-hook env + two host-load timeouts, both pass in isolation 182/182); check:rag:fixtures 36/26 passed; medication checks up to date; pr-policy ok clinicalRisk preflight satisfied | From 317154229ac3b8ea67e85585b2d291f4b99728a1 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:32:41 +0800 Subject: [PATCH 3/5] =?UTF-8?q?docs(issues):=20queue=20two=20Phase=203=20f?= =?UTF-8?q?ollow-ups=20=E2=80=94=20check:drift=20column-diff=20clip,=20PR?= =?UTF-8?q?=20template=20RAG=20impact=20guidance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .../565cf4ff-5aa6-456b-abfc-92c4765049e4.json | 14 ++++++++++++++ .../a42b6382-9e7c-4633-9180-c86d03ad0bf1.json | 14 ++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 docs/outstanding-issues-inbox/565cf4ff-5aa6-456b-abfc-92c4765049e4.json create mode 100644 docs/outstanding-issues-inbox/a42b6382-9e7c-4633-9180-c86d03ad0bf1.json diff --git a/docs/outstanding-issues-inbox/565cf4ff-5aa6-456b-abfc-92c4765049e4.json b/docs/outstanding-issues-inbox/565cf4ff-5aa6-456b-abfc-92c4765049e4.json new file mode 100644 index 0000000000..135482f5a3 --- /dev/null +++ b/docs/outstanding-issues-inbox/565cf4ff-5aa6-456b-abfc-92c4765049e4.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "565cf4ff-5aa6-456b-abfc-92c4765049e4", + "createdOn": "2026-08-18", + "action": "add", + "payload": { + "pri": "P3", + "type": "rec", + "summary": "check:drift clips table column diffs to 240 chars per side, so a wide-table column drift never names the column", + "detail": "scripts/check-drift.ts:192 (fieldDiff) serialises the whole alphabetised columns array of a table and clips each side to 240 characters, so for a wide table such as document_chunks (19 columns, several kB) a single late-alphabet column drift (token_estimate, forensics Phase 3 §3.3) prints two identical prefixes and the finding fires without naming the column. Phase 3 needed a raw schema_drift_snapshot() read and an offline per-column diff to classify three staging table findings. Recommendation: for the columns field, diff per column name (only-in-manifest / only-in-live / differing fields) and print those rows instead of the clipped arrays; keep the clip for other fields. Offline-testable against supabase/drift-manifest.json plus a mutated copy.", + "source": "session 2026-08-18 Phase 3 repo-side codification (PR #2106)", + "issueUlid": "01M0A3BTJF90EVWZMKG18KDC2S" + } +} diff --git a/docs/outstanding-issues-inbox/a42b6382-9e7c-4633-9180-c86d03ad0bf1.json b/docs/outstanding-issues-inbox/a42b6382-9e7c-4633-9180-c86d03ad0bf1.json new file mode 100644 index 0000000000..0447b91a34 --- /dev/null +++ b/docs/outstanding-issues-inbox/a42b6382-9e7c-4633-9180-c86d03ad0bf1.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "a42b6382-9e7c-4633-9180-c86d03ad0bf1", + "createdOn": "2026-08-18", + "action": "add", + "payload": { + "pri": "P3", + "type": "rec", + "summary": "PR template carries no RAG impact: guidance although pr-policy hard-blocks RAG-surface PRs without the line", + "detail": ".github/pull_request_template.md has zero occurrences of 'RAG impact', yet scripts/pr-policy.mjs ragImpactDeclared (lines 238-245) hard-blocks any PR touching a RAG-ranking-surface path unless the body carries a line matching 'RAG impact: ' with 'no ... behaviour change' or 'canary' and at least 12 characters. The authoring rule lives only in the pr-policy error string and AGENTS.md. Recommendation: add a commented placeholder line under ## Risk and rollout (or a dedicated ## RAG impact stanza) in the template with both canonical forms, so the exact-format contract is visible where the body is written; guard with the existing pr-policy self-test.", + "source": "session 2026-08-18 Phase 3 repo-side codification (PR #2106)", + "issueUlid": "01M0A3CDWM5JK9FMW032CEPGGN" + } +} From 3e73b8bd43a689b9c6f8c83b9cbfddede0bb231b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 09:49:37 +0000 Subject: [PATCH 4/5] fix(db): close guard-migration loophole and add lock timeouts for PR #2106 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of the Phase 3 codification migrations found the forensics runbook offered mark-applied-by-CLI as an equal alternative to db push for the three new migrations, which would violate the guard-migration contract in AGENTS.md since none of them ships a validation guard. Narrow the runbook to require db push (real execution) and forbid migration repair for these versions. Also add SET LOCAL lock_timeout / statement_timeout to the two migrations that take ACCESS EXCLUSIVE locks on hot tables (documents, ingestion_jobs, document_chunks), matching the existing 20260804110240 guard pattern, so a production window fails fast instead of queueing behind a long transaction. No schema.sql change, no function body change, no live Supabase access. RAG impact: no retrieval behaviour change — same scope as the parent PR (SET work_mem attribute codification only; this commit only tightens the production-application runbook and adds lock timeouts to unrelated schema codification migrations). --- docs/audit/live-drift-forensics-2026-08.md | 16 ++++++++++++---- ...0_codify_schema_only_indexes_and_triggers.sql | 8 ++++++++ ...12000_reconcile_chain_stale_table_columns.sql | 9 +++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/docs/audit/live-drift-forensics-2026-08.md b/docs/audit/live-drift-forensics-2026-08.md index ecc906e327..1d23a13a18 100644 --- a/docs/audit/live-drift-forensics-2026-08.md +++ b/docs/audit/live-drift-forensics-2026-08.md @@ -870,7 +870,12 @@ run `32051068106`, 2026-08-17 — the same values §1.2 Query 1 recorded): 10/10 equal. Because `check:drift` compares functions on `def_hash` + `acl` and ACLs were already equal (§1.2), the next production live-drift run will report **zero `match_*` function mismatches** -without any hosted change — the migration only needs to be marked applied in a production window. +once this migration runs in a production window. That window must run `supabase db push` (or +equivalent verbatim execution of the ten idempotent `ALTER FUNCTION` statements) — not +`supabase migration repair --status applied` or any other history-only mark-applied path. Per +AGENTS.md "Supabase project safety", a mark-applied version requires a fail-fast validation guard +migration shipped in the same change (the `20260804110240` pattern); this PR ships no such guard, +so `migration repair` on this version is out of scope for the coordinator. Eval-canary: none dispatched (D2 — planner memory, latency-only; no live eval in this task). ### 3.2 Eight never-created objects — codified verbatim (`20260818111000`) @@ -980,8 +985,11 @@ exactly one named residual — `document_chunks_content_trgm_idx` (`c3db2960…` ### 3.6 Production window (NOT authorised in this task — for the coordinator) -Live state already matches for everything in this phase, so the window is mark-applied-by-CLI / -`db push` of already-true statements, plus the one real change from Phase 6.1: +Live state already matches for everything in this phase, so applying each migration is a no-op on +production. The window must still run `supabase db push` (verbatim execution of each migration's +statements) — **not** `supabase migration repair --status applied` or any other mark-applied-only +path. None of the three Phase 3 migrations ships a validation guard, so per AGENTS.md "Supabase +project safety" none of them is eligible for history repair; only real execution is authorised here. | Migration | Effect on production | | -------------------------------------------------------- | ---------------------------------------------------------- | @@ -990,7 +998,7 @@ Live state already matches for everything in this phase, so the window is mark-a | `20260818111000_codify_schema_only_indexes_and_triggers` | no-op — all eight objects already exist (3.2) | | `20260818112000_reconcile_chain_stale_table_columns` | no-op — column and both defaults already as declared (3.3) | -One window covers all four; none needs a canary (D2) and none builds an index. +One window covers all four via `db push`; none needs a canary (D2) and none builds an index. ## Phase 4 — Index restoration diff --git a/supabase/migrations/20260818111000_codify_schema_only_indexes_and_triggers.sql b/supabase/migrations/20260818111000_codify_schema_only_indexes_and_triggers.sql index 29365265de..aefb81d08b 100644 --- a/supabase/migrations/20260818111000_codify_schema_only_indexes_and_triggers.sql +++ b/supabase/migrations/20260818111000_codify_schema_only_indexes_and_triggers.sql @@ -34,6 +34,14 @@ -- Idempotent on production (sjrfecxgysukkwxsowpy): all eight already exist there -- (2026-08-14 live-drift reported no missing_live for these names, and no -- unexpected trigger drift), so every statement is a no-op in the production window. +-- +-- Timeouts use SET LOCAL so they do not leak into later migrations applied on the +-- same CLI session connection. The trigger DDL below takes ACCESS EXCLUSIVE on +-- documents and ingestion_jobs (both hot tables); a bounded lock_timeout makes the +-- production window fail fast instead of queueing behind a long-running transaction. + +set local lock_timeout = '5s'; +set local statement_timeout = '30s'; create index if not exists documents_status_idx on public.documents(status); diff --git a/supabase/migrations/20260818112000_reconcile_chain_stale_table_columns.sql b/supabase/migrations/20260818112000_reconcile_chain_stale_table_columns.sql index 23f157ce20..ac2c6466f3 100644 --- a/supabase/migrations/20260818112000_reconcile_chain_stale_table_columns.sql +++ b/supabase/migrations/20260818112000_reconcile_chain_stale_table_columns.sql @@ -31,6 +31,15 @@ -- chain agree with schema.sql. (indexing_v3_agent_jobs.id is NOT touched: -- schema.sql itself declares extensions.gen_random_uuid() there, so chain, -- mirror and live already agree.) +-- +-- Timeouts use SET LOCAL so they do not leak into later migrations applied on the +-- same CLI session connection. document_chunks is a hot ingestion-path table; a +-- bounded lock_timeout makes the production window fail fast instead of queueing +-- behind a long-running transaction for the brief ACCESS EXCLUSIVE these +-- catalog-only ALTER statements take. + +set local lock_timeout = '5s'; +set local statement_timeout = '30s'; alter table public.document_chunks add column if not exists token_estimate integer not null default 0; From 63c3e86953d3eacf2e210f495b89a2e085440e1f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 09:50:09 +0000 Subject: [PATCH 5/5] docs(ledger): review record for PR #2106 fix commit Records the supabase-schema-guardian review of the Phase 3 codification migrations and the follow-up fix commit at the correct HEAD. --- ...aac89158b14059d7ea3c97f584cd8555ccb73d251d72ed9845e.record.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/branch-review-records/094e8208cf8f6aac89158b14059d7ea3c97f584cd8555ccb73d251d72ed9845e.record.md diff --git a/docs/branch-review-records/094e8208cf8f6aac89158b14059d7ea3c97f584cd8555ccb73d251d72ed9845e.record.md b/docs/branch-review-records/094e8208cf8f6aac89158b14059d7ea3c97f584cd8555ccb73d251d72ed9845e.record.md new file mode 100644 index 0000000000..3a0599c36a --- /dev/null +++ b/docs/branch-review-records/094e8208cf8f6aac89158b14059d7ea3c97f584cd8555ccb73d251d72ed9845e.record.md @@ -0,0 +1 @@ +| 2026-08-18 | claude/schema-work-mem-codify-6200f1 | 3e73b8bd43a689b9c6f8c83b9cbfddede0bb231b | supabase migrations, schema.sql (PR #2106 Phase 3 codification review) | Reviewed via supabase-schema-guardian: signatures, ordering, idempotency, schema.sql/migration consistency, RAG-impact claim, and guard-migration contract all verified correct against the diff. Fixed one concern (forensics runbook offered mark-applied-by-CLI as equal to db push for three migrations shipping no validation guard — narrowed to require db push and forbid migration repair) and added SET LOCAL lock_timeout/statement_timeout to the two migrations taking ACCESS EXCLUSIVE locks on hot tables (documents, ingestion_jobs, document_chunks), matching the 20260804110240 pattern. No schema.sql or function-body change from this review; no live Supabase access. | check:migration-role passed; vitest tests/migration-history-guards+hosted-migration-role-guard+supabase-schema+drift-detection+search-health-index-coverage+guard-push.test.ts 141/141 passed; prettier --check on the edited doc passed; npm run format whole-tree no-op; drift:manifest Docker replay not run (Docker daemon unavailable in this sandbox) — CI Migration replay + Unit coverage + Static PR checks + PR required all green on the prior head and these edits are additive SQL/prose only |