Skip to content

Release Automation

Releases are Cabal-owned. The package version in amaru-treasury-tx.cabal is the product version; there is no release-please manifest and no separate version.txt.

Main-branch planner

Every push to main runs the Release Planner workflow. It reads Conventional Commit messages since the last v* tag and either:

  • opens or updates release/cabal-release with a Cabal version bump and a generated CHANGELOG.md section;
  • creates the release tag if the just-merged release PR already contains a changelog section for the current Cabal version;
  • exits without changes when there are no releasable commits.

The planner maps commit intent onto PVP-shaped Cabal versions:

Commit signal Cabal bump
fix: / perf: fourth component, for example 0.1.1.0 to 0.1.1.1
feat: third component, for example 0.1.1.0 to 0.1.2.0
! or BREAKING CHANGE: second component, for example 0.1.1.0 to 0.2.0.0

For user-facing CLI changes that invalidate old operator scripts, use a breaking Conventional Commit such as feat!: and include a BREAKING CHANGE: footer. The release planner treats either marker as a breaking release signal.

The planner must push branches and tags through RELEASE_BOT_SSH_KEY. Using the default GITHUB_TOKEN for git writes would create branch and tag events that do not trigger the normal CI and publication workflows.

One-time setup

Create a dedicated write deploy key and store the private half as a repository secret. This is repo-scoped and does not require a personal access token.

tmpdir="$(mktemp -d)"
ssh-keygen -t ed25519 \
  -C 'amaru-release-bot@lambdasistemi/amaru-treasury-tx' \
  -f "$tmpdir/amaru-release-bot" \
  -N ''

gh api repos/lambdasistemi/amaru-treasury-tx/keys \
  -f title='amaru-release-bot' \
  -f key="$(cat "$tmpdir/amaru-release-bot.pub")" \
  -F read_only=false

gh secret set RELEASE_BOT_SSH_KEY \
  --repo lambdasistemi/amaru-treasury-tx \
  --body "$(cat "$tmpdir/amaru-release-bot")"

rm -rf "$tmpdir"
gh secret list --repo lambdasistemi/amaru-treasury-tx \
  | grep '^RELEASE_BOT_SSH_KEY'
gh api repos/lambdasistemi/amaru-treasury-tx/keys \
  --jq '.[] | select(.title == "amaru-release-bot")'

The deploy key must have write access. A read-only key can fetch the repository but cannot push the release branch or release tag.

Publication

Merging the release PR does not publish directly. The next planner run creates v<package-version>, and the tag push starts the Linux and Darwin release workflows.

Before merging a release-bound PR, run:

nix develop --quiet -c just ci

That includes just smoke, which exercises the swap-wizard signer regression and checks that the built CLI advertises --extra-signer,--signer SCOPE|HEX.

Strings that follow the Cabal version

