import { expect, test, vi } from "vitest"; import { continueEvidencePreprocessing, preprocessEvidence, type EvidenceJobState, type EvidencePreprocessingDependencies, } from "../../../src/workspaces/evidence/preprocessing.js"; import type { WorkspaceDescriptor } from "../../../src/workspaces/schema.js"; type EvidenceConfig = NonNullable; const filesystemEvidence = { source: { type: "filesystem", uri: "research/evidence" }, } as EvidenceConfig; const privateHttpEvidence = { source: { type: "http", uris: ["http://127.0.0.1/private.md"], authentication: "none", connect_timeout_ms: 1000, read_timeout_ms: 2000, max_bytes: 100, max_redirects: 0, allow_private_hosts: true, max_cache_bytes: 100, }, } as EvidenceConfig; function job(overrides: Partial = {}): EvidenceJobState { return { runId: "a".repeat(32), childRuns: {}, completedStages: [], ...overrides, }; } function dependencies(payload: Record = {}): EvidencePreprocessingDependencies & { runStage: ReturnType; persistJob: ReturnType; semanticPreflight: ReturnType; } { return { runStage: vi.fn(async () => payload), persistJob: vi.fn(), semanticPreflight: vi.fn(async () => ({ ok: true as const })), requireRunId(value) { if (typeof value !== "string" || !/^[0-9a-f]{32}$/.test(value)) { throw new Error("child run id is invalid"); } return value; }, numberRecord(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return undefined; return Object.fromEntries( Object.entries(value as Record).map(([key, nested]) => [key, Number(nested)]), ); }, }; } test("owns the standalone Evidence stage argv and mutation order", async () => { const state = job({ childRuns: { evidence: "b".repeat(32) } }); const deps = dependencies({ run_id: "c".repeat(32), counts: { added: 2 } }); const result = await preprocessEvidence( { evidence: filesystemEvidence, job: state, dryRun: false }, deps, ); expect(deps.semanticPreflight).toHaveBeenCalledOnce(); expect(deps.runStage).toHaveBeenCalledWith([ "preprocess", "evidence", "--resume", "b".repeat(32), "--json", "-c", "/dev/fd/3", ]); expect(deps.persistJob).toHaveBeenCalledOnce(); expect(state).toMatchObject({ childRuns: { evidence: "c".repeat(32) }, completedStages: ["evidence"], }); expect(result).toEqual({ status: "succeeded", code: "ok", runId: "a".repeat(32), childRuns: { evidence: "c".repeat(32) }, completedStages: ["evidence"], counts: { added: 2 }, }); }); test("owns Evidence egress refusal before shared semantic infrastructure", async () => { const deps = dependencies(); const result = await preprocessEvidence( { evidence: privateHttpEvidence, job: job(), httpPrivateHostAllowlist: ["metadata.internal"], }, deps, ); expect(result).toEqual({ status: "failed", code: "egress_policy_refused" }); expect(deps.semanticPreflight).not.toHaveBeenCalled(); expect(deps.runStage).not.toHaveBeenCalled(); expect(deps.persistJob).not.toHaveBeenCalled(); }); test("projects aggregate no-Evidence and completed-stage outcomes without rerunning", async () => { const deps = dependencies(); const noEvidence = await continueEvidencePreprocessing( { evidence: undefined, job: job({ completedStages: ["dwh", "schema_index"] }), priorCounts: { added: 2 }, }, deps, ); const completed = await continueEvidencePreprocessing( { evidence: filesystemEvidence, job: job({ completedStages: ["dwh", "schema_index", "evidence"] }), }, deps, ); expect(noEvidence).toMatchObject({ status: "succeeded", code: "ok", warnings: ["workspace has no Evidence source"], counts: { added: 2 }, }); expect(completed).toMatchObject({ status: "unchanged", code: "ok", completedStages: ["dwh", "schema_index", "evidence"], }); expect(deps.runStage).not.toHaveBeenCalled(); expect(deps.persistJob).not.toHaveBeenCalled(); }); test("preserves the narrow standalone projection for an already completed Evidence stage", async () => { const deps = dependencies(); const result = await preprocessEvidence( { evidence: filesystemEvidence, job: job({ childRuns: { evidence: "b".repeat(32) }, completedStages: ["evidence"] }), }, deps, ); expect(result).toEqual({ status: "unchanged", code: "ok", runId: "a".repeat(32), completedStages: ["evidence"], }); expect(deps.runStage).not.toHaveBeenCalled(); expect(deps.persistJob).not.toHaveBeenCalled(); });