diff --git a/docs/semver-policy.md b/docs/semver-policy.md index e1af0365..92c66d6d 100644 --- a/docs/semver-policy.md +++ b/docs/semver-policy.md @@ -148,13 +148,52 @@ tag prefix, it: version, refusing anything that is not exactly the next version in some lane — a skip, a reversal, or a repeat are all refused, not just wrong bump sizes; -4. if the changelog entry has a `### Breaking` section, requires the bump to - be at least major (or, pre-1.0, at least minor); -5. fails loudly, naming the package, the previous and new version, and (when - relevant) the changelog heading that triggered the requirement. +4. when the release script supplies built assemblies (below), treats the + release as breaking if the public API measurably lost or changed a member + against the previous tag, whether or not the changelog says so; a + measured break with no `### Breaking` section is refused, naming the + count, the first diagnostics, and the heading that must declare them; +5. if the release is breaking (a `### Breaking` section, a measured break, or + both), requires the bump to be at least major (or, pre-1.0, at least + minor); a `### Breaking` section with nothing measured is valid, because + behavioural breaks are invisible to a signature comparison; +6. fails loudly, naming the package, the previous and new version, and (when + relevant) the changelog heading or the measured diagnostics that + triggered the requirement. + +Additions are never a rule: adding a public member is not breaking (see +above), and the check neither refuses nor demands a bump for one. + +### Measured input + +The Unity release scripts pass `--api-baseline-path ` and one `--api-built ` per assembly they built (plus +`--api-refs ` for directories that hold the built assemblies' +dependencies). For each built DLL that has a namesake under the baseline path +at the previous tag, the checker reads that namesake as a git blob at the tag +(no worktree, no network, unaffected by what the working tree tracks now) and +compares public API with `Microsoft.DotNet.ApiCompat.Tool`, pinned in +`dotnet-tools.json` and run through `dotnet tool run`; the first +`dotnet tool restore` needs NuGet, and later runs use the local cache. Each +`CPnnnn` diagnostic is one measured break. A built DLL with no baseline is new +and measures nothing. A `GameCult.*` assembly the previous tag tracked and the +build no longer produces is itself a measured break. A package's first release +(no previous tag) measures nothing. + +The tool failing to measure (not installed, an unreadable assembly, a crash: +a non-zero exit with no diagnostic line) refuses the release; it is never a +pass. ApiCompat cannot resolve `netstandard.dll` for the shipped +netstandard2.1 assemblies and prints a resolution warning; removed inherited +members and base types are still reported, and a test pins that. No +suppression file is kept: a suppression would be a second, silent declaration +of a break, so a break is declared only under `### Breaking`. + +The release scripts run the check once the assemblies under judgement exist, +after the build and before anything is copied into the package. Run it directly: `node scripts/check-changelog-semver.mjs --package ---changelog --version --tag-prefix `. Its own tests +--changelog --version --tag-prefix ` (CI's TS and Python +jobs and `verify-caching-unity-release.ps1` pass no measured input). Its own tests live in `scripts/check-changelog-semver.test.mjs` (`node --test scripts/check-changelog-semver.test.mjs`). @@ -193,6 +232,10 @@ existing assembly-version check does. A tag pushed by hand without running the release script bypasses both checks equally; closing that gap is a CI trigger on tag push for the Unity packages, not something this change adds. +What stays unmeasured: behaviour behind an unchanged signature, wire formats, +native exports of the QUIC bridge, and the Caching Unity package's source +(it has no assembly). Those rest on the `### Breaking` declaration. + Rust crates (consumed as pinned git revisions, not tagged releases in the usual sense) and the CultNet protocol surface itself (no automated wire compatibility test currently gates a release) are policy by convention only —