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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ authorization and strengthens release compatibility gates.
synchronization, file, import, and provider-control writes remain fenced.
- Exact Editor qualification preserves immediate revalidation on immutable
Pages assets while separately verifying the canonical domain's bounded,
managed four-hour edge cache policy.
managed four-hour policy for edge-cache-eligible asset classes.

## 0.1.0-beta.93

Expand Down
14 changes: 8 additions & 6 deletions apps/editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,12 @@ has no target override. Operations must independently record and validate the
Pages project, `candidate-b` production branch, canonical domain, and managed
Cloudflare account before each qualified release. The immutable Pages origin
must serve the repository's asset cache policy exactly. The canonical custom
domain has a separately managed, exact edge transformation to `Cache-Control:
public, max-age=14400, must-revalidate` for `/assets/*`; the manifest remains
`no-store` and all security headers remain exact on both origins. Any other
canonical cache policy fails qualification. If Cloudflare configuration no
domain has a separately managed edge policy: cache-eligible `.css`, `.js`,
`.woff`, and `.woff2` assets must be exactly `Cache-Control: public,
max-age=14400, must-revalidate`, while `.map` and `.wasm` assets retain the
repository's exact immediate-revalidation policy. Unknown asset classes fail
closed. The manifest remains `no-store` and all security headers remain exact
on both origins. Any other canonical cache policy fails qualification. If Cloudflare configuration no
longer matches this repository contract, an operator must correct and document
that prerequisite before using the command. Do not guess a replacement
branch or attach the LAB domain from this script.
Expand Down Expand Up @@ -131,8 +133,8 @@ Cloudflare's supported root routing is explicit: local `index.html` is fetched
at `/`, while other implicit HTML routes are rejected. Pages `_headers` and
`_redirects` control files are validated locally rather than fetched. Security
headers and the manifest cache policy are checked exactly on both origins. The
immutable origin's asset cache header must match `_headers`; the canonical
origin must match the separately managed exact four-hour edge policy documented
immutable origin's asset cache header must match `_headers`; each canonical
asset must match the separately managed, extension-bounded edge class documented
above. The manifest
homepage, redirects, Connect origin, and full build revision must all match. In
the report contract, `verification.assertions.build_revision` is the boolean
Expand Down
13 changes: 10 additions & 3 deletions scripts/deploy-editor-dev.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -653,15 +653,22 @@ function assertRemoteHeaders(headers, path, policy, canonical) {
throw new Error("Remote manifest cache policy does not match _headers.");
}
if (path.startsWith("assets/")) {
const expected = canonical
? "public, max-age=14400, must-revalidate"
: policy.assets.get("cache-control");
const sourcePolicy = policy.assets.get("cache-control");
const expected = canonical ? canonicalAssetCacheControl(path, sourcePolicy) : sourcePolicy;
if (headers.get("cache-control") !== expected) {
throw new Error(`Remote ${canonical ? "canonical" : "immutable"} asset cache policy is not exact.`);
}
}
}

function canonicalAssetCacheControl(path, sourcePolicy) {
if (/\.(?:css|js|woff2?)$/u.test(path)) {
return "public, max-age=14400, must-revalidate";
}
if (/\.(?:map|wasm)$/u.test(path)) return sourcePolicy;
throw new Error(`Canonical asset cache class is unsupported for ${path}.`);
}

export function createSuccessReport({ qualification, deployment, wranglerDeployment, wranglerEvidence, verification }) {
return {
format: "mdbase-editor-lab-deployment/v1",
Expand Down
28 changes: 23 additions & 5 deletions scripts/deploy-editor-dev.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -585,18 +585,25 @@ test("exact verifier rejects missing, duplicated, omitted, reordered, or non-nor
}
});

test("exact verifier requires distinct exact immutable and canonical asset cache policies", async () => {
test("exact verifier requires distinct exact immutable and canonical asset cache classes", async () => {
for (const options of [
{ immutableAssetCacheControl: "public, max-age=14400, must-revalidate" },
{ canonicalAssetCacheControl: "public, max-age=0, must-revalidate" },
{ canonicalAssetCacheControl: "public, max-age=14400" }
{ canonicalEdgeCacheControl: "public, max-age=0, must-revalidate" },
{ canonicalEdgeCacheControl: "public, max-age=14400" },
{ canonicalRevalidatedCacheControl: "public, max-age=14400, must-revalidate" }
]) {
const fixture = await deploymentFixture(options);
await assert.rejects(verifyExactDeployment({
...fixture.options,
fetchImplementation: fixture.fetch([])
}), /asset cache policy is not exact/u);
}

const unsupported = await deploymentFixture({ extraAssetPath: "assets/data.bin" });
await assert.rejects(verifyExactDeployment({
...unsupported.options,
fetchImplementation: unsupported.fetch([])
}), /Canonical asset cache class is unsupported/u);
});

test("exact verifier rejects unconfigured implicit HTML routes", async () => {
Expand Down Expand Up @@ -743,7 +750,9 @@ async function deploymentFixture({
redirectEscape = false,
missingHeader = null,
immutableAssetCacheControl = "public, max-age=0, must-revalidate",
canonicalAssetCacheControl = "public, max-age=14400, must-revalidate"
canonicalEdgeCacheControl = "public, max-age=14400, must-revalidate",
canonicalRevalidatedCacheControl = "public, max-age=0, must-revalidate",
extraAssetPath = null
} = {}) {
const directory = await mkdtemp(resolve(tmpdir(), "mdbase-editor-dist-"));
const canonicalOrigin = "https://editor-lab.mdbase.dev";
Expand All @@ -764,10 +773,16 @@ async function deploymentFixture({
const files = new Map([
[".well-known/mdbase-app.json", Buffer.from(`${JSON.stringify(manifest)}\n`)],
["assets/main.js", Buffer.from(`globalThis.revision="${commit}";\n`)],
["assets/main.js.map", Buffer.from("{}\n")],
["assets/runtime.wasm", Buffer.from("wasm\n")],
["assets/styles.css", Buffer.from("body {}\n")],
["assets/font.woff", Buffer.from("font\n")],
["assets/font.woff2", Buffer.from("font2\n")],
["index.html", Buffer.from("<!doctype html>\n")],
["_headers", Buffer.from(headersText)],
["_redirects", Buffer.from("# no redirects\n")]
]);
if (extraAssetPath) files.set(extraAssetPath, Buffer.from("extra\n"));
for (const [path, content] of files) {
const target = resolve(directory, path);
await mkdir(resolve(target, ".."), { recursive: true });
Expand Down Expand Up @@ -799,8 +814,11 @@ async function deploymentFixture({
};
if (path === ".well-known/mdbase-app.json") globalHeaders["cache-control"] = "no-store";
if (path.startsWith("assets/")) {
const canonicalCacheControl = /\.(?:css|js|woff2?)$/u.test(path)
? canonicalEdgeCacheControl
: canonicalRevalidatedCacheControl;
globalHeaders["cache-control"] = url.origin === canonicalOrigin
? canonicalAssetCacheControl
? canonicalCacheControl
: immutableAssetCacheControl;
}
if (missingHeader) delete globalHeaders[missingHeader];
Expand Down