Releasing Doberman-Core
September 5, 2026 · View on GitHub
The checklist for cutting a release, kept in sync with .github/workflows/publish.yml, .github/workflows/ci.yml, and pyproject.toml. The goal: every published claim resolves to something in the repo, a test, a parity cell, or a reproducible number, at the moment of release.
Before tagging
- Green
main. CI passes onmain:ruff check .,ruff format --check ., the offline Markdown link check,lint-imports,pytest -n auto --cov=doberman --cov-report=term-missing --cov-fail-under=90, the parity--checkstep, and the secret scan. - Parity matrix current.
python -m tools.parity.generate_parity --checkpasses (CI enforces it). Every checkmark inPARITY.mdstill resolves to a collected test. - Refresh the benchmark numbers. Re-run the suites per
BENCHMARKS.mdand update its Results tables in the release PR:- Synthetic (deterministic, from a cold clone):
python -m tests.benchmarks.run --suite synthetic --profile before_after. - AgentDojo (operator-supplied):
pip install agentdojoat a pinned commit, thenpython -m tests.benchmarks.run --suite agentdojo --profile before_after. Record the pinned commit and the run date. Keep the raw run out of git (test-logs/); transcribe only the aggregate numbers. - Update the "Fixed bypasses" wall with anything disclosed and fixed since the last release.
- Synthetic (deterministic, from a cold clone):
- Version and changelog.
python scripts/compile_changelog.py --checkis green (CI already enforces this onmain). Thenpython scripts/compile_changelog.py --write --release vX.Y.Z --headline "<headline>"compiles every pendingchangelog.d/<PR-number>.<type>.mdfragment into a dated, grouped section and deletes them. Read it once as a stranger and tighten any bullet that needs a second read. Bumpversioninpyproject.tomlto match (follow semver). Confirm the README roadmap and versioning reflect reality. - Docs sweep. Every protection claim in the README resolves to a parity cell or a benchmark number. No orphan adjectives.
Cut a release
Publishing uses PyPI Trusted Publishing (OIDC) via .github/workflows/publish.yml. No API tokens are stored in this repo.
- Commit the version bump from step 4 above and merge to
main. - Optional dry run: GitHub -> Actions -> Publish -> Run workflow, on
main, targettestpypi(the default). This builds and uploads to TestPyPI. Verify:pip install -i https://test.pypi.org/simple/ \ --extra-index-url https://pypi.org/simple/ doberman-core - Create a GitHub Release with tag
vX.Y.Z(matching the version; the publish job refuses to upload whenpyproject.toml's version and the tag disagree). Publishing the release triggers thepypi-publishjob, which uploads to PyPI and attaches a CycloneDX SBOM (Software Bill of Materials, a list of the project's dependencies;sbom.json) to the release as a downloadable asset. The public core must build, test, and run with zero enterprise code installed (the standalone guarantee). CI's standalone step enforces this. If that run fails after the tag exists (a trusted-publisher or workflow bug), fixmain, then run the workflow manually with targetpypiandtagset to the release tag (gh workflow run publish.yml -f target=pypi -f tag=vX.Y.Z). A rerun of the failed run reuses the old workflow snapshot and cannot pick up the fix; the manual run buildsmain, whosepyproject.tomlalready carries the version. - Verify from a clean environment:
python -m venv /tmp/dob && /tmp/dob/bin/pip install doberman-core /tmp/dob/bin/doberman --help
Note The distribution name on PyPI is
doberman-core; the import name and CLI command staydoberman(import doberman,doberman --help). See "Claiming thedobermanname" below for why.
Release notes
Write the GitHub release body from this template (under 150 words); the title is vX.Y.Z — <the same headline>.
<Two or three sentences: what someone running Doberman gets from this release, in their words.>
**Highlights**
- <the three to five bullets a reader cares about most, copied from the changelog without the PR numbers>
**Upgrade**
`pip install -U doberman-core`. <One line per change that needs action, or "No action needed.">
Full changelog: https://github.com/DobermanCore/Doberman-Core/blob/main/CHANGELOG.md#vXYZ--YYYY-MM-DD ·
Compare: https://github.com/DobermanCore/Doberman-Core/compare/vPREV...vX.Y.Z · Thanks @a, @b.
The release body is never a copy of the changelog section. If a release has one theme, say it in the first sentence and let the highlights prove it; if it has none, say what kind of release it is (fixes, a single feature, a wave of small improvements) and stop.
One-time PyPI setup (already done; reference only)
Configuring PyPI/TestPyPI to trust this repo, in case it ever needs redoing:
- PyPI (production). Log in at https://pypi.org, go to Account settings -> Publishing -> Add a pending publisher, and fill in PyPI project name
doberman-core, ownerfu351, repositoryDoberman-Core, workflowpublish.yml, environmentpypi. - TestPyPI (optional). Repeat at https://test.pypi.org with environment
testpypi. - GitHub Environments. In the repo: Settings -> Environments -> create
pypiandtestpypi(names must match the workflow). Add protection rules or required reviewers topypiso a human approves every production publish.
Software Bill of Materials (SBOM)
Every published release ships a CycloneDX JSON SBOM as a GitHub Release asset (sbom.json), generated by pip-audit from the resolved dependency set in pyproject.toml. .github/workflows/publish.yml builds and attaches it automatically; there's nothing to do by hand for a normal release.
Generate one locally at any time:
pip install -e ".[sbom]"
pip-audit -f cyclonedx-json -o sbom.json .
pip-audit also reports known vulnerabilities in the resolved dependencies while it runs. The release workflow doesn't fail the build on a finding, since a newly disclosed CVE in a transitive dependency shouldn't silently block a release, but it does surface as a warning in the Actions run. Check there before shipping if one shows up.
Local build and inspect (sanity check before tagging)
python -m pip install --upgrade build twine
python -m build # -> dist/doberman_core-X.Y.Z-py3-none-any.whl + .tar.gz
twine check dist/*
# Public-release safety: confirm nothing local leaks into the artifacts.
unzip -l dist/*.whl
tar tzf dist/*.tar.gz
The sdist file list is pinned in pyproject.toml ([tool.hatch.build.targets.sdist]) to src/doberman, tests, tools, README.md, LICENSE, and pyproject.toml, so graphify-out/, .doberman/, DBs, keys, and dev plan files can never ship.
Claiming the doberman name (PEP 541)
doberman is already taken on PyPI by an unrelated, abandoned 2020 project, so releases publish as doberman-core until the name is reclaimed. Transferring it is a manual, human-reviewed process and not guaranteed.
- Contact the current owner first. Email the maintainer listed on https://pypi.org/project/doberman/ and ask if they'll transfer the name. Keep the message for your records.
- If there's no response, or they agree, file a name request under PEP 541 at https://github.com/pypi/support/issues (the project-name-related template). Say the project is abandoned (last release 0.0.4, September 2020), link this repo as evidence of active intended use, and reference any prior contact attempt.
- If granted, add
dobermanas the distribution name (or publish a thindobermanpackage that depends ondoberman-core) and update the install docs.
Until then, doberman-core is the canonical install name.