Skip to content

Release Process

How a SolidSyslog release is cut, for the maintainer. Releases are source-only (no binary artefacts) with a signed SBOM, a reproducible source-tree hash and a signed offline copy of the documentation attached so integrators can verify provenance and keep the documentation for the version they pinned (per security/release-verification.md and security/sbom.md). Security releases run in lockstep with security/triage-runbook.md.

Versioning

  • Semantic Versioning. Pre-1.0 (0.x), breaking changes bump the minor, not the major (bump-minor-pre-major), no premature 1.0 signal while the API is still settling.
  • release-please derives the version from the Conventional Commit types merged since the last release.

What the release notes contain

Every release carries three parts, in this order. The first two are written; the third is generated.

  1. What's in this release - prose, plus a compliance and platform snapshot. What the release is for, and what changed that an integrator would act on.

This is also where a documentation change that corrected a claim an integrator could have acted on gets surfaced, since docs commits do not appear in the generated list.

The snapshot is restated here rather than linked, which is deliberate and is not the duplication the documentation rules forbid. Those rules guard against two current-state copies drifting apart. A release note is not a copy - it is a frozen record with a different lifetime. The compliance matrix and the platform matrix answer where are we now; the release note answers where were we at this version, which nothing else records. The site publishes from main only, so a link out of an old release resolves to today's state and silently loses what that release actually shipped.

Where the full detail is wanted, link the tag rather than the branch - blob/vX.Y.Z/docs/rfc-compliance.md is frozen by git even though the site is not.

What to include:

  • RFC compliance. State in prose what moved - a clause newly met, a standard newly covered. Restate the summary table whenever a number changed; one line saying nothing changed when none did. Read the numbers off the matrix at the tag, not from memory.
  • Platforms. Name what was added, then give the full list. "Unchanged" is rarely true of the rest, since most releases touch them.

Restating absolute numbers rather than only deltas is what keeps the series answerable: deltas compose badly, and one wrong delta propagates through every later release with nothing to correct it.

Carry the qualification with the numbers. The matrix states that a status describes the library with a conforming platform supplying the roles it needs, that almost every requirement depends on which platform components are selected and how they are configured, and that this includes components the integrator writes, which the library cannot speak for. A table lifted out of that preamble claims more than the matrix does - and unlike the matrix, a release note is frozen and cannot be corrected later. So say in the release note that the figures are the maintainer's assessment of the library against the RFCs, that they depend on how the integrator configures it, and that they are neither a certification nor a conformance claim. One sentence is enough; omitting it is not. 2. Known limitations - the defects and divergences shipping with the release, each linking its tracking issue. State that they were found by audit and are disclosed on the pages that describe the affected platform: a bare list of open defects reads as unfinished work, and the same facts framed as deliberate disclosure read as rigour. 3. Full changelog - release-please's generated list.

Getting the written parts into both places

The CHANGELOG.md entry and the GitHub Release description carry the same three parts, and nothing copies one to the other.

  1. Write the two written parts and paste them above the generated list in CHANGELOG.md, in the release pull request, before merging it.
  2. After the Release exists, build a file holding all three parts - the two written ones and the generated list, which the merged CHANGELOG.md entry now contains - and set the description from it:
gh release edit v<version> --notes-file <file>

--notes-file replaces the Release description rather than adding to it, so a file carrying only the written parts would delete the generated changelog from the Release. Take the generated section from the merged CHANGELOG.md entry, or from the Release's own body with gh release view --json body, before editing.

Do not assume the Release description picks up a hand-edited CHANGELOG.md. Setting it explicitly is correct whether it would or not, and editing a Release fires release: edited rather than release: published, so neither sbom.yml nor docs-bundle.yml re-runs, and the signed assets are undisturbed.

What appears in the generated changelog

feat and fix only. refactor, ci, chore and docs are configured hidden in release-please-config.json, because none of them changes what a consumer of the library gets - refactoring is defined as preserving behaviour, and the other three never reach the consumer at all.

Breaking changes surface in their own section regardless of the type that carried them.

The Conventional Commit type is therefore a statement about consumer impact, not only about which files were touched. A change that alters what an integrator should do belongs under a type that appears.

