PersonalAI releases are signed with Sigstore/cosign and ship a CycloneDX SBOM (DEPENDENCY-POLICY).
PersonalAI uses one product version, kept as a single source of truth.
- Canonical source: the root
VERSIONfile holds the product version (currently0.9.0). This is the value to read when you need "the product version." - Runtime mirror:
apps/backend/src/personalai_backend/__init__.py__version__must be kept in sync withVERSION. It is the value the running app actually reports —app.pyfeeds it into the OpenAPI document'sinfo.versionand the unversionedGET /versionendpoint (nothing reads theVERSIONfile at runtime, so this string is what ships). - Packaged product manifests carry the same product version:
apps/backend/pyproject.toml,tools/mcp/pyproject.toml, andapps/ui/package.json. - Internal-only libraries stay at
0.0.0on purpose:contracts,core,providers/*,storage/*,modalities/*, andbenchmarks. They are never published independently — they are consumed only asuv-workspace path/source dependencies of the backend app, so a real version number would be noise. The single shareduv.lockat the repo root pins everything; member versions don't gate resolution. Leaving them at0.0.0keeps the product version unambiguous.
Policy (pre-1.0) — same as the CHANGELOG header: MAJOR stays 0 until
the first stable release; MINOR bumps for new capabilities; PATCH bumps for fixes/UX tweaks.
The current version is 0.9.0 (read it from the VERSION file). The HTTP API is
versioned independently in the URL path (/api/v1).
To bump the product version, update all five product files together — VERSION, the backend
__init__.py __version__, apps/backend/pyproject.toml, tools/mcp/pyproject.toml, and
apps/ui/package.json — then cut the CHANGELOG release (move [Unreleased] into a dated
[x.y.z] section and update the compare-link footers). Tagging is vX.Y.Z.
Status: the release pipeline and a CI signing smoke test exist. Distribution channels and a reproducible-build guarantee are still being refined (see the note below).
On a published GitHub release (.github/workflows/release.yml):
- Python distributions are built (
uv build --all-packages->dist/). - A CycloneDX SBOM is generated (
scripts/generate_sbom.sh). - Each artifact and the SBOM are signed keyless with cosign, using the workflow's GitHub
OIDC identity (no long-lived keys). This produces a
.sig(signature) and.crt(certificate) per file, recorded in the public Sigstore transparency log. - Each is verified in-workflow, then attached to the release.
The workflow can be run as a dry run via workflow_dispatch (artifacts are uploaded as a CI artifact instead of to a release).
cosign verify-blob <artifact> \
--signature <artifact>.sig \
--certificate <artifact>.crt \
--certificate-identity-regexp "https://github.com/lucianhanga/personal-ai/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"A successful verification proves the artifact was produced by this repo's release workflow and has not been tampered with.
Every PR/push runs a Signing smoke (cosign) job that signs and verifies a throwaway artifact
with an ephemeral key, fully offline (--tlog-upload=false, --insecure-ignore-tlog=true).
This proves the signing toolchain works without polluting the public transparency log. Locally:
# requires cosign on PATH
make signing-smokeGoal: identical inputs produce byte-identical artifacts so signatures are independently reproducible. Current status and next steps:
- The SBOM generator already uses
--output-reproduciblefor stable output. - Python wheels via
uv buildare largely deterministic but embed timestamps; pinningSOURCE_DATE_EPOCHand auditing the build for nondeterminism is a follow-up. - No reproducible-build guarantee is claimed yet.