Proposal
Publish aces-sdl to PyPI and make releases an automatic consequence of
promoting dev→main, 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 dev→main 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: 0 → python-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 dev→main 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
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).
Proposal
Publish
aces-sdlto PyPI and make releases an automatic consequence ofpromoting
dev→main, adopting the conventional-commit-driven release modeldefined 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 thecontract corpus and builds cleanly from
implementations/python, and there is a.github/workflows/release.yml. But that workflow:v*tag push (no auto-release on merge to main), andaces-sdlis unclaimed on PyPI (404), there are nov*tags in the repo,and the version is a static
version = "0.3.0"inimplementations/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; promotingdev→mainis the release act, andpython-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).
mainstays 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-vcsMigrate
implementations/python/pyproject.tomloff the staticversion = "0.3.0":build-system.requires = ["hatchling", "hatch-vcs"][project] dynamic = ["version"](drop the staticversion)[tool.hatch.version] source = "vcs"and a gitignored version-filesrc/aces/__init__.pyalready 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 tagsare repo-global. Confirm
hatch-vcsresolves the repo-root tag correctly for thesubdir build (
uv build implementations/python), and that PSR'sbuild_commandruns 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:3. Rewrite
release.ymlto the ADR-0006 shapeon: push: branches: [main]+workflow_dispatch(withforceinput for bootstrap).concurrency: { group: release, cancel-in-progress: false }.releasejob,environment: pypi,permissions: { contents: write, id-token: write }.fetch-depth: 0→python-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/*).#537's corpus guarantee: keep the "verify the contract corpus isbundled 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 itenforces Conventional Commit types strictly enough to drive PSR (feat/fix/…),
or add
amannn/action-semantic-pull-request. Ensure the PR-title check isrequired on
devandmain,squash_merge_commit_title = PR_TITLE, featurePRs squash-merge into
dev, anddev→mainuses merge/rebase (never squash, soper-change conventional history survives for PSR to read).
One-time setup
aces-sdlonPyPI before the first upload — owner
Brad-Edwards, repoaces, workflowfilename
release.yml, environmentpypi. (A filename mismatch 403s only thePyPI step.)
v0.3.0tag. Seed the baseline so PSR continues sensibly (e.g. create thev0.3.0tag as the baseline, then let PSR compute the next bump), or bootstrapthe first release via
workflow_dispatch force=minor. Decide and document thestarting version in the release runbook (
docs/explain/releasing.md).Acceptance criteria
dev→mainwith at least onefeat:/fix:since the last tagauto-creates the tag + GitHub Release and publishes the wheel + sdist to
PyPI with no stored token (OIDC).
dev→mainpromotion containing onlydocs:/chore:/etc. releasesnothing (no tag, no PyPI upload).
#537check isretained and gates the release).
pip install aces-sdlfrom PyPI yields a workingacesCLI whoseaces conformance backend --profile provisioning-onlypasses (the#537end goal, now from a real PyPI version).mainis never committed to by automation (PSR tag-only); the PR-titleConventional-Commit gate is required on
devandmain.docs/explain/releasing.mdupdated to the new model, referencingaces-scenario-packs ADR 0006 as the source pattern.
References
building on ADR 0003 (build/release model), ADR 0004 (SBOM/supply chain),
ADR 0005 (auto-release on merge, superseded by 0006).
#537/#543(corpus-bundled wheel + release line),program
#648(Packaging & Supply Chain).