CI and Release
Status: Current Last updated: 2026-09-15 12:06 EDT
Pre-Merge Verification
Run the shared local gate from Developer Verification Checks:
just gate
This runs the checks used by per-push CI, including doctests, both Rust
workspaces, generated-artifact currency, and the book. Wait for GitHub Actions
on the exact pushed commit before announcing it as ready. Release-only checks
run separately through just release-lint. That recipe first checks app versions
and the changelog section/link, before formatting and compiler checks, so a
missing release entry fails before compilation.
Generated artifact drift
After changing grammar, spec, or a registry, run just regen, then just test.
The regeneration recipe builds derived artifacts in dependency order; currency
tests detect stale output. Never hand-edit generated artifacts.
See Spec Workflow and spec/CLAUDE.md for the current
source-of-truth guidance.
Release Process
TalkBank/chatter is the public release source of truth. release.yml
(cargo-dist) builds the CLI artifacts and publishes the GitHub Release;
release-desktop.yml, called from it, creates the unpublished draft and adds
the desktop installers first. Signing differs by platform as described below.
The full publication order is in docs/strategy/coordinated-release.md.
Cutting a release: the two-command procedure
The version literal lives in many places (the workspace version, every
internal path-dep pin, the desktop package.json, the CHANGELOG section),
and the tag is the release trigger, so both steps are mechanized and
fail-closed. Hand-editing version fields or tagging with raw git tag is
how releases break (v0.1.1 shipped a desktop version mismatch; v0.5.0
tagged a bump commit before its CI reported and the desktop build died on
drift CI would have caught). The procedure:
just release-bump X.Y.Zrewrites the canonical[workspace.package] version, everypath = "crates/…"pin, andpackage.jsonand both root-version fields inpackage-lock.json, then refreshes both Rust lockfiles (root +spec/). The app-version check exercises this command against temporary manifests and lockfiles before checking the checkout, including independent drift in each lockfile version and preservation of dependency versions.- Write the
## [X.Y.Z]CHANGELOG section (the one deliberately manual step; every gate enforces its presence). - Format, run
just release-lintandjust gate, then squash the commits since the last push into one release commit whose message is the CHANGELOG section. Verify the gate on the squashed tree and, with maintainer authorization, push and wait for CI on that commit. The content stamp survives a squash that leaves the checked bytes unchanged. Push rarely; preserve already-published commits instead of rewriting history at release time. just release-tag X.Y.Ztags and pushesvX.Y.Z, refusing on a dirty tree, an unpushed HEAD, any version-copy drift, a missing CHANGELOG section, or CI/Cross-platform not yet green on the exact tagged commit.
After the tag: release-tag-dispatch.yml dispatches release.yml for that
tag, which builds, runs the desktop publication job, publishes and then adds
the app banner; verify the release page carries the CLI archives, the LSP
standalone artifacts, the desktop installers and the CHANGELOG notes before
announcing.
Workflows that actually exist in this repo
| Workflow | Purpose | Notes |
|---|---|---|
.github/workflows/ci.yml | Main build/test/book CI | Primary shared signal on pushes and PRs |
.github/workflows/cross-platform.yml | Cross-platform build coverage | Supplements the main CI workflow |
.github/workflows/crates-io-foundation.yml | First-wave crates.io readiness | Checks foundation-crate metadata, package surfaces, hold-backs, and publish order |
.github/workflows/release-tag-dispatch.yml | Tag trigger | On a version tag push, dispatches release.yml for that tag |
.github/workflows/release.yml | cargo-dist release automation | Generated by dist (dist generate; never hand-edit). Builds CLI artifacts, calls the desktop publish job, then publishes the draft in its announce step |
.github/workflows/release-desktop.yml | Desktop installer release automation | dist publish job: creates the draft with dist’s announcement title and CHANGELOG body, builds and uploads the installers and updater bundles, and verifies the candidate; workflow_dispatch runs build-only |
.github/workflows/release-app-banner.yml | Release page banner | dist post-announce job: prepends the desktop app banner to the published release notes |
.github/workflows/release-lint.yml | Release-time lint | just release-lint: clippy over both workspaces plus the feature-off build. Runs on a version tag and on workflow_dispatch, never per push |
.github/workflows/clippy-rolling.yml | New-stable clippy drift detection | Weekly maintenance workflow |
Current release stance
release.ymlis about workspace artifact packaging via cargo-dist, not about crates.io publication.- The first-wave crates.io path is documented separately in
Crates.io Publication and is checked by
just crates-io-foundation-checkplus.github/workflows/crates-io-foundation.yml.
Desktop release workflow: how the release jobs compose
dist-workspace.toml sets create-release = false, github-release = "announce", publish-jobs = ["./release-desktop"] and post-announce-jobs = ["./release-app-banner"]. After the CLI artifacts build, release.yml calls
release-desktop.yml, which creates the draft release with dist’s announcement
title and body, builds the Tauri installers and updater bundles, uploads them
and verifies the candidate. Only when that job succeeds does the announce step
upload the CLI archives, checksums and installer scripts and publish the draft;
the banner job then edits the published notes. Nothing polls, and a failed
desktop build leaves an unpublished draft. Two platform notes baked into the
workflow:
- macOS: Tauri signs, notarizes, and staples the
.app, but NOT the.dmgit wraps around it. The workflow therefore submits the.dmgitself to the notary service and staples it, then verifiescodesign,spctl, andstapler validateon both artifacts. The signing identity is supplied via environment, never hardcoded intauri.conf.json. - Windows / Linux: artifacts are currently unsigned by decision; see
docs/strategy/distribution-and-signing.md(“Decisions, 2026-06-12”) and the SmartScreen guidance in the install docs.
Release secrets (Actions secrets on this repository)
Required by the macOS jobs of release-desktop.yml (and by cargo-dist
macOS codesigning if macos-sign is enabled, which uses the separate
CODESIGN_* names documented in the strategy doc):
| Secret | Content |
|---|---|
APPLE_CERTIFICATE | base64-encoded Developer ID Application .p12 |
APPLE_CERTIFICATE_PASSWORD | password for the .p12 |
APPLE_SIGNING_IDENTITY | full identity string, Developer ID Application: <Name> (<TEAMID>) |
APPLE_API_KEY | App Store Connect API key ID (notarization) |
APPLE_API_ISSUER | App Store Connect issuer ID |
APPLE_API_KEY_CONTENT | contents of the AuthKey_*.p8 file |
Rotation: replacing the certificate or notary key means updating these secrets and nothing else; no workflow edits are needed. A maintainer must re-create all of them on any new repository (secrets do not transfer).
The development loop
Set on 2026-08-27, after a single parser fix cost a day to the process around it rather than to the fix.
- Inner loop:
just test. Write the failing test or the type change first, then make it green.clippyandfmtare run before a release, not per edit. - After any change under
grammar/,spec/or a registry:just regen, thenjust test. Every derived artifact has a currency test, and they are far cheaper to satisfy together than one gate run at a time. - Before committing: review the final diff once.
- Before pushing:
just gate, once. It mirrors per-push CI exactly, so CI is a confirmation and never a discovery. The pre-push hook refuses a push without the stamp; the stamp hashes tree content, so a gate run on uncommitted changes stays valid once the same bytes are committed. Clippy and the feature-off build arejust release-lint, run before a release. - Releasing: format,
just release-lint, gate, squash every commit since the last push into one release commit carrying the changelog section, gate once more, push, wait for CI, thenjust release-tag. Push rarely and preserve already-pushed history.
Nothing on this path needs data that is not in the repository.
This page last changed: 2026-09-15 (commit bb4bef82). The whole book last changed: 2026-09-15 (commit bb4bef82).