Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/finalize-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Copy to .github/workflows/finalize-release.yml in the tool repo.
#
# Triggered by repository_dispatch from homebrew-tap's publish workflow
# after bottles are uploaded. Publishes the draft GitHub Release.
# after the formula bump lands on the tap. Marks the GitHub Release latest.
name: Finalize Release

on:
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
# crate: agentnative (crates.io name) | bin: anc (installed executable)
#
# Pipeline: check-version -> build (7 targets) -> attest (build provenance, and a
# CycloneDX SBOM of the binary) -> crates.io -> release (non-draft) -> verify the
# published files against their attestations -> homebrew bottle build -> bottles
# upload back to release assets -> finalize-release marks the release final.
# CycloneDX SBOM of the binary) -> crates.io -> release (visible, not latest) ->
# verify the published files against their attestations -> homebrew-tap tests the
# formula bump from the archives and lands it -> finalize-release marks the
# release latest.
#
# The `build` job is the only place a release compiles its binaries. The tap
# installs the published archives and builds no bottle.
name: Release

on:
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ cargo binstall agentnative
# https://github.com/brettdavies/agentnative-cli/releases
```

The Homebrew formula installs the pre-built archive the release publishes for your platform and compiles nothing. The
tap signs the bottles it builds from those archives, and `brew verify brettdavies/tap/agentnative` checks one against
that attestation.
The Homebrew formula installs the pre-built archive the release publishes for your platform, checked against the
checksum the formula pins, and compiles nothing. The tap pins a checksum only after it has verified the archive against
the release's build-provenance attestation; [Verifying an
archive](https://github.com/brettdavies/homebrew-tap#verifying-an-archive) in the tap's README has the command that
repeats the check.

Each release archive carries a build-provenance attestation and an attested SBOM, signed by the release workflow. To
check that an archive you downloaded was built by it, from this repository:
Expand Down
23 changes: 10 additions & 13 deletions RELEASES-POSTFLIGHT.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Sub-commands let you re-run one verification in isolation:
| Sub-command | What it checks | Source of truth |
| ------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------- |
| `release` | `release.yml` on the tag push: `gh run view ... --json conclusion` is `"success"` | `gh run view` |
| `tap` | `brettdavies/homebrew-tap` `update-formula` (repository_dispatch) + `Publish bottles` (workflow_run) SUCCESS | `gh run list -R brettdavies/homebrew-tap` |
| `tap` | `brettdavies/homebrew-tap` `update-formula` (repository_dispatch) + `Publish formula` (workflow_run) SUCCESS | `gh run list -R brettdavies/homebrew-tap` |
| `finalize` | `finalize-release.yml` callback ran in this repo (cross-repo dispatch loop closed) | `gh run list -e repository_dispatch` |
| `make-latest` | GitHub Release `vX.Y.Z` is non-draft, non-prerelease, and `releases/latest` resolves to it | `gh api /releases/latest` |
| `crates` | `crates.io` shows `agentnative vX.Y.Z` published | `crates.io` index API |
Expand All @@ -56,10 +56,10 @@ Run immediately after the tag push triggers `release.yml`.
Trusted Publishing, and dispatches `update-formula` into the homebrew-tap. Run `scripts/release/postflight.sh release`
for the automated check.
- [ ] **Homebrew-tap dispatch landed.** `gh run list -R brettdavies/homebrew-tap --limit 5` should show a recent
`update-formula` (event=repository_dispatch) and a `Publish bottles` (event=workflow_run) both SUCCESS. The bottles
workflow auto-merges the formula bump PR and pushes an `agentnative: add <version> bottle.` commit to tap `main`. Run
`scripts/release/postflight.sh tap` for the automated check.
- [ ] **`finalize-release.yml` callback ran.** After the bottles publish, the tap dispatches back to this repo and the
`update-formula` (event=repository_dispatch) and a `Publish formula` (event=workflow_run) both SUCCESS. The publish
workflow pushes the formula bump to tap `main` as one `chore(agentnative): bump to v<version>` commit, with no bottle:
the formula installs this release's archives. Run `scripts/release/postflight.sh tap` for the automated check.
- [ ] **`finalize-release.yml` callback ran.** After the formula lands, the tap dispatches back to this repo and the
callback flips the GitHub Release `make_latest: true`. Check `gh run list -e repository_dispatch --limit 3`; expect a
`finalize-release` SUCCESS. Run `scripts/release/postflight.sh finalize` for the automated check.
- [ ] **GitHub Release marked latest.** `gh api repos/brettdavies/agentnative-cli/releases/latest --jq .tag_name`
Expand All @@ -70,14 +70,11 @@ Run immediately after the tag push triggers `release.yml`.
- [ ] **`cargo install agentnative --version <new>` on a clean environment** resolves and runs. Drive on a fresh
container or a sibling machine so the local `~/.cargo/bin` isn't polluted. Confirms the publish landed all package
data and the installer can reconstruct `anc` from source.
- [ ] **`brew update && brew install brettdavies/tap/agentnative`** on a fresh prefix resolves the new bottle and
`anc --version` reports the new tag. Drive on a throwaway prefix (`HOMEBREW_PREFIX=/tmp/brew-postflight-X brew ...`).
Confirms the homebrew-tap end of the cross-repo dispatch chain landed cleanly and the published bottle SHA matches the
formula.
- [ ] **Every Homebrew bottle verifies against its attestation.**
`brew verify --os=all --arch=all brettdavies/tap/agentnative` reports `has a valid attestation` for each bottle. The
tap signs the bottles in its `publish.yml`; this is the check Homebrew runs for a user who sets
`HOMEBREW_VERIFY_ATTESTATIONS`, and a failure means that user cannot install the formula.
- [ ] **`brew update && brew install brettdavies/tap/agentnative`** on a fresh prefix downloads this release's archive
for the platform and `anc --version` reports the new tag. Drive on a throwaway prefix
(`HOMEBREW_PREFIX=/tmp/brew-postflight-X brew ...`). Confirms the homebrew-tap end of the cross-repo dispatch chain
landed cleanly and the archive matches the checksum the formula pins. The install compiles nothing and pours no
bottle; the tap verified each archive against this release's attestation before it pinned the checksum.
- [ ] **`cargo binstall agentnative`** (without `--version`) resolves to the new tag and installs the matching prebuilt
binary. Confirms the GitHub Release asset layout (binary + completions + licenses, expected archive naming) matches
binstall's asset-resolution rules and the `[package.metadata.binstall]` overrides in `Cargo.toml`. Drive on a clean
Expand Down
11 changes: 6 additions & 5 deletions RELEASES-RATIONALE.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,12 @@ publish (`v0.1.0`) requires a regular crates.io API token because Trusted Publis

### Why `make_latest: false` then `finalize-release`

The GitHub Release is created visible-but-not-latest (`make_latest: false`) so `cargo-binstall` and `/releases/latest`
don't 404 during the bottle-build window, but the release isn't yet promoted to "Latest" while bottles upload. After the
homebrew-tap workflow uploads bottles to this repo's release assets, it dispatches `finalize-release` back to this repo,
which idempotently flips `make_latest: true`. End result: crate on crates.io, GitHub Release marked latest, Homebrew
formula updated with bottles, all atomically advertised.
The GitHub Release is created visible-but-not-latest (`make_latest: false`) so its archives resolve by tag at once,
which the tap's bump needs, while `cargo-binstall` and `/releases/latest` keep resolving the previous version until
Homebrew can install the new one. The tap verifies the archives, tests the formula bump on four platforms, and lands
it; it builds no bottle, so nothing is uploaded back to this repo's release. It then dispatches `finalize-release` back
to this repo, which idempotently flips `make_latest: true`. End result: crate on crates.io, GitHub Release marked
latest, Homebrew formula updated, all atomically advertised.

### Why backport `main` → `dev` after publish

Expand Down
13 changes: 7 additions & 6 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,11 @@ Always use annotated tags (`-a -m`). The tag push triggers `.github/workflows/re
The tap's formula installs this release's archives. Its `update-formula` workflow downloads the four it names (the two
`apple-darwin` and the two `linux-musl` archives), verifies each against the attestation `attest` made, and pins its
checksum; an archive with no attestation stops the bump, so `attest: true` in `release.yml` is what lets a release reach
Homebrew. The tap then builds bottles from those archives, signs them in its own `publish.yml`, and uploads them to this
repo's release assets.
Homebrew. The tap then installs and tests the formula from those archives on four platforms and lands the bump on its
`main`. It builds no bottle: the binaries are compiled once, by `release.yml`, and `brew install` downloads the archive.

