Skip to content

Publish aces-sdl to PyPI and auto-release on merge to main (conventional-commit model, per aces-scenario-packs ADR 0006) #684

Description

@Brad-Edwards

Proposal

Publish aces-sdl to PyPI and make releases an automatic consequence of
promoting devmain
, adopting the conventional-commit-driven release model
defined in aces-scenario-packs ADR 0006 (docs/decisions/adrs/0006-conventional-commit-releases.md).
That ADR is written as a reusable blueprint — copy its four building blocks and
swap the repo-specific names.

Current state / gap

#537 (merged as #543) already did the hard part: the wheel bundles the
contract corpus and builds cleanly from implementations/python, and there is a
.github/workflows/release.yml. But that workflow:

  • triggers only on a manual v* tag push (no auto-release on merge to main), and
  • cuts a GitHub Release only — it does not publish to PyPI, and has no SBOM.

aces-sdl is unclaimed on PyPI (404), there are no v* tags in the repo,
and the version is a static version = "0.3.0" in
implementations/python/pyproject.toml. So the specific capability — PyPI +
push-to-main auto-release — is untracked. This issue closes that gap on top of
#537's foundation.

Target model (ADR 0006)

Releases become inescapable and governed by a mechanical rubric: merge freely
into dev; promoting devmain is the release act, and
python-semantic-release (PSR) inspects the Conventional Commit messages since the
last tag, computes the next SemVer, creates the tag + GitHub Release, builds
dist, attaches an SBOM, and publishes to PyPI via OIDC trusted publishing (no
stored token). main stays branch-protected — PSR is tag-only (commit = false),
it never pushes a commit back to main.

Work (four building blocks + monorepo wrinkles)

1. Tag-driven versioning via hatch-vcs

Migrate implementations/python/pyproject.toml off the static version = "0.3.0":

  • build-system.requires = ["hatchling", "hatch-vcs"]
  • [project] dynamic = ["version"] (drop the static version)
  • [tool.hatch.version] source = "vcs" and a gitignored version-file
  • src/aces/__init__.py already reads __version__ from installed metadata
    (package_version("aces-sdl", ...)) — keep that; just align the fallback.

Monorepo wrinkle: the package lives in implementations/python, but git tags
are repo-global. Confirm hatch-vcs resolves the repo-root tag correctly for the
subdir build (uv build implementations/python), and that PSR's build_command
runs in / targets that subdirectory (dist must land where the publish + corpus
check expect it).

2. PSR config (tag-only, no commit-back)

Add to pyproject.toml:

[tool.semantic_release]
tag_format = "v{version}"
commit = false
allow_zero_version = true
major_on_zero = false
build_command = "..."   # build the implementations/python dist

[tool.semantic_release.branches.main]
match = "main"

3. Rewrite release.yml to the ADR-0006 shape

  • on: push: branches: [main] + workflow_dispatch (with force input for bootstrap).
  • concurrency: { group: release, cancel-in-progress: false }.
  • One release job, environment: pypi, permissions: { contents: write, id-token: write }.
  • Steps: checkout fetch-depth: 0python-semantic-release@v10 (id: release)
    if released: build SBOM (CycloneDX) + gh release upload
    if released: pypa/gh-action-pypi-publish@release/v1 (OIDC)
    if released: python-semantic-release/publish-action@v10 (attach dist/*).
  • Preserve #537's corpus guarantee: keep the "verify the contract corpus is
    bundled in the wheel" check (fail the release if the corpus payload is missing).

4. Conventional-commit enforcement (make it inescapable)

The repo already has pr-title-lint.yml + tools/check_pr_title.py. Verify it
enforces Conventional Commit types strictly enough to drive PSR (feat/fix/…),
or add amannn/action-semantic-pull-request. Ensure the PR-title check is
required on dev and main, squash_merge_commit_title = PR_TITLE, feature
PRs squash-merge into dev, and devmain uses merge/rebase (never squash, so
per-change conventional history survives for PSR to read).

One-time setup

  • PyPI trusted publisher: register a pending publisher for aces-sdl on
    PyPI before the first upload — owner Brad-Edwards, repo aces, workflow
    filename release.yml, environment pypi. (A filename mismatch 403s only the
    PyPI step.)
  • Version continuity / bootstrap: the package is at 0.3.0 but there is no
    v0.3.0 tag. Seed the baseline so PSR continues sensibly (e.g. create the
    v0.3.0 tag as the baseline, then let PSR compute the next bump), or bootstrap
    the first release via workflow_dispatch force=minor. Decide and document the
    starting version in the release runbook (docs/explain/releasing.md).

Acceptance criteria

  • Merging devmain with at least one feat:/fix: since the last tag
    auto-creates the tag + GitHub Release and publishes the wheel + sdist to
    PyPI
    with no stored token (OIDC).
  • A devmain promotion containing only docs:/chore:/etc. releases
    nothing (no tag, no PyPI upload).
  • The published wheel still bundles the contract corpus (the #537 check is
    retained and gates the release).
  • pip install aces-sdl from PyPI yields a working aces CLI whose
    aces conformance backend --profile provisioning-only passes (the
    #537 end goal, now from a real PyPI version).
  • A CycloneDX SBOM is attached to each GitHub Release.
  • main is never committed to by automation (PSR tag-only); the PR-title
    Conventional-Commit gate is required on dev and main.
  • docs/explain/releasing.md updated to the new model, referencing
    aces-scenario-packs ADR 0006 as the source pattern.

References

  • Blueprint: aces-scenario-packs ADR 0006 (conventional-commit releases),
    building on ADR 0003 (build/release model), ADR 0004 (SBOM/supply chain),
    ADR 0005 (auto-release on merge, superseded by 0006).
  • Foundation in this repo: #537 / #543 (corpus-bundled wheel + release line),
    program #648 (Packaging & Supply Chain).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:supply-chainModule resolution, packaging, and supply-chain codeenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions