Skip to main content

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:

  1. Bootstrap — A2C structure, hooks, adoption record (stack-agnostic + optional stack profile).
  2. Short stack-ready step — repository is ready to implement software, not only to follow A2C rules.
  3. 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:

AspectBehavior
IntentPublishable Python package — dev stack plus release scaffolding
CompositionDelegates 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 scaffoldForge-agnostic release templates (see §3) and profile README checklist
Additional doctorRead-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:

PromptMaps 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):

  1. a2c doctor — structural + profile checks green (warnings for missing host tools allowed per ADR-0016 §6)
  2. a2c setup-dev — venv, pre-commit, profile setup()
  3. Fill release.project when forge is not none (§4)
  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:

ArtifactRole
scripts/bump_release.pySuggest/apply semver bump, finalize CHANGELOG.md, sync version source, release commit
scripts/verify_release_version.pyAssert tag ↔ on-disk version consistency
docs/release-pipeline.mdPortable 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
FieldValuesPurpose
release.forgegitlab, github, noneSelect CI / publish template pack during python-pypi scaffold
release.projectstring or nullForge 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 deferred
  • gitlab — copy/adapt GitLab CI fragments and optional LibESys publish orchestrator templates from profiles/python-pypi/templates/release/gitlab/
  • github — copy/adapt GitHub Actions fragments from profiles/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)

  1. Bundled python profile (ADR-0016 §8)
  2. Config schema extension for release.* (C3)
  3. Bundled python-pypi composer + forge-agnostic release templates
  4. GitLab forge adapter template pack (LibESys default)
  5. GitHub forge adapter template pack (when ready)
  6. 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_core and profiles/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 python only need re-bootstrap or manual adoption of release templates

References

Supersedes

  • (none)

Superseded by

  • (none)