Status: accepted baseline (release automation and distribution contract) Scope: release artifact packaging, validation gates, version stamping, and public distribution
Decision history: provider distribution and local activation are recorded in ADR-0012; the R2 namespace, immutable artifact, and latest-pointer contract is recorded in ADR-0014.
scripts/release.sh [version] (default 0.1.0) produces
target/wright-<version>.tar.gz containing:
- the standalone
wrightandwright-lsprelease binaries; version.jsonwith the version,wright-result/v1contract identity, git commit, build timestamp, and the runtime-dependency claim ("requires": { "node": false, "overpy": false }).
This is the local staging path for checking the release package on the host platform. GitHub Actions builds the complete native matrix for both release channels.
- Quality gates:
cargo fmt --check,cargo clippy -D warnings,cargo test --workspace --all-targets --all-features. - First-party provider integration: bootstrap the explicitly pinned provider release and compile the minimal Wright-owned OPY input through the release binary.
- Benchmarks:
wright-benchwith declared regression thresholds (target/wright-bench-report.json). - Standalone proof: the packaged binaries run
compile/checkover the retained Wright test inputs withPATH=/usr/bin:/bin(Node and OverPy absent), andwright-lsp --versionreports the release version.
Any gate failure aborts the release before the version is stamped.
The binaries report the workspace implementation version (one authoritative
version = "<release version>" in [workspace.package]; every crate inherits it via
version.workspace = true). A stable release accepts only a stable
MAJOR.MINOR.PATCH version; prerelease and build metadata are rejected. It
synchronizes that version into Cargo.lock, version.txt, and the checked-in
dist/ manifests before committing the release version on main. wright version /
wright --version prints the CLI banner, wright-lsp --version prints the LSP
banner, and the LSP initialize response carries serverInfo.version. Every
wright-result/v1 envelope carries wright.version + wright.contract. The
release archive's version.json is the authoritative stamp for a shipped
artifact.
Wright has two release channels and one shared native build workflow:
- Every successful
CIrun onmaintriggers theworkflow_runpath of.github/workflows/release.ymlfor that exact commit. It builds and smoke-tests the native matrix, then publishes immutable objects underwright/nightly/<commit>/and advances onlywright/nightly/version. Nightlies do not create Git tags, GitHub Releases, or package-manager updates. - A maintainer explicitly dispatches
.github/workflows/release.ymlfrommain;stableis the default channel. An emptyversioninput selects the next patch version, while an explicit input selects a newer stableMAJOR.MINOR.PATCH; prerelease and build metadata are rejected. The workflow synchronizes the source and derived version state, commits that change directly tomain, and then builds and smoke-tests the native matrix from that post-bump commit. It publishes the versioned GitHub Release only after those artifacts and generated package-manager manifests pass validation. The stable R2 objects and pointer are updated only after the GitHub Release is complete.
No workspace crate is published to crates.io. Every workspace package
explicitly sets publish = false, so Cargo package publication cannot become
an accidental release surface. A failed publication can be rerun for the same
commit: stable tags and releases are checked for exact identity, while R2
versioned objects are reused only when their bytes match.
The stable release decision is an explicit workflow_dispatch of release.yml
from main. The workflow does not infer version changes from commits and does
not turn ordinary merges into stable releases. With the default stable channel,
an empty version input performs exactly the next patch bump; an explicit
version must be a stable MAJOR.MINOR.PATCH newer than the checked-in workspace
version. The
workflow commits the resulting Cargo.toml, Cargo.lock, version.txt, and
dist/ synchronization directly to main, so the operator does not prepare a
version commit or release PR. version.txt remains the product-version input
used by local distribution fixtures and must match the Cargo workspace version.
The nightly path is triggered by completed CI, not by a tag or Release event.
It verifies that the completed CI commit is still the current default-branch
head before building, so a stale, out-of-order completion cannot advance the
nightly pointer. Manual runs use the ref selected in the Actions UI; automatic
nightly runs use the exact workflow_run.head_sha.
workshop-rs is a normal compile-time Cargo dependency. Wright declares its
ordinary SemVer-compatible requirement in Cargo.toml; a compatible owner
release can be adopted by updating Cargo.lock without changing that manifest
requirement. A breaking owner release requires an explicit Wright migration,
manifest update, and validation before stable publication.
OPY and DEL/OSTW remain independently released provider products. Wright does
not bundle their versions into its product SemVer or dynamically replace
workshop-rs; provider availability and compatibility are negotiated through
the applicable LPP contract.
Artifacts use the stable scheme wright-<version>-<target-triple>.<ext>:
| Platform | Target triple | Archive |
|---|---|---|
| Linux x86_64 | x86_64-unknown-linux-gnu |
wright-0.1.0-x86_64-unknown-linux-gnu.tar.gz |
| macOS x86_64 | x86_64-apple-darwin |
wright-0.1.0-x86_64-apple-darwin.tar.gz |
| macOS arm64 | aarch64-apple-darwin |
wright-0.1.0-aarch64-apple-darwin.tar.gz |
| Windows x86_64 | x86_64-pc-windows-msvc |
wright-0.1.0-x86_64-pc-windows-msvc.zip |
Each archive contains wright (wright.exe), wright-lsp
(wright-lsp.exe), and version.json. Every archive has a
<archive>.sha256 checksum, and the Release also carries a combined
SHA256SUMS. Binaries are unstripped release builds; code
signing/notarization remains deferred. Package-manager distribution is
supported through the channels below.
install.sh uses the WrightKit R2 custom domain by default. GitHub Releases
remain the canonical stable release record and package-manager source; R2 is
the HTTP installer/object-delivery channel and also carries the separate
nightly channel. The stable R2 objects are exact copies of the archives in the
completed GitHub Release.
Pinned installs use immutable versioned objects:
https://releases.wrightkit.dev/wright/releases/<version>/wright-<version>-<target-triple>.<ext>
https://releases.wrightkit.dev/wright/releases/<version>/wright-<version>-<target-triple>.<ext>.sha256
Latest installs first read https://releases.wrightkit.dev/wright/latest/version,
then download the corresponding version-named archive and checksum from
/releases/<version>/. The release workflow publicly verifies every versioned
archive/checksum pair before writing that latest/version pointer, so the
installer cannot resolve a new version before its complete artifact set is
available. latest/version uses Cache-Control: no-store; all archive and
checksum paths are version-named and use long-lived immutable caching. This
avoids stale latest pointers without a separate Worker, API, or GitHub Releases
API lookup.
Versioned R2 objects are uploaded with If-None-Match: *; retries may reuse an
already-present object only after comparing its bytes to the release artifact.
The workflow retrieves every public copy and checks its bytes and cache headers
before it advances the channel pointer. Nightly objects are keyed by commit and
the wright/nightly/version pointer contains that commit, so a nightly can be
retrieved without changing stable installer resolution:
https://releases.wrightkit.dev/wright/nightly/version
https://releases.wrightkit.dev/wright/nightly/<commit>/wright-<version>-<target-triple>.<ext>
The download URL is deterministic:
https://github.com/wrightkit/wright/releases/download/v<version>/wright-<version>-<target-triple>.<ext>
For example, Linux x86_64 at v0.1.0:
VERSION=0.1.0
TARGET=x86_64-unknown-linux-gnu
BASE="https://github.com/wrightkit/wright/releases/download/v$VERSION"
curl -fsSL -O "$BASE/wright-$VERSION-$TARGET.tar.gz"
curl -fsSL -O "$BASE/wright-$VERSION-$TARGET.tar.gz.sha256"
shasum -a 256 -c "wright-$VERSION-$TARGET.tar.gz.sha256" # verify before use
tar -xzf "wright-$VERSION-$TARGET.tar.gz"
export PATH="$PWD/wright-$VERSION-$TARGET:$PATH"
wright --versionConsumers should verify the checksum before use; the recorded name inside a
.sha256 file is the archive basename, so shasum -a 256 -c works from the
directory holding both files. Windows consumers can download the .zip and
matching .zip.sha256, then extract with tar -xf or Explorer.
Windows x86_64 users can install the canonical release ZIP with the first-party PowerShell installer:
irm https://raw.githubusercontent.com/wrightkit/wright/main/install.ps1 | iexFor a pinned version or custom user-writable directory:
& .\install.ps1 -Version 0.1.0 -InstallDir "$env:LOCALAPPDATA\Programs\Wright\bin"The installer verifies the published .zip.sha256 before extraction, installs
both wright.exe and wright-lsp.exe, and runs both binaries directly for a
version smoke check. If the install directory is not already on the user
PATH, it prints a copyable PowerShell command and asks you to open a new
terminal. It does not require Cargo, npm, or a source checkout.
Each build leg smoke-tests its packaged archive (not workspace binaries):
it extracts the archive and runs the shared scripts/smoke-native.py contract
against the extracted binaries. The contract checks both version banners,
representative OPY compile/check paths, and first-party OPY provider bootstrap
from an empty provider store. The upload job re-verifies that every declared
target's archive and checksum are present before attaching them to the final
GitHub Release.
The normal CI distribution validation uses independent channel legs. It stages
a canonical-shaped local release archive and generated local metadata for
install.sh on Linux/macOS, install.ps1 on Windows, Homebrew on macOS, Scoop
on Windows, and WinGet on Windows. Each leg provisions or configures only its
own package-manager prerequisite, runs that channel's real installation
command, and runs the same native smoke against the installed binaries. These
channel checks are labelled separately from the native runtime smoke and do
not publish or modify any external package-manager repository.
Create a protected release environment if publication approval is required;
the stable publish-release job is the only job that uses it.
Configure these optional/required environment secrets:
GH_TOKENis the authorized release token with permission to push the stable version commit tomain, trigger and read the resultingCIrun, and write towrightkit/homebrew-tap. The stable workflow uses it for the main push and waits for that post-bump commit'sCIbefore publication.- The workflow's built-in
GITHUB_TOKENcreates the final GitHub Release and uploads its verified assets. CLOUDFLARE_ACCOUNT_ID,R2_ACCESS_KEY_ID, andR2_SECRET_ACCESS_KEYgrant the release workflow S3 API access to thewrightkit-releasebucket. The bucket must exposereleases.wrightkit.devas its production custom domain before a release; the workflow verifies that public route during publication.
All channels consume canonical released archives and none of them rebuild
Wright. install.sh and install.ps1 consume the R2 copies described above;
the package managers continue to consume GitHub Release archives.
Metadata lives under dist/, generated
by scripts/update-dist-manifests.py, and is regenerated by the release PR
maintenance step and again by the package-manifests job from the published per-target
checksums, then attached to the Release as
wright-<version>.homebrew.rb, wright-<version>.winget.zip, and
wright-<version>.scoop.json. The publish-tap job then pushes the generated
Homebrew formula into wrightkit/homebrew-tap automatically; see
dist/README.md for the required GH_TOKEN
secret and the per-channel publication process and boundaries.
| Channel | Platforms | Installs | Checksum control |
|---|---|---|---|
install.sh |
Linux x86_64, macOS arm64, macOS x86_64 | wright + wright-lsp into ~/.local/bin (or --dir) |
script verifies the published .sha256 before extraction |
install.ps1 |
Windows x86_64 | wright.exe + wright-lsp.exe into %LOCALAPPDATA%\Programs\Wright\bin (or -InstallDir) |
script verifies the published .sha256 before extraction |
Homebrew (wrightkit/tap) |
macOS arm64 + Intel | wright + wright-lsp formula |
per-arch sha256 in the formula |
WinGet (WrightKit.Wright) |
Windows x86_64 | wright + wright-lsp portable ZIP |
InstallerSha256 in the manifest |
Scoop (wrightkit bucket) |
Windows x86_64 | wright + wright-lsp ZIP |
hash in the manifest |
install.sh is the supported Unix installer: it detects the platform (with
explicit failures for unsupported OS/architecture combinations), resolves the
latest stable release from the R2 latest route by default or an exact
--version from an immutable versioned route on request, downloads the archive
and checksum, verifies the SHA-256 before extracting, installs both binaries,
and runs a post-install version smoke check. Its functional behavior is covered
by scripts/test-install.sh against a mock R2 route on Linux and macOS CI.
install.ps1 is the supported Windows x86_64 installer. It resolves the latest
stable release by default or an exact -Version, downloads the canonical ZIP
and matching .sha256, verifies the checksum before extraction, installs both
executables into the user-writable default directory or -InstallDir, and
runs a direct native version smoke check. When needed, it prints an actionable
user PATH update instruction. Its functional behavior is covered by
scripts/test-install.ps1 against a local test release server on Windows.
Standalone installations are also updatable in place: wright update
continues to consume the canonical GitHub Release artifacts and checksums (no
install.sh re-execution, no second build path), verifies the checksum before
replacing wright and wright-lsp, and refuses to overwrite
package-manager-managed binaries. See docs/cli.md for the
command contract and its WRIGHT_INSTALL_BASE_URL/WRIGHT_API_URL/
WRIGHT_INSTALL_OS/WRIGHT_INSTALL_ARCH test overrides. The installer has a
separate R2 route contract and only shares the platform overrides.
Package-manager availability is not instantaneous: the Homebrew tap is updated
automatically by the publish-tap job, while the Scoop bucket and WinGet
community-repository publication require their own external steps, and the
release pipeline does not assume them. Version drift is
detectable: CI runs scripts/verify-dist.py, which regenerates the checked-in
metadata for the current workspace version and fails on any mismatch, and the
release workflow generates the attached manifests from the release's own
checksum files.
The binary contract deliberately does not solve: crates.io publication, background/automatic update checks or silent startup updates, signed/notarized installers, MSI/MSIX/APT/RPM packages, or independent crate-by-crate versioning. Those can be added later when real consumer evidence justifies their maintenance cost.