← Files ModRetro Chromatic PluginARCHIVED FILE

scripts/setup-runtime.mjs

37.1 KB · Oct 2, 2026 · 00:37 UTC

↓ Download file

/**
 * Prepare a compiled package in caller-owned staging, without executing its scripts.
 *
 * The caller holds the setup-root lock and owns promotion, quarantine and cleanup.
 * This module never reuses or removes an existing destination. Failure preserves
 * the new runtime and npm operation directory and reports the last known phase.
 *
 * The shipped runtime lock is the production-only projection (omit dev:true
 * entries and root devDependencies) of the public partner package-lock.json at
 * OpenAI-Partners/ext-openai-modretro@0a263ffb121cebc569c85e3012534d13a5268987.
 * Preserve its exact dependency versions/integrities; do not copy the private
 * checkout's registry URLs into this portable installer. Release root versions
 * are rebased in the owned destination, never by editing the installed payload.
 */

import { createHash, randomUUID } from "node:crypto";
import { constants } from "node:fs";
import { access, lstat, mkdir, open, readFile, readdir, realpath, stat } from "node:fs/promises";
import path from "node:path";
import { ensureOwnedDirectory, runSetupCommand, setupEnvironment } from "./setup-io.mjs";
import {
  CHROMATIC_PACKED, CHROMATIC_STORAGE_SOURCES, CHROMATIC_VERSION, chromaticRequiredPaths, chromaticTargetFromManifest, chromaticFormatFromManifest,
  requireChromaticHostTarget, verifyChromaticBundle, verifyChromaticRecords,
} from "./chromatic-runtime.mjs";

const PACKAGE_NAME = "modretro-chromatic";
const MAX_FILES = 4096;
// Match the universal package limits in prepare-plugin-payload.mjs and start-mcp.mjs.
const MAX_BYTES = 136 * 1024 * 1024;
const MAX_FILE_BYTES = 24 * 1024 * 1024;
const AUTOMATIC_PACKAGE_FILES = new Set(["LICENSE", "README.md", "THIRD_PARTY_NOTICES.md"]);
const LOCAL_ONLY_FILES = new Set(["package.json", "package-lock.json", ".codex-plugin/local-payload.json", ".gitignore", ".npmignore"]);
const PRIVATE_DIRECTORIES = new Set([".git", ".agents", ".codex", ".local", "node_modules", "vendor", "artifacts", ".cache", "__pycache__", "coverage", "save-states", "src", "tests", "test"]);
const WRECKLIGHT_MANIFEST = "examples/wrecklight/MANIFEST.json";
const WRECKLIGHT_MANIFEST_PIN = Object.freeze({ bytes: 228142, sha256: "239d182ec218f5bc4145f58dc3c4ed3ec5ad3fc1425356c63c79269b659f9cdc" });

const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
const jsonBytes = (value) => Buffer.from(JSON.stringify(value, null, 2) + "\n");
const key = (value) => process.platform === "win32" ? value.toLowerCase() : value;
const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
const stableObject = (value) => JSON.stringify(Object.fromEntries(Object.entries(value ?? {}).sort(([a], [b]) => a.localeCompare(b))));