Cutting a release

CHANGELOG.md is the authoritative record of what changed between releases. Its generated section is built by release-please from the Conventional Commit types listed above; the two written parts are added by hand in the release pull request.

  1. Conventional Commits land on main. feat and fix map to a generated CHANGELOG section; the hidden types above produce no entry, and a breaking change is listed whichever type carried it.
  2. release-please maintains a release PR that bumps the version and CHANGELOG.md. Write the first two parts of the release notes into that PR before merging it, per Getting the written parts into both places above.
  3. Merging that PR creates the tag and a draft GitHub Release (made with the release-please GitHub App's token, no personal GPG/SSH signing). The tag is created at once; a draft would otherwise get its tag only on publication, and release-please could not then find the previous release.
  4. Publishing the draft is a separate step. Nothing the release attaches exists until then, so publish once everything the documentation links by name is in place - the example stacks' release/<version> branches, which pin the tag's SHA and so can only be cut once the tag exists. Publish with gh release edit v<version> --draft=false --latest, so the release is the one GitHub serves as latest; a fix to an older line takes --latest=false instead.
  5. The release: published event triggers sbom.yml: it renders and validates the CycloneDX SBOM, writes the content-tree SHA-256 (scope: Core/ + Platform/ + CMakeLists.txt, CMakePresets.json, LICENSE.md, LICENSES/), cosign keyless-signs both (GitHub OIDC), and attaches each file with its signature to the Release.
  6. The same event triggers docs-bundle.yml: it builds the documentation from the tag with mkdocs-offline.yml, checks that the result works with no server and no network, cosign-signs the zip and attaches it with its signature. The bundle is built and checked on every CI run as well, so only the signing and the attachment wait for a release.
  7. Signing and attachment hard-fail. The Release already exists by the time the jobs run, so a failure cannot block it - it means the Release went out without provenance. A red run is the signal: fix the cause and re-run the job (see release verification).

Why the documentation ships as a file

A published copy of the site for every version would answer the same question, and was the original plan. It was dropped: indexed version directories make superseded API pages findable by search, the site grows with every release, and published URLs cannot be withdrawn once linked. A fixed, hashed, signed file is also closer to what an archival reader wants - it can be kept and verified later, which a URL cannot.

The reader-facing consequence is that the bundle's footer names the release and nothing else. The commit, the tag, the build date and the toolchain digest are in MANIFEST.txt at the root of the bundle, where an auditor looks for them.

Security releases

Coordinated with the disclosure; see the runbook's Release coordination stage:

  • High / Critical: develop the fix on the advisory's temporary private fork; the draft GHSA stays the single tracking record, published coordinated with the release.
  • Low / Medium: fix in the open; the advisory publishes when the release ships.
  • Record affected and fixed version ranges in the advisory before publishing.

Checklist

  • [ ] main is green.
  • [ ] Cut the release by merging the release PR.
  • [ ] Confirm the tag and the draft GitHub Release, and read the notes in the draft before anything goes out.
  • [ ] Set the draft's notes explicitly with gh release edit, per Getting the written parts into both places.
  • [ ] Cut the example stacks' release/<version> branches pinned to the tag's SHA, then publish the draft.
  • [ ] Confirm GitHub marks the new release as Latest: gh api repos/cososo-ltd/solid-syslog/releases/latest --jq .tag_name.
  • [ ] Confirm the attachments, and verify all six assets - the SBOM, source hash and documentation bundle, and their three cosign signatures - per security/release-verification.md, not just that the assets are present - a bundle that is present is not yet a bundle that verifies. Install the tool versions that page states rather than using whatever is already on your $PATH: verifying with your own toolchain proves the signature good but hides any drift between the guide and what the workflow actually produces, which is the failure an integrator meets first.
  • [ ] Open the documentation bundle before trusting it. Download solid-syslog-docs-<version>.zip, extract it and open index.html with the network off. The automated check proves every link resolves inside the bundle and that nothing is fetched from another host; it cannot tell you the pages render acceptably, and nothing else in the pipeline looks at them. Confirm the footer names the release, and that the notice explaining how to restore search appears.
  • [ ] Security release: publish the coordinated GHSA.