Files

12 KiB

Releasing

Gufo uses squash merges and Conventional Commit pull request titles to keep main machine-readable. A pull request may contain arbitrary work-in-progress commits; its title becomes the single commit retained on main.

Pull request titles

Titles have this form:

<type>[(optional-scope)][!]: <description>

The accepted types are:

Type Purpose Release effect
fix Correct user-visible behavior Patch
perf Improve user-visible performance Patch
feat Add backward-compatible functionality Minor
docs Change documentation only None
test Change tests only None
build Change the build system or dependencies None by default
ci Change automation only None
chore Repository maintenance None
refactor Restructure code without changing behavior None
style Change formatting without changing behavior None
revert Revert an earlier change Determined from the reverted change

Use a short, lowercase scope when it adds useful ownership context. Examples:

fix(cache): preserve image prefixes after cancellation
perf(qwen): make prompt tokenization linear
feat(server): support constrained JSON output
docs: explain the release policy
feat(server)!: remove the legacy completion endpoint

Breaking changes must include ! before the colon. Do not rely only on a pull request body footer: the title is the canonical release signal retained by a single-line squash merge.

Version policy

Release versions follow Semantic Versioning. The compatibility surface is the documented command-line interface, HTTP API and public Nix flake outputs. A change is breaking when an existing documented use must be changed to keep working.

Before 1.0.0, Gufo uses this policy:

  • fix and perf changes increment the patch version.
  • feat changes increment the minor version.
  • breaking changes increment the minor version.
  • 1.0.0 is an explicit stability decision and is never selected implicitly.

After 1.0.0, breaking changes increment the major version. A release uses the largest required increment among all changes since the previous release. Ordinary pull requests do not bump the version; release automation owns version and changelog updates.

Git tags use vX.Y.Z. A released version is immutable. Development builds keep their exact Git revision so diagnostics can identify the source independently of the release version.

Release flow

version.txt is the canonical release version. A maintainer starts a release by running the Release automation workflow from the GitHub Actions tab ("Run workflow" on main); it has no schedule. The workflow reads the Conventional Commit history after the latest release and opens or updates one release pull request. That pull request updates version.txt, CHANGELOG.md and the release manifest. It receives the same CI as any other change, plus the complete Devlab functional suite on Strix Halo (other pull requests run its smaller PR tier). The workflow waits up to 160 minutes for the pull request title, the repository checks and devlab/functional. If any of them fails, errors or is still pending, it leaves the pull request open without publishing a release and comments on it to notify the maintainers.

After the checks pass, the workflow squash-merges the release pull request and immediately creates the immutable tag and GitHub Release. Do not edit the version, changelog or release manifest in an ordinary feature or fix pull request.

Nix keeps release identity and source identity separate. The default package is a development build and reports gufo version development (<revision>). Official artifacts build the release package from a vX.Y.Z tag and report gufo version X.Y.Z (<revision>). Both package outputs retain the exact flake revision; only the explicitly selected release output reads the semantic version from version.txt.

After Release Please creates a GitHub Release, the same workflow sends its exact vX.Y.Z tag to the Toolboxes repository. Toolboxes builds the release package from that tag and publishes OCI images tagged X.Y.Z, X.Y and latest. Rolling Toolboxes images remain separate: they follow Gufo main and use edge and sha-<revision> tags.

In parallel with the OCI builds, Toolboxes calls Gufo's reusable Windows workflow from main, with the same exact release tag as its source checkout. It uses the existing Windows toolchain and host contract suite, sets the release version and source revision explicitly, and checks the packaged gufo.exe --version and platform diagnosis with only the bundled runtime on PATH. This does not run the manual Windows Devlab GPU/model checks.

The called workflow uploads its artifact to the Toolboxes workflow run. Toolboxes then attaches gufo-vX.Y.Z-windows-x86_64.zip and its .sha256 checksum to the existing Gufo GitHub Release. The ZIP includes gufo.exe, the HIP and other runtime DLLs, kernel directories and notices. Extract the entire archive before running the executable; use gufo.exe serve for the HTTP server.

Merge the reusable Windows workflow in Gufo before enabling its Toolboxes caller. Toolboxes needs RELEASE_APP_CLIENT_ID and RELEASE_APP_PRIVATE_KEY available to its release workflow. The app must be installed on Gufo with Contents write permission; Toolboxes' own GITHUB_TOKEN cannot upload assets to Gufo. The upload job creates a fresh app token after the build and does not overwrite existing release assets. Toolboxes' manual release workflow accepts a tag for recovery runs.

Extension API and build selection

Gufo's CLI, HTTP API and existing public flake outputs retain the release compatibility rules above. The separately versioned source extension API and the future experimental flake extensions argument are outside that surface. A source extension API break alone does not require !; a CLI/HTTP/public flake break made during migration does. Extension API versioning and its generated change log remain independent of release automation's ownership of Gufo's version/changelog.

