feat(standards): add versioning + changelog standard, release templates, and Drone version guard
Add lib page 11 (SemVer contract, pre-1.0 rule, tag==version invariant, Keep a Changelog), the releasing wiki runbook, and canonical release templates (CHANGELOG.md, VERSION). Vendor them into the MCP bundle as templates/release + release.json and expose registry://release with list/get tools. Add a changelog component to the app template. Enforce the tag<->version contract in both Drone shapes: Type A check-version and the backfill loop verify pyproject version; Type B adds a VERSION guard step.
This commit is contained in:
@@ -25,9 +25,10 @@ needs no edits, the Type B file needs its placeholders filled in (below).
|
||||
For internal libraries. The file contains **two pipelines**:
|
||||
|
||||
1. **`build-and-publish-pypi`** — triggered on `tag`:
|
||||
- `check-version` — read `name` / `version` from `pyproject.toml` and query
|
||||
- `check-version` — **guard**: fail unless `$DRONE_TAG` equals the version in
|
||||
`pyproject.toml`; then read `name` / `version` and query
|
||||
`$PIP_REPO_URL/simple/<package>/`; if that version already exists, exit
|
||||
`78` and skip the whole build.
|
||||
`78` and skip the whole build (see [11 – Versioning & Changelog](11_versioning_and_changelog.md)).
|
||||
- `quality-gate` — install the package with its `[dev]` extras from the
|
||||
private index, then `black --check`, `ruff check`, `mypy`, `pydoclint`,
|
||||
`pytest`.
|
||||
@@ -36,8 +37,9 @@ For internal libraries. The file contains **two pipelines**:
|
||||
2. **`backfill-pypi-from-tags`** — triggered on `custom`:
|
||||
- `fetch-tags` — `git fetch --tags --force`.
|
||||
- `build-and-upload-missing` — walk every tag in version order, skip the ones
|
||||
already on the index, `git checkout` the rest, build, and upload. This is
|
||||
the disaster-recovery / replay path.
|
||||
already on the index, `git checkout` the rest, verify `tag == pyproject
|
||||
version` at that commit, build, and upload. This is the disaster-recovery /
|
||||
replay path.
|
||||
|
||||
Secrets: `PIP_REPO_URL`, `PIP_USERNAME`, `PIP_PASSWORD`.
|
||||
|
||||
@@ -55,6 +57,8 @@ pipelines**, each mirroring a repo family:
|
||||
API/worker service, e.g. `auth-server`):
|
||||
- `fetch-tags` — resolve the latest tag into `LATEST_TAG.txt`; fail if there
|
||||
are no tags.
|
||||
- `check-version` — **guard**: fail unless `$DRONE_TAG` equals the repo-root
|
||||
`VERSION` file (see [11 – Versioning & Changelog](11_versioning_and_changelog.md)).
|
||||
- `check-remote-image` — if `aetos/<app>:<tag>` already exists locally, exit
|
||||
`78` and skip the build.
|
||||
- `quality-gate` — install from `requirements.txt` and run
|
||||
@@ -67,7 +71,8 @@ pipelines**, each mirroring a repo family:
|
||||
--restart always` with the service's `-e` environment.
|
||||
2. **`build-and-deploy-site`** — triggered on `push` (a static/HTTP site, e.g.
|
||||
`docs`, `mcp`): `build-image` → `push-image` → `stop-old` → `run-container`,
|
||||
with no tag resolution and no quality gate.
|
||||
with no tag resolution, no quality gate, and no version guard (it ships
|
||||
`:latest` only).
|
||||
|
||||
Secrets: `PIP_REPO_URL`, `PIP_USERNAME`, `PIP_PASSWORD` (service build args),
|
||||
`REGISTRY_HOST`, `REGISTRY_USER`, `REGISTRY_PASS`, plus the service runtime env
|
||||
@@ -91,6 +96,9 @@ Placeholders to fill in per repo:
|
||||
- Do not inline secrets; always use `from_secret`.
|
||||
- Do not drop the `check-version` / `check-remote-image` skip — it guards the
|
||||
index and the running container against duplicate publishes.
|
||||
- Do not drop the tag↔version guard. A package needs its `pyproject.toml`
|
||||
version to equal the tag; a service needs a root `VERSION` file that equals
|
||||
the tag. See [11 – Versioning & Changelog](11_versioning_and_changelog.md).
|
||||
- Do not rename the pipelines; tooling and runbooks key off
|
||||
`build-and-publish-pypi`, `build-and-deploy-service`, and
|
||||
`build-and-deploy-site`.
|
||||
@@ -99,4 +107,4 @@ Placeholders to fill in per repo:
|
||||
|
||||
## Related
|
||||
|
||||
- [01 – Build Recipe](01_build_recipe.md) · [09 – Infra Adapters](09_infra_adaptors.md) · [13 – Library Registry](13_library_registry.md)
|
||||
- [01 – Build Recipe](01_build_recipe.md) · [09 – Infra Adapters](09_infra_adaptors.md) · [11 – Versioning & Changelog](11_versioning_and_changelog.md) · [13 – Library Registry](13_library_registry.md)
|
||||
|
||||
119
standards/src/lib/11_versioning_and_changelog.md
Normal file
119
standards/src/lib/11_versioning_and_changelog.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Versioning & Changelog
|
||||
|
||||
## Summary
|
||||
|
||||
Every Aetos artifact — library, service, or site — is versioned with
|
||||
[Semantic Versioning](https://semver.org/spec/v2.0.0.html) and documents its
|
||||
releases in a [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) 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](10_drone_pipelines.md): 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` |
|
||||
|
||||
Rules:
|
||||
|
||||
1. **One version per repository.** No per-module versions.
|
||||
2. **Tag = version.** The git tag is bare SemVer (`0.0.6`), no `v` prefix, 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.
|
||||
3. **A release is a tag.** Bump the version file, promote the changelog, commit,
|
||||
tag, push. Everything else is automated by Drone.
|
||||
4. **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:
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
### Changed
|
||||
### Deprecated
|
||||
### Removed
|
||||
### Fixed
|
||||
### Security
|
||||
```
|
||||
|
||||
On release you **promote** it — never delete history:
|
||||
|
||||
```markdown
|
||||
## [0.0.6] - 2026-03-07
|
||||
|
||||
### Added
|
||||
- ...
|
||||
```
|
||||
|
||||
and add a link reference at the bottom:
|
||||
|
||||
```markdown
|
||||
[Unreleased]: https://git.aetoskia.com/aetos/<repo>/compare/0.0.6...HEAD
|
||||
[0.0.6]: https://git.aetoskia.com/aetos/<repo>/compare/0.0.5...0.0.6
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Release checklist
|
||||
|
||||
1. Confirm the quality gate is green on `main`.
|
||||
2. Move every `[Unreleased]` entry under the new version heading with today's
|
||||
date (`## [x.y.z] - YYYY-MM-DD`); restore an empty `[Unreleased]` on top.
|
||||
3. Add the two compare-link references at the bottom of the changelog.
|
||||
4. Set the version file: `pyproject.toml` (`version = "x.y.z"`) or `VERSION`.
|
||||
5. Commit: `chore(release): x.y.z`.
|
||||
6. Tag: `git tag x.y.z` and push the tag (`git push origin x.y.z`).
|
||||
7. Watch Drone: Type A publishes the wheel/sdist, Type B builds and deploys the
|
||||
image. Both first assert `tag == version`.
|
||||
8. 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.
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- [10 – Drone Pipelines](10_drone_pipelines.md) · [01 – Build Recipe](01_build_recipe.md) · [13 – Library Registry](13_library_registry.md)
|
||||
@@ -15,6 +15,8 @@ patterns, and the internal library catalog that agents consult while coding.
|
||||
- **Human-in-the-Loop** — guardrails and approvals inside automations.
|
||||
- **Infra Adapters** — how infrastructure connects without leaking into domain.
|
||||
- **Drone Pipelines** — the two CI shapes: a PyPI package or a Docker deployment.
|
||||
- **Versioning & Changelog** — SemVer, the tag↔version contract, and the release
|
||||
changelog.
|
||||
- **Genericity Matrix** — what is library-backed, template-provided, or authored.
|
||||
- **Auth Integration** — consuming the shared auth-server service (spec-driven,
|
||||
no auth code).
|
||||
|
||||
Reference in New Issue
Block a user