Versioning & Changelog
Summary
Every Aetos artifact — library, service, or site — is versioned with Semantic Versioning and documents its releases in a Keep a Changelog file. The version file is the source of truth, the git tag must equal it, and a Drone guard enforces that equality before anything is published. Releases are cut manually; a tag is what triggers the pipeline.
This page pairs with 10 – Drone Pipelines: the version and tag contract is exactly what those two CI shapes consume.
The version contract
| Artifact | Source of truth | Notes |
|---|---|---|
| Library (PyPI package) | version in pyproject.toml |
check-version also reads it to skip duplicates |
| Service / site (Docker) | VERSION file at the repo root |
one line, x.y.z; the service reads it (build-arg) — never hardcode the version |
Rules:
- One version per repository. No per-module versions.
- Tag = version. The git tag is bare SemVer (
0.0.6), novprefix, and it MUST match the version file byte-for-byte. The Drone guard fails the build otherwise — this is what stops a tag from silently publishing nothing. - A release is a tag, created after the change is merged. The version bump and the changelog promotion travel inside the merge request (MR); the tag is added on the merged commit and pushed. See Tagging strategy.
- Never reuse a tag. If a release is wrong, cut a new patch version.
Pre-1.0 (0.y.z)
While a package is 0.y.z, 0.y is treated as the major line:
- breaking change →
0.(y+1).0 - new feature →
0.y.(z+1) - bug fix →
0.y.(z+1)
Once a package reaches 1.0.0, normal SemVer applies. Do not ship 1.0.0
until the public API is considered stable.
The changelog
CHANGELOG.md at the repo root, in Keep a Changelog form. It always has exactly
one open [Unreleased] section with the change categories that apply:
On release you promote it — never delete history:
and add a link reference at the bottom:
Conventions:
- Write entries for humans: one line each, imperative, referencing the user impact, not the commit hash.
- Categories: Added, Changed, Deprecated, Removed, Fixed, Security. Omit empty categories.
- Security fixes get their own entry even if the change is otherwise a bug fix.
- The
[Unreleased]heading is never renamed; copy its content into the new version heading and clear it.
Tagging strategy (MR-based)
The release version is known while the change is still a merge request, so the bump travels with the change and only the tag is added afterwards:
- In the MR — set the intended release version:
versioninpyproject.toml(or theVERSIONfile) tox.y.z, and promote the changelog (below). Changelog entries and link references name the version/tag, never a commit id. - Merge or squash the MR into
main. - Create the tag
x.y.zon the resulting commit (the merge/squash commit). - Push the tag (
git push origin x.y.z) — Drone builds the release.
Because the version bump is inside the tagged commit, the guard's
tag == version check can only fail if the wrong commit was tagged.
Agent-assisted tagging
The agent neither authors nor merges the MR, and never tags before the merge. After the user confirms the MR is merged/squashed and says to proceed:
- Wait for that explicit go-ahead.
- Find the commit that introduced the version bump:
- packages:
git log -S 'version = "x.y.z"' -n 1 --format='%H %s' -- pyproject.toml - services:
git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION - Confirm that commit's version file equals
x.y.z, then tag only that commit, lightweight and bare:git tag x.y.z <sha>. - Do not push. Ask the user to push the tag:
git push origin x.y.z.
Release checklist
- Confirm the quality gate is green and
mainis up to date. - In the MR: move every
[Unreleased]entry under the new version heading with the target date (## [x.y.z] - YYYY-MM-DD); restore an empty[Unreleased]on top. - In the MR: add the two compare-link references at the bottom of the changelog, naming the version/tag (never a commit id).
- In the MR: set the version file —
pyproject.toml(version = "x.y.z") orVERSION— to the intended release version. - Merge or squash the MR into
main. - Create the tag on the resulting commit: lightweight
git tag x.y.z. - Push the tag (
git push origin x.y.z). Watch Drone: Type A publishes the wheel/sdist, Type B builds and deploys the image; both first asserttag == version. - Do not reuse the tag if the run fails — fix and cut
x.y.(z+1).
Why the guard matters
The version file and the tag are two facts that must agree; nothing in git
enforces that by itself. A mismatch is not a theoretical risk — a package can be
tagged 0.0.2 while its pyproject.toml still says 0.0.1, and the publish
step (which compares the file against the index) then skips the build
entirely, so 0.0.2 never reaches the index while looking like a release. The
Drone guard turns that silent no-op into a hard failure.