RUNBOOK — CDN Browser Deployment (CORS / CSP / HTTPS / cache)

Status: v1 deliverable (promoted from "deferred to guide" per entity-browser-rust review absorption §2.1). The browser is the headline consumer of the static-HTTP CDN path; its deployment headers cannot be a forward-reference. Date: Author: entity-system-architecture Owner: W3 (deployment); arch authors the contract, entity-browser-rust validates in real browsers. Scope: what a CDN origin serving an entity peer's tree+content MUST send so a browser/WASM consumer can fetch+verify. Covers both deployment shapes. Native (Go/CLI) consumers can ignore everything here — CORS/CSP/cache are browser-only concerns.


§0 Why this exists

BRIDGE-HTTP §5 correctly classifies CORS / CSP / HTTPS as deployment config, not spec body. For a native consumer that's right. For a browser consumer it's the difference between working and a blank screen. The corridor-close impl-evidence (python3 -m http.server) is native and sends no CORS headers — so the cited demo cannot replicate in a browser. This runbook closes that gap with worked headers for the two deployment shapes, and the conformance vector (§4) proves the browser-success path, not just graceful failure.

The load-bearing fact: hash-verify (BRIDGE-HTTP §2) defeats forgery — a tampered body fails the hash. It does not defeat staleness — a stale-but-valid manifest still hash-verifies. So mutable-endpoint cache discipline (§3) is a separate, required concern; hash-verify is not a substitute.


§1 The two deployment shapes

ShapeCDN origin vs app originCORSCache risk
Cross-origincdn.example.com ≠ app originapplies — origin MUST send Access-Control-Allow-Originservice worker bypasses cross-origin → no staleness flag
Same-originCDN served from app originnone — no CORS preflightmutable-endpoint staleness (§3) bites; SW intercepts same-origin GET cache-first

You don't get to dodge both at once. Cross-origin needs CORS headers; same-origin needs cache discipline. The runbook covers both; a deployment picks one shape and applies that column.


§2 Cross-origin CDN — CORS + CSP

§2.1 Required response headers on the CDN origin

Every response from the CDN origin a browser peer fetches (tree leaves, content blobs, manifest):

Access-Control-Allow-Origin: https://app.example.com    ; or * for fully-public content
Access-Control-Allow-Methods: GET, HEAD
Access-Control-Allow-Headers: Range                     ; if range requests used

§2.2 App-side CSP (if the app ships one)

The entity-WASM app today ships no CSP (confirmed by entity-browser-rust against index.html), so it is permissive by default. ANY future hardening pass MUST include the CDN origin in connect-src:

Content-Security-Policy: connect-src 'self' https://cdn.example.com;

Omitting the CDN origin from connect-src blocks the fetch at the page level before CORS even applies — a silent blank-screen failure. If the app adds a CSP, this line is mandatory.

§2.3 HTTPS

Browser fetch from a https:// page to an http:// origin is mixed-content-blocked. CDN origin MUST be HTTPS when the app is served over HTTPS (the normal case). Same-origin inherits the app's scheme.


§3 Same-origin CDN — mutable-endpoint cache discipline

When the CDN is served same-origin, no CORS — but the consumer's caching layers (service workers, HTTP cache) will serve mutable endpoints stale unless told not to. This silently defeats the seq-monotonicity freshness check (STORAGE-SUBSTITUTE-HTTP §3-RES.4): the consumer never sees the newer seq because the cache answered from the old copy first.

§3.1 Endpoint mutability classification

EndpointMutabilityCache policy
{content_url_prefix}/{layout}/{hash}immutable (hash-addressed)cache-forever; Cache-Control: public, max-age=31536000, immutable
{tree_url_prefix}/{path}{tree_leaf_suffix}mutable (tree path can rebind)revalidate; Cache-Control: no-cache (or short max-age + ETag)
manifest/currentmutable (seq advances)Cache-Control: no-store
signed-pointer (current tree root)mutableCache-Control: no-store

§3.2 Required headers on the CDN origin (same-origin)

# immutable content
Cache-Control: public, max-age=31536000, immutable     ; on {content_url_prefix}/...

# mutable freshness endpoints
Cache-Control: no-store                                 ; on manifest/current + signed pointers

§3.3 Consumer-side obligation (the part hash-verify doesn't cover)

Consumers with their own caching layer (service workers especially) MUST NOT serve mutable CDN endpoints cache-first. A browser service worker that intercepts same-origin GET cache-first MUST exclude */manifest/* and signed-pointer paths from cache-first (route them network-first or pass-through). This is the consumer's discipline, not the origin's — the origin's no-store only works if the consumer's cache honors it. (entity-browser-rust review flag #1: their sw.js does its own caches.match and ignores response cache directives — so it must explicitly exclude mutable paths.)


§4 Conformance — the browser-success path (closes the "tests the wrong direction" gap)

The existing CORS test vector (BRIDGE-HTTP §8 TV-BH-CORE-7) asserts only CORS-blocked → network_error — graceful failure. There is no vector proving a browser can successfully fetch+verify. This section specifies the missing vectors. entity-browser-rust authors + runs them (only browser impl); arch specs the contract here.

§4.1 TV-CDN-BROWSER-SUCCESS

GIVEN  a CDN origin with §2.1 CORS headers (cross-origin) OR §3.2 cache headers (same-origin)
  AND  a published peer tree + content + signed manifest at seq=N
WHEN   a browser/WASM consumer fetches {content_url_prefix}/{layout}/{hash}
THEN   the fetch succeeds (not network_error)
  AND  the in-Rust entity_hash over the body equals {hash}
  AND  the entity ingests into target_namespace

This is the vector the native python3 http.server demo cannot satisfy (no CORS headers). It MUST run in a real browser against a CORS-correct origin.

§4.2 TV-CDN-BROWSER-FRESHNESS (the staleness catch)

GIVEN  a same-origin CDN + a consumer with a service worker
  AND  a published manifest at seq=N, already fetched once (now in any cache)
WHEN   the publisher updates the manifest to seq=N+1
  AND  the consumer re-resolves
THEN   the consumer observes seq=N+1 (NOT the cached seq=N)

This catches the "green for Go, silently wrong in browser" class — the service worker serving a stale-but-hash-valid manifest. Nothing in the native conformance suite catches it.

§4.3 Test-method warning (elevate to all impls)

make e2e-worker serves same-origin with no CORS and may show green while masking both the staleness flag (§3) and the CORS flag (§2). Real-browser + real-CORS verification is required — Firefox green ≠ WebKitGTK/Tauri green; same-origin-e2e green ≠ cross-origin-CORS green. Validation per entity-browser-rust's verify_user_facing_surfaces + test_each_webview_runtime discipline.


§5 Quick reference — deployment checklist

Cross-origin CDN:

Same-origin CDN:

Both:


§6 Cross-references


v1 browser-deployment runbook. Promoted to a v1 deliverable because the browser is the motivating consumer of the static-HTTP path and its headers cannot be a forward-reference. Covers cross-origin (CORS) + same-origin (cache) shapes; specifies the browser-success + freshness conformance vectors that close the "conformance tests the wrong direction" gap. entity-browser-rust validates in real browsers.