After the homebrew-tap workflow uploads bottles to this repo's release assets, it dispatches `finalize-release` back to
this repo, which idempotently flips `make_latest: true`.
After the homebrew-tap workflow lands the formula bump, it dispatches `finalize-release` back to this repo, which
idempotently flips `make_latest: true`.

→ Rationale (`make_latest` flow, musl hard-block, annotated-tag gotcha):
[`RELEASES-RATIONALE.md` § Release pipeline](./RELEASES-RATIONALE.md#release-pipeline).
Expand Down Expand Up @@ -504,9 +504,10 @@ cargo yank --version "${BAD#v}" agentnative
gh release edit "$PREV" --latest
gh release edit "$BAD" --prerelease

# Homebrew: revert the formula bump on the tap so `brew install` resolves the last-good bottle.
# Homebrew: revert the formula bump on the tap so `brew install` resolves the last-good version.
gh api repos/brettdavies/homebrew-tap/commits --jq '.[0:5][] | .sha[0:7] + " " + .commit.message'
# then revert the `agentnative: add <version> bottle.` and formula-bump commits via a PR to the tap's main.
# then revert the formula-bump commit via a PR to the tap's main. A release the tap published with a
# bottle (0.6.0 and earlier) also has an `agentnative: add <version> bottle.` commit to revert first.
```

Un-yank with `cargo yank --undo --version <version> agentnative` if the yank was wrong. A yanked crate version cannot be
Expand Down
Loading
Loading