Releasing engrama¶
Release pipeline lives in .github/workflows/release.yml.
It triggers on push of any tag matching v*, runs a six-stage pipeline
(guardian → build → sbom → attest → publish → release-notes), and produces:
- A PyPI release published via trusted publishing (OIDC, no API key).
- A GitHub Release with the wheel, sdist, and SBOMs attached.
- SLSA build-provenance attestations on both the wheel and the sdist (verifiable from the GitHub UI and via
gh attestation verify). - PEP 740 attestations on PyPI (generated by
pypa/gh-action-pypi-publish).
One-time setup (PyPI trusted publishing)¶
Required before the first release reaches PyPI. The workflow is already wired for OIDC; PyPI just needs to know which workflow it should trust.
- Sign in to https://pypi.org/ with an account that owns (or will own) the
engramaproject. - Go to Manage project → Publishing (or Your projects → Add a new publisher if the project doesn't exist yet).
- Click Add a new pending publisher (or Add a new publisher if the project already exists) and fill in:
- PyPI Project Name:
engrama - Owner:
scops - Repository name:
engrama - Workflow name:
release.yml - Environment name:
pypi - Save. Subsequent tag pushes will publish automatically.
Same flow on https://test.pypi.org/ if you want to rehearse against TestPyPI first — register a pending publisher there with environment name testpypi and point the workflow at it (the published version of this workflow does not currently target TestPyPI; add a parallel publish-test job if needed).
Cutting a release¶
The release is driven entirely by the version-bump commit and the tag. There is no manual step inside the workflow.
- Pick a SemVer version (e.g.
0.9.1). - Bump it in three places —
guardianwill refuse to publish if any drifts: pyproject.toml—version = "..."engrama/__init__.py—__version__ = "..."changelog.md— add a new## [X.Y.Z] — YYYY-MM-DDheading at the top, with the release notes for this version. Keep the heading format intact; therelease-notesjob parses it.- Commit and open a PR titled
release: vX.Y.Z. Merge it tomainafter CI passes. - Tag the merge commit and push:
git checkout main && git pull
git tag -a vX.Y.Z -m "engrama vX.Y.Z"
git push origin vX.Y.Z
- Watch the run at https://github.com/scops/engrama/actions/workflows/release.yml. If
guardianfails, fix the drift onmain, delete the tag locally and remotely, and re-tag.
Dry-running a release¶
workflow_dispatch is wired with a dry_run toggle (defaults to true).
Use it to validate guardian + build + sbom + attest without publishing
or creating a Release:
- Actions → Release → Run workflow.
- Enter the candidate tag (e.g.
v0.9.1) and leavedry_run: true. - Pipeline runs everything up through SBOM + attest.
publishandrelease-notesare skipped viaif:guards.
Smoke-testing the wheel locally¶
Before tagging, run the wheel through a fresh venv to catch packaging
bugs that the matrixed import-smoke CI job can't see — e.g. a missing
file in MANIFEST.in, a broken [mcp] extra, or a CLI entry point that
crashes on a base install:
# 1. Build
uv build --sdist --wheel
# 2. Fresh venv with stdlib python (NOT uv venv — avoid uv's resolution cache)
python -m venv /tmp/engrama-cleantest
source /tmp/engrama-cleantest/bin/activate # PowerShell: . /tmp/engrama-cleantest/Scripts/Activate.ps1
# 3. Base install only
pip install dist/engrama-*.whl
# 4. cd OUT of the repo (so source tree doesn't shadow the installed package)
cd /tmp
# 5. Sanity-check the install path resolves to the venv, not the source tree
python -c "import engrama; print(engrama.__file__)"
# Expect: .../engrama-cleantest/.../site-packages/engrama/__init__.py
# 6. Exercise the zero-config SQLite path end-to-end
python -c "from engrama import Engrama; \
e = Engrama(); e.remember('Concept', 'Smoke', 'works'); \
print('hits:', len(e.recall('Smoke', hops=0)))"
# 7. CLI entry points
engrama --help
engrama-mcp --help # must fail with a CLEAR install hint, not a traceback
# 8. Now install the [mcp] extra and re-check
pip install "dist/engrama-*.whl[mcp]"
engrama-mcp --help # should print the usage block
If step 7's engrama-mcp call without the extra prints a Python traceback
instead of a one-line install hint, the friendly-error handler in
engrama/adapters/mcp/__init__.py regressed — fix that before tagging.
Verifying a release¶
After a release lands, you can verify the artifacts were built by this exact workflow:
gh attestation verify --owner scops <path-to-wheel-or-sdist>
The SBOMs (engrama-X.Y.Z.cyclonedx.json, engrama-X.Y.Z.spdx.json)
attached to the GitHub Release are the canonical SBOMs to ship to SCA
tools or enterprise procurement — they reflect what was actually built
and published, not just what the dependency graph thinks.
What lives where¶
| Concern | Location |
|---|---|
| Workflow | .github/workflows/release.yml |
| Version drift gate | guardian job |
| Build (wheel + sdist) | build job — uv build |
| CycloneDX + SPDX SBOM | sbom job — cyclonedx-bom + syft |
| pip-audit SBOM (cross-check) | sbom job |
| SLSA build-provenance | attest job — actions/attest-build-provenance |
| PyPI publish (OIDC) | publish job — pypa/gh-action-pypi-publish with attestations: true |
| GitHub Release | release-notes job — extracts top CHANGELOG entry |
| PR-time vuln gate | audit-deps job in .github/workflows/ci.yml |