← Files DataARCHIVED FILE

tests/data-app-sites-package-source.test.mjs

35.1 KB · Sep 30, 2026 · 23:19 UTC

↓ Download file

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import test, { describe } from "node:test";

import {
  digest, fixture, fixtureOwnerSource, fixtureWorkerSource, legacyFixture,
  packageDataApp as packageDefaultDataApp, updateFixtureProtectedManifest,
  verifyFixtureProtectedManifest, writeSnapshot,
} from "./data-app-sites-package-fixtures.mjs";

const packageDataApp = (state, options = {}) => packageDefaultDataApp(state, { ...options, source: true });
const publicationPaths = [
  "dist/index.html", "src/data.json", "src/data-app-owner.js", ".openai/hosting.json",
  "protected-runtime.json", "dist/.openai/hosting.json", "dist/server/index.js",
  ".data-app-assets/manifest.json", ".data-app-assets/html.html", ".data-app-assets/snapshot.json", ".data-app-offline/index.html",
];
function publicationBytes({ project }) {
  return publicationPaths.map((path) => (existsSync(join(project, path)) ? readFileSync(join(project, path)) : null));
}

test("explicit source packaging verifies and retains a customized local Worker without installing packages", () => {
  const state = fixture();
  const originalHtml = readFileSync(join(state.project, "dist/index.html"));
  const originalSnapshot = JSON.parse(readFileSync(join(state.project, "src/data.json")));
  const customized = `${fixtureWorkerSource}\n// authorized source customization\n`;
  writeFileSync(join(state.project, "src/worker.js"), customized);
  updateFixtureProtectedManifest(state, ["src/worker.js"]);
  const output = JSON.parse(
    packageDataApp(state, {
      source: true,
      environment: { ...process.env, PATH: "", NAPI_RS_FORCE_WASI: "existing-selection" },
    }).trim(),
  );
  assert.equal(output.buildMode, "source");
  assert.equal(output.ownerAuthorization, "environment");
  assert.equal(output.ownerEnvironmentVariable, "DATA_APP_OWNER_EMAIL_SHA256");
  assert.equal(output.ownerSeeded, undefined);
  assert.equal(readFileSync(join(state.project, ".test-source-verified"), "utf8"), "yes");
  const worker = readFileSync(join(state.project, "dist/server/index.js"), "utf8");
  assert.match(worker, /authorized source customization/u);
  assert.match(worker, /initialPresentation: JSON\.parse\(/u);
  assert.match(worker, /inherited WASI mode "existing-selection"/u);
  assert.doesNotMatch(worker, /import\s+(?:dataAppHtml|seedSnapshot)\s/u);
  const assetManifest = JSON.parse(readFileSync(output.assetManifestPath));
  assert.equal(output.thinBootstrap, false);
  assert.deepEqual(readFileSync(output.offlineHtmlPath), originalHtml);
  assert.deepEqual(JSON.parse(readFileSync(output.assetFiles.snapshot)), originalSnapshot);
  for (const [kind, descriptor] of Object.entries(assetManifest.assets)) {
    const bytes = readFileSync(output.assetFiles[kind]);
    assert.equal(descriptor.sha256, digest(bytes));
    assert.equal(descriptor.bytes, bytes.length);
    assert.equal(output.deploymentAssets[kind].key, descriptor.key);
  }
  assert.equal(assetManifest.snapshotResponse.rowCount, 1);
  assert.equal(JSON.parse(readFileSync(join(state.project, ".openai/hosting.json"))).r2, "BUCKET");
  assert.deepEqual(JSON.parse(readFileSync(join(state.project, ".test-vite-node-arguments.json"))), []);
  assert.equal(JSON.parse(readFileSync(join(state.project, ".openai/hosting.json"))).artifact_metadata, undefined);
  assert.deepEqual(JSON.parse(readFileSync(join(state.project, "dist/.openai/hosting.json"))).artifact_metadata, {
    surface: "dashboard", producer: "data-analytics",
  });
});

test("source packaging attributes reviewed reports and rejects unapproved surfaces before mutation", () => {
  const state = fixture();
  writeSnapshot(state, { surface: "report", queries: { reviewed: { rows: [{ amount: 42 }] } } });
  packageDataApp(state);
  assert.equal(JSON.parse(readFileSync(join(state.project, ".openai/hosting.json"))).artifact_metadata, undefined);
  assert.deepEqual(JSON.parse(readFileSync(join(state.project, "dist/.openai/hosting.json"))).artifact_metadata, {
    surface: "report", producer: "data-analytics",
  });
  for (const surface of [null, "customer-authored-value", { customer_content: "secret" }]) {
    writeSnapshot(state, { surface, queries: { reviewed: { rows: [{ amount: 42 }] } } });
    const before = publicationBytes(state);
    assert.throws(() => packageDataApp(state), /surface must be dashboard or report/u);
    assert.deepEqual(publicationBytes(state), before);
  }
});

test("repeated embedded assets cannot bypass fragment validation or nested privacy checks", (t) => {
  const state = fixture();
  t.after(() => {
    rmSync(state.project, { recursive: true, force: true });
    rmSync(join(dirname(state.helperPath), "../../.."), { recursive: true, force: true });
  });
  const htmlPath = join(state.project, "dist/index.html");
  const reviewed = readFileSync(htmlPath, "utf8");
  const asset = `data:image/svg+xml;base64,${Buffer.from("<svg/>").toString("base64")}`;
  const repeated = `<link href="${asset}"><link href="${asset}">`;
  const clean = reviewed.replace("</head>", `${repeated}</head>`);
  writeFileSync(htmlPath, clean);
  packageDataApp(state);
  assert.equal(readFileSync(htmlPath, "utf8"), clean);

  const privateUrl = `data:text/plain;base64,${Buffer.from(state.project).toString("base64")}`;
  for (const [fragment, expected] of [
    ["%ZZ", /malformed embedded data URL/u],
    [encodeURIComponent(privateUrl), /local filesystem reference/u],
  ]) {
    writeFileSync(htmlPath, reviewed.replace("</head>", `${repeated}<link href="${asset}#${fragment}"></head>`));
    for (const path of [".test-source-verified", ".test-vite-arguments.json"]) {
      rmSync(join(state.project, path), { force: true });
    }
    const before = publicationBytes(state);
    assert.throws(() => packageDataApp(state), expected);
    assert.deepEqual(publicationBytes(state), before);
    for (const path of [".test-source-verified", ".test-vite-arguments.json", "src/.sites-worker-entry.mjs"]) {
      assert.equal(existsSync(join(state.project, path)), false);
    }
  }
});

test("source publication rejects an incompatible owner runtime and preserves customized Worker source", () => {
  const state = legacyFixture();
  const workerPath = join(state.project, "src/worker.js");
  const ownerPath = join(state.project, "src/data-app-owner.js");
  writeFileSync(ownerPath, `export const dataAppOwnerUserIdSha256 = "${digest("previous-site-owner")}";\n`);
  const source = [
    'import { dataAppOwnerUserIdSha256 } from "./data-app-owner.js";',
    'const id = "current";',
    'const store = (database) => database.prepare("presentation").bind(id, "{}", 0, new Date().toISOString()).run();',
    'export default { fetch(request) { return new Response(new URL(request.url).pathname === "/custom" ? "custom response" : "ok"); } };',
  ].join("\n");
  writeFileSync(workerPath, source);
  updateFixtureProtectedManifest(state, ["src/worker.js", "src/data-app-owner.js"]);
  const before = publicationBytes(state);
  assert.throws(() => packageDataApp(state), /requires a confirmed, scoped protected runtime upgrade/u);
  assert.deepEqual(publicationBytes(state), before);
  assert.equal(readFileSync(workerPath, "utf8"), source);
  verifyFixtureProtectedManifest(state);
});

test("source publication rejects copied factories that still authorize a packaged owner or ignore the environment", async t => {
  for (const [name, ownerSource] of [
    ["packaged owner fallback", "(environment.DATA_APP_OWNER_EMAIL_SHA256 || configuration.ownerEmailSha256)"],
    ["ignored environment", "configuration.ownerEmailSha256"],
  ]) await t.test(name, () => {
    const state = fixture();
    const runtimePath = join(state.project, "src/data-app-worker.js");
    const oldRuntime = readFileSync(runtimePath, "utf8").replace("environment.DATA_APP_OWNER_EMAIL_SHA256", ownerSource);
    writeFileSync(runtimePath, oldRuntime);
    updateFixtureProtectedManifest(state, ["src/data-app-worker.js"]);
    const before = publicationBytes(state);
    assert.throws(() => packageDataApp(state), /requires a confirmed, scoped protected runtime upgrade.*prebuilt packaging/u);
    assert.deepEqual(publicationBytes(state), before);
    assert.equal(readFileSync(runtimePath, "utf8"), oldRuntime);
    assert.equal(existsSync(join(state.project, ".test-vite-arguments.json")), false);
  });
});

test("source packaging preserves an existing inline factory's HTML and snapshot contract", async t => {
  for (const hybrid of [false, true]) await t.test(hybrid ? "asset-aware factory still requiring its seed" : "environment-aware inline factory", async () => {
  const state = fixture();
  const runtime = [
    'import { createHash } from "node:crypto";',
    "export function createDataAppWorker(configuration) {",
    hybrid ? "  Object.keys(configuration.seedSnapshot.queries);" : "  [...configuration.html.matchAll(/<head>/g)];",
    "  return { configuration, async fetch(request, environment) {",
    '    if (new URL(request.url).pathname === "/api/presentation") {',
    '      const hash = createHash("sha256").update(request.headers.get("oai-authenticated-user-email") ?? "").digest("hex");',
    '      return new Response(null, { status: hash === environment.DATA_APP_OWNER_EMAIL_SHA256 ? 400 : 403 });',
    "    }",
    "    const asset = configuration.deploymentAssets?.html;",
    "    return new Response(asset ? (await environment.BUCKET.get(asset.key)).body : configuration.html);",
    "  } };",
    "}",
  ].join("\n");
  writeFileSync(join(state.project, "src/data-app-worker.js"), runtime);
  updateFixtureProtectedManifest(state, ["src/data-app-worker.js"]);
  writeFileSync(join(state.project, "node_modules/vite/bin/vite.js"), [
    'import { mkdirSync, readFileSync, writeFileSync } from "node:fs";',
    'const entry = process.argv[process.argv.indexOf("--ssr") + 1];',
    'const output = process.argv[process.argv.indexOf("--outDir") + 1];',
    'let source = readFileSync(entry, "utf8");',
    'source = source.replace(/^import dataAppHtml.*$/m, "const dataAppHtml = " + JSON.stringify(readFileSync("dist/index.html", "utf8")) + ";");',
    'source = source.replace(/^import seedSnapshot.*$/m, "const seedSnapshot = JSON.parse(" + JSON.stringify(readFileSync("src/data.json", "utf8")) + ");");',
    'source = source.replace(/^import .*createDataAppWorker.*$/m, readFileSync("src/data-app-worker.js", "utf8"));',
    'mkdirSync(output, { recursive: true }); writeFileSync(output + "/index.js", source);',
  ].join("\n"));
  const originalHtml = readFileSync(join(state.project, "dist/index.html"), "utf8");
  const originalSnapshot = JSON.parse(readFileSync(join(state.project, "src/data.json")));
  const output = JSON.parse(packageDataApp(state));
  const { default: worker } = await import(`data:text/javascript;base64,${readFileSync(output.serverPath).toString("base64")}`);
  assert.equal(await (await worker.fetch(new Request("https://site.example/"), {})).text(), originalHtml);
  assert.deepEqual(worker.configuration.seedSnapshot, originalSnapshot);
  assert.equal(output.deploymentAssets, undefined);
  assert.equal(JSON.parse(readFileSync(join(state.project, ".openai/hosting.json"))).r2, null);
  assert.equal(readFileSync(join(state.project, "src/data-app-worker.js"), "utf8"), runtime);
  verifyFixtureProtectedManifest(state);
  });
});

test("source-mode integrity and local dependency failures leave reviewed files unchanged", async (t) => {
  for (const [name, prepare, expected] of [
    [
      "modified source Worker",
      ({ project }) => writeFileSync(join(project, "src/data-app-worker.js"), "throw new Error('Unapproved source');\n"),
      /runtime files were modified outside packaging ownership/u,
    ],
    [
      "missing local Vite",
      ({ project }) => rmSync(join(project, "node_modules"), { recursive: true, force: true }),
      /already-installed project Vite/u,
    ],
    [
      "failed source verifier",
      ({ project }) => writeFileSync(join(project, "scripts/verify-protected-runtime.mjs"), "process.exit(17);\n"),
      /source verification failed with status 17/u,
    ],
    [
      "missing Worker factory",
      ({ project }) =>
        writeFileSync(
          join(project, "src/worker.js"),
          fixtureWorkerSource.replace("createDataAppWorker({", "customWorker({"),
        ),
      /requires a confirmed, scoped protected runtime upgrade/u,
    ],
    [
      "obsolete owner seed remains in the Worker",
      ({ project }) =>
        writeFileSync(
          join(project, "src/worker.js"),
          fixtureWorkerSource.replace("});", `  ownerEmailSha256: "${digest("another-owner@example.com")}",\n});`),
        ),
      /requires a confirmed, scoped protected runtime upgrade/u,
    ],
  ]) {
    await t.test(name, () => {
      const state = fixture();
      prepare(state);
      updateFixtureProtectedManifest(state, ["scripts/verify-protected-runtime.mjs", "src/worker.js"]);
      const before = publicationBytes(state);
      assert.throws(() => packageDataApp(state, { source: true }), expected);
      assert.deepEqual(publicationBytes(state), before);
      assert.equal(existsSync(join(state.project, "src/.sites-worker-entry.mjs")), false);
      assert.equal(existsSync(join(state.project, ".test-vite-arguments.json")), false);
    });
  }
});

test("source-mode Vite must stay inside the reviewed project", () => {
  const state = fixture();
  const outside = fixture();
  const localVite = join(state.project, "node_modules/vite/bin/vite.js");
  rmSync(localVite);
  symlinkSync(join(outside.project, "node_modules/vite/bin/vite.js"), localVite);
  const before = publicationBytes(state);
  assert.throws(() => packageDataApp(state, { source: true }), /inside the Data app project/u);
  assert.deepEqual(publicationBytes(state), before);
});


test("failed Sites Worker builds restore reviewed HTML and preserve source and hosting", () => {
  const state = fixture();
  const threadId = "550e8400-e29b-41d4-a716-446655440000";
  const ownerPath = join(state.project, "src/data-app-owner.js");
  const hostingPath = join(state.project, ".openai/hosting.json");
  const htmlPath = join(state.project, "dist/index.html");
  const previousOwnerHash = createHash("sha256").update("previous-site-owner").digest("hex");
  writeFileSync(ownerPath, fixtureOwnerSource(previousOwnerHash));
  updateFixtureProtectedManifest(state, ["src/data-app-owner.js"]);
  writeFileSync(
    htmlPath,
    readFileSync(htmlPath, "utf8").replace(
      "</head>",
      `<meta name="data-app-local-thread" content="${threadId}"></head>`,
    ),
  );
  const vitePath = join(state.project, "node_modules/vite/bin/vite.js");
  writeFileSync(vitePath, `${readFileSync(vitePath, "utf8")}\nprocess.exit(23);\n`);
  const ownerBefore = readFileSync(ownerPath);
  const hostingBefore = readFileSync(hostingPath);
  const htmlBefore = readFileSync(htmlPath);

  assert.throws(
    () =>
      packageDataApp(state, {
        environment: { ...process.env, CODEX_SESSION_ID: threadId },
        source: true,
      }),
    /Sites Worker packaging failed with status 23/u,
  );
  assert.deepEqual(readFileSync(ownerPath), ownerBefore);
  assert.deepEqual(readFileSync(hostingPath), hostingBefore);
  assert.deepEqual(readFileSync(htmlPath), htmlBefore);
  assert.equal(
    existsSync(join(state.project, "dist/server/index.js")),
    false,
    "A partially emitted Worker must not survive a failed Sites build",
  );
  assert.equal(existsSync(join(state.project, "dist/.openai/hosting.json")), false);
  assert.equal(existsSync(join(state.project, "src/.sites-worker-entry.mjs")), false);
});

test("failed Sites publication restores the previously published Worker byte-for-byte", async (t) => {
  const threadId = "550e8400-e29b-41d4-a716-446655440000";
  for (const [name, injectFailure, errorPattern] of [
    [
      "failed SSR build",
      ({ project }) => {
        const vitePath = join(project, "node_modules/vite/bin/vite.js");
        writeFileSync(vitePath, `${readFileSync(vitePath, "utf8")}\nprocess.exit(23);\n`);
      },
      /Sites Worker packaging failed with status 23/u,
    ],
    [
      "post-build local-task privacy failure",
      ({ project }) => {
        const workerPath = join(project, "src/worker.js");
        writeFileSync(workerPath, `${readFileSync(workerPath, "utf8")}\n// ${threadId.toUpperCase()}\n`);
      },
      /Sites Worker must not contain a local task identifier/u,
    ],
  ]) {
    await t.test(name, () => {
      const state = fixture();
      const htmlPath = join(state.project, "dist/index.html");
      const ownerPath = join(state.project, "src/data-app-owner.js");
      const hostingPath = join(state.project, ".openai/hosting.json");
      const publishedWorkerPath = join(state.project, "dist/server/index.js");
      mkdirSync(join(state.project, "dist/server"), { recursive: true });
      const publishedWorker = Buffer.from(
        'export default { fetch() { return new Response("previous reviewed Worker"); } };\n',
      );
      writeFileSync(publishedWorkerPath, publishedWorker);
      writeFileSync(
        htmlPath,
        readFileSync(htmlPath, "utf8").replace(
          "</head>",
          `<meta name="data-app-local-thread" content="${threadId}"></head>`,
        ),
      );
      injectFailure(state);
      updateFixtureProtectedManifest(state, ["src/worker.js"]);
      const originalHtml = readFileSync(htmlPath);
      const originalOwner = readFileSync(ownerPath);
      const originalHosting = readFileSync(hostingPath);

      assert.throws(
        () =>
          packageDataApp(state, {
            environment: { ...process.env, CODEX_SESSION_ID: threadId },
            source: true,
          }),
        errorPattern,
      );
      assert.deepEqual(
        readFileSync(publishedWorkerPath),
        publishedWorker,
        "A failed republication must restore the last reviewed Sites Worker exactly",
      );
      assert.deepEqual(readFileSync(htmlPath), originalHtml);
      assert.deepEqual(readFileSync(ownerPath), originalOwner);
      assert.deepEqual(readFileSync(hostingPath), originalHosting);
      assert.equal(existsSync(join(state.project, "src/.sites-worker-entry.mjs")), false);
    });
  }
});

test("a stale source Worker entry rejects source packaging before source, hosting, or HTML mutation", () => {
  const state = fixture();
  const threadId = "550e8400-e29b-41d4-a716-446655440000";
  const ownerPath = join(state.project, "src/data-app-owner.js");
  const hostingPath = join(state.project, ".openai/hosting.json");
  const htmlPath = join(state.project, "dist/index.html");
  const temporaryWorkerPath = join(state.project, "src/.sites-worker-entry.mjs");
  const previousOwnerHash = createHash("sha256").update("previous-site-owner").digest("hex");
  writeFileSync(ownerPath, fixtureOwnerSource(previousOwnerHash));
  updateFixtureProtectedManifest(state, ["src/data-app-owner.js"]);
  writeFileSync(
    htmlPath,
    readFileSync(htmlPath, "utf8").replace(
      "</head>",
      `<meta name="data-app-local-thread" content="${threadId}"></head>`,
    ),
  );
  writeFileSync(temporaryWorkerPath, "// existing Worker entry\n");
  const ownerBefore = readFileSync(ownerPath);
  const hostingBefore = readFileSync(hostingPath);
  const htmlBefore = readFileSync(htmlPath);

  assert.throws(
    () => packageDataApp(state),
    /A stale Sites Worker entry must be removed first/u,
  );
  assert.deepEqual(
    readFileSync(ownerPath),
    ownerBefore,
    "A failed republication must preserve protected source",
  );
  assert.deepEqual(readFileSync(hostingPath), hostingBefore);
  assert.deepEqual(readFileSync(htmlPath), htmlBefore);
  assert.equal(readFileSync(temporaryWorkerPath, "utf8"), "// existing Worker entry\n");
  assert.equal(existsSync(join(state.project, "dist/server/index.js")), false);
});


describe("source integrity and independent publication rollback", () => {
  test("a dangling asset symlink cannot create an outside-project target", t => {
    const state = fixture();
    packageDataApp(state);
    const asset = join(state.project, ".data-app-assets/snapshot.json");
    const outside = `${state.project}-missing-target`;
    t.after(() => rmSync(outside, { force: true }));
    assert.equal(existsSync(outside), false);
    const previousWorker = readFileSync(join(state.project, "dist/server/index.js"));
    rmSync(asset); symlinkSync(outside, asset);
    assert.throws(() => packageDataApp(state), /asset outputs must be regular files/u);
    assert.equal(existsSync(outside), false);
    assert.deepEqual(readFileSync(join(state.project, "dist/server/index.js")), previousWorker);
  });

  test("nonregular prior asset outputs are rejected without replacing their contents", () => {
    const state = fixture();
    packageDataApp(state);
    const asset = join(state.project, ".data-app-assets/snapshot.json");
    const server = join(state.project, "dist/server/index.js");
    const previousWorker = readFileSync(server);
    rmSync(asset); mkdirSync(asset); writeFileSync(join(asset, "keep"), "unrelated content");
    assert.throws(() => packageDataApp(state), /asset outputs must be regular files/u);
    assert.equal(readFileSync(join(asset, "keep"), "utf8"), "unrelated content");
    assert.deepEqual(readFileSync(server), previousWorker);
  });

  test("repackaging does not read prior snapshot assets into memory for rollback", () => {
    const state = fixture();
    packageDataApp(state);
    const preloadPath = join(state.project, "reject-asset-read.cjs");
    writeFileSync(preloadPath, [
      'const fs = require("node:fs");',
      "const read = fs.readFileSync;",
      `const asset = ${JSON.stringify(join(state.project, ".data-app-assets/snapshot.json"))};`,
      'fs.readFileSync = function(path, ...args) { if (String(path) === asset) throw new Error("Prior asset must stay on disk"); return read.call(this, path, ...args); };',
      'require("node:module").syncBuiltinESMExports();',
    ].join("\n"));
    assert.doesNotThrow(() => packageDataApp(state, {
      environment: { ...process.env, NODE_OPTIONS: `${process.env.NODE_OPTIONS ?? ""} --require=${JSON.stringify(preloadPath)}`.trim() },
    }));
  });

  test("requires protected verifier and existing digest-helper entries before executing source tools", async (t) => {
    for (const [name, omittedPath, linkedHelper, expected] of [
      [
        "omitted regular digest helper",
        "scripts/protected-file-digest.mjs",
        false,
        /must protect its existing digest helper before any source verification/u,
      ],
      [
        "omitted symlinked digest helper",
        "scripts/protected-file-digest.mjs",
        true,
        /must protect its existing digest helper before any source verification/u,
      ],
      [
        "omitted source verifier",
        "scripts/verify-protected-runtime.mjs",
        false,
        /manifest does not protect scripts\/verify-protected-runtime\.mjs/u,
      ],
      [
        "omitted source factory",
        "src/data-app-worker.js",
        false,
        /manifest does not protect src\/data-app-worker\.js/u,
      ],
    ]) {
      await t.test(name, () => {
        const state = fixture();
        packageDefaultDataApp(state);
        const digestHelperPath = join(state.project, "scripts/protected-file-digest.mjs");
        const helperProbe = join(state.project, ".test-digest-helper-executed");
        const helperSource =
          `${readFileSync(digestHelperPath, "utf8")}\n` +
          'import { writeFileSync as recordHelperProbe } from "node:fs";\n' +
          `recordHelperProbe(${JSON.stringify(helperProbe)}, "yes");\n`;
        if (linkedHelper) {
          const target = join(mkdtempSync(join(tmpdir(), "data-app-external-digest-")), "digest.mjs");
          writeFileSync(target, helperSource);
          rmSync(digestHelperPath);
          symlinkSync(target, digestHelperPath);
        } else {
          writeFileSync(digestHelperPath, helperSource);
        }
        const verifierPath = join(state.project, "scripts/verify-protected-runtime.mjs");
        writeFileSync(
          verifierPath,
          'import { writeFileSync as recordVerifierProbe } from "node:fs";\n' +
            'recordVerifierProbe(".test-source-verifier-entered", "yes");\n' +
            readFileSync(verifierPath, "utf8"),
        );
        updateFixtureProtectedManifest(state, [
          "scripts/protected-file-digest.mjs",
          "scripts/verify-protected-runtime.mjs",
        ]);
        const manifestPath = join(state.project, "protected-runtime.json");
        const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
        delete manifest.files[omittedPath];
        writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
        writeFileSync(
          join(state.project, ".openai/hosting.json"),
          JSON.stringify({
            d1: "DB",
            r2: null,
            project_id: "appgprj_reviewed",
          }),
        );
        const before = publicationBytes(state);

        assert.throws(
          () =>
            packageDataApp(state, {
              source: true,
              migratePackagingManifest: true,
            }),
          expected,
        );
        assert.deepEqual(publicationBytes(state), before);
        for (const path of [
          ".test-digest-helper-executed",
          ".test-source-verifier-entered",
          ".test-source-verified",
          ".test-vite-arguments.json",
          "src/.sites-worker-entry.mjs",
        ]) {
          assert.equal(existsSync(join(state.project, path)), false, `${path} must not be created`);
        }
      });
    }
  });

  test("temporary legacy manifest repair restores the exact original before a verifier or build failure", async (t) => {
    for (const [name, verifierFails] of [
      ["verifier failure", true],
      ["later source build failure", false],
    ]) {
      await t.test(name, () => {
        const state = legacyFixture();
        packageDataApp(state);
        const verifierPath = join(state.project, "scripts/verify-protected-runtime.mjs");
        writeFileSync(
          verifierPath,
          `${readFileSync(verifierPath, "utf8")}\n` +
            'writeFileSync(".test-manifest-during-verification.json", JSON.stringify(manifest));\n' +
            (verifierFails ? "process.exit(17);\n" : ""),
        );
        updateFixtureProtectedManifest(state, ["scripts/verify-protected-runtime.mjs"]);
        writeFileSync(
          join(state.project, ".openai/hosting.json"),
          JSON.stringify({
            d1: "DB",
            r2: null,
            project_id: "appgprj_reviewed",
            existingDeployment: "preserved",
          }),
        );
        const manifestPath = join(state.project, "protected-runtime.json");
        const originalManifest = readFileSync(manifestPath);
        const prospectiveManifest = JSON.parse(originalManifest);
        for (const path of [".openai/hosting.json"]) {
          prospectiveManifest.files[path] = digest(readFileSync(join(state.project, path)));
        }
        rmSync(join(state.project, ".test-vite-arguments.json"), { force: true });
        if (!verifierFails) {
          const vitePath = join(state.project, "node_modules/vite/bin/vite.js");
          writeFileSync(
            vitePath,
            `${readFileSync(vitePath, "utf8")}\n` +
              'writeFileSync(join(process.cwd(), ".test-manifest-during-build.json"), ' +
              'readFileSync(join(process.cwd(), "protected-runtime.json")));\n' +
              "process.exit(23);\n",
          );
        }
        const before = publicationBytes(state);

        assert.throws(
          () =>
            packageDataApp(state, {
              migratePackagingManifest: true,
            }),
          verifierFails
            ? /Protected Data app source verification failed with status 17/u
            : /Sites Worker packaging failed with status 23/u,
        );
        assert.deepEqual(
          JSON.parse(readFileSync(join(state.project, ".test-manifest-during-verification.json"), "utf8")),
          prospectiveManifest,
          "Only the hosting hash may be repaired for verification",
        );
        if (verifierFails) {
          assert.equal(existsSync(join(state.project, ".test-vite-arguments.json")), false);
        } else {
          assert.deepEqual(
            readFileSync(join(state.project, ".test-manifest-during-build.json")),
            originalManifest,
            "The source build must observe the original manifest, not a temporary authorization repair",
          );
        }
        assert.deepEqual(publicationBytes(state), before);
        assert.equal(existsSync(join(state.project, "src/.sites-worker-entry.mjs")), false);
      });
    }
  });

  test("legacy hosting migration rejects owner-file changes before tools run", async (t) => {
    const ownerHash = digest("previous-verified-owner");
    const realExport = fixtureOwnerSource(ownerHash);
    for (const [name, source] of [
      ["line separator ends a line comment", `${realExport}// harmless\u2028globalThis.ownerSideEffect = true;\n`],
      ["paragraph separator ends a line comment", `${realExport}// harmless\u2029globalThis.ownerSideEffect = true;\n`],
      ["line-commented fake export", `// ${realExport}`],
      ["block-commented fake export", `/* ${realExport} */\n`],
      ["duplicate owner export", `${realExport}${realExport}`],
    ]) {
      await t.test(name, () => {
        const state = legacyFixture();
        packageDataApp(state);
        rmSync(join(state.project, ".test-source-verified"), { force: true });
        rmSync(join(state.project, ".test-vite-arguments.json"), { force: true });
        writeFileSync(
          join(state.project, ".openai/hosting.json"),
          JSON.stringify({
            d1: "DB",
            r2: null,
            project_id: "appgprj_reviewed",
          }),
        );
        writeFileSync(join(state.project, "src/data-app-owner.js"), source);
        const before = publicationBytes(state);

        assert.throws(
          () =>
            packageDataApp(state, {
              migratePackagingManifest: true,
            }),
          /runtime files were modified outside packaging ownership: src\/data-app-owner\.js/u,
        );
        assert.deepEqual(publicationBytes(state), before);
        for (const path of [".test-source-verified", ".test-vite-arguments.json", "src/.sites-worker-entry.mjs"]) {
          assert.equal(existsSync(join(state.project, path)), false, `${path} must not be created`);
        }
      });
    }
  });

  test("a failed final manifest write restores other artifacts even if Worker restoration also fails", async (t) => {
    for (const failServerRestore of [false, true]) {
      await t.test(failServerRestore ? "independent restoration after a second failure" : "complete rollback", () => {
        const state = fixture();
        packageDataApp(state);
        const presentationPath = join(state.project, "presentation.json");
        const presentation = JSON.parse(readFileSync(presentationPath));
        writeFileSync(presentationPath, JSON.stringify({ ...presentation, title: "Updated reviewed presentation" }));
        const htmlPath = join(state.project, "dist/index.html");
        writeFileSync(
          htmlPath,
          readFileSync(htmlPath, "utf8").replace(
            "</head>",
            '<meta name="data-app-local-thread" content="550e8400-e29b-41d4-a716-446655440000"></head>',
          ),
        );
        const before = publicationBytes(state);
        const injectionDirectory = mkdtempSync(join(tmpdir(), "data-app-write-failure-"));
        const preloadPath = join(injectionDirectory, "inject-write-failure.cjs");
        const logPath = join(injectionDirectory, "restoration-attempts.json");
        const manifestPath = join(state.project, "protected-runtime.json");
        const serverPath = join(state.project, "dist/server/index.js");
        writeFileSync(
          preloadPath,
          [
            'const fs = require("node:fs");',
            "const originalWrite = fs.writeFileSync;",
            "const originalRename = fs.renameSync;",
            `const manifestPath = ${JSON.stringify(manifestPath)};`,
            `const serverPath = ${JSON.stringify(serverPath)};`,
            `const logPath = ${JSON.stringify(logPath)};`,
            `const failServerRestore = ${JSON.stringify(failServerRestore)};`,
            "const attempts = [];",
            "let manifestFailed = false;",
            "fs.renameSync = function (from, to) {",
            "  if (manifestFailed) { attempts.push(String(to)); originalWrite.call(fs, logPath, JSON.stringify(attempts)); }",
            "  return originalRename.call(this, from, to);",
            "};",
            "fs.writeFileSync = function (file, ...args) {",
            "  const path = String(file);",
            "  if (path === manifestPath && !manifestFailed) {",
            "    manifestFailed = true;",
            '    originalWrite.call(fs, file, "partially written manifest");',
            '    throw new Error("Injected final manifest write failure");',
            "  }",
            "  if (manifestFailed) {",
            "    attempts.push(path);",
            "    originalWrite.call(fs, logPath, JSON.stringify(attempts));",
            "    if (failServerRestore && path === serverPath)",
            '      throw new Error("Injected Worker restore failure");',
            "  }",
            "  return originalWrite.call(this, file, ...args);",
            "};",
            'require("node:module").syncBuiltinESMExports();',
          ].join("\n"),
        );

        assert.throws(
          () =>
            packageDataApp(state, {
              environment: {
                ...process.env,
                NODE_OPTIONS: `${process.env.NODE_OPTIONS ?? ""} --require=${JSON.stringify(preloadPath)}`.trim(),
              },
            }),
          (error) => {
            assert.match(error.stderr, /Injected final manifest write failure/u);
            if (failServerRestore) {
              assert.match(
                error.stderr,
                /AggregateError: Sites packaging failed and some prior artifacts could not be restored/u,
              );
              assert.match(error.stderr, /Injected Worker restore failure/u);
            }
            return true;
          },
        );
        assert.deepEqual(JSON.parse(readFileSync(logPath, "utf8")), [
          join(state.project, ".data-app-assets/manifest.json"),
          join(state.project, ".data-app-assets/snapshot.json"),
          join(state.project, ".data-app-assets/html.html"),
          join(state.project, ".data-app-offline/index.html"),
          manifestPath,
          serverPath,
          join(state.project, ".openai/hosting.json"),
          join(state.project, "dist/.openai/hosting.json"),
          htmlPath,
        ]);
        const after = publicationBytes(state);
        for (const [index, path] of publicationPaths.entries()) {
          if (failServerRestore && path === "dist/server/index.js") {
            assert.notDeepEqual(
              after[index],
              before[index],
              "The injected Worker restoration failure must actually fire",
            );
          } else {
            assert.deepEqual(after[index], before[index], `${path} must be restored independently`);
          }
        }
        assert.equal(existsSync(join(state.project, "src/.sites-worker-entry.mjs")), false);
      });
    }
  });
});

SHA-256: 44f5c2b70785bf33f1c38661f23b31df5226d5d600102a9cfc72900c8486b4f8