Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
66d6802
DEN-3829: prove installs survive total Zed control-plane outage
ORESoftware Sep 14, 2026
3971481
DEN-3829: document GitHub-only outage recovery contract
ORESoftware Sep 14, 2026
2540b30
DEN-3829: stage GitHub-first fallback ordering patch
ORESoftware Sep 14, 2026
2099b1c
DEN-3829: prefer GitHub before Zed-owned mirrors during fallback
Sep 14, 2026
777be15
DEN-3829: remove temporary fallback patch workflow
ORESoftware Sep 14, 2026
4d00aef
DEN-3829: prove GitHub fallback never touches R2 first
ORESoftware Sep 14, 2026
9605251
DEN-3829: stage GitHub smart-HTTP recovery patch
ORESoftware Sep 14, 2026
4e902b6
DEN-3829: survive GitHub REST exhaustion via smart HTTP
Sep 14, 2026
7032ceb
DEN-3829: remove temporary patch workflow
ORESoftware Sep 14, 2026
f286e57
DEN-3829: scope outage assertions to positive recovery
ORESoftware Sep 14, 2026
f3123eb
chore: apply rustfmt to TOML contract tests
ORESoftware Sep 14, 2026
8f81445
ci: apply and verify GHCR auth fix
ORESoftware Sep 14, 2026
3ccdc99
ci: salvage proven GHCR registry auth into outage branch
ORESoftware Sep 14, 2026
fc48d78
fix: use proven GHCR registry token exchange
github-actions[bot] Sep 14, 2026
27c7876
ci: remove completed one-shot GHCR auth patch workflow
ORESoftware Sep 14, 2026
33a95e4
ci: remove completed one-shot GHCR salvage workflow
ORESoftware Sep 14, 2026
fe14b02
ci: migrate outage fallback to shared recovery authority
ORESoftware Sep 14, 2026
eb04f76
ci: fix shared recovery migration gate definition
ORESoftware Sep 14, 2026
f55db4b
ci: consolidate shared recovery policy and GHCR upload URL fix
ORESoftware Sep 14, 2026
3994afa
fix: resolve relative GHCR upload locations
ORESoftware Sep 14, 2026
8d35294
ci: remove failed one-shot shared recovery workflow
ORESoftware Sep 14, 2026
7af44a2
ci: remove failed outage consolidation workflow
ORESoftware Sep 14, 2026
898293c
ci: serialize GitHub outage fallback runs
ORESoftware Sep 14, 2026
7944b5c
fix: use canonical flags2env integer type
ORESoftware Sep 14, 2026
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 .cli-flags.toml
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ help = "Print the installed dependency graph as a tree."
[commands.tree.flags.tree_depth]
env = "ZED_PKG_TREE_DEPTH"
aliases = ["depth"]
type = "int"
type = "integer"
help = "Levels below the root to print; omit for the whole graph."

[commands.tree.flags.tree_json]
Expand Down
234 changes: 234 additions & 0 deletions .github/workflows/github-control-plane-outage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
name: GitHub control-plane outage fallback

on:
pull_request:
paths:
- 'src/source_fallback.rs'
- 'src/registry.rs'
- 'src/fetch.rs'
- 'src/store.rs'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/github-control-plane-outage.yml'
- 'docs/github-control-plane-outage.md'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: github-control-plane-outage-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

defaults:
run:
shell: bash

env:
# The registry cannot resolve; the R2 endpoint is a local sentinel that logs
# any request. A successful GitHub-first restore must leave that log with no
# GETs, proving Zed/Cloudflare infrastructure is not consulted first.
DEAD_REGISTRY: https://zed-control-plane-intentionally-unavailable.invalid
DEAD_R2: http://127.0.0.1:18081

jobs:
github-direct:
name: GitHub is sufficient when Zed infrastructure is gone
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- name: Check out exact candidate
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
show-progress: false

- name: Install pinned Rust toolchain
uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30
with:
toolchain: '1.98.1'

- name: Build exact candidate with locked dependencies
run: |
set -Eeuo pipefail
cargo build --locked --release --bin zed
test "$(git rev-parse HEAD)" = "$GITHUB_SHA"

- name: Run focused fallback security and archive tests
run: |
set -Eeuo pipefail
cargo test --locked source_fallback::tests:: -- --nocapture