async function writeRuntimeFile(filename, bytes, mode) {
  const handle = await open(filename, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
  try {
    await handle.writeFile(bytes);
    // setup.sh intentionally uses umask 077. Publish the pinned mode only
    // after writing, through the same exclusively created file descriptor.
    await handle.chmod(mode);
  } finally { await handle.close(); }
}

function within(root, candidate) {
  const relative = path.relative(key(root), key(candidate));
  return relative === "" || (relative !== ".." && !relative.startsWith(".." + path.sep) && !path.isAbsolute(relative));
}

function absolute(value, label) {
  if (typeof value !== "string" || !path.isAbsolute(value) || /[\u0000-\u001f]/u.test(value)) {
    throw new Error(label + " must be an explicit absolute path.");
  }
  return path.resolve(value);
}

async function metadata(filename) {
  try { return await lstat(filename); }
  catch (error) {
    if (error.code === "ENOENT" || error.code === "ENOTDIR") return undefined;
    throw error;
  }
}

/** Reject links/junctions in owned write paths and copied package members. */
async function unlinked(filename, { allowMissing = false } = {}) {
  const selected = path.resolve(filename);
  const root = path.parse(selected).root;
  let current = root;
  for (const component of selected.slice(root.length).split(path.sep).filter(Boolean)) {
    current = path.join(current, component);
    const entry = await metadata(current);
    if (!entry) {
      if (allowMissing) continue;
      throw new Error("Required path is missing: " + current);
    }
    if (entry.isSymbolicLink() || key(await realpath(current)) !== key(current)) {
      throw new Error("Runtime preparation cannot traverse a symbolic link or junction: " + current);
    }
  }
  return selected;
}

async function directory(filename, label) {
  const selected = await realpath(absolute(filename, label));
  if (!(await stat(selected)).isDirectory()) throw new Error(label + " must be a directory.");
  return selected;
}

function relativeFile(filename) {
  if (typeof filename !== "string" || filename.length === 0 || filename.length > 1024 || /[\\:<>"|?*\u0000-\u001f]/u.test(filename)) {
    throw new Error("Invalid runtime package member: " + filename);
  }
  const components = filename.split("/");
  if (components.length > 32 || components.some((part) => !part || part === "." || part === ".." || /[. ]$/u.test(part) || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(part))) {
    throw new Error("Invalid runtime package member: " + filename);
  }
  return filename;
}

function runtimeMember(filename) {
  // Maintainer compression input is not a second runtime representation.
  return !LOCAL_ONLY_FILES.has(filename) && !CHROMATIC_STORAGE_SOURCES.includes(filename);
}

function assertPublicMember(filename, enginePaths = new Set()) {
  relativeFile(filename);
  const lower = filename.toLowerCase();
  const parts = lower.split("/");
  const basename = parts.at(-1);
  if (parts.some((part) => PRIVATE_DIRECTORIES.has(part) && !(part === "src" && enginePaths.has(filename))) || basename === ".npmrc" || basename === ".ds_store" ||
    basename === ".env" || basename.startsWith(".env.") || /\.(?:sav|ram|rtc|state|pyc|pyo)$/u.test(basename) ||
    /\.test\.[cm]?[jt]sx?$/u.test(basename) || /\.(?:playtest|gbstate)\.json$/u.test(basename) ||
    /^dist\/playtest\.(?:js|js\.map|d\.ts)$/u.test(lower) || /^scripts\/dogfood-/u.test(lower) ||
    lower === "scripts/integration-smoke.mjs" || lower === "scripts/test-isolated.mjs") {
    throw new Error("Refusing private or test-only runtime package member: " + filename);
  }
}

// Pin native game inputs independently of the preparation receipt. Only the
// exact delivered manifest can authorize engine/src files and ancestors.
async function bundledEngineSourcePolicy(root) {
  const paths = new Set();
  const members = new Map();
  const pins = new Map();
  if (!await metadata(path.join(root, WRECKLIGHT_MANIFEST))) return { paths, members, pins };
  const manifest = await regularBytes(root, WRECKLIGHT_MANIFEST);
  assertBundledSourceBytes(WRECKLIGHT_MANIFEST, manifest, WRECKLIGHT_MANIFEST_PIN);
  const parsed = JSON.parse(manifest.bytes.toString("utf8"));
  for (const member of parsed.files) {
    const engineSource = /^plugins\/[^/]+\/engine\/src\//u.test(member.path);
    if (!engineSource && !/^plugins\/[^/]+\/(?:engine\/include|events)\//u.test(member.path)) continue;
    const filename = "examples/wrecklight/" + member.path;
    relativeFile(filename);
    pins.set(filename, member);
    if (!engineSource) continue;
    members.set(filename, member);
    const components = filename.split("/");
    for (let index = 1; index <= components.length; index++) paths.add(components.slice(0, index).join("/"));
  }
  return { paths, members, pins };
}

function assertBundledSourceBytes(filename, actual, expected) {
  if (expected && (actual.bytes.length !== expected.bytes || digest(actual.bytes) !== expected.sha256 || actual.mode !== 0o644)) {
    throw new Error("Bundled Wrecklight engine source or manifest differs from its pin: " + filename);
  }
}

function requireBundledManifest(files, policy) {
  const paths = new Set(files.map((file) => file.path));
  if ([...paths].some((filename) => policy.pins.has(filename)) && !paths.has(WRECKLIGHT_MANIFEST)) {
    throw new Error("Bundled Wrecklight engine source requires its pinned manifest in the distribution.");
  }
}

async function regularBytes(root, relative, limit = MAX_FILE_BYTES) {
  relativeFile(relative);
  const filename = path.join(root, ...relative.split("/"));
  if (!within(root, filename)) throw new Error("Package member escapes its root: " + relative);
  await unlinked(filename);
  const before = await lstat(filename);
  if (!before.isFile() || before.size > limit) throw new Error("Package member is not a bounded regular file: " + relative);
  const handle = await open(filename, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
  try {
    const opened = await handle.stat();
    if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size) {
      throw new Error("Package member changed while opening: " + relative);
    }
    const bytes = await handle.readFile();
    const after = await handle.stat();
    if (bytes.length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs) {
      throw new Error("Package member changed while reading: " + relative);
    }
    await unlinked(filename);
    return { bytes, mode: before.mode & 0o111 ? 0o755 : 0o644 };
  } finally { await handle.close(); }
}

async function objectFile(root, relative) {
  const result = JSON.parse((await regularBytes(root, relative, 1024 * 1024)).bytes.toString("utf8"));
  if (!isObject(result)) throw new Error(relative + " must contain a JSON object.");
  return result;
}

/** Resolve only the npm installed beside the explicitly selected Node binary. */
export async function resolveRuntimeNpm({ nodeExecutable }) {
  const selectedNode = await realpath(absolute(nodeExecutable, "Node executable"));
  if (!(await lstat(selectedNode)).isFile()) throw new Error("Node executable must be a regular file.");
  await access(selectedNode, constants.X_OK);
  const nodeDirectory = path.dirname(selectedNode);
  const candidates = [
    path.join(nodeDirectory, "node_modules", "npm"),
    path.resolve(nodeDirectory, "..", "lib", "node_modules", "npm"),
  ];
  for (const npmRoot of candidates) {
    if (!await metadata(npmRoot)) continue;
    await unlinked(npmRoot);
    const manifest = await objectFile(npmRoot, "package.json");
    if (manifest.name !== "npm" || typeof manifest.version !== "string") throw new Error("The selected Node installation has an invalid npm manifest.");
    if (!/^\d+\.\d+\.\d+(?:[-+].*)?$/u.test(manifest.version) || Number(manifest.version.split(".")[0]) < 9) {
      throw new Error("The selected Node installation needs npm 9 or newer for the reviewed lockfile format.");
    }
    await regularBytes(npmRoot, "bin/npm-cli.js", 1024 * 1024);
    return { nodeExecutable: selectedNode, npmCli: path.join(npmRoot, "bin", "npm-cli.js"), npmVersion: manifest.version };
  }
  throw new Error("The selected Node installation has no sibling npm-cli.js; choose a Node distribution containing npm.");
}

/** The product allowlist uses literal positive paths and ordinary exclusion globs. */
function exclusion(pattern) {
  let result = "";
  for (let index = 0; index < pattern.length; index++) {
    const character = pattern[index];
    if (character === "*" && pattern[index + 1] === "*") {
      index++;
      if (pattern[index + 1] === "/") { index++; result += "(?:.*/)?"; }
      else result += ".*";
    } else if (character === "*") result += "[^/]*";
    else if (character === "?") result += "[^/]";
    else if (character === "[") {
      const end = pattern.indexOf("]", index + 1);
      if (end < 0 || !/^[A-Za-z0-9-]+$/u.test(pattern.slice(index + 1, end))) throw new Error("Unsupported package exclusion: " + pattern);
      result += pattern.slice(index, end + 1); index = end;
    } else result += /[\\^$+.()|{}]/u.test(character) ? "\\" + character : character;
  }
  return new RegExp("^" + result + "(?:/.*)?$", "u");
}

async function distributionFiles(packageRoot, packageManifest) {
  const chromaticTarget = chromaticTargetFromManifest(packageManifest);
  const chromaticFormat = chromaticFormatFromManifest(packageManifest);
  requireChromaticHostTarget(chromaticTarget);
  const enginePolicy = await bundledEngineSourcePolicy(packageRoot);
  const records = new Map();
  let visited = 0;
  let totalBytes = 0;
  const seen = new Set();
  async function add(relative, receiptRecord) {
    assertPublicMember(relative, enginePolicy.members);
    if (!runtimeMember(relative)) return;
    const identity = relative.toLowerCase();
    if (seen.has(identity)) throw new Error("Duplicate or case-aliased runtime member: " + relative);
    seen.add(identity);
    const file = await regularBytes(packageRoot, relative);
    assertBundledSourceBytes(relative, file, relative === WRECKLIGHT_MANIFEST ? WRECKLIGHT_MANIFEST_PIN : enginePolicy.pins.get(relative));
    const sha256 = digest(file.bytes);
    if (receiptRecord && (receiptRecord.sha256 !== sha256 || receiptRecord.size !== file.bytes.length || receiptRecord.mode !== file.mode)) {
      throw new Error("Prepared payload member differs from its receipt: " + relative);
    }
    totalBytes += file.bytes.length;
    if (seen.size > MAX_FILES || totalBytes > MAX_BYTES) throw new Error("Compiled runtime package exceeds its copy limits.");
    records.set(relative, { path: relative, ...file, sha256 });
  }

  const receiptPresent = await metadata(path.join(packageRoot, ".codex-plugin", "local-payload.json"));
  if (!receiptPresent && Array.isArray(packageManifest.files) && packageManifest.files.length > 0) {
    if (!packageManifest.files.every((entry) => typeof entry === "string")) throw new Error("The package files allowlist must contain strings.");
    const excluded = packageManifest.files.filter((entry) => entry.startsWith("!")).map((entry) => exclusion(entry.slice(1)));
    const selected = packageManifest.files.filter((entry) => !entry.startsWith("!")).map((entry) => entry.replace(/\/$/u, ""));
    async function visit(relative) {
      if (excluded.some((pattern) => pattern.test(relative))) return;
      relativeFile(relative);
      if (!runtimeMember(relative)) return;
      if (++visited > MAX_FILES * 2) throw new Error("Compiled runtime package contains too many entries.");
      const filename = path.join(packageRoot, ...relative.split("/"));
      const entry = await metadata(filename);
      if (!entry) return;
      await unlinked(filename);
      if (entry.isDirectory()) {
        assertPublicMember(relative, enginePolicy.paths);
        for (const child of (await readdir(filename)).sort()) await visit(relative + "/" + child);
      } else if (!records.has(relative)) await add(relative);
    }
    for (const relative of selected) await visit(relative);
    for (const filename of AUTOMATIC_PACKAGE_FILES) {
      if (await metadata(path.join(packageRoot, filename)) && !records.has(filename)) await add(filename);
    }
  } else {
    // Prepared Codex payloads intentionally have files:[] and scripts:{}.
    const receipt = await objectFile(packageRoot, ".codex-plugin/local-payload.json");
    if (receipt.schemaVersion !== 1 || receipt.pluginName !== PACKAGE_NAME || !Array.isArray(receipt.files) || receipt.files.length > MAX_FILES) {
      throw new Error("A package without a files allowlist requires its prepared local-payload receipt.");
    }
    const identities = new Set();
    let packageVerified = false;
    for (const entry of receipt.files) {
      if (!isObject(entry)) throw new Error("Invalid local-payload file receipt.");
      relativeFile(entry.path);
      if (identities.has(entry.path.toLowerCase())) throw new Error("Duplicate local-payload file receipt.");
      identities.add(entry.path.toLowerCase());
      if (entry.path === "package.json") {
        const actual = await regularBytes(packageRoot, "package.json", 1024 * 1024);
        if (entry.sha256 !== digest(actual.bytes) || entry.size !== actual.bytes.length || entry.mode !== actual.mode) throw new Error("Prepared payload package manifest differs from its receipt.");
        packageVerified = true;
      }
      if (runtimeMember(entry.path)) await add(entry.path, entry);
    }
    if (!packageVerified) throw new Error("Prepared local-payload receipt does not identify its package manifest.");
  }
  for (const required of [".codex-plugin/plugin.json", ".mcp.json", "dist/server.js", "scripts/start-mcp.mjs", "scripts/chromatic-runtime.mjs", "scripts/setup-io.mjs", "scripts/emulator_worker.py", "scripts/runtime-package.json", "scripts/runtime-package-lock.json"]) {
    if (!records.has(required)) throw new Error("Compiled distribution is missing a required runtime member: " + required);
  }
  if (packageManifest.bundledChromatic !== undefined) {
    if (packageManifest.bundledChromatic !== CHROMATIC_VERSION) throw new Error("Unsupported bundled Chromatic release.");
    for (const required of chromaticRequiredPaths(chromaticTarget, chromaticFormat)) {
      if (!records.has(required)) throw new Error("Compiled distribution is missing bundled Chromatic member: " + required);
    }
    verifyChromaticBundle(packageRoot, { target: chromaticTarget, format: chromaticFormat });
    verifyChromaticRecords([...records.values()], { target: chromaticTarget, format: chromaticFormat });
  }
  requireBundledManifest([...records.values()], enginePolicy);
  return [...records.values()].sort((left, right) => left.path.localeCompare(right.path, "en"));
}

const fileInventory = (files) => files.map(({ path: filename, mode, bytes, sha256 }) => ({ path: filename, mode, size: bytes.length, sha256 }));

async function inspectSource(packageRoot) {
  const root = await directory(packageRoot, "Package root");
  const manifestFile = await regularBytes(root, "package.json", 1024 * 1024);
  const manifest = JSON.parse(manifestFile.bytes.toString("utf8"));
  if (!isObject(manifest)) throw new Error("The source package manifest must be a JSON object.");
  const files = await distributionFiles(root, manifest);
  const inventory = [...fileInventory(files), { path: "package.json", mode: manifestFile.mode, size: manifestFile.bytes.length, sha256: digest(manifestFile.bytes) }]
    .sort((left, right) => left.path.localeCompare(right.path, "en"));
  return { root, manifest, files, sourceSha256: digest(JSON.stringify(inventory)) };
}

/** Passive identity of the complete reviewed input, before portable rewrites. */
export async function inspectRuntimeSource({ packageRoot }) {
  return (await inspectSource(packageRoot)).sourceSha256;
}

/**
 * Passive verification of a promoted runtime against its preparation receipt.
 * node_modules is npm-owned and inspected separately by dependency doctor; every
 * other member must match the copied distribution or the exact manifest/lock.
 */
export async function verifyPreparedRuntime({ runtimeRoot, provenance }) {
  const root = absolute(runtimeRoot, "Prepared runtime root");
  await unlinked(root);
  if (!(await lstat(root)).isDirectory()) throw new Error("Prepared runtime must be a real directory.");
  const modules = await metadata(path.join(root, "node_modules"));
  if (!modules?.isDirectory() || modules.isSymbolicLink()) throw new Error("Prepared runtime must retain its real npm-owned node_modules directory.");
  if (!isObject(provenance) || provenance.status !== "prepared" || !Array.isArray(provenance.files) ||
    provenance.files.length < 1 || provenance.files.length > MAX_FILES ||
    ![provenance.distributionSha256, provenance.manifestSha256, provenance.lockSha256].every((value) => typeof value === "string" && /^[a-f0-9]{64}$/u.test(value))) {
    throw new Error("Prepared runtime has no bounded complete distribution receipt.");
  }
  const enginePolicy = await bundledEngineSourcePolicy(root);
  const expectedFiles = new Set(["package.json", "package-lock.json"]);
  const expectedDirectories = new Set([""]);
  const identities = new Set();
  let totalBytes = 0;
  for (const entry of provenance.files) {
    if (!isObject(entry)) throw new Error("Invalid prepared runtime file receipt.");
    assertPublicMember(entry.path, enginePolicy.members);
    if (!runtimeMember(entry.path) || identities.has(entry.path.toLowerCase()) ||
      ![0o644, 0o755].includes(entry.mode) || !Number.isSafeInteger(entry.size) || entry.size < 0 || entry.size > MAX_FILE_BYTES ||
      typeof entry.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(entry.sha256)) throw new Error("Invalid or duplicate prepared runtime file receipt: " + entry.path);
    totalBytes += entry.size;
    if (totalBytes > MAX_BYTES) throw new Error("Prepared runtime receipt exceeds its total size bound.");
    identities.add(entry.path.toLowerCase()); expectedFiles.add(entry.path);
    const pieces = entry.path.split("/");
    for (let index = 1; index < pieces.length; index++) expectedDirectories.add(pieces.slice(0, index).join("/"));
  }
  if (digest(JSON.stringify(provenance.files)) !== provenance.distributionSha256) throw new Error("Prepared runtime inventory no longer matches its recorded digest.");
  requireBundledManifest(provenance.files, enginePolicy);
  for (const required of [".codex-plugin/plugin.json", ".mcp.json", "dist/server.js", "scripts/start-mcp.mjs", "scripts/chromatic-runtime.mjs", "scripts/setup-io.mjs", "scripts/emulator_worker.py", "scripts/runtime-package.json", "scripts/runtime-package-lock.json"]) {
    if (!expectedFiles.has(required)) throw new Error("Prepared runtime inventory lacks a required member: " + required);
  }
  let visited = 0;
  async function visit(directoryName, prefix = "") {
    for (const entry of await readdir(directoryName, { withFileTypes: true })) {
      if (++visited > MAX_FILES * 2) throw new Error("Prepared runtime has too many directory entries.");
      const relative = prefix ? prefix + "/" + entry.name : entry.name;
      if (entry.isSymbolicLink()) throw new Error("Prepared runtime has a redirected member: " + relative);
      if (relative === "node_modules") {
        if (!entry.isDirectory()) throw new Error("Prepared runtime node_modules must be a real directory.");
        await unlinked(path.join(root, "node_modules"));
        continue;
      }
      if (entry.isDirectory()) {
        if (!expectedDirectories.has(relative)) throw new Error("Prepared runtime has an unlisted directory: " + relative);
        await visit(path.join(directoryName, entry.name), relative);
      } else if (!entry.isFile() || !expectedFiles.has(relative)) throw new Error("Prepared runtime has an unlisted member: " + relative);
    }
  }
  await visit(root);
  for (const entry of provenance.files) {
    const actual = await regularBytes(root, entry.path);
    assertBundledSourceBytes(entry.path, actual, entry.path === WRECKLIGHT_MANIFEST ? WRECKLIGHT_MANIFEST_PIN : enginePolicy.pins.get(entry.path));
    if (actual.bytes.length !== entry.size || actual.mode !== entry.mode || digest(actual.bytes) !== entry.sha256) {
      throw new Error("Prepared runtime distribution member changed: " + entry.path);
    }
  }
  let manifest;
  for (const [filename, hash] of [["package.json", provenance.manifestSha256], ["package-lock.json", provenance.lockSha256]]) {
    const actual = await regularBytes(root, filename, 1024 * 1024);
    if (digest(actual.bytes) !== hash) throw new Error("Prepared runtime manifest/lock changed: " + filename);
    if (filename === "package.json") manifest = JSON.parse(actual.bytes.toString("utf8"));
  }
  const chromaticTarget = chromaticTargetFromManifest(manifest);
  const chromaticFormat = chromaticFormatFromManifest(manifest);
  requireChromaticHostTarget(chromaticTarget);
  if (manifest.bundledChromatic === CHROMATIC_VERSION) verifyChromaticBundle(root, { target: chromaticTarget, format: chromaticFormat });
  return true;
}

function validateManifests(source, manifest, lock) {
  if (source.name !== PACKAGE_NAME || typeof source.version !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u.test(source.version)) {
    throw new Error("Runtime preparation requires a versioned modretro-chromatic package.");
  }
  const allowed = new Set(["name", "version", "description", "private", "type", "main", "engines", "license", "dependencies"]);
  if (!isObject(manifest.dependencies) || Object.keys(manifest).some((field) => !allowed.has(field)) ||
    manifest.name !== PACKAGE_NAME || manifest.private !== true || manifest.type !== "module" || manifest.main !== "./dist/server.js" ||
    stableObject(manifest.dependencies) !== stableObject(source.dependencies) || stableObject(manifest.engines) !== stableObject(source.engines)) {
    throw new Error("The shipped runtime manifest does not match this package's reviewed production dependencies.");
  }
  if (lock.lockfileVersion !== 3 || lock.name !== PACKAGE_NAME || !isObject(lock.packages) || !isObject(lock.packages[""]) ||
    stableObject(lock.packages[""].dependencies) !== stableObject(manifest.dependencies) || lock.packages[""].devDependencies !== undefined) {
    throw new Error("The shipped runtime lock does not match its production manifest.");
  }
  for (const [filename, entry] of Object.entries(lock.packages)) {
    if (filename === "") continue;
    relativeFile(filename);
    if (!filename.startsWith("node_modules/") || !isObject(entry) || entry.dev || entry.link || typeof entry.version !== "string") {
      throw new Error("The runtime lock contains a non-production registry package: " + filename);
    }
    const resolved = new URL(entry.resolved);
    if (resolved.origin !== "https://registry.npmjs.org" || resolved.username || resolved.password || resolved.search || resolved.hash ||
      !/^sha(?:256|384|512)-[A-Za-z0-9+/=]+$/u.test(entry.integrity ?? "")) {
      throw new Error("Runtime dependencies require public-registry URLs and integrity hashes: " + filename);
    }
  }
  for (const dependency of Object.keys(manifest.dependencies)) {
    if (!lock.packages["node_modules/" + dependency]) throw new Error("The runtime lock is missing direct dependency " + dependency);
  }
  return {
    manifest: { ...manifest, version: source.version },
    lock: { ...lock, version: source.version, packages: { ...lock.packages, "": { ...lock.packages[""], version: source.version } } },
  };
}

/** Restore a portable source eligible for a NEW payload; never retain old bindings. */
function portablePluginFiles(files, version) {
  const manifestFile = files.find((file) => file.path === ".codex-plugin/plugin.json");
  const manifest = JSON.parse(manifestFile.bytes.toString("utf8"));
  if (!isObject(manifest) || manifest.name !== PACKAGE_NAME || typeof manifest.version !== "string" ||
    manifest.version.split("+")[0] !== version.split("+")[0]) {
    throw new Error("The distributed plugin manifest does not match this package release.");
  }
  manifestFile.bytes = jsonBytes({ ...manifest, version: version.split("+")[0] });
  manifestFile.sha256 = digest(manifestFile.bytes);
  const configuration = files.find((file) => file.path === ".mcp.json");
  const original = JSON.parse(configuration.bytes.toString("utf8"));
  if (!isObject(original?.mcpServers?.["modretro-chromatic"])) throw new Error("The distributed plugin is missing its ModRetro Chromatic MCP launcher.");
  configuration.bytes = jsonBytes({ mcpServers: { "modretro-chromatic": {
    // Allow the 600-second compiler limit plus source validation and cleanup.
    tool_timeout_sec: 900,
    type: "stdio", cwd: ".", command: "node", args: ["./scripts/start-mcp.mjs"], env: { GB_STUDIO_LOG_LEVEL: "info" },
  } } });
  configuration.sha256 = digest(configuration.bytes);
}

function npmEnvironment(parent, shared) {
  const result = { ...shared };
  const networkNpm = new Set(["NPM_CONFIG_CA", "NPM_CONFIG_CAFILE", "NPM_CONFIG_PROXY", "NPM_CONFIG_HTTPS_PROXY", "NPM_CONFIG_NOPROXY"]);
  const seen = new Map();
  for (const [name, value] of Object.entries(parent ?? {})) {
    if (value === undefined) continue;
    const normalized = name.toUpperCase();
    if (!networkNpm.has(normalized)) continue;
    if (seen.has(normalized) && seen.get(normalized) !== value) throw new Error("Conflicting environment aliases for " + name);
    seen.set(normalized, value);
    result[normalized.toLowerCase()] = value;
  }
  return {
    ...result, npm_config_registry: "https://registry.npmjs.org", npm_config_strict_ssl: "true", npm_config_global: "false",
    npm_config_ignore_scripts: "true", npm_config_audit: "false", npm_config_fund: "false", npm_config_update_notifier: "false",
  };
}

export class RuntimePreparationError extends Error {
  constructor(message, { cause, phase, runtimeRoot, operationRoot, cancelled, execution }) {
    super(message, { cause });
    this.name = "RuntimePreparationError";
    this.code = cancelled ? "RUNTIME_PREPARATION_CANCELLED" : "RUNTIME_PREPARATION_FAILED";
    this.phase = phase; this.runtimeRoot = runtimeRoot; this.operationRoot = operationRoot;
    // Preserve process-exit/cleanup evidence at this boundary as well as in the
    // original cause. The setup coordinator must retain its lock if exit was
    // not observed, even though this wrapper adds a runtime-specific phase.
    this.details = { ...(execution ?? {}), ...(cause?.details ?? {}), phase, runtimeRoot, operationRoot };
  }
}

/**
 * Explicit apply only. setupRoot already exists and is owned/locked by caller.
 * runtimeRoot is a fresh nonexistent child, commonly inside caller staging.
 * runCommand is an injectable executor for unit tests; no npm runs on import.
 */
export async function prepareRuntime({ packageRoot, runtimeRoot, setupRoot, nodeExecutable, environment = process.env, signal, onProgress, runCommand = runSetupCommand }) {
  let phase = "preflight"; let operationRoot; let destination; let execution;
  const cancelled = () => { if (signal?.aborted) throw new Error("Runtime preparation was cancelled."); };
  const progress = (next, message) => { phase = next; onProgress?.({ phase, message }); cancelled(); };
  try {
    cancelled();
    const source = await directory(packageRoot, "Package root");
    const owned = await directory(setupRoot, "Owned setup root");
    destination = absolute(runtimeRoot, "Runtime destination");
    await unlinked(owned); await unlinked(destination, { allowMissing: true });
    if (!within(owned, destination) || key(owned) === key(destination) || within(source, destination) || within(destination, source) || within(source, owned)) {
      throw new Error("Runtime staging must be a child of an owned setup root separate from the source package.");
    }
    if (await metadata(destination)) throw new Error("Runtime destination already exists; caller must select a fresh staging child.");
    if (!(await stat(path.dirname(destination))).isDirectory()) throw new Error("Runtime destination parent must already exist in caller-owned staging.");
    const npm = await resolveRuntimeNpm({ nodeExecutable });
    if (within(source, npm.nodeExecutable) || within(destination, npm.nodeExecutable) || within(source, npm.npmCli) || within(destination, npm.npmCli)) {
      throw new Error("Selected Node and npm must be outside the source package and runtime destination.");
    }
    const inspected = await inspectSource(source);
    const sourceManifest = inspected.manifest;
    const files = inspected.files;
    const reviewedManifest = JSON.parse(files.find((file) => file.path === "scripts/runtime-package.json").bytes.toString("utf8"));
    const reviewedLock = JSON.parse(files.find((file) => file.path === "scripts/runtime-package-lock.json").bytes.toString("utf8"));
    if (!isObject(reviewedManifest) || !isObject(reviewedLock)) throw new Error("Runtime dependency manifests must contain JSON objects.");
    const prepared = validateManifests(sourceManifest, reviewedManifest, reviewedLock);
    const sourceSha256 = inspected.sourceSha256;
    portablePluginFiles(files, prepared.manifest.version);
    // Literal inventory entries work for both built checkouts and files:[]
    // cached payloads, without shipping source/dev files or stale cache markers.
    prepared.manifest.files = files.map((file) => file.path);
    prepared.manifest.scripts = {};
    if (sourceManifest.bundledChromatic === CHROMATIC_VERSION) {
      prepared.manifest.bundledChromatic = sourceManifest.bundledChromatic;
      const chromaticTarget = chromaticTargetFromManifest(sourceManifest);
      if (chromaticTarget !== undefined) prepared.manifest.bundledChromaticTarget = chromaticTarget;
      const chromaticFormat = chromaticFormatFromManifest(sourceManifest);
      if (chromaticFormat !== undefined) prepared.manifest.bundledChromaticFormat = chromaticFormat;
      prepared.manifest.license = "SEE LICENSE IN THIRD_PARTY_NOTICES.md";
      prepared.lock.packages[""].license = prepared.manifest.license;
    }
    const manifestBytes = jsonBytes(prepared.manifest); const lockBytes = jsonBytes(prepared.lock);
    const copiedFiles = fileInventory(files);
    cancelled();
    // The caller holds this setup root's lock. Only npm's integrity-checked
    // archive cache is shared; temporary files, config and logs stay per attempt.
    const npmCachePath = path.join(owned, "cache", "npm");
    if (within(destination, npmCachePath) || within(npmCachePath, destination)) {
      throw new Error("Runtime staging must remain separate from the shared npm cache.");
    }
    if (within(source, npmCachePath) || within(npmCachePath, source)) {
      throw new Error("The source package must remain separate from the shared npm cache.");
    }
    const npmCache = await ensureOwnedDirectory(owned, npmCachePath);
    progress("copy", "Copying reviewed compiled runtime into fresh owned staging.");
    await mkdir(destination, { mode: 0o700 });
    for (const file of files) {
      cancelled();
      const filename = path.join(destination, ...file.path.split("/"));
      await unlinked(path.dirname(filename), { allowMissing: true });
      await mkdir(path.dirname(filename), { recursive: true, mode: 0o700 });
      await unlinked(path.dirname(filename));
      await writeRuntimeFile(filename, file.bytes, file.mode);
    }
    await writeRuntimeFile(path.join(destination, "package.json"), manifestBytes, 0o644);
    await writeRuntimeFile(path.join(destination, "package-lock.json"), lockBytes, 0o644);
    operationRoot = path.join(owned, ".runtime-prepare-" + randomUUID());
    await mkdir(operationRoot, { mode: 0o700 });
    const sharedEnvironment = await setupEnvironment({ setupRoot: operationRoot, nodeExecutable: npm.nodeExecutable, environment, platform: process.platform });
    const npmLogs = await ensureOwnedDirectory(operationRoot, path.join(operationRoot, "logs", "npm"));
    const env = npmEnvironment(environment, { ...sharedEnvironment, npm_config_cache: npmCache, npm_config_logs_dir: npmLogs });
    progress("npm", "Installing locked production dependencies with scripts disabled and owned npm storage.");
    const result = await runCommand(npm.nodeExecutable, [npm.npmCli, "ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund", "--workspaces=false", "--global=false", "--prefix", destination], {
      cwd: destination, env, signal, onProgress, label: "Runtime npm dependencies", timeoutMs: 10 * 60_000,
    });
    execution = result;
    cancelled();
    if (result?.cancelled) throw new Error("Runtime dependency installation was cancelled after its npm process closed.");
    if (result?.timedOut) throw new Error("Runtime dependency installation exceeded its bounded npm timeout.");
    if (result?.exitObserved === false || result?.cleanupErrors?.length) throw new Error("Runtime npm process cleanup was not confirmed; preserve the setup lock and partial state.");
    const exitCode = result?.exitCode ?? result?.status;
    if (exitCode !== 0) throw new Error("Locked production npm installation failed (status " + exitCode + "): " + String(result?.stderr ?? "").slice(-4096));
    progress("verify", "Checking installed production package versions without importing them.");
    await unlinked(destination);
    for (const dependency of Object.keys(prepared.manifest.dependencies)) {
      const metadataPath = "node_modules/" + dependency + "/package.json";
      const installed = await objectFile(destination, metadataPath);
      if (installed.name !== dependency || installed.version !== prepared.lock.packages["node_modules/" + dependency].version) {
        throw new Error("Prepared runtime dependency metadata does not match its lock: " + dependency);
      }
    }
    for (const file of files) {
      cancelled();
      if (digest((await regularBytes(destination, file.path)).bytes) !== file.sha256) {
        throw new Error("A copied distribution member changed during dependency installation: " + file.path);
      }
    }
    if (digest(await readFile(path.join(destination, "package.json"))) !== digest(manifestBytes) ||
      digest(await readFile(path.join(destination, "package-lock.json"))) !== digest(lockBytes)) {
      throw new Error("The prepared runtime manifest or lock changed during dependency installation.");
    }
    cancelled();
    return { status: "prepared", runtimeRoot: destination, operationRoot, ...npm, packageVersion: prepared.manifest.version,
      manifestSha256: digest(manifestBytes), lockSha256: digest(lockBytes), sourceSha256,
      distributionSha256: digest(JSON.stringify(copiedFiles)), files: copiedFiles, filesCopied: files.length };
  } catch (cause) {
    throw new RuntimePreparationError("Runtime preparation failed during " + phase + "; any owned partial staging is preserved. " + cause.message, {
      cause, phase, runtimeRoot: destination ?? runtimeRoot, operationRoot, cancelled: signal?.aborted === true || cause?.code === "ABORT_ERR",
      execution,
    });
  }
}

SHA-256: 70e30e424fd611484132d70c6125e3a2676f97ba0e5decd52cdaa2626a377754