Releasing daari¶
Human-gated by design — agents prepare, a maintainer pulls the trigger.
Checklist¶
- Bump
versioninpyproject.tomlanddaari.__version__; moveCHANGELOG.mdUnreleased → new section. - Write
docs/RELEASE-vX.Y.Z.md(scope, highlights, validation results). Put Human steps remaining at the top — agents must not tag, create a GitHub release, or publish. - Verify this file against current workflows (especially
docker.ymlcosign / SBOM / provenance from #311). - Merge via PR (CI checks). Then tag + GitHub release:
git tag vX.Y.Z && git push origin vX.Y.Z
gh release create vX.Y.Z --title "vX.Y.Z — <name>" --notes-file docs/RELEASE-vX.Y.Z.md
What automation does from there¶
| Artifact | Workflow | Trigger | Gate |
|---|---|---|---|
| PyPI package | publish.yml |
GitHub release published (or manual dispatch, incl. TestPyPI) | Trusted publisher registered 2026-08-13 |
| Docker image | docker.yml |
tag push → ghcr.io/naveenreddyalka/daari:vX.Y.Z + latest on main |
ghcr package visibility (first publish creates it) |
| Image signature | docker.yml (cosign keyless) |
same push as the image (non-PR) | OIDC id-token: write; signs IMAGE@DIGEST |
| SBOM + provenance | docker.yml (sbom: true, provenance: true on build-push) |
attached to the pushed image | no-op on PR builds (push: false) |
| Docs site | docs-site.yml |
push to main | GitHub Pages (auto-enabled) |
The build job runs twine check --strict on the sdist/wheel before any upload.
Verify a signed image (after the tag push)¶
cosign verify \
--certificate-identity-regexp 'https://github.com/naveenreddyalka/daari/.github/workflows/docker.yml@.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/naveenreddyalka/daari:vX.Y.Z
Inspect SBOM / provenance attestations with docker buildx imagetools inspect or cosign download sbom / cosign download attestation against the image digest from the docker.yml run.
PyPI trusted publisher¶
Registered 2026-08-13. daari==1.2.0 is on PyPI.
v1.1.1 / v1.1.2 / the first v1.2.0 attempt failed invalid-publisher because this
form had not been filled in; a failed publish does not fail the GitHub release, so
it went unnoticed three times.
scripts/release_pypi.py is the release path from here:
python scripts/release_pypi.py check # what is blocking, changes nothing
python scripts/release_pypi.py publish --target testpypi # optional dry run
python scripts/release_pypi.py publish --target pypi # triggers, watches, diagnoses
python scripts/release_pypi.py verify --version X.Y.Z # clean-venv install check
publish reruns the failed job from the tag's own release run when one exists, so
the upload matches the tag instead of whatever main currently holds. It refuses
to publish a version already on the index, since PyPI uploads cannot be replaced,
and decodes an invalid-publisher failure back into the claim values to fix.
The publisher is already on the project. Recreate it only if PyPI drops the registration, using exactly these OIDC claims — they must match the workflow:
| Field | Value |
|---|---|
| PyPI Project Name | daari |
| Owner | naveenreddyalka |
| Repository name | daari |
| Workflow name | publish.yml |
| Environment name | pypi |
No API token is created or stored. For a version that already has a tag but never reached the index, re-run just the failed job so it keeps the tag's ref:
gh run rerun $(gh run list --workflow publish.yml --limit 1 --json databaseId -q '.[0].databaseId') --failed
Dry-run against TestPyPI first if you prefer — it needs its own publisher with
environment testpypi:
gh workflow run publish.yml -f repository=testpypi
Verify: pip install daari==X.Y.Z in a clean venv, then daari --help.
Homebrew formula¶
Formula/daari.rb needs the release tarball's sha256 plus a resource block for
every transitive dependency, because Homebrew builds with no network access and
virtualenv_install_with_resources installs only what the formula declares. Both
are generated — never hand-edited:
python scripts/update_formula.py --version X.Y.Z
The formula cannot be installed from a path — Homebrew 6 rejects formulae outside
a tap (Error: Homebrew requires formulae to be in a tap). It is served from
naveenreddyalka/homebrew-daari;
after regenerating, copy the file into that repo. Users additionally need
brew trust naveenreddyalka/daari, which Homebrew 6 requires for third-party taps.
To validate a change without touching the public tap, use a throwaway local tap —
brew fetch verifies every checksum without installing:
brew tap-new --no-git naveenreddyalka/formulatest
cp Formula/daari.rb "$(brew --repository naveenreddyalka/formulatest)/Formula/"
brew fetch --formula --build-from-source naveenreddyalka/formulatest/daari
brew untap naveenreddyalka/formulatest
After release¶
- Verify
pip install daari==X.Y.Zin a clean venv. - Local deploy: the launchd watchdog redeploys
mainand runs live E2E within 2h. - Update
CONTEXT.mdcurrent-phase line anddocs/TRACKING.md.