Skip to content

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.

  1. Sign in to https://pypi.org/ with an account that owns (or will own) the engrama project.
  2. Go to Manage project → Publishing (or Your projects → Add a new publisher if the project doesn't exist yet).
  3. Click Add a new pending publisher (or Add a new publisher if the project already exists) and fill in:
  4. PyPI Project Name: engrama
  5. Owner: scops
  6. Repository name: engrama
  7. Workflow name: release.yml
  8. Environment name: pypi
  9. 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.

  1. Pick a SemVer version (e.g. 0.9.1).
  2. Bump it in three places — guardian will refuse to publish if any drifts:
  3. pyproject.tomlversion = "..."
  4. engrama/__init__.py__version__ = "..."
  5. changelog.md — add a new ## [X.Y.Z] — YYYY-MM-DD heading at the top, with the release notes for this version. Keep the heading format intact; the release-notes job parses it.
  6. Commit and open a PR titled release: vX.Y.Z. Merge it to main after CI passes.
  7. 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
  1. Watch the run at https://github.com/scops/engrama/actions/workflows/release.yml. If guardian fails, fix the drift on main, 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:

  1. Actions → ReleaseRun workflow.
  2. Enter the candidate tag (e.g. v0.9.1) and leave dry_run: true.
  3. Pipeline runs everything up through SBOM + attest. publish and release-notes are skipped via if: 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