diff --git a/docs/LOCAL_OCR.md b/docs/LOCAL_OCR.md index f7d8e5e..72917ff 100644 --- a/docs/LOCAL_OCR.md +++ b/docs/LOCAL_OCR.md @@ -115,6 +115,24 @@ are not accepted as completed proposals and are not silently deleted. ## Limits +Current-source macOS Vision qualification can also exercise the actual stdio +server without touching a desktop application's configuration: + +```sh +node scripts/macos-local-ocr-smoke.mjs --pdf /absolute/two-page-scan.pdf --expect-source-sha256 --python /absolute/venv/bin/python --helper /absolute/local-ocr-proposal.py --encrypted-pdf /absolute/encrypted-fixture.pdf --output-dir /absolute/new-private-evidence --job-pages 1,2 +``` + +The check requires the exact current default tool names and all four configured +OCR tools with their source input/output schemas and annotations. It retains +one page proposal, creates a two-page job without OCR, +processes its first page, closes its own server, and resumes the second page +in a fresh server process. Repeating the completed resume must do no new page +work. Retained image, proposal, commit and review hashes are checked, and the +original PDF must stay unchanged. Omitting `--job-pages` performs the single-page +check only. Missing consent, stale source identity and encrypted input must +be refused. This is local protocol and engine evidence, not installed Claude +or ChatGPT acceptance, independent word verification or general scan accuracy. + - One active OCR page operation per server and a shared 120-second generation/replay budget per page with process-group termination. The single-page tool keeps its existing contract; a job resume handles at most diff --git a/scripts/local-ocr-smoke-contract.mjs b/scripts/local-ocr-smoke-contract.mjs new file mode 100644 index 0000000..3f4dcba --- /dev/null +++ b/scripts/local-ocr-smoke-contract.mjs @@ -0,0 +1,57 @@ +import assert from "node:assert/strict"; + +export function assertLocalOcrSmokeEncryptedRefusal(error) { + assert.equal(error.code, 2, "Encryption control failed for an unexpected reason"); + assert.equal(error.stdout, ""); + assert.deepEqual(JSON.parse(error.stderr), { status: "ocr_proposal_failed", + message: "Optional OCR does not accept encrypted PDFs" }); +} + +// Check the current default name inventory, not a historical tool count. +// Optional input/output schemas and annotations match the source definitions. +export function assertLocalOcrSmokeCatalog(tools, manifestTools, optionalTools, enabled) { + const names = definitions => { + assert(Array.isArray(definitions) && definitions.length > 0, "Tool inventory must be nonempty"); + const result = definitions.map(tool => { + assert(typeof tool.name === "string" && tool.name.length > 0, "Invalid tool name"); + return tool.name; + }); + assert.equal(new Set(result).size, result.length, "Duplicate tool name"); + return result.sort(); + }; + const defaults = names(manifestTools); + const optional = names(optionalTools); + assert(!optional.some(name => defaults.includes(name)), "Optional tool in default inventory"); + const expected = enabled ? [...defaults, ...optional].sort() : defaults; + assert.deepEqual(names(tools), expected, "Live tool catalog differs from the current source inventory"); + if (enabled) { + for (const definition of optionalTools) { + const observed = tools.find(tool => tool.name === definition.name); + assert.deepEqual(observed.inputSchema, definition.inputSchema, "Optional input schema drift"); + assert.deepEqual(observed.annotations, definition.annotations, "Optional annotations drift"); + assert(definition.outputSchema?.type === "object", "Expected optional output schema missing"); + assert.deepEqual(observed.outputSchema, definition.outputSchema, "Optional output schema drift"); + } + } + return expected.length; +} + +export function assertLocalOcrSmokeProgress(result, { pages, completed, processed, sourceSha256, job }) { + assert(result && result.verification_status === "unverified_ocr_proposals", "OCR words must remain unverified"); + assert.equal(result.source_pdf_sha256, sourceSha256); + if (job) { + assert.equal(result.job_id, job.job_id); + assert.equal(result.job_identity_sha256, job.job_identity_sha256); + } + const remaining = pages.filter(page => !completed.includes(page)); + assert.equal(result.page_count, pages.length); + assert.equal(result.completed_count, completed.length); + assert.equal(result.remaining_count, remaining.length); + assert.deepEqual(result.completed_pages.map(page => page.page_number), completed); + assert.deepEqual(result.remaining_pages, remaining); + assert.equal(result.processed_this_call, processed); + assert.equal(result.failed_page, null, "An OCR page failed"); + assert.equal(result.failure_code, null, "An OCR page failed"); + assert.equal(result.status, remaining.length === 0 ? "ocr_job_complete" + : completed.length === 0 ? "ocr_job_ready" : "ocr_job_paused"); +} diff --git a/scripts/macos-local-ocr-smoke.mjs b/scripts/macos-local-ocr-smoke.mjs index 6653eba..ce0f11b 100644 --- a/scripts/macos-local-ocr-smoke.mjs +++ b/scripts/macos-local-ocr-smoke.mjs @@ -3,21 +3,33 @@ // Isolated stdio qualification, not a Claude Desktop installation or host test. import assert from "node:assert/strict"; import { createHash } from "node:crypto"; +import { execFile } from "node:child_process"; import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; -import { LOCAL_OCR_HELPER_SHA256 } from "../server/local-ocr-tools.js"; +import { LOCAL_OCR_HELPER_SHA256, LOCAL_OCR_TOOL_DEFINITION } from "../server/local-ocr-tools.js"; +import { LOCAL_OCR_JOB_TOOL_DEFINITIONS } from "../server/local-ocr-jobs.js"; +import { withToolOutputSchema } from "../server/output-schemas.js"; +import { assertLocalOcrSmokeCatalog, assertLocalOcrSmokeEncryptedRefusal, assertLocalOcrSmokeProgress } from "./local-ocr-smoke-contract.mjs"; const options = {}; -const keys = new Set(["--pdf", "--expect-source-sha256", "--python", "--helper", "--encrypted-pdf", "--output-dir"]); +const requiredKeys = ["--pdf", "--expect-source-sha256", "--python", "--helper", "--encrypted-pdf", "--output-dir"]; +const keys = new Set([...requiredKeys, "--job-pages"]); for (let index = 2; index < process.argv.length; index += 2) { const key = process.argv[index]; assert(keys.has(key) && !(key in options) && process.argv[index + 1], "Unknown, duplicate or missing smoke argument"); options[key] = process.argv[index + 1]; } -for (const key of keys) assert(options[key], `Required ${key}`); +for (const key of requiredKeys) assert(options[key], `Required ${key}`); +let jobPages; +if (options["--job-pages"]) { + assert(/^[1-9][0-9]*,[1-9][0-9]*$/u.test(options["--job-pages"]), "Use two distinct ascending job pages"); + jobPages = options["--job-pages"].split(",").map(Number); + assert(jobPages[0] < jobPages[1] && jobPages[1] <= 10000, "Use two distinct ascending job pages"); +} assert(process.platform === "darwin", "Real OCR smoke requires macOS"); for (const key of ["--pdf", "--python", "--helper", "--encrypted-pdf", "--output-dir"]) { assert(path.isAbsolute(options[key]), `Absolute path required: ${key}`); @@ -25,7 +37,8 @@ for (const key of ["--pdf", "--python", "--helper", "--encrypted-pdf", "--output const sha = bytes => createHash("sha256").update(bytes).digest("hex"); const sourceBefore = await fs.readFile(options["--pdf"]); assert.equal(sha(sourceBefore), options["--expect-source-sha256"]); -assert.equal(sha(await fs.readFile(options["--helper"])), LOCAL_OCR_HELPER_SHA256); +const helperBytes = await fs.readFile(options["--helper"]); +assert.equal(sha(helperBytes), LOCAL_OCR_HELPER_SHA256); const root = options["--output-dir"]; await fs.mkdir(root, { mode: 0o700 }); // No reuse of a previous smoke. const home = path.join(root, "home"); @@ -37,6 +50,9 @@ const config = { allowedDirectories: [...new Set([path.dirname(options["--pdf"]) const writeConfig = () => fs.writeFile(configPath, JSON.stringify(config), { mode: 0o600 }); await writeConfig(); const repo = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const manifestBytes = await fs.readFile(path.join(repo, "manifest.json")); +const manifestTools = JSON.parse(manifestBytes).tools; +const optionalTools = [LOCAL_OCR_TOOL_DEFINITION, ...LOCAL_OCR_JOB_TOOL_DEFINITIONS].map(withToolOutputSchema); const opened = []; async function connect(label) { const transport = new StdioClientTransport({ @@ -53,13 +69,29 @@ const text = response => response.content?.filter(item => item.type === "text"). const request = { pdf_path: options["--pdf"], page: 1, expected_source_sha256: sha(sourceBefore), confirm_local_ocr: true }; const jobsPath = path.join(profiles, "local-ocr-proposals"); const jobs = async () => fs.readdir(jobsPath).catch(error => { if (error.code === "ENOENT") return []; throw error; }); +async function retainedPage(result) { + assert.equal(sha(await fs.readFile(result.review_html_path)), result.review_html_sha256); + assert.equal(sha(await fs.readFile(path.join(result.proposal_directory, "render.png"))), result.render_png_sha256); + const proposalBytes = await fs.readFile(path.join(result.proposal_directory, "proposal.json")); + const proposal = JSON.parse(proposalBytes); + const commit = JSON.parse(await fs.readFile(path.join(result.proposal_directory, "commit.json"), "utf8")); + assert.equal(commit.proposal_file_sha256, sha(proposalBytes)); + assert.equal(commit.proposal_sha256, result.proposal_sha256); + assert.equal(commit.render_png_sha256, result.render_png_sha256); + assert.equal(proposal.source_pdf_sha256, request.expected_source_sha256); + assert.equal(proposal.page_number, result.page_number); + assert.equal(proposal.proposal_sha256, result.proposal_sha256); + assert.equal(proposal.proposals.length, result.observation_count); + for (const file of [result.review_html_path, ...["proposal.json", "commit.json", "render.png"].map(name => path.join(result.proposal_directory, name))]) { + const stat = await fs.lstat(file); assert(stat.isFile() && !stat.isSymbolicLink()); assert.equal(stat.mode & 0o777, 0o600); + } + return proposal; +} try { const disabled = await connect("disabled"); const defaultTools = (await disabled.listTools()).tools; - assert.equal(defaultTools.length, 57); - assert(!defaultTools.some(tool => tool.name === "propose_pdf_ocr")); + const defaultCount = assertLocalOcrSmokeCatalog(defaultTools, manifestTools, optionalTools, false); const defaultDigest = sha(JSON.stringify(defaultTools)); - assert.equal(defaultDigest, "2edabecaa5aa61e9ee533f8811180e8661a9423dddaee8ba59fec19cb846ab7d"); const disabledResult = await disabled.callTool({ name: "propose_pdf_ocr", arguments: request }); assert.equal(disabledResult.isError, true); assert.match(text(disabledResult), /not configured/); @@ -70,24 +102,43 @@ try { await writeConfig(); const enabled = await connect("enabled"); const enabledTools = (await enabled.listTools()).tools; - assert.equal(enabledTools.length, 58); - assert(enabledTools.some(tool => tool.name === "propose_pdf_ocr" && tool.outputSchema)); + const configuredCount = assertLocalOcrSmokeCatalog(enabledTools, manifestTools, optionalTools, true); const identity = await enabled.callTool({ name: "get_pdf_identity", arguments: { pdf_path: request.pdf_path } }); assert.notEqual(identity.isError, true); assert.equal(identity.structuredContent.sha256, request.expected_source_sha256); - for (const arguments_ of [ - { ...request, confirm_local_ocr: false }, - { ...request, expected_source_sha256: "0".repeat(64) }, + for (const [arguments_, reason] of [ + [{ ...request, confirm_local_ocr: false }, /confirm_local_ocr|confirmation/iu], + [{ ...request, expected_source_sha256: "0".repeat(64) }, /source bytes do not match/iu], ]) { const result = await enabled.callTool({ name: "propose_pdf_ocr", arguments: arguments_ }); assert.equal(result.isError, true); + assert.match(text(result), reason); assert.deepEqual(await jobs(), []); } const encrypted = await fs.readFile(options["--encrypted-pdf"]); + // The public wrapper intentionally gives generic adapter diagnostics. Prove + // this fixture's exact encryption refusal independently, using hashed helper + // bytes and no host credentials, before labelling the negative control. + const negativeHelper = path.join(root, "encrypted-negative-adapter.py"); + await fs.writeFile(negativeHelper, helperBytes, { flag: "wx", mode: 0o600 }); + let encryptedDiagnostic; + try { + await promisify(execFile)(options["--python"], ["-I", "-B", negativeHelper, + "--pdf", options["--encrypted-pdf"], "--page", "1", "--expect-source-sha256", sha(encrypted), + "--output-dir", path.join(root, "encrypted-negative-output")], + { timeout: 15_000, maxBuffer: 256 * 1024, env: { PATH: "/usr/bin:/bin", PYTHONDONTWRITEBYTECODE: "1" } }); + assert.fail("Encrypted adapter fixture unexpectedly succeeded"); + } catch (error) { + assertLocalOcrSmokeEncryptedRefusal(error); + encryptedDiagnostic = error.stderr; + } + await assert.rejects(fs.stat(path.join(root, "encrypted-negative-output")), { code: "ENOENT" }); + await fs.writeFile(path.join(root, "encrypted-adapter.stderr"), encryptedDiagnostic, { flag: "wx", mode: 0o600 }); const encryptionResult = await enabled.callTool({ name: "propose_pdf_ocr", arguments: { ...request, pdf_path: options["--encrypted-pdf"], expected_source_sha256: sha(encrypted), } }); assert.equal(encryptionResult.isError, true); + assert.match(text(encryptionResult), /Optional local OCR adapter failed/u); const failedJobs = await jobs(); assert.equal(failedJobs.length, 1); await assert.rejects(fs.stat(path.join(jobsPath, failedJobs[0], "proposal/commit.json")), { code: "ENOENT" }); @@ -100,23 +151,55 @@ try { assert(result.observation_count > 0); assert.equal(result.returned_observation_count + result.omitted_observation_count, result.observation_count); assert.match(text(response), /UNVERIFIED/); - const proposalBytes = await fs.readFile(path.join(result.proposal_directory, "proposal.json")); - const proposal = JSON.parse(proposalBytes); + const proposal = await retainedPage(result); assert.deepEqual(result.proposals, proposal.proposals.slice(0, 200)); - assert.equal(proposal.proposal_sha256, result.proposal_sha256); - assert.equal(sha(await fs.readFile(result.review_html_path)), result.review_html_sha256); - assert.equal(sha(await fs.readFile(path.join(result.proposal_directory, "render.png"))), result.render_png_sha256); - for (const file of [result.review_html_path, ...["proposal.json", "commit.json", "render.png"].map(name => path.join(result.proposal_directory, name))]) { - const stat = await fs.lstat(file); assert(stat.isFile() && !stat.isSymbolicLink()); assert.equal(stat.mode & 0o777, 0o600); + let jobReceipt = null; + if (jobPages) { + const call = async (client, name, args, timeout = 125_000) => { + const response = await client.callTool({ name, arguments: args }, undefined, { timeout }); + assert.notEqual(response.isError, true, text(response)); + return response.structuredContent; + }; + const beforeStart = (await jobs()).sort(); + const started = await call(enabled, "start_pdf_ocr_job", { pdf_path: request.pdf_path, + pages: jobPages, expected_source_sha256: request.expected_source_sha256, confirm_local_ocr: true }); + const progress = { pages: jobPages, sourceSha256: request.expected_source_sha256, job: started }; + assertLocalOcrSmokeProgress(started, { ...progress, completed: [], processed: 0 }); + assert.deepEqual((await jobs()).sort(), beforeStart, "Job creation must not run OCR"); + const jobArgs = { job_id: started.job_id, expected_job_identity_sha256: started.job_identity_sha256 }; + const denied = await enabled.callTool({ name: "resume_pdf_ocr_job", arguments: { ...jobArgs, confirm_local_ocr: false } }); + assert.equal(denied.isError, true); + const first = await call(enabled, "resume_pdf_ocr_job", { ...jobArgs, confirm_local_ocr: true }); + assertLocalOcrSmokeProgress(first, { ...progress, completed: [jobPages[0]], processed: 1 }); + await retainedPage(first.completed_pages[0]); + await enabled.close(); // A new server process must use the retained checkpoint. + const restarted = await connect("resumed"); + assertLocalOcrSmokeCatalog((await restarted.listTools()).tools, manifestTools, optionalTools, true); + const read = await call(restarted, "read_pdf_ocr_job", jobArgs); + assertLocalOcrSmokeProgress(read, { ...progress, completed: [jobPages[0]], processed: 0 }); + assert.deepEqual(read.completed_pages, first.completed_pages); + const complete = await call(restarted, "resume_pdf_ocr_job", { ...jobArgs, confirm_local_ocr: true }); + assertLocalOcrSmokeProgress(complete, { ...progress, completed: jobPages, processed: 1 }); + assert.deepEqual(complete.completed_pages[0], first.completed_pages[0]); + for (const page of complete.completed_pages) await retainedPage(page); + const beforeRepeated = (await jobs()).sort(); + const repeated = await call(restarted, "resume_pdf_ocr_job", { ...jobArgs, confirm_local_ocr: true }); + assertLocalOcrSmokeProgress(repeated, { ...progress, completed: jobPages, processed: 0 }); + assert.deepEqual(repeated.completed_pages, complete.completed_pages); + assert.deepEqual((await jobs()).sort(), beforeRepeated, "Completed resume must not create a new OCR attempt"); + jobReceipt = { started, first, after_server_restart: read, completed: complete, repeated, + resumed_across_server_processes: true, completed_pages_not_rerun: true }; } assert.equal(sha(await fs.readFile(request.pdf_path)), sha(sourceBefore)); const receipt = { schema_version: 1, scope: "isolated_source_stdio_real_macos_ocr_not_installed_desktop", observed_at: new Date().toISOString(), platform: process.platform, arch: process.arch, node_version: process.version, - default_tool_count: 57, default_tool_contract_sha256: defaultDigest, configured_tool_count: 58, + default_tool_count: defaultCount, default_tool_contract_sha256: defaultDigest, configured_tool_count: configuredCount, + manifest_sha256: sha(manifestBytes), optional_tool_names: optionalTools.map(tool => tool.name), source_pdf_sha256: sha(sourceBefore), encrypted_fixture_sha256: sha(encrypted), encrypted_fixture_rejected: true, + encrypted_adapter_stderr_sha256: sha(encryptedDiagnostic), explicit_confirmation_and_stale_source_rejected: true, original_source_unchanged: true, - result, engine: proposal.engine, source_server_sha256: sha(await fs.readFile(path.join(repo, "server/index.js"))), + result, job: jobReceipt, engine: proposal.engine, source_server_sha256: sha(await fs.readFile(path.join(repo, "server/index.js"))), installed_desktop_qualified: false, word_correctness_verified: false, }; await fs.writeFile(path.join(root, "receipt.json"), `${JSON.stringify(receipt, null, 2)}\n`, { flag: "wx", mode: 0o600 }); diff --git a/scripts/node-test-files.mjs b/scripts/node-test-files.mjs index 9adf7c2..c170e52 100644 --- a/scripts/node-test-files.mjs +++ b/scripts/node-test-files.mjs @@ -2,6 +2,10 @@ const RELEASE_PLATFORMS = Object.freeze(["darwin", "linux", "win32"]); const POSIX_PLATFORMS = Object.freeze(["darwin", "linux"]); export const NODE_TEST_SUITES = Object.freeze([ + { + file: "test/local-ocr-smoke-contract-native.test.js", + platforms: RELEASE_PLATFORMS, + }, { file: "test/local-ocr-tools-native.test.js", platforms: POSIX_PLATFORMS, diff --git a/test/local-ocr-smoke-contract-native.test.js b/test/local-ocr-smoke-contract-native.test.js new file mode 100644 index 0000000..4e2474b --- /dev/null +++ b/test/local-ocr-smoke-contract-native.test.js @@ -0,0 +1,60 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { assertLocalOcrSmokeCatalog, assertLocalOcrSmokeEncryptedRefusal, assertLocalOcrSmokeProgress } from "../scripts/local-ocr-smoke-contract.mjs"; + +const defaults = [{ name: "read_pdf_content" }, { name: "get_pdf_identity" }]; +const optional = ["propose_pdf_ocr", "start_pdf_ocr_job", "resume_pdf_ocr_job", "read_pdf_ocr_job"] + .map(name => ({ name, inputSchema: { type: "object", additionalProperties: false }, + outputSchema: { type: "object", required: ["status"] }, + annotations: { readOnlyHint: name === "read_pdf_ocr_job" } })); +const configured = [...defaults, ...optional]; + +const encryptedRefusal = { code: 2, stdout: "", stderr: JSON.stringify({ + status: "ocr_proposal_failed", message: "Optional OCR does not accept encrypted PDFs" }) }; +test("encryption control requires the real helper exit code and exact reason", () => { + assertLocalOcrSmokeEncryptedRefusal(encryptedRefusal); +}); +for (const [key, value] of Object.entries({ code: 1, stdout: "success", stderr: JSON.stringify({ + status: "ocr_proposal_failed", message: "Optional renderer unavailable" }) })) { + test(`encryption control rejects unrelated ${key}`, () => { + assert.throws(() => assertLocalOcrSmokeEncryptedRefusal({ ...encryptedRefusal, [key]: value })); + }); +} + +test("smoke follows exact current default and four optional tool names without historical counts", () => { + assert.equal(assertLocalOcrSmokeCatalog(defaults, defaults, optional, false), 2); + assert.equal(assertLocalOcrSmokeCatalog([...configured].reverse(), defaults, optional, true), 6); +}); +for (const [name, mutate] of [ + ["missing job tool", tools => tools.pop()], + ["extra tool", tools => tools.push({ name: "unexpected" })], + ["duplicate tool", tools => tools.push(tools[0])], + ["optional schema drift", tools => { tools[2].inputSchema.additionalProperties = true; }], + ["optional annotation drift", tools => { tools[2].annotations.readOnlyHint = true; }], + ["missing output schema", tools => { delete tools[2].outputSchema; }], + ["valid-shape output schema substitution", tools => { tools[2].outputSchema.required = ["forged"]; }], +]) { + test(`catalog rejects ${name}`, () => { + const tools = structuredClone(configured); mutate(tools); + assert.throws(() => assertLocalOcrSmokeCatalog(tools, defaults, optional, true)); + }); +} +test("default catalog cannot silently enable OCR", () => { + assert.throws(() => assertLocalOcrSmokeCatalog(configured, defaults, optional, false)); +}); + +const expectation = { pages: [1, 2], completed: [1], processed: 1, sourceSha256: "a".repeat(64), + job: { job_id: "fixture-job", job_identity_sha256: "b".repeat(64) } }; +const paused = { ...expectation.job, verification_status: "unverified_ocr_proposals", source_pdf_sha256: expectation.sourceSha256, + page_count: 2, completed_count: 1, remaining_count: 1, completed_pages: [{ page_number: 1 }], remaining_pages: [2], + processed_this_call: 1, failed_page: null, failure_code: null, status: "ocr_job_paused" }; +test("progress accepts the exact retained page partition", () => { + assertLocalOcrSmokeProgress(paused, expectation); +}); +for (const [key, value] of Object.entries({ verification_status: "verified", source_pdf_sha256: "c".repeat(64), + job_identity_sha256: "c".repeat(64), completed_count: 2, remaining_pages: [], processed_this_call: 0, + failed_page: 2, status: "ocr_job_complete" })) { + test(`progress rejects substituted ${key}`, () => { + assert.throws(() => assertLocalOcrSmokeProgress({ ...paused, [key]: value }, expectation)); + }); +} diff --git a/test/test-runner-contract.test.js b/test/test-runner-contract.test.js index 13a003b..7502dca 100644 --- a/test/test-runner-contract.test.js +++ b/test/test-runner-contract.test.js @@ -541,6 +541,7 @@ describe("aggregate test-runner contract", () => { it("uses exact, explicit native-suite partitions on each release platform", () => { expect(nodeTestFilesForPlatform("win32")).toEqual([ + "test/local-ocr-smoke-contract-native.test.js", "test/deep-malformed-native-v2-contract.test.js", "test/deep-malformed-native-v3-contract.test.js", "test/deep-malformed-native-v4-contract.test.js", @@ -549,6 +550,7 @@ describe("aggregate test-runner contract", () => { "test/deep-malformed-native-windows-portable.test.js", ]); const posixFiles = [ + "test/local-ocr-smoke-contract-native.test.js", "test/local-ocr-tools-native.test.js", "test/deep-malformed-native-v2-contract.test.js", "test/deep-malformed-native-v2-mechanisms.test.js",