diff --git a/scripts/lib/verify-commands.mjs b/scripts/lib/verify-commands.mjs index bbe9f7c64a..eaa4a02cfb 100644 --- a/scripts/lib/verify-commands.mjs +++ b/scripts/lib/verify-commands.mjs @@ -1,12 +1,12 @@ +import { importVerifierSource } from "./verifier-source-loader.mjs"; + let implementation; /** Load the development command owner and its canonical process source graph. */ export async function createVerifierCommands(options = {}) { - implementation ??= import("tsx/esm/api").then(({ tsImport }) => - tsImport( - new URL("./verifier-command-owner.mts", import.meta.url).href, - import.meta.url, - ), + implementation ??= importVerifierSource( + new URL("./verifier-command-owner.mts", import.meta.url).href, + import.meta.url, ); const module = await implementation; return module.createVerifierCommands(options); diff --git a/scripts/lib/verify-package-core.mjs b/scripts/lib/verify-package-core.mjs index eb15c49085..2c5a8977e8 100644 --- a/scripts/lib/verify-package-core.mjs +++ b/scripts/lib/verify-package-core.mjs @@ -1,13 +1,10 @@ import { lstat } from "node:fs/promises"; import { Ajv2020 } from "ajv/dist/2020.js"; -// Only catalog verification needs built MCP contracts; the other helpers work +// Only catalog verification needs built MCP contracts; path checks work // from source without a runtime build. const loadMcpVerifierResults = () => import("./mcp-verifier-results.mjs"); -/** Parse JSON from a command output string. */ -export const json = (text) => JSON.parse(text); - /** Verify schema validity and that the MCP catalog matches session metadata. */ export const verifyCompleteToolCatalog = async (client, options) => { const { requireMcpLifecycleResult, sortVerifierNames } = diff --git a/scripts/verify-package-artifact.mjs b/scripts/verify-package-artifact.mjs index 60583ffd76..ef87ce1105 100644 --- a/scripts/verify-package-artifact.mjs +++ b/scripts/verify-package-artifact.mjs @@ -3,8 +3,6 @@ import { isDeepStrictEqual } from "node:util"; import { join } from "node:path"; import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js"; -import { json } from "./lib/verify-package-core.mjs"; - /** Run artifact and JavaScript application analysis against packaged fixtures. */ export async function verifyPackageArtifactAndElectron({ commands, @@ -37,14 +35,14 @@ const verifyPackagedArtifact = async ({ const artifactWriter = new ZipWriter(new Uint8ArrayWriter()); await artifactWriter.add("app/main.js", new TextReader("main();")); await writeFile(artifactArchive, await artifactWriter.close()); - const artifactInspection = json( + const artifactInspection = JSON.parse( await commands.run( cli, ["inspect-artifact", artifactArchive, "--json"], environment, ), ); - const repeatedArtifactInspection = json( + const repeatedArtifactInspection = JSON.parse( await commands.run( cli, ["inspect-artifact", artifactArchive, "--json"], @@ -95,7 +93,7 @@ const verifyPackagedElectronApplication = async ({ join(applicationRoot, "preload.js"), 'const { contextBridge, ipcRenderer } = require("electron");\ncontextBridge.exposeInMainWorld("rea", { ping: () => ipcRenderer.invoke("rea:ping") });\n', ); - const applicationAnalysis = json( + const applicationAnalysis = JSON.parse( await commands.run( cli, ["analyze-javascript-application", applicationRoot, "--json"], @@ -110,7 +108,7 @@ const verifyPackagedElectronApplication = async ({ ?.paired_renderer_transmissions !== 1 ) throw new Error("packaged JavaScript application analysis CLI failed"); - const routedApplicationAnalysis = json( + const routedApplicationAnalysis = JSON.parse( await commands.run( cli, ["analyze", applicationRoot, "--json"], @@ -138,7 +136,7 @@ const verifyPackagedArtifactExtraction = async ({ ); if (occurrence?.logical_path === undefined) throw new Error("packaged artifact inventory omitted the selected member"); - const extraction = json( + const extraction = JSON.parse( await commands.run( cli, ["extract-artifact", artifactArchive, "--json"], diff --git a/scripts/verify-package-capabilities.mjs b/scripts/verify-package-capabilities.mjs index af2375bb43..18dd2f8c1b 100644 --- a/scripts/verify-package-capabilities.mjs +++ b/scripts/verify-package-capabilities.mjs @@ -1,4 +1,3 @@ -import { json } from "./lib/verify-package-core.mjs"; import { expandCapabilitiesOutput, expandProviderOutput, @@ -11,12 +10,14 @@ export async function verifyPackageCapabilitiesAndSearch({ environment, }) { const capabilities = expandCapabilitiesOutput( - json(await commands.run(cli, ["capabilities", "--json"], environment)), + JSON.parse( + await commands.run(cli, ["capabilities", "--json"], environment), + ), ); if (!Array.isArray(capabilities.capabilities)) throw new Error("packaged capabilities CLI failed"); const providers = expandProviderOutput( - json(await commands.run(cli, ["providers", "--json"], environment)), + JSON.parse(await commands.run(cli, ["providers", "--json"], environment)), ); const candidates = providers.analysis_provider_candidates; if ( @@ -32,7 +33,7 @@ export async function verifyPackageCapabilitiesAndSearch({ ) throw new Error("packaged providers CLI failed"); if (process.platform !== "linux") { - const searchResult = json( + const searchResult = JSON.parse( await commands.run( cli, [ diff --git a/scripts/verify-package-discovery.mjs b/scripts/verify-package-discovery.mjs index 65a119da92..8b6748da29 100644 --- a/scripts/verify-package-discovery.mjs +++ b/scripts/verify-package-discovery.mjs @@ -1,5 +1,3 @@ -import { json } from "./lib/verify-package-core.mjs"; - const REQUIRED_HELP_COMMANDS = [ "setup", "update", @@ -87,7 +85,7 @@ export async function verifyPackageDiscovery({ commands, cli, environment }) { ["doctor", "--json"], environment, ); - const doctor = json(doctorExecution.stdout); + const doctor = JSON.parse(doctorExecution.stdout); const { supportedSetupHost, hopperReadinessRequired, hopperSetupSupported } = packageSetupSupport(doctor.checks, { platform: process.platform, diff --git a/scripts/verify-package-evidence.mjs b/scripts/verify-package-evidence.mjs index 5f0630bae4..f513632e79 100644 --- a/scripts/verify-package-evidence.mjs +++ b/scripts/verify-package-evidence.mjs @@ -2,7 +2,6 @@ import { readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { createEvidence } from "../dist/domain/evidence.js"; -import { json } from "./lib/verify-package-core.mjs"; /** Round-trip historical reference import and evidence bundles. */ export async function verifyPackageEvidence({ @@ -12,7 +11,7 @@ export async function verifyPackageEvidence({ referenceRoot, environment, }) { - const referenceImport = json( + const referenceImport = JSON.parse( await commands.run( cli, ["import-reference-source", referenceRoot, "--json"], @@ -39,21 +38,21 @@ export async function verifyPackageEvidence({ const sourceBundlePath = join(evidenceRoot, "source.json"); const canonicalBundlePath = join(evidenceRoot, "canonical.json"); await writeFile(sourceBundlePath, serializeEvidenceBundle(sourceBundle)); - const importedBundle = json( + const importedBundle = JSON.parse( await commands.run( cli, ["evidence-import", sourceBundlePath, "--json"], environment, ), ); - const exportedBundle = json( + const exportedBundle = JSON.parse( await commands.run( cli, ["evidence-export", sourceBundlePath, canonicalBundlePath, "--json"], environment, ), ); - const comparedBundle = json( + const comparedBundle = JSON.parse( await commands.run( cli, ["compare", sourceBundlePath, canonicalBundlePath, "--json"], diff --git a/scripts/verify-package-install.mjs b/scripts/verify-package-install.mjs index 74e520dc0e..27f3315c23 100644 --- a/scripts/verify-package-install.mjs +++ b/scripts/verify-package-install.mjs @@ -2,8 +2,6 @@ import { rm } from "node:fs/promises"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; -import { json } from "./lib/verify-package-core.mjs"; - /** Install the packed CLI globally with and without optional dependencies and verify PTY degradation. */ export async function verifyPackageInstall({ tarball, @@ -39,7 +37,7 @@ export async function verifyPackageInstall({ "lib/node_modules/rea-agents/dist/process/capture/ProcessCaptureCapability.js", ), ).href; - const processCaptureCapability = json( + const processCaptureCapability = JSON.parse( ( await commands.exec( process.execPath, @@ -84,7 +82,7 @@ export async function verifyPackageInstall({ "lib/node_modules/rea-agents/dist/process/capture/ProcessCaptureCapability.js", ), ).href; - const noOptionalCapability = json( + const noOptionalCapability = JSON.parse( ( await commands.exec( process.execPath, diff --git a/scripts/verify-package-managed.mjs b/scripts/verify-package-managed.mjs index 28ea7785ab..3d37462c40 100644 --- a/scripts/verify-package-managed.mjs +++ b/scripts/verify-package-managed.mjs @@ -4,7 +4,6 @@ import { join } from "node:path"; import { createEvidence } from "../dist/domain/evidence.js"; import { buildManagedPeFixture } from "./fixtures/managed/pe.mjs"; import { functionDossier } from "./fixtures/managed/function-dossier.mjs"; -import { json } from "./lib/verify-package-core.mjs"; const buildManagedFixtures = async (workspace) => { const managedPath = join(workspace, "managed-fixture.exe"); @@ -44,7 +43,7 @@ const verifyManagedArtifact = async ({ managedPath, environment, }) => { - const managedArtifact = json( + const managedArtifact = JSON.parse( await commands.run( cli, ["inspect-managed-artifact", managedPath, "--json"], @@ -66,7 +65,7 @@ const verifyManagedMembers = async ({ managedPath, environment, }) => { - const managedMembers = json( + const managedMembers = JSON.parse( await commands.run( cli, ["inspect-managed-members", managedPath, "--json"], @@ -111,7 +110,7 @@ const verifyManagedReconstruction = async ({ ], notes: ["packaged synthetic reconstruction import"], }; - const managedReconstruction = json( + const managedReconstruction = JSON.parse( await commands.run( cli, [ @@ -138,7 +137,7 @@ const verifyManagedBoundaries = async ({ managedPath, environment, }) => { - const managedBoundaries = json( + const managedBoundaries = JSON.parse( await commands.run( cli, ["inspect-managed-native-boundaries", managedPath, "--json"], @@ -174,7 +173,7 @@ const verifyManagedApplicationGraph = async ({ "inspect_managed_native_boundaries", ), }; - const managedApplicationGraph = json( + const managedApplicationGraph = JSON.parse( await commands.run( cli, [ @@ -260,7 +259,7 @@ const verifyManagedNativeVerification = async ({ ), ], }; - const managedNativeVerification = json( + const managedNativeVerification = JSON.parse( await commands.run( cli, [ @@ -289,7 +288,7 @@ const verifyManagedComparison = async ({ managedRightPath, environment, }) => { - const managedComparison = json( + const managedComparison = JSON.parse( await commands.run( cli, ["compare-managed-members", managedPath, managedRightPath, "--json"], diff --git a/scripts/verify-package-mcp.mjs b/scripts/verify-package-mcp.mjs index 12bb7bc44b..c7dc6d83e1 100644 --- a/scripts/verify-package-mcp.mjs +++ b/scripts/verify-package-mcp.mjs @@ -11,7 +11,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; import { resolveMcpStartupBudgets } from "../dist/config/mcpStartupBudgets.js"; import { parseEvidence } from "../dist/domain/evidence.js"; import * as prompts from "./verify-package-prompts.mjs"; -import { json, verifyCompleteToolCatalog } from "./lib/verify-package-core.mjs"; +import { verifyCompleteToolCatalog } from "./lib/verify-package-core.mjs"; import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs"; const verifyMcpToolsAndPrompts = async (client, mcpOptions) => { @@ -95,7 +95,7 @@ const verifyMcpNonLinuxCurrentDocument = async ( mcpOptions, current, ) => { - if (json(prompts.mcpText(current)).normalized_result !== "fixture") + if (JSON.parse(prompts.mcpText(current)).normalized_result !== "fixture") throw new Error("packaged MCP bridge call failed"); const batch = await client.callTool( { @@ -104,7 +104,7 @@ const verifyMcpNonLinuxCurrentDocument = async ( }, mcpOptions, ); - const batchResult = json(prompts.mcpText(batch)).normalized_result; + const batchResult = JSON.parse(prompts.mcpText(batch)).normalized_result; if ( batch.isError === true || batchResult?.total !== 1 || @@ -178,7 +178,7 @@ const verifyMcpInlineArtifactEvidence = async ( inspected.structuredContent?.normalized_result, ); assert.deepEqual( - json(prompts.mcpText(inspected)), + JSON.parse(prompts.mcpText(inspected)), inspected.structuredContent, ); const compared = await client.callTool( @@ -251,7 +251,7 @@ export async function verifyPackageMcp({ evidenceRoot, artifactArchive, }) { - const diagnosed = json( + const diagnosed = JSON.parse( ( await commands.exec(cli, ["mcp", "doctor", "--json"], { env: environment, diff --git a/scripts/verify-package-platform.mjs b/scripts/verify-package-platform.mjs index 4f9027d75f..57fb59a80f 100644 --- a/scripts/verify-package-platform.mjs +++ b/scripts/verify-package-platform.mjs @@ -1,5 +1,3 @@ -import { json } from "./lib/verify-package-core.mjs"; - /** Run platform-specific deep-analysis smoke tests. */ export async function verifyPackagePlatform({ commands, @@ -15,7 +13,7 @@ export async function verifyPackagePlatform({ ); assertLinuxPackageProviderFailure(unsupportedExecution); } else { - const overview = json( + const overview = JSON.parse( await commands.run( cli, ["analyze", process.execPath, "--provider", "hopper", "--json"], @@ -26,7 +24,7 @@ export async function verifyPackagePlatform({ throw new Error( `packaged Hopper-backed analyze CLI failed: ${JSON.stringify(overview)}`, ); - const inspected = json( + const inspected = JSON.parse( await commands.run( cli, ["inspect", process.execPath, "--provider", "hopper", "--json"], @@ -35,7 +33,7 @@ export async function verifyPackagePlatform({ ); if (!hasPositiveProcedureCount(inspected)) throw new Error("packaged inspect CLI failed"); - const functionResult = json( + const functionResult = JSON.parse( await commands.run( cli, [ @@ -57,7 +55,7 @@ export async function verifyPackagePlatform({ throw new Error( `packaged function CLI failed: ${JSON.stringify(functionResult)}`, ); - const xrefs = json( + const xrefs = JSON.parse( await commands.run( cli, ["xrefs", process.execPath, "0x1000", "--provider", "hopper", "--json"], @@ -69,7 +67,7 @@ export async function verifyPackagePlatform({ JSON.stringify(xrefs.normalized_result) !== JSON.stringify(["0x1000"]) ) throw new Error(`packaged xrefs CLI failed: ${JSON.stringify(xrefs)}`); - const trace = json( + const trace = JSON.parse( await commands.run( cli, [ @@ -108,7 +106,7 @@ export const isUnsupportedHopperLaunchFailure = (failure) => { /** Require an exact pre-execution rejection, observed helper exit, or missing-Xvfb diagnostic. */ export const assertLinuxPackageProviderFailure = (execution) => { - const failure = json(execution.stdout); + const failure = JSON.parse(execution.stdout); const details = failure?.details; const diagnostic = details?.diagnostics; const missingDisplayDependency = diff --git a/scripts/verify-package-setup.mjs b/scripts/verify-package-setup.mjs index ddd84cc5d4..4f93b4af26 100644 --- a/scripts/verify-package-setup.mjs +++ b/scripts/verify-package-setup.mjs @@ -11,7 +11,7 @@ import { dirname, join } from "node:path"; import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js"; import { PRODUCT_IDENTITY } from "../dist/identity.js"; -import { json, pathExists } from "./lib/verify-package-core.mjs"; +import { pathExists } from "./lib/verify-package-core.mjs"; import { parse as parseJsonc } from "jsonc-parser"; import { skillReferenceIssues } from "./lib/docs-facts.mjs"; import { @@ -61,7 +61,7 @@ const verifySetupPlan = async ({ ["setup", "--all-detected", "--dry-run", "--json"], environment, ); - const planned = json(plannedExecution.stdout); + const planned = JSON.parse(plannedExecution.stdout); if (supportedSetupHost) { const plannedActions = Array.isArray(planned?.plannedActions) ? planned.plannedActions @@ -114,7 +114,7 @@ const verifyAbsentOpenCodeSetup = async ({ ["setup", "--yes", "--client", "opencode", "--skill=false", "--json"], { ...environment, XDG_CONFIG_HOME: xdgConfigHome }, ); - const result = json(execution.stdout); + const result = JSON.parse(execution.stdout); const config = JSON.parse(await readFile(configPath, "utf8")); if ( execution.status !== 0 || @@ -163,8 +163,8 @@ const verifyAbsentClaudeCodeSetup = async ({ args, childEnvironment, ); - const first = json(firstExecution.stdout); - const config = json(await readFile(configPath, "utf8")); + const first = JSON.parse(firstExecution.stdout); + const config = JSON.parse(await readFile(configPath, "utf8")); const registration = first.doctor.identity?.registrations?.find( ({ client }) => client === "claude_code", ); @@ -186,7 +186,7 @@ const verifyAbsentClaudeCodeSetup = async ({ args, childEnvironment, ); - const repeat = json(repeatExecution.stdout); + const repeat = JSON.parse(repeatExecution.stdout); if ( repeatExecution.status !== 0 || repeat.status !== "ready" || @@ -214,13 +214,13 @@ const verifySetupApply = async ({ ["setup", "--yes", "--all-detected", "--json"], environment, ); - const first = json(firstExecution.stdout); + const first = JSON.parse(firstExecution.stdout); const alignedExecution = await commands.runWithStatus( cli, ["setup", "--all-detected", "--json"], environment, ); - const aligned = json(alignedExecution.stdout); + const aligned = JSON.parse(alignedExecution.stdout); const secondExecution = await commands.runWithStatus( cli, [ @@ -236,7 +236,7 @@ const verifySetupApply = async ({ ], environment, ); - const second = json(secondExecution.stdout); + const second = JSON.parse(secondExecution.stdout); if (supportedSetupHost) { if ( first.status !== status || @@ -267,7 +267,7 @@ const verifySetupApply = async ({ /** Verify the live registration and exact pre-setup backup bytes. */ export const assertClientConfig = async (cli, configPath, original) => { - const config = json(await readFile(configPath, "utf8")); + const config = JSON.parse(await readFile(configPath, "utf8")); if ( config.existing !== true || config.mcpServers?.rea?.command !== cli || @@ -285,8 +285,7 @@ const assertOpenCodeConfig = async ({ }) => { const contents = await readFile(configPath, "utf8"); const errors = []; - const parsed = parseJsonc(contents, errors, { allowTrailingComma: true }); - const config = parsed; + const config = parseJsonc(contents, errors, { allowTrailingComma: true }); const mcp = config?.mcp; if ( errors.length !== 0 || @@ -357,7 +356,7 @@ const assertAlignedDoctor = async (commands, cli, environment) => { ["doctor", "--json"], environment, ); - const alignedDoctor = json(alignedDoctorExecution.stdout); + const alignedDoctor = JSON.parse(alignedDoctorExecution.stdout); if ( alignedDoctorExecution.status !== (alignedDoctor.healthy ? 0 : 1) || alignedDoctor.identity?.skill?.state !== "aligned" || @@ -425,7 +424,8 @@ const verifySetupFailureRecovery = async ({ ["setup", "--yes", "--all-detected", "--json"], environment, ); - const failed = json(failedExecution.stdout); + const failed = JSON.parse(failedExecution.stdout); + const cursorAfterFailure = JSON.parse(await readFile(cursorConfig, "utf8")); if ( failed.status !== "needs_human" || failedExecution.status !== 1 || @@ -433,8 +433,8 @@ const verifySetupFailureRecovery = async ({ failed.appliedActions.length !== 0 || !failed.remediation?.includes("Claude Desktop") || (await readFile(claudeConfig, "utf8")) !== "malformed" || - json(await readFile(cursorConfig, "utf8")).existing !== true || - json(await readFile(cursorConfig, "utf8")).mcpServers?.rea !== undefined + cursorAfterFailure.existing !== true || + cursorAfterFailure.mcpServers?.rea !== undefined ) throw new Error( `packaged setup mutated clients after a preflight failure: ${JSON.stringify(failed)}`, @@ -445,11 +445,12 @@ const verifySetupFailureRecovery = async ({ ["setup", "--yes", "--all-detected", "--json"], environment, ); - const recovered = json(recoveredExecution.stdout); + const recovered = JSON.parse(recoveredExecution.stdout); if ( recovered.status !== status || recoveredExecution.status !== (status === "ready" ? 0 : 1) || - json(await readFile(cursorConfig, "utf8")).mcpServers?.rea?.command !== cli + JSON.parse(await readFile(cursorConfig, "utf8")).mcpServers?.rea + ?.command !== cli ) throw new Error("packaged setup did not recover"); }; @@ -464,7 +465,7 @@ const verifyDanglingSymlink = async ({ commands, cli, environment, home }) => { ["setup", "--yes", "--client", "gemini_cli", "--json"], environment, ); - const dangling = json(danglingExecution.stdout); + const dangling = JSON.parse(danglingExecution.stdout); if ( dangling.status !== "needs_human" || danglingExecution.status !== 1 || @@ -496,8 +497,8 @@ const verifyUninstall = async ({ ["uninstall", "--json"], environment, ); - const uninstall = json(uninstallExecution.stdout); - const cursorAfterUninstall = json(await readFile(cursorTarget, "utf8")); + const uninstall = JSON.parse(uninstallExecution.stdout); + const cursorAfterUninstall = JSON.parse(await readFile(cursorTarget, "utf8")); const codexAfterUninstall = await readFile(codexTarget, "utf8"); const openCodeErrors = []; const openCodeAfterUninstall = parseJsonc( diff --git a/scripts/verify-package-unknown-provider.mjs b/scripts/verify-package-unknown-provider.mjs index 775e6426ec..6d590c9ada 100644 --- a/scripts/verify-package-unknown-provider.mjs +++ b/scripts/verify-package-unknown-provider.mjs @@ -1,5 +1,3 @@ -import { json } from "./lib/verify-package-core.mjs"; - /** Verify that an unknown --provider is rejected by the packaged CLI. */ export async function verifyUnknownProvider({ commands, cli, environment }) { const unknownProviderExecution = await commands.runWithStatus( @@ -7,7 +5,7 @@ export async function verifyUnknownProvider({ commands, cli, environment }) { ["analyze", process.execPath, "--provider", "missing-provider", "--json"], environment, ); - const unknownProvider = json(unknownProviderExecution.stdout); + const unknownProvider = JSON.parse(unknownProviderExecution.stdout); if ( unknownProviderExecution.status !== 1 || unknownProvider.details?.selection_reason !== "unknown_provider" || diff --git a/scripts/verify-package-update.mjs b/scripts/verify-package-update.mjs index bf39f12cdf..09295a0b6f 100644 --- a/scripts/verify-package-update.mjs +++ b/scripts/verify-package-update.mjs @@ -4,7 +4,6 @@ import { readFile, writeFile, mkdir } from "node:fs/promises"; import { dirname, join } from "node:path"; import { pathToFileURL } from "node:url"; -import { json } from "./lib/verify-package-core.mjs"; import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs"; /** Exercise the packaged updater with real npm and a local release registry fixture. */ @@ -17,7 +16,7 @@ export async function verifyPackageUpdate({ commands, }) { const metadataPath = join(packageRoot, "package.json"); - const metadata = json(await readFile(metadataPath, "utf8")); + const metadata = JSON.parse(await readFile(metadataPath, "utf8")); const archive = await readFile(tarball); const entry = join(packageRoot, "scripts", "rea.mjs"); const home = join(workspace, "update-home"); @@ -144,7 +143,7 @@ process.stdout.write(JSON.stringify(result)); if (result.status === "failed") process.exitCode = 1; `, ); - const result = json( + const result = JSON.parse( ( await commands.exec(process.execPath, [harness], { env: runtimeEnvironment, diff --git a/tests/boundary/process/nativeVerifierSourceOwnership.test.ts b/tests/boundary/process/nativeVerifierSourceOwnership.test.ts new file mode 100644 index 0000000000..d8c4d72e3e --- /dev/null +++ b/tests/boundary/process/nativeVerifierSourceOwnership.test.ts @@ -0,0 +1,83 @@ +import { fileURLToPath } from "node:url"; +import { expect } from "vitest"; +import { z } from "zod"; + +import { processTest } from "../../support/process/processFixture.js"; + +const commandsModule = new URL( + "../../../scripts/lib/verify-commands.mjs", + import.meta.url, +).href; +const verifierModule = new URL( + "../../../scripts/lib/verifier-run.mjs", + import.meta.url, +).href; +const repository = fileURLToPath(new URL("../../../", import.meta.url)); +const commandOutput = "authored native verifier command completed\n"; + +const reportSchema = z.object({ + command: z.object({ stdout: z.string(), stderr: z.string() }), + verifier: z.object({ + process_lineage: z.discriminatedUnion("status", [ + z.object({ + status: z.literal("verified"), + descendants: z.array( + z.object({ + pid: z.number().int().positive(), + parent_pid: z.number().int().positive(), + process_group_id: z.number().int().positive(), + }), + ), + }), + z.object({ status: z.literal("unavailable"), reason: z.string() }), + ]), + }), +}); + +// This exercises the public source loader and real command cleanup. The lineage +// receipt is sampled inside the driver before its exit releases loader children. +// Windows has no native Job Object lease for this verifier lineage observation. +processTest.skipIf(process.platform === "win32")( + "releases a cold native verifier source loader before reporting command cleanup complete", + { timeout: 30_000 }, + async ({ processes }) => { + const result = await processes.run( + process.execPath, + [ + "--input-type=module", + "-e", + ` +import { createVerifierRun, completeVerifierRun } from ${JSON.stringify(verifierModule)}; +const run = createVerifierRun(); +const { createVerifierCommands } = await import(${JSON.stringify(commandsModule)}); +const commands = await createVerifierCommands(); +let command; +try { + command = await commands.exec(process.execPath, [ + "-e", ${JSON.stringify(`process.stdout.write(${JSON.stringify(commandOutput)})`)}, + ], { timeout: 5000 }); +} finally { + await commands.close(); +} +const verifier = await completeVerifierRun(run); +process.stdout.write(JSON.stringify({ command, verifier })); +`, + ], + { + cwd: repository, + // Installed tsx reads this switch before choosing its transform cache. + // A fresh process must transform source even when disk cache is warm. + env: { ...process.env, TSX_DISABLE_CACHE: "1" }, + timeoutMs: 20_000, + }, + ); + expect(result.exitCode, result.stderr).toBe(0); + expect(result.signal, result.stderr).toBeNull(); + const report = reportSchema.parse(JSON.parse(result.stdout)); + expect(report.command).toEqual({ stdout: commandOutput, stderr: "" }); + expect(report.verifier.process_lineage).toEqual({ + status: "verified", + descendants: [], + }); + }, +);