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

9.4 KiB

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. 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".
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.
  2. Update CITATION.cffversion: 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:
    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

  1. Open one PR with all prep commits. Title: release: vX.Y.Z (<summary>). Description: copy the new CHANGELOG.md section.
  2. Wait for CI greentest-fast + test-cgal + doc-build.
  3. Merge with a merge commit (the merge SHA becomes the release target).

After-merge — tag and announce

  1. Verify main head is the merge commit:
    git fetch origin && git log origin/main --oneline -1
    
  2. 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.
  3. Sync the codeberg mirror:
    git fetch origin --tags
    git push codeberg main --tags
    
  4. 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):
    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.


  • 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.