- name: Start fail-if-used R2 sentinel
run: |
set -Eeuo pipefail
mkdir -p "$RUNNER_TEMP/dead-r2-root"
: > "$RUNNER_TEMP/r2-sentinel.log"
python3 -m http.server 18081 \
--bind 127.0.0.1 \
--directory "$RUNNER_TEMP/dead-r2-root" \
>"$RUNNER_TEMP/r2-sentinel.log" 2>&1 &
echo "$!" > "$RUNNER_TEMP/r2-sentinel.pid"
sleep 1
kill -0 "$(cat "$RUNNER_TEMP/r2-sentinel.pid")"

- name: Create five-language consumer
run: |
set -Eeuo pipefail
mkdir -p "$RUNNER_TEMP/github-only-consumer"
cat > "$RUNNER_TEMP/github-only-consumer/.zpkg.toml" <<'EOF'
[package]
org = "zed-pkg-test"
name = "github-only-outage-consumer"
version = "0.0.0"
description = "Proves zed-cli can install with every Zed-owned network service unavailable"

[package.repository]
vcs = "git"
url = "https://github.com/zed-pkg-test/github-api-fallback-canary"

[install]
dir = "zed_modules"
adapter = "none"

[dependencies]
"zed-pkg-test/go-lib" = "=1.0.0"
"zed-pkg-test/python-lib" = "=1.0.0"
"zed-pkg-test/dart-lib" = "=1.0.0"
"zed-pkg-test/node-lib" = "=1.0.0"
"zed-pkg-test/gleam-lib" = "=1.0.0"
EOF

- name: Install with registry down, R2 forbidden, and no GitHub token
working-directory: ${{ runner.temp }}/github-only-consumer
env:
ZED_PKG_SOURCE_FALLBACK: 'true'
ZED_PKG_R2_PUBLIC_BASE: ${{ env.DEAD_R2 }}
run: |
set -Eeuo pipefail
ZED="$GITHUB_WORKSPACE/target/release/zed"
unset GITHUB_TOKEN GH_TOKEN ZED_PKG_GITHUB_TOKEN || true
"$ZED" --registry "$DEAD_REGISTRY" --home "$RUNNER_TEMP/zed-home-a" install \
--install-mode copy --adapter none --allow-ecosystem-mismatch \
2>&1 | tee "$RUNNER_TEMP/github-only-a.log"

test "$(grep -E -c 'using GitHub(-first)? fallback' "$RUNNER_TEMP/github-only-a.log")" -ge 5
test -s .zpkg.lock
for name in go-lib python-lib dart-lib node-lib gleam-lib; do
grep -R -q "name = \"$name\"" zed_modules
done
cp .zpkg.lock "$RUNNER_TEMP/github-only.lock"

- name: Prove clean-store resolution is deterministic
working-directory: ${{ runner.temp }}/github-only-consumer
env:
ZED_PKG_SOURCE_FALLBACK: 'true'
ZED_PKG_R2_PUBLIC_BASE: ${{ env.DEAD_R2 }}
run: |
set -Eeuo pipefail
ZED="$GITHUB_WORKSPACE/target/release/zed"
unset GITHUB_TOKEN GH_TOKEN ZED_PKG_GITHUB_TOKEN || true
rm -rf zed_modules .zpkg.lock "$RUNNER_TEMP/zed-home-b"
"$ZED" --registry "$DEAD_REGISTRY" --home "$RUNNER_TEMP/zed-home-b" install \
--install-mode copy --adapter none --allow-ecosystem-mismatch \
2>&1 | tee "$RUNNER_TEMP/github-only-b.log"
cmp "$RUNNER_TEMP/github-only.lock" .zpkg.lock

