ADR-0017: Publishable Python profile and forge-neutral release scaffolding
- Status: Accepted
- Date: 2026-06-22
- Deciders: Michel Gillet
Context
ADR-0016 defines the stack profile plugin protocol in a2c_core: registry, bootstrap --profile, setup-dev profile phase, doctor integration, and composition of bundled profiles (for example python-cpp-extension delegating to python and cpp-cmake-conan).
ADR-0016 §8 lists bundled v1 profile ids and their intent. The minimal python profile covers dev ergonomics (pyproject.toml, Ruff pre-commit overlay, editable install, pytest-oriented doctor). It does not scaffold release discipline or publish readiness.
A2C adoption has a practical goal beyond governance scaffolding:
- Bootstrap — A2C structure, hooks, adoption record (stack-agnostic + optional stack profile).
- Short stack-ready step — repository is ready to implement software, not only to follow A2C rules.
- Implement software — features, tests, releases as normal project work.
For publishable Python packages, step 2 should include portable release scaffolding (semver, changelog finalization, version verify, tag discipline) so operators do not discover missing release tooling after coding starts.
LibESys already uses script-driven release flows (ADR-0002, ADR-0009, ADR-0010) with reference scripts in a2c-workflow and superbuild-workflow. Those reference implementations today assume GitLab CI for orchestration and publish gates — but A2C-governed repositories may use GitHub Actions, other CI, or no hosted CI initially.
Profiles must express product shape, not forge host. Multiplying profile ids per forge (python-gitlab-pypi, python-github-pypi, …) would collapse the profile model into an unmaintainable cartesian product.
This ADR extends ADR-0016. It does not change the stack profile protocol, registry API, or hook semantics defined there.
Decision
1. Add bundled profile python-pypi (composed over python)
Introduce a bundled stack profile id python-pypi:
| Aspect | Behavior |
|---|---|
| Intent | Publishable Python package — dev stack plus release scaffolding |
| Composition | Delegates to bundled python for dev scaffold, pre-commit overlay, editable install, and pytest-oriented doctor checks — same pattern as python-cpp-extension in ADR-0016 §1 |
| Additional scaffold | Forge-agnostic release templates (see §3) and profile README checklist |
| Additional doctor | Read-only checks that release scripts and version source exist and are consistent |
Keep bundled python for internal or non-publish Python work (dev layout without release automation).
Bootstrap and workflows.default_profile accept one profile id — either python or python-pypi, not both.
Implementation file layout (illustrative):
src/a2c_core/profiles/stacks/
├── python.py
├── python_pypi.py # composes python + release scaffold/doctor
└── ...
Method-repository assets:
profiles/python-pypi/
├── README.md # post-bootstrap checklist before SW implementation
└── templates/
├── scripts/ # bump_release.py.stub, verify_release_version.py.stub, …
└── release/ # forge adapter template packs (§4)
2. Bootstrap UX — product type before forge
a2c bootstrap --profile remains the primary non-interactive entrypoint.
When stdin is a TTY, bootstrap may prompt (worst case) after stack-agnostic scaffolding:
| Prompt | Maps to |
|---|---|
| Publishable Python package on PyPI (or private index)? | --profile python-pypi vs python |
| Where is the remote / CI hosted? | release.forge (§4) |
Non-interactive bootstrap requires explicit --profile; forge may be set later via config or TUI.
After bootstrap with python-pypi, the operator runs a short checklist (documented in profiles/python-pypi/README.md):
a2c doctor— structural + profile checks green (warnings for missing host tools allowed per ADR-0016 §6)a2c setup-dev— venv, pre-commit, profilesetup()- Fill
release.projectwhen forge is notnone(§4) - Begin implementation under
planning/and project source tree
Bootstrap and profile setup() do not run bump, tag, or publish — those remain explicit maintainer actions after development work exists.
3. Forge-agnostic release core (always scaffolded by python-pypi)
The python-pypi profile scaffolds host-neutral release discipline aligned with ADR-0002 and ADR-0009:
| Artifact | Role |
|---|---|
scripts/bump_release.py | Suggest/apply semver bump, finalize CHANGELOG.md, sync version source, release commit |
scripts/verify_release_version.py | Assert tag ↔ on-disk version consistency |
docs/release-pipeline.md | Portable sequence: green CI → release branch → bump → tag → verify |
Version source of truth for the bundled template defaults to __version__ in the installable package (Python package convention; see superseded ADR-0004 and ADR-0009). Template stubs accept bootstrap-time substitution for package import path and module location.
These scripts are git- and semver-centric — they do not call GitLab or GitHub APIs.
Optional publish_release.py-class orchestrators (GitLab API polling, ADR-0010 state machine) are forge adapter concerns (§4), not part of the portable release core.
4. Forge adapter via config — not via profile id
Record forge choice in .a2c/config.yaml under a new top-level section (contract C3 extension):
version: 1
workflows:
default_profile: python-pypi
release:
forge: gitlab # gitlab | github | none
project: org/repo # forge-specific slug when forge != none; optional at bootstrap
| Field | Values | Purpose |
|---|---|---|
release.forge | gitlab, github, none | Select CI / publish template pack during python-pypi scaffold |
release.project | string or null | Forge project slug (GitLab group/project, GitHub org/repo) for adapter scripts and docs |
Rules:
none— portable release core only; no forge-specific CI templates copied; doctor notes that CI publish wiring is deferredgitlab— copy/adapt GitLab CI fragments and optional LibESys publish orchestrator templates fromprofiles/python-pypi/templates/release/gitlab/github— copy/adapt GitHub Actions fragments fromprofiles/python-pypi/templates/release/github/
Bootstrap may set release.forge from an interactive answer; default when omitted is none (safe, host-neutral).
Doctor reads release.forge and checks that expected adapter artifacts exist when forge is not none. Missing release.project when forge requires it → warning, not bootstrap hard failure.
Implementing GitHub adapter templates may follow GitLab in a later release; the config shape is stable from this ADR.
5. Relationship to ADR-0016 non-goals
ADR-0016 §9 states that v1 does not fully auto-generate GitLab CI YAML. This ADR preserves that limit:
- Forge adapters ship as documented template packs copied during scaffold, with placeholders — not a dynamic CI emitter in
a2c_core - Profile README documents expected CI jobs per forge; operators customize copied YAML
ADR-0016 §9 also lists publishing third-party profile packages to PyPI as out of scope — unchanged. This ADR scaffolds consumer project publish tooling, not distribution of A2C profile wheels.
6. Implementation ordering (after ADR-0016 protocol land)
- Bundled
pythonprofile (ADR-0016 §8) - Config schema extension for
release.*(C3) - Bundled
python-pypicomposer + forge-agnostic release templates - GitLab forge adapter template pack (LibESys default)
- GitHub forge adapter template pack (when ready)
- Bootstrap interactive prompts (optional; may ship after non-interactive
--profile python-pypi)
Steps 1–3 may share a release train; forge packs and prompts may follow incrementally without protocol changes.
Consequences
Positive
- One bootstrap decision (
python-pypi) closes the gap between A2C adoption and implementing a publishable Python product - Forge neutrality — GitLab and GitHub are config adapters, not profile dimensions
- Composition reuses
python— no duplicated dev-stack logic - Aligns with existing ADR-0009 release semantics and reference scripts without locking A2C core to GitLab
- Extends ADR-0016 without amending its protocol sections
Negative
- Additional bundled profile and template maintenance in
a2c_coreandprofiles/python-pypi/ - Config C3 extension requires schema, TUI, and validation updates
- Forge adapter templates can drift from method-repo reference scripts — needs explicit sync policy
- Repositories that already bootstrapped with
pythononly need re-bootstrap or manual adoption of release templates
References
- ADR-0016 — stack profile plugin protocol (extended, not superseded)
- ADR-0002 — changelog and semver
- ADR-0009 — release branch and promotion semantics
- ADR-0010 — optional GitLab publish orchestration (forge adapter)
- docs/architecture/initial-contracts.md — C3 config (extended with
release) - docs/workflow/ci-release-flow.md
- superbuild-workflow — single-package Python release reference
Supersedes
- (none)
Superseded by
- (none)