Release Guide¶
This guide is manual-first. It covers token-based PyPI releases.
For 1.0.0 and later, treat release-gate.yml as the only release-signoff workflow. Fast PR and main workflows are confidence lanes, not approval lanes.
Release Policy¶
- Use SemVer:
MAJOR.MINOR.PATCH. - Package version is generated automatically from git tags via
setuptools-scm. - Use annotated git tags in the form
vX.Y.Z. - Tag and package version must match exactly (for example, tag
v0.7.1produces package version0.7.1). - Never reuse a version number after it has been uploaded to PyPI.
- Do not publish artifacts built before the release tag exists. Build the upload artifacts from the tagged
vX.Y.Zcommit context.
One-Time Setup¶
Accounts and project names¶
- Create/verify account on:
- PyPI:
https://pypi.org/ - Ensure package name
stacksatsis available/owned by this project.
Local tooling¶
Use Python 3.11+ and install packaging tools:
python -m venv venv
source venv/bin/activate
python -m pip install --upgrade pip
pip install -c requirements/constraints-maintainer.txt -e ".[dev,all]"
venv/bin/python -m pip install -c requirements/constraints-maintainer.txt --upgrade build twine
Local token handling (default)¶
Use PyPI API tokens locally only. Do not commit them.
Example with environment variables:
If your local workflow uses PYPI_API_KEY in .env, map it at runtime:
Manual Release Checklist¶
1) Prepare release branch/PR¶
- Sanity-check that
README.mddeep-links into docs instead of duplicating long CLI matrices (seedocs/docs_ownership.md). - Update
CHANGELOG.md(required): - Add user-visible changes under
## [Unreleased]. - Before tagging, move unreleased entries into the new
vX.Y.Zsection with the release date. - Start a fresh
## [Unreleased]section for follow-up work. - Update
docs/whats-new.mdso the latest released section still matches the newest changelog release. - Ensure CI/tests are green.
- Merge to
main. - Create a release branch such as
release/1.0.0for final pre-tag verification.
1a) Verify GitHub protections¶
Before cutting a 1.x release, verify repository settings are aligned with the release process:
mainrequires pull requests.release/*branches require therelease-gateworkflow.- Release tags require the
release-gateworkflow before publish. - Normal maintainer bypass is disabled unless there is an explicit emergency policy exception.
2) Local preflight checks¶
From repository root:
This is a release-preflight command. It runs lint, docs checks, generated-doc sync checks, the full non-performance release suite, a preflight package build, and twine check.
It also runs the BRK source-contract guard (scripts/check_no_coinmetrics_refs.py).
The preflight build only verifies buildability; rebuild release artifacts after the release tag is created.
If your environment has constrained SSL trust roots, scripts/release_check.sh will:
- Fall back to already-installed local build/twine when tool refresh via pip fails.
- Retry package build with python -m build --no-isolation only when isolated-build failure looks SSL-related.
- Still fail normally for non-SSL build errors.
3) Push release branch and verify release gate¶
Push the release candidate to a release branch and wait for release-gate.yml to pass:
The release gate must pass on the release branch before you create the annotated tag. It enforces:
- Linux quality checks, strict docs build, full non-performance tests, package build, and metadata validation
- isolated built-wheel smoke validation
- macOS Python 3.11 supported-platform smoke validation
4) Build artifacts¶
Create and push the release tag before building the publishable artifacts:
The tag is the source of truth for the version. No manual version bump is required.
Then build from the tagged commit context:
rm -rf dist/ build/ .eggs/ *.egg-info
venv/bin/python -m build
venv/bin/python -m twine check dist/*
5) Publish to PyPI (default)¶
6) Post-release verification¶
- Verify package page on PyPI.
- Install from PyPI in a fresh virtual environment.
- Verify the stable CLI entry point:
stacksats- Confirm the required fresh-install smoke covers:
stacksats demo backteststacksats-plot-mvrvin an environment withvizinstalled- Optional manual helper verification when relevant:
stacksats-plot-weightsin an environment with bothvizanddeployinstalled plus a validDATABASE_URL
CI Workflow Notes¶
- Verification lanes at a glance:
| Workflow | Role | What it proves |
|---|---|---|
package-check-pr.yml |
fast PR confidence | quick lint/docs/tests/package confidence before merge |
package-check.yml |
main confidence | ongoing confidence on main, not release approval |
docs-check.yml |
docs quality | markdown, spelling, links, docs sync, docs UX, strict docs build |
docs-pages.yml |
docs publish | strict docs build plus GitHub Pages deploy from main |
example-commands-smoke.yml |
scheduled/manual command smoke | docs-example regression lane via scripts/test_example_commands.py plus inherited-environment wheel regression |
coverage-report.yml |
scheduled/manual maintenance coverage | branch-aware coverage visibility via scripts/check_coverage.sh |
release-gate.yml |
release signoff | release-grade quality/docs/tests/coverage/package validation plus isolated wheel smoke via scripts/release_wheel_smoke.py |
release-gate.ymlis the release-grade blocking workflow forrelease/*branches andv*tags.release-gate.ymlnow validates the built wheel in isolated virtual environments. It does not rely on inherited site-packages.release-gate.ymlcovers the stablestacksatsCLI path plus the optionalstacksats-plot-mvrvhelper; it does not attempt database-backedstacksats-plot-weightssmoke in CI.- Pull requests run fast confidence checks (
package-check-pr.yml) and docs checks. They are intentionally faster than the release gate and are not release sign-off.package-check-pr.ymlincludesdocs/**so doc contract tests run on doc-only PRs. - Pushes to
mainrunpackage-check.ymlfor ongoing confidence, not release approval. package-check.ymlincludesdocs/**(not only code paths), so pytest doc contract tests stay aligned withdocs/run/*.mdpages.- Full non-performance branch-aware coverage also runs in
release-gate.yml;coverage-report.ymlremains scheduled/manual maintenance visibility. - Coverage fail-under is
100%line and branch coverage forstacksats/and should not be lowered in routine maintenance PRs. - CLI docs examples are smoke-tested in scheduled/manual
example-commands-smoke.ymlviascripts/test_example_commands.py. - PyPI publishing is manual only via
scripts/publish_pypi_manual.sh. - Pull requests also run docs quality checks (
docs-check.yml): - markdown lint across all tracked
.mdfiles - spelling checks across all tracked
.mdfiles - link checks across all tracked
.mdfiles - release docs sync check
- docs reference checks
- docs UX structure checks
- strict docs build
- Both
docs-check.ymlanddocs-pages.ymlinstall withrequirements/constraints-maintainer.txtand.[dev,all]so docs environments stay aligned with release-grade installs. - Pushes to
mainpublish docs to GitHub Pages viadocs-pages.yml. example-commands-smoke.ymlkeeps a lighter inherited-environment wheel regression test for maintainers, but it is not the release artifact gate.release-gate.ymlis still the only release-signoff workflow; none of the faster confidence or scheduled/manual lanes replace it.
GitHub Pages Source¶
Configure Pages source to GitHub Actions.
End-to-End Validation Runbook¶
Use this sequence after workflows are merged:
- Open a PR with release notes/changelog updates and verify
package-check-pr.ymlpasses. - Run
bash scripts/release_check.shon the release candidate commit. - Push the release candidate to a
release/*branch and verifyrelease-gate.ymlpasses. - Create and push annotated tag
vX.Y.Z. - Verify the tag-triggered
release-gate.ymlrun passes. - Rebuild from the tagged commit context and run
twine check dist/*. - Run
bash scripts/publish_pypi_manual.sh. - Verify package page on PyPI.
- Install from PyPI in a fresh virtual environment and run command smoke tests.
Expected results:
- Manual publish succeeds with local
PYPI_API_KEY. - Tagged build and
twine checkpass before upload. - Release branch and tagged release both pass
release-gate.ymlbefore publish.
Operational Notes¶
- Do not commit PyPI tokens or store them in repository files.
- Keep
scripts/publish_pypi_manual.shas the default release path. - If a release fails after version/tag creation, bump to the next version and retry; do not overwrite versions.
- If
scripts/release_check.shchanges, update this page andCONTRIBUTING.mdin the same PR. - If GitHub branch/tag protection settings change, update this page in the same PR that changes the policy.
- Keep contributor and policy docs current:
CONTRIBUTING.mdSECURITY.mdCODE_OF_CONDUCT.md