- name: Prove frozen replay works from another empty store
working-directory: ${{ runner.temp }}/github-only-consumer
env:
ZED_PKG_SOURCE_FALLBACK: 'true'
ZED_PKG_R2_PUBLIC_BASE: ${{ env.DEAD_R2 }}
run: |
set -Eeuo pipefail
ZED="$GITHUB_WORKSPACE/target/release/zed"
unset GITHUB_TOKEN GH_TOKEN ZED_PKG_GITHUB_TOKEN || true
rm -rf zed_modules "$RUNNER_TEMP/zed-home-frozen"
"$ZED" --registry "$DEAD_REGISTRY" --home "$RUNNER_TEMP/zed-home-frozen" install --frozen \
--install-mode copy --adapter none --allow-ecosystem-mismatch \
2>&1 | tee "$RUNNER_TEMP/github-only-frozen.log"
cmp "$RUNNER_TEMP/github-only.lock" .zpkg.lock
test "$(find zed_modules -name .zpkg.toml -type f | wc -l)" -ge 5

- name: Prove successful GitHub recovery did not touch R2 or Cloudflare
run: |
set -Eeuo pipefail
log="$RUNNER_TEMP/github-only-a.log"
test -s "$log"
grep -E -q 'using GitHub(-first)? fallback' "$log"
if grep -q 'GET /' "$RUNNER_TEMP/r2-sentinel.log"; then
echo 'Successful GitHub recovery unexpectedly contacted the configured R2/CDN endpoint:' >&2
cat "$RUNNER_TEMP/r2-sentinel.log" >&2
exit 1
fi
! grep -E 'success.*(zpkg\.net|cdn\.zpkg\.net|cloudflare)' "$log"
# Negative tests below intentionally exercise failure paths that may
# probe configured mirrors. Reset the sentinel so they cannot taint
# the positive recovery assertion above.
: > "$RUNNER_TEMP/r2-sentinel.log"

- name: Prove fallback is required rather than accidental cache reuse
working-directory: ${{ runner.temp }}/github-only-consumer
env:
ZED_PKG_SOURCE_FALLBACK: 'false'
ZED_PKG_R2_PUBLIC_BASE: ${{ env.DEAD_R2 }}
run: |
set -Eeuo pipefail
ZED="$GITHUB_WORKSPACE/target/release/zed"
unset GITHUB_TOKEN GH_TOKEN ZED_PKG_GITHUB_TOKEN || true
rm -rf zed_modules "$RUNNER_TEMP/zed-home-no-fallback"
set +e
"$ZED" --registry "$DEAD_REGISTRY" --home "$RUNNER_TEMP/zed-home-no-fallback" install --frozen \
--install-mode copy --adapter none --allow-ecosystem-mismatch \
>"$RUNNER_TEMP/no-fallback.log" 2>&1
status=$?
set -e
cat "$RUNNER_TEMP/no-fallback.log"
test "$status" -ne 0

- name: Reject a GitHub tag whose manifest claims another package identity
run: |
set -Eeuo pipefail
mkdir -p "$RUNNER_TEMP/github-negative"
cat > "$RUNNER_TEMP/github-negative/.zpkg.toml" <<'EOF'
[package]
org = "zed-pkg-test"
name = "github-negative-consumer"
version = "0.0.0"
description = "Fail-closed GitHub identity canary"

[package.repository]
vcs = "git"
url = "https://github.com/zed-pkg-test/github-api-fallback-canary"

[install]
dir = "zed_modules"
adapter = "none"

[dependencies]
"zed-pkg-test/awkward-lib" = "=0.1.0"
EOF

ZED="$GITHUB_WORKSPACE/target/release/zed"
unset GITHUB_TOKEN GH_TOKEN ZED_PKG_GITHUB_TOKEN || true
set +e
(
cd "$RUNNER_TEMP/github-negative"
ZED_PKG_SOURCE_FALLBACK=true ZED_PKG_R2_PUBLIC_BASE="$DEAD_R2" \
"$ZED" --registry "$DEAD_REGISTRY" --home "$RUNNER_TEMP/zed-home-negative" install \
--install-mode copy --adapter none --allow-ecosystem-mismatch
) >"$RUNNER_TEMP/github-negative.log" 2>&1
status=$?
set -e
cat "$RUNNER_TEMP/github-negative.log"
test "$status" -ne 0
grep -F 'does not self-claim package zed-pkg-test/awkward-lib' "$RUNNER_TEMP/github-negative.log"

- name: Stop R2 sentinel
if: always()
run: |
set -Eeuo pipefail
if test -s "$RUNNER_TEMP/r2-sentinel.pid"; then
kill "$(cat "$RUNNER_TEMP/r2-sentinel.pid")" || true
fi
59 changes: 59 additions & 0 deletions docs/github-control-plane-outage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# GitHub-only recovery when the Zed control plane is unavailable

