Repository navigation
Conversation
|
Rehearsal record at Run · immutable release. Release ID 420703797, 17 exact assets, no index/build-evidence/build-recipe/rebuild-check/inventory companions. All four builds and signers passed. Publication, initial release-ID/immutability validation, Independent verification below completed with exit status 0: one release verification, 17 asset verifications, 16 attestation verifications (four binaries × provenance/SPDX × signer-prefix/exact tag-ref identity), 16 checksum entries and four signed-SPDX predicate comparisons. It uses the guide commands with only repository/tag/commit substitutions. Workflow CLI: 2.102.0; independent CLI: 2.97.0. Full independent verification transcript — commands and output |
|
Successful rehearsal at Workflow — all jobs passed · immutable release · publisher log. Release ID 420812096: immutable=true, draft=false, 17 exact nonempty assets (four binaries, four SPDX2.3 documents, eight real action-produced bundles and SHA256SUMS). No index, inventory, build-evidence, build-recipe or rebuild-check companions. All four builds, all four isolated signing jobs, publication, cryptographic release/membership verification and the final release-ID/API read passed. The connection fix is exercised successfully; no mutation retry or tag reuse occurred. Immutable Independent verification completed with exit status 0 using checksum-verified GitHub CLI2.102.0, matching the publisher's recorded version: one release verification, 17 asset verifications, 16 attestation verifications (four binaries × provenance/SPDX × signer-prefix/exact tag-ref identity), all16 checksum entries and four signed-SPDX predicate comparisons. Commands are the guide's verification blocks with only repository/tag/commit substitutions. Full verification transcript — commands and output |
|
Rehearsal GitHub CLI 2.102.0 matches the publisher log. Its official macOS ARM archive was checksum-verified (SHA-256 Offline proofAll four binaries passed provenance and SPDX verification under OS-enforced network denial, with both signer identity forms, exact source/ref/signer digest pins, self-hosted runners denied, and published-SPDX predicate equality. No gh --version
gh attestation trusted-root > trusted_root.jsonl
shasum -a 256 trusted_root.jsonl
/usr/bin/sandbox-exec -p '(version 1)(allow default)(deny network*)' env GH_TOKEN=offline-verification-no-network bash -x offline-verify.shFull offline commands and success outputLocal verified Fastly routeThe downloaded wasm and host CLI first passed provenance and immutable release membership checks. Viceroy 0.21.0, pinned in the source tree, served a real configured page: HTTP 200, fixture origin marker, injected JS and the exact full source SHA in Complete local commands and outputset -euxo pipefail
REPO_ROOT=<source-checkout>
EVIDENCE=<evidence-directory>
WORK="$EVIDENCE/viceroy-smoke-2"
TS="$EVIDENCE/ts-aarch64-apple-darwin"
WASM="$EVIDENCE/trusted-server-fastly.wasm"
VICEROY=<pinned-runtime-tools>/viceroy
mkdir "$WORK"
for port in 18888 17676; do
if lsof -nP -iTCP:"$port" -sTCP:LISTEN; then exit 1; fi
done
cp "$REPO_ROOT/edgezero.toml" "$REPO_ROOT/fastly.toml" "$WORK/"
cp "$REPO_ROOT/trusted-server.example.toml" "$WORK/app.toml"
ln -s "$REPO_ROOT/crates" "$WORK/crates"
node - "$WORK" <<'JS'
const fs=require('node:fs');const root=process.argv[2];
const path=`${root}/app.toml`;let text=fs.readFileSync(path,'utf8');text=text.replace('domain = "example.com"','domain = "127.0.0.1"').replace('cookie_domain = ".example.com"','cookie_domain = "127.0.0.1"');text=text.replace('origin_url = "https://origin.example.com"','origin_url = "http://127.0.0.1:18888"');if(!text.includes('origin_url = "http://127.0.0.1:18888"'))throw Error('Origin fixture replacement failed');fs.writeFileSync(path,text);
const fastly=`${root}/fastly.toml`;text=fs.readFileSync(fastly,'utf8').replace('env = "FASTLY_KEY"','data = "fictional-local-api-key"');fs.writeFileSync(fastly,text);
JS
chmod u+x "$TS"
"$TS" --version
(cd "$WORK" && "$TS" config push --adapter fastly --local \
--manifest "$WORK/edgezero.toml" --app-config "$WORK/app.toml" --no-diff --yes)
"$VICEROY" --version
cat > "$WORK/origin.mjs" <<'JS'
import http from 'node:http';
http.createServer((req,res)=>{res.writeHead(200,{'content-type':'text/html'});res.end('<!doctype html><html><head><title>Local release proof</title></head><body>phase1-verified-origin-marker</body></html>');}).listen(18888,'127.0.0.1');
JS
node "$WORK/origin.mjs" > "$WORK/origin.log" 2>&1 &
origin_pid=$!
cleanup() {
if [[ -n "${viceroy_pid:-}" ]]; then kill "$viceroy_pid" 2>/dev/null || true; wait "$viceroy_pid" 2>/dev/null || true; fi
kill "$origin_pid" 2>/dev/null || true
wait "$origin_pid" 2>/dev/null || true
}
trap cleanup EXIT
"$VICEROY" serve -C "$WORK/fastly.toml" --addr 127.0.0.1:17676 "$WASM" > "$WORK/viceroy.log" 2>&1 &
viceroy_pid=$!
curl --fail --silent --show-error --retry 20 --retry-connrefused --retry-delay 1 \
--retry-max-time 45 --max-time 30 -H 'Host: 127.0.0.1' \
-D "$WORK/headers.txt" -o "$WORK/body.html" http://127.0.0.1:17676/
cat "$WORK/headers.txt"
node - "$WORK" <<'JS'
const fs=require('node:fs');const assert=require('node:assert/strict');const crypto=require('node:crypto');const work=process.argv[2];const headers=fs.readFileSync(`${work}/headers.txt`,'utf8');const body=fs.readFileSync(`${work}/body.html`);assert.match(headers,/^HTTP\/1\.1 200/m);assert.match(headers,/^x-ts-version: 922c2211c0cc7941bfc8ffa9f775ef8ac0971232\r?$/im);assert.ok(body.includes('phase1-verified-origin-marker'));assert.ok(body.includes('/static/tsjs=tsjs-unified.min.js'));console.log(`SUCCESS: configured Viceroy page returned HTTP200, origin marker, injected JS and exact release source header; ${body.length} bytes, sha256=${crypto.createHash('sha256').update(body).digest('hex')}`);
JS |
|
Rehearsal
Download recordThe ordinary release download was interrupted because transfer throughput was too low. Read-only bounded range downloads fetched the same release URLs; complete assembled files matched the published SHA-256 metadata before verification. No binary was substituted from a CI artifact or another release. Small companions were independently digest-checked as well. Commands and output follow; scripts existed only in the temporary evidence directory. Download commands and complete digest resultsgh api repos/prk-Jr/trusted-server/releases/tags/v0.0.0-rehearsal.4 > release-metadata.json
node range-download.mjs
node small-download.mjsimport fs from 'node:fs';
import {promisify} from 'node:util';
import {execFile} from 'node:child_process';
import crypto from 'node:crypto';
const run=promisify(execFile);
const release=JSON.parse(fs.readFileSync('release-metadata.json'));
const binaries=release.assets.filter(a=> !a.name.includes('.') || a.name.endsWith('.wasm'));
fs.mkdirSync('ranges',{recursive:true});
const tasks=[]; const chunk=2*1024*1024;
for(const a of binaries) for(let offset=0;offset<a.size;offset+=chunk) tasks.push({a,offset,end:Math.min(a.size-1,offset+chunk-1)});
let done=0;
async function worker(){while(tasks.length){const {a,offset,end}=tasks.shift();const out=`ranges/${a.name}.${offset}`; await run('curl',['--fail','--silent','--show-error','--location','--retry','2','--max-time','180','--range',`${offset}-${end}`,'--max-filesize',String(end-offset+1),'--output',out,a.browser_download_url]);if(fs.statSync(out).size!==end-offset+1)throw Error(`Range size mismatch: ${a.name} ${offset}`);done++;if(done%16===0)console.log(`Downloaded ${done} ranges`);}}
await Promise.all(Array.from({length:16},worker));
for(const a of binaries){const dest=fs.openSync(`${a.name}.assembled`,'w');const hash=crypto.createHash('sha256');for(let o=0;o<a.size;o+=chunk){const b=fs.readFileSync(`ranges/${a.name}.${o}`);fs.writeSync(dest,b);hash.update(b);}fs.closeSync(dest);if(`sha256:${hash.digest('hex')}`!==a.digest)throw Error(`Digest mismatch: ${a.name}`);fs.renameSync(`${a.name}.assembled`,a.name);console.log(`SUCCESS: ${a.name}: ${a.size} bytes match ${a.digest}`);}import fs from 'node:fs';import crypto from 'node:crypto';import {promisify} from 'node:util';import {execFile} from 'node:child_process';
const run=promisify(execFile);const {assets}=JSON.parse(fs.readFileSync('release-metadata.json'));await Promise.all(assets.filter(a=>a.size<2*1024*1024).map(async a=>{const valid=()=>fs.existsSync(a.name)&&`sha256:${crypto.createHash('sha256').update(fs.readFileSync(a.name)).digest('hex')}`===a.digest;if(!valid())await run('curl',['--fail','--silent','--show-error','--location','--max-time','180','--output',a.name,a.browser_download_url]);if(!valid())throw Error(`Bad asset: ${a.name}`);console.log(`SUCCESS: ${a.name} matches published digest`);}));Full online verification transcriptA small shell wrapper prints an explicit success line after each successful attestation command; JSON results remain separate for predicate comparison. |
|
Rehearsal Build/sign/publication run passed all seven builds/signing jobs and immutable publication. Published release: ID 421431085, Full online verification transcript, part 1. A wrapper prints explicit success after each successful attestation command; JSON output stays separate for SPDX equality. |
|
Rehearsal Build/sign/publication run passed all seven builds/signing jobs and immutable publication. Published release: ID 421431085, Full online verification transcript, part 2. |
|
Rehearsal Build/sign/publication run passed all seven builds/signing jobs and immutable publication. Published release: ID 421431085, All seven binaries passed provenance and SPDX verification with both signer identity forms, exact source/ref/signer pins, self-hosted runners denied and published-SPDX predicate equality under OS-enforced network denial. No gh attestation trusted-root > trusted_root.jsonl
shasum -a 256 trusted_root.jsonl
/usr/bin/sandbox-exec -p '(version 1)(allow default)(deny network*)' env GH_TOKEN=offline-verification-no-network bash -x offline-verify.sh |
|
Rehearsal Build/sign/publication run passed all seven builds/signing jobs and immutable publication. Published release: ID 421431085, Verified release wasm and host CLI were executed locally with pinned Viceroy 0.21.0, fictional loopback configuration and a local Node origin. A real page returned HTTP200, origin marker, injected JS and the exact full source SHA in set -euxo pipefail
REPO_ROOT=<source-checkout>
EVIDENCE=<evidence-directory>
WORK="$EVIDENCE/viceroy-smoke"
TS="$EVIDENCE/ts-aarch64-apple-darwin"
WASM="$EVIDENCE/trusted-server-fastly.wasm"
VICEROY=<pinned-runtime-tools>/viceroy
mkdir "$WORK"
for port in 18888 17676; do
if lsof -nP -iTCP:"$port" -sTCP:LISTEN; then exit 1; fi
done
cp "$REPO_ROOT/edgezero.toml" "$REPO_ROOT/fastly.toml" "$WORK/"
cp "$REPO_ROOT/trusted-server.example.toml" "$WORK/app.toml"
ln -s "$REPO_ROOT/crates" "$WORK/crates"
node - "$WORK" <<'JS'
const fs=require('node:fs');const root=process.argv[2];
const path=`${root}/app.toml`;let text=fs.readFileSync(path,'utf8');text=text.replace('domain = "example.com"','domain = "127.0.0.1"').replace('cookie_domain = ".example.com"','cookie_domain = "127.0.0.1"');text=text.replace('origin_url = "https://origin.example.com"','origin_url = "http://127.0.0.1:18888"');if(!text.includes('origin_url = "http://127.0.0.1:18888"'))throw Error('Origin fixture replacement failed');fs.writeFileSync(path,text);
const fastly=`${root}/fastly.toml`;text=fs.readFileSync(fastly,'utf8').replace('env = "FASTLY_KEY"','data = "fictional-local-api-key"');fs.writeFileSync(fastly,text);
JS
chmod u+x "$TS"
"$TS" --version
(cd "$WORK" && "$TS" config push --adapter fastly --local \
--manifest "$WORK/edgezero.toml" --app-config "$WORK/app.toml" --no-diff --yes)
"$VICEROY" --version
cat > "$WORK/origin.mjs" <<'JS'
import http from 'node:http';
http.createServer((req,res)=>{res.writeHead(200,{'content-type':'text/html'});res.end('<!doctype html><html><head><title>Local release proof</title></head><body>phase1-verified-origin-marker</body></html>');}).listen(18888,'127.0.0.1');
JS
node "$WORK/origin.mjs" > "$WORK/origin.log" 2>&1 &
origin_pid=$!
cleanup() {
if [[ -n "${viceroy_pid:-}" ]]; then kill "$viceroy_pid" 2>/dev/null || true; wait "$viceroy_pid" 2>/dev/null || true; fi
kill "$origin_pid" 2>/dev/null || true
wait "$origin_pid" 2>/dev/null || true
}
trap cleanup EXIT
"$VICEROY" serve -C "$WORK/fastly.toml" --addr 127.0.0.1:17676 "$WASM" > "$WORK/viceroy.log" 2>&1 &
viceroy_pid=$!
curl --fail --silent --show-error --retry 20 --retry-connrefused --retry-delay 1 \
--retry-max-time 45 --max-time 30 -H 'Host: 127.0.0.1' \
-D "$WORK/headers.txt" -o "$WORK/body.html" http://127.0.0.1:17676/
cat "$WORK/headers.txt"
node - "$WORK" <<'JS'
const fs=require('node:fs');const assert=require('node:assert/strict');const crypto=require('node:crypto');const work=process.argv[2];const headers=fs.readFileSync(`${work}/headers.txt`,'utf8');const body=fs.readFileSync(`${work}/body.html`);assert.match(headers,/^HTTP\/1\.1 200/m);assert.match(headers,/^x-ts-version: 937214a632f9387601dba94f996de78b2e6ee424\r?$/im);assert.ok(body.includes('phase1-verified-origin-marker'));assert.ok(body.includes('/static/tsjs=tsjs-unified.min.js'));console.log(`SUCCESS: configured Viceroy page returned HTTP200, origin marker, injected JS and exact release source header; ${body.length} bytes, sha256=${crypto.createHash('sha256').update(body).digest('hex')}`);
JS |
|
Rehearsal Build/sign/publication run passed all seven builds/signing jobs and immutable publication. Published release: ID 421431085, All 29 published files were downloaded from this release. Bounded range downloads were assembled and checked against the published SHA-256 metadata; small companions were independently digest-checked. No earlier release or CI artifact substituted for a binary. gh api repos/prk-Jr/trusted-server/releases/tags/v0.0.0-rehearsal.5 > release-metadata.json
node range-download.mjs
node small-download.mjs
node check-downloads.mjsimport fs from 'node:fs';
import {promisify} from 'node:util';
import {execFile} from 'node:child_process';
import crypto from 'node:crypto';
const run=promisify(execFile);
const release=JSON.parse(fs.readFileSync('release-metadata.json'));
const binaries=release.assets.filter(a=> a.size>=2*1024*1024);
fs.mkdirSync('ranges',{recursive:true});
const tasks=[]; const chunk=2*1024*1024;
for(const a of binaries) for(let offset=0;offset<a.size;offset+=chunk) tasks.push({a,offset,end:Math.min(a.size-1,offset+chunk-1)});
let done=0;
async function worker(){while(tasks.length){const {a,offset,end}=tasks.shift();const out=`ranges/${a.name}.${offset}`; await run('curl',['--fail','--silent','--show-error','--location','--retry','2','--max-time','180','--range',`${offset}-${end}`,'--max-filesize',String(end-offset+1),'--output',out,a.browser_download_url]);if(fs.statSync(out).size!==end-offset+1)throw Error(`Range size mismatch: ${a.name} ${offset}`);done++;if(done%32===0)console.log(`Downloaded ${done} ranges`);}}
await Promise.all(Array.from({length:32},worker));
for(const a of binaries){const dest=fs.openSync(`${a.name}.assembled`,'w');const hash=crypto.createHash('sha256');for(let o=0;o<a.size;o+=chunk){const b=fs.readFileSync(`ranges/${a.name}.${o}`);fs.writeSync(dest,b);hash.update(b);}fs.closeSync(dest);if(`sha256:${hash.digest('hex')}`!==a.digest)throw Error(`Digest mismatch: ${a.name}`);fs.renameSync(`${a.name}.assembled`,a.name);console.log(`SUCCESS: ${a.name}: ${a.size} bytes match ${a.digest}`);}import fs from 'node:fs';import crypto from 'node:crypto';import {promisify} from 'node:util';import {execFile} from 'node:child_process';
const run=promisify(execFile);const {assets}=JSON.parse(fs.readFileSync('release-metadata.json'));await Promise.all(assets.filter(a=>a.size<2*1024*1024).map(async a=>{const valid=()=>fs.existsSync(a.name)&&`sha256:${crypto.createHash('sha256').update(fs.readFileSync(a.name)).digest('hex')}`===a.digest;if(!valid())await run('curl',['--fail','--silent','--show-error','--location','--max-time','180','--output',a.name,a.browser_download_url]);if(!valid())throw Error(`Bad asset: ${a.name}`);console.log(`SUCCESS: ${a.name} matches published digest`);}));import fs from'node:fs';import assert from'node:assert/strict';import crypto from'node:crypto';
const release=JSON.parse(fs.readFileSync('release-metadata.json'));const expected=JSON.parse(fs.readFileSync('expected-assets.json'));
assert.equal(release.immutable,true);assert.equal(release.draft,false);assert.deepEqual(release.assets.map(a=>a.name).sort(),expected);assert.equal(expected.length,29);
for(const a of release.assets){assert(fs.lstatSync(a.name).isFile());const bytes=fs.readFileSync(a.name);assert(bytes.length>0);assert.equal(bytes.length,a.size);assert.equal('sha256:'+crypto.createHash('sha256').update(bytes).digest('hex'),a.digest);}
const sums=fs.readFileSync('SHA256SUMS','utf8').trim().split('\n');assert.equal(sums.length,28);assert.deepEqual(sums.map(line=>line.match(/^[a-f0-9]{64} (.+)$/)[1]).sort(),expected.filter(a=>a!=='SHA256SUMS'));
console.log('SUCCESS: immutable published release contains exactly29 nonempty assets with published digests; SHA256SUMS covers exactly28 companions and binaries, excluding itself.'); |
|
Rehearsal
Full commands and explicit success output:
Any following documentation-only head retains this exact rehearsed implementation SHA. The PR remains draft; no reviewers requested. Maintainer production-release/settings and allowlist review gates remain. |
- Supply a fictional commit to the release-mode CLI test so a fallback to local git cannot pass, and assert release mode matches the supplied control - Anchor the publication gate assertion to the job-level if - Document the read timeout and job-bounded mutations
|
Phase 1 rehearsal Exact reviewed head Read-only diagnosis found that this SHA is referenced only by the new tag in the fork; the successful Phase 2 |
|
Phase 1 exact-head rehearsal Reviewed source |
|
Phase 1 exact-head rehearsal Reviewed source |
|
Phase 1 rehearsal Source: The lightweight |
|
Phase 1 main-merge rehearsal Source Prior |
|
Phase 1 main-merge rehearsal Source Prior |
Docs-only since rehearsed 68eef90 (v0.0.0-rehearsal.15).
Docs-only since rehearsed 68eef90. Append actual fresh review outcomes and clarify historical adapter investigation inputs.
dhruv8sh
left a comment
There was a problem hiding this comment.
Solid Phase 1 for #1235. The trust boundaries hold up: builds run read-only with no caches; signing is tag-gated and isolated; it never checks out or runs build inputs; and before signing it checks how many files arrived, that none are symlinks, and their digests. The publisher creates the release once, refuses to touch an existing tag or draft, uploads by release ID, checks every asset digest and that the release is immutable, and re-downloads everything to compare bytes.
On the Rust side, release_source fails closed: the release flag must be exactly 1, it rejects non-Unicode input, it requires a full 40-character lowercase commit, and it never falls back to local git in release mode. Tests cover each of these. BuildInfo is a small, const, well-documented type, and the Fastly build facts only appear in reuse mode, so single-mode startup diagnostics don't change.
CI: all checks pass. release and build / sign are skipped because they need a v* tag.
The inline comments below are non-blocking.
🌱 About 7k of the +10.8k lines are design docs, plans, a slice map and qualification transcripts. The implementation is reviewable on its own, but in future, putting the spec and qualification material in a separate PR, with the implementation PR stacked on top, would keep code review focused.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
The seven-binary Phase 1 pipeline separates build, signing and publication permissions and keeps later assurance work explicitly scoped out. Changes are requested for annotated-tag failure after immutable publication and SPDX creation timestamps rejected by strict consumers.
Reviewed head: 4940928e800fed00e4bddd8f3727fb52dcb2b296.
One of the three inline comments carries a one-click GitHub
suggestion. Use Commit suggestion or Add suggestion to batch to apply it. The other inline fixes require changes outside the suggested range or to additional fixtures.
Blocking
🔧 wrench
- Reject unsupported annotated tags before publishing, or handle tag-object identity — see inline at
.github/workflows/release.yml:227. - Normalize the SPDX creation timestamp — see inline at
scripts/release/phase1.mjs:77.
Non-blocking
🏕 camp site
- Exclude the new release control from legacy settings overlays — see inline at
crates/trusted-server-core/build.rs:163-164.
Cross-cutting / body-level findings
📌 GitHub CLI still downloads unrelated tags' bundle URLs
The publisher filters other tags locally, but the GitHub CLI implementation it invokes fetches every returned bundle_url before filtering release attestations by tag. An older RC and a later release can share a commit, so a slow or unavailable older bundle can still fail current-release verification after immutable publication. This affects both gh release verify at .github/workflows/release.yml:219 and gh release verify-asset at line 266; the isolation guarantee in docs/guide/release-verification.md:362 is stronger than the dependency provides.
A temporary HTTPS fixture with the real installed CLI confirmed that an older tag's 503 bundle URL was fetched four times and caused failure before current-attestation verification. Removing that unrelated record allowed execution to reach the current verification stage. This is a routing/availability reproduction, not a cryptographic-validity claim. The relevant 2.102.0 client implementation fetches bundles before the release command filters tags; installed 2.103.3 exhibits the same behavior.
Track an upstream filter-before-fetch fix and qualify a CLI that supplies that behavior. This is a non-blocking dependency follow-up, not a request to implement the deferred custom Trusted Server verifier. Meanwhile, narrow the guide's guarantee:
Other-tag records are ignored by the publisher's own availability check, but GitHub CLI still fetches all returned bundle URLs before filtering by tag. An unavailable older bundle can therefore fail the current release's read-only verification. Preserve the immutable release and follow the read-only recovery procedure.Apply manually — the behavioral fix belongs to GitHub CLI; dependency qualification and guide/test updates cannot be expressed as a single local suggestion.
Verification
- Exact SPDX suggestion verified alone: 19 producer tests; three fractional/whole-second cases and existing incomplete-fixture behavior; strict SPDX tools-java 2.0.7 validation of output from the actual helper; syntax, Prettier 3.8.1, shellcheck and diff checks. No post-verification patch drift.
- Focused publisher reproduction confirmed annotated-tag failure after one publication and 29 uploads. Read-only verification of a real signed annotated-tag release confirmed the distinct tag-object identity.
- Downloaded
.15Fastly sample passed immutable membership, both predicates, both signer identity forms, source/ref/digest pins and SPDX predicate equality. This is evidence for its recorded68eef902asource, not a current-head tag rehearsal. - Passing Rust, adapter and browser CI results were reused; their relevant inputs were unchanged by the release-helper suggestion. No current-head tag publication/deployment, administrator-settings audit or full local core settings test was performed.
- Scratch edits were discarded; the worktree is clean at the reviewed head. No commits or pushes were made.
CI Status
- release: SKIPPED
- build / sign: SKIPPED
- integration tests: PASS
- browser integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- CodeQL: PASS
- build / Build trusted-server-axum-aarch64-unknown-linux-gnu: PASS
- build / Build ts-x86_64-apple-darwin: PASS
- build / Build ts-aarch64-apple-darwin: PASS
- build / Build trusted-server-axum-x86_64-unknown-linux-gnu: PASS
- build / Build ts-x86_64-unknown-linux-gnu: PASS
- build / Build trusted-server-fastly.wasm: PASS
- build / Build trusted-server-axum-x86_64-unknown-linux-musl: PASS
- build / checks: PASS
- Analyze (rust): PASS
- Analyze (javascript-typescript): PASS
- Analyze (actions): PASS
- vitest: PASS
- cargo test (ts CLI, native) (macos-latest): PASS
- cargo test (ts CLI, native) (ubuntu-latest): PASS
- CLAUDE.md symlink guard: PASS
- format-typescript: PASS (required)
- cargo fmt: PASS (required)
- docs-proposal scripts: PASS
- cargo test (axum native): PASS
- format-docs: PASS (required)
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- prepare integration artifacts: PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo test (cross-adapter parity): PASS
- cargo test: PASS (required)
- HTML EOF runtime (Fastly): PASS
- Analyze (python): PASS
- Analyze (javascript-typescript): PASS
Merge current main to retain the required CI Node and domain guards. Reject unsupported tag objects before release mutations, normalize SPDX creation timestamps, and clean task-owned build files. Exclude release controls from legacy test settings and clarify operator verification limits.
|
Addressed the body-level GitHub CLI follow-up from the current review in 79b118858. The operator guide, compatibility record and publisher comment now distinguish the publisher's own other-tag filtering from GitHub CLI's bundle fetching. An unavailable older bundle can still fail current-release read-only verification; preserve the immutable release and use the documented recovery procedure. Filter-before-fetch behavior and qualification of a CLI that supplies it remain a dependency follow-up; no upstream behavioral fix or custom Trusted Server verifier is claimed. The same signed, GitHub-Verified update contains the inline corrections and approved merge of main. Required local checks passed, including the full 1232-test JS suite, adapter/CLI tests, Clippy, production Fastly WASM build, release-control regressions, 12 Chrome fixtures, Node/domain guards and strict SPDX timestamp interoperability probe. Initial sandbox-only keychain/listener failures were rerun successfully with host access. Linux-only NSS coverage remains hosted evidence. Exact-head upstream CI passed: 34 successful checks and two expected tag-only skips. Five independently verified threads are resolved. The annotated-tag and SPDX threads remain open: the authorized fork rehearsal .21 cannot qualify the release because GitHub cancelled the ARM macOS job without assigning a runner, citing hosted runner capacity. Five builds passed and Intel macOS was still running at readback; no signing/publication has run and no matching draft/release exists. The source/tag remain unchanged; no second attempt or settings change was made. Historical rehearsal .15 applies only to its recorded source, and does not qualify this updated release machinery. The PR is ready for review; fresh release qualification and maintainer settings/spec decisions remain outstanding. |
aram356
left a comment
There was a problem hiding this comment.
Summary
This adds the Phase 1 release build, signing and publication workflows, build-identity facts and the operator verification guide. Two blocking problems are in the release path itself. The publisher rejects the attestation record shape GitHub returns for other repositories, and that failure comes after the release is already immutable. The signing job's symlink guard never fails. Several docs (the spec, the plan and the compatibility notes) also describe Phase 1 in ways the code contradicts.
7 of the inline comments below carry a one-click GitHub
suggestion. Use Commit suggestion, or Add suggestion to batch for several at once, to apply them as commits on the PR branch. The remaining comments describe the fix in prose because it touches multiple files or lines outside the diff. Each suggestion was applied on its own and then all together on990509bc8. In every case the release suite (node --test scripts/release/phase1.test.mjs, 21/21), docs Prettier, root Markdown Prettier, actionlint (no new warnings) and shellcheck passed.
Blocking
🔧 wrench
- Publisher assumes an inline attestation
bundleand fails after immutable publication: see inline at.github/workflows/release.yml:241 - Signing input guard lets symlinks through (suggestion): see inline at
.github/workflows/release-build.yml:132 - Move the inline publisher into
scripts/release/: see inline at.github/workflows/release.yml:111 - Pin actions by version tag: see inline at
.github/workflows/release.yml:45 - Offline verification block can exit 0 without verifying (suggestion): see inline at
docs/guide/release-verification.md:149 - Spec labels contradicted MUSTs as Phase 1: see inline at
docs/superpowers/specs/2026-10-09-1235-release-attestation-design.md:123 - "Docs-only since
149754d35" is no longer true: see inline atdocs/superpowers/plans/binary-attestations-phase1-compatibility.md:39 - Plan checks off work that isn't here: see inline at
docs/superpowers/plans/2026-10-13-attested-release-implementation.md:200 - Unread copies of #1262 data and docs: see inline at
crates/trusted-server-cli/release-policy.json:1 - Release notes pre-fill the pins that need independent review: see inline at
.github/workflows/release.yml:166 - Source-audit tokenizer misses
br#/cr#raw strings and astral chars (suggestion): see inline atscripts/release/phase1.mjs:249 - Build-script audit rule skips
#[path]helpers: see inline atscripts/release/phase1.mjs:350 - Release-path test gaps (53 of 101 mutants survive): see inline at
scripts/release/phase1.test.mjs:154 - Unpinned
gh/Node in the publisher;verifyBundlenot retried: see inline at.github/workflows/release.yml:110 - Test-only release
cfgin the CLI build script: see inline atcrates/trusted-server-cli/build.rs:20 ts --versionchange is not marked Breaking (suggestion): see inline atCHANGELOG.md:16--signer-workflowis not a prefix match (suggestion): see inline atdocs/guide/release-verification.md:108- Prerequisites omit auth, minimum
ghandshasum(suggestion): see inline atdocs/guide/release-verification.md:70 - CLI link 404s from the release notes (suggestion): see inline at
docs/guide/release-verification.md:183 - Build-identity paragraph inaccuracies: see inline at
docs/guide/release-verification.md:301
❓ question
- Why run all seven release builds on PRs, with a hand-maintained path list?: see inline at
.github/workflows/release.yml:5 - Is Axum now a production deployment target?: see inline at
CHANGELOG.md:12
Cross-cutting / body-level findings
- 🔧 Smaller documentation fixes:
docs/superpowers/plans/binary-attestations-phase1-compatibility.md:94links/guide/release-verification.docs/superpowers/**is excluded from the VitePress build, so this only renders on GitHub, where the root-relative link 404s. Use../../guide/release-verification.md.docs/superpowers/specs/schemas/release-index-v1.schema.jsonhas no$schema. It usesminContains(:107, :119), which draft-07 evaluators, the common default, silently ignore. Declare 2020-12.- If the qualification reports stay (see the comment on
release-policy.json):scripts/release/qualification/2026-10-15-consumer-real-fixtures.mdembeds a real third-party project's repository, run IDs and signer identity (:30-52, :120), plus local tool paths (/opt/homebrew/bin/gh, :12, :106, :108). AGENTS.md asks for fictional data in docs. Also,TS_RELEASE_NATIVE_REPRODUCTION(2026-10-21-native-reproduction-schedule.md:10) breaks theTRUSTED_SERVER__environment-variable prefix. - The spec's Status line (:129) still says the spec, plans and implementation "share PR #1262" and that "no separate implementation PR is opened". #1262 is closed, and this PR is the implementation.
- The spec's Review log (:786) records a review of
20152315c, which predates the annotated-tag and SPDX-timestamp fixes in79b118858, and doesn't say who or what reviewed it.
CI Status
- cargo fmt: PASS (required)
- cargo test: PASS (required)
- format-docs: PASS (required)
- format-typescript: PASS (required)
- .github/dependabot.yml: PASS
- Analyze (actions): PASS
- Analyze (javascript-typescript): PASS
- Analyze (python): PASS
- Analyze (rust): PASS
- browser integration tests: PASS
- build / checks: PASS
- build / Build trusted-server-axum-aarch64-unknown-linux-gnu: PASS
- build / Build trusted-server-axum-x86_64-unknown-linux-gnu: PASS
- build / Build trusted-server-axum-x86_64-unknown-linux-musl: PASS
- build / Build trusted-server-fastly.wasm: PASS
- build / Build ts-aarch64-apple-darwin: PASS
- build / Build ts-x86_64-apple-darwin: PASS
- build / Build ts-x86_64-unknown-linux-gnu: PASS
- build / sign: SKIPPED (tag-only)
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo test (axum native): PASS
- cargo test (cross-adapter parity): PASS
- cargo test (ts CLI, native) (macos-latest): PASS
- cargo test (ts CLI, native) (ubuntu-latest): PASS
- CI Node pin guard: PASS
- CLAUDE.md symlink guard: PASS
- CodeQL: PASS
- docs-proposal scripts: PASS
- HTML EOF runtime (Fastly): PASS
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- prepare integration artifacts: PASS
- release: SKIPPED (tag-only)
- ts dev lint domains (changed lines): PASS
- vitest: PASS
| for (const record of page.attestations) { | ||
| if (!record || typeof record.initiator !== 'string') throw Error('Malformed release attestation record'); | ||
| if (record.initiator !== 'github') continue; | ||
| const envelope = record.bundle?.dsseEnvelope; |
There was a problem hiding this comment.
🔧 wrench: The publisher assumes an inline bundle that the attestations API doesn't guarantee, and the failure lands after the release is immutable.
GitHub's list attestations response documents repository_id, bundle_url and initiator for each record. An inline bundle isn't guaranteed, and real release attestations come back both ways. The fork rehearsals and EnjoyableWork/mcp-doctor return an inline bundle. cli/cli returns "bundle": null with only bundle_url (checked for v2.102.0 and v2.103.3):
$ gh api "repos/cli/cli/attestations/sha1:fc4b137cdef0a6bd28fd461b7cf9c84a5812a8cd?predicate_type=release" \
--jq '.attestations[] | {initiator, bundle_is_null: (.bundle == null), has_bundle_url: (.bundle_url != null)}'
{"bundle_is_null":true,"has_bundle_url":true,"initiator":"github"}With bundle: null, envelope is undefined here, so line 242 throws Malformed release attestation bundle. That runs after the PATCH at line 189 has published the release. The job fails on a release that is already immutable, and line 161 refuses any rerun. Line 228 has the same dependency, because it compares proof.attestation?.bundle against a null record.bundle.
Proposed fix (apply manually; it spans lines 224-255): accept a record whose bundle is null when bundle_url is set. gh release verify --format json already returns the fetched bundle as attestation.bundle and the decoded statement as verificationResult.statement. Run it for the candidate record, apply the existing envelope, statement and subject checks to that output, and compare against record.bundle only when the API returned one inline. Add a fixture mode that returns bundle: null plus bundle_url so the suite covers both shapes.
The guide's recovery check at docs/guide/release-verification.md:383-384 makes the same assumption (.bundle.dsseEnvelope.payload). Reading .verificationResult.statement.predicate.tag from gh release verify "$TAG" --repo "$REPO" --format json works for both shapes.
| set -euo pipefail | ||
| cd release-assets | ||
| test "$(find . -mindepth 1 -maxdepth 1 | wc -l)" -eq 2 | ||
| for file in "$ASSET" "$ASSET.spdx.json"; do test -f "$file" && test ! -L "$file" && test -s "$file"; done |
There was a problem hiding this comment.
🔧 wrench: This guard lets a symlinked or non-regular input through to signing.
set -e ignores a failure in any command of an && list except the last one. When test -f or test ! -L fails, the list stops but the step carries on, so test -s is the only test here that can fail the step. Reproduced on bash 3.2.57 and 5.2.15: with either $ASSET or $ASSET.spdx.json replaced by a symlink to a non-empty file, the original line exits 0 and the step continues to sha256sum and both attest steps. With || exit 1, both cases exit 1.
| for file in "$ASSET" "$ASSET.spdx.json"; do test -f "$file" && test ! -L "$file" && test -s "$file"; done | |
| for file in "$ASSET" "$ASSET.spdx.json"; do test -f "$file" && test ! -L "$file" && test -s "$file" || exit 1; done |
| run: | | ||
| set -euo pipefail | ||
| gh --version | ||
| node <<'NODE' |
There was a problem hiding this comment.
🔧 wrench: Move the ~180-line inline publisher into a script under scripts/release/.
This step embeds a full Node program in a YAML heredoc, so Prettier can't format it, and 17 of its lines run past 200 characters. The tests have to regex it out of the workflow by its exact 10-space indent (publicationScript() at phase1.test.mjs:140-153, plus the scrapes at :778 and :1070). Re-indenting the step breaks the scrape. The wrapper at line 290 also falls outside the scraped range, so deleting its process.exitCode = 1 leaves the suite green (I checked with a mutation run). The release-notes builder at lines 164-177 has the same problem.
Proposed shape (apply manually; it adds new files):
- Put the publication logic in
scripts/release/publish.mjsas an exported function, with a thin entry point that sets a non-zero exit code on failure. - In the
releasejob, sparse-check out only that script (and the notes template) atgithub.shawithpersist-credentials: false, then runnode scripts/release/publish.mjs. The script comes from the same commit as the workflow file, so the trust boundary doesn't change, and the job still never checks out build inputs. - Have the tests import the module instead of scraping YAML, and cover the entry point's exit code.
- Make the release-notes body a template file. The guide-block comparison at
phase1.test.mjs:378-392can then read it directly.
| permissions: | ||
| contents: write | ||
| steps: | ||
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 |
There was a problem hiding this comment.
🔧 wrench: Pin actions by version tag, as the rest of the repository does.
Every other workflow here references actions by major tag (actions/checkout@v4, actions/upload-artifact@v4, actions/setup-node@v4, and so on). These two workflows use commit SHAs instead, at mixed majors: checkout v5, setup-node v5, upload-artifact v6, download-artifact v7 and attest v4. Nothing will ever bump those SHAs either, because .github/dependabot.yml has no github-actions entry.
Use tags (actions/download-artifact@v7, actions/upload-artifact@v6, actions/checkout@v5, actions/setup-node@v5, actions/attest@v4) in both release.yml and release-build.yml. Update the /@[a-f0-9]{40} # v/ assertion at phase1.test.mjs:119-127 in the same change.
Optional, while these lines are being edited: the 14 download-artifact steps (lines 45-100) can collapse to two with pattern: release-* and pattern: signed-*. Each artifact then lands in a directory named after it, so the publisher's build/${name} and signed/${name} paths would need the release- and signed- prefixes.
| on: | ||
| push: | ||
| tags: ['v*'] | ||
| pull_request: |
There was a problem hiding this comment.
❓ question: Why does every PR touching these paths run all seven release builds, and what keeps this list complete?
The earlier thread on line 16 was resolved by keeping the full matrix for Phase 1, with a smaller PR matrix left as a follow-up. That thread didn't cover two things:
- The list is hand-maintained and already misses an input. The shipped
tsbinary compiles intrusted-server.example.toml:crates/trusted-server-cli/src/commands/config/init.rs:5-8pulls it in withinclude_str!. A PR that only edits that file doesn't trigger this workflow, and nothing keeps the list in sync as crates and embedded files change. If the goal is to catch release-build breakage before tagging, running onpushtomain(plus tags) does that without a path list. - Fork and Dependabot PRs. The caller grants
id-token: writeandattestations: writeto the reusable workflow (lines 33-36), and thesignjob requests them. On fork and Dependabot PRs, GitHub capsGITHUB_TOKENat read-only. Others have reported that a nested job requesting more than the caller can grant fails the whole run at startup (community discussion #121112, DataDog/orchestrion#866, posit-dev/images-shared#442). I couldn't confirm that for this exact shape, wheresignis skipped by itsif:. Has a fork PR run been observed? If it does fail, every external contribution touchingcrates/trusted-server-core/src/**gets a red check.
Proposal: keep the fast checks job (the Node suite) on pull_request, and run the seven builds on push to main and on tags. If the smaller PR matrix is still planned, please link the follow-up issue here.
|
|
||
| ### Changed | ||
|
|
||
| - Attested release binaries report `x-ts-version` as the full 40-character source commit, rather than the release tag. CLI output labels package/source/target/variant, and Axum supports runtime `EDGEZERO__ADAPTER__HOST` and primary `EDGEZERO__ADAPTER__PORT` (legacy `PORT` must agree when both are set) binding with a loopback default. |
There was a problem hiding this comment.
🔧 wrench: Mark the ts --version change as Breaking, and make this entry consistent with the next one.
This line says release binaries report the full commit "rather than the release tag". The existing Breaking entry just below says x-ts-version reports "the release tag, else the branch, else the first 6 characters". Both are true for different builds, but side by side they read as a contradiction.
The new ts --version format (ts package=… source=… target=… variant=default instead of ts <version>) breaks any script that parses the old output, so it belongs under Breaking:. Axum's new refusal to start on port 0 or an invalid value is also a behavior change worth listing.
| - Attested release binaries report `x-ts-version` as the full 40-character source commit, rather than the release tag. CLI output labels package/source/target/variant, and Axum supports runtime `EDGEZERO__ADAPTER__HOST` and primary `EDGEZERO__ADAPTER__PORT` (legacy `PORT` must agree when both are set) binding with a loopback default. | |
| - Attested release binaries report `x-ts-version` as the full 40-character source commit; deploy-pipeline builds keep the tag, branch or short-commit value described below. Axum supports runtime `EDGEZERO__ADAPTER__HOST` and primary `EDGEZERO__ADAPTER__PORT` (legacy `PORT` must agree when both are set) binding with a loopback default, and refuses to start on port `0` or an invalid value instead of falling back. | |
| - **Breaking:** `ts --version` now prints `ts package=<version> source=<commit> target=<triple> variant=default` instead of `ts <version>`. Update scripts that parse the old output. |
|
|
||
| <!-- release-verification:end --> | ||
|
|
||
| Provenance covers the binary and its SPDX document; the SBOM attestation covers the binary. The verified SBOM predicate must equal the shipped SPDX document. `--signer-workflow` matches a prefix; the separate `--cert-identity` check pins the exact reusable workflow at the tag. GitHub CLI makes those two identity options mutually exclusive. |
There was a problem hiding this comment.
🔧 wrench: --signer-workflow isn't a prefix match.
In GitHub CLI 2.102.0, --signer-workflow becomes an anchored pattern: ^, the quoted value, an optional @ ref, then $. It matches that workflow path at any ref, not any path that starts with it. Readers use this sentence to understand why the separate --cert-identity check is needed, so it should describe the actual behavior.
| Provenance covers the binary and its SPDX document; the SBOM attestation covers the binary. The verified SBOM predicate must equal the shipped SPDX document. `--signer-workflow` matches a prefix; the separate `--cert-identity` check pins the exact reusable workflow at the tag. GitHub CLI makes those two identity options mutually exclusive. | |
| Provenance covers the binary and its SPDX document; the SBOM attestation covers the binary. The verified SBOM predicate must equal the shipped SPDX document. `--signer-workflow` matches that workflow path at any ref; the separate `--cert-identity` check pins the exact reusable workflow at the tag. GitHub CLI makes those two identity options mutually exclusive. |
| Use an independently trusted GitHub CLI installation with `gh attestation verify`, | ||
| `gh release verify`, and `gh release verify-asset`. Use the GitHub CLI version | ||
| recorded in the release job log. The SBOM comparison below also uses `jq`. |
There was a problem hiding this comment.
🔧 wrench: The prerequisites leave out authentication, a minimum gh version and shasum.
gh release verifyandgh attestation verifycall the GitHub API. Withoutgh auth loginorGH_TOKEN,gh release verifystops with "please run: gh auth login".- "The GitHub CLI version recorded in the release job log" is whatever unpinned
ghthe runner image had (release.yml:110), so it isn't a stable requirement. The compatibility doc records 2.102.0 as the verified version. shasumisn't installed on slim Linux images, whilesha256sum --checkreads the same file format.
| Use an independently trusted GitHub CLI installation with `gh attestation verify`, | |
| `gh release verify`, and `gh release verify-asset`. Use the GitHub CLI version | |
| recorded in the release job log. The SBOM comparison below also uses `jq`. | |
| Use an independently trusted GitHub CLI 2.102.0 or later with `gh attestation verify`, | |
| `gh release verify`, and `gh release verify-asset`, authenticated with | |
| `gh auth login` or `GH_TOKEN`. The SBOM comparison below also uses `jq`, and the | |
| checksum steps use `shasum` (use `sha256sum --check` where `shasum` is absent). |
| the `gh attestation verify` and `gh release verify-asset` commands above, then | ||
| copy that verified file into a directory on their `PATH` as `ts` and make it | ||
| executable. Verify before first execution. Individual commands may still require | ||
| a source checkout and provider tools; see [CLI](/guide/cli). |
There was a problem hiding this comment.
🔧 wrench: This link 404s where the release notes send operators.
The release notes link to this guide's GitHub blob view (release.yml:165). There, the root-relative /guide/cli resolves to github.com/guide/cli. A relative ./cli.md link works both on GitHub and in VitePress.
| a source checkout and provider tools; see [CLI](/guide/cli). | |
| a source checkout and provider tools; see [CLI](./cli.md). |
|
|
||
| Release Fastly responses report `x-ts-version` as the full 40-character source | ||
| commit supplied by the release build. Development display fallbacks are not the | ||
| release identity. `ts --version` labels `package=<version> source=<commit> target=<triple> variant=default`. Release mode requires the full 40-character lowercase source commit; the package version is not the release tag. Attestation pins authenticate these inputs externally. |
There was a problem hiding this comment.
🔧 wrench: A few inaccuracies in the build-identity paragraphs:
- :299 says release Fastly responses report
x-ts-version, but Axum sends the header too (CHANGELOG: "sent by every adapter"). - :301 describes
ts --versionaspackage=… variant=default. The actual output starts withts(ts package=…), which matters to anyone matching it. - :301 ends mid-sentence ("Attestation pins authenticate the") and runs into :302, so the rendered text reads "authenticate the The executable's SHA-256 …".
- :303's bare
#161doesn't link anywhere in VitePress; use the issue URL. - :305 refers to "the spec's Phase 1 note" without a link.
- :312's "release PR's compatibility audit" links to this PR rather than to
docs/superpowers/plans/binary-attestations-phase1-compatibility.md, and the PR won't be the obvious place to look after merge.
Summary
Changes
55 files, +11,037/−58 lines against main after the approved base update. The retained design, plans, policies and candidate reports describe future phases as well as this implementation; their presence does not claim those phases are implemented or qualified.
All changed files and their purpose
.github/workflows/format.yml.github/workflows/release-build.yml.github/workflows/release.yml.github/workflows/test.ymlAGENTS.mdCHANGELOG.mdcrates/trusted-server-adapter-axum/src/main.rscrates/trusted-server-adapter-axum/tests/startup.rscrates/trusted-server-adapter-fastly/build.rscrates/trusted-server-adapter-fastly/src/main.rscrates/trusted-server-cli/build.rscrates/trusted-server-cli/release-policy.jsoncrates/trusted-server-cli/src/commands/dev/lint/domains.rscrates/trusted-server-cli/src/lib.rscrates/trusted-server-cli/src/run.rscrates/trusted-server-cli/tests/release_build_facts.rscrates/trusted-server-core/build.rscrates/trusted-server-core/build_support/git_version.rscrates/trusted-server-core/src/build_info.rscrates/trusted-server-core/src/constants.rscrates/trusted-server-core/src/lib.rscrates/trusted-server-core/src/settings.rscrates/trusted-server-core/tests/git_version_resolve.rsdocs/.vitepress/config.mtsdocs/guide/cli.mddocs/guide/release-verification.mddocs/superpowers/plans/2026-10-13-attested-release-implementation.mddocs/superpowers/plans/2026-10-13-cli-packaging-attestation-integration.mddocs/superpowers/plans/2026-10-13-fastly-prebuilt-edgezero-integration.mddocs/superpowers/plans/binary-attestations-phase1-compatibility.mddocs/superpowers/plans/binary-attestations-slice-map.jsondocs/superpowers/specs/2026-01-15-attestation-design.mddocs/superpowers/specs/2026-10-09-1235-release-attestation-design.mddocs/superpowers/specs/2026-10-12-1235-release-attestation-feasibility.mddocs/superpowers/specs/2026-10-14-release-tooling-rust-migration.mddocs/superpowers/specs/schemas/release-index-v1.schema.jsonscripts/release/js-composition.mdscripts/release/phase1-build.shscripts/release/phase1.mjsscripts/release/phase1.test.mjsscripts/release/qualification/2026-10-15-consumer-real-fixtures.mdscripts/release/qualification/2026-10-15-js-two-builds.mdscripts/release/qualification/2026-10-16-confined-cargo-candidate.mdscripts/release/qualification/2026-10-16-fastly-canonical-reproduction-equality.mdscripts/release/qualification/2026-10-16-fastly-runtime-gate.mdscripts/release/qualification/2026-10-16-linux-cli-candidate.mdscripts/release/qualification/2026-10-16-producer-normalized-manifests.mdscripts/release/qualification/2026-10-19-apple-sdk-inputs.mdscripts/release/qualification/2026-10-20-apple-cli-candidates.mdscripts/release/qualification/2026-10-20-linux-cli-gnu-follow-up.mdscripts/release/qualification/2026-10-21-candidate-release-index.mdscripts/release/qualification/2026-10-21-four-asset-candidate-join.mdscripts/release/qualification/2026-10-21-native-reproduction-schedule.mdscripts/release/release-cohort.jsonscripts/release/release-tools.jsonCloses
Part of #1235. Keep the issue open: spec/merge approval and upstream immutable-release/tag-ruleset settings remain maintainer obligations. This partial delivery intentionally does not use an auto-closing keyword.
Spec
Phased spec · Implementation plan · Compatibility audit and rehearsal history.
The carried design originated in #1262; its review history and disposition appendix remain linked from the spec. Spec and implementation are combined here by the recorded delivery agreement; maintainer approval is still required. The required Review log now records two fresh independent AI source-review rounds, both without blocking Phase 1 findings. The historical-adapter advisory was addressed with an append-only baseline clarification; original spec bytes remain intact.
How it works
v*tag. The release helper checks the supplied full source SHA, requires fresh npm/JS output, rejects skip/prebuilt/test controls and scans for planted operator values. It produces Fastly wasm, three CLI binaries and three Axum binaries, each with a conservative Cargo/npm SPDX inventory tied to the final executable hash.release-build.ymlreusable workflow owns signing. Signing runs only for tag pushes withRELEASE_PUBLICATION_ENABLED=true; these jobs do not check out source, compile, or execute the binaries. Provenance covers the executable and its SPDX document. The separate SBOM attestation uses the executable as its subject and the SPDX document as its predicate. Both real action-produced bundles are retained.release.ymlchecks exact build/signer file sets and signed-input digests. It refuses existing matching releases/drafts, creates one draft and retains its release ID through upload/publication. Mutations are single-shot. The distribution contains 7 binaries + 7 SPDX documents + 14 bundles + SHA256SUMS = 29 assets; the checksum file covers the other 28 files. The publisher verifies immutability, release identity, cryptographic membership and a fresh download of every asset against the validated handoff bytes. Only bounded transient read failures may retry.release-build.ymlat the approved tag. Offline bundle verification uses independently obtained roots and proves build evidence; it does not establish current release membership or authorize deployment. The Fastly route packages the verified wasm without rebuilding it.Runtime identity stays distinct from release metadata:
ts --versionlabels package/source/target/variant; Fastly reports the full source SHA inx-ts-version. Axum host/port choices remain runtime inputs, preserve the loopback default, and reject invalid or conflicting values with a visible refusal even when logging is off.Review corrections — 2026-10-29
Current head:
79b1188587cfadc0d193544a3a5c0008666727df. This signed merge includes main5b4e7e872eb30300cb7db3d973773884ea46bc87and the scoped review corrections.Final-tree local verification:
all required Rust/JS/docs/script/Node/domain gates passed; all eight Clippy aliases, adapter/parity/CLI tests, build-digest checks, builds, inherited release controls and 12 Chrome fixtures passed. The first concurrent JS run timed out in unchanged Prebid code; the final full single-worker run passed 1232 tests with original timeouts. Sandbox-blocked Viceroy launches are recorded separately from theirsuccessful host-accessretries. Current-head hosted evidence: 34 checks passed, with two expected tag-only signing/publication skips. The authorized fresh fork rehearsal .21 is blocked: GitHub cancelled its ARM macOS job with no runner assigned, citing hosted macOS ARM capacity. Five builds passed; Intel macOS was still active at readback. No signing/publication has run, and no matching draft/release exists. Tag.21remains unchanged at this exact source; no new attempt was made. Five independent review threads are resolved; the annotated-tag and SPDX threads remain open for signed-release qualification.The PR is ready for review. Release qualification remains pending; no successful new tag/release publication, deployment, administrator-setting audit or release rehearsal is claimed.
.15remains historical evidence for68eef902a8bddd9024676cc3806fa5377ed67fee; it does not qualify this updated source or the corrected SPDX documents. Maintainer settings/spec decisions and fresh release qualification remain separate obligations.Test plan
Verification summary and evidence
Historical pre-review head:
4940928e800fed00e4bddd8f3727fb52dcb2b296, the later web merge of main054c7b777after review-log commitc9a6117dd. Its tree exactly matches the already tested synthetic merge52978c5b5146f0c008108a9b0bfb2bf2c5de2bb2; fresh CI on this new head also passed. The spec’s two fresh review rounds and their append-only clarification remain intact. Rehearsed implementation:68eef902a8bddd9024676cc3806fa5377ed67feein.15. The later main merge includes upstream application changes, with unchanged release machinery, and is not described as docs-only since that rehearsal. No new tag-rehearsal result is claimed. PR release-matrix binaries identify their tested merge ref and remain distinct from the recorded tag assets..15passed all 16 jobs at68eef902a; seven builds and seven signers produced an immutable prerelease, not Latest.425726260currently reportsimmutable=true,draft=false,prerelease=true./healthsuccess/invalid-port refusal passed in the recorded rehearsal. These are startup checks, not full platform qualification.Together, these demonstrate the intended Phase 1 path: reviewed source produces runnable bytes; real tag-only signers attest those bytes and their inventory; the publisher releases the exact immutable distribution; independent verification authenticates the downloaded assets. They do not establish reproducibility, complete native dependency coverage, SLSA Build Level 3 or production deployment.
Completed checks
These checked items refer to the linked hosted evidence, not a claim that every suite was rerun locally.
cargo test-fastly, reuse tests,cargo test-axum, Cloudflare, Spin, native CLI and cross-adapter parity checks.cargo fmt --all -- --check.Historical evidence:
.12success applies toab6e54936only..11draft-create denial and.14hosted ARM64 runner-capacity failure remain failures for those attempts;.15supplies the successful current-implementation rehearsal. No tag or immutable release was reused or overwritten.Exact signer verification and issue acceptance
Use independently approved
REPO,TAGand fullCOMMITvalues from the operator guide. For each asset, run both predicates (https://slsa.dev/provenance/v1andhttps://spdx.dev/Document/v2.3) with each mutually exclusive identity option separately:The guide includes complete loops, release membership checks, offline roots and SPDX comparisons. The issue's original
release.ymlsigner example is superseded by the reusablerelease-build.ymlidentity.artifact-metadata: writeis intentionally unnecessary for file attestations..15; upstream settings remain D7.v*tags configured by maintainers.Checklist
unwrap()introduced; release/config error paths are explicit.log; Axum deliberately writes startup refusals to stderr soRUST_LOG=offcannot hide them. This is a disclosed exception to the general logging convention.Maintainer decisions and Phase 1 limits
The two-round spec review-log requirement is complete. The review-log fix and subsequent main-merge head both passed CI; this PR is ready for human review. Spec/code approval remains the purpose of human review. Before enabling production publication, maintainers must enable immutable releases and the restricted
v*tag ruleset, then explicitly setRELEASE_PUBLICATION_ENABLED=trueand authorize the first production tag. RC-branch tag eligibility remains a maintainer decision. Upstream production publication and provider deployment are not claimed by the fork rehearsal.Reference hosts requiring review:
actions.github.io,slsa.dev,spdx.dev,cli.github.com. Per-line suppressions:cyclonedx.org(intermediate-format reference, removed from the allowlist),api.github.com,uploads.github.com.Phase 1 SBOMs are conservative build dependency inventories, not complete retained-code/native composition or vulnerability scans. Producing-job transfer binding, checked policy consumption, source-branch ancestry enforcement, reproducibility, custom verification/indexes, integrated EdgeZero recovery and further Axum production hardening remain deferred. A compromised build dependency could replace another job's artifact; exact handoff checks do not close that later transfer-binding boundary. Historical candidate reports and frozen policies grant no present release qualification.