Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions supabase/functions/indexing-v3-agent/behavior.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,36 @@ export type DeferralDecision = {
};
};

export type JobStatusRpcResult = {
ok: boolean;
gate_passed: boolean;
missing: string[] | null;
status: string;
};

function asObjectRecord(value: unknown): Record<string, unknown> | null {
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : null;
}

export function parseJobStatusRpcResult(row: unknown, rpcName: string): JobStatusRpcResult | null {
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 =
candidate.missing === null
? []
: 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: {
Expand Down
13 changes: 4 additions & 9 deletions supabase/functions/indexing-v3-agent/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,9 +4,11 @@ import {
completionGateFromRow,
deferralDecision,
missingArtifactPlan,
parseJobStatusRpcResult,
shouldRunVisualArtifacts,
type CompletionGate,
type CompletionGateRow,
type JobStatusRpcResult,
type MissingArtifactPlan,
} from "./behavior.ts";

Expand DownExpand Up@@ -1922,14 +1924,7 @@ async function deferJob(job: ClaimedJob, gate: CompletionGate): Promise<void> {
}

async function completeJob(job: ClaimedJob): Promise<void> {
const rows = await sql<
Array<{
ok: boolean;
gate_passed: boolean;
missing: string[] | null;
status: string;
}>
>`
const rows = await sql<JobStatusRpcResult[]>`
select *
from public.complete_strict_enrichment_job(
${job.document_id}::uuid,
Expand All@@ -1939,7 +1934,7 @@ async function completeJob(job: ClaimedJob): Promise<void> {
'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({
Expand Down
62 changes: 62 additions & 0 deletions tests/indexing-v3-agent.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
deferralDecision,
metadataNumber,
missingArtifactPlan,
parseJobStatusRpcResult,
shouldRunVisualArtifacts,
type CompletionGateRow,
} from "../supabase/functions/indexing-v3-agent/behavior";
Expand DownExpand Up@@ -169,4 +170,65 @@ 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: [],
});
});

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: [],
});
});
});
Loading