From ac96564a8a818ad4f3e8549fc4c5bc2911bc7e7d Mon Sep 17 00:00:00 2001 From: Alexander Mills Date: Thu, 10 Sep 2026 21:08:09 -0500 Subject: [PATCH 01/13] test: add deterministic Cloudflare R2 and GitHub fallback contract --- .../cloudflare_github_fallback_contract.mjs | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 scripts/cloudflare_github_fallback_contract.mjs diff --git a/scripts/cloudflare_github_fallback_contract.mjs b/scripts/cloudflare_github_fallback_contract.mjs new file mode 100644 index 00000000..fa5dfeed --- /dev/null +++ b/scripts/cloudflare_github_fallback_contract.mjs @@ -0,0 +1,180 @@ +#!/usr/bin/env node + +import assert from 'node:assert/strict'; +import crypto from 'node:crypto'; +import path from 'node:path'; +import process from 'node:process'; +import { pathToFileURL } from 'node:url'; + +function parseArgs(argv) { + const args = new Map(); + for (let index = 0; index < argv.length; index += 1) { + const key = argv[index]; + if (!key.startsWith('--')) throw new Error(`unexpected argument: ${key}`); + const value = argv[index + 1]; + if (!value || value.startsWith('--')) throw new Error(`missing value for ${key}`); + args.set(key.slice(2), value); + index += 1; + } + const infraRoot = args.get('infra-root'); + if (!infraRoot) throw new Error('--infra-root is required'); + return { infraRoot: path.resolve(infraRoot) }; +} + +function fakeR2(objects, reads) { + return { + async get(key) { + reads.push(key); + const bytes = objects.get(key); + if (!bytes) return null; + return { + body: new Uint8Array(bytes), + size: bytes.byteLength, + httpEtag: `"${crypto.createHash('sha256').update(bytes).digest('hex')}"`, + writeHttpMetadata(headers) { + headers.set('content-type', key.endsWith('.zip') ? 'application/zip' : 'application/gzip'); + }, + }; + }, + }; +} + +function githubFixture(payload, upstreamRequests) { + const releasePath = '/acme/widget/releases/download/v1.2.3/widget-1.2.3.tar.gz'; + const assetUrl = + 'https://release-assets.githubusercontent.com/acme/widget/widget-1.2.3.tar.gz?sig=test'; + return async (input, init = {}) => { + const url = typeof input === 'string' ? input : input.url; + const method = init.method || (typeof input === 'string' ? 'GET' : input.method); + const headers = new Headers( + init.headers || (typeof input === 'string' ? undefined : input.headers), + ); + upstreamRequests.push({ url, method, headers: Object.fromEntries(headers.entries()) }); + assert.equal( + headers.get('authorization'), + null, + 'Cloudflare must never send auth to public GitHub fallback', + ); + assert.equal( + headers.get('cookie'), + null, + 'Cloudflare must never send cookies to public GitHub fallback', + ); + + const parsed = new URL(url); + if (parsed.hostname === 'github.com' && parsed.pathname === releasePath) { + return new Response(null, { status: 302, headers: { location: assetUrl } }); + } + if (url === assetUrl) { + return new Response(new Uint8Array(payload), { + status: 200, + headers: { + 'content-length': String(payload.byteLength), + 'content-type': 'application/gzip', + etag: '"github-fixture"', + }, + }); + } + throw new Error(`unexpected upstream request: ${method} ${url}`); + }; +} + +async function responseBytes(response) { + return Buffer.from(await response.arrayBuffer()); +} + +async function run() { + const { infraRoot } = parseArgs(process.argv.slice(2)); + const workerPath = path.join(infraRoot, 'workers', 'cdn-proxy', 'src', 'index.js'); + const workerUrl = `${pathToFileURL(workerPath).href}?contract=${Date.now()}`; + const { default: worker } = await import(workerUrl); + assert.equal(typeof worker?.fetch, 'function', 'cdn-proxy worker must export fetch()'); + + const payload = Buffer.from('cloudflare-r2-before-github\n', 'utf8'); + const sha256 = crypto.createHash('sha256').update(payload).digest('hex'); + const contentPath = `/artifacts/${sha256}.tar.gz`; + const githubPath = '/github/acme/widget/v1.2.3/widget-1.2.3.tar.gz'; + const originalFetch = globalThis.fetch; + + try { + // Phase 1: an immutable content-addressed object in our R2 bucket must win. + const r2Reads = []; + const forbiddenUpstream = []; + globalThis.fetch = async (...args) => { + forbiddenUpstream.push(args); + throw new Error('upstream fetch must not run while the R2 object exists'); + }; + const r2Response = await worker.fetch( + new Request(`https://cdn.zpkg.net${contentPath}`), + { + ARTIFACTS: fakeR2(new Map([[contentPath.slice(1), payload]]), r2Reads), + FALLBACK_TIMEOUT_MS: '1000', + }, + ); + assert.equal(r2Response.status, 200); + assert.equal(r2Response.headers.get('x-zed-edge'), 'cdn'); + assert.equal(r2Response.headers.get('x-zed-source'), 'r2'); + assert.deepEqual(await responseBytes(r2Response), payload); + assert.deepEqual(r2Reads, [contentPath.slice(1)]); + assert.equal(forbiddenUpstream.length, 0); + + // Phase 2: after an R2 miss, the client moves to the Cloudflare /github path. + // Cloudflare performs the credential-free, allowlisted GitHub fetch and + // returns the bytes from cdn.zpkg.net rather than redirecting the client. + const missReads = []; + const upstreamRequests = []; + globalThis.fetch = githubFixture(payload, upstreamRequests); + const missResponse = await worker.fetch( + new Request(`https://cdn.zpkg.net${contentPath}`), + { + ARTIFACTS: fakeR2(new Map(), missReads), + FALLBACK_TIMEOUT_MS: '1000', + }, + ); + assert.equal(missResponse.status, 404); + assert.deepEqual(missReads, [contentPath.slice(1)]); + assert.equal(upstreamRequests.length, 0, 'a digest alone must not guess a GitHub identity'); + + const githubResponse = await worker.fetch( + new Request(`https://cdn.zpkg.net${githubPath}`), + { + ARTIFACTS: fakeR2(new Map(), missReads), + FALLBACK_TIMEOUT_MS: '1000', + }, + ); + assert.equal(githubResponse.status, 200); + assert.equal(githubResponse.headers.get('x-zed-edge'), 'cdn'); + assert.equal(githubResponse.headers.get('x-zed-source'), 'github-release'); + assert.deepEqual(await responseBytes(githubResponse), payload); + assert.equal(upstreamRequests.length, 2, 'GitHub release plus one allowlisted redirect'); + assert.equal(new URL(upstreamRequests[0].url).hostname, 'github.com'); + assert.equal(new URL(upstreamRequests[1].url).hostname, 'release-assets.githubusercontent.com'); + + // Coordinate aliases remain non-R2 read oracles: /github is a public proxy, + // while only the immutable digest path can read the private bucket directly. + assert.deepEqual(missReads, [contentPath.slice(1)]); + + console.log( + JSON.stringify( + { + ok: true, + order: ['owned-registry', 'cloudflare-r2', 'cloudflare-github-proxy'], + content_path: contentPath, + github_path: githubPath, + r2_source: r2Response.headers.get('x-zed-source'), + fallback_source: githubResponse.headers.get('x-zed-source'), + upstream_hosts: upstreamRequests.map(({ url }) => new URL(url).hostname), + }, + null, + 2, + ), + ); + } finally { + globalThis.fetch = originalFetch; + } +} + +run().catch((error) => { + console.error(error?.stack || String(error)); + process.exitCode = 1; +}); From 124e7d00dd3d5b3adab313dd2ed325e07dbaef41 Mon Sep 17 00:00:00 2001 From: Alexander Mills Date: Thu, 10 Sep 2026 21:11:03 -0500 Subject: [PATCH 02/13] test: execute exact Cloudflare fallback contract --- .github/workflows/github-r2-fallback.yml | 75 +++++++++++++++++++++--- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/.github/workflows/github-r2-fallback.yml b/.github/workflows/github-r2-fallback.yml index ca47d53c..13ebfbc0 100644 --- a/.github/workflows/github-r2-fallback.yml +++ b/.github/workflows/github-r2-fallback.yml @@ -1,10 +1,11 @@ -name: GitHub R2 fallback and typed RPC canary +name: Cloudflare R2-first and GitHub fallback canary on: pull_request: paths: - ".github/workflows/github-r2-fallback.yml" - "scripts/github_r2_fallback.py" + - "scripts/cloudflare_github_fallback_contract.mjs" - "docs/github-r2-fallback-canary.md" workflow_dispatch: inputs: @@ -16,6 +17,11 @@ on: description: Exact 40-character zed-pkg/zed-interfaces commit required: false type: string + zed_infra_ref: + description: Exact 40-character zed-pkg/zed-infra commit + required: false + type: string + default: "2150c08b94ae64bb6a30ef7d2465462b6d65cd48" workflow_call: inputs: zed_cli_ref: @@ -26,6 +32,11 @@ on: description: Exact 40-character zed-pkg/zed-interfaces commit required: true type: string + zed_infra_ref: + description: Exact 40-character zed-pkg/zed-infra commit + required: false + type: string + default: "2150c08b94ae64bb6a30ef7d2465462b6d65cd48" permissions: contents: read @@ -41,13 +52,14 @@ defaults: env: CARGO_TERM_COLOR: always RUST_BACKTRACE: "1" + DEFAULT_ZED_INFRA_REF: "2150c08b94ae64bb6a30ef7d2465462b6d65cd48" jobs: - syntax: - name: Canary script compiles + contract: + name: Exact Cloudflare Worker fallback contract if: github.event_name == 'pull_request' runs-on: ubuntu-24.04 - timeout-minutes: 5 + timeout-minutes: 10 steps: - name: Check out harness uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -55,11 +67,35 @@ jobs: fetch-depth: 1 persist-credentials: false show-progress: false - - name: py_compile - run: python3 -m py_compile scripts/github_r2_fallback.py + + - name: Check out exact zed-infra Worker + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: zed-pkg/zed-infra + ref: ${{ env.DEFAULT_ZED_INFRA_REF }} + path: product/zed-infra + fetch-depth: 1 + persist-credentials: false + show-progress: false + + - name: Install Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v6.0.0 + with: + node-version: "22" + + - name: Verify immutable Worker identity + run: test "$(git -C product/zed-infra rev-parse HEAD)" = "$DEFAULT_ZED_INFRA_REF" + + - name: Compile harnesses and execute the real Worker + run: | + set -euo pipefail + python3 -m py_compile scripts/github_r2_fallback.py + node --check scripts/cloudflare_github_fallback_contract.mjs + node scripts/cloudflare_github_fallback_contract.mjs \ + --infra-root product/zed-infra canary: - name: Registry-down CDN install + name: Registry-down R2-first install with Cloudflare GitHub proxy if: github.event_name != 'pull_request' runs-on: ubuntu-24.04 timeout-minutes: 45 @@ -76,10 +112,12 @@ jobs: env: CLI_IN: ${{ inputs.zed_cli_ref || '' }} INTERFACES_IN: ${{ inputs.zed_interfaces_ref || '' }} + INFRA_IN: ${{ inputs.zed_infra_ref || '' }} run: | set -euo pipefail cli="${CLI_IN:-}" interfaces="${INTERFACES_IN:-}" + infra="${INFRA_IN:-$DEFAULT_ZED_INFRA_REF}" if [[ -z "$cli" ]]; then echo "zed_cli_ref is required (workflow_call or workflow_dispatch)" >&2 exit 1 @@ -90,8 +128,10 @@ jobs: fi [[ "$cli" =~ ^[0-9a-f]{40}$ ]] [[ "$interfaces" =~ ^[0-9a-f]{40}$ ]] + [[ "$infra" =~ ^[0-9a-f]{40}$ ]] printf 'cli=%s\n' "$cli" >> "$GITHUB_OUTPUT" printf 'interfaces=%s\n' "$interfaces" >> "$GITHUB_OUTPUT" + printf 'infra=%s\n' "$infra" >> "$GITHUB_OUTPUT" - name: Check out exact zed-cli candidate uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -113,6 +153,16 @@ jobs: persist-credentials: false show-progress: false + - name: Check out exact zed-infra Worker + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: zed-pkg/zed-infra + ref: ${{ steps.pins.outputs.infra }} + path: product/zed-infra + fetch-depth: 1 + persist-credentials: false + show-progress: false + - name: Check out zed-clients contract uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -136,6 +186,7 @@ jobs: set -euo pipefail test "$(git -C product/zed-cli rev-parse HEAD)" = "${{ steps.pins.outputs.cli }}" test "$(git -C product/zed-interfaces rev-parse HEAD)" = "${{ steps.pins.outputs.interfaces }}" + test "$(git -C product/zed-infra rev-parse HEAD)" = "${{ steps.pins.outputs.infra }}" - name: Install Rust uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 @@ -148,10 +199,18 @@ jobs: with: node-version: "22" + - name: Cloudflare R2 and GitHub proxy contract + run: | + set -euo pipefail + node --check scripts/cloudflare_github_fallback_contract.mjs + node scripts/cloudflare_github_fallback_contract.mjs \ + --infra-root product/zed-infra + - name: Contract tests (interfaces, clients, lib-core) run: | set -euo pipefail cargo test --manifest-path product/zed-interfaces/src/rust/Cargo.toml --lib zed_api + cargo test --manifest-path product/zed-interfaces/src/rust/Cargo.toml --lib locators_try_registry_then_r2_then_github cargo test --manifest-path product/zed-lib-core/src/rust-orm/Cargo.toml \ --features read-write \ publication::tests::canonical_artifact_key_is_content_addressed @@ -165,7 +224,7 @@ jobs: install -Dm755 product/zed-cli/target/release/zed "$RUNNER_TEMP/zed-bin/zed" "$RUNNER_TEMP/zed-bin/zed" --version - - name: Run registry-down CDN canary + - name: Run registry-down R2-first and GitHub-proxy canary run: | set -euo pipefail python3 -m py_compile scripts/github_r2_fallback.py From 4a07d4c301d79ff18d6057da69b3db9e3e7c4ad2 Mon Sep 17 00:00:00 2001 From: Alexander Mills Date: Thu, 10 Sep 2026 21:12:41 -0500 Subject: [PATCH 03/13] ci: stage two-phase Cloudflare fallback canary --- .../agent-apply-cloudflare-canary.yml | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 .github/workflows/agent-apply-cloudflare-canary.yml diff --git a/.github/workflows/agent-apply-cloudflare-canary.yml b/.github/workflows/agent-apply-cloudflare-canary.yml new file mode 100644 index 00000000..2aeb050d --- /dev/null +++ b/.github/workflows/agent-apply-cloudflare-canary.yml @@ -0,0 +1,219 @@ +name: Apply two-phase Cloudflare fallback canary + +on: + push: + branches: + - test/cloudflare-r2-github-fallback-20260910 + +permissions: + contents: write + +jobs: + apply: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Check out canary branch + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: test/cloudflare-r2-github-fallback-20260910 + fetch-depth: 1 + persist-credentials: true + show-progress: false + + - name: Apply request-order assertions + run: | + set -euo pipefail + python3 <<'PY' + from pathlib import Path + + path = Path('scripts/github_r2_fallback.py') + text = path.read_text(encoding='utf-8') + + start = text.index('def start_cdn(') + end = text.index('\n\n\nclass VersionRpcHandler', start) + new_server = '''def start_cdn( + root: Path, port: int + ) -> tuple[http.server.ThreadingHTTPServer, list[tuple[str, str]]]: + requests: list[tuple[str, str]] = [] + + class BoundHandler(http.server.SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=str(root), **kwargs) + + def _record(self) -> None: + requests.append((self.command, self.path.partition("?")[0])) + + def do_GET(self) -> None: # noqa: N802 — http.server hook + self._record() + super().do_GET() + + def do_HEAD(self) -> None: # noqa: N802 — http.server hook + self._record() + super().do_HEAD() + + def log_message(self, format: str, *args) -> None: # noqa: A003 + print(f"cdn: {format % args}", flush=True) + + server = http.server.ThreadingHTTPServer(("127.0.0.1", port), BoundHandler) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + return server, requests + + + def unique_request_paths(requests: Sequence[tuple[str, str]]) -> list[str]: + paths: list[str] = [] + for _, path in requests: + if not paths or paths[-1] != path: + paths.append(path) + return paths + ''' + text = text[:start] + new_server + text[end:] + + old_home = ''' fallback_home = root / "fallback-home" + cdn_root = root / "cdn" + ''' + new_home = ''' fallback_home = root / "fallback-home" + github_fallback_home = root / "github-fallback-home" + cdn_root = root / "cdn" + ''' + if text.count(old_home) != 1: + raise SystemExit(f'expected one fallback-home block, found {text.count(old_home)}') + text = text.replace(old_home, new_home, 1) + + old_start = ''' cdn = start_cdn(cdn_root, cdn_port) + ''' + new_start = ''' cdn, cdn_requests = start_cdn(cdn_root, cdn_port) + ''' + if text.count(old_start) != 1: + raise SystemExit(f'expected one CDN start, found {text.count(old_start)}') + text = text.replace(old_start, new_start, 1) + + block_start = text.index(' fallback_registry = f"http://127.0.0.1:{dead_port}"') + block_end = text.index(' print(\n', block_start) + replacement = ''' fallback_registry = f"http://127.0.0.1:{dead_port}" + cdn_base = f"http://127.0.0.1:{cdn_port}" + content_path = f"/artifacts/{sha256}.tar.gz" + github_path = f"/github/{ORG}/{NAME}/{TAG}/{FILENAME}" + + def install_from_fallback(home: Path) -> None: + run( + [ + zed, + "--registry", + fallback_registry, + "--home", + home, + "--r2-public-base", + cdn_base, + "install", + "--frozen", + "--install-mode", + "copy", + "--adapter", + "none", + "--allow-ecosystem-mismatch", + ], + cwd=consumer, + extra_env={ + "ZED_PKG_SOURCE_FALLBACK": "true", + "ZED_PKG_SOURCE_FALLBACK_ALLOW_LOOPBACK": "true", + "ZED_PKG_R2_PUBLIC_BASE": cdn_base, + }, + ) + + # Phase 1: the immutable content-addressed R2 object exists. It must + # win without touching the Cloudflare /github proxy path. + cdn_requests.clear() + install_from_fallback(fallback_home) + restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" + if restored.read_text(encoding="utf-8") != "cdn-fallback\\n": + raise AssertionError("R2 fallback did not restore the packed payload") + r2_request_order = unique_request_paths(cdn_requests) + if not r2_request_order or r2_request_order[0] != content_path: + raise AssertionError( + f"content-addressed R2 must be first, saw {r2_request_order!r}" + ) + if github_path in r2_request_order: + raise AssertionError( + "Cloudflare GitHub proxy was reached even though the R2 object existed" + ) + + digest = hashlib.sha256(artifact.read_bytes()).hexdigest() + if digest != sha256: + raise AssertionError("packed artifact digest drifted") + + # Phase 2: remove the R2 digest object. The next locator must stay on + # the Cloudflare custom domain and use its /github proxy route. + shutil.rmtree(consumer / "zed_modules") + content_copy.unlink() + cdn_requests.clear() + install_from_fallback(github_fallback_home) + restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" + if restored.read_text(encoding="utf-8") != "cdn-fallback\\n": + raise AssertionError("Cloudflare GitHub proxy did not restore the payload") + github_request_order = unique_request_paths(cdn_requests) + expected_prefix = [content_path, github_path] + if github_request_order[:2] != expected_prefix: + raise AssertionError( + "R2 miss must fall through to the Cloudflare /github proxy; " + f"expected {expected_prefix!r}, saw {github_request_order!r}" + ) + ''' + text = text[:block_start] + replacement + text[block_end:] + + marker = ''' "sha256": sha256, + "cdn_keys": [ + ''' + enriched = ''' "sha256": sha256, + "r2_request_order": r2_request_order, + "github_proxy_request_order": github_request_order, + "cdn_keys": [ + ''' + if text.count(marker) != 1: + raise SystemExit(f'expected one result marker, found {text.count(marker)}') + text = text.replace(marker, enriched, 1) + + old_summary = '''"""Black-box canary: registry down, public CDN still installs. + + Publishes a disposable package through a file:// registry, copies the packed + tarball onto the guessable R2 keys (`packages/…` and `github/…`), takes the + HTTP registry down, and proves `zed install --frozen` fetches from a loopback + stand-in for `https://cdn.zpkg.net`. Also round-trips the oresoftware/api-docs + JSON call/receipt frame for `get_version` over TCP NDJSON. + """ + ''' + new_summary = '''"""Black-box canary: owned registry, R2, then Cloudflare GitHub proxy. + + Publishes a disposable package through a file:// registry, takes the HTTP + registry down, and proves `zed install --frozen` first fetches the immutable + content-addressed R2 object. A second clean install removes that object and + proves the next request stays on the Cloudflare custom domain at `/github/*`. + The exact Worker proxy behavior is independently exercised by the companion + Node contract. The canary also round-trips the typed get_version TCP receipt. + """ + ''' + if text.count(old_summary) != 1: + raise SystemExit(f'expected one module docstring, found {text.count(old_summary)}') + text = text.replace(old_summary, new_summary, 1) + + path.write_text(text, encoding='utf-8') + PY + + - name: Verify Python harness + run: | + set -euo pipefail + python3 -m py_compile scripts/github_r2_fallback.py + node --check scripts/cloudflare_github_fallback_contract.mjs + git diff --check + + - name: Commit verified canary + run: | + set -euo pipefail + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add scripts/github_r2_fallback.py + git diff --cached --quiet && exit 0 + git commit -m 'test: prove R2 precedes the Cloudflare GitHub proxy' + git push origin HEAD:test/cloudflare-r2-github-fallback-20260910 From 0057fd57ff49b1b0d9e1a9fb83da4b044c4ae1e5 Mon Sep 17 00:00:00 2001 From: Alexander Mills Date: Thu, 10 Sep 2026 21:15:13 -0500 Subject: [PATCH 04/13] ci: fix two-phase canary patch marker --- .../workflows/agent-apply-cloudflare-canary.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/agent-apply-cloudflare-canary.yml b/.github/workflows/agent-apply-cloudflare-canary.yml index 2aeb050d..7279ac68 100644 --- a/.github/workflows/agent-apply-cloudflare-canary.yml +++ b/.github/workflows/agent-apply-cloudflare-canary.yml @@ -163,14 +163,12 @@ jobs: ''' text = text[:block_start] + replacement + text[block_end:] - marker = ''' "sha256": sha256, - "cdn_keys": [ - ''' - enriched = ''' "sha256": sha256, - "r2_request_order": r2_request_order, - "github_proxy_request_order": github_request_order, - "cdn_keys": [ - ''' + marker = '"sha256": sha256,\n' + enriched = ( + '"sha256": sha256,\n' + + ' ' * 20 + '"r2_request_order": r2_request_order,\n' + + ' ' * 20 + '"github_proxy_request_order": github_request_order,\n' + ) if text.count(marker) != 1: raise SystemExit(f'expected one result marker, found {text.count(marker)}') text = text.replace(marker, enriched, 1) From 388adcbf37eee01c907762355b761dd1abd529ee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2026 02:15:22 +0000 Subject: [PATCH 05/13] test: prove R2 precedes the Cloudflare GitHub proxy --- scripts/github_r2_fallback.py | 134 +++++++++++++++++++++++++--------- 1 file changed, 99 insertions(+), 35 deletions(-) diff --git a/scripts/github_r2_fallback.py b/scripts/github_r2_fallback.py index 80419787..db6b17c4 100644 --- a/scripts/github_r2_fallback.py +++ b/scripts/github_r2_fallback.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 -"""Black-box canary: registry down, public CDN still installs. - -Publishes a disposable package through a file:// registry, copies the packed -tarball onto the guessable R2 keys (`packages/…` and `github/…`), takes the -HTTP registry down, and proves `zed install --frozen` fetches from a loopback -stand-in for `https://cdn.zpkg.net`. Also round-trips the oresoftware/api-docs -JSON call/receipt frame for `get_version` over TCP NDJSON. +"""Black-box canary: owned registry, R2, then Cloudflare GitHub proxy. + +Publishes a disposable package through a file:// registry, takes the HTTP +registry down, and proves `zed install --frozen` first fetches the immutable +content-addressed R2 object. A second clean install removes that object and +proves the next request stays on the Cloudflare custom domain at `/github/*`. +The exact Worker proxy behavior is independently exercised by the companion +Node contract. The canary also round-trips the typed get_version TCP receipt. """ from __future__ import annotations @@ -149,18 +150,42 @@ def write_consumer(directory: Path) -> None: ) -def start_cdn(root: Path, port: int) -> http.server.ThreadingHTTPServer: +def start_cdn( + root: Path, port: int +) -> tuple[http.server.ThreadingHTTPServer, list[tuple[str, str]]]: + requests: list[tuple[str, str]] = [] + class BoundHandler(http.server.SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): super().__init__(*args, directory=str(root), **kwargs) + def _record(self) -> None: + requests.append((self.command, self.path.partition("?")[0])) + + def do_GET(self) -> None: # noqa: N802 — http.server hook + self._record() + super().do_GET() + + def do_HEAD(self) -> None: # noqa: N802 — http.server hook + self._record() + super().do_HEAD() + def log_message(self, format: str, *args) -> None: # noqa: A003 print(f"cdn: {format % args}", flush=True) server = http.server.ThreadingHTTPServer(("127.0.0.1", port), BoundHandler) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() - return server + return server, requests + + +def unique_request_paths(requests: Sequence[tuple[str, str]]) -> list[str]: + paths: list[str] = [] + for _, path in requests: + if not paths or paths[-1] != path: + paths.append(path) + return paths + class VersionRpcHandler(socketserver.StreamRequestHandler): @@ -335,6 +360,7 @@ def main() -> int: publish_home = root / "publish-home" install_home = root / "install-home" fallback_home = root / "fallback-home" + github_fallback_home = root / "github-fallback-home" cdn_root = root / "cdn" cdn_root.mkdir() @@ -385,7 +411,7 @@ def zed_cmd(*command: str | Path, cwd: Path, home: Path, extra_env: Mapping[str, dead_port = free_port() cdn_port = free_port() tcp_port = free_port() - cdn = start_cdn(cdn_root, cdn_port) + cdn, cdn_requests = start_cdn(cdn_root, cdn_port) rpc = start_tcp_rpc(tcp_port) try: receipt = tcp_get_version(tcp_port) @@ -396,36 +422,72 @@ def zed_cmd(*command: str | Path, cwd: Path, home: Path, extra_env: Mapping[str, fallback_registry = f"http://127.0.0.1:{dead_port}" cdn_base = f"http://127.0.0.1:{cdn_port}" - run( - [ - zed, - "--registry", - fallback_registry, - "--home", - fallback_home, - "--r2-public-base", - cdn_base, - "install", - "--frozen", - "--install-mode", - "copy", - "--adapter", - "none", - "--allow-ecosystem-mismatch", - ], - cwd=consumer, - extra_env={ - "ZED_PKG_SOURCE_FALLBACK": "true", - "ZED_PKG_SOURCE_FALLBACK_ALLOW_LOOPBACK": "true", - "ZED_PKG_R2_PUBLIC_BASE": cdn_base, - }, - ) + content_path = f"/artifacts/{sha256}.tar.gz" + github_path = f"/github/{ORG}/{NAME}/{TAG}/{FILENAME}" + + def install_from_fallback(home: Path) -> None: + run( + [ + zed, + "--registry", + fallback_registry, + "--home", + home, + "--r2-public-base", + cdn_base, + "install", + "--frozen", + "--install-mode", + "copy", + "--adapter", + "none", + "--allow-ecosystem-mismatch", + ], + cwd=consumer, + extra_env={ + "ZED_PKG_SOURCE_FALLBACK": "true", + "ZED_PKG_SOURCE_FALLBACK_ALLOW_LOOPBACK": "true", + "ZED_PKG_R2_PUBLIC_BASE": cdn_base, + }, + ) + + # Phase 1: the immutable content-addressed R2 object exists. It must + # win without touching the Cloudflare /github proxy path. + cdn_requests.clear() + install_from_fallback(fallback_home) restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" if restored.read_text(encoding="utf-8") != "cdn-fallback\n": - raise AssertionError("CDN fallback did not restore the packed payload") + raise AssertionError("R2 fallback did not restore the packed payload") + r2_request_order = unique_request_paths(cdn_requests) + if not r2_request_order or r2_request_order[0] != content_path: + raise AssertionError( + f"content-addressed R2 must be first, saw {r2_request_order!r}" + ) + if github_path in r2_request_order: + raise AssertionError( + "Cloudflare GitHub proxy was reached even though the R2 object existed" + ) + digest = hashlib.sha256(artifact.read_bytes()).hexdigest() if digest != sha256: raise AssertionError("packed artifact digest drifted") + + # Phase 2: remove the R2 digest object. The next locator must stay on + # the Cloudflare custom domain and use its /github proxy route. + shutil.rmtree(consumer / "zed_modules") + content_copy.unlink() + cdn_requests.clear() + install_from_fallback(github_fallback_home) + restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" + if restored.read_text(encoding="utf-8") != "cdn-fallback\n": + raise AssertionError("Cloudflare GitHub proxy did not restore the payload") + github_request_order = unique_request_paths(cdn_requests) + expected_prefix = [content_path, github_path] + if github_request_order[:2] != expected_prefix: + raise AssertionError( + "R2 miss must fall through to the Cloudflare /github proxy; " + f"expected {expected_prefix!r}, saw {github_request_order!r}" + ) print( json.dumps( { @@ -434,6 +496,8 @@ def zed_cmd(*command: str | Path, cwd: Path, home: Path, extra_env: Mapping[str, "cdn": cdn_base, "tcp_rpc": f"127.0.0.1:{tcp_port}", "sha256": sha256, + "r2_request_order": r2_request_order, + "github_proxy_request_order": github_request_order, "cdn_keys": [ f"packages/{ORG}/{NAME}/{VERSION}/{FILENAME}", f"github/{ORG}/{NAME}/{TAG}/{FILENAME}", From 99918b0bdf7ed9e911a966893cd38cc29feac702 Mon Sep 17 00:00:00 2001 From: Alexander Mills Date: Thu, 10 Sep 2026 21:16:18 -0500 Subject: [PATCH 06/13] docs: define owned-origin R2 and GitHub fallback order --- docs/github-r2-fallback-canary.md | 76 +++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/docs/github-r2-fallback-canary.md b/docs/github-r2-fallback-canary.md index b0000334..90036150 100644 --- a/docs/github-r2-fallback-canary.md +++ b/docs/github-r2-fallback-canary.md @@ -1,34 +1,63 @@ -# GitHub / R2 registry-fallback canary +# Cloudflare R2-first and GitHub-fallback canary -This repository proves that `zed install --frozen` still materializes a -package when the HTTP registry is down, as long as the packed tarball is at a -guessable public CDN key (`packages/{org}/{name}/{version}/…` or -`github/{owner}/{repo}/{tag}/…`). That is the `cdn.zpkg.net` contract. +This repository certifies the public artifact retrieval order used by Zed: -The canary is credential-free and uses only loopback HTTP: +1. the owned registry and API servers are authoritative; +2. immutable packed artifacts are read from the owned Cloudflare R2 bucket through + `cdn.zpkg.net/artifacts/{sha256}.{ext}`; +3. only after that R2 object is unavailable may the Cloudflare CDN Worker proxy an + anonymously readable GitHub Release through `cdn.zpkg.net/github/{owner}/{repo}/{tag}/{filename}`; +4. direct GitHub Release, GitHub Packages, and source-archive locators remain later + client fallbacks. -1. publish a disposable package to a `file://` registry; -2. copy the tarball onto the guessable CDN layout; -3. install once so a lock exists, then uninstall and drop the store; -4. point `--registry` at a closed `127.0.0.1` port; -5. set `ZED_PKG_SOURCE_FALLBACK_ALLOW_LOOPBACK=true` (production loopback stays - hermetic) and `--r2-public-base http://127.0.0.1:`; -6. `zed install --frozen` must restore the packed payload from the CDN mock. +The `/github/*` route is a Cloudflare proxy contract, not a public read oracle for +private R2 coordinate aliases. Only content-addressed `artifacts/` objects +are read directly from the private bucket. This keeps public exposure bounded by an +immutable digest while still allowing Cloudflare to serve GitHub bytes when the +owned registry and R2 copy are unavailable. -It also round-trips the `get_version` JSON call/receipt frame over TCP NDJSON, -using the same key as `zed-interfaces/route-maps/zed-api.route-map.json` -(generated by `github.com/oresoftware/api-docs`). zed-cli itself still speaks -HTTP; the TCP check is the typed RPC wire, not a second CLI transport. +## Exact Worker contract -When the TypeScript copies are passed in, the generated `zed-clients` routes -file must match `zed-interfaces/generated/typescript/zed_api.ts` (banner -comments excluded). +`scripts/cloudflare_github_fallback_contract.mjs` imports the real +`zed-pkg/zed-infra` CDN Worker at an immutable commit and executes it with fake R2 +and network adapters. It proves: -## Local run +- an R2 hit returns `x-zed-source: r2`, exact bytes, and performs zero upstream + requests; +- an R2 miss on a digest path does not guess a GitHub identity; +- the subsequent `/github/*` request follows only the allowlisted GitHub release + redirect chain and returns `x-zed-source: github-release`; +- caller authorization and cookie headers are never forwarded to GitHub; +- the Worker streams the result from the Cloudflare hostname instead of redirecting + the Zed client to GitHub. -From a sibling checkout of `zed-pkg`: +## Black-box CLI canary + +`scripts/github_r2_fallback.py` publishes a disposable package through a `file://` +registry, creates a frozen lock, and then points the CLI at a closed loopback +registry port. The canary performs two clean installs against a loopback stand-in +for `cdn.zpkg.net`: + +1. with `artifacts/{sha256}.tar.gz` present, the first request must be the + content-addressed object and the `/github/*` route must not be touched; +2. after removing the digest object and using a fresh Zed home, the observed request + prefix must be `artifacts/{sha256}.tar.gz` followed by + `github/{owner}/{repo}/{tag}/{filename}`. + +Both installs verify the packed payload and digest. The canary also round-trips the +`get_version` JSON call/receipt frame over TCP NDJSON using the same key as +`zed-interfaces/route-maps/zed-api.route-map.json`, and compares generated +TypeScript route contracts between `zed-interfaces` and `zed-clients` when those +files are supplied. + +## Local checks + +From a sibling checkout containing `zed-pkg/zed-infra` and `zed-pkg/zed-cli`: ```bash +node scripts/cloudflare_github_fallback_contract.mjs \ + --infra-root ../zed-pkg/zed-infra + cargo build --release --manifest-path ../zed-pkg/zed-cli/Cargo.toml --bin zed python3 scripts/github_r2_fallback.py \ --zed ../zed-pkg/zed-cli/target/release/zed \ @@ -38,4 +67,5 @@ python3 scripts/github_r2_fallback.py \ --clients-generated-ts ../zed-pkg/zed-clients/clients/typescript/src/generated/zed-api.routes.ts ``` -The work root must not already exist. +The work root must not already exist. Production does not enable the loopback-only +fallback escape hatch used by the hermetic CLI test. From 5fad35e342461ddc223b07e6f4b33809ea2dbcba Mon Sep 17 00:00:00 2001 From: Alexander Mills Date: Thu, 10 Sep 2026 21:16:26 -0500 Subject: [PATCH 07/13] ci: remove temporary canary patch workflow --- .../agent-apply-cloudflare-canary.yml | 217 ------------------ 1 file changed, 217 deletions(-) delete mode 100644 .github/workflows/agent-apply-cloudflare-canary.yml diff --git a/.github/workflows/agent-apply-cloudflare-canary.yml b/.github/workflows/agent-apply-cloudflare-canary.yml deleted file mode 100644 index 7279ac68..00000000 --- a/.github/workflows/agent-apply-cloudflare-canary.yml +++ /dev/null @@ -1,217 +0,0 @@ -name: Apply two-phase Cloudflare fallback canary - -on: - push: - branches: - - test/cloudflare-r2-github-fallback-20260910 - -permissions: - contents: write - -jobs: - apply: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-24.04 - timeout-minutes: 10 - steps: - - name: Check out canary branch - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: test/cloudflare-r2-github-fallback-20260910 - fetch-depth: 1 - persist-credentials: true - show-progress: false - - - name: Apply request-order assertions - run: | - set -euo pipefail - python3 <<'PY' - from pathlib import Path - - path = Path('scripts/github_r2_fallback.py') - text = path.read_text(encoding='utf-8') - - start = text.index('def start_cdn(') - end = text.index('\n\n\nclass VersionRpcHandler', start) - new_server = '''def start_cdn( - root: Path, port: int - ) -> tuple[http.server.ThreadingHTTPServer, list[tuple[str, str]]]: - requests: list[tuple[str, str]] = [] - - class BoundHandler(http.server.SimpleHTTPRequestHandler): - def __init__(self, *args, **kwargs): - super().__init__(*args, directory=str(root), **kwargs) - - def _record(self) -> None: - requests.append((self.command, self.path.partition("?")[0])) - - def do_GET(self) -> None: # noqa: N802 — http.server hook - self._record() - super().do_GET() - - def do_HEAD(self) -> None: # noqa: N802 — http.server hook - self._record() - super().do_HEAD() - - def log_message(self, format: str, *args) -> None: # noqa: A003 - print(f"cdn: {format % args}", flush=True) - - server = http.server.ThreadingHTTPServer(("127.0.0.1", port), BoundHandler) - thread = threading.Thread(target=server.serve_forever, daemon=True) - thread.start() - return server, requests - - - def unique_request_paths(requests: Sequence[tuple[str, str]]) -> list[str]: - paths: list[str] = [] - for _, path in requests: - if not paths or paths[-1] != path: - paths.append(path) - return paths - ''' - text = text[:start] + new_server + text[end:] - - old_home = ''' fallback_home = root / "fallback-home" - cdn_root = root / "cdn" - ''' - new_home = ''' fallback_home = root / "fallback-home" - github_fallback_home = root / "github-fallback-home" - cdn_root = root / "cdn" - ''' - if text.count(old_home) != 1: - raise SystemExit(f'expected one fallback-home block, found {text.count(old_home)}') - text = text.replace(old_home, new_home, 1) - - old_start = ''' cdn = start_cdn(cdn_root, cdn_port) - ''' - new_start = ''' cdn, cdn_requests = start_cdn(cdn_root, cdn_port) - ''' - if text.count(old_start) != 1: - raise SystemExit(f'expected one CDN start, found {text.count(old_start)}') - text = text.replace(old_start, new_start, 1) - - block_start = text.index(' fallback_registry = f"http://127.0.0.1:{dead_port}"') - block_end = text.index(' print(\n', block_start) - replacement = ''' fallback_registry = f"http://127.0.0.1:{dead_port}" - cdn_base = f"http://127.0.0.1:{cdn_port}" - content_path = f"/artifacts/{sha256}.tar.gz" - github_path = f"/github/{ORG}/{NAME}/{TAG}/{FILENAME}" - - def install_from_fallback(home: Path) -> None: - run( - [ - zed, - "--registry", - fallback_registry, - "--home", - home, - "--r2-public-base", - cdn_base, - "install", - "--frozen", - "--install-mode", - "copy", - "--adapter", - "none", - "--allow-ecosystem-mismatch", - ], - cwd=consumer, - extra_env={ - "ZED_PKG_SOURCE_FALLBACK": "true", - "ZED_PKG_SOURCE_FALLBACK_ALLOW_LOOPBACK": "true", - "ZED_PKG_R2_PUBLIC_BASE": cdn_base, - }, - ) - - # Phase 1: the immutable content-addressed R2 object exists. It must - # win without touching the Cloudflare /github proxy path. - cdn_requests.clear() - install_from_fallback(fallback_home) - restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" - if restored.read_text(encoding="utf-8") != "cdn-fallback\\n": - raise AssertionError("R2 fallback did not restore the packed payload") - r2_request_order = unique_request_paths(cdn_requests) - if not r2_request_order or r2_request_order[0] != content_path: - raise AssertionError( - f"content-addressed R2 must be first, saw {r2_request_order!r}" - ) - if github_path in r2_request_order: - raise AssertionError( - "Cloudflare GitHub proxy was reached even though the R2 object existed" - ) - - digest = hashlib.sha256(artifact.read_bytes()).hexdigest() - if digest != sha256: - raise AssertionError("packed artifact digest drifted") - - # Phase 2: remove the R2 digest object. The next locator must stay on - # the Cloudflare custom domain and use its /github proxy route. - shutil.rmtree(consumer / "zed_modules") - content_copy.unlink() - cdn_requests.clear() - install_from_fallback(github_fallback_home) - restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" - if restored.read_text(encoding="utf-8") != "cdn-fallback\\n": - raise AssertionError("Cloudflare GitHub proxy did not restore the payload") - github_request_order = unique_request_paths(cdn_requests) - expected_prefix = [content_path, github_path] - if github_request_order[:2] != expected_prefix: - raise AssertionError( - "R2 miss must fall through to the Cloudflare /github proxy; " - f"expected {expected_prefix!r}, saw {github_request_order!r}" - ) - ''' - text = text[:block_start] + replacement + text[block_end:] - - marker = '"sha256": sha256,\n' - enriched = ( - '"sha256": sha256,\n' - + ' ' * 20 + '"r2_request_order": r2_request_order,\n' - + ' ' * 20 + '"github_proxy_request_order": github_request_order,\n' - ) - if text.count(marker) != 1: - raise SystemExit(f'expected one result marker, found {text.count(marker)}') - text = text.replace(marker, enriched, 1) - - old_summary = '''"""Black-box canary: registry down, public CDN still installs. - - Publishes a disposable package through a file:// registry, copies the packed - tarball onto the guessable R2 keys (`packages/…` and `github/…`), takes the - HTTP registry down, and proves `zed install --frozen` fetches from a loopback - stand-in for `https://cdn.zpkg.net`. Also round-trips the oresoftware/api-docs - JSON call/receipt frame for `get_version` over TCP NDJSON. - """ - ''' - new_summary = '''"""Black-box canary: owned registry, R2, then Cloudflare GitHub proxy. - - Publishes a disposable package through a file:// registry, takes the HTTP - registry down, and proves `zed install --frozen` first fetches the immutable - content-addressed R2 object. A second clean install removes that object and - proves the next request stays on the Cloudflare custom domain at `/github/*`. - The exact Worker proxy behavior is independently exercised by the companion - Node contract. The canary also round-trips the typed get_version TCP receipt. - """ - ''' - if text.count(old_summary) != 1: - raise SystemExit(f'expected one module docstring, found {text.count(old_summary)}') - text = text.replace(old_summary, new_summary, 1) - - path.write_text(text, encoding='utf-8') - PY - - - name: Verify Python harness - run: | - set -euo pipefail - python3 -m py_compile scripts/github_r2_fallback.py - node --check scripts/cloudflare_github_fallback_contract.mjs - git diff --check - - - name: Commit verified canary - run: | - set -euo pipefail - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add scripts/github_r2_fallback.py - git diff --cached --quiet && exit 0 - git commit -m 'test: prove R2 precedes the Cloudflare GitHub proxy' - git push origin HEAD:test/cloudflare-r2-github-fallback-20260910 From 8161aefb8fb987517ec5139fc79e0b443c8399c5 Mon Sep 17 00:00:00 2001 From: Alexander Mills Date: Thu, 10 Sep 2026 21:27:11 -0500 Subject: [PATCH 08/13] ci: stage owned-registry precedence phase --- .../agent-add-owned-registry-phase.yml | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 .github/workflows/agent-add-owned-registry-phase.yml diff --git a/.github/workflows/agent-add-owned-registry-phase.yml b/.github/workflows/agent-add-owned-registry-phase.yml new file mode 100644 index 00000000..35752f03 --- /dev/null +++ b/.github/workflows/agent-add-owned-registry-phase.yml @@ -0,0 +1,215 @@ +name: Add owned-registry precedence phase + +on: + push: + branches: + - test/cloudflare-r2-github-fallback-20260910 + +permissions: + contents: write + +jobs: + apply: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Check out canary branch + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: test/cloudflare-r2-github-fallback-20260910 + fetch-depth: 1 + persist-credentials: true + show-progress: false + + - name: Add healthy owned-registry phase + run: | + set -euo pipefail + python3 <<'PY' + from pathlib import Path + + path = Path('scripts/github_r2_fallback.py') + text = path.read_text(encoding='utf-8') + + old_paths = ''' fallback_home = root / "fallback-home" + github_fallback_home = root / "github-fallback-home" + cdn_root = root / "cdn" + ''' + new_paths = ''' owned_registry_home = root / "owned-registry-home" + fallback_home = root / "fallback-home" + github_fallback_home = root / "github-fallback-home" + owned_registry_root = root / "owned-registry-http" + cdn_root = root / "cdn" + ''' + if text.count(old_paths) != 1: + raise SystemExit(f'expected one home-path block, found {text.count(old_paths)}') + text = text.replace(old_paths, new_paths, 1) + + copy_marker = ''' shutil.copy2(artifact, content_copy) + ''' + copy_replacement = ''' shutil.copy2(artifact, content_copy) + owned_artifact = owned_registry_root / "v1" / "artifacts" / sha256 + owned_artifact.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(artifact, owned_artifact) + ''' + if text.count(copy_marker) != 1: + raise SystemExit(f'expected one content copy marker, found {text.count(copy_marker)}') + text = text.replace(copy_marker, copy_replacement, 1) + + block_start = text.index(' dead_port = free_port()') + block_end = text.index(' print(\n', block_start) + replacement = ''' dead_port = free_port() + owned_registry_port = free_port() + cdn_port = free_port() + tcp_port = free_port() + owned_registry_server, owned_registry_requests = start_cdn( + owned_registry_root, owned_registry_port + ) + cdn, cdn_requests = start_cdn(cdn_root, cdn_port) + rpc = start_tcp_rpc(tcp_port) + try: + receipt = tcp_get_version(tcp_port) + if receipt["body"]["version"] != VERSION: + raise AssertionError(receipt) + if receipt["transport"] != "tcp": + raise AssertionError(receipt) + + owned_registry_base = f"http://127.0.0.1:{owned_registry_port}" + fallback_registry = f"http://127.0.0.1:{dead_port}" + cdn_base = f"http://127.0.0.1:{cdn_port}" + registry_artifact_path = f"/v1/artifacts/{sha256}" + content_path = f"/artifacts/{sha256}.tar.gz" + github_path = f"/github/{ORG}/{NAME}/{TAG}/{FILENAME}" + + def install_with_registry(home: Path, registry_base: str) -> None: + run( + [ + zed, + "--registry", + registry_base, + "--home", + home, + "--r2-public-base", + cdn_base, + "install", + "--frozen", + "--install-mode", + "copy", + "--adapter", + "none", + "--allow-ecosystem-mismatch", + ], + cwd=consumer, + extra_env={ + "ZED_PKG_SOURCE_FALLBACK": "true", + "ZED_PKG_SOURCE_FALLBACK_ALLOW_LOOPBACK": "true", + "ZED_PKG_R2_PUBLIC_BASE": cdn_base, + }, + ) + + # Phase 0: the owned registry is healthy. It must serve the exact + # digest endpoint without any request reaching the CDN or GitHub. + owned_registry_requests.clear() + cdn_requests.clear() + install_with_registry(owned_registry_home, owned_registry_base) + restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" + if restored.read_text(encoding="utf-8") != "cdn-fallback\\n": + raise AssertionError("owned registry did not restore the packed payload") + owned_registry_request_order = unique_request_paths(owned_registry_requests) + if ( + not owned_registry_request_order + or owned_registry_request_order[0] != registry_artifact_path + ): + raise AssertionError( + "owned registry digest endpoint must be first; " + f"saw {owned_registry_request_order!r}" + ) + if cdn_requests: + raise AssertionError( + f"CDN was touched while owned registry was healthy: {cdn_requests!r}" + ) + shutil.rmtree(consumer / "zed_modules") + + # Phase 1: the immutable content-addressed R2 object exists. It must + # win without touching the Cloudflare /github proxy path. + cdn_requests.clear() + install_with_registry(fallback_home, fallback_registry) + restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" + if restored.read_text(encoding="utf-8") != "cdn-fallback\\n": + raise AssertionError("R2 fallback did not restore the packed payload") + r2_request_order = unique_request_paths(cdn_requests) + if not r2_request_order or r2_request_order[0] != content_path: + raise AssertionError( + f"content-addressed R2 must be first, saw {r2_request_order!r}" + ) + if github_path in r2_request_order: + raise AssertionError( + "Cloudflare GitHub proxy was reached even though the R2 object existed" + ) + + digest = hashlib.sha256(artifact.read_bytes()).hexdigest() + if digest != sha256: + raise AssertionError("packed artifact digest drifted") + + # Phase 2: remove the R2 digest object. The next locator must stay on + # the Cloudflare custom domain and use its /github proxy route. + shutil.rmtree(consumer / "zed_modules") + content_copy.unlink() + cdn_requests.clear() + install_with_registry(github_fallback_home, fallback_registry) + restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" + if restored.read_text(encoding="utf-8") != "cdn-fallback\\n": + raise AssertionError("Cloudflare GitHub proxy did not restore the payload") + github_request_order = unique_request_paths(cdn_requests) + expected_prefix = [content_path, github_path] + if github_request_order[:2] != expected_prefix: + raise AssertionError( + "R2 miss must fall through to the Cloudflare /github proxy; " + f"expected {expected_prefix!r}, saw {github_request_order!r}" + ) + ''' + text = text[:block_start] + replacement + text[block_end:] + + marker = ''' "registry": fallback_registry, + "cdn": cdn_base, + ''' + enriched = ''' "owned_registry": owned_registry_base, + "fallback_registry": fallback_registry, + "cdn": cdn_base, + "owned_registry_request_order": owned_registry_request_order, + ''' + if text.count(marker) != 1: + raise SystemExit(f'expected one result marker, found {text.count(marker)}') + text = text.replace(marker, enriched, 1) + + old_finally = ''' finally: + cdn.shutdown() + rpc.shutdown() + ''' + new_finally = ''' finally: + owned_registry_server.shutdown() + cdn.shutdown() + rpc.shutdown() + ''' + if text.count(old_finally) != 1: + raise SystemExit(f'expected one finally block, found {text.count(old_finally)}') + text = text.replace(old_finally, new_finally, 1) + + path.write_text(text, encoding='utf-8') + PY + + - name: Verify harness syntax + run: | + set -euo pipefail + python3 -m py_compile scripts/github_r2_fallback.py + git diff --check + + - name: Commit verified phase + run: | + set -euo pipefail + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add scripts/github_r2_fallback.py + git diff --cached --quiet && exit 0 + git commit -m 'test: prove owned registry precedes Cloudflare fallbacks' + git push origin HEAD:test/cloudflare-r2-github-fallback-20260910 From d99bf3d3c660049cdfc32270b3f40bf95bc65d6c Mon Sep 17 00:00:00 2001 From: Alexander Mills Date: Thu, 10 Sep 2026 21:28:50 -0500 Subject: [PATCH 09/13] ci: correct owned-registry result marker --- .../agent-add-owned-registry-phase.yml | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/.github/workflows/agent-add-owned-registry-phase.yml b/.github/workflows/agent-add-owned-registry-phase.yml index 35752f03..9e82cc9d 100644 --- a/.github/workflows/agent-add-owned-registry-phase.yml +++ b/.github/workflows/agent-add-owned-registry-phase.yml @@ -107,8 +107,8 @@ jobs: }, ) - # Phase 0: the owned registry is healthy. It must serve the exact - # digest endpoint without any request reaching the CDN or GitHub. + # Phase 0: a healthy owned registry must serve the digest and + # prevent any request from reaching the CDN or GitHub. owned_registry_requests.clear() cdn_requests.clear() install_with_registry(owned_registry_home, owned_registry_base) @@ -130,8 +130,7 @@ jobs: ) shutil.rmtree(consumer / "zed_modules") - # Phase 1: the immutable content-addressed R2 object exists. It must - # win without touching the Cloudflare /github proxy path. + # Phase 1: after registry failure, immutable R2 must win. cdn_requests.clear() install_with_registry(fallback_home, fallback_registry) restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" @@ -151,8 +150,7 @@ jobs: if digest != sha256: raise AssertionError("packed artifact digest drifted") - # Phase 2: remove the R2 digest object. The next locator must stay on - # the Cloudflare custom domain and use its /github proxy route. + # Phase 2: after removing the R2 digest, Cloudflare may proxy GitHub. shutil.rmtree(consumer / "zed_modules") content_copy.unlink() cdn_requests.clear() @@ -170,17 +168,17 @@ jobs: ''' text = text[:block_start] + replacement + text[block_end:] - marker = ''' "registry": fallback_registry, - "cdn": cdn_base, - ''' - enriched = ''' "owned_registry": owned_registry_base, - "fallback_registry": fallback_registry, - "cdn": cdn_base, - "owned_registry_request_order": owned_registry_request_order, - ''' - if text.count(marker) != 1: - raise SystemExit(f'expected one result marker, found {text.count(marker)}') - text = text.replace(marker, enriched, 1) + needle = '"registry": fallback_registry,' + marker_start = text.index(needle) + line_start = text.rfind('\n', 0, marker_start) + 1 + line_end = text.index('\n', marker_start) + indent = text[line_start:marker_start] + enriched = ( + f'{indent}"owned_registry": owned_registry_base,\n' + f'{indent}"fallback_registry": fallback_registry,\n' + f'{indent}"owned_registry_request_order": owned_registry_request_order,' + ) + text = text[:line_start] + enriched + text[line_end:] old_finally = ''' finally: cdn.shutdown() From 155da8a915214c4ba6148750df054c05714f5771 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2026 02:29:18 +0000 Subject: [PATCH 10/13] test: prove owned registry precedes Cloudflare fallbacks --- scripts/github_r2_fallback.py | 53 +++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/scripts/github_r2_fallback.py b/scripts/github_r2_fallback.py index db6b17c4..3a9a98e4 100644 --- a/scripts/github_r2_fallback.py +++ b/scripts/github_r2_fallback.py @@ -359,8 +359,10 @@ def main() -> int: consumer = root / "consumer" publish_home = root / "publish-home" install_home = root / "install-home" + owned_registry_home = root / "owned-registry-home" fallback_home = root / "fallback-home" github_fallback_home = root / "github-fallback-home" + owned_registry_root = root / "owned-registry-http" cdn_root = root / "cdn" cdn_root.mkdir() @@ -385,6 +387,9 @@ def zed_cmd(*command: str | Path, cwd: Path, home: Path, extra_env: Mapping[str, content_copy = cdn_root / "artifacts" / f"{sha256}.tar.gz" content_copy.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(artifact, content_copy) + owned_artifact = owned_registry_root / "v1" / "artifacts" / sha256 + owned_artifact.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(artifact, owned_artifact) zed_cmd( "install", @@ -409,8 +414,12 @@ def zed_cmd(*command: str | Path, cwd: Path, home: Path, extra_env: Mapping[str, raise AssertionError("uninstall left materialization behind") dead_port = free_port() + owned_registry_port = free_port() cdn_port = free_port() tcp_port = free_port() + owned_registry_server, owned_registry_requests = start_cdn( + owned_registry_root, owned_registry_port + ) cdn, cdn_requests = start_cdn(cdn_root, cdn_port) rpc = start_tcp_rpc(tcp_port) try: @@ -420,17 +429,19 @@ def zed_cmd(*command: str | Path, cwd: Path, home: Path, extra_env: Mapping[str, if receipt["transport"] != "tcp": raise AssertionError(receipt) + owned_registry_base = f"http://127.0.0.1:{owned_registry_port}" fallback_registry = f"http://127.0.0.1:{dead_port}" cdn_base = f"http://127.0.0.1:{cdn_port}" + registry_artifact_path = f"/v1/artifacts/{sha256}" content_path = f"/artifacts/{sha256}.tar.gz" github_path = f"/github/{ORG}/{NAME}/{TAG}/{FILENAME}" - def install_from_fallback(home: Path) -> None: + def install_with_registry(home: Path, registry_base: str) -> None: run( [ zed, "--registry", - fallback_registry, + registry_base, "--home", home, "--r2-public-base", @@ -451,10 +462,32 @@ def install_from_fallback(home: Path) -> None: }, ) - # Phase 1: the immutable content-addressed R2 object exists. It must - # win without touching the Cloudflare /github proxy path. + # Phase 0: a healthy owned registry must serve the digest and + # prevent any request from reaching the CDN or GitHub. + owned_registry_requests.clear() + cdn_requests.clear() + install_with_registry(owned_registry_home, owned_registry_base) + restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" + if restored.read_text(encoding="utf-8") != "cdn-fallback\n": + raise AssertionError("owned registry did not restore the packed payload") + owned_registry_request_order = unique_request_paths(owned_registry_requests) + if ( + not owned_registry_request_order + or owned_registry_request_order[0] != registry_artifact_path + ): + raise AssertionError( + "owned registry digest endpoint must be first; " + f"saw {owned_registry_request_order!r}" + ) + if cdn_requests: + raise AssertionError( + f"CDN was touched while owned registry was healthy: {cdn_requests!r}" + ) + shutil.rmtree(consumer / "zed_modules") + + # Phase 1: after registry failure, immutable R2 must win. cdn_requests.clear() - install_from_fallback(fallback_home) + install_with_registry(fallback_home, fallback_registry) restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" if restored.read_text(encoding="utf-8") != "cdn-fallback\n": raise AssertionError("R2 fallback did not restore the packed payload") @@ -472,12 +505,11 @@ def install_from_fallback(home: Path) -> None: if digest != sha256: raise AssertionError("packed artifact digest drifted") - # Phase 2: remove the R2 digest object. The next locator must stay on - # the Cloudflare custom domain and use its /github proxy route. + # Phase 2: after removing the R2 digest, Cloudflare may proxy GitHub. shutil.rmtree(consumer / "zed_modules") content_copy.unlink() cdn_requests.clear() - install_from_fallback(github_fallback_home) + install_with_registry(github_fallback_home, fallback_registry) restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" if restored.read_text(encoding="utf-8") != "cdn-fallback\n": raise AssertionError("Cloudflare GitHub proxy did not restore the payload") @@ -492,7 +524,9 @@ def install_from_fallback(home: Path) -> None: json.dumps( { "ok": True, - "registry": fallback_registry, + "owned_registry": owned_registry_base, + "fallback_registry": fallback_registry, + "owned_registry_request_order": owned_registry_request_order, "cdn": cdn_base, "tcp_rpc": f"127.0.0.1:{tcp_port}", "sha256": sha256, @@ -509,6 +543,7 @@ def install_from_fallback(home: Path) -> None: flush=True, ) finally: + owned_registry_server.shutdown() cdn.shutdown() rpc.shutdown() return 0 From 72097823868b1d2916257907ef9a3d13ceeaf573 Mon Sep 17 00:00:00 2001 From: Alexander Mills Date: Thu, 10 Sep 2026 21:31:35 -0500 Subject: [PATCH 11/13] ci: remove temporary owned-registry patch workflow --- .../agent-add-owned-registry-phase.yml | 213 ------------------ 1 file changed, 213 deletions(-) delete mode 100644 .github/workflows/agent-add-owned-registry-phase.yml diff --git a/.github/workflows/agent-add-owned-registry-phase.yml b/.github/workflows/agent-add-owned-registry-phase.yml deleted file mode 100644 index 9e82cc9d..00000000 --- a/.github/workflows/agent-add-owned-registry-phase.yml +++ /dev/null @@ -1,213 +0,0 @@ -name: Add owned-registry precedence phase - -on: - push: - branches: - - test/cloudflare-r2-github-fallback-20260910 - -permissions: - contents: write - -jobs: - apply: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-24.04 - timeout-minutes: 10 - steps: - - name: Check out canary branch - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: test/cloudflare-r2-github-fallback-20260910 - fetch-depth: 1 - persist-credentials: true - show-progress: false - - - name: Add healthy owned-registry phase - run: | - set -euo pipefail - python3 <<'PY' - from pathlib import Path - - path = Path('scripts/github_r2_fallback.py') - text = path.read_text(encoding='utf-8') - - old_paths = ''' fallback_home = root / "fallback-home" - github_fallback_home = root / "github-fallback-home" - cdn_root = root / "cdn" - ''' - new_paths = ''' owned_registry_home = root / "owned-registry-home" - fallback_home = root / "fallback-home" - github_fallback_home = root / "github-fallback-home" - owned_registry_root = root / "owned-registry-http" - cdn_root = root / "cdn" - ''' - if text.count(old_paths) != 1: - raise SystemExit(f'expected one home-path block, found {text.count(old_paths)}') - text = text.replace(old_paths, new_paths, 1) - - copy_marker = ''' shutil.copy2(artifact, content_copy) - ''' - copy_replacement = ''' shutil.copy2(artifact, content_copy) - owned_artifact = owned_registry_root / "v1" / "artifacts" / sha256 - owned_artifact.parent.mkdir(parents=True, exist_ok=True) - shutil.copy2(artifact, owned_artifact) - ''' - if text.count(copy_marker) != 1: - raise SystemExit(f'expected one content copy marker, found {text.count(copy_marker)}') - text = text.replace(copy_marker, copy_replacement, 1) - - block_start = text.index(' dead_port = free_port()') - block_end = text.index(' print(\n', block_start) - replacement = ''' dead_port = free_port() - owned_registry_port = free_port() - cdn_port = free_port() - tcp_port = free_port() - owned_registry_server, owned_registry_requests = start_cdn( - owned_registry_root, owned_registry_port - ) - cdn, cdn_requests = start_cdn(cdn_root, cdn_port) - rpc = start_tcp_rpc(tcp_port) - try: - receipt = tcp_get_version(tcp_port) - if receipt["body"]["version"] != VERSION: - raise AssertionError(receipt) - if receipt["transport"] != "tcp": - raise AssertionError(receipt) - - owned_registry_base = f"http://127.0.0.1:{owned_registry_port}" - fallback_registry = f"http://127.0.0.1:{dead_port}" - cdn_base = f"http://127.0.0.1:{cdn_port}" - registry_artifact_path = f"/v1/artifacts/{sha256}" - content_path = f"/artifacts/{sha256}.tar.gz" - github_path = f"/github/{ORG}/{NAME}/{TAG}/{FILENAME}" - - def install_with_registry(home: Path, registry_base: str) -> None: - run( - [ - zed, - "--registry", - registry_base, - "--home", - home, - "--r2-public-base", - cdn_base, - "install", - "--frozen", - "--install-mode", - "copy", - "--adapter", - "none", - "--allow-ecosystem-mismatch", - ], - cwd=consumer, - extra_env={ - "ZED_PKG_SOURCE_FALLBACK": "true", - "ZED_PKG_SOURCE_FALLBACK_ALLOW_LOOPBACK": "true", - "ZED_PKG_R2_PUBLIC_BASE": cdn_base, - }, - ) - - # Phase 0: a healthy owned registry must serve the digest and - # prevent any request from reaching the CDN or GitHub. - owned_registry_requests.clear() - cdn_requests.clear() - install_with_registry(owned_registry_home, owned_registry_base) - restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" - if restored.read_text(encoding="utf-8") != "cdn-fallback\\n": - raise AssertionError("owned registry did not restore the packed payload") - owned_registry_request_order = unique_request_paths(owned_registry_requests) - if ( - not owned_registry_request_order - or owned_registry_request_order[0] != registry_artifact_path - ): - raise AssertionError( - "owned registry digest endpoint must be first; " - f"saw {owned_registry_request_order!r}" - ) - if cdn_requests: - raise AssertionError( - f"CDN was touched while owned registry was healthy: {cdn_requests!r}" - ) - shutil.rmtree(consumer / "zed_modules") - - # Phase 1: after registry failure, immutable R2 must win. - cdn_requests.clear() - install_with_registry(fallback_home, fallback_registry) - restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" - if restored.read_text(encoding="utf-8") != "cdn-fallback\\n": - raise AssertionError("R2 fallback did not restore the packed payload") - r2_request_order = unique_request_paths(cdn_requests) - if not r2_request_order or r2_request_order[0] != content_path: - raise AssertionError( - f"content-addressed R2 must be first, saw {r2_request_order!r}" - ) - if github_path in r2_request_order: - raise AssertionError( - "Cloudflare GitHub proxy was reached even though the R2 object existed" - ) - - digest = hashlib.sha256(artifact.read_bytes()).hexdigest() - if digest != sha256: - raise AssertionError("packed artifact digest drifted") - - # Phase 2: after removing the R2 digest, Cloudflare may proxy GitHub. - shutil.rmtree(consumer / "zed_modules") - content_copy.unlink() - cdn_requests.clear() - install_with_registry(github_fallback_home, fallback_registry) - restored = consumer / "zed_modules" / ORG / NAME / "src" / "payload.txt" - if restored.read_text(encoding="utf-8") != "cdn-fallback\\n": - raise AssertionError("Cloudflare GitHub proxy did not restore the payload") - github_request_order = unique_request_paths(cdn_requests) - expected_prefix = [content_path, github_path] - if github_request_order[:2] != expected_prefix: - raise AssertionError( - "R2 miss must fall through to the Cloudflare /github proxy; " - f"expected {expected_prefix!r}, saw {github_request_order!r}" - ) - ''' - text = text[:block_start] + replacement + text[block_end:] - - needle = '"registry": fallback_registry,' - marker_start = text.index(needle) - line_start = text.rfind('\n', 0, marker_start) + 1 - line_end = text.index('\n', marker_start) - indent = text[line_start:marker_start] - enriched = ( - f'{indent}"owned_registry": owned_registry_base,\n' - f'{indent}"fallback_registry": fallback_registry,\n' - f'{indent}"owned_registry_request_order": owned_registry_request_order,' - ) - text = text[:line_start] + enriched + text[line_end:] - - old_finally = ''' finally: - cdn.shutdown() - rpc.shutdown() - ''' - new_finally = ''' finally: - owned_registry_server.shutdown() - cdn.shutdown() - rpc.shutdown() - ''' - if text.count(old_finally) != 1: - raise SystemExit(f'expected one finally block, found {text.count(old_finally)}') - text = text.replace(old_finally, new_finally, 1) - - path.write_text(text, encoding='utf-8') - PY - - - name: Verify harness syntax - run: | - set -euo pipefail - python3 -m py_compile scripts/github_r2_fallback.py - git diff --check - - - name: Commit verified phase - run: | - set -euo pipefail - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add scripts/github_r2_fallback.py - git diff --cached --quiet && exit 0 - git commit -m 'test: prove owned registry precedes Cloudflare fallbacks' - git push origin HEAD:test/cloudflare-r2-github-fallback-20260910 From 1f847ea2d299f8560f9bdd01d3223df112492fb6 Mon Sep 17 00:00:00 2001 From: Alexander Mills Date: Thu, 10 Sep 2026 21:32:21 -0500 Subject: [PATCH 12/13] docs: cover owned registry in the full fallback canary --- docs/github-r2-fallback-canary.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/github-r2-fallback-canary.md b/docs/github-r2-fallback-canary.md index 90036150..eba4aaa2 100644 --- a/docs/github-r2-fallback-canary.md +++ b/docs/github-r2-fallback-canary.md @@ -34,18 +34,22 @@ and network adapters. It proves: ## Black-box CLI canary `scripts/github_r2_fallback.py` publishes a disposable package through a `file://` -registry, creates a frozen lock, and then points the CLI at a closed loopback -registry port. The canary performs two clean installs against a loopback stand-in -for `cdn.zpkg.net`: +registry, creates a frozen lock, and performs three clean installs while recording +the actual HTTP paths requested by the CLI: -1. with `artifacts/{sha256}.tar.gz` present, the first request must be the - content-addressed object and the `/github/*` route must not be touched; -2. after removing the digest object and using a fresh Zed home, the observed request - prefix must be `artifacts/{sha256}.tar.gz` followed by +1. a healthy loopback stand-in for the owned registry serves + `/v1/artifacts/{sha256}`; the canary requires that path first and requires zero + CDN requests; +2. with the registry unavailable and `artifacts/{sha256}.tar.gz` present in the CDN + stand-in, the content-addressed R2 path must be first and `/github/*` must not be + touched; +3. after removing the digest object and using a fresh Zed home, the observed CDN + request prefix must be `artifacts/{sha256}.tar.gz` followed by `github/{owner}/{repo}/{tag}/{filename}`. -Both installs verify the packed payload and digest. The canary also round-trips the -`get_version` JSON call/receipt frame over TCP NDJSON using the same key as +All three installs verify the packed payload, and the artifact digest is checked +against the frozen metadata. The canary also round-trips the `get_version` JSON +call/receipt frame over TCP NDJSON using the same key as `zed-interfaces/route-maps/zed-api.route-map.json`, and compares generated TypeScript route contracts between `zed-interfaces` and `zed-clients` when those files are supplied. From fdc925ccd8ea92a5320f81a001c8413e54aa6f31 Mon Sep 17 00:00:00 2001 From: Alexander Mills Date: Thu, 10 Sep 2026 21:34:54 -0500 Subject: [PATCH 13/13] test: attest Cloudflare route and R2 binding config --- .../cloudflare_github_fallback_contract.mjs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/scripts/cloudflare_github_fallback_contract.mjs b/scripts/cloudflare_github_fallback_contract.mjs index fa5dfeed..f4018311 100644 --- a/scripts/cloudflare_github_fallback_contract.mjs +++ b/scripts/cloudflare_github_fallback_contract.mjs @@ -2,6 +2,7 @@ import assert from 'node:assert/strict'; import crypto from 'node:crypto'; +import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; import { pathToFileURL } from 'node:url'; @@ -21,6 +22,30 @@ function parseArgs(argv) { return { infraRoot: path.resolve(infraRoot) }; } +function attestCloudflareConfig(infraRoot) { + const wranglerPath = path.join(infraRoot, 'workers', 'cdn-proxy', 'wrangler.toml'); + const wrangler = fs.readFileSync(wranglerPath, 'utf8'); + assert.match(wrangler, /^name = "zpkg-cdn"$/m); + assert.match(wrangler, /^main = "src\/index\.js"$/m); + assert.match(wrangler, /^REGISTRY_URL = "https:\/\/registry\.zpkg\.net"$/m); + assert.match( + wrangler, + /\[\[r2_buckets\]\][\s\S]*?binding = "ARTIFACTS"[\s\S]*?bucket_name = "zed-pkg-artifacts"/, + 'production CDN Worker must bind the owned ARTIFACTS R2 bucket', + ); + assert.match( + wrangler, + /\[\[routes\]\][\s\S]*?pattern = "cdn\.zpkg\.net\/\*"[\s\S]*?zone_name = "zpkg\.net"/, + 'production CDN Worker must intercept the cdn.zpkg.net zone route', + ); + return { + worker_name: 'zpkg-cdn', + route: 'cdn.zpkg.net/*', + r2_binding: 'ARTIFACTS', + r2_bucket: 'zed-pkg-artifacts', + }; +} + function fakeR2(objects, reads) { return { async get(key) { @@ -85,6 +110,7 @@ async function responseBytes(response) { async function run() { const { infraRoot } = parseArgs(process.argv.slice(2)); + const cloudflareConfig = attestCloudflareConfig(infraRoot); const workerPath = path.join(infraRoot, 'workers', 'cdn-proxy', 'src', 'index.js'); const workerUrl = `${pathToFileURL(workerPath).href}?contract=${Date.now()}`; const { default: worker } = await import(workerUrl); @@ -159,6 +185,7 @@ async function run() { { ok: true, order: ['owned-registry', 'cloudflare-r2', 'cloudflare-github-proxy'], + cloudflare_config: cloudflareConfig, content_path: contentPath, github_path: githubPath, r2_source: r2Response.headers.get('x-zed-source'),