ADR 0005 — Automatic release on merge to main¶
Status: Accepted
Date: 2026-07-05
Amends: ADR 0003 (release trigger and versioning source)
Context¶
ADR 0003 established a tag-triggered release: a human pushes a v<X.Y.Z> tag and
the workflow builds, generates the SBOM, publishes to PyPI, and cuts a GitHub
Release. In practice the tag push is a manual, easy-to-forget step decoupled from
the change that actually warrants a release, and it invites tag/__version__
drift (a tag that doesn’t match what shipped). We want a release to happen — be
created, tagged, and tracked in GitHub — automatically when release-worthy code
lands on main, with no separate ceremony.
ADR 0003 also left two version fields in pyproject.toml: a static
[project].version and a [tool.hatch.version] pointing at the package
__init__.py. That is two sources of truth for one value.
Decision¶
Single version source of truth:
__version__insrc/aces_scenario_packs/__init__.py.[project].versionis declareddynamicand hatchling reads it from that module. The release workflow reads the same string. There is exactly one place to bump.Primary release trigger — merge to
main: on push tomain, the release workflow reads the packaged__version__. If nov<version>tag exists yet, it creates and pushes that annotated tag, then builds the sdist + wheel, generates the CycloneDX SBOM (ADR 0004), publishes to PyPI via OIDC trusted publishing, and cuts a GitHub Release carrying the wheel, sdist, and SBOM with auto-generated notes. If the tag already exists, the run is a no-op — so ordinarymainpushes that don’t bump the version do not release.How you cut a release: bump
__version__in the PR (with the changelog fragment). Merging the PR tomainis the release action. No manual tagging.Manual
v*tag push remains supported as a backfill / re-run escape hatch; pushing a semver tag releases that exact ref. The single-workflow design means a merge-created tag and a hand-pushed tag follow the identical build/publish path and one PyPI trusted-publisher configuration (release.yml).Tag authorship: the merge-to-
mainpath creates the tag with the workflowGITHUB_TOKENfrom inside the release run itself, so no personal access token or downstream-trigger workaround is needed, and the token-created tag does not recursively re-trigger the workflow.
Consequences¶
A release is a consequence of merging, not a separate manual step; the tag always matches the shipped
__version__.Forgetting to bump
__version__means no release (safe default), not a mismatched or duplicate one; re-releasing a shipped version is prevented by the tag-exists check.The version lives in one file; the build and the release read the same value.
Releasing still requires the OIDC trust relationship (ADR 0003) and the SBOM (ADR 0004); this ADR changes only when and how the release fires.