Before 1.0, the API version is 0.MINOR.REVISION: a minor bump may break source compatibility, while revision bumps are additive. Extensions request a revision floor within one minor series. From 1.0, minor bumps are additive and breaking changes increment the major; retain an adapter from the preceding major for at least 60 days where feasible and report deprecations at configure time. Fix in-tree extensions in the same PR. Compatibility means rebuilding extension source against a compatible Gufo release; there is no binary ABI promise. Experimental hooks have no stability guarantee and graduate only by explicit review after their own 60 unchanged days. API 1.0 remains gated by all RFC criteria, including the measured text surface, external pilot, independently pinned external text compatibility fixture, five additive API features and an actual 60-day period. In-tree migrations alone do not establish external compatibility. No 1.0 stability claim is made today.

Every declaration/contract change in the generated SURFACE transitive closure, including forwarded core/cache headers, and every public CMake/schema contract change requires the ext-api PR label and a new, uniquely named Markdown change record under include/gufo/ext/changes/<major>.<minor>/ (for example serving-metadata.md). Use a descriptive filename and heading without an assigned API version; see the record instructions. Describe the concrete declaration or contract change. Additive API PRs do not edit version constants or a shared changelog. Existing records must never be modified, renamed or removed, including after a release. The one-time normalization of the original numbered records removes assigned versions while preserving their contract descriptions and the computed API revision.

api-series.json defines major, minor and revision_base. The revision is revision_base + number of revision records in the active series; CMake generates the public version constants and uses the same value for manifest compatibility checks. The baseline is fixed within a series. baseline.md describes the initial contracts and is excluded from the revision count. Records in older series remain permanent but do not count toward the active series. A breaking change explicitly advances the compatibility series, sets its initial baseline, and adds a record in that series. Review the compatibility transition; it is not inferred from a record's prose.

Two parallel additive PRs each add a different, descriptively named record. Their merge counts both, advancing the revision twice without editing a shared counter or reconciling assigned versions in filenames or headings. Branch revisions are provisional until merged; numeric revision floors promise compatibility along the canonical main history, not between divergent branches. An extension using an API available only on a branch must pin that Gufo source revision, then choose its minimum API version from main after the feature merges. Older minimum requirements in existing manifests remain valid and need no bump. Source archives and Nix builds compute the same version without Git metadata.

Generate a combined changelog with:

python3 tools/ci/check-extension-api.py --changelog /tmp/gufo-extension-api-changes.md

The generated log groups all records by series and preserves the assigned headings of converted historical revisions. New record ordering does not assign per-record revision numbers. CMake regenerates its version header automatically when records are added. The generated header is provided through the extension runtime's public include requirements; build extensions through CMake rather than using the source include directory alone.

The PR workflow runs tools/ci/check-extension-api.py --base <base-sha> --labels <json-label-list>; the hosted suite checks inventory drift and policy fixtures. The initial token check conservatively includes inline bodies, initializers and CMake/schema statements; comments/whitespace do not trigger it. Card10 will expand classification of stable/platform/experimental/private headers and measure declarations; the initial inventory is no claim that all future text interfaces are already stable.

nix build / .#default, every normal CMake preset and both Devlab build paths use discovery: all supported in-tree extensions are selected, including ones not in extensions.lock. .#release and the release-tag Windows ZIP use GUFO_EXTENSIONS_FROM_LOCK=ON. Toolboxes can select the same policy through gufo.override { extensionsFromLock = true; }; publishing actual Toolboxes images remains an external distribution deliverable. The lock is an exact name list, including every family dependency, not an implicit dependency closure. An unavailable platform or missing dependency in the lock is an error in every configure mode, including plain discovery, so a stale lock also fails development builds. GUFO_EXTENSIONS is also an exact semicolon list; * selects discovery and an explicitly empty list selects none. An explicit list and lock mode conflict, and an empty entry from a stray ; is rejected.

Bootstrap: the current lock contains no migrated extensions. Existing legacy models still build and serve through their existing routes in both modes; they are not falsely reported as extension providers. Lock mode controls discovered extension manifests only. The pilot and later model cards populate this list as providers move, while Card14 adds metadata for remaining models before code migration. gufo --version preserves the version line first, then reports API, selection and extension provenance. gufo diagnose --section extensions [--json] inspects provenance without model loads/HIP probes; ordinary human diagnose also lists extensions. Existing diagnostic JSON schema is unchanged.

Hosted CPU checks build a real lock-mode executable, report unshipped extensions and reject shipped/unshipped artifact-key, option-name and model-ID collisions. They do not establish HIP release behavior or performance parity. Run affected functional, numerical and performance gates on the actual shipped HIP lock artifact as well as the default artifact when providers migrate; include dialect selection/fallback and forced tool choice. Keep unshipped discovery evidence separate. The RFC's strict-subset/default-Devlab inference is not accepted as release qualification.