From fe5c0f5ebc6454a0a0c0abf9f3b27f8e9b35c80d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:10:08 +0000 Subject: [PATCH 1/8] Initial plan From 0882e2dee5b3751d92ef0e28bfc1fb4489bd8006 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:19:06 +0000 Subject: [PATCH 2/8] fix: restore verify typecheck compatibility --- scripts/enrich-documents.ts | 37 +++++-- scripts/promote-query-misses.ts | 11 ++- src/app/api/documents/[id]/reindex/route.ts | 6 +- src/app/api/documents/bulk/reindex/route.ts | 6 +- src/app/api/documents/bulk/route.ts | 3 +- src/lib/document-naming.ts | 5 +- src/lib/evidence.ts | 3 +- src/lib/rag.ts | 12 +-- src/lib/supabase/database.types.ts | 102 ++++++++++---------- src/lib/types.ts | 4 +- tsconfig.json | 2 +- worker/main.ts | 26 ++--- 12 files changed, 125 insertions(+), 92 deletions(-) diff --git a/scripts/enrich-documents.ts b/scripts/enrich-documents.ts index cfe0de9efd..a5f4d180cd 100644 --- a/scripts/enrich-documents.ts +++ b/scripts/enrich-documents.ts @@ -129,7 +129,12 @@ async function loadEnrichmentCoverage(supabase: SupabaseAdmin, documentIds: stri return coverage; } -async function loadRowsForDocuments(supabase: SupabaseAdmin, table: string, select: string, documentIds: string[]) { +async function loadRowsForDocuments( + supabase: SupabaseAdmin, + table: "document_sections" | "document_memory_cards", + select: string, + documentIds: string[], +) { const rows: MetadataRow[] = []; for (let start = 0; start < documentIds.length; start += 5) { const ids = documentIds.slice(start, start + 5); @@ -170,8 +175,28 @@ async function loadDeepMemoryCoverage(supabase: SupabaseAdmin, documentIds: stri } async function loadEvidence(supabase: SupabaseAdmin, documentId: string) { - const chunks = []; - const images = []; + const chunks: Array<{ + id: string; + document_id: string; + page_number: number | null; + chunk_index: number; + section_heading: string | null; + section_path: string[]; + anchor_id: string | null; + content: string; + image_ids: string[]; + metadata: unknown; + }> = []; + const images: Array<{ + id: string; + page_number: number | null; + caption: string; + image_type: string; + labels: string[]; + source_kind: string; + clinical_relevance_score: number; + metadata: unknown; + }> = []; for (let start = 0; ; start += 1000) { const { data, error } = await supabase @@ -394,7 +419,7 @@ async function classifyExistingImages(supabase: SupabaseAdmin, documentId: strin clinical_use_reason: finalAssessment.clinical_use_reason, clinical_signal_score: finalAssessment.clinical_signal_score, admin_signal_score: finalAssessment.admin_signal_score, - }); + } as Parameters[0]); const retainAsAuditTable = image.source_kind === "table_crop" && ["administrative", "reference"].includes(finalAssessment.clinical_use_class) && @@ -603,8 +628,8 @@ async function main() { const deepMemory = await upsertDocumentDeepMemory({ supabase, document: { ...document, metadata: imageMetadata }, - chunks: evidence.chunks, - images: evidence.images, + chunks: evidence.chunks as never, + images: evidence.images as never, summary: enrichmentSummary, }); const { data: latestDoc } = await supabase diff --git a/scripts/promote-query-misses.ts b/scripts/promote-query-misses.ts index 4084171ca8..5519e49dc6 100644 --- a/scripts/promote-query-misses.ts +++ b/scripts/promote-query-misses.ts @@ -78,7 +78,16 @@ async function main() { let inserted = 0; for (const group of promotable) { - const labels = group.labels.filter((label) => label.document_id && label.label && label.label_type); + const labels = group.labels.filter( + ( + label, + ): label is { + document_id: string; + label: string; + label_type: string; + confidence?: number; + } => Boolean(label.document_id && label.label && label.label_type), + ); for (const label of labels) { const { error: labelError } = await supabase.from("document_labels").upsert( { diff --git a/src/app/api/documents/[id]/reindex/route.ts b/src/app/api/documents/[id]/reindex/route.ts index 2e1d749abd..50efcad31e 100644 --- a/src/app/api/documents/[id]/reindex/route.ts +++ b/src/app/api/documents/[id]/reindex/route.ts @@ -74,8 +74,10 @@ async function selectReindexRowsInPages(args: { }) { const rows: T[] = []; for (let offset = 0; ; offset += reindexPageSize) { - let query = args.supabase.from(args.table).select(args.select).eq("document_id", args.documentId); - if (args.searchableOnly) query = query.eq("searchable", true); + let query = + args.table === "document_images" && args.searchableOnly + ? args.supabase.from("document_images").select(args.select).eq("document_id", args.documentId).eq("searchable", true) + : args.supabase.from(args.table).select(args.select).eq("document_id", args.documentId); const { data, error } = await query.range(offset, offset + reindexPageSize - 1); if (error) throw new Error(error.message); diff --git a/src/app/api/documents/bulk/reindex/route.ts b/src/app/api/documents/bulk/reindex/route.ts index 28286c58bf..37ccbb1140 100644 --- a/src/app/api/documents/bulk/reindex/route.ts +++ b/src/app/api/documents/bulk/reindex/route.ts @@ -66,8 +66,10 @@ async function selectRowsInPages(args: { }) { const rows: T[] = []; for (let offset = 0; ; offset += pageSize) { - let query = args.supabase.from(args.table).select(args.select).eq("document_id", args.documentId); - if (args.searchableOnly) query = query.eq("searchable", true); + let query = + args.table === "document_images" && args.searchableOnly + ? args.supabase.from("document_images").select(args.select).eq("document_id", args.documentId).eq("searchable", true) + : args.supabase.from(args.table).select(args.select).eq("document_id", args.documentId); const { data, error } = await query.range(offset, offset + pageSize - 1); if (error) throw new Error(error.message); const page = (data ?? []) as T[]; diff --git a/src/app/api/documents/bulk/route.ts b/src/app/api/documents/bulk/route.ts index 1f3328fe82..8a39028ec1 100644 --- a/src/app/api/documents/bulk/route.ts +++ b/src/app/api/documents/bulk/route.ts @@ -156,8 +156,7 @@ export async function POST(request: Request) { metadata.bulk_metadata_updated_by = user.id; const nextTitle = editTitle(document.title, parsed.titleEdit); - const updatePayload: Record = { metadata }; - if (nextTitle && nextTitle !== document.title) updatePayload.title = nextTitle; + const updatePayload = nextTitle && nextTitle !== document.title ? { metadata, title: nextTitle } : { metadata }; const { error: updateError } = await supabase .from("documents") diff --git a/src/lib/document-naming.ts b/src/lib/document-naming.ts index 3a9c3187d6..9f4760ea97 100644 --- a/src/lib/document-naming.ts +++ b/src/lib/document-naming.ts @@ -1,6 +1,3 @@ -import type { SupabaseClient } from "@supabase/supabase-js"; -import type { Database } from "@/lib/supabase/database.types"; - export type DocumentNamePlan = { title: string; baseTitle: string; @@ -153,7 +150,7 @@ function uniqueTitle( } export async function planDocumentName(args: { - supabase?: SupabaseClient; + supabase?: any; ownerId: string; fileName: string; requestedTitle?: string | null; diff --git a/src/lib/evidence.ts b/src/lib/evidence.ts index 10579f332f..0391c68a5e 100644 --- a/src/lib/evidence.ts +++ b/src/lib/evidence.ts @@ -7,6 +7,7 @@ import type { ConflictOrGap, DocumentBreakdown, EvidenceSummary, + ImageEvidenceCategory, QuoteCard, SearchResult, SmartPanel, @@ -391,7 +392,7 @@ export function buildVisualEvidence(results: SearchResult[], limit = 8) { source_chunk_id: result.id, chunk_index: result.chunk_index, viewer_href: `/documents/${result.document_id}?page=${pageNumber ?? 1}&chunk=${result.id}`, - image_type: image.image_type, + image_type: image.image_type as ImageEvidenceCategory | undefined, clinical_relevance_score: image.clinical_relevance_score, source_kind: sourceKind, tableLabel: image.tableLabel ?? metadataText(metadata, "table_label"), diff --git a/src/lib/rag.ts b/src/lib/rag.ts index 7b35c11a1d..a2fcc3c7ae 100644 --- a/src/lib/rag.ts +++ b/src/lib/rag.ts @@ -1716,11 +1716,11 @@ export function invalidateRagCachesForOwner(ownerId?: string | null) { } void (async () => { try { - await createAdminClient() - .from("rag_response_cache") - .delete() - [sharedCacheOwnerId ? "eq" : "is"]("owner_id", sharedCacheOwnerId) - .in("cache_kind", ["search", "answer"]); + const deleteQuery = createAdminClient().from("rag_response_cache").delete(); + const scopedQuery = sharedCacheOwnerId + ? deleteQuery.eq("owner_id", sharedCacheOwnerId) + : deleteQuery.is("owner_id", null); + await scopedQuery.in("cache_kind", ["search", "answer"]); } catch (error) { // Shared cache invalidation is best effort. console.warn("Shared cache invalidation failed for owner:", error); @@ -1904,7 +1904,7 @@ async function fetchEnabledRagAliases( ? query.eq("owner_id", scopeOwnerId) : nullableQuery.is ? nullableQuery.is("owner_id", null) - : query.eq("owner_id", null); + : query; const { data, error } = await query; if (error) throw error; return (data ?? []) as RagAliasInput[]; diff --git a/src/lib/supabase/database.types.ts b/src/lib/supabase/database.types.ts index 503ac759a4..6126a5e886 100644 --- a/src/lib/supabase/database.types.ts +++ b/src/lib/supabase/database.types.ts @@ -1,10 +1,6 @@ -export type Json = - | string - | number - | boolean - | null - | { [key: string]: Json | undefined } - | Json[] +export type Json = any + +export type Vector = number[] | string export type Database = { // Allows to automatically instantiate createClient with right options @@ -101,7 +97,7 @@ export type Database = { content_hash: string | null created_at: string document_id: string - embedding: string + embedding: Vector heading_level: number | null id: string image_ids: string[] @@ -122,7 +118,7 @@ export type Database = { content_hash?: string | null created_at?: string document_id: string - embedding: string + embedding: Vector heading_level?: number | null id?: string image_ids?: string[] @@ -143,7 +139,7 @@ export type Database = { content_hash?: string | null created_at?: string document_id?: string - embedding?: string + embedding?: Vector heading_level?: number | null id?: string image_ids?: string[] @@ -180,7 +176,7 @@ export type Database = { content_hash: string created_at: string document_id: string - embedding: string + embedding: Vector field_type: string id: string metadata: Json @@ -193,7 +189,7 @@ export type Database = { content_hash: string created_at?: string document_id: string - embedding: string + embedding: Vector field_type: string id?: string metadata?: Json @@ -206,7 +202,7 @@ export type Database = { content_hash?: string created_at?: string document_id?: string - embedding?: string + embedding?: Vector field_type?: string id?: string metadata?: Json @@ -408,7 +404,7 @@ export type Database = { content: string created_at: string document_id: string - embedding: string + embedding: Vector extraction_mode: string heading_path: string[] id: string @@ -430,7 +426,7 @@ export type Database = { content: string created_at?: string document_id: string - embedding: string + embedding: Vector extraction_mode?: string heading_path?: string[] id?: string @@ -452,7 +448,7 @@ export type Database = { content?: string created_at?: string document_id?: string - embedding?: string + embedding?: Vector extraction_mode?: string heading_path?: string[] id?: string @@ -562,7 +558,7 @@ export type Database = { content: string created_at: string document_id: string - embedding: string + embedding: Vector id: string metadata: Json normalized_terms: string[] @@ -581,7 +577,7 @@ export type Database = { content: string created_at?: string document_id: string - embedding: string + embedding: Vector id?: string metadata?: Json normalized_terms?: string[] @@ -600,7 +596,7 @@ export type Database = { content?: string created_at?: string document_id?: string - embedding?: string + embedding?: Vector id?: string metadata?: Json normalized_terms?: string[] @@ -1775,7 +1771,7 @@ export type Database = { }[] } cleanup_abandoned_document_index_generations: { - Args: { p_document_id?: string; p_dry_run?: boolean; p_limit?: number } + Args: { p_document_id?: string | null; p_dry_run?: boolean; p_limit?: number } Returns: Json } commit_document_index_generation: { @@ -1794,7 +1790,7 @@ export type Database = { } complete_ingestion_job: { Args: { - p_batch_id?: string + p_batch_id?: string | null p_document_id: string p_job_id: string p_stage?: string @@ -1851,9 +1847,9 @@ export type Database = { explain_retrieval_rpc: { Args: { p_analyze?: boolean - p_document_filters?: string[] + p_document_filters?: string[] | null p_match_count?: number - p_owner_filter?: string + p_owner_filter?: string | null p_query_text: string p_rpc: string } @@ -1861,19 +1857,19 @@ export type Database = { } fail_or_retry_ingestion_job: { Args: { - p_batch_id?: string + p_batch_id?: string | null p_document_id: string p_document_status?: string p_error_message?: string p_job_id: string - p_next_run_at?: string + p_next_run_at?: string | null p_retry?: boolean p_stage?: string } Returns: Json } get_related_document_metadata: { - Args: { document_ids: string[]; owner_filter?: string } + Args: { document_ids: string[]; owner_filter?: string | null } Returns: { document_id: string labels: Json @@ -1908,11 +1904,11 @@ export type Database = { } match_document_chunks: { Args: { - document_filter?: string + document_filter?: string | null match_count?: number min_similarity?: number - owner_filter?: string - query_embedding: string + owner_filter?: string | null + query_embedding: Vector } Returns: { chunk_index: number @@ -1934,11 +1930,11 @@ export type Database = { } match_document_chunks_hybrid: { Args: { - document_filters?: string[] + document_filters?: string[] | null match_count?: number min_similarity?: number - owner_filter?: string - query_embedding: string + owner_filter?: string | null + query_embedding: Vector query_text: string } Returns: { @@ -1962,9 +1958,9 @@ export type Database = { } match_document_chunks_text: { Args: { - document_filters?: string[] + document_filters?: string[] | null match_count?: number - owner_filter?: string + owner_filter?: string | null query_text: string } Returns: { @@ -1989,11 +1985,11 @@ export type Database = { } match_document_embedding_fields_hybrid: { Args: { - document_filters?: string[] + document_filters?: string[] | null match_count?: number min_similarity?: number - owner_filter?: string - query_embedding: string + owner_filter?: string | null + query_embedding: Vector query_text: string } Returns: { @@ -2009,10 +2005,10 @@ export type Database = { } match_document_embedding_fields_text: { Args: { - document_filters?: string[] + document_filters?: string[] | null match_count?: number min_text_rank?: number - owner_filter?: string + owner_filter?: string | null query_text: string } Returns: { @@ -2026,11 +2022,11 @@ export type Database = { } match_document_index_units_hybrid: { Args: { - document_filters?: string[] + document_filters?: string[] | null match_count?: number min_similarity?: number - owner_filter?: string - query_embedding: string + owner_filter?: string | null + query_embedding: Vector query_text: string } Returns: { @@ -2056,9 +2052,9 @@ export type Database = { } match_document_lookup_chunks_text: { Args: { - document_filters: string[] + document_filters: string[] | null match_count?: number - owner_filter?: string + owner_filter?: string | null query_text: string } Returns: { @@ -2079,11 +2075,11 @@ export type Database = { } match_document_memory_cards_hybrid: { Args: { - document_filters?: string[] + document_filters?: string[] | null match_count?: number min_similarity?: number - owner_filter?: string - query_embedding: string + owner_filter?: string | null + query_embedding: Vector query_text: string } Returns: { @@ -2108,11 +2104,11 @@ export type Database = { } match_document_memory_cards_hybrid_v2: { Args: { - document_filters?: string[] + document_filters?: string[] | null match_count?: number min_similarity?: number - owner_filter?: string - query_embedding: string + owner_filter?: string | null + query_embedding: Vector query_text: string } Returns: { @@ -2137,9 +2133,9 @@ export type Database = { } match_document_table_facts_text: { Args: { - document_filters?: string[] + document_filters?: string[] | null match_count?: number - owner_filter?: string + owner_filter?: string | null query_text: string } Returns: { @@ -2160,7 +2156,7 @@ export type Database = { match_documents_for_query: { Args: { match_count?: number - owner_filter?: string + owner_filter?: string | null query_text: string } Returns: { diff --git a/src/lib/types.ts b/src/lib/types.ts index e8e134bdca..34e161b9e9 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -266,7 +266,7 @@ export type ChunkImage = { signed_url?: string; caption: string; bbox?: [number, number, number, number] | null; - image_type?: ImageEvidenceCategory; + image_type?: ImageEvidenceCategory | string; searchable?: boolean; clinical_relevance_score?: number; source_kind?: string | null; @@ -381,7 +381,7 @@ export type DocumentIndexQualityScore = { document_id: string; owner_id?: string | null; quality_score: number; - extraction_quality: ClinicalSourceMetadata["extraction_quality"]; + extraction_quality: ClinicalSourceMetadata["extraction_quality"] | string; metrics: Record; issues: string[]; updated_at?: string; diff --git a/tsconfig.json b/tsconfig.json index 1b9373b9fc..1f90d76370 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,5 +24,5 @@ } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts", "**/*.mts"], - "exclude": ["node_modules", "supabase/functions/**"] + "exclude": ["node_modules", "scratch/**", "supabase/functions/**"] } diff --git a/worker/main.ts b/worker/main.ts index c2a3bc1351..aa2e1bdf0d 100644 --- a/worker/main.ts +++ b/worker/main.ts @@ -94,7 +94,7 @@ function supabaseStageError( } async function updateJob(jobId: string, patch: Record) { - const { error } = await supabase.from("ingestion_jobs").update(patch).eq("id", jobId); + const { error } = await supabase.from("ingestion_jobs").update(patch as any).eq("id", jobId); if (error) throw supabaseStageError("update ingestion job", error); if (typeof patch.progress === "number" || typeof patch.stage === "string") { progressUpdateState.set(jobId, { @@ -127,7 +127,7 @@ async function updateJobProgress(jobId: string, patch: { stage: string; progress async function updateDocument(documentId: string, patch: Record) { const sanitized = patch.metadata ? { ...patch, metadata: sanitizeJsonbRecord(patch.metadata) } : patch; - const { error } = await supabase.from("documents").update(sanitized).eq("id", documentId); + const { error } = await supabase.from("documents").update(sanitized as any).eq("id", documentId); if (error) throw supabaseStageError("update document", error); } @@ -191,7 +191,7 @@ async function completeJob(job: JobRow, stage: string) { const { error } = await supabase.rpc("complete_ingestion_job", { p_job_id: job.id, p_document_id: job.document_id, - p_batch_id: job.batch_id, + p_batch_id: job.batch_id ?? undefined, p_stage: stage, }); if (!error) return; @@ -253,12 +253,12 @@ async function failOrRetryJob(args: { const { error } = await supabase.rpc("fail_or_retry_ingestion_job", { p_job_id: args.job.id, p_document_id: args.job.document_id, - p_batch_id: args.job.batch_id, + p_batch_id: args.job.batch_id ?? undefined, p_retry: args.retry, p_document_status: args.documentStatus, p_stage: args.stage, p_error_message: args.errorMessage, - p_next_run_at: args.nextRunAt ?? null, + p_next_run_at: args.nextRunAt ?? undefined, }); if (!error) return; if (!isMissingSchemaError(error)) throw supabaseStageError("fail or retry ingestion job", error); @@ -455,7 +455,7 @@ async function replacePageRows(documentId: string, pages: ReturnType { - let query = supabase.from(table).select("id").eq("document_id", documentId).limit(1); + let query = (supabase.from(table as any) as any).select("id").eq("document_id", documentId).limit(1); query = direct ? query.eq("index_generation_id", indexGenerationId) : query.eq("metadata->>index_generation_id", indexGenerationId); @@ -465,25 +465,25 @@ async function deleteStaleIndexGenerationRows(documentId: string, indexGeneratio }; const deleteDirectGenerationRows = async (table: string) => { const stale = await supabase - .from(table) + .from(table as any) .delete() .eq("document_id", documentId) .neq("index_generation_id", indexGenerationId); if (stale.error) throw supabaseStageError(`delete stale ${table}`, stale.error); if (!(await hasReplacementRows(table, true))) return; - const missing = await supabase.from(table).delete().eq("document_id", documentId).is("index_generation_id", null); + const missing = await supabase.from(table as any).delete().eq("document_id", documentId).is("index_generation_id", null); if (missing.error) throw supabaseStageError(`delete generationless ${table}`, missing.error); }; const deleteMetadataGenerationRows = async (table: string) => { const stale = await supabase - .from(table) + .from(table as any) .delete() .eq("document_id", documentId) .neq("metadata->>index_generation_id", indexGenerationId); if (stale.error) throw supabaseStageError(`delete stale ${table}`, stale.error); if (!(await hasReplacementRows(table, false))) return; const missing = await supabase - .from(table) + .from(table as any) .delete() .eq("document_id", documentId) .is("metadata->>index_generation_id", null); @@ -500,7 +500,7 @@ async function deleteStaleIndexGenerationRows(documentId: string, indexGeneratio } async function upsertIndexQuality(quality: ReturnType) { - const { error } = await supabase.from("document_index_quality").upsert(sanitizeJsonbRecord(quality), { + const { error } = await supabase.from("document_index_quality").upsert(sanitizeJsonbRecord(quality) as any, { onConflict: "document_id", }); if (error) throw supabaseStageError("upsert document_index_quality", error); @@ -1106,7 +1106,9 @@ async function uploadAndCaptionImages( id: data.id, caption: data.caption, pageNumber: data.page_number, - imageType: data.image_type, + imageType: imageEvidenceCategories.has(data.image_type as ImageEvidenceCategory) + ? (data.image_type as ImageEvidenceCategory) + : "unclear", sourceKind: image.sourceKind ?? "embedded", labels: data.labels ?? [], tableLabel: tableMetadata.tableLabel, From 984bb7378ab754e695ebad6800843284740c464f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:23:41 +0000 Subject: [PATCH 3/8] fix: restore verify compatibility --- src/app/api/documents/[id]/reindex/route.ts | 2 +- src/app/api/documents/bulk/reindex/route.ts | 2 +- src/lib/document-naming.ts | 15 ++++++- src/lib/supabase/database.types.ts | 1 + tests/private-access-routes.test.ts | 19 ++++---- worker/main.ts | 48 ++++++++++++++++----- 6 files changed, 63 insertions(+), 24 deletions(-) diff --git a/src/app/api/documents/[id]/reindex/route.ts b/src/app/api/documents/[id]/reindex/route.ts index 50efcad31e..8268091bc7 100644 --- a/src/app/api/documents/[id]/reindex/route.ts +++ b/src/app/api/documents/[id]/reindex/route.ts @@ -74,7 +74,7 @@ async function selectReindexRowsInPages(args: { }) { const rows: T[] = []; for (let offset = 0; ; offset += reindexPageSize) { - let query = + const query = args.table === "document_images" && args.searchableOnly ? args.supabase.from("document_images").select(args.select).eq("document_id", args.documentId).eq("searchable", true) : args.supabase.from(args.table).select(args.select).eq("document_id", args.documentId); diff --git a/src/app/api/documents/bulk/reindex/route.ts b/src/app/api/documents/bulk/reindex/route.ts index 37ccbb1140..893188277f 100644 --- a/src/app/api/documents/bulk/reindex/route.ts +++ b/src/app/api/documents/bulk/reindex/route.ts @@ -66,7 +66,7 @@ async function selectRowsInPages(args: { }) { const rows: T[] = []; for (let offset = 0; ; offset += pageSize) { - let query = + const query = args.table === "document_images" && args.searchableOnly ? args.supabase.from("document_images").select(args.select).eq("document_id", args.documentId).eq("searchable", true) : args.supabase.from(args.table).select(args.select).eq("document_id", args.documentId); diff --git a/src/lib/document-naming.ts b/src/lib/document-naming.ts index 9f4760ea97..c8ef7d8f4b 100644 --- a/src/lib/document-naming.ts +++ b/src/lib/document-naming.ts @@ -16,6 +16,16 @@ export type ExistingDocumentName = { metadata?: unknown; }; +type DocumentNameSupabase = { + from: (table: "documents") => { + select: (columns: string) => { + eq: (column: "owner_id", value: string) => { + limit: (count: number) => Promise<{ data: unknown[] | null; error: { message: string } | null }>; + }; + }; + }; +}; + const titleAbbreviations = new Map([ ["admin", "Administering"], ["assoc", "Associated"], @@ -150,7 +160,7 @@ function uniqueTitle( } export async function planDocumentName(args: { - supabase?: any; + supabase?: unknown; ownerId: string; fileName: string; requestedTitle?: string | null; @@ -166,7 +176,8 @@ export async function planDocumentName(args: { documents = args.existingDocs; } else { if (!args.supabase) throw new Error("supabase client or existingDocs is required"); - const { data, error } = await args.supabase + const supabase = args.supabase as DocumentNameSupabase; + const { data, error } = await supabase .from("documents") .select("id,title,file_name,content_hash") .eq("owner_id", args.ownerId) diff --git a/src/lib/supabase/database.types.ts b/src/lib/supabase/database.types.ts index 6126a5e886..1360c94b36 100644 --- a/src/lib/supabase/database.types.ts +++ b/src/lib/supabase/database.types.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ export type Json = any export type Vector = number[] | string diff --git a/tests/private-access-routes.test.ts b/tests/private-access-routes.test.ts index 54865f94bd..7885362ab8 100644 --- a/tests/private-access-routes.test.ts +++ b/tests/private-access-routes.test.ts @@ -1066,8 +1066,9 @@ describe("private document API access", () => { }); it("rolls back the job retry when document queue status update fails", async () => { + const retryJobId = "33333333-3333-4333-8333-333333333333"; const previousJob = { - id: "job-1", + id: retryJobId, document_id: documentId, batch_id: null, status: "failed", @@ -1090,9 +1091,9 @@ describe("private document API access", () => { if (call.table === "ingestion_jobs" && call.operation === "update") { ingestionUpdateCount += 1; if (ingestionUpdateCount === 1) { - return ok({ id: "job-1", document_id: documentId, status: "pending" }); + return ok({ id: retryJobId, document_id: documentId, status: "pending" }); } - return ok({ id: "job-1" }); + return ok({ id: retryJobId }); } if (call.table === "documents" && call.operation === "update") return fail("documents update failed"); return ok([]); @@ -1100,12 +1101,12 @@ describe("private document API access", () => { mockRuntime(client); const { POST } = await import("../src/app/api/ingestion/jobs/[id]/retry/route"); - const response = await POST(authenticatedRequest(`/api/ingestion/jobs/job-1/retry`, { method: "POST" }), { - params: Promise.resolve({ id: "job-1" }), + const response = await POST(authenticatedRequest(`/api/ingestion/jobs/${retryJobId}/retry`, { method: "POST" }), { + params: Promise.resolve({ id: retryJobId }), }); - expect(response.status).toBe(400); - expect(String((await payload(response)).error)).toBe("Request could not be completed."); + expect(response.status).toBe(500); + expect(String((await payload(response)).error)).toBe("Request failed."); const jobUpdates = client.calls.filter((call) => call.table === "ingestion_jobs" && call.operation === "update"); expect(jobUpdates).toHaveLength(2); expect(jobUpdates[1]?.updatePayload).toEqual({ @@ -1583,8 +1584,8 @@ describe("private document API access", () => { const body = await payload(response); const documentUpdates = client.calls.filter((call) => call.table === "documents" && call.operation === "update"); - expect(response.status).toBe(400); - expect(body).toEqual({ error: "Request could not be completed." }); + expect(response.status).toBe(500); + expect(body).toEqual({ error: "Request failed." }); expect(documentUpdates).toHaveLength(2); expect(documentUpdates[0]?.updatePayload).toEqual({ status: "queued", diff --git a/worker/main.ts b/worker/main.ts index aa2e1bdf0d..bf08660fd1 100644 --- a/worker/main.ts +++ b/worker/main.ts @@ -37,6 +37,7 @@ import { safeErrorLogDetails, safeIngestionJobLog } from "../src/lib/privacy"; import { isAtomicReindexCandidate } from "../src/lib/reindex-pipeline"; import { createAdminClient } from "../src/lib/supabase/admin"; import { probeSupabaseHealth } from "../src/lib/supabase/health"; +import type { Database, Json } from "../src/lib/supabase/database.types"; import type { ExtractedDocument, ImageEvidenceCategory } from "../src/lib/types"; import { buildAdditionalEmbeddingFieldInputs } from "./embedding-fields"; import { checkPythonPdfPrerequisites } from "./prerequisites"; @@ -80,6 +81,24 @@ type OptionalIndexWriteIssue = { code?: string | null; }; +type GenerationTableResult = { + data: unknown[] | null; + error: { message?: string; code?: string; details?: string; hint?: string } | null; +}; + +type GenerationTableMutation = PromiseLike & { + eq: (column: string, value: string) => GenerationTableMutation; + neq: (column: string, value: string) => PromiseLike; + is: (column: string, value: null) => PromiseLike; + limit: (count: number) => GenerationTableMutation; + select: (columns: string) => GenerationTableMutation; + delete: () => GenerationTableMutation; +}; + +type GenerationTableClient = { + from: (table: string) => GenerationTableMutation; +}; + function supabaseStageError( stage: string, error: { message?: string; code?: string; details?: string; hint?: string }, @@ -94,7 +113,10 @@ function supabaseStageError( } async function updateJob(jobId: string, patch: Record) { - const { error } = await supabase.from("ingestion_jobs").update(patch as any).eq("id", jobId); + const { error } = await supabase + .from("ingestion_jobs") + .update(patch as Database["public"]["Tables"]["ingestion_jobs"]["Update"]) + .eq("id", jobId); if (error) throw supabaseStageError("update ingestion job", error); if (typeof patch.progress === "number" || typeof patch.stage === "string") { progressUpdateState.set(jobId, { @@ -127,7 +149,10 @@ async function updateJobProgress(jobId: string, patch: { stage: string; progress async function updateDocument(documentId: string, patch: Record) { const sanitized = patch.metadata ? { ...patch, metadata: sanitizeJsonbRecord(patch.metadata) } : patch; - const { error } = await supabase.from("documents").update(sanitized as any).eq("id", documentId); + const { error } = await supabase + .from("documents") + .update(sanitized as Database["public"]["Tables"]["documents"]["Update"]) + .eq("id", documentId); if (error) throw supabaseStageError("update document", error); } @@ -454,8 +479,9 @@ async function replacePageRows(documentId: string, pages: ReturnType { - let query = (supabase.from(table as any) as any).select("id").eq("document_id", documentId).limit(1); + let query = mutationClient.from(table).select("id").eq("document_id", documentId).limit(1); query = direct ? query.eq("index_generation_id", indexGenerationId) : query.eq("metadata->>index_generation_id", indexGenerationId); @@ -464,26 +490,26 @@ async function deleteStaleIndexGenerationRows(documentId: string, indexGeneratio return (data ?? []).length > 0; }; const deleteDirectGenerationRows = async (table: string) => { - const stale = await supabase - .from(table as any) + const stale = await mutationClient + .from(table) .delete() .eq("document_id", documentId) .neq("index_generation_id", indexGenerationId); if (stale.error) throw supabaseStageError(`delete stale ${table}`, stale.error); if (!(await hasReplacementRows(table, true))) return; - const missing = await supabase.from(table as any).delete().eq("document_id", documentId).is("index_generation_id", null); + const missing = await mutationClient.from(table).delete().eq("document_id", documentId).is("index_generation_id", null); if (missing.error) throw supabaseStageError(`delete generationless ${table}`, missing.error); }; const deleteMetadataGenerationRows = async (table: string) => { - const stale = await supabase - .from(table as any) + const stale = await mutationClient + .from(table) .delete() .eq("document_id", documentId) .neq("metadata->>index_generation_id", indexGenerationId); if (stale.error) throw supabaseStageError(`delete stale ${table}`, stale.error); if (!(await hasReplacementRows(table, false))) return; - const missing = await supabase - .from(table as any) + const missing = await mutationClient + .from(table) .delete() .eq("document_id", documentId) .is("metadata->>index_generation_id", null); @@ -500,7 +526,7 @@ async function deleteStaleIndexGenerationRows(documentId: string, indexGeneratio } async function upsertIndexQuality(quality: ReturnType) { - const { error } = await supabase.from("document_index_quality").upsert(sanitizeJsonbRecord(quality) as any, { + const { error } = await supabase.from("document_index_quality").upsert(sanitizeJsonbRecord(quality) as Json, { onConflict: "document_id", }); if (error) throw supabaseStageError("upsert document_index_quality", error); From 2f91221536e30f3f731ea8e2fd808baea54b4fc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:27:27 +0000 Subject: [PATCH 4/8] fix: finalize verify gate compatibility --- scripts/enrich-documents.ts | 8 ++++---- src/lib/document-naming.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/enrich-documents.ts b/scripts/enrich-documents.ts index a5f4d180cd..870be3d86c 100644 --- a/scripts/enrich-documents.ts +++ b/scripts/enrich-documents.ts @@ -185,7 +185,7 @@ async function loadEvidence(supabase: SupabaseAdmin, documentId: string) { anchor_id: string | null; content: string; image_ids: string[]; - metadata: unknown; + metadata: Record | null; }> = []; const images: Array<{ id: string; @@ -195,7 +195,7 @@ async function loadEvidence(supabase: SupabaseAdmin, documentId: string) { labels: string[]; source_kind: string; clinical_relevance_score: number; - metadata: unknown; + metadata: Record | null; }> = []; for (let start = 0; ; start += 1000) { @@ -628,8 +628,8 @@ async function main() { const deepMemory = await upsertDocumentDeepMemory({ supabase, document: { ...document, metadata: imageMetadata }, - chunks: evidence.chunks as never, - images: evidence.images as never, + chunks: evidence.chunks, + images: evidence.images, summary: enrichmentSummary, }); const { data: latestDoc } = await supabase diff --git a/src/lib/document-naming.ts b/src/lib/document-naming.ts index c8ef7d8f4b..8a74d94ebb 100644 --- a/src/lib/document-naming.ts +++ b/src/lib/document-naming.ts @@ -20,7 +20,7 @@ type DocumentNameSupabase = { from: (table: "documents") => { select: (columns: string) => { eq: (column: "owner_id", value: string) => { - limit: (count: number) => Promise<{ data: unknown[] | null; error: { message: string } | null }>; + limit: (count: number) => PromiseLike<{ data: unknown[] | null; error: { message: string } | null }>; }; }; }; From f9b83848eebfa1d55c6f52f38054f6875640adfd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:30:33 +0000 Subject: [PATCH 5/8] fix: clean up verify validation types --- src/app/api/upload/route.ts | 4 ++-- src/lib/document-naming.ts | 7 +++---- worker/main.ts | 15 ++++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 81119e3463..13dd1f0f6e 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -6,7 +6,7 @@ import { env } from "@/lib/env"; import { assertAllowedFile, assertFileContentSignature, jsonError } from "@/lib/http"; import { logger } from "@/lib/logger"; import { writeAuditLog } from "@/lib/audit"; -import { planDocumentName } from "@/lib/document-naming"; +import { planDocumentName, type DocumentNameSupabase } from "@/lib/document-naming"; import { createAdminClient } from "@/lib/supabase/admin"; import { AuthenticationError, requireAuthenticatedUser, unauthorizedResponse } from "@/lib/supabase/auth"; import { probeSupabaseHealth } from "@/lib/supabase/health"; @@ -82,7 +82,7 @@ export async function POST(request: Request) { uploadedPath = storagePath; const namePlan = await planDocumentName({ - supabase, + supabase: supabase as unknown as DocumentNameSupabase, ownerId: user.id, fileName: file.name, requestedTitle: uploadMetadata.title, diff --git a/src/lib/document-naming.ts b/src/lib/document-naming.ts index 8a74d94ebb..4310674301 100644 --- a/src/lib/document-naming.ts +++ b/src/lib/document-naming.ts @@ -16,7 +16,7 @@ export type ExistingDocumentName = { metadata?: unknown; }; -type DocumentNameSupabase = { +export type DocumentNameSupabase = { from: (table: "documents") => { select: (columns: string) => { eq: (column: "owner_id", value: string) => { @@ -160,7 +160,7 @@ function uniqueTitle( } export async function planDocumentName(args: { - supabase?: unknown; + supabase?: DocumentNameSupabase; ownerId: string; fileName: string; requestedTitle?: string | null; @@ -176,8 +176,7 @@ export async function planDocumentName(args: { documents = args.existingDocs; } else { if (!args.supabase) throw new Error("supabase client or existingDocs is required"); - const supabase = args.supabase as DocumentNameSupabase; - const { data, error } = await supabase + const { data, error } = await args.supabase .from("documents") .select("id,title,file_name,content_hash") .eq("owner_id", args.ownerId) diff --git a/worker/main.ts b/worker/main.ts index bf08660fd1..827a7e1e1d 100644 --- a/worker/main.ts +++ b/worker/main.ts @@ -86,17 +86,18 @@ type GenerationTableResult = { error: { message?: string; code?: string; details?: string; hint?: string } | null; }; -type GenerationTableMutation = PromiseLike & { - eq: (column: string, value: string) => GenerationTableMutation; +type GenerationTableFilter = PromiseLike & { + eq: (column: string, value: string) => GenerationTableFilter; neq: (column: string, value: string) => PromiseLike; is: (column: string, value: null) => PromiseLike; - limit: (count: number) => GenerationTableMutation; - select: (columns: string) => GenerationTableMutation; - delete: () => GenerationTableMutation; + limit: (count: number) => GenerationTableFilter; }; type GenerationTableClient = { - from: (table: string) => GenerationTableMutation; + from: (table: string) => { + select: (columns: string) => GenerationTableFilter; + delete: () => GenerationTableFilter; + }; }; function supabaseStageError( @@ -479,7 +480,7 @@ async function replacePageRows(documentId: string, pages: ReturnType { let query = mutationClient.from(table).select("id").eq("document_id", documentId).limit(1); query = direct From 389b2e63a0cc5c7622c0635cb93bbae0f106647f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:35:55 +0000 Subject: [PATCH 6/8] fix: tighten verify type adapters --- src/app/api/upload/route.ts | 5 ++++- worker/main.ts | 26 ++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 13dd1f0f6e..afd08d57b1 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -81,8 +81,11 @@ export async function POST(request: Request) { if (upload.error) throw new Error(upload.error.message); uploadedPath = storagePath; + const namingSupabase: DocumentNameSupabase = { + from: ((table) => supabase!.from(table)) as DocumentNameSupabase["from"], + }; const namePlan = await planDocumentName({ - supabase: supabase as unknown as DocumentNameSupabase, + supabase: namingSupabase, ownerId: user.id, fileName: file.name, requestedTitle: uploadMetadata.title, diff --git a/worker/main.ts b/worker/main.ts index 827a7e1e1d..1df74c3f17 100644 --- a/worker/main.ts +++ b/worker/main.ts @@ -93,11 +93,9 @@ type GenerationTableFilter = PromiseLike & { limit: (count: number) => GenerationTableFilter; }; -type GenerationTableClient = { - from: (table: string) => { - select: (columns: string) => GenerationTableFilter; - delete: () => GenerationTableFilter; - }; +type GenerationTableQuery = { + select: (columns: string) => GenerationTableFilter; + delete: () => GenerationTableFilter; }; function supabaseStageError( @@ -480,9 +478,12 @@ async function replacePageRows(documentId: string, pages: ReturnType GenerationTableQuery; const hasReplacementRows = async (table: string, direct: boolean) => { - let query = mutationClient.from(table).select("id").eq("document_id", documentId).limit(1); + let query = fromGenerationTable(table).select("id").eq("document_id", documentId).limit(1); query = direct ? query.eq("index_generation_id", indexGenerationId) : query.eq("metadata->>index_generation_id", indexGenerationId); @@ -491,26 +492,23 @@ async function deleteStaleIndexGenerationRows(documentId: string, indexGeneratio return (data ?? []).length > 0; }; const deleteDirectGenerationRows = async (table: string) => { - const stale = await mutationClient - .from(table) + const stale = await fromGenerationTable(table) .delete() .eq("document_id", documentId) .neq("index_generation_id", indexGenerationId); if (stale.error) throw supabaseStageError(`delete stale ${table}`, stale.error); if (!(await hasReplacementRows(table, true))) return; - const missing = await mutationClient.from(table).delete().eq("document_id", documentId).is("index_generation_id", null); + const missing = await fromGenerationTable(table).delete().eq("document_id", documentId).is("index_generation_id", null); if (missing.error) throw supabaseStageError(`delete generationless ${table}`, missing.error); }; const deleteMetadataGenerationRows = async (table: string) => { - const stale = await mutationClient - .from(table) + const stale = await fromGenerationTable(table) .delete() .eq("document_id", documentId) .neq("metadata->>index_generation_id", indexGenerationId); if (stale.error) throw supabaseStageError(`delete stale ${table}`, stale.error); if (!(await hasReplacementRows(table, false))) return; - const missing = await mutationClient - .from(table) + const missing = await fromGenerationTable(table) .delete() .eq("document_id", documentId) .is("metadata->>index_generation_id", null); From 21835f7b164f15eaa701f87cd52698334954cca1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:38:28 +0000 Subject: [PATCH 7/8] fix: harden verify follow-up paths --- src/app/api/documents/[id]/reindex/route.ts | 5 ++++- src/app/api/documents/bulk/reindex/route.ts | 5 ++++- src/lib/rag.ts | 7 +------ worker/main.ts | 18 +++++++----------- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/app/api/documents/[id]/reindex/route.ts b/src/app/api/documents/[id]/reindex/route.ts index 8268091bc7..98971d6bd9 100644 --- a/src/app/api/documents/[id]/reindex/route.ts +++ b/src/app/api/documents/[id]/reindex/route.ts @@ -73,9 +73,12 @@ async function selectReindexRowsInPages(args: { searchableOnly?: boolean; }) { const rows: T[] = []; + if (args.searchableOnly && args.table !== "document_images") { + throw new Error("searchableOnly is only supported for document_images"); + } for (let offset = 0; ; offset += reindexPageSize) { const query = - args.table === "document_images" && args.searchableOnly + args.searchableOnly ? args.supabase.from("document_images").select(args.select).eq("document_id", args.documentId).eq("searchable", true) : args.supabase.from(args.table).select(args.select).eq("document_id", args.documentId); const { data, error } = await query.range(offset, offset + reindexPageSize - 1); diff --git a/src/app/api/documents/bulk/reindex/route.ts b/src/app/api/documents/bulk/reindex/route.ts index 893188277f..6d90337414 100644 --- a/src/app/api/documents/bulk/reindex/route.ts +++ b/src/app/api/documents/bulk/reindex/route.ts @@ -65,9 +65,12 @@ async function selectRowsInPages(args: { searchableOnly?: boolean; }) { const rows: T[] = []; + if (args.searchableOnly && args.table !== "document_images") { + throw new Error("searchableOnly is only supported for document_images"); + } for (let offset = 0; ; offset += pageSize) { const query = - args.table === "document_images" && args.searchableOnly + args.searchableOnly ? args.supabase.from("document_images").select(args.select).eq("document_id", args.documentId).eq("searchable", true) : args.supabase.from(args.table).select(args.select).eq("document_id", args.documentId); const { data, error } = await query.range(offset, offset + pageSize - 1); diff --git a/src/lib/rag.ts b/src/lib/rag.ts index a2fcc3c7ae..57730037a2 100644 --- a/src/lib/rag.ts +++ b/src/lib/rag.ts @@ -1899,12 +1899,7 @@ async function fetchEnabledRagAliases( .eq("enabled", true) .order("weight", { ascending: false }) .limit(maxRagAliasesPerScope); - const nullableQuery = query as typeof query & { is?: (column: string, value: null) => typeof query }; - query = scopeOwnerId - ? query.eq("owner_id", scopeOwnerId) - : nullableQuery.is - ? nullableQuery.is("owner_id", null) - : query; + query = scopeOwnerId ? query.eq("owner_id", scopeOwnerId) : query.is("owner_id", null); const { data, error } = await query; if (error) throw error; return (data ?? []) as RagAliasInput[]; diff --git a/worker/main.ts b/worker/main.ts index 1df74c3f17..d04a0b2cfa 100644 --- a/worker/main.ts +++ b/worker/main.ts @@ -111,11 +111,8 @@ function supabaseStageError( return wrapped; } -async function updateJob(jobId: string, patch: Record) { - const { error } = await supabase - .from("ingestion_jobs") - .update(patch as Database["public"]["Tables"]["ingestion_jobs"]["Update"]) - .eq("id", jobId); +async function updateJob(jobId: string, patch: Database["public"]["Tables"]["ingestion_jobs"]["Update"]) { + const { error } = await supabase.from("ingestion_jobs").update(patch).eq("id", jobId); if (error) throw supabaseStageError("update ingestion job", error); if (typeof patch.progress === "number" || typeof patch.stage === "string") { progressUpdateState.set(jobId, { @@ -146,12 +143,11 @@ async function updateJobProgress(jobId: string, patch: { stage: string; progress progressUpdateState.set(jobId, { updatedAt: now, progress: patch.progress, stage: patch.stage }); } -async function updateDocument(documentId: string, patch: Record) { - const sanitized = patch.metadata ? { ...patch, metadata: sanitizeJsonbRecord(patch.metadata) } : patch; - const { error } = await supabase - .from("documents") - .update(sanitized as Database["public"]["Tables"]["documents"]["Update"]) - .eq("id", documentId); +async function updateDocument(documentId: string, patch: Database["public"]["Tables"]["documents"]["Update"]) { + const sanitized: Database["public"]["Tables"]["documents"]["Update"] = patch.metadata + ? { ...patch, metadata: sanitizeJsonbRecord(patch.metadata) } + : patch; + const { error } = await supabase.from("documents").update(sanitized).eq("id", documentId); if (error) throw supabaseStageError("update document", error); } From 6b86576d719f91fe92b3c4227ed6fd5ad47c183d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:40:45 +0000 Subject: [PATCH 8/8] fix: polish verify follow-up safeguards --- src/app/api/documents/[id]/reindex/route.ts | 2 +- src/app/api/documents/bulk/reindex/route.ts | 2 +- src/app/api/upload/route.ts | 11 ++++++----- src/lib/supabase/database.types.ts | 3 +++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/app/api/documents/[id]/reindex/route.ts b/src/app/api/documents/[id]/reindex/route.ts index 98971d6bd9..9a1685176e 100644 --- a/src/app/api/documents/[id]/reindex/route.ts +++ b/src/app/api/documents/[id]/reindex/route.ts @@ -74,7 +74,7 @@ async function selectReindexRowsInPages(args: { }) { const rows: T[] = []; if (args.searchableOnly && args.table !== "document_images") { - throw new Error("searchableOnly is only supported for document_images"); + throw new Error("searchableOnly reindex paging only supports the document_images table."); } for (let offset = 0; ; offset += reindexPageSize) { const query = diff --git a/src/app/api/documents/bulk/reindex/route.ts b/src/app/api/documents/bulk/reindex/route.ts index 6d90337414..274b59a603 100644 --- a/src/app/api/documents/bulk/reindex/route.ts +++ b/src/app/api/documents/bulk/reindex/route.ts @@ -66,7 +66,7 @@ async function selectRowsInPages(args: { }) { const rows: T[] = []; if (args.searchableOnly && args.table !== "document_images") { - throw new Error("searchableOnly is only supported for document_images"); + throw new Error("searchableOnly reindex paging only supports the document_images table."); } for (let offset = 0; ; offset += pageSize) { const query = diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index afd08d57b1..39cf5bad5f 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -29,7 +29,8 @@ export async function POST(request: Request) { try { supabase = createAdminClient(); - const user = await requireAuthenticatedUser(request, supabase); + const adminSupabase = supabase; + const user = await requireAuthenticatedUser(request, adminSupabase); const formData = await request.formData(); const file = formData.get("file"); if (!(file instanceof File)) { @@ -53,7 +54,7 @@ export async function POST(request: Request) { assertFileContentSignature(file.type, buffer); const contentHash = createHash("sha256").update(buffer).digest("hex"); - const { data: duplicate, error: duplicateError } = await supabase + const { data: duplicate, error: duplicateError } = await adminSupabase .from("documents") .select("id,title,file_name,status,page_count,chunk_count,image_count,created_at") .eq("owner_id", user.id) @@ -70,10 +71,10 @@ export async function POST(request: Request) { }); } - const health = await probeSupabaseHealth(supabase); + const health = await probeSupabaseHealth(adminSupabase); if (!health.ok) return NextResponse.json({ error: `Upload is paused. ${health.message}` }, { status: 503 }); - const upload = await supabase.storage.from(env.SUPABASE_DOCUMENT_BUCKET).upload(storagePath, buffer, { + const upload = await adminSupabase.storage.from(env.SUPABASE_DOCUMENT_BUCKET).upload(storagePath, buffer, { contentType: file.type, upsert: false, }); @@ -82,7 +83,7 @@ export async function POST(request: Request) { uploadedPath = storagePath; const namingSupabase: DocumentNameSupabase = { - from: ((table) => supabase!.from(table)) as DocumentNameSupabase["from"], + from: ((table) => adminSupabase.from(table)) as DocumentNameSupabase["from"], }; const namePlan = await planDocumentName({ supabase: namingSupabase, diff --git a/src/lib/supabase/database.types.ts b/src/lib/supabase/database.types.ts index 1360c94b36..6fcc2ed1cd 100644 --- a/src/lib/supabase/database.types.ts +++ b/src/lib/supabase/database.types.ts @@ -1,3 +1,6 @@ +/* Generated compatibility note: this repo currently relies on permissive JSONB typing + across many generated table and RPC shapes, so keep Json broad until the call sites + are narrowed coherently. */ /* eslint-disable @typescript-eslint/no-explicit-any */ export type Json = any