Release and downloadable-asset operations
Canonical Quirl project documentation synced from docs/releasing.md.
Release policy lives in Quirl's Rust xtask. Workflow files are orchestration
only. ADR 0026
defines the identity, immutability, recovery, and storage contracts.
Local commands
Inspect the next version and deterministic notes without changing files:
cargo xtask release plan
cargo xtask release prepareApply the idempotent version and changelog preparation only after reviewing the plan, then verify the clean candidate:
cargo xtask release prepare --write
cargo xtask check
cargo xtask release verifyPreparation writes the reviewable canonical notes to RELEASE_NOTES.md and
updates the local workspace package versions in Cargo.lock. After the release
PR is merged, preview the exact tag operation with cargo xtask release tag --expected-tag vVERSION; publication adds --write to create and non-force
push that exact lightweight tag.
Platform jobs package one native target. Aggregation accepts only the complete target set for one version and commit:
cargo xtask release package --target aarch64-apple-darwin --output target/release-package
cargo xtask release aggregate --input target/release-input --output target/release-aggregateBuild separate downloadable assets and their strict manifest:
cargo xtask assets build --kind completion-database --output target/assets
cargo xtask assets build --kind command-model --output target/assets
cargo xtask assets manifest --input target/assets --output target/assets/asset-manifest-v1.jsonRender and validate the tap formula from the aggregate release manifest:
cargo xtask homebrew render --release-manifest target/release-aggregate/release-manifest-v1.json --expected-tag vVERSION --tap-root ../homebrew-tap --write
cargo xtask homebrew check --tap-root ../homebrew-tap --release-manifest target/release-aggregate/release-manifest-v1.json --package-root target/release-aggregateRendering is a dry run by default. The example writes only because it assumes a
dedicated tap update branch. The checkout tag, HEAD, manifest commit, version,
files, URLs, and hashes must all agree. The package check uses an already
downloaded host archive and runs quirl --version without fetching runtime
assets. Never edit generated hashes or platform URLs by hand.
GitHub configuration
Use protected environments so untrusted pull requests never receive secrets:
| Environment | Authority | Contents |
|---|---|---|
release-preparation | Quirl preparation PR only | Quirl-scoped App client ID/private key; protected manual workflow |
release | Quirl repository only | GitHub-provided token with contents: write; required reviewer for publication |
release-assets | Asset source only | Narrow read credentials needed to acquire the model/database inputs; none when inputs are public |
homebrew-tap | Tap repository only | GitHub App client ID and private key; fine-grained PAT fallback |
Repository variables may contain the public repository names and GitHub App
client ID. Secrets contain only the App private key, source credentials,
or fallback PAT. Do not store checksums, versions, URLs, or policy as variables;
xtask derives them from candidate inputs.
For the checked-in workflows, define RELEASE_APP_CLIENT_ID and
HOMEBREW_APP_CLIENT_ID as repository variables. Store
RELEASE_APP_PRIVATE_KEY on the protected release-preparation environment and
HOMEBREW_APP_PRIVATE_KEY on the protected homebrew-tap environment. Give
the release environment a required reviewer, enable immutable releases, and
protect release tags from mutation. Keep the repository's default workflow
token read-only; only the publication job receives contents: write.
The release-preparation App is installed only on niklas-heer/quirl with
repository Contents: read and write and Pull requests: read and write. Its
token is minted only after preparation and the canonical gate pass, allowing
the resulting PR commit to trigger normal CI. Rotate it with the same
replace-test-revoke sequence as the tap App.
The GitHub App is installed only on niklas-heer/homebrew-tap and has
repository Contents: read and write and Pull requests: read and write.
Metadata read is implicit. It has no Quirl release authority. The fallback PAT
is fine-grained, restricted to the same repository and permissions, and has a
short expiry. Rotate by creating a replacement, updating the protected
environment, exercising a formula-render dry run and branch PR, then revoking
the old credential. Rotate immediately after suspected disclosure.
When the App is unavailable, use the PAT only from a trusted local checkout:
render with cargo xtask homebrew render --expected-tag vVERSION --write, run
the manifest/package form of cargo xtask homebrew check shown above, create a
non-default tap branch, push that branch with the PAT, and open the PR. Do not
place the PAT in a command argument, repository remote, workflow file, or log;
provide it through the credential manager or GH_TOKEN for the single PR
operation, then unset it.
Workflows pin third-party and GitHub actions to full commit SHAs and declare
permissions per job. The supported native matrix uses explicit current standard
runner labels: ubuntu-24.04 (Linux x86_64), ubuntu-24.04-arm (Linux ARM64),
macos-15-intel (macOS x86_64), and macos-15 (macOS ARM64). Recheck GitHub's
hosted-runner reference before changing those labels.
Publication and recovery
- Merge the preparation PR and select its clean commit as candidate A.
- Run the local or hosted verification gate against A.
- Package all four native targets and build the separate completion/model assets. Each binary must report the planned version and A's commit.
- Aggregate once. Review the release manifest, checksums, notes, and
provenance before approving the protected
releaseenvironment. - Run
cargo xtask release tag --expected-tag vVERSION --writeto create the immutable exact-candidate tag, then create the GitHub Release and upload only the aggregate's exact bytes. Refuse a conflicting tag, release, or asset. - Generate
Formula/quirl.rbfrom that published release manifest on a new tap branch and open a PR. Never push the tap default branch.
Before a tag or public asset exists, fix a failure and rerun from a new clean candidate. After a tag or any asset is public, do not move, delete-and-recreate, or overwrite it as routine recovery. Preserve logs and manifests, correct the problem in source, and publish a new SemVer version. An empty unpublished draft may be removed only after verifying that no tag or asset escaped.
If the tap PR fails, the Quirl release remains valid. Regenerate the formula from its published manifest and retry with a new tap branch. If asset hosting is temporarily unavailable, existing installed assets and Quirl's degraded mode remain valid; do not repoint a published manifest to mutable replacement bytes.
Runtime assets and offline behavior
Version 0.1 publishes the completion SQLite database, command model bundle, and
asset-manifest-v1.json as versioned GitHub Release assets. Homebrew installs
only quirl. It must not fetch either runtime asset in install or test.
The asset manifest binds both logical assets to the exact release version,
candidate commit, and source epoch as well as their individual format,
compatibility, byte-size, digest, and immutable URL contracts.
Quirl stores downloaded assets below the platform data/cache roots:
- macOS:
$HOME/Library/Application Support/quirl/assetswith retry/cache metadata below$HOME/Library/Caches/quirl/assets; - Linux:
$XDG_DATA_HOME/quirl/assetsor$HOME/.local/share/quirl/assets, with retry/cache metadata in$XDG_CACHE_HOME/quirl/assetsor$HOME/.cache/quirl/assets.
Missing assets never delay the first prompt. Completion uses builtins and local discovery, and natural-command retrieval uses its lexical/degraded path while a bounded background update is eligible. An offline launch reports status without discarding a previously valid asset. Transient failures retry on later launches with capped backoff; integrity and compatibility failures require explicit operator action.
Use the CLI asset commands to inspect state and request bounded work:
quirl assets status
quirl assets retry
quirl assets updateCancellation removes the partial staging file. A valid installed generation is not removed until its replacement passes exact size, SHA-256, compatibility, format, and private-file checks.
Adding S3 or a mirror
Add a provider that resolves a versioned logical asset to an immutable URL and bounded byte stream. Do not change manifest admission, compatibility checks, download limits, retry classification, or the install transaction. Publish the same digest under all mirrors; provider failover may change where bytes are read, never which bytes are accepted.