Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/reader/scripts/native-publisher.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,11 @@ export function assertInventoryCoverage(inventory, sdkTrust) {
if (!inventory.some((asset) => asset.sha256 === pin.sha256 && asset.bytes === pin.bytes))
throw new Error(`Missing exact SDK native/SQLite bytes for ${name}; refusing`);
}
if (sdkTrust && !sdkTrust.extension && !inventory.some((asset) => asset.path === "_worker.js"))
if (
sdkTrust &&
!sdkTrust.extension &&
!inventory.some((asset) => asset.path === "_worker.js" || asset.path === "_worker.js/index.js")
)
throw new Error("Missing precompiled Pages Worker; refusing");
const present = new Set(inventory.map((asset) => classifyAsset(asset.path)));
const missing = requiredAssetCategories.filter((category) => !present.has(category));
Expand Down
22 changes: 14 additions & 8 deletions scripts/lib/reader-hosting-policy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ export function coveredHostingControls(policy, inventory) {
!parts.some((p) =>
["_headers", "_redirects", "_routes.json", "_worker.js"].includes(p),
)) ||
["_routes.json", "_redirects", "_worker.js"].includes(item.path),
["_routes.json", "_redirects", "_worker.js"].includes(item.path) ||
item.path.startsWith("_worker.js/"),
"unknown hosting control requires reviewed source policy",
);
}
Expand All @@ -97,13 +98,18 @@ export function coveredHostingControls(policy, inventory) {
"source policy + immutable upload + scoped deployment metadata + bounded behavior probes",
});
}
const worker = inventory.find((a) => a.path === "_worker.js");
check(worker?.bytes > 0, "missing precompiled capture Worker");
covered.push({
...worker,
evidence:
"source/run-bound precompiled Worker + scoped deployment metadata + capture denial probes; NOT Worker-source GET",
});
const workers = inventory.filter(
(a) => a.path === "_worker.js" || a.path.startsWith("_worker.js/"),
);
const entry = workers.find((a) => a.path === "_worker.js" || a.path === "_worker.js/index.js");
check(entry?.bytes > 0, "missing precompiled capture Worker");
covered.push(
...workers.map((worker) => ({
...worker,
evidence:
"source/run-bound precompiled Worker + scoped deployment metadata + capture denial probes; NOT Worker-source GET",
})),
);
return covered;
}
export function safeProject(project, environment) {
Expand Down
5 changes: 4 additions & 1 deletion scripts/postqualify-native-staging.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ for (const dir of ["publish", "extension"]) {
const files = dir === "publish" ? inventory : extensionInventory;
const js = await Promise.all(
files
.filter((f) => /\.m?js$/.test(f.path) && f.path !== "_worker.js")
.filter(
(f) =>
/\.m?js$/.test(f.path) && f.path !== "_worker.js" && !f.path.startsWith("_worker.js/"),
)
.map((f) => readFile(join(root, dir, f.path), "utf8")),
);
for (const literal of [
Expand Down
7 changes: 5 additions & 2 deletions scripts/qualify-native-staging.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ try {
"functions",
"build",
join(repo, "apps/reader/functions"),
"--outfile",
"--outdir",
join(repo, "apps/reader/dist/_worker.js"),
],
tools,
Expand Down Expand Up @@ -189,7 +189,10 @@ try {
const files = dir === "publish" ? inventory : extensionInventory;
const js = await Promise.all(
files
.filter((a) => /\.m?js$/.test(a.path) && a.path !== "_worker.js")
.filter(
(a) =>
/\.m?js$/.test(a.path) && a.path !== "_worker.js" && !a.path.startsWith("_worker.js/"),
)
.map((a) => readFile(join(root, dir, a.path), "utf8")),
);
// Exact original native/SQLite members plus compiled authenticated trust
Expand Down
78 changes: 77 additions & 1 deletion scripts/tests/reader-publisher.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ async function servedFixture() {
};
const transport = async (url) => {
const path = new URL(url).pathname;
assert.notEqual(path, "/_worker.js");
assert.ok(path !== "/_worker.js" && !path.startsWith("/_worker.js/"));
if (path === "/api/capture")
return new Response(
JSON.stringify({ code: "method_not_allowed", message: "Use POST to capture a page." }),
Expand Down Expand Up @@ -464,6 +464,82 @@ async function servedFixture() {
transport,
};
}
test("Pages Functions build emits advanced-mode modules, not a multipart outfile", async () => {
const build = await readFile("scripts/qualify-native-staging.mjs", "utf8");
const flag = build.match(
/"(--out(?:file|dir))",\s*join\(repo, "apps\/reader\/dist\/_worker\.js"\)/,
)?.[1];
// Official Wrangler 4.114 semantics already exercised by Writer's synthetic
// regression: outfile is a multipart upload body, outdir emits index.js.
assert.equal(flag, "--outdir");
});
test("advanced-mode Worker retains every module/map in inventory and hosting coverage", async () => {
const f = await servedFixture();
try {
const pub = join(f.root, "publish");
await rm(join(pub, "_worker.js"));
await mkdir(join(pub, "_worker.js"));
// SYNTHETIC module/map DATA only; never compiled or executed.
for (const [name, body] of Object.entries({
"index.js": "export default {};",
"module.mjs": "export {};",
"index.js.map": "{}",
}))
await writeFile(join(pub, "_worker.js", name), body);
const inventory = await inventoryDirectory(pub);
assert.doesNotThrow(() => assertInventoryCoverage(inventory, { nativeAssets: {} }));
const v = await verifyServedAssets({
origin,
inventory,
publication: f.publication,
fetch: f.transport,
attempts: 1,
});
assert.equal(v.ok, true, JSON.stringify(v));
assert.deepEqual(v.policyEvidence.coveredControls.map((a) => a.path).sort(), [
"_redirects",
"_routes.json",
"_worker.js/index.js",
"_worker.js/index.js.map",
"_worker.js/module.mjs",
]);
assert.ok(v.verified.every((a) => !a.path.startsWith("_worker.js/")));
const args = {
target: "staging",
source: { sha: source, tree },
releaseRef: "release/native-staging",
sdkTrust: {},
inventory,
deploymentId: id,
verification: v,
};
assert.equal(createReceipt(args).status, "verified");
for (const mode of ["omitted-map", "changed-module"]) {
const broken = structuredClone(v);
if (mode === "omitted-map")
broken.policyEvidence.coveredControls = broken.policyEvidence.coveredControls.filter(
(a) => a.path !== "_worker.js/index.js.map",
);
else
broken.policyEvidence.coveredControls.find(
(a) => a.path === "_worker.js/module.mjs",
).sha256 = "0".repeat(64);
assert.equal(createReceipt({ ...args, verification: broken }).status, "failed-or-incomplete");
}
const missingEntry = inventory.filter((a) => a.path !== "_worker.js/index.js");
assert.throws(
() => assertInventoryCoverage(missingEntry, {}),
/Missing precompiled Pages Worker/,
);
const policy = await sourceHostingPolicy(process.cwd());
assert.throws(
() => coveredHostingControls(policy, missingEntry),
/missing precompiled capture Worker/,
);
} finally {
await rm(f.root, { recursive: true, force: true });
}
});
test("ALL bytes + .nojekyll GET + source controls + 9 bounded route probes can verify", async () => {
const f = await servedFixture();
try {
Expand Down