From f86ae205a9a05ff4377e9c8b0814039e08f16342 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 9 Jul 2026 00:26:54 +0800 Subject: [PATCH 01/16] feat: add image generation metadata re-stamp script --- package.json | 1 + scripts/reindex-image-generation-metadata.ts | 247 +++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 scripts/reindex-image-generation-metadata.ts diff --git a/package.json b/package.json index 987bcba5a..e44f6bc8a 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "reindex": "tsx scripts/reindex.ts", "reindex:health": "tsx scripts/reindex-health.ts", "reindex:cleanup-staged": "tsx scripts/cleanup-abandoned-reindex-generations.ts", + "images:re-stamp-generation": "tsx scripts/reindex-image-generation-metadata.ts", "supabase:recovery-status": "tsx scripts/supabase-recovery-status.ts", "promote:query-misses": "tsx scripts/promote-query-misses.ts", "promote:public-documents": "tsx scripts/promote-public-documents.ts", diff --git a/scripts/reindex-image-generation-metadata.ts b/scripts/reindex-image-generation-metadata.ts new file mode 100644 index 000000000..a537f1487 --- /dev/null +++ b/scripts/reindex-image-generation-metadata.ts @@ -0,0 +1,247 @@ +import { loadEnvConfig } from "@next/env"; +import { confirm } from "./cli-utils"; +import { committedIndexGeneration, metadataRecord } from "@/lib/reindex-pipeline"; +import { assertSupabaseHealthy, probeSupabaseHealth } from "@/lib/supabase/health"; +import { checkSupabaseProjectConfig, formatSupabaseProjectCheck } from "@/lib/supabase/project"; +import { createAdminClient } from "@/lib/supabase/admin"; +import { requireServerEnv } from "@/lib/env"; + +loadEnvConfig(process.cwd()); + +type Args = { + documentId: string | null; + ownerId: string | null; + allOwners: boolean; + limit: number; + write: boolean; + confirm: boolean; +}; + +type DocumentRow = { + id: string; + title: string | null; + file_name: string | null; + metadata: unknown; +}; + +type ImageRow = { + id: string; + index_generation_id: string | null; + metadata: unknown; +}; + +type ImagePatch = { + imageId: string; + indexGenerationId: string; + metadata: Record; +}; + +function parseArgs(argv: string[]): Args { + const args: Args = { + documentId: null, + ownerId: process.env.RAG_EVAL_OWNER_ID ?? process.env.LOCAL_NO_AUTH_OWNER_ID ?? null, + allOwners: false, + limit: 25, + write: false, + confirm: false, + }; + + for (let index = 0; index < argv.length; index += 1) { + const token = argv[index]; + if (token === "--all-owners") { + args.allOwners = true; + args.ownerId = null; + continue; + } + if (token === "--write") { + args.write = true; + continue; + } + if (token === "--confirm") { + args.confirm = true; + continue; + } + + const value = argv[index + 1]; + if (!value || value.startsWith("--")) { + if (token === "--document-id" || token === "--owner-id" || token === "--limit") { + throw new Error(`Missing value for ${token}`); + } + continue; + } + if (token === "--document-id") args.documentId = value; + if (token === "--owner-id") args.ownerId = value; + if (token === "--limit") args.limit = Number.parseInt(value, 10); + if (token === "--document-id" || token === "--owner-id" || token === "--limit") { + index += 1; + } + } + + if (!Number.isFinite(args.limit) || args.limit <= 0) { + throw new Error("--limit must be a positive integer."); + } + if (!args.documentId && !args.ownerId && !args.allOwners) { + throw new Error("Provide --document-id, --owner-id, or --all-owners."); + } + return args; +} + +function rowNeedsRefresh(row: ImageRow, committedGeneration: string) { + const rowMetadataGeneration = committedIndexGeneration(row.metadata); + if (row.index_generation_id !== null && row.index_generation_id !== committedGeneration) return true; + return row.index_generation_id === null + ? rowMetadataGeneration !== committedGeneration + : rowMetadataGeneration !== committedGeneration; +} + +async function loadDocuments(args: { + supabase: ReturnType; + ownerId: string | null; + documentId: string | null; + allOwners: boolean; + limit: number; +}) { + let query = args.supabase.from("documents").select("id,title,file_name,metadata").eq("status", "indexed").order("created_at", { + ascending: false, + }); + + if (args.documentId) query = query.eq("id", args.documentId); + if (args.ownerId) query = query.eq("owner_id", args.ownerId); + if (!args.allOwners && !args.ownerId && !args.documentId) return []; + + const { data, error } = await query.limit(args.limit); + if (error) throw new Error(error.message); + return (data ?? []) as DocumentRow[]; +} + +async function loadImages(args: { + supabase: ReturnType; + documentId: string; +}) { + const rows: ImageRow[] = []; + for (let offset = 0; ; offset += 1000) { + const { data, error } = await args.supabase + .from("document_images") + .select("id,index_generation_id,metadata") + .eq("document_id", args.documentId) + .range(offset, offset + 999); + if (error) throw new Error(error.message); + const page = (data ?? []) as ImageRow[]; + rows.push(...page); + if (page.length < 1000) break; + } + return rows; +} + +async function main() { + const args = parseArgs(process.argv.slice(2)); + requireServerEnv(); + + const projectCheck = checkSupabaseProjectConfig( + { + NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL, + SUPABASE_PROJECT_REF: process.env.SUPABASE_PROJECT_REF, + SUPABASE_PROJECT_NAME: process.env.SUPABASE_PROJECT_NAME, + }, + { requireMetadata: true }, + ); + if (projectCheck.status === "mismatch") { + throw new Error(`Supabase project mismatch: ${formatSupabaseProjectCheck(projectCheck)}`); + } + if (projectCheck.warnings.length > 0) { + console.log(`Supabase project warning: ${projectCheck.warnings.join(" ; ")}`); + } + + const supabase = createAdminClient(); + console.log(`Target project: ${projectCheck.expected.name} (${projectCheck.expected.ref})`); + assertSupabaseHealthy(await probeSupabaseHealth(supabase), "Re-stamp document image generation"); + + const documents = await loadDocuments({ + supabase, + ownerId: args.ownerId, + documentId: args.documentId, + allOwners: args.allOwners, + limit: args.limit, + }); + + if (documents.length === 0) { + console.log("No indexed documents matched the target filter."); + return; + } + + const label = args.allOwners ? "all owners" : args.documentId ? `document ${args.documentId}` : `owner ${args.ownerId}`; + console.log(`Loaded ${documents.length} indexed document(s) for ${label} inspection.`); + + const patches: ImagePatch[] = []; + let inspectedImageCount = 0; + let documentsWithCandidates = 0; + + for (const document of documents) { + const committedGeneration = committedIndexGeneration(document.metadata); + if (!committedGeneration) { + console.log(`SKIP ${document.title ?? document.file_name ?? document.id}: missing index_generation_id in documents.metadata`); + continue; + } + + const images = await loadImages({ supabase, documentId: document.id }); + const staleRows = images.filter((image) => rowNeedsRefresh(image, committedGeneration)); + inspectedImageCount += images.length; + + if (staleRows.length === 0) { + console.log(`OK ${document.title ?? document.file_name ?? document.id}: ${images.length} image rows aligned`); + continue; + } + + documentsWithCandidates += 1; + console.log( + `MISMATCH ${document.title ?? document.file_name ?? document.id}: ${staleRows.length}/${images.length} image rows need re-stamp`, + ); + for (const row of staleRows) { + patches.push({ + imageId: row.id, + indexGenerationId: committedGeneration, + metadata: { ...metadataRecord(row.metadata), index_generation_id: committedGeneration }, + }); + } + } + + if (patches.length === 0) { + console.log(`\nNo stale image generation metadata found. Images inspected: ${inspectedImageCount}.`); + return; + } + + console.log(`\nStale rows found: ${patches.length} image rows across ${documentsWithCandidates} documents.`); + if (!args.write) { + console.log("Dry run complete. Run with --write --confirm to apply changes."); + return; + } + + const approved = args.confirm || (await confirm(`Apply re-stamp to ${patches.length} image rows?`)); + if (!approved) { + console.log("Operation cancelled. No rows updated."); + return; + } + + for (let start = 0; start < patches.length; start += 8) { + const batch = patches.slice(start, start + 8); + await Promise.all( + batch.map(async (patch) => { + const { error } = await supabase + .from("document_images") + .update({ + index_generation_id: patch.indexGenerationId, + metadata: patch.metadata, + }) + .eq("id", patch.imageId); + if (error) throw new Error(`Failed to re-stamp image row ${patch.imageId}: ${error.message}`); + }), + ); + } + + console.log(`Completed re-stamp. Updated ${patches.length} image rows across ${documentsWithCandidates} documents.`); +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exitCode = 1; +}); From bac17526dd674d4e5451949d92000b51e768b4d0 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:31:00 +0800 Subject: [PATCH 02/16] chore: reconcile ingestion RPC execute privileges, schema.sql and drift manifest --- .cursor/skills/accessibility-review/SKILL.md | 18 ++ .../skills/ai-architecture-review/SKILL.md | 23 ++ .cursor/skills/api-review/SKILL.md | 28 ++ .cursor/skills/code-quality-review/SKILL.md | 19 ++ .cursor/skills/design-review/SKILL.md | 18 ++ .../frontend-architecture-review/SKILL.md | 18 ++ .cursor/skills/performance-review/SKILL.md | 18 ++ .../skills/release-readiness-review/SKILL.md | 20 ++ .cursor/skills/repo-auditor/SKILL.md | 15 + .cursor/skills/security-review/SKILL.md | 20 ++ .../CHANGELOG.md | 29 ++ .../supabase-postgres-best-practices/SKILL.md | 22 +- .../references/_contributing.md | 28 +- .../references/_sections.md | 8 - .../references/security-rls-performance.md | 12 +- .cursor/skills/supabase/CHANGELOG.md | 35 +++ .cursor/skills/supabase/SKILL.md | 45 ++- .cursor/skills/testing-review/SKILL.md | 19 ++ .cursor/skills/ux-review/SKILL.md | 18 ++ package.json | 3 + scripts/compare-retrieval-eval.ts | 87 ++++++ scripts/embed-registry-records.ts | 162 ++++++++++ scripts/eval-retrieval.ts | 30 +- scripts/reindex-image-generation-metadata.ts | 51 +++- scripts/seed-differential-records.ts | 12 + scripts/seed-medication-records.ts | 12 + scripts/seed-registry-records.ts | 14 + src/lib/differential-seed.ts | 11 +- src/lib/differentials.ts | 4 +- src/lib/document-index-units.ts | 78 ++++- src/lib/env.ts | 4 + src/lib/forms.ts | 2 +- src/lib/medication-seed.ts | 11 +- src/lib/rag.ts | 96 ++++-- src/lib/registry-corpus.ts | 277 ++++++++++++++++++ src/lib/registry-seed.ts | 11 +- src/lib/source-governance.ts | 17 +- src/lib/source-metadata.ts | 2 + src/lib/types.ts | 4 +- supabase/drift-allowlist.json | 23 +- supabase/drift-manifest.json | 14 +- ...000_reconcile_ingestion_rpc_privileges.sql | 12 + supabase/schema.sql | 21 ++ tests/document-index-units.test.ts | 35 +++ tests/registry-corpus.test.ts | 70 +++++ tests/source-governance.test.ts | 38 +++ 46 files changed, 1391 insertions(+), 123 deletions(-) create mode 100644 .cursor/skills/accessibility-review/SKILL.md create mode 100644 .cursor/skills/ai-architecture-review/SKILL.md create mode 100644 .cursor/skills/api-review/SKILL.md create mode 100644 .cursor/skills/code-quality-review/SKILL.md create mode 100644 .cursor/skills/design-review/SKILL.md create mode 100644 .cursor/skills/frontend-architecture-review/SKILL.md create mode 100644 .cursor/skills/performance-review/SKILL.md create mode 100644 .cursor/skills/release-readiness-review/SKILL.md create mode 100644 .cursor/skills/repo-auditor/SKILL.md create mode 100644 .cursor/skills/security-review/SKILL.md create mode 100644 .cursor/skills/supabase-postgres-best-practices/CHANGELOG.md create mode 100644 .cursor/skills/supabase/CHANGELOG.md create mode 100644 .cursor/skills/testing-review/SKILL.md create mode 100644 .cursor/skills/ux-review/SKILL.md create mode 100644 scripts/compare-retrieval-eval.ts create mode 100644 scripts/embed-registry-records.ts create mode 100644 src/lib/registry-corpus.ts create mode 100644 supabase/migrations/20260709150000_reconcile_ingestion_rpc_privileges.sql create mode 100644 tests/registry-corpus.test.ts diff --git a/.cursor/skills/accessibility-review/SKILL.md b/.cursor/skills/accessibility-review/SKILL.md new file mode 100644 index 000000000..209a598a9 --- /dev/null +++ b/.cursor/skills/accessibility-review/SKILL.md @@ -0,0 +1,18 @@ +--- +name: accessibility-review +description: Reviews keyboard navigation support, semantic HTML tags, drawer/dialog behaviors, focus management, and screen-reader standards. Use during UI audits. +--- + +# Accessibility Review Skill + +Use this skill when reviewing user interfaces to ensure compliance with a11y standards. + +## Review Checklist + +### 1. Keyboard & Focus Control +- **Interactive Elements:** Verify all button-like and link-like components use semantic HTML elements (`