From 3ebe8026293a942ef7d646bae7aef87edb6eca88 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 05:56:05 +0000 Subject: [PATCH 1/4] ci(lexicon): gate the generated lexicon report, and correct the remediation records MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups from the Phase 0 close-out review. check:medication-lexicon-report ran in the local verify:pr-local chain but in no CI job at all — a grep across .github/workflows returned nothing. It is the last step of the local chain, so it failed every local preflight while every required check stayed green, which is how the generated file rotted on main until three independent branches tripped over it. That is the open half of #333. It now runs in static-pr under the same scope.static_heavy_changed condition verify-pr-local.mjs already uses, so CI and the local chain agree. The plan and playbook still described the 2026-08-09 world. Corrected to the 2026-08-14 measurement from dispatch run 31813064485: 10 RPC def_hash mismatches (unchanged), 20 missing_live indexes rather than 21, 2 unexpected_live (unchanged), with documents_title_trgm_idx and document_chunks_content_trgm_idx no longer among the missing. Phase 0 is marked complete, Phase 1 partial with 1.2 (the RPC dossier) called out as the gate for Phase 3, and Phase 4 partial. Phase 3 is named as the next step. Both documents now say to check the open-PR list for the surface before starting a phase (#292): Phase 0 was built twice independently on 2026-08-14, and a duplicate in Phase 3 or 4 spends an approved production window and eval-canary budget rather than just tokens. The ledger row title is quoted verbatim in the playbook and still contains "21" on purpose — the playbook resolves the tracking anchor by exact title, so editing the quote would break that lookup. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BKNFogaYfCQBvFVqFQnfRt --- .github/workflows/ci.yml | 8 ++++++ docs/database-remediation-plan.md | 38 ++++++++++++++++++++------- docs/database-remediation-playbook.md | 38 +++++++++++++++++++-------- 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad4aa2ba01..5e76a606ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,6 +188,14 @@ jobs: if: needs.changes.outputs.static_heavy_changed == 'true' run: npm run check:upload-limit-parity + # The generated lexicon review is a clinical-facing artefact. It was in the + # local verify:pr-local chain but in no CI job, so it rotted on main and + # failed every local preflight while every required check stayed green + # (#333). Same static-heavy condition verify-pr-local.mjs already uses. + - name: Medication lexicon report freshness + if: needs.changes.outputs.static_heavy_changed == 'true' + run: npm run check:medication-lexicon-report + - name: GitHub Actions pin check if: needs.changes.outputs.workflow_changed == 'true' run: npm run check:github-actions diff --git a/docs/database-remediation-plan.md b/docs/database-remediation-plan.md index 9bb9a18561..977c30fba6 100644 --- a/docs/database-remediation-plan.md +++ b/docs/database-remediation-plan.md @@ -3,14 +3,34 @@ Owner: operator (Josh) + specialist session. Source findings: open ledger `#248` (whose causal conclusion awaits a read-only history and audit check) and the queued P1 live-drift follow-up. Companion evidence: live-drift Actions runs -`30763871562` (2026-08-02) and -`31330856982` (2026-08-09), PR #1614, `supabase/migrations/20260804110240_restore_rag_search_health_indexes.sql`. - -**Scope.** Fixes, in dependency order: the live-vs-repo schema gap (21 missing indexes, 2 -unexpected indexes, 10 diverged `match_*` RPC bodies), the unresolved cause of the affected -migration history, drift-detection routing, and the surrounding database debt (`#102`, `#011`, `#036`, -`#022`, `#025`, `#056`/`#057`, `#183`, `#188`/`#196`–`#200`, `#191`, `#098`/`#099`). Ends with -standing protections so unverified history repairs remain visible and cannot silently mask drift. +`30763871562` (2026-08-02), `31330856982` (2026-08-09) and +`31813064485` (2026-08-14, the current measurement), PR #1614, +`supabase/migrations/20260804110240_restore_rag_search_health_indexes.sql`. + +> **Status as of 2026-08-14.** Phase 0 is **complete** (routing, post-migration trigger, evidence +> file, and the forced-dispatch proof — which auto-created issue **#1963**). Phase 1 is **partial**: +> 1.1 and 1.3 were run in an owner-authorised incident window, but **1.2, the RPC divergence +> dossier, is outstanding and is the gate for Phase 3**. Phase 4 is **partial**: the two +> retrieval-critical trigram indexes were restored, ~20 remain. +> +> **Next step is Phase 3, not Phase 1.** Before starting it — or any later phase — check the open-PR +> list for the surface first (`#292`): Phase 0 was independently built twice on 2026-08-14, and +> Phases 3 and 4 spend approved production windows and eval-canary budget rather than just tokens. + +**Scope.** Fixes, in dependency order: the live-vs-repo schema gap, the unresolved cause of the +affected migration history, drift-detection routing, and the surrounding database debt (`#102`, +`#011`, `#036`, `#022`, `#025`, `#056`/`#057`, `#183`, `#188`/`#196`–`#200`, `#191`, `#098`/`#099`). +Ends with standing protections so unverified history repairs remain visible and cannot silently mask +drift. + +The schema gap, measured 2026-08-14 (superseding the 2026-08-09 figures this plan was written +against): + +| Category | 2026-08-09 | 2026-08-14 | +| ----------------------------- | ---------- | ------------------ | +| diverged `match_*` RPC bodies | 10 | **10 — unchanged** | +| `missing_live` indexes | 21 | **20** | +| `unexpected_live` indexes | 2 | **2 — unchanged** | **Standing rules for every phase.** No hosted mutation without explicit approval for that phase. Never raw-SQL a drift fix — every live change is codified (migration + `schema.sql` mirror + @@ -63,7 +83,7 @@ hypothesis into a named, evidence-backed conclusion. noise** (whitespace/qualifier only). No edits. This is the highest-stakes unknown in the whole plan — live retrieval behaviour may currently depend on bodies the repo does not contain. -1.3 **Index inventory & sizing.** For the 21 missing and 2 unexpected indexes: owning-table +1.3 **Index inventory & sizing.** For the 20 missing and 2 unexpected indexes: owning-table `pg_relation_size`, and `EXPLAIN (ANALYZE, BUFFERS)` for the known hot queries (`documents` title ILIKE, `document_chunks` content search, `rag_retrieval_logs` miss scan). These are the before-measurements for Phase 4, including the `#231` latency hypothesis. diff --git a/docs/database-remediation-playbook.md b/docs/database-remediation-playbook.md index d9d089aac1..ac126e6afc 100644 --- a/docs/database-remediation-playbook.md +++ b/docs/database-remediation-playbook.md @@ -19,19 +19,31 @@ migrations show that history advanced, but neither point distinguishes skipped D history from indexes that were created and later dropped. The Phase 1 read-only history and audit check must establish that cause before the row is closed or remediation is attributed to it. -**Current live state** (from scheduled `live-drift.yml` Actions run `31330856982`, 2026-08-09): +**Current live state** (from `live-drift.yml` Actions run `31813064485`, 2026-08-14 — this +supersedes the 2026-08-09 run `31330856982` the plan was originally written against): -- 21 `missing_live` indexes across many migrations — tables: `audit_logs` (2), `api_rate_limits`, - `document_chunks` (2, incl. `document_chunks_content_trgm_idx`), `document_images` (3), - `document_index_quality`, `document_index_units`, `document_publication_approvals`, - `document_summaries`, `documents` (2, incl. `documents_title_trgm_idx`), +- 20 `missing_live` indexes across many migrations — tables: `audit_logs` (2), `api_rate_limits`, + `document_chunks`, `document_images` (3), `document_index_quality`, `document_index_units`, + `document_publication_approvals`, `document_summaries`, `documents`, `image_caption_cache`, `indexing_v3_agent_jobs`, `ingestion_job_stages`, `medication_records`, `rag_aliases`, `rag_queries`, `rag_query_misses`, `storage_cleanup_jobs`. + **`documents_title_trgm_idx` and `document_chunks_content_trgm_idx` are no longer among them** — + both were restored in the 2026-08-14 incident window and re-verified `indisvalid`/`indisready`. - 2 `unexpected_live` indexes: `document_table_facts_document_id_idx`, `storage_cleanup_jobs_owner_id_idx`. -- `def_hash` mismatches on 10 `match_*` retrieval RPCs (protected RAG surface; live bodies vs - repo — direction unknown until diffed). -- The weekly `live-drift` run has been red since 2026-07-26 with no notification routing. +- `def_hash` mismatches on 10 `match_*` retrieval RPCs — **unchanged, and entirely outstanding** + (protected RAG surface; live bodies vs repo — direction unknown until diffed). This is now the + highest-stakes remaining unknown and the reason Phase 3 comes next. +- Drift-failure routing is **live**: a failed run creates or updates the pinned issue + "Live drift check failing" (currently **#1963**) and a green run closes it. + +**Phase status.** Phase 0 complete. Phase 1 partial — 1.1 and 1.3 done in the owner-authorised +incident window, **1.2 (RPC dossier) outstanding and gating Phase 3**. Phase 4 partial — two +indexes restored, ~20 remain. **Next: Phase 3.** + +**Before starting any phase, check the open-PR list for the surface** (`#292`). Phase 0 was built +twice independently on 2026-08-14; a duplicate in Phase 3 or 4 wastes an approved production window +and eval-canary budget, not just tokens. **Prior repair to imitate.** PR #1614 / migration `20260804110240_restore_rag_search_health_indexes.sql` is the approved pattern: operator prebuilds indexes with `CREATE INDEX CONCURRENTLY` outside any @@ -70,7 +82,10 @@ mistakes. Opus is sufficient for Phases 0, 2, 4, 5, 7 — execution against this --- -## Phase 0 — Enablement (repo-only; no approval window needed) · Opus · 2–4 h +## Phase 0 — Enablement (repo-only; no approval window needed) · Opus · 2–4 h — **COMPLETE 2026-08-14** + +Delivered in PRs #1938, #1939 and #1951; the forced-dispatch proof is Actions run `31813064485`, +which auto-created issue #1963. The prompt below is retained as history. Deliverables: drift-failure routing, post-migration drift trigger, evidence file scaffold. Definition of done: PR merged; `check:github-actions` and `verify:pr-local` green; a forced @@ -119,7 +134,7 @@ conclusion for `#248`; no writes performed. > classify each as live-ahead, repo-ahead, or normalization noise, quoting the decisive diff hunks > — this is a protected RAG surface, so classification accuracy matters more than speed, and any > ambiguous diff is recorded as UNCLASSIFIED with the ambiguity explained, never guessed; (3) for -> the 21 missing and 2 unexpected indexes, record owning-table pg_relation_size and run EXPLAIN +> the 20 missing and 2 unexpected indexes, record owning-table pg_relation_size and run EXPLAIN > (ANALYZE, BUFFERS) for the documents title ILIKE query, the document_chunks content search, and > the rag_retrieval_logs miss scan as before-baselines. Write all evidence with dates and run IDs > into docs/audit/live-drift-forensics-2026-08.md, update the live-drift tracking item, commit, push, PR (docs-only; @@ -169,7 +184,8 @@ allowlist entries); eval evidence attached for any behaviour-changing deploy. ## Phase 4 — Index restoration (approved off-peak production window) · Opus · 2–3 h active -Prerequisites: Phases 1–3. Deliverables: 21 indexes restored + validated, 2 unexpected indexes +Prerequisites: Phases 1–3. Deliverables: the ~20 still-missing indexes restored + validated (the two +trigram indexes were already restored on 2026-08-14), 2 unexpected indexes dispositioned, guard migrations landed, live-drift green. Definition of done: green live-drift dispatch output pasted; `search_schema_health()` still `ok: true`. From d793fae759fc4957e6ae89dc869c0e6fcb6f7e55 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 05:57:36 +0000 Subject: [PATCH 2/4] =?UTF-8?q?docs(issues):=20close=20#333=20=E2=80=94=20?= =?UTF-8?q?the=20lexicon=20gate=20now=20runs=20in=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge-safe inbox request; docs/outstanding-issues.md is untouched and is reconciled separately after this PR lands. Both halves of the row are addressed: the regeneration in #1951 and the CI wiring in this PR. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BKNFogaYfCQBvFVqFQnfRt --- .../dc19c854-4eab-48dc-b0d9-a1a4e6015389.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docs/outstanding-issues-inbox/dc19c854-4eab-48dc-b0d9-a1a4e6015389.json diff --git a/docs/outstanding-issues-inbox/dc19c854-4eab-48dc-b0d9-a1a4e6015389.json b/docs/outstanding-issues-inbox/dc19c854-4eab-48dc-b0d9-a1a4e6015389.json new file mode 100644 index 0000000000..79fb7848f8 --- /dev/null +++ b/docs/outstanding-issues-inbox/dc19c854-4eab-48dc-b0d9-a1a4e6015389.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "id": "dc19c854-4eab-48dc-b0d9-a1a4e6015389", + "createdOn": "2026-08-15", + "action": "done", + "payload": { + "id": "#333", + "outcome": "Both halves closed. The regeneration landed in PR #1951 (pure re-wrap, whitespace-normalised byte-identical across all 28 catalogue terms, so no clinical review was implicated). The real question this row exposed — a gate in verify:pr-local but in no CI job — is answered in PR #1979: check:medication-lexicon-report now runs in static-pr under the same scope.static_heavy_changed condition verify-pr-local.mjs uses, so CI and the local chain agree and the file cannot silently rot again. The sibling diagnosis in #331 was closed separately: it was not a staleness-comparison bug, it was a genuinely stale committed file that prettier could not flag because proseWrap: preserve accepts both wrappings." + } +} From 9278f9615536d6ff57724fd2d6379bfa393e97b6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 05:58:23 +0000 Subject: [PATCH 3/4] =?UTF-8?q?docs(issues):=20withdraw=20the=20#333=20clo?= =?UTF-8?q?se=20request=20=E2=80=94=20the=20row=20was=20already=20archived?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #333 was closed by another session while this branch was in flight, so the queued done request was invalid and made applyRequestBatch throw with "#333 is already archived", red-lining docs:check-links for this branch. Removing the request rather than cancelling it is the correct mechanism here: the file only ever existed on this branch and never reached main, so its removal nets to zero against the write-discipline guard's base. Verified: ledger write discipline passes and docs:check-links is green again. My pre-check was too weak and is worth recording. I checked that no competing mutation targeted #333 and that a #333 row existed, but the existence grep matched the ARCHIVED row. The check that mattered was whether the row was still in the open table. The substance is unaffected: the CI wiring in this PR is what #333 asked for, and the row's own closure note already records it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BKNFogaYfCQBvFVqFQnfRt --- .../dc19c854-4eab-48dc-b0d9-a1a4e6015389.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 docs/outstanding-issues-inbox/dc19c854-4eab-48dc-b0d9-a1a4e6015389.json diff --git a/docs/outstanding-issues-inbox/dc19c854-4eab-48dc-b0d9-a1a4e6015389.json b/docs/outstanding-issues-inbox/dc19c854-4eab-48dc-b0d9-a1a4e6015389.json deleted file mode 100644 index 79fb7848f8..0000000000 --- a/docs/outstanding-issues-inbox/dc19c854-4eab-48dc-b0d9-a1a4e6015389.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": 1, - "id": "dc19c854-4eab-48dc-b0d9-a1a4e6015389", - "createdOn": "2026-08-15", - "action": "done", - "payload": { - "id": "#333", - "outcome": "Both halves closed. The regeneration landed in PR #1951 (pure re-wrap, whitespace-normalised byte-identical across all 28 catalogue terms, so no clinical review was implicated). The real question this row exposed — a gate in verify:pr-local but in no CI job — is answered in PR #1979: check:medication-lexicon-report now runs in static-pr under the same scope.static_heavy_changed condition verify-pr-local.mjs uses, so CI and the local chain agree and the file cannot silently rot again. The sibling diagnosis in #331 was closed separately: it was not a staleness-comparison bug, it was a genuinely stale committed file that prettier could not flag because proseWrap: preserve accepts both wrappings." - } -} From 4407faa6762f5f0798efc93d9c7761c141bfcb21 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 15 Aug 2026 14:43:30 +0800 Subject: [PATCH 4/4] docs: record lexicon CI review --- ...c376d150342864090de1d5f90e53c3187b03f4165df702e9b5d.record.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/branch-review-records/d50a877214261c376d150342864090de1d5f90e53c3187b03f4165df702e9b5d.record.md diff --git a/docs/branch-review-records/d50a877214261c376d150342864090de1d5f90e53c3187b03f4165df702e9b5d.record.md b/docs/branch-review-records/d50a877214261c376d150342864090de1d5f90e53c3187b03f4165df702e9b5d.record.md new file mode 100644 index 0000000000..a4f7f63491 --- /dev/null +++ b/docs/branch-review-records/d50a877214261c376d150342864090de1d5f90e53c3187b03f4165df702e9b5d.record.md @@ -0,0 +1 @@ +| 2026-08-15 | claude/ledger-guard-ci-followups | 8fe2a50b9bce29e24710fdf72ce8b5f187c0569e | Lexicon-report CI freshness and database remediation sequencing | Fixed P1 prerequisite order and P2 generated-report scope gap; merged current base with no conflicts | git diff --check; ci-change-scope direct-report classification and self-test; GitHub Actions pin check; remediation-order/workflow-contract assertions; ledger-inbox; outstanding-issues; branch-review-ledger; ledger-write-discipline; focused Vitest unavailable (node_modules absent) |