Releasing base-cli¶
The base-cli distribution is built and published from the standalone
basefoundry/base-cli repository. The package name on PyPI is base-cli; the
Python import name is base_cli.
base-cli releases are independent of Base releases. The shared provider,
platform, license, and release-artifact rules are summarized in the Base
ecosystem platform, license, and release
policy.
Version and tag contract¶
VERSION is the release version source of truth. The build backend reads it for
the wheel and sdist metadata, and base_cli.__version__ reports the same value
from a source checkout or from installed distribution metadata.
Production releases use a matching annotated tag such as v0.1.0, created from
main. Lightweight tags, tags pointing at a commit outside main, forced tag
updates, and tags that do not exactly match v${VERSION} are rejected before
publication. The workflow fetches the complete trusted main history so an
ancestor check fails closed instead of relying on shallow checkout state.
The default-branch and v* tag rulesets are release prerequisites. Before a
production tag is created, an independent maintainer must verify in the live
repository settings that the default branch requires one approving pull-request
review from someone other than the last pusher, strict up-to-date status checks,
and the policy, quality, runtime, and consumer checks listed in the ruleset.
The same verification must confirm that tag deletion and non-fast-forward
updates are disabled and that no unreviewed bypass actor is configured. This
document describes the required controls; it does not treat a ruleset as active
until that live readback has been recorded in the release evidence.
Release-path changes are additionally covered by .github/CODEOWNERS, including
the package workflows, VERSION, the hash-locked release toolchain, the
artifact/provenance validators, and this release procedure. CODEOWNERS is a
review-routing control, not enforcement by itself: the protected-branch
ruleset must require the matching owner review and protect the CODEOWNERS file.
The independent reviewer must rerun the quality and release gates before a
production tag is created. The tag ruleset must protect v* tags from deletion
and force updates; bypass actors must be intentionally empty.
If a maintainer credential is suspected to be compromised, pause publication, revoke the affected token or OIDC trust, preserve the workflow run and review evidence, and rotate the release version rather than moving an existing tag. Restore access only after an independent maintainer has reviewed the ruleset, environment approvals, trusted-publisher claims, and the exact artifact digests.
Validation workflow¶
Pull requests and pushes to main start from a clean artifact destination,
build one sdist and one wheel, enforce the source allowlist, run twine check,
and install the reviewed wheel in an isolated environment. The installed-wheel
smoke test exercises public API, lifecycle, and output behavior without the
source tree on sys.path. Tests run across Python 3.10 through 3.14 on Linux,
macOS, and Windows, with Debian, Fedora, and WSL validation retained. Blocking
quality gates cover Ruff formatting/lint, strict public-sample and
framework-wide typing, and an 80% combined statement-and-branch coverage
threshold. Documentation/example checks and dependency/static security scans
are separate required gates. The performance contract also checks fresh
import and isolated invocation budgets, while the adversarial suite exercises
redaction, protocol framing, persistence, concurrency, retention, and signal
cleanup.
The publish job downloads that same reviewed artifact; it does not rebuild
during publication. The build also emits a deterministic SHA256SUMS file, an
SPDX 2.3 SBOM.spdx.json release artifact, and a
RELEASE-BOM-ROW.json component record for the ecosystem compatibility BOM.
The row binds the exact base-cli version, full source commit, API contract,
supported platforms, and passing release evidence. On tag and protected dispatch
runs, GitHub's OIDC-backed actions/attest job records both build provenance
and an SBOM attestation for the exact artifact digests; no PyPI token or other
long-lived publish secret is used.
The provenance job runs after build and smoke validation and before any
publication, attestation, or GitHub Release write. It verifies the full source
SHA, annotated tag object, exact tag target, trusted origin/main ancestry,
non-shallow history, and push-event force/deletion flags. A TestPyPI dispatch
from a branch remains available for rehearsal, but still requires full history
and a full source SHA.
For a version tag, the same Package workflow creates a GitHub Release after
the protected PyPI publication and attestations succeed. The release attaches
the exact reviewed wheel, sdist, SHA256SUMS, SBOM.spdx.json, and
RELEASE-BOM-ROW.json downloaded from the build job. GitHub-generated
comparison notes are supplemented by the
dated section in CHANGELOG.md; the tagged release is rejected when VERSION
or that section does not match the tag. Once the live v* tag ruleset has been
verified, published tags and release assets are treated as immutable. Before
creating a release, the workflow verifies that the tag still
resolves to the reviewed commit, that the wheel and sdist have provenance and
SBOM attestations for that tag and commit, and that the release metadata binds
the same version, commit, and assets. If an existing GitHub Release is found,
the workflow downloads its assets and permits an idempotent rerun only when the
published release is non-draft, has the same tag identity, and every filename
and byte matches the reviewed artifacts. It makes no changes to an identical
release. Any changed, missing, extra, or renamed asset fails with expected and
observed checksums; corrections require a new patch version rather than
overwriting published bytes. A failed first upload that leaves a partial
release must be reviewed and recovered with a new release version.
Independent verification¶
The release job uses the reviewed, hash-locked toolchain in
requirements/release.txt; it does not install mutable latest build or
publishing packages. The lock includes transitive release-path dependencies,
and the PEP 517 backend is pinned to the same setuptools and wheel versions in
pyproject.toml. The job builds two clean source archives with the same
SOURCE_DATE_EPOCH and rejects digest drift before publishing the reviewed
artifacts.
To intentionally refresh the toolchain, edit the direct requirements in
requirements/release.in and regenerate the lock with:
uv pip compile requirements/release.in --python-version 3.13 \
--generate-hashes --output-file requirements/release.txt
Review the complete diff, run the package workflow on a pull request, and only then merge the update. Runtime dependency windows are deliberately not tied to this release-only toolchain.
Download the release metadata artifact from the successful Package workflow
run (the artifact is named base-cli-release-metadata-<run-id>), alongside
the wheel or sdist you downloaded from PyPI:
gh run download <run-id> \
--repo basefoundry/base-cli \
--name base-cli-release-metadata-<run-id> \
--dir release-metadata
sha256sum -c release-metadata/SHA256SUMS
The SPDX document's namespace and comment include the source revision used by the workflow. For a tagged release, verify the matching GitHub attestations with the GitHub CLI:
gh attestation verify base_cli-<version>-py3-none-any.whl \
--repo basefoundry/base-cli
The same command can verify the sdist. A clean-room verifier should compare
the downloaded artifact's digest with SHA256SUMS, confirm the SBOM namespace
contains the expected tag commit, and inspect the attestation's workflow and
repository identity before installation.
Release-facing documentation guardrails¶
The package README is part of the published PyPI metadata. Before any tagged
build, the Package workflow runs scripts/validate_release_docs.py to require a
single release row whose version and GitHub release link match VERSION and to
reject pre-publication wording. scripts/validate_package_artifact.py then
compares the wheel metadata and sdist PKG-INFO description with the tagged
README, and the release BOM records the README SHA-256.
After a production PyPI upload, the workflow runs
scripts/verify_pypi_metadata.py before creating the GitHub Release. It reads
the published PyPI JSON metadata, retries propagation delays, and requires the
published version and long description to match the reviewed source. A mismatch
blocks release completion; published PyPI bytes remain immutable and require a
new patch version for correction.
Changelog and release notes¶
CHANGELOG.md follows Keep a Changelog.
Keep [Unreleased] first, use one section per change category, and write
user-facing bullets rather than internal implementation notes. Every released
version must include its release date and a reference link at the bottom of the
file. Move entries from [Unreleased] into the dated section when the release
PR is prepared; do not rewrite an already published section.
The repository validates these rules in CI with
python scripts/validate_changelog.py, including duplicate bullets, duplicate
categories, missing release links, malformed dates, and accidental internal
planning text.
Documentation site¶
The Documentation workflow builds this site with mkdocs build --strict and
publishes the reviewed site to GitHub Pages after changes land on main. The
canonical URL is https://basefoundry.github.io/base-cli/ and is exposed in
the PyPI project metadata as the Documentation link.
Repository administrators should enable GitHub Pages for the repository using
the GitHub Actions source and approve the github-pages environment the first
time the workflow deploys. Pull requests run the strict build and repository
link checks without publishing.
TestPyPI rehearsal¶
- Dispatch Package from the branch or tag to be rehearsed and choose
testpypi. - Approve the protected
testpypienvironment when prompted. - Verify the published artifact from a clean environment:
python -m venv /tmp/base-cli-smoke
/tmp/base-cli-smoke/bin/python -m pip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
base-cli
/tmp/base-cli-smoke/bin/python -c \
'import base_cli; print(base_cli.__version__)'
The testpypi GitHub environment must be configured with PyPI trusted
publishing for this repository and workflow before the dispatch can upload.
Production release¶
- Update
VERSIONand the changelog in a reviewed pull request. - Merge to
mainand create the matchingv${VERSION}tag. - Approve the protected
pypienvironment. The workflow verifies the tag, dated changelog section, builds and tests the artifact, then publishes the exact artifact to PyPI via trusted publishing and creates the matching GitHub Release. - Verify installation from PyPI and download the matching GitHub Release assets:
python -m venv /tmp/base-cli-smoke
/tmp/base-cli-smoke/bin/python -m pip install --upgrade base-cli
/tmp/base-cli-smoke/bin/python -c \
'import base_cli; import importlib.metadata as m; assert base_cli.__version__ == m.version("base-cli"); print(base_cli.__version__)'
The pypi GitHub environment requires approval, rejects self-review, disallows
administrator bypass, and is configured with the PyPI trusted publisher for
.github/workflows/package.yml. No long-lived PyPI token is stored in the
repository. The independent release/security reviewer is designated in
GOVERNANCE.md; the release record must identify the
reviewer who checked the exact artifact and commit. If a temporary solo-
maintainer exception is ever needed, it must be time-bounded and record an
owner, expiry, audit trail, and link to #252
before an administrator changes the environment policy.
Recovery¶
PyPI versions cannot be overwritten. If validation fails, fix the branch and rerun the workflow before creating a tag. If TestPyPI succeeds but a production publish fails, inspect the workflow logs and rerun the same approved tag only after confirming that neither artifact nor metadata needs correction. A version that was published successfully must be incremented for the next release.
0.5.0 preparation¶
Use the 0.5.0 checklist and
0.4.x migration notes. The changelog validator permits the
newest section matching VERSION to precede its tag while preparing a release
PR; all earlier published sections remain checked against their tags. Once the
current tag exists, its section is immutable too.