// The isolated, short-lived validation process of Module Package v1 verification (ADR 0049). // // The verifier forks this file with an empty environment and one JSON argument naming a candidate // module's backend entrypoints. It imports each one, so a package whose code cannot load, or whose // bundle still reaches for a dependency it does not contain, is refused before it is ever installed. // Whatever the module's top-level code does happens here, in a process with no database URL, no // secret, and no platform state, and that is discarded as soon as it reports. // // The result travels over the IPC channel, never stdout, so nothing a module prints can forge it. // It carries fixed role and reason names only: never an error message, a path, or module output. // // Written as erasable TypeScript, so Node can run the source file directly in tests as well as the // compiled file in the image. Its one relative import names the source file's own extension, which // the build rewrites to `.js` (`rewriteRelativeImportExtensions`); that file follows the same rules. import { pathToFileURL } from "node:url"; import { parseModuleRuntimeDefinition, parseModuleSchemaMetadataDefinition, } from "@ekklesia/contracts"; import { parseModuleSchemaDefinition } from "./legacy-schema-definition.ts"; interface ProbeRequest { readonly databaseAbi?: 1; readonly moduleId: string; readonly version: string; readonly entrypoints: Readonly>; } interface ProbeFailure { readonly role: string; readonly reason: "contract" | "export" | "identity" | "load"; } function defaultExport(namespace: unknown): unknown { return typeof namespace === "object" && namespace !== null && "default" in namespace ? (namespace as { default: unknown }).default : undefined; } async function probe(request: ProbeRequest): Promise { const failures: ProbeFailure[] = []; for (const role of Object.keys(request.entrypoints).sort()) { const entrypoint = request.entrypoints[role]; if (entrypoint === undefined) { continue; } let namespace: unknown; try { namespace = await import(pathToFileURL(entrypoint).href); } catch { failures.push({ reason: "load", role }); continue; } try { if (role === "server") { const definition = parseModuleRuntimeDefinition(defaultExport(namespace)); if (definition.id !== request.moduleId || definition.version !== request.version) { failures.push({ reason: "identity", role }); } if ((definition.database?.abi === 1) !== (request.databaseAbi === 1)) { failures.push({ reason: "contract", role }); } } else if (role === "schema") { const definition = request.databaseAbi === 1 ? parseModuleSchemaMetadataDefinition(defaultExport(namespace)) : parseModuleSchemaDefinition(defaultExport(namespace)); if (definition.moduleId !== request.moduleId) { failures.push({ reason: "identity", role }); } } } catch { failures.push({ reason: "export", role }); } } return failures; } function report(message: unknown): void { if (typeof process.send !== "function") { process.exitCode = 2; return; } process.send(message, () => { // A module may leave timers or handles behind; the probe's work is done either way. process.exit(0); }); } let request: ProbeRequest | undefined; try { request = JSON.parse(process.argv[2] ?? "") as ProbeRequest; } catch { report({ failures: [{ reason: "load", role: "request" }] }); } if (request !== undefined) { probe(request).then( (failures) => report({ failures }), () => report({ failures: [{ reason: "load", role: "probe" }] }), ); }