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. |
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
- Move the release publisher into scripts/release/publish.mjs with a release-notes template; tests import it instead of scraping YAML. - Accept release-attestation records that carry only bundle_url, verify them through gh release verify output, and retry the list read and failed proof fetches within the availability checks. - Install a checksummed GitHub CLI 2.102.0 and pinned Node for the publisher and checks job; refuse non-SemVer tags before building. - Fix the signing-input symlink guard and bundle check, which set -e ignored inside && lists. - Run release builds on main pushes and tags only; run the Node release suite on pull requests in format.yml. Reference actions by major tag. - Leave REPO, TAG and COMMIT blank in release notes, with a guard that also works in bash 3.2. - Extend the source audit to byte/C raw strings, astral chars and build helpers; drop the test-only CLI cfg and duplicate release validation. - Remove carried #1262 data and docs (frozen at 122b786), frame Axum binaries as dev/evaluation builds, map Phase 1 spec scope by R-ID, and correct the guide, plan, compatibility record and changelog.
|
Body-level items from the review, addressed in c827eb2:
Before this push, I had three independent automated review passes cover the whole PR: the publication path, the source audit and Rust, and docs-versus-code. Their blocking finding was that the new notes guard did nothing on macOS bash 3.2. That and their should-fix findings are in this commit, along with these fixes:
Local checks:
Outstanding: a fresh fork rehearsal of this head (sparse-checkout publisher, pinned gh install, |
|
Phase 1 rehearsal
The fork's API returned the release-attestation bundle inline. The Independent operator verification: run in a fresh directory with an official GitHub CLI 2.102.0 macOS ARM archive (checksum verified against cli/cli's checksums file). I used the published release notes' block and filled in
The published notes show the "stay editable" warning and the blank pins. |
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.
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-core/src/build_info.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-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 both are set, any writer can push av*tag whoserelease.ymlruns.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(current head): 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.A fork rehearsal does not qualify the upstream signer identity, settings or immutable-release state.
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.28passed;.29confirmed the branch check refuses a tag-only commit)🤖 Generated with Claude Code