Distribution
This document defines the credential-free checks shared by GitHub Releases and downstream installers. Channel-specific tests may add stricter requirements, but they must not weaken these boundaries or replace a published digest with one computed from an untrusted download.
Release assets
Section titled “Release assets”Each supported platform has one versioned archive, one adjacent checksum file,
and one entry in sodapop-<version>-manifest.json. Archive names use
sodapop-<version>-<goos>-<goarch> plus the platform’s archive extension. Every
archive has a single same-named top-level directory and contains the native
sodapop command (sodapop.exe on Windows).
The release manifest binds bytes across channels; it is not by itself proof of
publisher identity. Obtain it through the approved release and verify release
attestations or signatures separately. Schema version 1 records the release
version, full Git commit, copilot_sdk_version, copilot_runtime_version, and
these fields for every artifact:
platformarchivearchive_sha256binary_sha256
The release job must generate the manifest from already-built native archives, after verifying each adjacent checksum. Missing, duplicate, malformed, or unexpected platform entries fail the release rather than producing a partial manifest.
The default release set contains all five supported targets. The manifest tool’s
explicit --platforms option permits a declared subset for local tests; it must
not be used to hide a failed platform that a public channel still advertises.
Windows portable packages use ZIP, not a Unix-only extraction instruction.
Checksum verification
Section titled “Checksum verification”The .sha256 file contains exactly the archive SHA-256 and archive basename so
it works from the download directory with shasum -a 256 -c or
sha256sum -c. Installers verify archive_sha256 before extraction and
binary_sha256 before installing or executing the extracted command.
Verification failure is terminal. An installer must not launch, copy, cache as valid, or report success for an asset whose archive or binary digest differs from the manifest.
Installed-command smoke checks
Section titled “Installed-command smoke checks”Every native release and installer exercises the installed command itself, not a build-tree or staging executable:
sodapop --helpsodapop --versionsodapop --check-runtimeThese checks run without OAuth credentials, a saved login, a model request, or
an interactive terminal. --check-runtime is a local bundled-runtime handshake;
it is not a substitute for the separately authorized live qualification gate.
Package contents
Section titled “Package contents”Native archives use an explicit allowlist. They contain the executable, project README and license, third-party notices, the bundled runtime terms, generated dependency notices, and the documentation/media intentionally named by the packaging script. They do not contain environment files, credentials, caches, repository metadata, unrelated build output, or stale staging content. Archive entries must be regular files or directories under the single package root; links and path traversal are rejected.
Package-manager metadata and launchers may be shipped separately from the native
archive. Their package tests must use an explicit file allowlist and prove that
installation selects the matching manifest platform. Repackaging may change a
channel package’s own digest, but the installed executable must still match the
manifest’s binary_sha256.
Upgrade and uninstall boundaries
Section titled “Upgrade and uninstall boundaries”Install and upgrade operations may change only paths owned by that channel. The local installer stages a new command beside the destination before replacing the existing regular file, and refuses to replace another manager’s symlink. Verification and extraction failures must not replace a good installation. Each package manager’s actual upgrade and recovery behavior needs its own installation tests, not an assumption that every manager offers atomic rollback.
Uninstall removes only package-owned commands, shims, and package metadata. It preserves Sodapop preferences, session history, runtime caches, secure-store credentials, shell profiles, and unrelated files. Destructive state removal, if ever added, must be a separate explicit command and is not part of uninstall.
Cross-channel hash consistency
Section titled “Cross-channel hash consistency”Homebrew, npm, Windows packaging, direct downloads, and future channels resolve
the same platform entry from the published release manifest. A channel that
downloads the native archive must use the manifest’s exact archive_sha256; all
channels must install an executable matching its exact binary_sha256.
Credential-free tests should use temporary archives, fake commands, and isolated
filesystem roots. The reusable helpers in
scripts/distribution_contract_test.go validate manifest/assets, archive
payloads, installed-command smoke checks, owned-path mutations, and channel hash
references without downloading a release or accessing account state.
Test layers and evidence
Section titled “Test layers and evidence”| Layer | What runs | What it establishes |
|---|---|---|
make distribution-test |
Go and Node regression suites, including production archive verification | Deterministic failure handling and packaging contracts; not public availability |
make native-install-test |
A verified archive and a copied portable executable | Real native command startup, exact versions/hashes, cold and cached runtime startup |
| Native installation candidates | Two real test-only releases, package-manager installations, and native Windows delivery checks | Installation/upgrade/removal mechanics without publishing or signing in |
| Tagged release installation gate | Installation checks consuming the exact candidate archives | Candidate byte identity and native delivery behavior before draft creation |
make public-download-test / public-download workflow |
Unauthenticated HTTPS downloads and native execution | Availability and integrity of a published archive, not merely an Actions artifact |
Native installation checks create isolated homes and state directories. Direct archive checks deliberately remove Go, Node, and Copilot from the installed command’s PATH. npm tests additionally require Node and must not fetch a missing native payload at first run. Local-tarball npm tests and published-registry tests are separate evidence; only the latter establish that the public command works.
Use make release-tools to build bin/releasectl and bin/installcheck.
For a local single-platform candidate, generate its manifest explicitly:
bin/releasectl manifest --dir dist --version 0.0.2 \ --commit "$(git rev-parse HEAD)" \ --platforms "$(go env GOHOSTOS)/$(go env GOHOSTARCH)"make native-install-test SODAPOP_VERSION=0.0.2An incomplete or inaccessible public release must fail, not turn into a skipped success. Native Windows results require a Windows runner; cross-compilation and ZIP/MSI manifest fixtures do not establish successful Windows installation. MSI installation is per-user, and destructive installer tests require explicit consent to use a disposable runner. Signed production distribution still needs configured signing identities, macOS notarization where applicable, and owner-reviewed publication. Never infer signing from an archive checksum.
OAuth device authorization, secure credential persistence, entitlement, and a real Copilot session remain separately authorized qualification. None of the credential-free installation commands grants permission for live model usage.
Owner-controlled publication
Section titled “Owner-controlled publication”publish-channels.yml is manually dispatched for a release that is already
public. It verifies GitHub’s release attestation, the downloaded manifest’s
attestation, and the archive/binary hashes before generating channel packages.
It never rebuilds the application for another channel. Enable immutable releases
on the repository before using this workflow.
Configure protected npm-publish and homebrew-publish environments with required
reviewers. Creating a workflow that names an environment does not configure those
reviewers. Confirm ownership of every @sodapop-sh package, perform initial registry
bootstrap if necessary, and authorize this exact workflow/environment as a trusted
publisher. The pinned npm publishing CLI supports OIDC; no npm write token is
stored in source. SODAPOP_NPM_PUBLISH_ENABLED=true is an explicit owner switch,
not a substitute for registry permission.
The npm publisher creates actual tarballs, compares their integrity with any
already-published version, and publishes native packages before the launcher.
A retry may reuse identical published bytes; different bytes or an ambiguous
registry/network failure are fatal. Prereleases use preview, never latest.
The Homebrew job uses a GitHub App limited to the existing homebrew-sodapop
repository with contents and pull-request write permissions. Configure the
SODAPOP_HOMEBREW_APP_ID variable and SODAPOP_HOMEBREW_APP_PRIVATE_KEY environment
secret in GitHub, not in this checkout or chat. It opens a version-update PR; it
does not create the tap, merge the PR, or imply that the public tap has updated.
Stable npm publication and stable tap updates require
SODAPOP_STABLE_RELEASE_QUALIFIED=true. This records owner sign-off after native
installation, signing/notarization, and sign-in qualification; it is not an
automated signature validator. Leave it unset until that evidence exists.
Registering WinGet/Scoop channels, configuring signing credentials, and obtaining
native ARM64 evidence remain separate external/platform gates.
Windows installer execution
Section titled “Windows installer execution”The five-platform install workflow exercises the native Windows portable path
and generates channel manifests. windows-installers.yml is a separate manual
gate for MSI and opt-in WinGet/Scoop installation. It consumes two published,
attested numeric releases and tests actual installation, repair, upgrade, and
removal. Those public releases must exist before that workflow can succeed.
Its runner labels must identify a provisioned, disposable, non-elevated
Windows x64 user. The default sodapop-disposable label is a requirement to
configure, not a runner created by this repository. An elevated token is rejected
so administrator rights cannot mask a per-user MSI defect. Go, PowerShell, .NET,
and any selected WinGet/Scoop prerequisites must be available to that user.
The workflow requires explicit WiX terms acceptance before building test
containers. It does not accept those terms or enable privileged WinGet settings
on the developer’s behalf.
The MSI containers built by this test workflow are explicitly unsigned candidates; only logs/evidence are uploaded. Production signing and a repeat of native evidence against the final signed container are separate requirements. The Windows delivery guide documents those commands and the per-user state/registration boundaries.