Files
ConformalLabpp/doc/release-policy.md
Tarik Moussa 0b1bf07232 docs: fix 2 broken internal links
- doc/release-policy.md:85 corrected `doc/api/tests.md` link to relative
  `api/tests.md` (was resolving to nonexistent doc/doc/api/tests.md).
- doc/tutorials/block-fd-hessian.md:40 redirected stale reference
  `../math/hyper-ideal.md` to the actual file `../math/geometry-modes.md`.

Found by a sweep of all doc/*.md before the reviewer meeting.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:09:40 +02:00

217 lines
9.4 KiB
Markdown

# Release & versioning policy
> **Audience:** maintainers and recurring contributors of conformallab++.
> External users only need this if they are publishing a fork.
>
> **Created:** 2026-05-22, after the v0.9.0 release that exposed two
> recurring failure modes: (a) the v0.9.0 tag had to be moved twice
> because release-prep landed on the wrong commit, and (b) the test
> counts were hardcoded in eight different documents and drifted apart.
This document codifies how conformallab++ chooses version numbers,
how releases are produced, and which artefacts must stay in sync.
---
## 1. Version-number rules (SemVer)
The project follows [Semantic Versioning 2.0](https://semver.org).
Tag format: `vMAJOR.MINOR.PATCH` (e.g. `v0.9.0`).
### Pre-1.0 phase (current — until CGAL submission)
While the project is at version `0.x.y`, the public API is **not yet
frozen**. The convention we use during the pre-1.0 phase is:
| Bump | When |
|---|---|
| **`0.x.0 → 0.(x+1).0`** (MINOR) | Significant new feature: new functional, new Newton solver, new public CGAL entry, new Phase milestone completed. May break the public API. Treated as the "interesting" release type during pre-1.0. |
| **`0.x.y → 0.x.(y+1)`** (PATCH) | Bug fixes, performance optimisations without API change, doc-only follow-up releases, CI/infrastructure fixes. Never breaks the public API. |
| **`0.x.y → 1.0.0`** | API freeze + CGAL submission readiness. All public headers under `include/CGAL/` are stable. Cumulative changelog updated to call out every public-API decision in retrospect. |
Phase milestone → MINOR-bump mapping (historical + planned):
| Tag | Phase milestone |
|-----------|--------------------------------------------------------|
| `v0.1.0` | Phase 1 (special functions) |
| `v0.2.0` | Phase 2 (hyper-ideal geometry) |
| `v0.7.0` | Phase 7 complete (period matrix, fundamental domain) |
| `v0.9.0` | Phase 9a + 9b + 8b-Lite |
| `v0.10.0` | Phase 9c (4g-polygon, genus g > 1) |
| `v0.11.0` | Phase 10a (harmonic + holomorphic 1-forms) |
| `v1.0.0` | CGAL package submission-ready |
### Post-1.0 phase (planned, ≥ `v1.0.0`)
Standard SemVer with no pre-1.0 exceptions:
| Bump | When |
|---|---|
| **MAJOR** (`X.0.0 → (X+1).0.0`) | Any breaking change to the public API: removed function, signature change, renamed CGAL entry, behaviour change of a documented contract. |
| **MINOR** (`X.Y.0 → X.(Y+1).0`) | New features, new public entries, new Default-traits classes. Strictly additive — no removal, no behaviour change. |
| **PATCH** (`X.Y.Z → X.Y.(Z+1)`) | Bug fixes only. No new files, no new public symbols. |
### Pre-release tags
For testing-quality releases:
* `v1.0.0-alpha.1`, `v1.0.0-alpha.2`, … — early preview, API may still
shift.
* `v1.0.0-beta.1` — feature-complete preview, API stable but tests
may still find issues.
* `v1.0.0-rc.1`, `v1.0.0-rc.2`, … — release candidate, only blocker
fixes before the final tag.
### What does NOT trigger a release
* Internal refactoring without user-visible change.
* Documentation-only updates (unless the documentation is itself a
product, like a new tutorial).
* Test additions / coverage improvements.
* CI / infrastructure changes.
Group such changes into the next planned release.
---
## 2. Single source of truth for moving numbers
Three numbers tend to drift across multiple documents in any project:
test counts, version strings, and date strings. The conformallab++
rule is:
| Number / string | Single source of truth | Anywhere else |
|-----------------------------|--------------------------------|--------------------------------------|
| **Total / per-suite test counts** | `doc/api/tests.md` | Use qualitative phrasing + link, e.g. "full test suite passes, 0 skipped — see [`doc/api/tests.md`](api/tests.md)". |
| **Project version** | `CITATION.cff` `version:` field | Don't hardcode in headers, READMEs, doc bodies. Reference by name ("v0.9.0") only when the historical version actually matters (changelog entries, release-note bodies, Phase-milestone tables). |
| **Release date** | `CITATION.cff` `date-released:` + `CHANGELOG.md` section header | Don't hardcode elsewhere. |
| **Test-suite description per file** | `doc/api/tests.md` | Headers and code files may list the suites they implement, but never claim a count of suites elsewhere. |
### Why qualitative phrasing for test counts
The counts change with every PR that adds or removes a test. Hardcoding
the number "227" in eight documents means eight separate edits per PR —
and historically we have failed to keep them in sync more than once.
The cost of looking up the exact number in `doc/api/tests.md` is one
click; the cost of fixing a stale "227" in seven places is real PR
overhead. We pay the click.
### Audit script
`scripts/check-test-counts.sh` (when present) reads the totals from
`doc/api/tests.md`, runs `ctest`, and fails the CI if they diverge.
This catches the `tests.md` itself going stale even if no other doc
hardcodes the number.
---
## 3. Release process (checklist)
The recipe that worked for v0.9.0 (after the false-start with PR #11 /
#12). Apply for every `vX.Y.Z` release.
### Pre-release prep — on a release branch
1. **Update `CHANGELOG.md`** — add a new top-level section
`## [vX.Y.Z] — YYYY-MM-DD`. Categorise changes by `Added`,
`Changed`, `Removed`, `Fixed`, `Deprecated`, `Security`. See
[Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/).
2. **Update `CITATION.cff`**`version:` and `date-released:` fields.
3. **Refresh `doc/api/tests.md`** — re-run the full test suite locally
and update the per-suite tables + the two `**Total: N tests, 0 skipped.**`
lines (one for `conformallab_tests`, one for `conformallab_cgal_tests`).
4. **Run `scripts/check-test-counts.sh`** if present — must pass.
5. **Verify no hardcoded counts remain** elsewhere:
```bash
grep -rn "[0-9]\+ tests pass\|[0-9]\+ tests, 0 skipped\|[0-9]\+ CGAL tests" \
README.md CLAUDE.md doc/ scripts/ \
| grep -v "doc/api/tests.md"
# Expected: zero results (other than tests.md itself).
```
6. **Commit** with the message
`release: vX.Y.Z — <one-line summary>`. Push to a feature branch.
### Open the release PR
7. **Open one PR** with all prep commits. Title:
`release: vX.Y.Z (<summary>)`. Description: copy the new
`CHANGELOG.md` section.
8. **Wait for CI green** — `test-fast` + `test-cgal` + `doc-build`.
9. **Merge** with a merge commit (the merge SHA becomes the release
target).
### After-merge — tag and announce
10. **Verify `main` head is the merge commit**:
```bash
git fetch origin && git log origin/main --oneline -1
```
11. **Create the Gitea release** via API or web UI. `target_commitish`
MUST be the merge SHA from step 10 (NOT the feature branch HEAD).
The web UI does this automatically; the API needs explicit `sha`.
12. **Sync the codeberg mirror**:
```bash
git fetch origin --tags
git push codeberg main --tags
```
13. **Update CLAUDE.md "Release state" section** so future
contributors see the new version at a glance. (This is the only
place where the current version appears outside `CITATION.cff` and
`CHANGELOG.md`.)
### Common failure modes and how to recover
| Symptom | Recovery |
|---|---|
| Tag points to an earlier commit than the one you wanted | Delete the release + tag via Gitea API, re-create with the correct `target_commitish`. |
| Release-prep commit lands on a feature branch but not on `main` after merge | Open a fast-follow `release/vX.Y.Z-finalise` PR. Cherry-pick the missing commit; merge; move the tag. |
| `doc/api/tests.md` counts are stale post-merge | One-line PR: re-run `ctest`, update tests.md, no version bump (it's a doc fix between releases). |
---
## 4. Hotfix policy
For a `vX.Y.0` minor release that needs an urgent fix before the next
planned minor:
1. Branch off the release tag (NOT off `main`):
```bash
git checkout -b hotfix/vX.Y.1 vX.Y.0
```
2. Apply the minimal fix.
3. Tag `vX.Y.1` from the hotfix branch.
4. Merge the hotfix back into `main` so the fix isn't lost.
Hotfixes are rare for a research-quality library; the typical path is
to wait for the next minor. Use hotfixes only for security issues or
build-broken-on-supported-platform regressions.
---
## 5. Deprecation policy (post-1.0)
When a public symbol is to be removed in version `M.0.0`:
1. In any earlier `M-1` minor release, mark it `[[deprecated]]` (C++17)
or via a Doxygen `\deprecated` tag.
2. Add a `Deprecated` section to that version's CHANGELOG entry with
the planned removal version.
3. Keep the symbol working for at least one full minor cycle before
removal.
4. Remove in the `M.0.0` release; document the removal in the
`Removed` section.
Pre-1.0, this policy does not apply — breaking changes are allowed
on any minor bump as long as they are listed in CHANGELOG.
---
## 6. Related documents
* `CHANGELOG.md` — per-version history.
* `CITATION.cff` — authoritative version + date for citation.
* `doc/api/tests.md` — single source of truth for test counts.
* `doc/contributing.md` — code style + PR mechanics.
* `doc/roadmap/phases.md` — what each future MINOR bump will deliver.