`zed` must continue resolving and installing already-published public packages when every Zed-operated network service is unavailable. `registry.zpkg.net`, `cdn.zpkg.net`, Cloudflare Workers, R2, and application servers are acceleration and control-plane services; they are not required availability dependencies for public package restore when GitHub contains enough provenance to reconstruct the package.

## Recovery contract

The normal path remains the configured registry. When an HTTP registry request fails and source fallback is enabled, `zed` may reconstruct package metadata from GitHub and fetch package bytes from GitHub-owned surfaces. GitHub is treated as an independent content/repository recovery plane, not as a new package-authority model.

A public package is admissible through GitHub fallback only when the repository can be mapped unambiguously to the requested `org/name`, the committed `.zpkg.toml` self-claims that package identity, and the requested version is represented by an admissible version tag. Release assets, GHCR artifacts, and the GitHub tag archive are acceptable GitHub-hosted content sources. A tag archive is repacked using the same deterministic Zed package rules before its digest is recorded or compared.

The fallback remains fail-closed. Repository-search ambiguity, a mismatched package identity, malformed or unsafe archives, missing required provenance, and digest/size mismatches are errors. Fallback never grants authority to an arbitrary repository merely because its name resembles the package name.

## What must not be required

The recovery path must succeed with all of the following unavailable at the same time:

- the configured Zed HTTP registry;
- every `*.zpkg.net` endpoint;
- Cloudflare Workers, Pages, Tunnels, and other Zed-owned edge services;
- the Zed R2/CDN origin; and
- any Zed application or API server behind Cloudflare.

The exact-head outage workflow configures a guaranteed-unresolvable registry plus a dead loopback R2 endpoint. It then removes `GITHUB_TOKEN`, `GH_TOKEN`, and `ZED_PKG_GITHUB_TOKEN` and restores public packages from GitHub. This proves the public recovery path does not rely on credentials or on a Zed-operated server.

## GitHub as a recovery CDN

For public packages, the GitHub recovery plane can use:

1. `api.github.com` for repository identity, tags, releases, and tag-to-commit provenance;
2. `raw.githubusercontent.com` for the committed `.zpkg.toml` used for identity admission;
3. GitHub Releases and GitHub Packages/GHCR when a package publishes compatible artifacts there; and
4. GitHub/codeload tag archives as the final reconstructable source artifact.

When a GitHub source succeeds, Zed-owned mirrors are not required for correctness. R2 remains an optional mirror for cases where GitHub is unavailable or incomplete; it must not be the sole recovery source for a package that can be reconstructed from GitHub.

For private repositories, an explicit GitHub token may be used. Credentials are attached only to GitHub API requests that require them; redirects to codeload or unrelated mirror hosts must not receive the token.

## Hermetic exceptions

`file://` registries and loopback registries remain hermetic by default. Local tests, air-gapped mirrors, and deliberately isolated development environments must not leak package identities to GitHub. The explicit loopback fallback opt-in remains available for controlled canaries.

Source fallback is a read/recovery capability. Operations that require Zed control-plane state or mutation—publishing, namespace claims, yanking, server-side search, and audit-log access—may still fail while the control plane is down. The outage guarantee is specifically resolution, download, deterministic reconstruction, install, and frozen restore of packages whose GitHub provenance is sufficient.

## Acceptance gates

The required outage canary must prove all of these conditions on the exact pull-request head:

1. the configured registry is unreachable;
2. the configured R2/CDN endpoint is unreachable;
3. no GitHub token is available for the public-package test;
4. five package ecosystems resolve from GitHub source fallback;
5. the packages are actually materialized, not merely resolved;
6. a second empty store produces the identical lockfile;
7. a frozen restore succeeds from another empty store;
8. disabling source fallback makes the same outage fail instead of reusing an accidental cache;
9. a GitHub repository/tag with the wrong package identity is rejected; and
10. successful recovery has no dependency on a `zpkg.net` or Cloudflare response.

These gates intentionally model a total Zed control-plane outage rather than a partial CDN degradation.
Loading
Loading