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
75 changes: 67 additions & 8 deletions .github/workflows/github-r2-fallback.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -41,25 +52,50 @@ 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
with:
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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
80 changes: 57 additions & 23 deletions docs/github-r2-fallback-canary.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,67 @@
# 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:<cdn>`;
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/<sha256>` 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 performs three clean installs while recording
the actual HTTP paths requested by the CLI:

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}`.

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.

## 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 \
Expand All @@ -38,4 +71,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.
Loading
Loading