Release
Loopwire releases are artifact-first. Every install channel must consume the same tarballs, SHA256SUMS, and SHA256SUMS.sig.
Local Artifact Smoke
pnpm verify:release
pnpm verify:docsThis creates temporary release artifacts with scripts/package-release.sh, verifies reproducible output for identical input, signs and validates SHA256SUMS, installs the generated host-architecture tarball through scripts/install.sh, and runs the installed binary from a temp prefix. The smoke also checks loopwire --background --help from the extracted tarball and the installed prefix, proving the packaged restore entrypoint is present. Installer smokes also reject signed artifacts whose tar members contain unsafe absolute or parent-traversing paths before extraction. verify:docs confirms the public install, support, troubleshooting, screenshot, and release-note docs stay linked in the VitePress navigation.
Evidence Bundle
For release rehearsals and VM runs, collect an attachable evidence bundle:
pnpm collect:evidence -- --output-dir .release-evidence/v0.1.0 --profile full --release-tag v0.1.0The collector writes command logs plus release-evidence.json, including git state, tool versions, command exit codes, backend detection, Rust compile status, workflow parsing, release-readiness preflight state, and GSD milestone state. The full profile records read-only DSP provider plan evidence and read-only JACK provider plan evidence as required evidence, records offline release readiness with GitHub/tag/clean-checkout checks skipped, and records the strict publish preflight, published-release installer smoke, and VM bundle verification as optional evidence. The manifest exposes parsed release.findings plus release.blockers from the readiness log. This lets a rehearsal bundle show current external blockers without failing evidence collection while still rejecting candidate-only versioned release notes. Use --profile quick inside VM runs when a full workspace check has already been captured separately.
Custom --output-dir values are local evidence directories only. They may be absolute temp directories or relative project paths, but the collector rejects root/home placeholders, parent/current-directory traversal, URL syntax, glob metacharacters, symlinks, and existing non-directory paths before writing command logs or release-evidence.json.
For final release evidence after the GitHub Release, docs deployment, and VM bundles exist, make published-release installer smoke, live docs smoke, and all VM evidence mandatory:
pnpm collect:evidence -- \
--output-dir .release-evidence/v0.1.0-published \
--profile full \
--release-tag v0.1.0 \
--require-published-release \
--require-live-docs \
--docs-hostname "$BUNNY_PULL_ZONE_HOSTNAME" \
--require-vm-evidence \
--require-dsp-provider-plan \
--require-jack-provider-plan \
--vm-target all \
--vm-evidence-dir '.vm/evidence/{target}'The live docs command runs scripts/verify-docs-live.sh against the pull-zone URL, verifies the deployed homepage and /install.sh, and compares the deployed installer with apps/docs/docs/public/install.sh.
The VM evidence command expands --vm-target all from vm/targets.tsv and runs scripts/verify-vm-evidence.sh against each selected bundle. When --require-published-release is also present, every VM verifier requires published-release-smoke.log and the successful published-release-smoke command ledger row from the guest. Every evidence bundle also records vm-launch-plan.tsv from bash scripts/vm-matrix.sh render-launch-plan --all, so the release archive carries the operator handoff for every declared VM target, deterministic SSH port, dry-run launch command, and paired evidence-pull command. When published-release proof is required, that launch plan is rendered with --require-published-release, --release-tag <tag>, --published-release-repo <owner/repo>, --release-public-key <file>, and --require-github-release-source. Every paired evidence-pull command carries the same GitHub release source requirements before --execute. The DSP provider plan command runs scripts/collect-dsp-provider-plan.sh against scripts/fixtures/dsp-provider-configuration.json without --execute, records the expected read-source, write-output, verify-output, and clear-output operation rows, and binds the configuration path plus frame count in release-evidence.json. It proves the release still exposes the provider contract without mutating host audio. Release tarballs must also expose loopwire-dsp-provider beside loopwire; the provider is file-backed smoke infrastructure, not live backend capture. The JACK provider plan command runs scripts/describe-jack-ports.mjs against scripts/fixtures/jack-provider-configuration.json with --loopwire-owned-only and without --verify. It records the deterministic Loopwire-owned client names and suggested channel ports that a bundled or operator-supplied JACK provider must create, while proving release evidence did not call jack_lsp or execute a provider.
The tag release workflow collects the published-release portion automatically after gh release create or upload finishes. It runs pnpm collect:evidence with --require-published-release --require-dsp-provider-plan --require-jack-provider-plan, verifies the bundle with pnpm verify:release-evidence, writes loopwire-release-evidence-<tag>.tar.gz into the release directory, regenerates the signed SHA256SUMS manifest so the evidence archive is checksummed, uploads the archive plus updated manifest files to the GitHub Release, and uploads a matching loopwire-release-evidence-<tag> workflow artifact. VM evidence remains operator-collected because GitHub-hosted runners do not provide the declared desktop/audio VM matrix.
The evidence collector and verifier enforce the same v-prefixed semver tag rule as the release workflow. A path-like tag such as v0.1.0/preview is rejected before command planning, manifest acceptance, or archive attachment. Release proof commands also require repository identity in OWNER/REPO form; URLs, spaces, or extra path segments are rejected before GitHub access or evidence verification.
Preview the evidence command plan without running commands or writing files:
pnpm collect:evidence -- --list-commands --profile full --require-published-release --require-vm-evidence --vm-target allVerify an already collected final evidence bundle before attaching it to a release or PR:
pnpm verify:release-evidence -- \
--evidence-dir .release-evidence/v0.1.0-published \
--public-key packaging/release-signing-public.pem \
--git-head "$(git rev-parse HEAD)" \
--require-published-release \
--require-live-docs \
--require-vm-evidence \
--require-all-vm-targets \
--require-vm-launch-plan \
--require-dsp-provider-plan \
--require-jack-provider-plan \
--require-no-release-blockers \
--require-clean-gitThe verifier checks release-evidence.json, validates git source metadata (git.head, git.origin, and git.statusShort), proves required commands succeeded, and requires non-empty command logs. Add --git-head for final release bundles that must match the resolved release tag commit. Add --require-clean-git for final release bundles that must prove the evidence came from a clean checkout. The verifier rejects command log paths that escape the evidence directory or resolve through symlinks. Required VM evidence must name known vm/targets.tsv target ids exactly once, keep each evidenceDir relative and target-scoped, and show the matching bash scripts/verify-vm-evidence.sh --target ... --evidence-dir ... command row. With --require-all-vm-targets, the same check also rejects evidence that misses any target from vm/targets.tsv. With --require-vm-launch-plan, the verifier also requires a successful vm-launch-plan command row, validates the vm-launch-plan.tsv header and one row for every target, and checks that each row pairs the rendered scripts/vm-matrix.sh launch command with the matching scripts/collect-vm-evidence-ssh.sh --execute command. When --require-published-release is also active, both the vm-launch-plan command row and every evidence-pull row must include the manifest-bound release tag, GitHub repository, signing public key, and GitHub-release-source strictness flags. With --require-dsp-provider-plan, the verifier also requires a successful dsp-provider-plan command row that invokes bash scripts/collect-dsp-provider-plan.sh in read-only mode and validates dsp-provider-plan.tsv contains read-source, write-output, verify-output, and clear-output rows for the manifest-bound frame count and configuration. The row targets, labels, and channel counts must match the configured routed sources and outputs, so unrelated placeholder DSP rows cannot satisfy final release proof. With --require-jack-provider-plan, the verifier also requires a successful jack-provider-plan command row that invokes node scripts/describe-jack-ports.mjs in read-only mode with --loopwire-owned-only. The JSON log must contain only Loopwire-owned requirements for the manifest-bound fixture, must not contain readiness fields from --verify, and must match the deterministic device names and channel counts expected from the configured routed inputs, outputs, and monitors. When --require-live-docs is present, the verifier requires a successful docs-live-smoke command row that executed bash scripts/verify-docs-live.sh against the public installer and the same deployed docs base URL or hostname plus remote prefix recorded in release-evidence.json. When --require-published-release is present, the verifier requires a successful published-release-smoke command row that executed bash scripts/verify-published-release.sh with the same repo, tag, and public key recorded in release-evidence.json. Pass --public-key for final release bundles so the manifest must match the same signing public key used to verify the release assets. Pass --git-head so the evidence manifest must match the tag commit the release workflow checked out. Those rows are tokenized and must invoke the expected script directly, so an echo command that only prints the expected verifier path and flags is rejected. Published-release evidence rows must not include --release-dir; local staged release directories are valid for pre-publish smoke tests, but they cannot satisfy final published-release evidence.
After the GitHub Release exists, Bunny.net docs are live, and all VM bundles have been copied back and promoted, run the single final proof gate:
pnpm verify:final-release -- \
--repo sandwichfarm/loopwire \
--tag v0.1.0 \
--public-key packaging/release-signing-public.pem \
--git-head "$(git rev-parse refs/tags/v0.1.0^{commit})" \
--release-evidence-dir .release-evidence/v0.1.0-published \
--release-evidence-asset loopwire-release-evidence-v0.1.0.tar.gz \
--vm-evidence-asset loopwire-vm-evidence-v0.1.0.tar.gz \
--docs-hostname "$BUNNY_PULL_ZONE_HOSTNAME" \
--docs-remote-prefix "$BUNNY_REMOTE_PREFIX" \
--vm-evidence-root .vm/evidence \
--support-matrix apps/docs/docs/guide/support-matrix.mdThis wrapper requires the current checkout HEAD to equal --git-head before it builds docs or reads local evidence, so final proof cannot mix published release assets for one commit with local docs, VM evidence, or support-matrix checks from another checkout. Use --allow-head-mismatch only for offline fixture rehearsal. It runs the published-release verifier with the public evidence archive gate, the live docs smoke, strict final release-evidence verification, every target-specific VM evidence verifier with installed-release smoke, support-matrix verification with installed-release smoke required for Verified rows, read-only DSP provider plan evidence, and the docs contract. If non-default tag-bound release or VM evidence archive names are used, pass --release-evidence-asset NAME and --vm-evidence-asset NAME; the wrapper passes the release evidence name to published-release verification and the VM evidence name to the dry-run VM evidence preparation handoff. Use --dry-run first to print the exact command plan without touching network, release assets, docs URLs, or VM evidence. Add --plan-output dist/release/final-release-proof-plan.txt to dry-run mode when you need a durable handoff artifact for release review or CI logs. Plan output paths must stay under dist/release/; the verifier rejects absolute paths and . or .. traversal before writing the file. The dry-run handoff also prints the pnpm vm:prepare-release-evidence command plan, including the VM evidence archive packaging step, signed SHA256SUMS refresh, signed-checksum verification, and matching gh release upload --clobber command, so the operator can attach loopwire-vm-evidence-<tag>.tar.gz before running the manual final proof workflow. When --release-dir is used for local signed-release rehearsal, the final-proof wrapper rejects traversal, root/home-expanded paths, URL syntax, glob metacharacters, symlinks, and file paths before using that directory as the release surface. Other local final-proof inputs are checked before dry-run rendering or execution as well: --public-key, --release-evidence-dir, --docs-deployment-manifest, --vm-evidence-root, and --support-matrix reject traversal, root/home-expanded paths, URL syntax, glob metacharacters, symlinks, and existing paths with the wrong file or directory type before the wrapper reads proof artifacts or emits the command plan.
The manual final release proof workflow defaults to loopwire-release-evidence-<tag>.tar.gz and loopwire-vm-evidence-<tag>.tar.gz. If custom asset inputs are supplied, they are validated with scripts/validate-release-asset-name.sh before gh release download: names must be basename-only .tar.gz assets, must match the selected release tag and evidence kind, and must not contain traversal, URL syntax, or glob metacharacters.
To render the complete no-side-effect operator handoff before dispatching workflows:
pnpm release:handoff -- \
--repo sandwichfarm/loopwire \
--tag v0.1.0 \
--git-head "$(git rev-parse HEAD)" \
--env-file /secure/loopwire-release-secrets.envTo prove the repository is ready for that handoff before operator-only secret entry, workflow dispatch, VM execution, or evidence upload:
pnpm release:agent-ready -- \
--repo sandwichfarm/loopwire \
--tag v0.1.0 \
--git-head "$(git rev-parse HEAD)"release:agent-ready is read-only. It runs offline release readiness, verifies the final handoff renders the Operator-deferred after agent delivery section, and by default also checks workflow contracts, docs contracts, VM matrix/cloud-init metadata, packaging metadata, a read-only DSP provider graph-edge plan, and local release artifact smoke. It also verifies that the rendered final-proof dispatch and closing release:status audit carry the tag-bound release and VM evidence archive names, so the repo-ready handoff cannot drop the archive proof contract. Use --release-evidence-asset NAME and --vm-evidence-asset NAME when rehearsing a handoff with non-default tag-bound archive names. When the Deploy Docs workflow artifact names are overridden for a rehearsal, pass --docs-artifact NAME and --manifest-artifact NAME with --require-docs-deployment-artifacts so the post-Bunny artifact gate checks the same proof artifacts that the fetch/final-proof handoff will use. Use --dsp-configuration and --dsp-frame-count only when rehearsing a different release DSP proof topology. By default it requires a clean checkout whose current HEAD exactly matches --git-head, so the rendered handoff matches the pushed commit. Use --allow-dirty and --allow-head-mismatch only for local development rehearsal before committing. Use --skip-local-gates only for fast script-contract rehearsal. Add --require-hosted-checks when the checkout has already been pushed and you need the handoff tied to successful hosted CI and Deploy Docs runs filtered by the exact --git-head; it is optional because it requires GitHub API access. After operator-configured Bunny secrets and a Deploy Docs run, add --require-docs-deployment-artifacts to require the same artifact-bearing docs proof (loopwire-docs plus loopwire-docs-deployment) before treating the handoff as post-deploy ready. Do not use it before Bunny deployment proof exists. The strict final proof still requires published GitHub Release assets. It also still requires Bunny deployment proof, a successful final-proof workflow, and VM evidence from operator-controlled hosts. When the published release or one of its evidence archives is absent, release:status stops that archive check at the download failure and prints the missing asset plus the underlying gh release download detail, rather than cascading into checksum, extraction, or manifest errors for files that were never downloaded.
The handoff prints the agent-ready release automation preflight for the exact commit and selected evidence archive names, the required secret check, strict release readiness command, reviewed annotated tag command, exact tag push ref, Release workflow dispatch, Deploy Docs workflow dispatch, docs deployment run selection, docs deployment proof download, a post-deploy pnpm release:agent-ready -- --require-docs-deployment-artifacts --skip-local-gates check with the same evidence archive names, all-target VM host setup and doctor preflights, VM SSH plan/runbook/evidence commands, VM evidence asset preparation command with the selected --asset-name, explicit gh release upload --clobber for the VM evidence archive plus refreshed SHA256SUMS files, a post-upload pnpm release:status audit, final proof workflow dispatch, and local final-proof dry-run with the selected release and VM evidence asset names, followed by the final final pnpm release:status audit of the published release state. The post-upload status audit should prove the release and VM evidence archive assets are visible, then remain blocked until Final Release Proof runs. After the final-proof dispatch command, it prints the expected GitHub Actions run name that release:status will require: Final Release Proof <tag> @ <git-head>. It does not set secrets, create tags, dispatch workflows, run the printed VM evidence upload command, or mutate host audio. It starts with an Operator-deferred after agent delivery section that names secret filling, protected workflow dispatch, VM guest execution, and signed evidence upload as operator-only activities. It also prints the safe no-value template command:
bash scripts/setup-github-secrets.sh --write-env-template /secure/loopwire-release-secrets.envFor offline rehearsal without GitHub API access, use the committed names-only fixture to prove the final-proof secret audit path without exposing values:
bash scripts/setup-github-secrets.sh --repo sandwichfarm/loopwire --check --scope final \
--secret-list-file scripts/fixtures/github-secret-list-final.tsvThe fixture mirrors the names that must appear in gh secret list output. It intentionally contains no secret values; operators can save their own names-only list from GitHub and pass that local file to the same --secret-list-file flag when rehearsing the release handoff on an offline machine.
If the Deploy Docs run id is not known yet, the handoff prints a docs_deployment_run_id="$(...)" assignment using pnpm release:select-docs-run / scripts/select-docs-deployment-run.sh, then reuses that verified run id in the docs proof fetch, post-deploy agent-ready artifact check, final proof dispatch, and final status audit commands. The selector is read-only and requires a completed successful Deploy Docs run for the expected commit that exposes both loopwire-docs and loopwire-docs-deployment artifacts. --env-file accepts the same local file used by scripts/setup-github-secrets.sh, but the handoff consumes only LOOPWIRE_RELEASE_PRIVATE_KEY_FILE, LOOPWIRE_RELEASE_PUBLIC_KEY_FILE, BUNNY_PULL_ZONE_HOSTNAME, and BUNNY_REMOTE_PREFIX. Bunny storage credentials are ignored by the handoff so access keys never appear in rendered release commands. When --env-file is present, the rendered secret-check, docs proof fetch, and VM evidence asset-prep commands keep using that env file instead of expanding env-derived release key paths. Explicit CLI key flags still override the env file and are rendered only when supplied directly. Custom --vm-ssh-plan and --vm-runbook outputs must stay repo-relative and cannot contain ., .., absolute paths, home-directory expansion, or URL syntax, because the rendered handoff is meant to write reviewable release artifacts inside the checkout. The VM evidence asset-prep helper also validates custom --release-dir values before dry-run or execution: absolute and relative directories are allowed, but parent traversal, URL syntax, glob metacharacters, symlinks, and file paths are rejected before the helper can regenerate SHA256SUMS or SHA256SUMS.sig.
After Deploy Docs succeeds, download and verify its proof artifacts before running final status or final proof:
docs_deployment_run_id="$(pnpm --silent release:select-docs-run -- \
--repo sandwichfarm/loopwire \
--git-head "$(git rev-parse HEAD)")"
pnpm release:fetch-docs-proof -- \
--repo sandwichfarm/loopwire \
--run-id "$docs_deployment_run_id" \
--git-head "$(git rev-parse HEAD)" \
--env-file /secure/loopwire-release-secrets.envThe selector first finds a commit-scoped Deploy Docs run that completed successfully and exposes both docs proof artifacts. The fetch helper then verifies that the selected Deploy Docs run completed successfully for the expected --git-head, downloads loopwire-docs and loopwire-docs-deployment, verifies that the deployment manifest is non-dry-run proof for the same commit, and writes the default paths consumed by pnpm release:status. Downloads and manifest checks are staged first, so a missing deployment artifact cannot leave partial docs proof in the final local paths. If the deployment artifact is missing because Bunny.net secrets were absent, --env-file is preserved in the deploy-scope recovery command without reading or printing secret values. Custom --docs-dist and --manifest outputs must stay repo-relative because the helper rewrites those paths while normalizing downloaded artifacts. Those outputs also reject URL syntax, glob metacharacters, symlinks, and existing paths with the wrong file or directory type before downloads begin. Custom --env-file recovery paths may be absolute or relative local files, but they reject traversal, URL syntax, glob metacharacters, symlinks, and existing non-file paths before the helper renders the secret setup command.
To audit the current final-release state from one read-only command:
pnpm release:status -- \
--repo sandwichfarm/loopwire \
--tag v0.1.0 \
--git-head "$(git rev-parse refs/tags/v0.1.0^{commit})" \
--env-file /secure/loopwire-release-secrets.envThe status command checks required GitHub secrets, the release signing public key, the GitHub Release object and required release assets, the release tag ref, the signed downloadable release evidence archive, the signed downloadable VM evidence archive manifest, completed successful CI, an artifact-bearing Deploy Docs run, and Final Release Proof workflow runs filtered by the expected release commit, the docs deployment manifest, published-release-bound VM evidence, support-matrix claims, and the local handoff plan. The Final Release Proof workflow run must also use the visible run name Final Release Proof <tag> @ <git-head>, preventing a successful proof run for the same commit but a different release tag from satisfying final status. It exits nonzero until every final proof surface is present. Draft releases, prereleases, mismatched release tags, release tag refs that do not resolve to --git-head, releases missing canonical tarballs, signed checksums, release evidence, or VM evidence archives, release evidence archives whose release-evidence.json does not match the selected tag/repo/commit, and VM evidence archives whose signed manifest does not match the selected tag and vm/targets.tsv target set are blockers. Empty, failed, cancelled, or still-running workflow lists are release blockers, even when the GitHub API call itself succeeds. The workflow run headSha and docs deployment manifest source commit must match --git-head, which defaults to the current checkout when omitted, so a successful CI, docs, or proof run for an older commit cannot satisfy final status. The docs deployment manifest must be non-dry-run proof for the built combined site dist; pass --docs-deployment-manifest and --docs-dist if you downloaded the workflow artifact to a non-default path. Use --secret-list-file release-secret-names.tsv to replay a saved names-only secret audit, --docs-deployment-run-id 123456 to pin the Deploy Docs run audited for final proof, --release-evidence-asset NAME or --vm-evidence-asset NAME to audit the same tag-bound evidence archive names passed to Final Release Proof, including the GitHub Release asset list and signed archive downloads, --vm-start-port 2600 to align VM evidence collection handoffs with the rendered SSH plan, or --skip-gh when you only want local evidence checks. Pinned Deploy Docs run ids still have to expose both loopwire-docs and loopwire-docs-deployment; if either artifact is missing, release:status leaves the run id unresolved in recovery commands instead of passing an artifact-incomplete run into final proof. Use --env-file to let the embedded local handoff plan reuse the release private-key path and Bunny docs host/prefix from the same local secret file without printing Bunny storage credentials. The embedded handoff keeps --env-file on the rendered secret-check and VM evidence asset-prep commands, so operators do not need to copy release key paths into separate command flags. It also keeps --env-file on the rendered docs proof fetch command. The embedded handoff also forwards the selected release and VM evidence asset names through agent-ready, VM evidence preparation, final-proof dispatch, and the final release:status audit, so recovery commands do not silently fall back to default archive names after an operator audits tag-bound override names. When a Deploy Docs workflow run is verified, release:status reuses that same verified run id for missing docs manifest recovery and final proof dispatch instead of re-querying a fresh run hint. Without --docs-deployment-run-id, release:status runs the same artifact-aware docs deployment run selection used by the handoff, so recovery commands only receive a run id after GitHub shows both loopwire-docs and loopwire-docs-deployment artifacts for the expected release commit. The embedded handoff treats --public-key as an override only when the status command received that flag explicitly; with --env-file alone, the VM evidence asset-prep command keeps the env-file route instead of expanding the default public-key path. If the docs deployment manifest is missing, release:status prints the matching pnpm release:fetch-docs-proof command for the expected commit and preserves --env-file when supplied. Custom local path inputs for release:status, including --env-file, --secret-list-file, --docs-deployment-manifest, --docs-dist, --vm-evidence-root, and --support-matrix, reject traversal, root/home-expanded paths, URL syntax, glob metacharacters, symlinks, and existing paths with the wrong file or directory type before the audit begins. Evidence asset overrides are also validated as basename-only, tag-bound loopwire-release-evidence-<tag>*.tar.gz or loopwire-vm-evidence-<tag>*.tar.gz names before GitHub downloads run. When --vm-evidence-root points at copied-back VM evidence outside .vm/evidence, release:status uses that same root for the matrix evidence-status audit, support-matrix promotion audit, and embedded recovery handoff. VM evidence collection, asset preparation, local final proof, and follow-up status commands therefore stay bound to the same operator-selected evidence bundle path.
Parse an existing release-readiness log without rerunning release checks:
pnpm collect:evidence -- --summarize-release-readiness-log release-readiness-publish-preflight.logThe summarized readiness log path is also a local file artifact: directories, symlinks, traversal, URL-like values, glob metacharacters, and root/home placeholders are rejected before the log is read.
For user-facing bug reports and compatibility triage, collect the smaller redacted support bundle instead:
pnpm collect:support -- --output-dir .support/$(date +%Y%m%d-%H%M%S) --profile quickUse --profile full when the maintainer also needs pnpm check and Tauri shell verification logs. The support bundle writes support-bundle.json, command-results.tsv, notes.md, backend detection, host diagnostics, and autostart status. The manifest also summarizes detect-audio.json as audio.backends, including availability, route-control scope, per-edge gain/mute flags, diagnostics, and known gaps. When --configuration or --state-file is provided, it also writes jack-port-requirements.json and summarizes read-only JACK readiness as jack in the manifest, including matched and missing ports for each requirement. Add --include-jack-provider-plan for provider-backed JACK reports; the bundle writes jack-provider-plan.json and summarizes deterministic Loopwire-owned requirements as jackProvider without running a JACK provider. Add --include-dsp-provider-plan for DSP restore or per-edge gain triage; the bundle then writes dsp-provider-plan.json and summarizes read-only DSP provider operations as dspProvider without running provider execute mode. It never uploads data automatically.
Published Release Smoke
CI can exercise the same verifier against a local staged release directory before any GitHub Release exists:
bash scripts/verify-published-release.sh \
--release-dir dist/release \
--public-key packaging/release-signing-public.pemThe local directory mode requires both canonical Linux tarballs, verifies they are listed in SHA256SUMS, verifies SHA256SUMS.sig, checks every checksum entry, installs the host tarball from that directory, and runs the installed binary. pnpm verify:scripts covers this mode with a signed fake release, a missing-architecture rejection case, and a tampered-asset rejection case.
After a tag workflow publishes assets, verify the release surface itself:
pnpm verify:published-release -- \
--repo sandwichfarm/loopwire \
--tag v0.1.0 \
--public-key packaging/release-signing-public.pem \
--require-release-evidenceThe script downloads release assets with gh, requires the x86_64 and aarch64 canonical tarballs, verifies the signed manifest and checksum entries, optionally requires loopwire-release-evidence-<tag>.tar.gz to be listed in the signed SHA256SUMS manifest, verifies that evidence archive against the expected release.tag, repository, and public key, requires the same read-only DSP and JACK provider-plan proof used by final release evidence, rejects unsafe archive paths before extraction, rejects link members before extraction, rejects unsafe manifest command log paths during evidence verification, installs from the downloaded asset directory, and runs the installed binary. This must pass before docs or package templates claim a public release is installable. When local --release-dir verification omits --tag, the verifier derives the expected tag from the single evidence archive filename and rejects any mismatch with release-evidence.json.
Artifact Contract
The curl installer and binary package templates expect these files in each GitHub Release:
loopwire-linux-x86_64.tar.gzloopwire-linux-aarch64.tar.gz- version-matched x86_64 and AArch64 AppImages
- Ubuntu 24.04 and Debian 13 deb packages on x86_64
- Fedora 44 and openSUSE Tumbleweed RPM packages on x86_64
release-assets.jsonSHA256SUMSSHA256SUMS.sigloopwire-release-evidence-<tag>.tar.gzfor completed tag releases
The Tauri AppImage is a release attachment. On x86_64, repository-owned Ubuntu, Debian, Fedora, and openSUSE native packages are built from the canonical tarball and replace Tauri's GUI-only deb/rpm outputs. Their recipes and KVM proof contract are documented in packaging/README.md; the four-target proof snapshot is under vm/native-package-proof/. The AArch64 release deliberately excludes Tauri's GUI-only deb/RPM bundles until native AArch64 package recipes have matching proof. Tauri receives the resolved tag version through an ephemeral build config, so published AppImage names cannot retain the source tree's 0.0.0 development version.
release-assets.json is a deterministic machine-readable inventory bound to the tag and commit. It records every payload's role, target, architecture, byte size, and SHA-256. The verifier requires two portable archives, two AppImages, and the four native x86_64 packages; after evidence collection it also requires the tag-bound evidence archive. Unknown, extra, missing, duplicated, linked, mis-versioned, or tampered artifacts fail before upload.
The public docs asset apps/docs/docs/public/install.sh is kept byte-for-byte synchronized with scripts/install.sh. That makes https://loopwire.app/install.sh deployable through the VitePress/Bunny.net docs pipeline without creating a second installer contract. Do not advertise the curl command as live until the published release verifier passes against signed GitHub Release assets.
Each canonical tarball contains a loopwire launcher plus installed support files under libexec/loopwire/:
loopwirestarts the GUI by default.loopwire --background ...runs the bundled background restore script.libexec/loopwire/loopwire-guiis the Tauri desktop binary.libexec/loopwire/scripts/restore-background.mjsandlibexec/loopwire/packages/*/distare the packaged restore engine.
The background launcher requires node on PATH. Package templates add it as a runtime dependency.
Each release tag must also have a versioned docs page at apps/docs/docs/release-notes/<version>.md. For tag v0.1.0, the workflow expects apps/docs/docs/release-notes/0.1.0.md.
GitHub Release Workflow
.github/workflows/release.yml runs on v* tag pushes or manual dispatch with an existing v-prefixed semver tag. The workflow:
- Resolves the release tag from the push ref or manual input, rejects non-semver or path-like tag names, and checks out that tag in detached mode before any build or publish work.
- Builds Linux artifacts through a matrix for
x86_64onubuntu-22.04andaarch64onubuntu-22.04-arm. - Uses Ubuntu 22.04-family runners so Linux builds keep an older supported glibc baseline.
- Installs the current Tauri v2 Linux prerequisites, including WebKitGTK 4.1 development packages.
- Runs
pnpm check, includingpnpm verify:tauri, on each architecture. - Builds Tauri Linux bundles on each architecture with the resolved tag version; AArch64 stages only its AppImage.
- Requires versioned release notes for the tag, checks the tag points at the detached checkout, and rejects release-candidate/not-published wording.
- Stages architecture-specific release attachments with
scripts/stage-release-artifacts.sh; the x86_64 job adds all four native distro packages from the canonical tarball. - Installs each generated architecture tarball from its local release directory with signature verification.
- Uploads the architecture artifacts to the publish job.
- Writes
release-assets.json, then regenerates one combinedSHA256SUMSandSHA256SUMS.sigcovering the inventory and every staged payload. - Installs the generated host tarball from the combined local release directory with signature verification.
- Creates or updates the GitHub Release with the generated artifacts and versioned release notes, then removes stale assets so reruns expose exactly the current signed inventory.
- Downloads every published GitHub Release asset, verifies the remote inventory/checksums, and runs a post-publish installer smoke.
- Collects and verifies published-release evidence.
- Regenerates the inventory and signature so
loopwire-release-evidence-<tag>.tar.gzis part of the signed contract. - Uploads the evidence archive,
release-assets.json, and updated checksums/signature, downloads the final remote inventory, then verifies the published release with--require-release-evidence.
The baseline follows Tauri's current Linux distribution guidance: build on the oldest supported base that provides WebKitGTK 4.1, with Ubuntu 22.04 or Debian 12 as suitable examples.
The desktop package build sets NO_STRIP=true for Tauri bundling. Local Arch validation showed AppImage bundling can fail when linuxdeploy's bundled strip cannot read newer .relr.dyn ELF sections, even though the application binary compiled successfully.
Release Readiness Preflight
Before pushing a release tag, run:
pnpm verify:release-readiness -- \
--repo sandwichfarm/loopwire \
--tag v0.1.0 \
--public-key packaging/release-signing-public.pemThe preflight does not publish. It requires a v-prefixed semver release tag without path separators, checks versioned release notes, rejects release-candidate/not-published wording, verifies that the public docs /install.sh asset matches the canonical installer, verifies that pnpm verify:docs-deployment is present and wired into the docs deploy workflow, verifies that pnpm verify:final-release, pnpm vm:package-evidence, and the final release proof workflow are wired, validates the signing public key, requires a clean git checkout, checks that the local or remote tag resolves to the current checkout commit, verifies repository access, and confirms required GitHub secrets for release and Bunny.net docs deployment. Candidate evidence collection passes --skip-clean-git because it is allowed to record in-progress source state without claiming final release readiness.
Custom --public-key and --secret-list-file values are local file artifacts only. They may be absolute or relative, but release readiness rejects root or home-expansion placeholders, parent/current-directory traversal, URL syntax, glob metacharacters, symlinks, and existing non-file paths before parsing a key or replaying saved secret names.
Workflow Contract
pnpm verify:workflowsThe workflow contract parser validates CI, continuous host diagnostics, docs deployment, release publication, and VM matrix workflow files. It checks the release workflow still checks out the resolved tag before build/publish work, keeps tag verification enabled in the release-readiness step, requires versioned notes, signing secrets, generated and published install smokes, and confirms VM/support-matrix changes trigger VM matrix validation.
Final Release Proof Workflow
After the GitHub Release exists, docs are deployed, and every VM target has operator-run evidence with installed-release smoke, run the manual Final Release Proof workflow. It requires the release tag, expected tag commit, and either a live docs base URL or Bunny pull-zone hostname. By default it downloads these release assets:
loopwire-release-evidence-<tag>.tar.gz, produced by the release workflow.loopwire-vm-evidence-<tag>.tar.gz, produced by the operator after collecting all VM target bundles.
The VM evidence archive must contain a vm-evidence/manifest.json root manifest plus vm-evidence/<target> bundles. The manifest must bind the release tag, every vm/targets.tsv target, published-release strictness, and the deterministic archive layout. The workflow checks out the exact tag commit, downloads the signed SHA256SUMS manifest, downloads both archives from the GitHub Release, verifies each archive is listed in the signed checksum manifest with scripts/verify-release-asset-checksum.sh, validates both downloaded tarballs with scripts/extract-safe-tar.sh before extraction, verifies the VM evidence archive manifest with scripts/verify-vm-evidence-archive-manifest.mjs, verifies every listed target bundle from the extracted archive with scripts/verify-vm-evidence.sh, verifies live docs and /install.sh, passes the validated archive names into scripts/verify-final-release-proof.sh, verifies the live GitHub release tag ref resolves to the expected tag commit, requires every VM target bundle to include published-release smoke, verifies support-matrix promotion rules, and reruns pnpm verify:docs. The composed scripts/verify-final-release-proof.sh step must not pass --release-dir; by this stage all release proof comes from GitHub Release downloads, signed checksum verification, the live release tag ref, live docs evidence, and operator-collected VM evidence.
After collecting and verifying every VM target bundle, create the archive for the GitHub Release:
pnpm vm:package-evidence -- \
--tag v0.1.0 \
--evidence-root .vm/evidence \
--all \
--require-published-release \
--output dist/release/loopwire-vm-evidence-v0.1.0.tar.gzThe packager re-runs scripts/verify-vm-evidence.sh --require-published-release --release-tag <tag> --require-github-release-source for each selected target before writing vm-evidence/<target> entries and a vm-evidence/manifest.json root manifest into the archive. The final proof also runs scripts/verify-published-release.sh --require-github-release-source, so a local --release-dir smoke cannot satisfy public release proof. Each VM bundle must include published-release.json matching the release tag and GitHub release source, in addition to a successful published-release-smoke ledger row. After writing, it validates the archive with scripts/extract-safe-tar.sh, validates the extracted root manifest, and verifies every listed target bundle from the extracted archive, so unsafe paths, link members, tag mismatches, missing target declarations, missing bundles, or invalid bundle contents are caught before the tarball is attached to a release. Custom --output paths may point at temp locations for local rehearsal, but the basename must still be a validated loopwire-vm-evidence-<tag>*.tar.gz release asset name and the path must not use traversal, URL syntax, glob metacharacters, symlinks, or a directory target. Prepare the archive as a signed release asset with the release private key:
pnpm vm:prepare-release-evidence -- \
--repo sandwichfarm/loopwire \
--tag v0.1.0 \
--release-dir dist/release \
--env-file /secure/loopwire-release-secrets.env \
--evidence-root .vm/evidence \
--allThe helper reruns the packager, regenerates and re-signs SHA256SUMS, verifies loopwire-vm-evidence-<tag>.tar.gz with scripts/verify-release-asset-checksum.sh, and prints the exact gh release upload --clobber command for the archive plus refreshed manifest files. Final proof can then prove the VM evidence archive is a signed release asset before extraction. The final release handoff repeats that upload command and the follow-up pnpm release:status audit so operators have a visible proof step between VM evidence packaging and Final Release Proof dispatch. When release:handoff --vm-evidence-asset NAME is used, the rendered pnpm vm:prepare-release-evidence command receives the same --asset-name NAME that upload, final proof, and status audit commands expect. --env-file consumes only LOOPWIRE_RELEASE_PRIVATE_KEY_FILE and LOOPWIRE_RELEASE_PUBLIC_KEY_FILE for this helper; Bunny storage credentials are ignored so access keys never appear in the dry-run or upload handoff output. The helper validates --env-file, --private-key, --public-key, and --evidence-root before artifact reads or signing: traversal, root/home-expanded paths, URL syntax, glob metacharacters, symlinks, and existing wrong-type paths fail closed.
This workflow is intentionally workflow_dispatch only. It should fail until the public release, live Bunny.net docs, release evidence archive, and VM evidence archive all exist.
Signing Key Setup
Release signing uses OpenSSL to sign SHA256SUMS, not each artifact independently. Generate the key outside the repo, commit only the public key, and store the private key as a GitHub secret:
pnpm release:prepare-key -- \
--private-key-out /secure/loopwire-release-private.pem \
--public-key-out packaging/release-signing-public.pem
pnpm setup:github -- \
--repo sandwichfarm/loopwire \
--scope final \
--public-key-file packaging/release-signing-public.pemrelease:prepare-key refuses to write the private key inside the repository, refuses to overwrite existing key files unless --force is passed, derives the public key, and verifies the pair by signing and verifying a temporary SHA256SUMS payload.
The release workflow requires LOOPWIRE_RELEASE_PRIVATE_KEY. The installer requires a trusted public key unless --skip-signature is passed explicitly for local unsigned development artifacts.
Use the cross-platform GitHub Actions setup guide for normal operator work. The remaining shell-helper commands document the legacy Unix-only env-file and offline secret-list surfaces retained for release rehearsal compatibility; they are not the recommended interactive path.
Audit or preview the GitHub secret ceremony before setting anything:
bash scripts/setup-github-secrets.sh --print-required
bash scripts/setup-github-secrets.sh --write-env-template /secure/loopwire-release-secrets.env
bash scripts/setup-github-secrets.sh --print-env-template
bash scripts/setup-github-secrets.sh --repo sandwichfarm/loopwire --check
bash scripts/setup-github-secrets.sh --repo sandwichfarm/loopwire --check --scope deploy
bash scripts/setup-github-secrets.sh \
--repo sandwichfarm/loopwire \
--check \
--secret-list-file release-secret-names.tsv
bash scripts/setup-github-secrets.sh \
--repo sandwichfarm/loopwire \
--scope final
bash scripts/setup-github-secrets.sh \
--repo sandwichfarm/loopwire \
--scope final \
--storage-zone loopwire-docs \
--access-key "$BUNNY_ACCESS_KEY" \
--pull-zone-hostname docs.example.test \
--release-private-key-file /secure/loopwire-release-private.pem \
--release-public-key-file packaging/release-signing-public.pem \
--dry-run
bash scripts/setup-github-secrets.sh \
--repo sandwichfarm/loopwire \
--scope final \
--env-file /secure/loopwire-release-secrets.env \
--dry-run--check reads secret names only. The default --scope final checks all final-proof secrets. Use --check --scope deploy to verify only the Bunny.net upload pair before the final release-signing and live-docs secrets are available. Set mode uses the same scopes before any gh secret set call runs. For operator use, run bash scripts/setup-github-secrets.sh --repo sandwichfarm/loopwire --scope final; the helper prompts for one secret at a time, explains where to find it, and writes each value directly to GitHub with gh secret set without printing the value. The LOOPWIRE_RELEASE_PRIVATE_KEY prompt is final-release signing material, not a Bunny.net or GitHub token. The private-key and public-key prompts both accept a local PEM path or a pasted PEM block. The helper reads pasted blocks through their matching END ... KEY line before continuing, expands literal $HOME/..., ${HOME}/..., and ~/... paths, validates the pair, and sends the private-key file contents so the release workflow can sign SHA256SUMS and installers can verify published artifacts. For automation, --scope deploy requires BUNNY_STORAGE_ZONE and BUNNY_ACCESS_KEY; the default --scope final also requires BUNNY_PULL_ZONE_HOSTNAME, LOOPWIRE_RELEASE_PRIVATE_KEY_FILE, and LOOPWIRE_RELEASE_PUBLIC_KEY_FILE. When --check --scope deploy sees the optional BUNNY_PULL_ZONE_HOSTNAME secret, it also reports that the docs deploy workflow can run post-upload live smoke without requiring the release signing secret yet. Without that hostname, deploy-scope checks still pass for Bunny upload readiness but report that live docs smoke will be skipped. --secret-list-file accepts saved gh secret list output for deterministic release rehearsal; the artifact may contain secret names and metadata columns, but never secret values. --dry-run validates inputs and prints secret names that would be set without printing secret values or writing to GitHub. --env-file accepts a local uncommitted file with simple KEY=VALUE lines for BUNNY_STORAGE_ZONE, BUNNY_ACCESS_KEY, BUNNY_STORAGE_ENDPOINT, BUNNY_PULL_ZONE_HOSTNAME, BUNNY_REMOTE_PREFIX, LOOPWIRE_RELEASE_PRIVATE_KEY_FILE, and LOOPWIRE_RELEASE_PUBLIC_KEY_FILE; command-line flags override env-file values. Values are taken literally after the first = byte, so shell-style quote parsing is intentionally not applied and the entered bytes reach GitHub unchanged. Enter canonical values exactly as they should be stored: BUNNY_STORAGE_ENDPOINT must already include its https:// scheme and must not end with /, while BUNNY_REMOTE_PREFIX must already omit leading and trailing slashes. .env.example is the committed key-name template; copy it to an uncommitted path such as /secure/loopwire-release-secrets.env before filling values, or run --write-env-template /secure/loopwire-release-secrets.env to create the same no-value template with 0600 permissions. --print-env-template prints the same template to stdout for review. Use file paths for release keys instead of storing raw private-key material in the env file. Local file inputs for --env-file, --secret-list-file, --release-private-key-file, and --release-public-key-file reject traversal, root/home-expanded paths, URL syntax, glob metacharacters, symlinks, and non-file paths before the helper reads them. When --release-public-key-file is supplied, the helper parses the private key, parses the public key, derives the public key from the private key, and fails before any secret write if the pair does not match. If the GitHub CLI cannot read repository secret names, --check and the release readiness preflight fail with the underlying gh secret list error instead of reporting those secrets as missing. When required secrets are missing, --check prints next-step commands with placeholders rather than values. If only Bunny.net storage or live-docs secrets are missing, it prints only the Bunny setup command; if only LOOPWIRE_RELEASE_PRIVATE_KEY is missing, it prints only the release signing setup section with complete final-scope placeholders. BUNNY_PULL_ZONE_HOSTNAME is required for final proof because the docs deployment must run post-upload live smoke against the served pull-zone URL. The Bunny next-step output includes direct flags plus bash scripts/setup-github-secrets.sh --write-env-template <secret-env-file> and the safer --env-file <secret-env-file> route, so operators can keep values out of shell history and avoid unsafe redirection. If storage credentials are already configured and only the hostname is missing, the helper still needs the complete final-scope input set because it cannot read existing secret values back from GitHub:
bash scripts/setup-github-secrets.sh \
--repo sandwichfarm/loopwire \
--scope final \
--storage-zone loopwire-docs \
--access-key "$BUNNY_ACCESS_KEY" \
--pull-zone-hostname docs.example.test \
--release-private-key-file /secure/loopwire-release-private.pem \
--release-public-key-file packaging/release-signing-public.pemThe helper rejects Bunny values that would fail deployment: storage zones cannot contain slashes, storage endpoints cannot contain newlines, pull-zone hostnames must be hostnames rather than URLs or paths, and remote prefixes cannot contain . or .. path segments.
pnpm verify:release-readiness also prints no-value next steps for the remaining blocker classes. With Bunny secrets missing it points back to scripts/setup-github-secrets.sh; with the release tag missing it prints the guarded git tag -a <tag> -m "Loopwire <tag>" and git push origin <tag> commands, explicitly after required secrets are configured and readiness passes.
Boundaries
- The workflow now has a dedicated
ubuntu-22.04-armAArch64 lane, but public AArch64 proof still requires a tagged workflow run and publishedloopwire-linux-aarch64.tar.gzasset. - The default flake package is now bound to the signed
v0.1.0tarball checksums. Fresh local non-skipped Nix build proof only coversx86_64-linux; publicaarch64-linuxproof still requires its own Nix-enabled run. packaging/release-signing-public.pemcontains the project release public key, and the livesandwichfarm/loopwirerepository has the matchingLOOPWIRE_RELEASE_PRIVATE_KEYsecret. Public installer claims still require Bunny deployment secrets plus a tagged release workflow run.- The installer does not mutate system audio configuration.
AUR Smoke
pnpm verify:aur
pnpm verify:aur:source -- --version 0.1.0
pnpm verify:aur:gitThe lightweight AUR smoke renders both packaging/aur/loopwire-bin/PKGBUILD.in and packaging/aur/loopwire/PKGBUILD.in, builds the binary recipe, and validates the source recipe metadata. verify:aur:source performs the full tagged-source compilation and package-content inspection on Arch. Neither command installs or submits anything. verify:aur:git separately builds the current default branch and checks its VCS-derived package version plus complete installed payload. Run that VCS build proof before publication and without an AUR key in its environment. The publisher handles loopwire-git as metadata-only so moving default-branch code never executes while the signing key is reachable.
After the GitHub release is public, publish one or both package bases from a clean Arch checkout:
pnpm deploy:aur -- --package loopwire --tag v0.1.0 --key ~/.ssh/aur
pnpm deploy:aur -- --package loopwire-bin --tag v0.1.0 --key ~/.ssh/aur
pnpm deploy:aur -- --package loopwire-git --tag v0.1.0 --key ~/.ssh/aurThe manual Publish AUR workflow uses the same helper inside an Arch container. It is isolated behind the aur GitHub environment and never runs on a pull request or ordinary branch push.
Documentation Ceremony
Before pushing a release tag:
- Move the user-facing notes from
/release-notes/unreleasedinto a versioned release-note page. - Start a fresh
/release-notes/unreleasedpage with known follow-up work and unsupported claims. - Update support matrix rows when VM evidence changes.
- Refresh
assets/product-screenshot.pngand the docs public copy only from a current app build or a reviewed visual mock that matches the app. - Run
pnpm verify:docsandpnpm build:web.
Release notes must describe what is supported, what remains experimental, and which install channels were smoke-tested.
Docs Deployment
Before the first deployment, complete the GitHub Actions setup for the repository.
The docs deployment workflow builds the Astro homepage plus the VitePress docs tree, uploads one combined site artifact, and deploys to Bunny.net only on explicit workflow dispatch, main, master, or v* tags. The deploy job is assigned to the docs-production GitHub environment so repository protection rules can require manual review or protected branches.
The deploy job fails before upload when BUNNY_STORAGE_ZONE, BUNNY_ACCESS_KEY, BUNNY_API_KEY, or BUNNY_PULL_ZONE_ID is missing. Use the guarded repository-level setup command:
pnpm setup:github -- --repo OWNER/REPO --scope deployAlternatively, add BUNNY_API_KEY as an environment secret and BUNNY_PULL_ZONE_ID as an environment variable in Settings → Environments → docs-production. The API key comes from the Bunny account API Keys page; the ID is the numeric CDN Pull Zone ID, not the Storage Zone ID. The guided helper's --check reads repository-level settings only, and environment values override matching repository values. See GitHub Actions setup.
The legacy scripts/setup-github-secrets.sh and .env.example still describe storage-upload and release-signing configuration only. They do not configure or check the new CDN purge settings; add those separately or use the guided helper. For final release proof, configure BUNNY_PULL_ZONE_HOSTNAME so live smoke can probe the public site.
Deployment uses scripts/deploy-docs-bunny.sh, which uploads raw files with Bunny Edge Storage's PUT endpoint and the storage-zone password in the AccessKey header. The script defaults to https://storage.bunnycdn.com, and BUNNY_STORAGE_ENDPOINT can point at a regional endpoint such as https://ny.storage.bunnycdn.com when the storage zone is not in Bunny's default region. BUNNY_REMOTE_PREFIX can deploy the site under a path inside the storage zone, which is useful when one zone serves multiple preview or product directories. The helper rejects unsafe . or .. remote-prefix segments before upload planning.
The workflow passes --purge-cache to this uploader. After every file upload and deployment-manifest write succeeds, the helper sends POST https://api.bunny.net/pullzone/{id}/purgeCache with the account API key in a private stdin header. It disables curl configuration-file loading, follows no redirects, discards the response body, and bounds connection time, request time, and retries. Only HTTP 2xx is accepted; authentication, HTTP, or transport failures fail the deployment without printing the account key. Failed uploads or manifest writes never trigger a purge.
This purges the entire CDN Pull Zone, including other prefixes served by that zone. An accepted request does not clear browser caches or prove immediate refresh at every edge. If purge fails after upload, the new storage objects remain in place; correct the error and rerun deployment. The local CLI remains upload-only unless --purge-cache is supplied. See Bunny's purge API documentation.
Preview the upload and purge plan without contacting Bunny.net or supplying either credential; use the real CDN Pull Zone ID in BUNNY_PULL_ZONE_ID so the target can be validated:
pnpm build:web
bash scripts/deploy-docs-bunny.sh \
--dist dist/site \
--storage-zone loopwire-docs \
--storage-endpoint https://ny.storage.bunnycdn.com \
--remote-prefix loopwire \
--deployment-manifest dist/docs-deployment/deployment-manifest.json \
--purge-cache \
--dry-runThe deploy helper fails closed if the built dist omits index.html or install.sh; the dry-run should include the Astro homepage, the VitePress /docs/ tree, and install.sh, which is the public curl installer endpoint once the site is deployed. When a deployment manifest is requested, the helper writes a non-secret loopwire.docs-deployment.v1 JSON file listing the deployed relative paths, remote paths, SHA-256 checksums, storage endpoint, storage zone, remote prefix, dry-run/live mode, source git head, and file count. The docs workflow verifies the manifest with pnpm verify:docs-deployment before uploading it as the loopwire-docs-deployment artifact after a Bunny.net deploy. The verifier compares the manifest to the built dist file inventory, rejects checksum drift, checks the remote-prefix mapping, rejects source git head drift, and rejects secret-like manifest keys. When BUNNY_PULL_ZONE_HOSTNAME is configured, the deploy workflow also runs scripts/verify-docs-live.sh --hostname "$BUNNY_PULL_ZONE_HOSTNAME" --remote-prefix "$BUNNY_REMOTE_PREFIX" after upload and purge acceptance. The smoke uses the same pull-zone prefix used for upload. It fetches the deployed homepage, /docs/, the basic-usage guide, and /install.sh, then checks the installer parses as shell and matches the local public installer.
Final release proof must be tied to the same deployment run. Pass the deploy-docs workflow run id that uploaded loopwire-docs-deployment; the proof workflow downloads that artifact, rebuilds the combined Astro homepage plus VitePress /docs/ artifact from the release commit, and verifies deployment-manifest.json against the rebuilt dist/site output and the requested release git head before accepting the live-docs smoke:
gh workflow run final-release-proof.yml \
--repo sandwichfarm/loopwire \
-f tag=v0.1.0 \
-f git_head=<release-tag-commit-sha> \
-f docs_deployment_run_id=<deploy-docs-run-id>If docs_base_url is omitted, the workflow uses the required BUNNY_PULL_ZONE_HOSTNAME repository secret. If docs_remote_prefix is omitted, it uses BUNNY_REMOTE_PREFIX when that optional secret exists. Pass docs_hostname or docs_remote_prefix only to override the stored Bunny pull-zone target for a specific proof run.
The GitHub secret helper can set or audit the deployment secrets:
bash scripts/setup-github-secrets.sh \
--repo sandwichfarm/loopwire \
--scope deploy \
--storage-zone loopwire-docs \
--access-key "$BUNNY_ACCESS_KEY" \
--pull-zone-hostname docs.example.test \
--storage-endpoint https://ny.storage.bunnycdn.com \
--remote-prefix loopwire \
--dry-runScoped CI evidence
CI path selection can omit native checks for documentation or website changes. The release audit therefore checks that the commit-scoped CI run actually completed Validate workspace and its Run workspace checks step; a successful impact-check-only run is insufficient. Follow the audit's manual ci.yml dispatch command at the release tag when a full run is missing, wait for completion, and repeat the audit. Legacy successful full CI runs remain valid. Release builds themselves still run the full workspace checks regardless of ordinary path filters.