Releasing
The full release and deployment process (protected main, Makefile, GitHub
Actions, gh CLI, checklist, troubleshooting) lives in:
AGENTS.md: Releasing and deployment
User-facing history (required on every release):
Short path: merge a release PR that bumps pyproject.toml and rolls
CHANGELOG.md (## [X.Y.Z] + compare link) → Auto-tag release pushes
vX.Y.Z if missing → dispatches Release with
gh workflow run Release --ref main -f tag=vX.Y.Z → lint/tests/gate → Nuitka
→ Cosign → GitHub Release.
You do not need a manual tag push after merge (that was what skipped 0.5.0
until fixed). After merge, do not also push a local vX.Y.Z unless Auto-tag
failed. Dual triggers race and waste runner minutes.
Hard gates (automated): Auto-tag and Release refuse to ship if
CHANGELOG.md lacks ## [X.Y.Z] / the bottom compare link, or if the tag does
not match pyproject.toml version (except explicit emergency
source_ref= rebuilds).
Emergency rebuild of an existing tag (packaging / native-bridge hotfix on
main, same version, do not move the published tag):
gh workflow run Release --ref main -f tag=v0.10.0 -f source_ref=main
That checks out main for the build while still publishing GitHub Release
v0.10.0. A plain re-run of the failed tag job will not pick up commits
that landed after the tag.
Provenance: each asset gets a Cosign .sigstore.json bundle and a
GitHub Attestations record (gh attestation verify). Release notes include the
CHANGELOG section for that version plus verify commands. Optional SignPath
Authenticode for Windows is off until repository vars/secrets are set (see
AGENTS.md).
macOS notarization: when the Developer ID secrets are set, the build job
signs each DMG with the team's Developer ID Application certificate
(team 83546T4BT8, G2 cert, expires 2031-09-17), notarizes it, and staples it
before Cosign. The p12 secret is an OpenSSL 3 pkcs12 -export -legacy export.
Without those secrets the DMG stays ad-hoc and notarization is skipped.
The notary upload returns a submission id immediately. The job then checks
that same Apple submission for up to five hours instead of uploading the DMG
again.
Non-code files Nuitka drops under Contents/MacOS are moved to
Contents/Resources (with symlinks left behind) before that signature, because
a Developer ID seal rejects them as unsigned nested code. Secret names and the
entitlements are in
AGENTS.md.
To test signing without publishing a GitHub Release:
gh workflow run Release --ref <branch> -f macos_only=true -f publish=false
The run uploads exr_converter-macos-arm64-signed and
exr_converter-macos-x86_64-signed. On a downloaded DMG:
spctl -a -vvv -t install exr_converter-macos-arm64.dmg
codesign --verify --deep --strict --verbose=2 exr_converter-macos-arm64.dmg
Microsoft Store: the Windows build can also publish
exr_converter-windows-x86_64.msixupload (unsigned Partner Center upload)
and exr_converter-windows-x86_64.msix (self-distribution). The loose
.msix is signed with signtool when Azure Artifact Signing secrets are
set, and left unsigned otherwise. An unsigned MSIX does not install. The
upload's contents are never signed. A later release can also submit the
.msixupload with msstore when the Store secrets and MSSTORE_APP_ID
are set. The first submission is manual, and a publish failure does not
block the GitHub Release. Steps: msix.md.
Docs site: Markdown under docs/ is built with Astro (site/) and published
by Vercel on push to main (config in site/vercel.json).
That is independent of the versioned app Release workflow. Preview locally
with make docs-serve. Public URL:
exr-converter.derekvfx.ca.
User-facing ProRes / VideoToolbox guide: prores.md. Related design notes (not release machinery): plan-12bit-prores-oxideav.md.
Source: EXR Converter on GitHub