Files
learnharness/scripts/smoke-image-runtime.mjs
T

229 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* OFFLINE image runtime gate: exercise the shipped Photon/WASM resize core and
* actual worker, with file modules confined to the standalone payload. No app,
* provider, canvas, or network needed; a tiny synthetic PNG is generated here.
*
* node scripts/smoke-image-runtime.mjs [--standalone <dir>] [--runtime <Node/Electron>]
*/
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import * as fs from "node:fs";
import { createRequire } from "node:module";
import * as os from "node:os";
import * as path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { Worker } from "node:worker_threads";
import { deflateSync } from "node:zlib";
import { formatProbeError, probeEnv } from "./smoke-demo-runtime.mjs";
export function verifyImageRuntime(standalone, runtime = process.execPath) {
const home = fs.mkdtempSync(path.join(os.tmpdir(), "lh-image-runtime-"));
try {
fs.writeFileSync(path.join(home, "auth.json"), "{}\n", { mode: 0o600 });
const result = spawnSync(runtime, [fileURLToPath(import.meta.url), "--probe", path.resolve(standalone)], {
cwd: home,
env: { ...probeEnv(process.env, home), ELECTRON_RUN_AS_NODE: "1" },
encoding: "utf8", timeout: 30_000, killSignal: "SIGKILL", maxBuffer: 128 * 1024,
});
if (result.status !== 0 || result.error) {
throw new Error(`image runtime probe failed (${result.error?.message ?? result.status}):\n${result.stdout ?? ""}${result.stderr ?? ""}`);
}
assert.match(result.stdout, /Photon in-process and worker resizing passed/, "probe exited without success marker");
process.stdout.write(result.stdout);
} finally { fs.rmSync(home, { recursive: true, force: true }); }
}
// Self-contained so the eval worker can install the SAME guards using only
// builtins, before importing any payload module. Module hooks do not otherwise
// propagate from the dedicated probe process into Worker threads.
function installGuards(require, standalone) {
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const { fileURLToPath } = require("node:url");
const { registerHooks, syncBuiltinESMExports } = require("node:module");
const root = fs.realpathSync(standalone);
const inside = (file) => {
const real = fs.realpathSync(file);
const relative = path.relative(root, real);
assert.ok(relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative),
`dependency escaped standalone payload: ${file}`);
return real;
};
const photonRoot = path.join(root, "node_modules/@silvia-odwyer/photon-node");
const manifest = JSON.parse(fs.readFileSync(inside(path.join(photonRoot, "package.json")), "utf8"));
assert.equal(manifest.version, "0.3.4", "unexpected Photon engine version");
const photonEntry = inside(path.join(photonRoot, "photon_rs.js"));
const photonWasm = inside(path.join(photonRoot, "photon_rs_bg.wasm"));
assert.ok(fs.statSync(photonWasm).size > 0, "empty Photon WASM asset");
let attempts = 0;
const deny = () => { attempts++; throw new Error("OFFLINE image gate: network forbidden"); };
globalThis.fetch = deny;
globalThis.WebSocket = class { constructor() { deny(); } };
for (const [name, methods] of [
["http", ["request", "get"]], ["https", ["request", "get"]],
["net", ["connect", "createConnection"]], ["tls", ["connect"]],
["dgram", ["createSocket"]],
["dns", ["lookup", "resolve", "resolve4", "resolve6", "resolveAny", "reverse"]],
["dns/promises", ["lookup", "resolve", "resolve4", "resolve6", "resolveAny", "reverse"]],
]) {
const module = require(`node:${name}`);
for (const method of methods) module[method] = deny;
}
require("node:net").Socket.prototype.connect = deny;
const loaded = new Set();
const wasmReads = new Set();
const originalRead = fs.readFileSync;
fs.readFileSync = function (file, ...args) {
const filename = file instanceof URL ? fileURLToPath(file) : file;
const wasmFile = typeof filename === "string" && filename.endsWith(".wasm") ? inside(filename) : null;
const bytes = originalRead.call(this, file, ...args);
if (wasmFile) wasmReads.add(wasmFile);
return bytes;
};
syncBuiltinESMExports();
const hooks = registerHooks({
resolve(specifier, context, nextResolve) {
const result = nextResolve(specifier, context);
if (result.url.startsWith("file:")) loaded.add(inside(fileURLToPath(result.url)));
else assert.ok(result.url.startsWith("node:"), `unexpected dependency URL: ${result.url}`);
return result;
},
});
return {
report() {
assert.equal(attempts, 0, "image runtime attempted network access");
assert.ok(loaded.has(photonEntry), "Photon JS was not loaded from the payload");
assert.ok(wasmReads.has(photonWasm), "Photon WASM was not loaded from the payload");
return { modules: loaded.size, wasmFiles: [...wasmReads], networkAttempts: attempts };
},
cleanup() {
hooks.deregister();
fs.readFileSync = originalRead;
syncBuiltinESMExports();
},
};
}
/** Pure Node 2×2 RGBA PNG, with valid PNG CRCs and zlib image data. */
function fixturePng() {
const chunk = (type, data) => {
const buffer = Buffer.alloc(data.length + 12);
buffer.writeUInt32BE(data.length);
buffer.write(type, 4, "ascii");
data.copy(buffer, 8);
let crc = ~0;
for (const byte of buffer.subarray(4, -4)) {
crc ^= byte;
for (let bit = 0; bit < 8; bit++) crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
}
buffer.writeUInt32BE(~crc >>> 0, buffer.length - 4);
return buffer;
};
const header = Buffer.alloc(13);
header.writeUInt32BE(2, 0);
header.writeUInt32BE(2, 4);
header[8] = 8; // bit depth
header[9] = 6; // RGBA
const pixels = Buffer.from([
0, 255, 0, 0, 255, 0, 255, 0, 255,
0, 0, 0, 255, 255, 255, 255, 255, 255,
]);
return Buffer.concat([Buffer.from("89504e470d0a1a0a", "hex"), chunk("IHDR", header), chunk("IDAT", deflateSync(pixels)), chunk("IEND", Buffer.alloc(0))]);
}
function assertResized(result) {
assert.ok(result, "resize returned null (missing/broken Photon must not silently pass)");
assert.equal(result.originalWidth, 2);
assert.equal(result.originalHeight, 2);
assert.equal(result.width, 1);
assert.equal(result.height, 1);
assert.equal(result.wasResized, true);
assert.equal(result.mimeType, "image/png");
const png = Buffer.from(result.data, "base64");
assert.equal(png.subarray(0, 8).toString("hex"), "89504e470d0a1a0a");
assert.equal(png.readUInt32BE(16), 1);
assert.equal(png.readUInt32BE(20), 1);
}
async function probeWorker(root, inputBytes, options, responseType) {
const readyType = "lh:image-runtime-ready";
const worker = new Worker(`
const { parentPort, workerData } = require("node:worker_threads");
const guards = (${installGuards.toString()})(require, workerData.root);
const post = parentPort.postMessage.bind(parentPort);
parentPort.postMessage = (message, ...args) => {
if (message?.type === workerData.responseType) {
post({ ...message, runtimeProof: guards.report() }, ...args);
} else post(message, ...args);
};
void import(workerData.entry).then(() => post({ type: workerData.readyType }));
`, {
eval: true,
workerData: { root, responseType, readyType, entry: pathToFileURL(path.join(root, "src/agent/image-processing/resize-worker.mjs")).href },
});
try {
return await new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error("image resize worker timed out")), 10_000);
const fail = (error) => { clearTimeout(timer); reject(error); };
let ready = false;
worker.once("error", fail);
worker.once("exit", (code) => fail(new Error(`image resize worker exited before response: ${code}`)));
worker.on("message", (message) => {
try {
if (message?.type === readyType) {
assert.equal(ready, false, "duplicate worker READY");
ready = true;
worker.postMessage({ inputBytes, mimeType: "image/png", options });
} else if (message?.type === responseType) {
assert.equal(ready, true, "worker replied before its import completed");
assert.equal(message.error, undefined, `worker resize failed: ${message.error}`);
assertResized(message.result);
assert.ok(message.runtimeProof?.modules > 0, "missing worker module-confinement proof");
assert.equal(message.runtimeProof.networkAttempts, 0);
assert.equal(message.runtimeProof.wasmFiles.length, 1);
clearTimeout(timer);
resolve(message.runtimeProof);
}
} catch (error) { fail(error); }
});
});
} finally { await worker.terminate(); }
}
async function probe(standalone) {
const root = fs.realpathSync(standalone);
const guards = installGuards(createRequire(import.meta.url), root);
try {
const entry = pathToFileURL(path.join(root, "src/agent/image-processing/resize-core.mjs"));
const { resizeImageInProcess, IMAGE_RESIZE_WORKER_RESPONSE_TYPE } = await import(entry.href);
assert.equal(typeof resizeImageInProcess, "function");
assert.equal(IMAGE_RESIZE_WORKER_RESPONSE_TYPE, "pi:image-resize-response");
const input = fixturePng();
const options = { maxWidth: 1, maxHeight: 1, maxBytes: 1024 * 1024 };
assertResized(await resizeImageInProcess(input, "image/png", options));
const inProcess = guards.report();
console.log(`[smoke-image-runtime] shipped resize core: PNG 2×2 → 1×1; ${inProcess.modules} modules and Photon WASM confined to ${root}`);
const worker = await probeWorker(root, input, options, IMAGE_RESIZE_WORKER_RESPONSE_TYPE);
guards.report();
console.log(`[smoke-image-runtime] shipped resize worker: READY then tagged PNG 2×2 → 1×1; ${worker.modules} modules and Photon WASM confined to ${root}`);
console.log("[smoke-image-runtime] Photon in-process and worker resizing passed; zero network attempts");
} finally { guards.cleanup(); }
}
async function main() {
const args = process.argv.slice(2);
if (args[0] === "--probe") return probe(args[1]);
const value = (name, fallback) => {
const index = args.indexOf(name);
if (index < 0) return fallback;
assert.ok(args[index + 1] && !args[index + 1].startsWith("--"), `${name} requires a path`);
return path.resolve(args[index + 1]);
};
verifyImageRuntime(value("--standalone", path.resolve(import.meta.dirname, "../.next/standalone")), value("--runtime", process.execPath));
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
main().catch((error) => { console.error(`[smoke-image-runtime] FAIL: ${formatProbeError(error)}`); process.exitCode = 1; });
}