Release process¶
This document describes the current release path for The Merovingian.
Alpha prereleases¶
Alpha packages are published from Git tags that match this pattern:
Pushing a matching tag triggers release.yml. That workflow:
- builds hardened packages for Linux and FreeBSD (
linux-alpha-package,freebsd-alpha-package) — OpenBSD and NetBSD packages are not part of the alpha release workflow; they are built by the rollingpackages.ymlworkflow and published to itslatestGitHub release instead - runs the full Meson test suite on both platforms
- runs Linux phase 1 configuration validation
- runs the unsafe-source gate
- runs release-readiness metadata checks
- packages
merovingian-serverandmerovingian-db-migrate - attaches SHA-256 checksum files
- collects release evidence per platform (
scripts/collect-release-evidence.sh): compiler version, link-time hardening confirmation (scripts/check-elf-hardening.sh), pinned dependency versions fromsubprojects/*.wrap, the test log's Ok/Fail/Timeout summary, and the mandatory fuzz target list — folded into the published release notes alongside the package checksums - publishes a GitHub prerelease with the built assets
Each tarball carries a SLSA provenance attestation generated by
actions/attest-build-provenance and verifiable with:
Published releases also trigger sbom.yml,
which generates SPDX and CycloneDX JSON SBOM files and attaches them to the
GitHub release as merovingian.spdx.json and merovingian.cdx.json.
Repository changes are additionally covered by:
- secret-scan.yml for Gitleaks-based history scanning
- dependency-vulnerability-triage.yml for PR dependency review and SBOM-backed vulnerability triage
- pages.yml, which builds the documentation
site with
mkdocs build --stricton every branch and pull request and deploys it to https://omg-software.github.io/Merovingian/ frommain, so the published documentation always matches the latest merged release line
The packaged tarballs include:
bin/merovingian-serverbin/merovingian-db-migrateconfig/merovingian.conf.exampledocs/user-manual.mddocs/release-process.mddocs/security-review-checklist.md- Linux and BSD packaging scaffolds, including Debian control metadata, RPM spec metadata, FreeBSD manifest metadata, OpenBSD packing-list metadata, and NetBSD/pkgsrc Makefile metadata
README.mdLICENSE
Starting a minor series: the upgrade fixture¶
Upgrades are supported from 1.0.0 onward, so this applies to minor series from 1.1 on. A database written by the previous supported minor series must open, migrate and serve under every candidate (ADR-0216). When the first version of such a series is cut (for example 1.1.0), add the fixture of the last release of the series before it (there, the last 1.0.x release):
python scripts/generate_upgrade_fixture.py <last-release-commit> tests/fixtures/upgrade/<last-release-version>
Then add scenarios to tests/integration/test_database_upgrade_flow.cpp that open that
fixture and serve it, and remove the previous fixture directory. Never regenerate a fixture from current code: it is
evidence only because the old release wrote it.
A pre-1.0 series start (such as 0.13.0) adds no fixture and no upgrade test. The migrations were collapsed into one initial schema in 0.12.47 (ADR-0224), so a pre-1.0 database can no longer be opened: that change removed the 0.11.13 fixture and its scenarios, and there is no fixture until the 1.1 series starts.
The v1 schema is frozen from 1.0.0¶
From 1.0.0, the v1 migration (the initial_schema step in src/database/migration.cpp)
and migrations/001_initial_schema.sql never change. A schema change is a new numbered
migration (v2 onwards) that keeps deployed databases compatible. A unit test requires the
SQL file to match the build's v1 step statement for statement.
Verifying release signatures¶
Every release tarball and package is accompanied by a detached GPG signature
(.asc). The signing key is an offline, long-lived maintainer key stored as a
GitHub Actions secret. Operators can verify artifacts without the GitHub CLI.
Verify a release artifact:
gpg --verify merovingian-<version>-linux-x86_64.tar.gz.asc \
merovingian-<version>-linux-x86_64.tar.gz
If the signature is valid, GPG reports a good signature from the maintainer identity. A missing or invalid signature must be treated as a build or distribution failure.
Release assets also include:
SHA256SUMSfor every package (in rollinglatestreleases)- per-tarball
.sha256files for alpha releases merovingian.spdx.jsonandmerovingian.cdx.jsonSBOMsmerovingian.licenses.jsonmachine-readable license summary
Operator sequence¶
The release operator should still run the normal local evidence path before creating the tag:
sh scripts/setup-dev-env.sh --check-only
meson setup build -Dbuild_tests=true -Dbuild_fuzz=true
meson compile -C build
meson test -C build --print-errorlogs
sh scripts/reject-unsafe.sh
sh scripts/check-release-readiness.sh
After that, create and push an alpha tag:
GitHub Actions publishes the prerelease assets for that tag. The matching release event also publishes the SBOM assets, and the scheduled dependency-triage workflow keeps a separate vulnerability report artifact.
Rolling latest packages¶
Pushes to main also trigger packages.yml.
That workflow rebuilds packages for all supported platforms, replaces the
rolling latest GitHub prerelease, and uploads fresh package checksums:
| Artifact | Job |
|---|---|
Ubuntu .deb |
deb |
Debian trixie .deb |
debian-pkg |
Fedora .rpm |
rpm |
RHEL-compatible .rpm (AlmaLinux 10) |
rhel-rpm |
OpenSUSE Tumbleweed .rpm |
opensuse-rpm |
FreeBSD .pkg |
freebsd-pkg |
OpenBSD .tgz |
openbsd-pkg |
NetBSD .tgz |
netbsd-pkg (retried once on QEMU infrastructure failure) |
| Portable static Linux tarball (musl) | static-linux-fallback |
Every packaging script configures Meson with --buildtype=release. Meson's
default debug build has no _FORTIFY_SOURCE, and the server refuses to start
when its startup hardening self-check cannot confirm it
(ADR-0148).
A new packaging path must do the same, and be added to PACKAGING_SCRIPTS in
tests/tooling/test_packages_workflow.py.
Before attesting, the deb and debian-pkg jobs install the package they
built and run scripts/check-startup-hardening-gate.sh against the installed
/usr/bin/merovingian-server, as the merovingian user with an empty
capability bounding set. The job fails unless every startup hardening check
reports enabled and the server stops cleanly. The docker-image job does the
same for the Dockerfile: it builds the image and runs the gate inside it with
--cap-drop=ALL --security-opt=no-new-privileges. The image is not published;
the job is a gate on the recipe.
The Dockerfile pins debian:trixie-slim by the digest of its
multi-architecture index. To move to a newer base, read the current digest
(for example docker buildx imagetools inspect debian:trixie-slim, the
Digest: line of the index, not a per-platform manifest), replace it in both
FROM lines, and let the docker-image job prove the new image still passes
the gate.
Each package is individually attested with
actions/attest-build-provenance before upload, verifiable with:
The publish step also creates a detached GPG .asc signature for every
package (including SHA256SUMS) using the maintainer key. Verify with:
The publish step resolves and deletes the existing latest release with an
explicit --repo "${{ github.repository }}" scope before recreating it so the
artifact-only job does not depend on checkout state.
The static Linux fallback artifact is built on Alpine/musl with -static-pie
and packaged as merovingian-<version>-linux-static-x86_64.tar.gz. It is a
portability fallback for older Linux distributions that cannot easily consume
the .deb or .rpm; distro packages remain preferred where available because
their OpenSSL, libsodium, and libpq dependencies receive normal OS security
updates.