Repository navigation
Publish attested release binaries with provenance and SBOMs - #1435
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. |
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
The seven-binary Phase 1 pipeline separates read-only builds, isolated signing and immutable publication, with conservative SPDX inventories and operator verification instructions. Changes are requested for an availability-deadline bug; two documentation corrections are non-blocking.
Reviewed head: 029f2046784ab1732eaa571a1374fcd3f5691f72.
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 comments describe manual documentation corrections across multiple passages or files.
Blocking
🔧 wrench
- Enforce the advertised availability deadline — see inline at
scripts/release/publish.mjs:391-436.
Non-blocking
🏕 camp site
- Correct the current-head rehearsal attribution — see inline at
docs/superpowers/plans/binary-attestations-phase1-compatibility.md:349-351. - Distinguish Ctrl-C draining from missing SIGTERM handling — see inline at
docs/guide/release-verification.md:43-44.
Verification
- Read all 36 changed files and the removed code, with runtime, build-identity and documentation cross-checks.
- Applied the exact approved suggestion alone in the isolated reviewer worktree. The clock-injected publisher reproduction accepted a proof at 73.5 seconds before the suggestion and rejects at the 60-second deadline afterward.
- All 28 release-producer tests passed; JavaScript syntax, Prettier, shellcheck and JS formatting passed. JS suite: 73 files passed, 1,809 tests passed, 1 skipped, no type errors.
- The complete patch was byte-identical before and after verification. Scratch changes were discarded; the reviewer worktree is clean at the reviewed head. No commits or pushes were made.
- Passing hosted Rust, adapter and integration results were reused. No live tag publication, deployment or administrator-settings audit was performed; the deadline reproduction uses mocked external operations and an injected clock.
CI Status
- cargo test (axum native): PASS
- integration tests (Fastly EC lifecycle): PASS
- browser integration tests: PASS
- integration tests: PASS
- trace runtime and browser acceptance: PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- CodeQL: PASS
- cargo test (cross-adapter parity): PASS
- cargo test: PASS (required)
- worker-deadlines (cloudflare): PASS
- worker-deadlines (spin): PASS
- cargo fmt: PASS (required)
- CI Node pin guard: PASS
- Analyze (javascript-typescript): PASS
- cargo test (ts CLI, native) (ubuntu-latest): PASS
- Analyze (rust): PASS
- Analyze (actions): PASS
- format-typescript: PASS (required)
- Analyze (python): PASS
- vitest: PASS
- cargo test (ts CLI, native) (macos-latest): PASS
- format-docs: PASS (required)
- docs-proposal scripts: PASS
- prepare integration artifacts: PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- HTML EOF runtime (Fastly): PASS
- ts dev lint domains (changed lines): PASS
- CLAUDE.md symlink guard: PASS
- Analyze (javascript-typescript): PASS
The release-build/signing/publication workflow does not run on pull requests; the passing PR checks do not establish a current-head release rehearsal.
The availability loop checked its 60-second deadline only at the start of each round, while the attestation list read and `gh release verify` proof fetch retried under the 180-second post-publish window. A round could therefore retry and accept a proof after the advertised deadline. Both now use a retrier and command timeouts bound to the availability deadline. Also correct the rehearsal attribution: fork rehearsal .22 covers source 5f0139b only, not the later main merge or this publisher change, and distinguish Axum's Ctrl-C request draining from missing SIGTERM handling.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
Publishes seven release binaries with build provenance, SPDX dependency inventories, retained Sigstore bundles and immutable-release verification. No verified blocking or non-blocking findings in the Phase 1 changes.
Verification scope
Reviewed all 36 changed files and removed code, including correctness, security, WASM compatibility, architecture, maintainability and repository conventions.
- The release producer suite passed 28/28 tests in CI for the reviewed head, including handoff corruption, partial uploads, tag validation and retry deadlines. Shellcheck also passed.
- Locally,
node scripts/release/phase1.mjs auditpassed with repository-pinned Node 24.20.0, andgit diff --check origin/main...refs/review/pr-1435/headpassed. - No current-head release rehearsal, live deployment or upstream immutability/tag-settings audit was performed. The latest documented successful fork rehearsal,
.22, covers5f0139b167cd0619cccd9563e8aadca4436281ae, not the reviewed head. This approval is a code-review result, not release qualification or deployment authorization.
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 (axum native): PASS
- CI Node pin guard: PASS
- CLAUDE.md symlink guard: PASS
- cargo test (ts CLI, native) (ubuntu-latest): PASS
- worker-deadlines (cloudflare): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo test: PASS (required)
- ts dev lint domains (changed lines): PASS
- worker-deadlines (spin): PASS
- cargo test (ts CLI, native) (macos-latest): PASS
- Analyze (javascript-typescript): PASS
- Analyze (python): PASS
- format-typescript: PASS (required)
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- Analyze (rust): PASS
- format-docs: PASS (required)
- prepare integration artifacts: PASS
- docs-proposal scripts: PASS
- cargo test (cross-adapter parity): PASS
- Analyze (javascript-typescript): PASS
- cargo fmt: PASS (required)
- HTML EOF runtime (Fastly): PASS
- vitest: PASS
- Analyze (actions): PASS
aram356
left a comment
There was a problem hiding this comment.
Summary
Second review, at ba54b274f. The round-1 blockers are fixed. The publisher now accepts bundle_url-only release-attestation records, and the symlink guard fails closed on bash 3.2. The gh checksum matches cli/cli's digest, actions use version tags, and the PR trigger and the #1262 files are gone. 69b74e2ae also states rehearsal coverage per commit, which settles the coverage claims in the compatibility record. The remaining blockers are:
- an incomplete fix to the build-script audit;
- test gaps that let several release checks be deleted without a failing test, including half of the new availability-deadline bound;
- docs that describe build-script and tagging behaviour that no longer holds.
4 of the 11 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 several files or lines outside the diff.
Blocking
🔧 wrench
- Build helpers in nested modules escape the build-script rule: see inline at
scripts/release/phase1.mjs:318 - Several producer and publisher checks can be deleted with no failing test: see inline at
scripts/release/phase1.test.mjs:1450 - The availability-deadline test pins only the proof fetch: see inline at
scripts/release/phase1.test.mjs:724 - Spec, plan and record say the CLI and Fastly build scripts validate release inputs: see inline at
docs/superpowers/specs/2026-10-09-1235-release-attestation-design.md:104 - Maintainer checklist: tagging precondition missing, branch-check step impossible: see inline at
docs/guide/release-verification.md:455
Non-blocking
♻️ refactor / 🤔 thinking / ⛏ nitpick
- ♻️ Skip the build upload on
mainpushes: see inline at.github/workflows/release-build.yml:106 - 🤔 Annotated or branchless tags are refused only after building and signing: see inline at
.github/workflows/release-build.yml:23 - 🤔 Linux release binaries carry debug info (132-282 MB): see inline at
scripts/release/phase1-build.sh:94 - ♻️ Release suite runs inside the "docs-proposal scripts" job: see inline at
.github/workflows/format.yml:228 - ♻️ Deadline errors drop the failure that caused the retry: see inline at
scripts/release/publish.mjs:152 - ⛏ Stale allowlist comment: see inline at
crates/trusted-server-cli/src/commands/dev/lint/domains.rs:97
Cross-cutting / body-level findings
- ⛏ Smaller doc fixes
binary-attestations-phase1-compatibility.md:383says every other non-200 status "fails immediately" and that current-tag verification is "single-shot". The publisher retries transient list reads and proof fetches within a check, and re-runs a failedgh release verifyat the next check, as the guide says atrelease-verification.md:384.2026-10-13-attested-release-implementation.md:110says the source PR "remains frozen and open"; #1262 is closed (the plan says so at:7).2026-10-13-attested-release-implementation.md:159and:185describe #1262'strusted-server-releasework as "implemented" in the present tense; none of it is in this PR.release-verification.md:363compares downloads "against the signedSHA256SUMS". No attestation coversSHA256SUMSuntil GitHub's release attestation after publication; "the validatedSHA256SUMS" is accurate.release-verification.md:366lists the reads that may retry as release/asset metadata GETs andverify-assetcalls. It leaves out the release-attestation list read, failed proof fetches and transport download failures (:384,:386).binary-attestations-phase1-compatibility.md:166puts current behaviour ("No PR builds;mainpushes and tags queue") in the "#1435 value at audit" column..11(ab6e54936, draft-create HTTP 403, nothing created) has no entry of its own. It appears only in passing atbinary-attestations-phase1-compatibility.md:309; an entry beside.12would complete the record.binary-attestations-phase1-compatibility.md:335says the publisher "retriesverifyBundle"; no such function exists inpublish.mjs.binary-attestations-slice-map.json:678still keys.github/workflows/release.yml inline publisher; the publisher is nowscripts/release/publish.mjs.- Spec
:475says the operator guide verifies with GitHub CLI 2.102.0. The guide says "2.102.0 or later" atrelease-verification.md:72.
- ⛏ PR description is stale. It still lists the deleted #1262 files (for example
release-policy.jsonand thequalification/records) and says a PR runs seven hosted builds ("Keep all seven PR builds"). It also describes the publisher as inline inrelease.yml, has no row forscripts/release/publish.mjs, and cites commits from beforec827eb222.
CI Status
- Analyze (actions): PASS
- Analyze (javascript-typescript): PASS
- Analyze (python): PASS
- Analyze (rust): PASS
- browser integration tests: PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo fmt: PASS
- cargo test: 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
- format-docs: PASS
- format-typescript: PASS
- HTML EOF runtime (Fastly): PASS
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- prepare integration artifacts: PASS
- trace runtime and browser acceptance: PASS
- ts dev lint domains (changed lines): PASS
- vitest: PASS
- worker-deadlines (cloudflare): PASS
- worker-deadlines (spin): PASS
Find build-script helpers that a non-mod.rs file loads from its own directory, refuse custom package.build scripts and #[path] modules outside the audited sources, and pin the producer and publisher checks the review showed could be deleted without a failing test, including the availability deadline bounds on list reads and proof fetches. Keep the cause of a retry the availability deadline cut short and name that window in the error. Refuse annotated tags before building, upload build artifacts only for tags, run the release suite in its own job and strip debug info from native release binaries. Correct the spec, plan, record and guide where they described CLI and Fastly build-script validation, tagging and retries that do not hold.
|
Review-body doc fixes from the second review, in
|
aram356
left a comment
There was a problem hiding this comment.
Summary
Third review, at 54d10bb01. Every round-2 inline finding is fixed, and I re-verified each one by running it:
- the nested-module plant is rejected;
- the 22 earlier producer mutants, the publisher block deletions and the deadline mutants all fail the suite;
- the availability label and the
causereach the log; - the tag-only upload and the annotated-tag check are in place;
- the release suite runs in its own job.
The remaining blockers are:
- two
phase1-build.shguards that never fire on the macOS jobs; - release checks that can still be removed without a failing test, including the create body's
draft: true; - a compatibility record that says this head's workflows are unchanged, though no rehearsal covers its changes;
- the PR description, which is still stale from round 2;
- one question on the SBOM root version.
The larger point of this round is scope. Phase 2 replaces phase1-build.sh and phase1.mjs (slices 2.5 and 2.6) and publish.mjs (slice 2.8), so effort spent hardening them now is thrown away. Several comments below propose cutting code instead of fixing it. The largest cut is the documentation (see Cross-cutting).
17 of the 27 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. Each was verified on its own and all together: the suite, shellcheck, actionlint, prettier,cargo fmt, clippy, and the affected Rust tests all pass. The remaining comments describe the fix in prose, because it touches several files or lines outside the diff.
Blocking
🔧 wrench
- Two build-script guards never stop the macOS jobs (bash 3.2): see inline at
scripts/release/phase1-build.sh:59 - Four producer checks can still be removed with no failing test: see inline at
scripts/release/phase1.test.mjs:1294 - Create and publish request bodies are not pinned (
draft: truecan be deleted): see inline atscripts/release/phase1.test.mjs:690 - Compatibility record says this head's workflows are unchanged; no rehearsal covers its changes: see inline at
docs/superpowers/plans/binary-attestations-phase1-compatibility.md:361 - PR description is still stale: see Cross-cutting below
❓ question
- Is the SBOM root version meant to be the release tag?: see inline at
scripts/release/phase1.mjs:477
Non-blocking
♻️ refactor / 🤔 thinking / ⛏ nitpick
- ♻️ Drop the post-publish checks that repeat
checkProof: see inline atscripts/release/publish.mjs:529 - ♻️ Drop the Rust source audit, or run it on PRs: see inline at
scripts/release/phase1-build.sh:23 - ♻️ Delete
release-cohort.json: see inline atscripts/release/release-cohort.json:1 - ♻️ No Phase 1 artifact emits the Fastly build-facts line: see inline at
crates/trusted-server-adapter-fastly/src/main.rs:129 - ♻️ Consider leaving the Axum assets out of Phase 1: see inline at
.github/workflows/release-build.yml:78 - 🤔 Signing bundles are not verified before the release becomes immutable: see inline at
scripts/release/publish.mjs:210 - ♻️ Last round's workflow changes have no tests: see inline at
scripts/release/phase1.test.mjs:1460 - ♻️ The three-minute window and the loop's deadline path have no tests: see inline at
scripts/release/publish.mjs:336 - ♻️ No CI step exercises the release-mode refusal: see inline at
.github/workflows/test.yml:373 - ♻️ Axum startup test cannot tell the variable names apart: see inline at
crates/trusted-server-adapter-axum/tests/startup.rs:21 - ⛏ No over-length all-hex source case: see inline at
crates/trusted-server-core/tests/git_version_resolve.rs:163 - ⛏ Non-Unicode development override skips the warning: see inline at
crates/trusted-server-core/build.rs:170 - ⛏ CHANGELOG bullet mixes two changes: see inline at
CHANGELOG.md:16 - ⛏ No release ID is logged when the created-identity check fails: see inline at
scripts/release/publish.mjs:307 - ⛏ Annotated-tag refusal prints nothing: see inline at
.github/workflows/release-build.yml:39 - ⛏ New SC2086 in the
release scriptsjob: see inline at.github/workflows/format.yml:241 - ⛏ Publisher step pins the
ghversion but not Node's: see inline at.github/workflows/release.yml:125 - ⛏ Tagging steps: cancelled waiting runs, and the rerun conflicts with the recovery rule: see inline at
docs/guide/release-verification.md:464 - ⛏ Guide misstates the timeouts, read count and terminal errors: see inline at
docs/guide/release-verification.md:386 - ⛏ Immutable-releases endpoint does not return 404 when disabled: see inline at
docs/guide/release-verification.md:482 - ⛏
export VAR="$(jq -er ...)"hides a missing value: see inline atdocs/guide/release-verification.md:219 - ⛏
phase1.jsonis written beside every member, but only one path is guarded: see inline atscripts/release/phase1-build.sh:47
Cross-cutting / body-level findings
- 🔧 The PR description is still stale.
- It says 55 files and +11,037/−58; the diff is 36 files and +9,093/−57.
- The file table has 21 rows for files that are no longer in the diff:
release-policy.json,release-tools.json, both integration plans, the feasibility and migration specs, the index schema,js-composition.mdand 13qualification/reports. - It has no rows for
scripts/release/publish.mjsorscripts/release/release-notes.md. - The
adapter-fastly/build.rsandcli/build.rsrows say they validate release inputs; only core's does. - It still describes an "inline publisher", says "a PR exercises seven hosted builds" and "Keep all seven PR builds", and names
.15as the latest rehearsal (it is.22).
- ♻️ Ship a Phase 1 spec plus the operator guide, and move the destination documents to the first Phase 2 PR.
- The implementation plan (1,636 lines) is about 93% Phase 2+ "historical destination" tasks. The slice map (682 lines) is about 95% #1262 path ownership. The compatibility record (400 lines) is history already recorded in PR comments. Spec lines 157-795 are the retained destination design.
- A Phase 1 spec of about 250 lines, linking frozen
122b786for later phases, keeps every Phase 1 guarantee. It removes about 3,300 lines (36% of the PR) and follows CONTRIBUTING's one-spec-per-phase guidance. - The
actions.github.ioallowlist entry would then be unused. - Each round so far has found documents that contradict the code, and this round's record finding is another; almost all of them came from these files.
- ♻️ Trim the operator guide.
- Keep one identity check per predicate. #1235 names
--signer-workflow, and with the digests and source ref pinned,--cert-identitypins nothing more. Dropping it halves the operator'sghcalls (28 to 14). - Link the release notes to the guide instead of copying its block, which also removes the equality test.
- Drop the reviewer-facing material at
release-verification.md:489-506; lines 500-506 are a verbatim copy of compat:373-379.
- Keep one identity check per predicate. #1235 names
- ⛏ Smaller doc fixes, if those files stay in this PR
- compat:
:91says "all three policies", but only the cohort file is carried;:133calls strict tag validation "additive later work", butpublish.mjs:22-23,:276-278andrelease-build.yml:23-39implement it;:167says main-push runs are "never cancelled", but a newer push cancels a waiting run;:385says "at most five release-attestation GETs" (it is up to 15).
- slice map
:10: still says the publisher is "still keyed by that name below", but:678is now keyedscripts/release/publish.mjs. - spec
:150: says only the publisher rejects annotated tags; the buildchecksjob now does too. - plan:
:72is a self-link copied from the spec;:152and:155say #1262 work "are implemented" and "pass ordinary CI";:67says "Hyphenated tags" where the spec and record say SemVer prerelease tags.
- Drifted duplicates: the "Phase 1 review notes" copied into the spec, plan and record have drifted. The spec has the strict-SemVer and release-notes sentences; the other two do not.
- guide
:327: calls the input audit "current". It was made against main atf9281917a, and names EdgeZero v0.0.10, while the lock is now at v0.0.11. - spec
:786: theallow-domain: cli.github.commarker is redundant now that the host is allowlisted, andts dev lint domainswarns about it.
- compat:
- ⛏ Producer notes
- The merged CycloneDX repeats two bom-refs (
phase1.mjs:474-476keeps the root'scomponents, and:495copies them again).components: undefinedin the root spread fixes it. - The ELF check verifies only the magic bytes, so an aarch64 ELF is accepted for an x86_64 target.
- The sentinel scan sees verbatim ASCII only.
$tool_root/cargo-metadata.jsonis written but never read.- The published
SHA256SUMSformat is unpinned: changing the two spaces to one atpublish.mjs:238passes the suite, and macOSshasum -a 256 --check, the command the guide uses, rejects one-space lines.
- The merged CycloneDX repeats two bom-refs (
- 📝 Notes
release scriptsis not a required check; themainruleset requires four other checks.- The repository has no tag ruleset yet, and immutable releases are not enabled (the endpoint returns
enabled: false). Until both are set, any writer can push av*tag whose ownrelease.ymlruns. The guide's enablement checklist covers this.
CI Status
- Analyze (actions): PASS
- Analyze (javascript-typescript): PASS
- Analyze (python): PASS
- Analyze (rust): PASS
- browser integration tests: PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo fmt: PASS
- cargo test: 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
- format-docs: PASS
- format-typescript: PASS
- HTML EOF runtime (Fastly): PASS
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- prepare integration artifacts: PASS
- release scripts: PASS
- trace runtime and browser acceptance: PASS
- ts dev lint domains (changed lines): PASS
- vitest: PASS
- worker-deadlines (cloudflare): PASS
- worker-deadlines (spin): PASS
Narrow Phase 1 to the Fastly wasm and the three ts CLI binaries (17 release assets). Drop the Axum assets, the Rust source audit, the post-publish re-verification, the descriptive cohort file and the Fastly build-facts line. Replace the destination spec, plan, slice map and compatibility record with a Phase 1 spec that links the frozen design, and trim the operator guide to one signer check per predicate. The publisher now verifies every signing bundle before creating a draft, logs the release ID before checking it, and keeps the SBOM root at Cargo's package version. Build-script guards exit explicitly under bash 3.2, every member's phase1.json is refused and cleaned, and the Linux format check requires an x86-64 ELF. Tests pin the create and publish bodies, the producer step order and container passthrough, the workflow permissions and gates, the post-publish deadlines and the checksum format. CI now checks that a release build refuses a branch source.
|
Round-3 body-level items, in
|
Rehearsal v0.0.0-rehearsal.24 failed before creating anything: the SBOM verification result embeds the whole SPDX document, which exceeds Node's default 1 MiB execFileSync buffer, so gh was killed with ENOBUFS. Allow up to 256 MiB, and add a publisher mode that sends a 2 MiB result through a real child process with the publisher's options. Also test the CLI version smoke check, the build checks' annotated-tag comparison and the publisher step's pinned-Node assertion, which could previously be removed without a failing test.
Cover three checks that could be removed without a failing test: API calls closing their connection after a gh command leaves pooled sockets idle, refusing a published asset whose name is not in the expected set, and refusing an SPDX package that stands in for the described root. Reword the bash 3.2 comment to state only what the guard needs.
In fork rehearsals, draft creation returned HTTP 403 when the tagged commit was reachable only through its tag, after every build and signing job had run and written public attestations. The checks job now fetches full history and refuses such a tag before anything is built or signed. Which branches may be released stays a maintainer decision.
aram356
left a comment
There was a problem hiding this comment.
Summary
Fourth review, at 861ed761c. The narrowing worked: the PR went from +9,093 to +4,901 lines, and I re-verified every round-3 finding by running it.
- Producer: the bash 3.2 guards stop
/bin/bash3.2, and the producer checks, memberphase1.jsonhandling and SBOM root version all fail the suite when reverted. - Publisher: request bodies, workflow pins, deadline paths and the pre-publish bundle verification are all pinned.
- Release refusal: a release build now refuses a branch source in CI.
- Rehearsal: fork rehearsal
.28ran this exact head and passed every job. A real localphase1-build.shrun for the macOS ARM CLI also passed end to end.
Two blockers remain, both in the operator guide, where following the text costs a production release:
- the guide spends a fresh tag (a new version number) after failures that created nothing;
- the manual Fastly deploy does not say which store links the activated version needs.
Two statements in my round-3 comments were wrong, and are corrected inline: "up to 15 GETs" (the maximum is 12), and "until both settings are in place" (only the tag ruleset stops tag pushes).
On scope, Phase 1 is now close to as small as it should be. Two more cuts are worth weighing: one bounded poll in place of the publisher's availability machinery, and inlining BuildInfo. Both are written as ♻️ proposals.
25 of the 30 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. Each was verified on its own and all together: the suite, prettier, actionlint,cargo fmt,clippy-cliand the affected CLI tests pass. The remaining comments describe the change in prose because it spans several files or lines outside the diff.
Blocking
🔧 wrench
- The guide spends a fresh tag after failures that created nothing: see inline at
docs/guide/release-verification.md:441 - The manual Fastly deploy does not name the store links it needs: see inline at
docs/guide/release-verification.md:200
Non-blocking
♻️ refactor / 🤔 thinking / ⛏ nitpick
- ♻️ Replace the availability machinery with one bounded poll: see inline at
scripts/release/publish.mjs:423 - ♻️ Inline
BuildInfointo the CLI (and 🤔 onvariant=default): see inline atcrates/trusted-server-core/src/build_info.rs:1 - ♻️ The CycloneDX dependency merge never reaches a published byte: see inline at
scripts/release/phase1.mjs:243 - ♻️
validateSpdxmostly re-checkscompleteSpdx's own output: see inline atscripts/release/phase1.mjs:163 - ♻️ The recovery block repeats the online block: see inline at
docs/guide/release-verification.md:393 - ♻️ Tag checks and the clean build environment are pinned by name only: see inline at
scripts/release/phase1.test.mjs:1588 - 🤔 The other-tag case points at a recovery that fails the same way: see inline at
docs/guide/release-verification.md:361 - ⛏ The provenance fixture returns the SPDX document: see inline at
scripts/release/phase1.test.mjs:807 - ⛏ Three documented publisher behaviours are untested: see inline at
scripts/release/phase1.test.mjs:1014 - ⛏ The cleanup test's
shasumstub never reads stdin: see inline atscripts/release/phase1.test.mjs:1864 - ⛏ The bash 3.2 guard fix is pinned only on macOS hosts: see inline at
scripts/release/phase1.test.mjs:1615 - ⛏ Dropping the npm tree from the SBOM passes: see inline at
scripts/release/phase1.test.mjs:100 - ⛏ Generator command lines are unpinned: see inline at
scripts/release/phase1.test.mjs:1668 - ⛏ The described SPDX root's shape is unpinned: see inline at
scripts/release/phase1.test.mjs:1286 - ⛏ A fixture still uses a tag-shaped root version: see inline at
scripts/release/phase1.test.mjs:1778 - ⛏ Dead readelf flags: see inline at
scripts/release/phase1.mjs:301 - ⛏ The refusal step prints nothing on other failures: see inline at
.github/workflows/test.yml:387 - ⛏ Single-element owner loop: see inline at
crates/trusted-server-cli/tests/release_build_facts.rs:84 - ⛏ "Does not validate branch ancestry", and the rc-branch pre-tag gate: see inline at
docs/guide/release-verification.md:288 - ⛏ The same ancestry wording in the spec: see inline at
docs/superpowers/specs/2026-10-09-1235-release-attestation-design.md:167 - ⛏ The single-shot list omits the pre-creation verifications: see inline at
docs/guide/release-verification.md:308 - ⛏ Availability text: GET bound (corrects round 3), ordering, trusted-root wording, error list: see inline at
docs/guide/release-verification.md:347 - ⛏ Rehearsal steps omit the variable and the branch push: see inline at
docs/guide/release-verification.md:410 - ⛏ The checklist does not name its checks: see inline at
docs/guide/release-verification.md:432 - ⛏ "Digest-pinned UBI 9.6 container" describes only the base: see inline at
docs/superpowers/specs/2026-10-09-1235-release-attestation-design.md:151 - ⛏ Tag-push risk wording (corrects round 3): see inline at
docs/superpowers/specs/2026-10-09-1235-release-attestation-design.md:221 - ⛏ CHANGELOG
x-ts-versionandsource=wording: see inline atCHANGELOG.md:17 - ⛏ Orphaned
cyclonedx.orgasserts: see inline atcrates/trusted-server-cli/src/commands/dev/lint/domains.rs:342
Cross-cutting / body-level findings
- ⛏ Smaller doc fixes
- Guide
:228-232. Cloning creates a new version; it does not "change" the base version. Suggested text: "This command can activate production: it clones an active or locked base version, uploads the package to the new version and activates it. Confirm its behavior for the pinned CLI before use." docs/superpowers/specs/2026-01-15-attestation-design.md:3. It still says the October file "becomes the implementation contract upon approval", but that file is now Phase 1 only. Suggested text: "Binary-attestation sections are superseded by the Phase 1 release specification and, for later phases, the frozen destination design it links."- Spec
:23names a GitHub user ("aram356's round-1 architecture review"). It is the only doc in the repository that does, which goes against the fictional-data rule; "the round-1 architecture review" keeps the link. phase1.test.mjs:1988-2003. TherunStepcomment says GitHub runsbash -eo pipefail. For steps withoutshell:, the.28and.29logs show/usr/bin/bash -e {0}. No step depends on pipefail today; the helper is just stricter than production.
- Guide
- 📝 Notes
- The PR description's file table leaves out
crates/trusted-server-cli/src/lib.rs,crates/trusted-server-core/src/constants.rsandcrates/trusted-server-core/src/lib.rs. - Dropping
cargo:rerun-if-env-changed=TRUSTED_SERVER__RELEASE_BUILD(core/build.rs:163) passes both CI steps, because each also changesGIT_VERSION. A dev check followed by a release check with the same source then accepts a branch source. Release builds cannot hit this, becausephase1-build.shrefuses an existing target directory. - Optional trims:
- guide paragraphs that repeat the spec (
:290-301,:274-282,:401-403,:405-410,:433-436; about −45 lines); - spec R-INPUTS and the phase-boundary table (about −25);
release.yml's eightdownload-artifactsteps reduced to twopattern:steps;docs/guide/cli.md:62-67repeating guide:172-178.
- guide paragraphs that repeat the spec (
- The branch is behind
main(merge base51730bdaf). It is still mergeable.
- The PR description's file table leaves out
CI Status
- Analyze (actions): PASS
- Analyze (javascript-typescript): PASS
- Analyze (python): PASS
- Analyze (rust): PASS
- browser integration tests: PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo fmt: PASS
- cargo test: 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
- format-docs: PASS
- format-typescript: PASS
- HTML EOF runtime (Fastly): PASS
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- prepare integration artifacts: PASS
- release scripts: PASS
- trace runtime and browser acceptance: PASS
- ts dev lint domains (changed lines): PASS
- vitest: PASS
- worker-deadlines (cloudflare): PASS
- worker-deadlines (spin): PASS
Fix the operator guide's recovery rules and manual Fastly deploy store links. Replace the publisher's post-publish availability machinery with one bounded poll over the release read and gh release verify. Inline the CLI build facts, drop the unused CycloneDX dependency merge and redundant SPDX re-checks, and pin the producer, workflow and publisher behaviours the review found untested.
|
Round-4 body-level items, in
|
Integrate main while preserving both CHANGELOG entries. Simplify release statement validation without weakening exact source and asset checks, correct recovery guidance, and strengthen the release regressions and CLI fallback coverage.
Summary
Phase 1 of #1235: a
v*tag push builds four binaries, attests SLSA provenance and an SPDX 2.3 SBOM for each, and publishes them in one immutable GitHub release (17 assets withSHA256SUMS). Operators verify withgh attestation verify --signer-workflowandgh release verify-asset.trusted-server-fastly.wasmwasm32-wasip1ts-x86_64-unknown-linux-gnux86_64-unknown-linux-gnuts-aarch64-apple-darwinaarch64-apple-darwints-x86_64-apple-darwinx86_64-apple-darwinReproducibility, other adapters (Axum, Cloudflare, Spin), the custom verifier and the publication owner are later phases; their destination design stays at frozen
122b78664.Changes
.github/workflows/release.ymlmainpushes andv*tags; publisher job (sparse checkout, pinnedghand Node).github/workflows/release-build.ymlactions/attest.github/workflows/format.ymlrelease scriptsjob: shellcheck and the release Node suite on PRs.github/workflows/test.ymlscripts/release/phase1-build.shscripts/release/phase1.mjsscripts/release/publish.mjsscripts/release/release-notes.mdscripts/release/phase1.test.mjscrates/trusted-server-core/build.rs,build_support/git_version.rscrates/trusted-server-cli/build.rs,src/run.rsts --versionprints labelled build facts (breaking output change)crates/trusted-server-cli/tests/release_build_facts.rscrates/trusted-server-core/tests/git_version_resolve.rscrates/trusted-server-core/src/settings.rscrates/trusted-server-core/src/constants.rsx-ts-versionvaluecrates/trusted-server-cli/src/commands/dev/lint/domains.rscli.github.com,slsa.dev,spdx.devdocs/guide/release-verification.mddocs/superpowers/specs/2026-10-09-1235-release-attestation-design.mddocs/superpowers/specs/2026-01-15-attestation-design.mddocs/guide/cli.md,docs/.vitepress/config.mts,AGENTS.md,CHANGELOG.mdMaintainer gates before publication
RELEASE_PUBLICATION_ENABLED=trueonly after immutable releases are enabled and an activev*tag ruleset is in place (the guide's checklist has read-only commands). Until the tag ruleset is active, any writer who can push av*tag can start that tag's ownrelease.yml.Rehearsal status
v0.0.0-rehearsal.24of07c2bdf19: builds and signing passed. The publisher failed safely before creating anything, because the SBOM verification output exceeded Node's 1 MiB default buffer. Fixed in739016b2a.v0.0.0-rehearsal.25of739016b2a: passed (checks, 4 builds, 4 signing jobs, release). The publisher verified every signing bundle before creating the draft, then found the release attestation on its first check. The guide's verification block, run unchanged against the published release, passed for every binary, SPDX file, bundle and checksum.3e2dd4842adds tests and rewords one shell comment only.v0.0.0-rehearsal.28of861ed761c: passed. The newchecksstep found the commit on a branch, and the guide's verification block passed against the release (17 assets, 16 checksum entries).v0.0.0-rehearsal.29, a commit reachable only through its tag: refused inchecksas intended, before any build or signing. No release or attestation exists for it.v0.0.0-rehearsal.31of3e6bb0f09(round-4 publisher rewrite, before the latest review fixes and main integration): passed. The firstgh release verifyfound no attestation yet; the publisher waited 2s and verified on the second attempt. The guide's verification and recovery blocks passed against the release.A fork rehearsal does not qualify the upstream signer identity, settings or immutable-release state.
Latest review corrections (
a94ddd348)mainat6fbe64aa0, preserving both CHANGELOG entries.The
.31rehearsal is historical evidence. A new rehearsal of the final merged commit is required before the first production tag; no new rehearsal or production publication was performed for these corrections.Test plan
node --test scripts/release/phase1.test.mjsandshellcheck scripts/release/phase1-build.shcargo fmt, the adapter clippy aliases,cargo test-fastly,cargo test-axumrelease_build_facts, and the release-mode refusal step.31passed;.29confirmed the branch check refuses a tag-only commit)🤖 Generated with Claude Code