Repository navigation
Conversation
Mirror each published release into GHCR: trusted-server-cli per target and trusted-server-fastly as OCI artifacts that carry each binary with its SBOM and Sigstore bundles, after checking every file against SHA256SUMS. Tags are write-once and each pushed manifest is checked layer by layer. Build the Axum server as a non-root distroless image from the tagged source and attach GitHub build provenance to its digest. Axum gains TRUSTED_SERVER_LISTEN_ADDR so a container can listen outside loopback. Both jobs stay off until PACKAGE_PUBLICATION_ENABLED is true.
…e jobs The publisher isolation test read everything after the release job, so the new packages and axum-image jobs made it count three checkouts. It now reads each job alone and pins the packages job's sparse checkout, pinned oras and lack of release-writing permission.
Run the image on Debian slim as UID 10001 with a read-only root, stop it with SIGINT, and read the listen address from EdgeZero's EDGEZERO__ADAPTER__HOST and EDGEZERO__ADAPTER__PORT instead of a new variable. A test.yml job now builds the image on every PR and runs the Axum smoke against it, including one case on the image's own bridge defaults. Make package publication fail closed on registry errors, complete a partial publish on re-run, authenticate SHA256SUMS first, and record two rounds of spec review.
Keep both new release-verification sections: the GitHub Packages download guide and Phase 2's Fastly package deployment. Gate 16 in AGENTS.md lists both the packages tests and the Fastly package validator, matching format.yml.
Phase 2 added trusted-server-fastly.tar.gz to the release and made it the documented Fastly deploy path, but the trusted-server-fastly GHCR artifact still carried only the wasm and its evidence. Add the archive as a gzip layer; the wasm's provenance and SBOM bundles already cover it. checkDownload now also refuses a release file that no package carries, so a new release asset stops the packages job instead of being silently left out. The tests built their release from the plan, so they could not see an omission; a new case adds an unplanned file.
There was a problem hiding this comment.
Superseded by a follow-up review with inline comments: #1543 (review)
dhruv8sh
left a comment
There was a problem hiding this comment.
Summary
Adds GHCR package publication for release binaries (byte-identical OCI artifacts checked against the authenticated SHA256SUMS) and an Axum container image with provenance, both gated behind PACKAGE_PUBLICATION_ENABLED. The publisher's fail-closed handling (exact-file check, not-found-only absence, manifest re-verification) is careful and well tested; only non-blocking observations below.
Non-blocking
🤔 thinking
- Image stop signal path is not exercised by the smoke — see inline at
scripts/smoke-axum.sh:160 PORTfallback duplicates EdgeZero's port-validity rule — see inline atcrates/trusted-server-adapter-axum/src/main.rs:105
CI Status
- cargo fmt: PASS (required)
- cargo test: PASS (required)
- format-typescript: PASS (required)
- format-docs: PASS (required)
- Axum image smoke: PASS
- release scripts: PASS
- vitest: PASS
- cargo test (axum native): PASS
- cargo test (cross-adapter parity): PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo test (ts CLI, native) (ubuntu-latest): PASS
- cargo test (ts CLI, native) (macos-latest): PASS
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- browser integration tests: PASS
- prepare integration artifacts: PASS
- trace runtime and browser acceptance: PASS
- HTML EOF runtime (Fastly): PASS
- worker-deadlines (cloudflare): PASS
- worker-deadlines (spin): PASS
- docs-proposal scripts: PASS
- CI Node pin guard: PASS
- CLAUDE.md symlink guard: PASS
- ts dev lint domains (changed lines): PASS
- CodeQL: PASS
- Analyze (rust): PASS
- Analyze (actions): PASS
- Analyze (python): PASS
- Analyze (javascript-typescript): PASS
| docker run --rm --init --pull never --name "$CONTAINER" \ | ||
| --read-only --cap-drop ALL --security-opt no-new-privileges \ | ||
| --publish "127.0.0.1:$DEFAULT_PORT:8080" \ | ||
| "$IMAGE" >"$WORKSPACE/image-defaults.log" 2>&1 & | ||
| APP_PID=$! | ||
| smoke_wait_http "http://127.0.0.1:$DEFAULT_PORT/" 500 \ | ||
| "$WORKSPACE/image-defaults.body" "$WORKSPACE/image-defaults.headers" | ||
| smoke_assert_process_alive "$APP_PID" "Axum image" "$WORKSPACE/image-defaults.log" | ||
| stop_app |
There was a problem hiding this comment.
🤔 thinking — The image's documented stop path is never exercised. Both image runs use --init (so tini, not the server, is PID 1) and end with stop_app, which runs docker rm --force (SIGKILL). STOPSIGNAL SIGINT and "docker stop shuts it down without waiting for the kill timeout" in docs/guide/axum-dev.md therefore have no CI coverage; a future change that drops the SIGINT handler or the STOPSIGNAL line would still pass this smoke.
Consider running this bridge-network case without --init (matching the documented docker run) and stopping it with docker stop, asserting it exits well before the timeout:
SECONDS=0
docker stop --time 10 "$CONTAINER" >/dev/null
CONTAINER=""
[ "$SECONDS" -lt 5 ] || smoke_die "image ignored its stop signal (took ${SECONDS}s)"
wait "$APP_PID" || true
APP_PID=""Apply manually — not offered as a one-click suggestion because verifying it needs a full image build plus the container smoke, which this pass did not run.
| edgezero_port: Option<&str>, | ||
| port: Option<&str>, | ||
| ) -> Result<BindAddrResolution, String> { | ||
| let edgezero_port_valid = |
There was a problem hiding this comment.
🤔 thinking — edgezero_port_valid re-implements resolve_port's validity rule from edgezero_core::addr (parses as u16 and is nonzero) to decide whether PORT is consulted. It matches EdgeZero v0.0.11 today, but if EdgeZero's rule changes (for example a range limit), this check and the real resolution would disagree, and an invalid PORT could stop startup even though EdgeZero would have used the EdgeZero port, or the reverse. A short comment naming the v0.0.11 coupling, or a test that asserts bind_address agrees with resolve_bind_addr on the zero and non-numeric cases, would make a future EdgeZero bump surface it.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
Adds opt-in GHCR publication of verified release binaries and an attested Axum container image, with bind-address configuration, container smoke coverage, and operator documentation. No actionable findings were identified across the 14 changed files.
Verification
All 31 reported CI checks pass. Passing suites were not rerun locally; package publication was not independently exercised.
CI Status
- integration tests: PASS
- browser integration tests: PASS
- trace runtime and browser acceptance: PASS
- integration tests (Fastly EC lifecycle): PASS
- CodeQL: PASS
- cargo test (ts CLI, native) (macos-latest): PASS
- HTML EOF runtime (Fastly): PASS
- Axum image smoke: PASS
- worker-deadlines (spin): PASS
- Analyze (rust): PASS
- vitest: PASS
- CI Node pin guard: PASS
- cargo test (ts CLI, native) (ubuntu-latest): PASS
- worker-deadlines (cloudflare): PASS
- cargo test: PASS (required)
- ts dev lint domains (changed lines): PASS
- CLAUDE.md symlink guard: PASS
- format-typescript: PASS (required)
- cargo test (axum native): PASS
- Analyze (actions): PASS
- format-docs: PASS (required)
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- docs-proposal scripts: PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- release scripts: PASS
- cargo test (cross-adapter parity): PASS
- Analyze (javascript-typescript): PASS
- cargo fmt: PASS (required)
- prepare integration artifacts: PASS
- Analyze (javascript-typescript): PASS
- Analyze (python): PASS
Per the user, the RC line is forward-ported to main (DEC-6). Rechecking the proposed stack against DEC-3 and DEC-16: #1536 and #1537 are mostly Axum-only stores, store selection and wiring that EZI-2b, T6 and T21 replace, so A2 becomes a new pull request that takes their tests, and both close with #1535 once their code moves. The image and Terraform units restack on #1543, which publishes the image and shares .dockerignore with #1539. #1531 goes on #1506 for A1 and #1532 on #1531 for #1529.
Per the user, binary attestation and package publishing (#1449, #1463, #1543) are not part of this work and carry lower priority (DEC-19). AX-5 keeps only the production image built from source, A6 shrinks to building it and no longer waits on #1449, and the image and Terraform units restack on #1531, where they apply cleanly, instead of #1543.
Summary
Why. A release publishes 19 assets, and each run adds 12 internal workflow artifacts, so operators can't tell which file to use. Each release now also appears under the repository's Packages as one package per thing to run:
trusted-server-cli, tagged<tag>-<target>;trusted-server-fastly, tagged<tag>, with the wasm and the deployabletrusted-server-fastly.tar.gz;trusted-server-axum, a container image tagged<tag>.CLI and Fastly packages are byte-for-byte copies of the release. Each file is one OCI layer whose digest is its
SHA256SUMSentry, so they add no new trust. Users verify what they pull with the existing release verification.The Axum image follows the image contract proposed in Make the Axum adapter a supported self-hosted deployment target #1236:
10001, read-only root filesystem, no baked config or secrets;EDGEZERO__ADAPTER__HOST, so it listens on0.0.0.0:8080;Axum stays development status.
Off by default. Both publishing jobs are skipped until a maintainer sets
PACKAGE_PUBLICATION_ENABLED=true.Stacked on #1449 (base
feature/binary-attestations-phase2), merged up tofd33f2c74(which includesmainat06e51be17). Phase 2's27361ec58addedtrusted-server-fastly.tar.gzto the release. Review it after #1449. When #1449 merges, this PR retargets tomain.Changes
.github/workflows/release.ymlpackagesjob: checksum-pinned oras,contents: readandpackages: writeonly. Newaxum-imagejob: build, push, digest check,actions/attest. Both needreleaseand the new variable.scripts/release/packages.mjsSHA256SUMSwithgh release verify-asset;<reference>: not found;scripts/release/packages.test.mjs.tar.gzlayer), the exactSHA256SUMScheck, refusal of a release file no package carries, the manifest check, first publish, re-run and partial completion, refusal of differing tags, fail-closed on registry errors, drafts, Dockerfile pins equal the repository pins.scripts/release/phase1.test.mjspackagesjob's sparse checkout, oras checksum and lack of release-write permission, and checks theaxum-imagegate.crates/trusted-server-adapter-axum/Dockerfile.tool-versionsimage, Rust from therust-toolchain.tomlchannel, and the Debian slim runtime.RUSTUP_TOOLCHAINstops rustup downloads. Embeds the source commit as the version, usesSTOPSIGNAL SIGINT, and runs asUSER 10001:10001..dockerignore.git, build output,node_modules, local config and secrets, and docs from the build context.crates/trusted-server-adapter-axum/src/main.rsedgezero_core::addr::resolve_bind_addr.EDGEZERO__ADAPTER__HOST/PORTcome first.PORTis read only as the fallback, andPORT=0is rejected.127.0.0.1:8787.scripts/smoke-axum.shAXUM_SMOKE_IMAGEruns every case against the image with a read-only root, no capabilities, and config and secrets passed by name. Adds a bridge-network case on the image's own defaults and an image-user check..github/workflows/test.ymlaxum-imagejob: builds the image and runs the smoke against it..github/workflows/format.yml,AGENTS.mdpackages.test.mjswith the release script tests.docs/guide/release-verification.md.tar.gzthat "Deploy the verified Fastly package" deploys), verifying pulled files, and verifying the image by digest before running it. New maintainer checklist item: the variable, rehearsal first, visibility, recovery.docs/guide/axum-dev.mddocs/superpowers/specs/2026-11-11-release-packages-design.mdCloses
No issue closes. This is part of #1235 (attested releases). It also implements the image and bind items of #1236 ahead of #1236's spec. That spec may still change them, and the
axum-imagejob is the one place to adjust. Publishing the image also takes on part of #897's scope early; the spec's scope note says so.Spec
Spec:
docs/superpowers/specs/2026-11-11-release-packages-design.md. It ends with the round 1 and round 2 review log.Test plan
cargo fmt --all -- --checkcargo clippy-fastly && cargo clippy-axum && cargo clippy-cloudflarecargo test-fastly && cargo test-axum && cargo test-cloudflare. Fastly was run with the pinned Viceroy 0.21.0.JS tests:
cd crates/trusted-server-js/lib && npx vitest run. 1,901 passed with the pinned Node 24.20.0. Local Node 24.12.0 fails 9observe-npmtests that require exactly 24.20.0.JS format:
cd crates/trusted-server-js/lib && npm run formatDocs format:
cd docs && npm run formatWASM build: not run separately. The Fastly adapter is unchanged.
Manual testing via
fastly compute serve: not applicable.Other:
node --test scripts/release/phase1.test.mjs scripts/release/packages.test.mjs: 54 passed.shellcheckon the release and smoke scripts,actionlint, and the CI Node-pin guard.packagesjobcontents: write;10001:10001and listens on0.0.0.0:8080;EDGEZERO__ADAPTER__HOSTfalls back to loopback with a warning;stoptakes about 0 seconds withSIGINT.v0.0.0-rehearsal.37(387f41b0b, before the alignment commit), the first publish to packages that did not exist yet:packagesandaxum-image;SHA256SUMS;gh attestation verifypassed for a CLI binary pulled from GHCR, andgh release verify-assetmatched it to the release;gh attestation verifypassed for the image digest the tag points to.This PR's
Axum image smokeCI job, the first run of the container smoke on Linux host networking.Fork rehearsal
v0.0.0-rehearsal.38ofdc1a063d8(the last commit before the base merge, so its release had no.tar.gz): every job passed, includingrelease,packagesandaxum-image.SHA256SUMS, with the empty OCI config and a link to the source repository.linux/amd64and runs as10001:10001. It has stop signalSIGINT, setsEDGEZERO__ADAPTER__HOST=0.0.0.0andEDGEZERO__ADAPTER__PORT=8080, and its labels show development status and revisiondc1a063d8.gh attestation verifypassed for the image by the digest its tag points to.After merging the base (
27361ec58) and adding the.tar.gzlayer:node --test scripts/release/phase1.test.mjs scripts/release/packages.test.mjs: 56 passed with Node 24.20.0..tar.gzentry fails 2 tests; dropping the "no package carries" check fails 1.cargo test-axum,cargo clippy-axum,cargo fmt --all -- --check,actionlint, the releaseshellcheckset and docs Prettier pass.Fork rehearsal
v0.0.0-rehearsal.41ofd719d2bfb(the.tar.gzfix plus the Phase 2 link fix): every job passed, includingrelease,packagesandaxum-image. The first two attempts were cancelled only because no macOS arm64 runner was assigned.trusted-server-fastlymanifest holds 6 layers: the wasm,trusted-server-fastly.tar.gz(application/gzip), the SPDX SBOM, both Sigstore bundles and the rebuild check. Every layer digest matches the release'sSHA256SUMS.main's Reject unknown integration configuration IDs and fields #1455 and Fall back locally after mediator HTTP errors #1493 through Phase 2; no release or package code changed after.41.PR CI on
e5e6ea7fa: all 31 checks passed.Checklist
unwrap()in production code — useexpect("should ...")logmacros (notprintln!). The Axummainkeeps its existingeprintln!pattern for fatal startup errors, such as an invalidPORT.Closes #1235