import assert from "node:assert/strict"; import { access, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { createApp } from "../../src/app.js"; import { env } from "../../src/config/env.js"; import { CatalogError } from "../../src/modules/catalog/errors.js"; import { OcrIndexingService, type ApprovedOcrCandidate, type OcrIndexingStore } from "../../src/modules/ocr/indexing.js"; import { classifyOcrReviewError, OcrReviewService, PostgresOcrReviewStore, type OcrReviewCandidate } from "../../src/modules/ocr/review.js"; import { DurableOcrReviewReader } from "../../src/modules/ocr/review.js"; import { OcrArtifactError, persistComposedCandidateArtifact, persistOcrResultArtifact, persistReviewImageArtifacts, readReviewedPagesArtifact, stageOcrArtifacts } from "../../src/modules/ocr/artifacts.js"; import { sha256Hex } from "../../src/shared/utils/ids.js"; import { buildOcrIdentity } from "../../src/modules/ocr/client.js"; function candidate(state: OcrReviewCandidate["state"] = "review_required"): OcrReviewCandidate { const lines = ["CBGO4a", "FATo7"].map((text, index) => ({ lineId: `line-${index + 1}`, text, confidence: 0.7, bbox: [0, index * 10, 50, index * 10 + 8] as [number, number, number, number], lineSha256: sha256Hex(text) })); return { versionId: "version-2", sourceId: "source-1", state, candidateSha256: "candidate-hash", baseActiveVersionId: "version-1", currentActiveVersionId: "version-1", activateRequested: true, processingFingerprint: "fingerprint", metadataHash: "metadata", documents: [{ documentId: "document-1", pages: [{ page: 1, imageUrl: "/private/page-1.png", nativeText: "", ocr: { text: "CBGO4a\nFATo7", lines }, candidateText: "CBGO4a\nFATo7", differences: ["native text is empty"], risks: ["CBGO4a", "FATo7"] }] }] }; } test("review HTTP rejects unauthorized and premature access without exposing artifacts", async (context) => { const previous = { token: env.lifecycleAdminToken, enabled: env.ocrIngestEnabled }; Object.assign(env, { lifecycleAdminToken: "review-token", ocrIngestEnabled: true }); context.after(() => Object.assign(env, { lifecycleAdminToken: previous.token, ocrIngestEnabled: previous.enabled })); let value = candidate(); let reads = 0; const review = new OcrReviewService({ async loadCandidate() { reads += 1; return value; }, async commitApproval() { throw new Error("not called"); } }); const server = createApp({ reviewService: review, startReconciler: false }).listen(0); context.after(() => server.close()); const address = server.address(); assert.ok(address && typeof address === "object"); const url = `http://127.0.0.1:${address.port}/ingestions/version-2/review`; const unauthorized = await fetch(url); assert.equal(unauthorized.status, 401); assert.equal((await fetch(url.replace("/review", "/approve"), { method: "POST" })).status, 401); assert.equal(reads, 0); value = candidate("indexing"); const premature = await fetch(url, { headers: { authorization: "Bearer review-token" } }); assert.equal(premature.status, 409); assert.deepEqual(await premature.json(), { ok: false, error: "OCR review is not available in the current lifecycle state", code: "OCR_REVIEW_STATE_INVALID", action: "inspect_ingestion_status" }); }); test("review exposes audit detail and commits current corrections as one immutable set", async () => { const commits: unknown[] = []; const service = new OcrReviewService({ async loadCandidate() { return candidate(); }, async commitApproval(value) { commits.push(value); } }); const review = await service.view("version-2"); assert.deepEqual(review.documents[0]?.pages[0]?.ocr.lines.map(({ text, confidence, bbox }) => [text, confidence, bbox]), [ ["CBGO4a", 0.7, [0, 0, 50, 8]], ["FATo7", 0.7, [0, 10, 50, 18]] ]); assert.deepEqual(review.documents[0]?.pages[0]?.risks, ["CBGO4a", "FATo7"]); const approved = await service.approve("version-2", { candidateSha256: "candidate-hash", expectedActiveVersionId: "version-1", reviewedBy: "admin", corrections: [ { documentId: "document-1", page: 1, lineId: "line-1", expectedLineSha256: sha256Hex("CBGO4a"), replacementText: "CBG04a" }, { documentId: "document-1", page: 1, lineId: "line-2", expectedLineSha256: sha256Hex("FATo7"), replacementText: "FAT07" } ] }); assert.equal(commits.length, 1); assert.deepEqual((commits[0] as { corrections: Array<{ replacementText: string }> }).corrections.map(({ replacementText }) => replacementText), ["CBG04a", "FAT07"]); assert.equal(approved.reviewedText, "CBG04a\nFAT07"); assert.equal(approved.reviewedTextSha256, sha256Hex("CBG04a\nFAT07")); }); test("stale or duplicate corrections conflict before any correction or transition", async () => { let commits = 0; const service = new OcrReviewService({ async loadCandidate() { return candidate(); }, async commitApproval() { commits += 1; } }); const base = { candidateSha256: "stale", expectedActiveVersionId: "version-1", reviewedBy: "admin", corrections: [] }; await assert.rejects(service.approve("version-2", base), (error) => error instanceof CatalogError && error.statusCode === 409); const duplicate = { ...base, candidateSha256: "candidate-hash", corrections: Array(2).fill({ documentId: "document-1", page: 1, lineId: "line-1", expectedLineSha256: sha256Hex("CBGO4a"), replacementText: "CBG04a" }) }; await assert.rejects(service.approve("version-2", duplicate), (error) => error instanceof CatalogError && error.code === "CORRECTION_CONFLICT"); await assert.rejects(service.approve("version-2", { ...base, candidateSha256: "candidate-hash", expectedActiveVersionId: "version-old" }), (error) => error instanceof CatalogError && error.code === "ACTIVE_VERSION_CHANGED"); await assert.rejects(service.approve("version-2", { ...base, candidateSha256: "candidate-hash", corrections: [{ documentId: "document-1", page: 1, lineId: "line-1", expectedLineSha256: sha256Hex("stale"), replacementText: "CBG04a" }] }), (error) => error instanceof CatalogError && error.code === "CORRECTION_CONFLICT"); assert.equal(commits, 0); }); function approved(activateRequested: boolean, state: ApprovedOcrCandidate["state"] = "indexing"): ApprovedOcrCandidate { return { versionId: "version-2", sourceId: "source-1", state, activateRequested, expectedActiveVersionId: "version-1", reviewedText: "reviewed", reviewedTextSha256: sha256Hex("reviewed"), processingFingerprint: "fingerprint", metadataHash: "metadata" }; } test("indexing activates only when requested and leaves a new candidate ready on an activation race", async () => { const calls: string[] = []; const store: OcrIndexingStore = { async findReusableVersion() { return undefined; }, async indexReviewed() { calls.push("embed"); return 1; }, async markReady() { calls.push("ready"); }, async settleReusable() { throw new Error("not reusable"); }, async activateVersion() { calls.push("activate"); return "version-2"; } }; const service = new OcrIndexingService(store); assert.deepEqual(await service.index(approved(true)), { versionId: "version-2", state: "active", activated: true, activatedVersionId: "version-2" }); assert.deepEqual(calls.splice(0), ["embed", "ready", "activate"]); assert.deepEqual(await service.index(approved(false)), { versionId: "version-2", state: "ready", activated: false }); assert.deepEqual(calls.splice(0), ["embed", "ready"]); store.activateVersion = async () => { throw new CatalogError("race", 409, "ACTIVE_VERSION_PRECONDITION_FAILED"); }; await assert.rejects(service.index(approved(true)), (error) => error instanceof CatalogError && error.code === "ACTIVE_VERSION_CHANGED"); assert.deepEqual(calls, ["embed", "ready"]); }); test("reusable and rejected candidates create no embeddings and reusable activation obeys CAS intent", async () => { const calls: string[] = []; const store: OcrIndexingStore = { async findReusableVersion() { return { versionId: "version-existing" }; }, async indexReviewed() { calls.push("embed"); return 1; }, async markReady() { calls.push("ready"); }, async settleReusable(_candidate, _reusable, activate) { calls.push(`settle:${activate}`); return activate; }, async activateVersion() { throw new Error("new activation must not run"); } }; const service = new OcrIndexingService(store); assert.deepEqual(await service.index(approved(true)), { versionId: "version-2", state: "rejected", activated: true, activatedVersionId: "version-existing", errorCode: "DUPLICATE_REUSABLE_VERSION" }); assert.deepEqual(await service.index(approved(false)), { versionId: "version-2", state: "rejected", activated: false, errorCode: "DUPLICATE_REUSABLE_VERSION" }); store.settleReusable = async () => { throw new CatalogError("race", 409, "ACTIVE_VERSION_PRECONDITION_FAILED"); }; await assert.rejects(service.index(approved(true)), (error) => error instanceof CatalogError && error.code === "ACTIVE_VERSION_CHANGED"); await assert.rejects(service.index(approved(true, "rejected")), (error) => error instanceof CatalogError && error.code === "INVALID_VERSION_STATE"); assert.deepEqual(calls, ["settle:true", "settle:false"]); }); test("authenticated rejection is durable, conflict-safe, and never indexes or activates", async (context) => { const previous = { token: env.lifecycleAdminToken, enabled: env.ocrIngestEnabled }; Object.assign(env, { lifecycleAdminToken: "review-token", ocrIngestEnabled: true }); context.after(() => Object.assign(env, { lifecycleAdminToken: previous.token, ocrIngestEnabled: previous.enabled })); let value = candidate(); let reads = 0; const audits: Array<{ reviewedBy: string; reason: string }> = []; const review = new OcrReviewService({ async loadCandidate() { reads += 1; return value; }, async commitApproval() { throw new Error("approval must not run"); }, async commitRejection({ reviewedBy, reason }) { audits.push({ reviewedBy, reason }); value = { ...value, state: "rejected" }; } }); let indexingCalls = 0; const catalog = { async getIngestionStatus() { return { versionId: value.versionId, state: value.state, phase: value.state, activated: false, error: value.state === "rejected" ? { code: "OCR_REJECTED", message: audits[0]?.reason, retryable: false } : null }; } }; const server = createApp({ catalog: catalog as never, reviewService: review, indexingService: { async index() { indexingCalls += 1; throw new Error("indexing must not run"); } }, startReconciler: false }).listen(0); context.after(() => server.close()); const address = server.address(); assert.ok(address && typeof address === "object"); const url = `http://127.0.0.1:${address.port}/ingestions/version-2`; const unauthorized = await fetch(`${url}/reject`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ candidateSha256: "candidate-hash", reviewedBy: "admin", reason: "Unreadable code" }) }); assert.equal(unauthorized.status, 401); assert.equal(reads, 0); const invalid = await fetch(`${url}/reject`, { method: "POST", headers: { authorization: "Bearer review-token", "content-type": "application/json" }, body: "{}" }); assert.equal(invalid.status, 400); assert.deepEqual(await invalid.json(), { ok: false, error: "Candidate hash, reviewer, and rejection reason are required", code: "INVALID_REJECTION", action: "retry_or_contact_support" }); assert.equal(reads, 0); const stale = await fetch(`${url}/reject`, { method: "POST", headers: { authorization: "Bearer review-token", "content-type": "application/json" }, body: JSON.stringify({ candidateSha256: "stale", reviewedBy: "admin", reason: "Unreadable code" }) }); assert.equal(stale.status, 409); assert.equal(audits.length, 0); const rejected = await fetch(`${url}/reject`, { method: "POST", headers: { authorization: "Bearer review-token", "content-type": "application/json" }, body: JSON.stringify({ candidateSha256: "candidate-hash", reviewedBy: "admin", reason: "Unreadable code" }) }); assert.equal(rejected.status, 200); assert.deepEqual(await rejected.json(), { versionId: "version-2", state: "rejected", activated: false }); assert.deepEqual(audits, [{ reviewedBy: "admin", reason: "Unreadable code" }]); const status = await fetch(url, { headers: { authorization: "Bearer review-token" } }); assert.equal(status.status, 200); assert.deepEqual(await status.json(), { versionId: "version-2", state: "rejected", phase: "rejected", activated: false, error: { code: "OCR_REJECTED", message: "Unreadable code", retryable: false } }); const repeated = await fetch(`${url}/reject`, { method: "POST", headers: { authorization: "Bearer review-token", "content-type": "application/json" }, body: JSON.stringify({ candidateSha256: "candidate-hash", reviewedBy: "admin", reason: "Again" }) }); assert.equal(repeated.status, 409); assert.equal(indexingCalls, 0); assert.equal(audits.length, 1); }); test("review routes expose safe actionable errors and recovery stays explicit", async (context) => { const previous = { token: env.lifecycleAdminToken, enabled: env.ocrIngestEnabled }; Object.assign(env, { lifecycleAdminToken: "review-token", ocrIngestEnabled: true }); context.after(() => Object.assign(env, previous)); const recoveries: Array<{ recoveredBy: string; reason: string }> = []; const review = { async view() { throw new Error("unexpected database connection failure"); }, async approve() { throw new Error("not called"); }, async reject() { throw new Error("not called"); } }; const recovery = { async recover(_versionId: string, input: { recoveredBy: string; reason: string }) { recoveries.push(input); return { versionId: "version-2", state: "failed" as const, outcome: "closed_failed" as const }; } }; const server = createApp({ reviewService: review, reviewRecoveryService: recovery, startReconciler: false }).listen(0); context.after(() => server.close()); const address = server.address(); assert.ok(address && typeof address === "object"); const base = `http://127.0.0.1:${address.port}/ingestions/version-2`; const unsafe = await fetch(`${base}/review`, { headers: { authorization: "Bearer review-token" } }); assert.equal(unsafe.status, 500); assert.deepEqual(await unsafe.json(), { ok: false, error: "Unexpected OCR review failure", code: "OCR_REVIEW_UNEXPECTED", action: "retry_or_contact_support" }); const unauthorized = await fetch(`${base}/recover`, { method: "POST" }); assert.equal(unauthorized.status, 401); const recovered = await fetch(`${base}/recover`, { method: "POST", headers: { authorization: "Bearer review-token", "content-type": "application/json" }, body: JSON.stringify({ recoveredBy: "admin", reason: "Missing durable candidate" }) }); assert.equal(recovered.status, 200); assert.deepEqual(await recovered.json(), { versionId: "version-2", state: "failed", outcome: "closed_failed" }); assert.deepEqual(recoveries, [{ recoveredBy: "admin", reason: "Missing durable candidate" }]); assert.deepEqual(classifyOcrReviewError(new OcrArtifactError("missing", 409, "OCR_ARTIFACT_UNAVAILABLE")), new CatalogError("missing", 409, "OCR_ARTIFACT_UNAVAILABLE")); }); test("playground serves the authenticated OCR review controls and audit fields", async (context) => { const server = createApp({ startReconciler: false }).listen(0); context.after(() => server.close()); const address = server.address(); assert.ok(address && typeof address === "object"); const base = `http://127.0.0.1:${address.port}`; const [html, script, styles] = await Promise.all([ fetch(`${base}/playground`).then((response) => response.text()), fetch(`${base}/playground/app.js`).then((response) => response.text()), fetch(`${base}/playground/styles.css`).then((response) => response.text()) ]); for (const marker of ["data-tab=\"review\"", "reviewVersionId", "reviewToken", "loadReviewButton", "approveReviewButton", "rejectReviewButton", "reviewCandidate"]) assert.match(html, new RegExp(marker)); for (const marker of ["Authorization", "/review", "/approve", "/reject", "candidateSha256", "expectedLineSha256", "imageUrl", "nativeText", "confidence", "bbox", "differences", "risks"]) assert.match(script, new RegExp(marker)); assert.match(script, /documentId: candidateDocument\.documentId/u); assert.doesNotMatch(script, /documentId: document\.documentId/u); assert.match(styles, /\.review-page/); }); test("production review reader survives restart and fails closed on unauthorized or mismatched durable evidence", async (context) => { const previous = { token: env.lifecycleAdminToken, enabled: env.ocrIngestEnabled, root: env.ocrArtifactRoot }; const rootDirectory = await mkdtemp(path.join(os.tmpdir(), "rag-review-loader-")); const versionId = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"; const documentId = "doc:review"; const original = Buffer.from("%PDF-review"); const text = "codigo CBGO4a has enough OCR characters for durable review"; Object.assign(env, { lifecycleAdminToken: "review-token", ocrIngestEnabled: true, ocrArtifactRoot: rootDirectory }); context.after(async () => { Object.assign(env, { lifecycleAdminToken: previous.token, ocrIngestEnabled: previous.enabled, ocrArtifactRoot: previous.root }); await rm(rootDirectory, { recursive: true, force: true }); }); await stageOcrArtifacts({ rootDirectory, versionId, createdAt: "2026-09-16T12:00:00.000Z", documents: [{ documentId, documentKey: "review.pdf", bytes: original, requestedPages: [2], pages: [ { page: 1, text: "native text with enough content", rasterCoverage: 0, textSha256: sha256Hex("native text with enough content") }, { page: 2, text: "weak native", rasterCoverage: 1, textSha256: sha256Hex("weak native") } ] }] }); const identity = buildOcrIdentity({ documentSha256: sha256Hex(original), pages: [2], idempotencyKey: "review-key" }); const result = { schemaVersion: "1" as const, jobId: "ocr-review", ...identity, engine: { name: "paddleocr" as const, version: "3.4.0" as const, runtime: "paddlepaddle-3.2.2" as const, device: "cpu" as const, configVersion: "ocr-v2" as const, dpi: 200 as const }, pages: [{ page: 2, width: 100, height: 100, processingMs: 1, text, metrics: { lineCount: 1, nonWhitespaceCharacters: 50, inkCoverage: 0.5, medianConfidence: 0.95, p10Confidence: 0.95, lowConfidenceLineRatio: 0 }, lines: [{ lineId: "p1-l1", text, confidence: 0.95, bbox: [1, 2, 30, 10] as [number, number, number, number] }] }] }; await persistOcrResultArtifact({ rootDirectory, versionId, documentId, result }); const png = Buffer.from("89504e470d0a1a0a0102", "hex"); const images = await persistReviewImageArtifacts({ rootDirectory, versionId, documentId, images: [{ page: 2, bytes: png, sha256: sha256Hex(png) }] }); const { candidate: durable } = await persistComposedCandidateArtifact({ rootDirectory, versionId, jobs: [{ documentId, remoteJobId: "ocr-review", requestedPages: [2], state: "succeeded" }] }); const pages = durable.documents[0]!.pages; let reads = 0; const contextValue = { versionId, sourceId: "source-1", state: "review_required" as const, baseActiveVersionId: null, currentActiveVersionId: null, activateRequested: false, processingFingerprint: "fingerprint", metadataHash: "metadata", pages: pages.map((page) => ({ documentId, page: page.page, nativeTextSha256: page.nativeTextSha256, ocrTextSha256: page.ocrTextSha256, candidateTextSha256: page.candidateTextSha256, metrics: page.metrics, risks: page.risks, qualityOutcome: page.qualityOutcome, warnings: page.warnings, blockingReasons: page.blockingReasons, primaryBlockingReason: page.primaryBlockingReason, qualityReportSha256: page.qualityReportSha256 })) }; const catalog = { async loadOcrReviewContext() { reads += 1; return contextValue; } }; const restarted = new DurableOcrReviewReader(catalog, rootDirectory); const review = await restarted.view(versionId); assert.equal(review.documents[0]!.pages.find(({ page }) => page === 1)!.imageUrl, null); assert.equal(review.documents[0]!.pages.find(({ page }) => page === 2)!.ocr.lines[0]!.lineSha256, sha256Hex(text)); assert.deepEqual((await restarted.image(versionId, documentId, 2)).bytes, png); const server = createApp({ catalog: catalog as never, startReconciler: false }).listen(0); context.after(() => server.close()); const address = server.address(); assert.ok(address && typeof address === "object"); const base = `http://127.0.0.1:${address.port}/ingestions/${versionId}`; assert.equal((await fetch(`${base}/review`)).status, 401); assert.equal(reads, 2); assert.equal((await fetch(`${base}/review`, { headers: { authorization: "Bearer review-token" } })).status, 200); const image = await fetch(`${base}/documents/${encodeURIComponent(documentId)}/pages/2/image`, { headers: { authorization: "Bearer review-token" } }); assert.equal(image.status, 200); assert.deepEqual(Buffer.from(await image.arrayBuffer()), png); assert.equal((await fetch(`${base}/approve`, { method: "POST", headers: { authorization: "Bearer review-token", "content-type": "application/json" }, body: "{}" })).status, 503); assert.equal((await fetch(`${base}/reject`, { method: "POST", headers: { authorization: "Bearer review-token", "content-type": "application/json" }, body: "{}" })).status, 503); contextValue.pages[0]!.candidateTextSha256 = "0".repeat(64); assert.equal((await fetch(`${base}/review`, { headers: { authorization: "Bearer review-token" } })).status, 422); contextValue.pages[0]!.candidateTextSha256 = pages[0]!.candidateTextSha256; await assert.rejects(restarted.image(versionId, "../escape", 1), /not found/i); await writeFile(images.images[0]!.artifactPath, "corrupt", { mode: 0o600 }); await assert.rejects(restarted.image(versionId, documentId, 2), /integrity validation failed/); }); function decisionPool(current: OcrReviewCandidate, state = "review_required", failReviewedPage = false) { const sql: string[] = []; const client = { async query(statement: string) { sql.push(statement); if (/^(BEGIN|COMMIT|ROLLBACK|SET CONSTRAINTS)/u.test(statement.trim())) return { rowCount: 0, rows: [] }; if (statement.includes("FOR UPDATE OF v, s")) return { rowCount: 1, rows: [{ version_id: current.versionId, source_id: current.sourceId, state, base_active_version_id: current.baseActiveVersionId, current_active_version_id: current.currentActiveVersionId, activate_requested: current.activateRequested, processing_fingerprint: current.processingFingerprint, metadata_hash: current.metadataHash }] }; if (statement.includes("FROM rag_document_pages") && statement.includes("FOR UPDATE")) return { rowCount: 1, rows: [{ document_id: current.documents[0]!.documentId, page_number: 1, native_text_hash: sha256Hex(current.documents[0]!.pages[0]!.nativeText), ocr_text_hash: sha256Hex(current.documents[0]!.pages[0]!.ocr.text), candidate_text_hash: sha256Hex(current.documents[0]!.pages[0]!.candidateText), risk_tokens: current.documents[0]!.pages[0]!.risks }] }; if (failReviewedPage && statement.includes("SET reviewed_text_hash")) return { rowCount: 0, rows: [] }; return { rowCount: 1, rows: [] }; }, release() {} }; return { pool: { connect: async () => client } as never, sql }; } test("production decision store commits corrected reviewed pages and the indexing transition in one locked transaction", async (context) => { const rootDirectory = await mkdtemp(path.join(os.tmpdir(), "rag-review-decision-")); const current = candidate(); current.versionId = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"; await mkdir(path.join(rootDirectory, current.versionId)); context.after(() => rm(rootDirectory, { recursive: true, force: true })); const { pool, sql } = decisionPool(current); const store = new PostgresOcrReviewStore(pool, { async view() { return structuredClone(current); } }, rootDirectory); const approved = await new OcrReviewService(store).approve(current.versionId, { candidateSha256: current.candidateSha256, expectedActiveVersionId: current.baseActiveVersionId, reviewedBy: "admin", corrections: [{ documentId: "document-1", page: 1, lineId: "line-1", expectedLineSha256: sha256Hex("CBGO4a"), replacementText: "CBG04a" }] }); const reviewed = await readReviewedPagesArtifact({ rootDirectory, versionId: current.versionId, candidateSha256: current.candidateSha256, reviewedTextSha256: approved.reviewedTextSha256 }); assert.equal(reviewed.documents[0]!.pages[0]!.candidateText, "CBG04a\nFATo7"); assert.match(sql.join("\n"), /FOR UPDATE OF v, s/u); assert.match(sql.join("\n"), /INSERT INTO rag_review_corrections/u); assert.match(sql.join("\n"), /reviewed_text_hash/u); assert.match(sql.join("\n"), /SET state = 'indexing'/u); assert.equal(sql.at(-1)?.trim(), "COMMIT"); }); test("production decisions reject stale, replayed, and path-corrupt writes without durable or lifecycle side effects", async (context) => { const rootDirectory = await mkdtemp(path.join(os.tmpdir(), "rag-review-conflict-")); const current = candidate(); current.versionId = "cccccccc-cccc-4ccc-8ccc-cccccccccccc"; context.after(() => rm(rootDirectory, { recursive: true, force: true })); const input = { candidateSha256: current.candidateSha256, expectedActiveVersionId: current.baseActiveVersionId, reviewedBy: "admin", corrections: [] }; for (const conflictState of ["indexing", "rejected"]) { const { pool, sql } = decisionPool(current, conflictState); const service = new OcrReviewService(new PostgresOcrReviewStore(pool, { async view() { return structuredClone(current); } }, rootDirectory)); await assert.rejects(service.approve(current.versionId, input), (error) => error instanceof CatalogError && error.statusCode === 409); assert.doesNotMatch(sql.join("\n"), /INSERT INTO rag_review_corrections|SET state = 'indexing'/u); } const { pool, sql } = decisionPool(current); const service = new OcrReviewService(new PostgresOcrReviewStore(pool, { async view() { return structuredClone(current); } }, rootDirectory)); await assert.rejects(service.approve(current.versionId, input), /ENOENT|artifact directory/u); assert.doesNotMatch(sql.join("\n"), /INSERT INTO rag_review_corrections|SET state = 'indexing'/u); await assert.rejects(access(path.join(rootDirectory, current.versionId, "reviewed-pages.json"))); await mkdir(path.join(rootDirectory, current.versionId)); const partial = decisionPool(current, "review_required", true); const partialService = new OcrReviewService(new PostgresOcrReviewStore(partial.pool, { async view() { return structuredClone(current); } }, rootDirectory)); await assert.rejects(partialService.approve(current.versionId, input), /page evidence changed/u); assert.equal(partial.sql.at(-1)?.trim(), "ROLLBACK"); await assert.rejects(access(path.join(rootDirectory, current.versionId, "reviewed-pages.json"))); }); test("production rejection records the bound reason without reviewed, correction, indexing, or activation artifacts", async (context) => { const rootDirectory = await mkdtemp(path.join(os.tmpdir(), "rag-review-rejection-")); const current = candidate(); current.versionId = "dddddddd-dddd-4ddd-8ddd-dddddddddddd"; await mkdir(path.join(rootDirectory, current.versionId)); context.after(() => rm(rootDirectory, { recursive: true, force: true })); const { pool, sql } = decisionPool(current); const service = new OcrReviewService(new PostgresOcrReviewStore(pool, { async view() { return structuredClone(current); } }, rootDirectory)); assert.deepEqual(await service.reject(current.versionId, { candidateSha256: current.candidateSha256, reviewedBy: " admin ", reason: " unreadable code " }), { versionId: current.versionId, state: "rejected", activated: false }); const statements = sql.join("\n"); assert.match(statements, /error_code = 'OCR_REJECTED'.*error_detail =/su); assert.doesNotMatch(statements, /rag_review_corrections|reviewed_text_hash|state = 'indexing'/u); await assert.rejects(access(path.join(rootDirectory, current.versionId, "reviewed-pages.json"))); });