From f7a6c3ea71574005e84af8deeca14756de5ee770 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 10:01:23 +0000 Subject: [PATCH 1/3] Initial plan From f86a2c0a9c9a6fa4d55fed667ac432ba7fc38440 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 10:06:43 +0000 Subject: [PATCH 2/3] Fix parsing of strict enrichment status RPC result --- .../functions/indexing-v3-agent/behavior.ts | 27 ++++++++++++ supabase/functions/indexing-v3-agent/index.ts | 13 ++---- tests/indexing-v3-agent.test.ts | 41 +++++++++++++++++++ 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/supabase/functions/indexing-v3-agent/behavior.ts b/supabase/functions/indexing-v3-agent/behavior.ts index a4aeb4012e..175bf2655d 100644 --- a/supabase/functions/indexing-v3-agent/behavior.ts +++ b/supabase/functions/indexing-v3-agent/behavior.ts @@ -57,6 +57,33 @@ export type DeferralDecision = { }; }; +export type JobStatusRpcResult = { + ok: boolean; + gate_passed: boolean; + missing: string[] | null; + status: string; +}; + +function asRecord(value: unknown): Record | null { + return value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : null; +} + +export function parseJobStatusRpcResult(row: unknown, rpcName: string): JobStatusRpcResult | null { + const direct = asRecord(row); + const nested = asRecord(direct?.[rpcName]); + const candidate = direct?.ok !== undefined ? direct : nested; + if (!candidate) return null; + + const ok = candidate.ok === true; + const gatePassed = candidate.gate_passed === true; + const status = typeof candidate.status === "string" ? candidate.status : "missing_result"; + const missing = Array.isArray(candidate.missing) + ? candidate.missing.map((item) => String(item)) + : ["completion_rpc_failed"]; + + return { ok, gate_passed: gatePassed, status, missing }; +} + export function completionGateFromRow(row: CompletionGateRow): CompletionGate { return { counts: { diff --git a/supabase/functions/indexing-v3-agent/index.ts b/supabase/functions/indexing-v3-agent/index.ts index 254ea19a32..1a6025edef 100644 --- a/supabase/functions/indexing-v3-agent/index.ts +++ b/supabase/functions/indexing-v3-agent/index.ts @@ -4,9 +4,11 @@ import { completionGateFromRow, deferralDecision, missingArtifactPlan, + parseJobStatusRpcResult, shouldRunVisualArtifacts, type CompletionGate, type CompletionGateRow, + type JobStatusRpcResult, type MissingArtifactPlan, } from "./behavior.ts"; @@ -1922,14 +1924,7 @@ async function deferJob(job: ClaimedJob, gate: CompletionGate): Promise { } async function completeJob(job: ClaimedJob): Promise { - const rows = await sql< - Array<{ - ok: boolean; - gate_passed: boolean; - missing: string[] | null; - status: string; - }> - >` + const rows = await sql` select * from public.complete_strict_enrichment_job( ${job.document_id}::uuid, @@ -1939,7 +1934,7 @@ async function completeJob(job: ClaimedJob): Promise { 'visual-v3' ) `; - const result = rows[0]; + const result = parseJobStatusRpcResult(rows[0], "complete_strict_enrichment_job"); if (!result?.ok || !result.gate_passed) { throw new Error( `Strict enrichment completion blocked: ${JSON.stringify({ diff --git a/tests/indexing-v3-agent.test.ts b/tests/indexing-v3-agent.test.ts index 62b8096c53..297fcfcbd7 100644 --- a/tests/indexing-v3-agent.test.ts +++ b/tests/indexing-v3-agent.test.ts @@ -4,6 +4,7 @@ import { deferralDecision, metadataNumber, missingArtifactPlan, + parseJobStatusRpcResult, shouldRunVisualArtifacts, type CompletionGateRow, } from "../supabase/functions/indexing-v3-agent/behavior"; @@ -169,4 +170,44 @@ describe("indexing-v3-agent behavior", () => { expect(metadataNumber({ indexing_v3_agent_deferral_count: "bad" }, "indexing_v3_agent_deferral_count")).toBe(0); expect(metadataNumber(null, "indexing_v3_agent_deferral_count", 2)).toBe(2); }); + + it("parses strict completion RPC rows returned as direct table columns", () => { + const result = parseJobStatusRpcResult( + { + ok: true, + gate_passed: true, + status: "completed", + missing: [], + }, + "complete_strict_enrichment_job", + ); + + expect(result).toEqual({ + ok: true, + gate_passed: true, + status: "completed", + missing: [], + }); + }); + + it("parses strict completion RPC rows returned as nested jsonb function columns", () => { + const result = parseJobStatusRpcResult( + { + complete_strict_enrichment_job: { + ok: true, + gate_passed: true, + status: "completed", + missing: [], + }, + }, + "complete_strict_enrichment_job", + ); + + expect(result).toEqual({ + ok: true, + gate_passed: true, + status: "completed", + missing: [], + }); + }); }); From baa87b51b01417db2c347b008003fc2e3a44ef49 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 10:10:28 +0000 Subject: [PATCH 3/3] Handle nested JSONB strict completion RPC responses --- .../functions/indexing-v3-agent/behavior.ts | 15 +++++++------ tests/indexing-v3-agent.test.ts | 21 +++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/supabase/functions/indexing-v3-agent/behavior.ts b/supabase/functions/indexing-v3-agent/behavior.ts index 175bf2655d..d5fceb2533 100644 --- a/supabase/functions/indexing-v3-agent/behavior.ts +++ b/supabase/functions/indexing-v3-agent/behavior.ts @@ -64,22 +64,25 @@ export type JobStatusRpcResult = { status: string; }; -function asRecord(value: unknown): Record | null { +function asObjectRecord(value: unknown): Record | null { return value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : null; } export function parseJobStatusRpcResult(row: unknown, rpcName: string): JobStatusRpcResult | null { - const direct = asRecord(row); - const nested = asRecord(direct?.[rpcName]); + const direct = asObjectRecord(row); + const nested = asObjectRecord(direct?.[rpcName]); const candidate = direct?.ok !== undefined ? direct : nested; if (!candidate) return null; const ok = candidate.ok === true; const gatePassed = candidate.gate_passed === true; const status = typeof candidate.status === "string" ? candidate.status : "missing_result"; - const missing = Array.isArray(candidate.missing) - ? candidate.missing.map((item) => String(item)) - : ["completion_rpc_failed"]; + const missing = + candidate.missing === null + ? [] + : Array.isArray(candidate.missing) + ? candidate.missing.map((item) => String(item)) + : ["completion_rpc_failed"]; return { ok, gate_passed: gatePassed, status, missing }; } diff --git a/tests/indexing-v3-agent.test.ts b/tests/indexing-v3-agent.test.ts index 297fcfcbd7..e7ecb55f11 100644 --- a/tests/indexing-v3-agent.test.ts +++ b/tests/indexing-v3-agent.test.ts @@ -210,4 +210,25 @@ describe("indexing-v3-agent behavior", () => { missing: [], }); }); + + it("treats null missing arrays from completion RPCs as no missing artifacts", () => { + const result = parseJobStatusRpcResult( + { + complete_strict_enrichment_job: { + ok: true, + gate_passed: true, + status: "completed", + missing: null, + }, + }, + "complete_strict_enrichment_job", + ); + + expect(result).toEqual({ + ok: true, + gate_passed: true, + status: "completed", + missing: [], + }); + }); });