Anything user-facing that embeds the package version (e.g. the User-Agent header sent to outbound quote providers) imports the autogenerated Paths_amaru_treasury_tx module and renders Data.Version.showVersion P.version. Do not hardcode the package version string anywhere — the release planner only bumps amaru-treasury-tx.cabal, and hardcoded copies drift silently and ship stale on every release (as happened to the CoinGecko User-Agent in v0.2.4.0, fixed by #96).

The library and unit-test stanzas in amaru-treasury-tx.cabal already list Paths_amaru_treasury_tx under autogen-modules and other-modules; new stanzas that need the version should do the same.

Local DevNet release evidence

For live local-node evidence, also run the opt-in devnet smoke:

nix develop --quiet -c just devnet-smoke node
nix develop --quiet -c just devnet-smoke registry-init
nix develop --quiet -c just devnet-smoke stake-reward-init
nix develop --quiet -c just devnet-smoke governance-withdrawal-init
nix develop --quiet -c just devnet-smoke disburse-submit
nix develop --quiet -c just devnet-smoke swap-ready

Record the generated runs/devnet/<timestamp>/ directory in the release notes when this check is used. The node phase proves the cardano-node-clients DevNet node boundary, socket magic 42, and 50-second epoch timing.

The registry-init phase proves the production-backed DevNet registry and reference-script publication command. The stake-reward-init phase then consumes the registry artifact and registers both the treasury reward account and the permissions reward account needed by later withdraw-zero witnesses. Latest branch stake/reward evidence is runs/devnet/20260517T005034Z, setup tx 527c1b08fdfd1c41fe1237d4d5f1bc3572dee98a81b76b62257c958e50dc9cc8, treasury reward account b2b7201c62e43ae8e03b61c96931379ebbcdce61befc3f4e4b1f4be4 with registered: true, and permissions reward account f9dc1d931a3f52eaf83891f8621cbba5ba64f6faa5792f1b00c17333 with registered: true. This is prerequisite evidence only; governance funding and disburse are recorded by their own command-proof slices.

The governance-withdrawal-init phase copies and patches a short-epoch genesis, runs registry-init and stake-reward-init prerequisites, then invokes the shipped devnet governance-withdrawal-init command. The production command submits and votes through the treasury-withdrawal governance action, observes the reward account funded, builds the withdrawal through the production withdraw/tx-build path, signs/submits it, and writes the #150 handoff at governance-withdrawal-init/materialized.json. Latest branch evidence is runs/devnet/20260516T231003Z: proposal tx baffa774b368b1da8c3ff80be399bcf6fa63b5cff658b6889fc00109da218e23, governance action baffa774b368b1da8c3ff80be399bcf6fa63b5cff658b6889fc00109da218e23#0, vote tx 009801303fc5cc3c3dfe474c30cc4b7d31e99b5af29467cc317072ea6b728c45, reward account b2b7201c62e43ae8e03b61c96931379ebbcdce61befc3f4e4b1f4be4, reward 0 -> 2000000 -> 0, withdrawal tx 4a87409b52b8104d51d41df7ee562196cf33621f64c4c40985b4aef5ff21e9bd, fee 456417, materialized output 4a87409b52b8104d51d41df7ee562196cf33621f64c4c40985b4aef5ff21e9bd#0, and treasury ADA 200000000 -> 202000000. The legacy withdraw smoke phase remains a compatibility alias for the same command proof.

The disburse-submit phase copies and patches a short-epoch genesis, runs registry-init, stake-reward-init, and governance-withdrawal-init prerequisites, then invokes the shipped devnet disburse-submit command. The production command consumes the #149 materialized treasury UTxO, builds through the production disburse/tx-build path, signs/submits the transaction, verifies the beneficiary output, and verifies the reduced treasury output. Latest branch evidence is runs/devnet/20260517T005034Z: submitted tx 0008ab902b2f835624f453af0467d826b02519d7139ec8e84a04c8a9c000011b, beneficiary output 0008ab902b2f835624f453af0467d826b02519d7139ec8e84a04c8a9c000011b#1 with 1000000 lovelace, treasury input 309e28ed5b95de38258bcc130d6390800b0719f6410b0d5fe6f3c33cc1b70817#0, and treasury lovelace 2000000 -> 1000000.

The swap readiness phase uses the checked-in public SundaeSwap-finance/sundae-contracts@be33466b7dbe0f8e6c0e0f46ff23737897f45835 order.spend artifact, publishes it as a local DevNet reference script, and writes swap-ready/registry.json for the later #84 order build/funding slice. Latest branch readiness evidence is runs/devnet/20260515T124545Z, script hash 02eee6c4d128c9700c178922163645f1fdb381bbdce071acbbd49465, reference UTxO 490b9bc8a80e8a55434b895bea6ca47fc612105c0cf71b781a61e99cd2be46af#0, and local order address addr_test1xqpwaeky6y5vjuqvz7yjy93kghclmvuph0wwqudvh02fgegzamnvf5fge9cqc9ufygtrv303lkecrw7uupc6ew75j3jsdhyjpu. This is readiness evidence only; it is not a built, funded, submitted, or spent swap order.

The DevNet bootstrap recovery is split into registry/reference-script publication (#147), stake/reward setup (#148), governance funding and treasury withdrawal setup (#149), and disburse/beneficiary receipt (#150). Older evidence slices also track governance action (#82), withdrawal (#83), disburse (#86), SundaeSwap V3 contract readiness (#132), SundaeSwap V3 order build/funding (#84), SundaeSwap V3 order spend (#85), and reorganize (#87). Do not record swap-order build/funding, swap-spend, or reorganize evidence until those slices land and their phase-specific smoke commands pass. SundaeSwap compatibility claims must be based on the public V3 contracts/SDK, not on an Amaru-only toy validator.

Release note wording for this slice:

Local DevNet disburse-submit evidence now proves #150: the smoke runs
registry-init, stake-reward-init, and governance-withdrawal-init
prerequisites, invokes the shipped `devnet disburse-submit` command,
builds/signs/submits the disburse through the production disburse and
tx-build path, verifies beneficiary receipt, and verifies the reduced
treasury output. Evidence: runs/devnet/20260517T005034Z, submitted tx
0008ab902b2f835624f453af0467d826b02519d7139ec8e84a04c8a9c000011b,
beneficiary output
0008ab902b2f835624f453af0467d826b02519d7139ec8e84a04c8a9c000011b#1
with 1000000 lovelace, treasury input
309e28ed5b95de38258bcc130d6390800b0719f6410b0d5fe6f3c33cc1b70817#0,
and treasury lovelace 2000000 -> 1000000. This is not SundaeSwap order
or reorganize proof.

Local DevNet governance-withdrawal-init evidence now proves #149: the
smoke runs registry-init and stake-reward-init prerequisites, invokes
the shipped `devnet governance-withdrawal-init` command, submits and
votes through the treasury-withdrawal governance action, observes the
reward account funded, builds/signs/submits the withdrawal through the
production withdraw/tx-build path, and records the materialized treasury
UTxO handoff for #150. Evidence: runs/devnet/20260516T231003Z,
proposal baffa774b368b1da8c3ff80be399bcf6fa63b5cff658b6889fc00109da218e23,
action baffa774b368b1da8c3ff80be399bcf6fa63b5cff658b6889fc00109da218e23#0,
vote 009801303fc5cc3c3dfe474c30cc4b7d31e99b5af29467cc317072ea6b728c45,
reward account b2b7201c62e43ae8e03b61c96931379ebbcdce61befc3f4e4b1f4be4,
reward 0 -> 2000000 -> 0 lovelace, withdrawal
4a87409b52b8104d51d41df7ee562196cf33621f64c4c40985b4aef5ff21e9bd,
fee 456417 lovelace, materialized output
4a87409b52b8104d51d41df7ee562196cf33621f64c4c40985b4aef5ff21e9bd#0,
and treasury ADA 200000000 -> 202000000. This is not disburse,
SundaeSwap order, or reorganize proof.

Local DevNet swap readiness evidence now proves the prerequisite for
the SundaeSwap order-build slice: the smoke hashes the checked-in public
SundaeSwap V3 order.spend artifact, publishes it as a local DevNet
reference script, and writes a readiness registry for #84. Evidence:
runs/devnet/20260515T124545Z, source
SundaeSwap-finance/sundae-contracts@be33466b7dbe0f8e6c0e0f46ff23737897f45835,
script hash
02eee6c4d128c9700c178922163645f1fdb381bbdce071acbbd49465,
reference UTxO
490b9bc8a80e8a55434b895bea6ca47fc612105c0cf71b781a61e99cd2be46af#0.
This is not swap order build, funding, submission, or spend proof.

The release workflows run scripts/release/check-version-consistency before building. A tag is publishable only when:

  • the tag version matches amaru-treasury-tx.cabal;
  • CHANGELOG.md contains a section for that version;
  • release-please state files are absent.

Linux publishes AppImage, DEB, and RPM assets. Darwin publishes an aarch64-darwin tarball and updates the Homebrew tap. Each release bundle is smoke-tested before upload; Linux extracts the generated artifacts with .#linux-artifact-smoke and checks the swap-wizard --help signer surface, while Darwin installs the tap-qualified formula and runs its Homebrew test.

Linux builds the .#linux-release-artifacts flake package, which produces:

  • amaru-treasury-tx-<version>-x86_64-linux.AppImage;
  • amaru-treasury-tx.AppImage, the unversioned AppImage alias;
  • amaru-treasury-tx-<version>-x86_64-linux.deb;
  • amaru-treasury-tx-<version>-x86_64-linux.rpm;
  • SHA256SUMS.

Binary bundling for AppImage, DEB, and RPM lives in nix/linux-release.nix. The Linux Release workflow builds the flake package, runs the shared smoke app against the result, and then uploads the staged files.

Darwin builds the .#darwin-release-artifacts flake package, which produces:

  • amaru-treasury-tx-<version>-aarch64-darwin.tar.gz;
  • SHA256SUMS;
  • amaru-treasury-tx.rb, the Homebrew formula with the tarball SHA.

The Darwin Release workflow uploads the tarball and copies the generated formula into the Homebrew tap. The smoke test installs lambdasistemi/tap/amaru-treasury-tx, runs brew test, and checks the offline help surfaces plus executable presence for the shipped tools. Binary bundling and dylib load-path patching live in nix/darwin-release.nix.

Linux dev bundles

The existing Linux Release workflow also has a manual dev mode:

gh workflow run release.yml \
  --ref main \
  -f mode=dev-linux

Dev mode builds the .#linux-dev-release-artifacts flake package on the NixOS runner, uploads the AppImage, DEB, RPM, and checksums to the dev-linux prerelease, downloads the versioned assets back from that release, and runs .#linux-artifact-smoke against the downloaded files.

On pull requests that touch the Linux workflow or Nix packaging, the same workflow runs in build-only dev mode and does not mutate releases.

Darwin dev Homebrew formula

The existing Darwin Release workflow also has a manual dev mode:

gh workflow run darwin-release.yml \
  --ref main \
  -f mode=dev-homebrew \
  -f update_tap=yes

Dev mode builds the .#darwin-dev-homebrew-artifacts flake package on macos-14, uploads the tarball to the dev-homebrew prerelease, and updates Formula/amaru-treasury-tx-dev.rb in lambdasistemi/homebrew-tap.

The dev formula uses the Cabal version plus source revision and conflicts with the release formula because both install the same command-line tools. After updating the tap, the workflow installs amaru-treasury-tx-dev through Homebrew and smoke-tests all shipped tools through the tap-qualified formula lambdasistemi/tap/amaru-treasury-tx-dev, including brew test. The test avoids node-socket-dependent commands, so swap-probe is checked for executable presence rather than invoked. On pull requests that touch the Darwin workflow or Nix packaging, the same workflow runs in build-only dev mode and does not mutate releases or the Homebrew tap; it still verifies the generated formula by installing it through a temporary local lambdasistemi/tap tap whose URL points at the freshly built tarball.