Quirlv0.5.1
ProjectReleases and evidence

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.

The 0.1 distribution is the native GitHub Release plus Homebrew tap. Product crates are explicitly publish = false; crates.io and cargo install are not release targets until the path dependency graph, package metadata, and support contract are designed for them.

Local commands

Inspect the next version and deterministic notes without changing files:

cargo xtask release plan
cargo xtask release prepare

Apply the idempotent version and changelog preparation only after reviewing the plan, then verify the clean candidate:

cargo xtask release prepare --write
npm ci --prefix website
npm --prefix website run sync:docs
npm --prefix website run sync:reference
cargo xtask check
cargo xtask website-check

Preparation writes the reviewable canonical notes to RELEASE_NOTES.md and updates the local workspace package versions in Cargo.lock. The compiled website references embed the workspace version, so refresh both reference pages as well as the source mirrors. Review and commit the preparation with chore(release): prepare next version, then run cargo xtask release verify against the clean candidate. The preparation workflow performs these same generation and validation steps before opening its pull request.

Website documentation follows the current source tree. Keep installation and download labels on the latest published version until the new GitHub Release exists, and identify source-only features in first-session instructions. A prepared changelog entry is not evidence of publication. 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-aggregate

Each native workflow job also builds the benchmark harness from the same clean candidate and runs the enforcing performance gate against the packaged binary's independently calculated SHA-256. macOS jobs first turn off Spotlight indexing: on the 3-vCPU ARM64 runner, mds and mdworker indexed the checkout and the fresh build at over 200% CPU for minutes, and the published v0.3.0 source then missed the unchanged 21 ms first-prompt budget just like the 0.4.0 candidate. Before measuring, every job also waits up to four minutes for the one-minute load average to fall below half its CPUs. Budgets are not relaxed. A size or latency miss stops that package from reaching aggregation. The performance-<target> workflow artifact preserves the JSON report, including a failed budget, for seven days. These runner measurements supplement the named-terminal review in the release checklist. If packaging succeeded, the separate package-<target> artifact is retained for one day even when measurement fails. It is diagnostic evidence, not an approved release: the failed job still blocks aggregation and publication. Compare the executable inside that archive with the report's binary digest when investigating a failure.

Measured PTY children use a private home, project database, and XDG directories. Project discovery remains enabled against that empty home; it cannot reuse a developer's project cache or scan personal repositories. The harness retains the real Cargo and Rustup locations for the normal toolchain prompt probes.

Each native archive has exactly four top-level contracts: executable bin/quirl, Quirl's LICENSE, process-bridge THIRD_PARTY_NOTICES.md, and a deterministic target-specific THIRD_PARTY_LICENSES.txt. Packaging checks the locked release dependency graph against distribution/quirl-cli-third-party.tsv and fails if a dependency, declared license, source, platform closure, or bounded license text drifts.

Build 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-v2.json

To refresh completions for the currently shipped binary without rebuilding the model or cutting a release, build only completion-database, then merge it with the current version-scoped website manifest:

cargo xtask assets build --kind completion-database --identity current --output target/assets/completion-database
cargo xtask assets manifest --identity current --input target/assets --previous-manifest website/public/reference/vVERSION/asset-manifest-v2.json --output target/assets/asset-manifest-v2.json
cargo xtask assets rebase-manifest --input target/assets/asset-manifest-v2.json --base-url https://quirl.vercel.app/reference/vVERSION --output target/assets/asset-manifest-v2-website.json

The checked-in completion workflow performs the bounded publication transaction: it copies new digest-named payload and notice files without overwriting existing names, verifies every retained sibling again, admits the complete result with the exact tagged binary, validates the complete website, and updates the small manifest last. It refuses to publish before that version's immutable tag and GitHub Release exist.

Render 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-aggregate

Rendering 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:

EnvironmentAuthorityContents
releaseQuirl repository onlyGitHub-provided token with contents: write; required reviewer for publication
release-assetsAsset source onlyNarrow read credentials needed to acquire the model/database inputs; none when inputs are public
homebrew-tapTap repository onlyGitHub 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 HOMEBREW_APP_CLIENT_ID as a repository variable and store HOMEBREW_APP_PRIVATE_KEY on the protected homebrew-tap environment. The release workflow calls the same-repository Homebrew workflow with secrets: inherit; without that forwarding, the protected environment's private key was unavailable in the reusable call for v0.2.0 even though the standalone workflow worked. The tap job retains its protected environment and checks only whether the required variable and secret are present before creating its narrowly scoped token; it never prints either credential. Give the release environment a required reviewer, enable immutable releases, and protect release tags from mutation. The preparation workflow uses the repository's built-in token with job-local contents: write and pull-requests: write; the publication job is the only other job that receives contents: write.

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

  1. Merge the preparation PR and select its clean commit as candidate A.
  2. Run the local or hosted verification gate against A.
  3. Package all four native targets and build the separate completion/model assets. Each binary must report the planned version and A's commit.
  4. Aggregate once. Review the release manifest, checksums, notes, and provenance before approving the protected release environment.
  5. Run cargo xtask release tag --expected-tag vVERSION --write to 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.
  6. Wait for the release-dispatched completion workflow to seed or advance the exact version's website channel and smoke-admit it with the released binary.
  7. Generate Formula/quirl.rb from that published release manifest on a new tap branch and open a PR. Never push the tap default branch.

Before a tag or GitHub Release asset exists, fix a binary-release failure and rerun from a new clean candidate. After either 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 release asset escaped. Website channel manifests may advance independently, but only after their new content-addressed bytes exist; an existing payload URL is never overwritten.

If the tap PR job fails after publication, the Quirl release remains valid. Preserve the failed job's logs and retry the standalone Homebrew workflow against the existing immutable tag:

gh workflow run homebrew.yml --repo niklas-heer/quirl --ref main -f tag=vVERSION

The workflow regenerates and checks the formula from the published manifest, then requires the normal protected homebrew-tap environment review before opening or updating its tap PR. Review that PR and the exact archive digests before merging; do not push directly to the tap default branch. This recovery does not rebuild or replace release artifacts or move the tag. The standalone path succeeded for v0.2.0 in run 33990534633.

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 an initial completion SQLite database, command model bundle, their retained license notices, and asset-manifest-v2.json as GitHub Release assets. Homebrew installs quirl plus the redistribution documents; it must not fetch runtime assets in install or test. The formula installs LICENSE, THIRD_PARTY_NOTICES.md, and THIRD_PARTY_LICENSES.txt below share/quirl/licenses so the redistribution notices accompany the binary.

The live website manifest is version-scoped, for example /reference/v0.1.0/asset-manifest-v2.json, and may advance without a binary release. Each entry has its own source revision and epoch, exact binary compatibility, format, byte size, digest, and content-addressed URL. A completion refresh replaces only that logical record and retains the current model. The workflow publishes new bytes before the manifest, never deletes or overwrites a payload, and admits at most 32 completion generations per binary version.

Quirl stores downloaded assets below the platform data/cache roots:

  • macOS: $HOME/Library/Application Support/quirl/assets with retry/cache metadata below $HOME/Library/Caches/quirl/assets;
  • Linux: $XDG_DATA_HOME/quirl/assets or $HOME/.local/share/quirl/assets, with retry/cache metadata in $XDG_CACHE_HOME/quirl/assets or $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 update

Cancellation 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.

On this page