This is the operator runbook for cutting a release of conclave.
Three names matter and they are deliberately different:
| Thing | Value |
|---|---|
PyPI distribution name (what pip install uses) |
conclave-cli |
| CLI command (what users type) | conclave |
Import package (what you import) |
conclave |
| GitHub repository | DataScience-EngineeringExperts/conclave |
Install is therefore pip install conclave-cli, but the command stays conclave
and the import stays from conclave import Council. The PyPI name conclave is an
unrelated package by another author (a blockchain client) — that is why the
distribution name is conclave-cli.
The publish + signing automation lives in
.github/workflows/release.yml. That workflow is
triggered only when a GitHub Release is published. The publish job succeeds only when
PyPI trusts the exact organization repository and workflow identity below.
The workflow publishes through OIDC Trusted Publishing; there is no API token or stored GitHub secret. On PyPI, open conclave-cli → Manage → Publishing and confirm an active publisher with these exact values:
- Owner:
DataScience-EngineeringExperts - Repository:
conclave - Workflow:
release.yml - Environment: blank (the workflow has no GitHub environment)
If the repository was transferred from ernestprovo23/conclave, replace the old publisher
before releasing. Public provenance proves the identity used by past releases, not the current
private publisher configuration. A mismatch fails closed in the publish job; never fall back to
a token upload.
Do this on a clean checkout of main with all intended release changes merged.
-
Update the changelog. In
CHANGELOG.md, move the## [Unreleased]entries under a new## [X.Y.Z] - <YYYY-MM-DD>heading with today's date. Leave a fresh empty## [Unreleased]section above it. -
Bump the version in BOTH places.
- In
pyproject.toml, set[project] version = "X.Y.Z". - In
src/conclave/__init__.py, set__version__ = "X.Y.Z". (The distribution nameconclave-cliis already set — do not change it.)
- In
-
Commit.
git add CHANGELOG.md pyproject.toml src/conclave/__init__.py git commit -m "release: vX.Y.Z" git push -u origin release/X.Y.Z -
Open and merge the release PR. Wait for every required CI check, merge through branch protection, then fast-forward a clean local
main. The tag must point to the merged release commit, not the pre-merge branch commit. -
Tag and push the tag. (A tag alone does NOT publish anything — it only marks the commit. The Release in the next step is what triggers the workflow.)
git tag vX.Y.Z git push origin vX.Y.Z
-
Create the GitHub Release. This is the trigger.
gh release create vX.Y.Z --title "vX.Y.Z" --generate-notesor use the GitHub UI: Releases → Draft a new release → choose tag
vX.Y.Z→ Publish release.Publishing the Release fires
release.yml, which:- build — builds the sdist + wheel with
python -m buildand uploads them as workflow artifacts so publish + sign use the exact same bytes; - pypi-publish — publishes those artifacts to PyPI via OIDC Trusted Publishing (no token), with PEP 740 attestations attached. This fails closed if the Trusted Publisher from section 0 is not yet configured;
- sign — signs the sdist + wheel with Sigstore keyless and attaches the
.sigstorebundle(s) to the GitHub Release assets.
- build — builds the sdist + wheel with
-
Install from PyPI (give the CDN a minute):
pip install conclave-cli conclave --help conclave providers # the version is printed in this command's footer python -c "import conclave; print(conclave.__version__)"
The install name is
conclave-cli, the command isconclave, the import isconclave. Thepython -cline must printX.Y.Z(there is no--versionflag; the running version is shown in theconclave providersfooter). Remember to bump__version__insrc/conclave/__init__.pytoX.Y.Zin the release commit (step 1.2) alongsidepyproject.toml. -
Verify the Sigstore bundle. On the GitHub Release page, confirm there is a
.sigstore(bundle) asset next to each.tar.gz/.whl. Thesignjob already self-verified against this workflow's own identity before attaching, but you can re-verify any artifact locally:pip install sigstore sigstore verify identity dist/conclave_cli-X.Y.Z-py3-none-any.whl \ --bundle conclave_cli-X.Y.Z-py3-none-any.whl.sigstore \ --cert-identity \ "https://github.com/DataScience-EngineeringExperts/conclave/.github/workflows/release.yml@refs/tags/vX.Y.Z" \ --cert-oidc-issuer "https://token.actions.githubusercontent.com"(Download the
.whland its.sigstorebundle from the Release assets first.) -
Confirm the PyPI page. Visit https://pypi.org/project/conclave-cli/ and check:
- version
X.Y.Zis listed; - the project URLs point at
DataScience-EngineeringExperts/conclave; - "Publisher" shows the Trusted Publisher (OIDC), not a token upload;
- PEP 740 attestations are present (the verified-publish badge).
- version
PyPI uploads are immutable — you cannot overwrite a published version. If a release is broken:
- Yank the bad version (keeps existing pins working, hides it from new installs): on https://pypi.org/project/conclave-cli/ → Manage → Releases → Options → Yank. Yanking is reversible.
- Ship a fix-forward patch release following section 1 again. This is the preferred remedy — never try to re-upload the same version.
- GitHub Release: you may delete or edit the GitHub Release and its assets freely; that does not affect what is already on PyPI. Re-running the workflow against the same version will fail the PyPI publish (duplicate filename), which is the correct fail-closed behavior — bump the version instead.
- pip-audit runs in CI (the
auditjob in.github/workflows/test.yml) and is fail-closed: a known vulnerability in any resolved dependency fails CI. conclave's dependency surface is tiny (httpxplus a few well-maintained libs), so false-positive churn is low. If a transitive CVE with no available fix blocks an unrelated PR, suppress it narrowly withpip-audit --ignore-vuln <GHSA/PYSEC id>in the workflow step and leave a tracking note in the PR; remove the suppression once a fixed version is available. - requirements-dev.lock is a hash-pinned lockfile of the full dev + runtime tree,
generated with:
Regenerate it whenever you change dependencies in
uv pip compile --universal --generate-hashes --python-version 3.11 \ --extra dev pyproject.toml -o requirements-dev.lock
pyproject.tomlso reproducible installs stay in sync.
- No stored secret. OIDC Trusted Publishing means GitHub never holds a PyPI token; PyPI trusts the workflow identity directly. Same trust model as the keyless Sigstore signing job.
- Signed releases. From v1.0.0 conclave signs its own release artifacts (the
signjob) with Sigstore keyless, so consumers can verify the wheel they install came from this repo's release workflow. PEP 740 attestations on the PyPI upload add a second, PyPI-native provenance signal. - Explicit gesture. A pushed tag does nothing; only publishing a Release ships. That keeps accidental tags from triggering a publish.