Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its
own changelog says "Breaking" and the version number lied. Operator ruling:
semver is policy, strictly, for every published package family.
docs/semver-policy.md states what counts as breaking per surface (managed
API, Unity inspector attributes, CultCache wire format/store compatibility,
CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag
naming, and required changelog shape. Records the 1.0.60 scar as history;
does not renumber it.
scripts/check-changelog-semver.mjs is the mechanical check: refuses a
changelog "### Breaking" section under too small a bump, and refuses a
version that skips, reverses, or repeats the previous published tag. Skips
re-litigating a version that is already tagged (rebuilding immutable
history is not a release action). Wired into publish-packages.yml for the
four npm/PyPI packages, and into build-unity-package.ps1 (cultlib),
packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new
scripts/verify-caching-unity-release.ps1 (caching.unity has no build script
of its own to hook into).
18 node:test cases cover the required table (breaking+major/minor/patch,
non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution.
Hand-mutated each rule (bump classification, pre-1.0 lane requirement,
breaking-heading detection, breaking early-return, already-published skip)
and confirmed each kill, then restored byte-exact. No mutation harness is
committed; CultLib has none today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:03:22 +02:00
|
|
|
# Semantic Versioning Policy
|
|
|
|
|
|
|
|
|
|
Operator ruling, 2026-09-17: **semantic versioning is policy** for every
|
|
|
|
|
package CultLib publishes, strictly. A release that removes or changes public
|
|
|
|
|
surface incompatibly is a major bump, full stop — not a judgment call left to
|
|
|
|
|
the person cutting the release.
|
|
|
|
|
|
|
|
|
|
This document says what "breaking" means for each kind of surface this repo
|
|
|
|
|
actually ships, and how a release is checked against it. See
|
|
|
|
|
`docs/nuget-packaging.md` for how the managed/Unity packaging mechanics work;
|
|
|
|
|
this document is the versioning contract those mechanics must satisfy.
|
|
|
|
|
|
|
|
|
|
## The scar
|
|
|
|
|
|
|
|
|
|
`org.gamecult.cultlib` 1.0.60 removed `CultInspectorAssetPath` — public API —
|
|
|
|
|
and shipped as a patch bump. Its own changelog entry is titled "Breaking"; the
|
|
|
|
|
version number said otherwise. A reviewer relying on the number alone would
|
|
|
|
|
have missed it. This is recorded as history, not fixed by renumbering: `1.0.60`
|
|
|
|
|
stays `1.0.60`. This policy, and the check below, exist so the next one is
|
|
|
|
|
caught before it ships instead of written up afterward.
|
|
|
|
|
|
|
|
|
|
## Packages in scope
|
|
|
|
|
|
2026-10-03 02:36:58 +02:00
|
|
|
| Package | Kind | Published by |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `org.gamecult.cultlib` | Unity UPM | `scripts/build-unity-package.ps1`, consumed by git tag |
|
|
|
|
|
| `org.gamecult.caching.unity` | Unity UPM | tagged directly (no build script; see "Coverage gaps") |
|
|
|
|
|
| `org.gamecult.cultmath` | Unity UPM | `packages/cultmath/scripts/build-unity-package.ps1`, consumed by git tag |
|
|
|
|
|
| `@gamecult/cultcache-ts` | npm | `.github/workflows/publish-packages.yml` |
|
|
|
|
|
| `cultcache-py` | PyPI | `.github/workflows/publish-packages.yml` |
|
|
|
|
|
| `cultnet-py` | PyPI | `.github/workflows/publish-packages.yml` |
|
|
|
|
|
| `cultmesh-py` | PyPI | `.github/workflows/publish-packages.yml` |
|
|
|
|
|
|
|
|
|
|
Each package's tag prefix, changelog path and, for the packages that ship
|
|
|
|
|
assemblies, the directory of tracked DLLs are declared in
|
|
|
|
|
`scripts/release-packages.mjs`, the one copy. The check reads them by package
|
|
|
|
|
name; no release script or workflow passes one.
|
Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its
own changelog says "Breaking" and the version number lied. Operator ruling:
semver is policy, strictly, for every published package family.
docs/semver-policy.md states what counts as breaking per surface (managed
API, Unity inspector attributes, CultCache wire format/store compatibility,
CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag
naming, and required changelog shape. Records the 1.0.60 scar as history;
does not renumber it.
scripts/check-changelog-semver.mjs is the mechanical check: refuses a
changelog "### Breaking" section under too small a bump, and refuses a
version that skips, reverses, or repeats the previous published tag. Skips
re-litigating a version that is already tagged (rebuilding immutable
history is not a release action). Wired into publish-packages.yml for the
four npm/PyPI packages, and into build-unity-package.ps1 (cultlib),
packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new
scripts/verify-caching-unity-release.ps1 (caching.unity has no build script
of its own to hook into).
18 node:test cases cover the required table (breaking+major/minor/patch,
non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution.
Hand-mutated each rule (bump classification, pre-1.0 lane requirement,
breaking-heading detection, breaking early-return, already-published skip)
and confirmed each kill, then restored byte-exact. No mutation harness is
committed; CultLib has none today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:03:22 +02:00
|
|
|
|
|
|
|
|
Rust crates are consumed as pinned git revisions (see `publish-packages.yml`'s
|
|
|
|
|
own header comment) and are out of scope for tagged-release semver policing;
|
|
|
|
|
a `Cargo.toml` version still exists and should follow this policy by
|
|
|
|
|
convention, but nothing in this repo checks it mechanically today.
|
|
|
|
|
|
2026-10-03 05:55:13 +02:00
|
|
|
Releases are tags named `<tagPrefix>-v<version>`, with the prefix declared in
|
|
|
|
|
`scripts/release-packages.mjs`. The check refuses a declaration in which two
|
|
|
|
|
packages share a prefix or an entry lacks a string prefix or changelog.
|
Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its
own changelog says "Breaking" and the version number lied. Operator ruling:
semver is policy, strictly, for every published package family.
docs/semver-policy.md states what counts as breaking per surface (managed
API, Unity inspector attributes, CultCache wire format/store compatibility,
CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag
naming, and required changelog shape. Records the 1.0.60 scar as history;
does not renumber it.
scripts/check-changelog-semver.mjs is the mechanical check: refuses a
changelog "### Breaking" section under too small a bump, and refuses a
version that skips, reverses, or repeats the previous published tag. Skips
re-litigating a version that is already tagged (rebuilding immutable
history is not a release action). Wired into publish-packages.yml for the
four npm/PyPI packages, and into build-unity-package.ps1 (cultlib),
packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new
scripts/verify-caching-unity-release.ps1 (caching.unity has no build script
of its own to hook into).
18 node:test cases cover the required table (breaking+major/minor/patch,
non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution.
Hand-mutated each rule (bump classification, pre-1.0 lane requirement,
breaking-heading detection, breaking early-return, already-published skip)
and confirmed each kill, then restored byte-exact. No mutation harness is
committed; CultLib has none today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:03:22 +02:00
|
|
|
|
|
|
|
|
## What counts as breaking
|
|
|
|
|
|
|
|
|
|
Semver's ordinary rule — additive is minor or patch, anything a consumer's
|
|
|
|
|
existing code or data can no longer rely on is major — applies per surface as
|
|
|
|
|
follows.
|
|
|
|
|
|
|
|
|
|
### Managed public API (`org.gamecult.cultlib`, NuGet packages, `cultmath`)
|
|
|
|
|
|
|
|
|
|
Breaking: removing or renaming a public type/member, changing a public
|
|
|
|
|
member's signature or observable behavior in an incompatible way, tightening
|
|
|
|
|
a public contract (new required parameter, narrowed accepted input),
|
|
|
|
|
or moving a type across assemblies/namespaces without a compatible forwarder.
|
|
|
|
|
|
|
|
|
|
Not breaking: adding a new public member, type, or overload; widening an
|
|
|
|
|
accepted type; deprecating (marking obsolete) without removing.
|
|
|
|
|
|
|
|
|
|
### Unity package inspector attributes (`org.gamecult.cultlib`,
|
|
|
|
|
`org.gamecult.caching.unity`)
|
|
|
|
|
|
|
|
|
|
These ship as ordinary public API by the rule above, but get their own
|
|
|
|
|
callout because they are easy to mistake for "just editor tooling": an
|
|
|
|
|
attribute class, its constructor shape, and the serialized value shape it
|
|
|
|
|
reads/writes are all public surface a consumer's `[CultInspectorX(...)]`
|
|
|
|
|
usage and serialized scene/prefab data depend on. Removing or renaming an
|
|
|
|
|
attribute, or changing what it expects a annotated member's persisted value to
|
|
|
|
|
look like (as `CultInspectorAssetPath` to `CultInspectorAssetGuid` did in
|
|
|
|
|
1.0.60) is breaking.
|
|
|
|
|
|
|
|
|
|
### Persisted wire format / store compatibility (CultCache)
|
|
|
|
|
|
|
|
|
|
Governed by `src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md`
|
|
|
|
|
and `cultcache-persistence-format.md` — read those for the authoritative
|
|
|
|
|
rules. In semver terms: anything those documents classify as **hard
|
|
|
|
|
rejection** (type change, reference/value change, one-vs-many change, target
|
|
|
|
|
schema change, name/index lookup semantics change, a persisted schema with no
|
|
|
|
|
compatible local candidate) is breaking for any package that reads or writes
|
|
|
|
|
the format. Anything classified as **soft-migratable drift** (compatible
|
|
|
|
|
schema id drift, an ignorable missing slot, a defaultable missing slot) is not
|
|
|
|
|
breaking on its own, but changing what counts as soft-migratable — loosening
|
|
|
|
|
or tightening the drift rules themselves — is breaking for whichever package
|
|
|
|
|
owns that classification logic.
|
|
|
|
|
|
|
|
|
|
### CultNet protocol surface
|
|
|
|
|
|
|
|
|
|
Breaking: a wire-incompatible change to a CultNet message, frame, or
|
|
|
|
|
handshake shape; removing or repurposing a message type or field that another
|
|
|
|
|
runtime's implementation depends on; changing routing, authority, or trust
|
|
|
|
|
semantics such that a peer built against the previous protocol version
|
|
|
|
|
misbehaves rather than cleanly failing. Additive, backward-compatible framing
|
|
|
|
|
changes (a new optional field a receiver may ignore) are not breaking.
|
|
|
|
|
|
|
|
|
|
### Native bridge exported functions (`GameCult.Mesh.Quic.Native`)
|
|
|
|
|
|
|
|
|
|
Breaking: removing or changing the signature/ABI of a `CULTMESH_API`
|
|
|
|
|
(`extern "C"`) exported function, or changing an exported function's
|
|
|
|
|
behavior such that the existing managed P/Invoke caller (`GameCult.Mesh.Quic.Native`'s
|
|
|
|
|
C# side) or Rust caller (`cultmath-core`'s equivalent `#[no_mangle] extern "C"`
|
|
|
|
|
surface) no longer gets what it asked for. Swapping the underlying TLS/QUIC
|
|
|
|
|
implementation (as 1.0.60 did, Schannel to OpenSSL MsQuic) is not breaking
|
|
|
|
|
*by itself* as long as the exported v1 functions keep their signatures and
|
|
|
|
|
behavior — 1.0.60's actual breaking change was the inspector attribute
|
|
|
|
|
removal, not the bridge swap, and its changelog correctly filed the bridge
|
|
|
|
|
swap under "Changed" rather than "Breaking".
|
|
|
|
|
|
|
|
|
|
## Pre-1.0 packages
|
|
|
|
|
|
2026-10-22 01:08:31 +02:00
|
|
|
A package at `0.y.z` (currently `org.gamecult.cultmath`, at `0.5.x`) uses the
|
Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its
own changelog says "Breaking" and the version number lied. Operator ruling:
semver is policy, strictly, for every published package family.
docs/semver-policy.md states what counts as breaking per surface (managed
API, Unity inspector attributes, CultCache wire format/store compatibility,
CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag
naming, and required changelog shape. Records the 1.0.60 scar as history;
does not renumber it.
scripts/check-changelog-semver.mjs is the mechanical check: refuses a
changelog "### Breaking" section under too small a bump, and refuses a
version that skips, reverses, or repeats the previous published tag. Skips
re-litigating a version that is already tagged (rebuilding immutable
history is not a release action). Wired into publish-packages.yml for the
four npm/PyPI packages, and into build-unity-package.ps1 (cultlib),
packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new
scripts/verify-caching-unity-release.ps1 (caching.unity has no build script
of its own to hook into).
18 node:test cases cover the required table (breaking+major/minor/patch,
non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution.
Hand-mutated each rule (bump classification, pre-1.0 lane requirement,
breaking-heading detection, breaking early-return, already-published skip)
and confirmed each kill, then restored byte-exact. No mutation harness is
committed; CultLib has none today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:03:22 +02:00
|
|
|
standard pre-1.0 convention: `MAJOR` stays `0` during initial development, so
|
|
|
|
|
`MINOR` carries what `MAJOR` means once the package reaches `1.0.0`. Concretely:
|
|
|
|
|
|
|
|
|
|
- a breaking change bumps `MINOR` (and resets `PATCH` to `0`);
|
|
|
|
|
- a non-breaking change bumps `PATCH`.
|
|
|
|
|
|
|
|
|
|
Leaving `0.y.z` for `1.0.0` is a deliberate, separate decision (the API is now
|
|
|
|
|
considered stable), not something the checker infers on its own.
|
|
|
|
|
|
|
|
|
|
## What a release changelog entry must contain
|
|
|
|
|
|
|
|
|
|
Every published package keeps a `CHANGELOG.md` beside its manifest
|
|
|
|
|
(`unity/org.gamecult.cultlib/CHANGELOG.md` and
|
|
|
|
|
`src/GameCult.Unity/Assets/Caching/CHANGELOG.md` are the existing precedent).
|
2026-10-03 15:02:55 +02:00
|
|
|
Each release adds a `## [<version>]` section (the check also reads
|
|
|
|
|
`## <version>`, and either form followed by Keep a Changelog's
|
|
|
|
|
` - YYYY-MM-DD` date, such as `## [0.4.0] - 2026-10-03`) containing:
|
Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its
own changelog says "Breaking" and the version number lied. Operator ruling:
semver is policy, strictly, for every published package family.
docs/semver-policy.md states what counts as breaking per surface (managed
API, Unity inspector attributes, CultCache wire format/store compatibility,
CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag
naming, and required changelog shape. Records the 1.0.60 scar as history;
does not renumber it.
scripts/check-changelog-semver.mjs is the mechanical check: refuses a
changelog "### Breaking" section under too small a bump, and refuses a
version that skips, reverses, or repeats the previous published tag. Skips
re-litigating a version that is already tagged (rebuilding immutable
history is not a release action). Wired into publish-packages.yml for the
four npm/PyPI packages, and into build-unity-package.ps1 (cultlib),
packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new
scripts/verify-caching-unity-release.ps1 (caching.unity has no build script
of its own to hook into).
18 node:test cases cover the required table (breaking+major/minor/patch,
non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution.
Hand-mutated each rule (bump classification, pre-1.0 lane requirement,
breaking-heading detection, breaking early-return, already-published skip)
and confirmed each kill, then restored byte-exact. No mutation harness is
committed; CultLib has none today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:03:22 +02:00
|
|
|
|
|
|
|
|
- a category subsection per change (`### Added`, `### Changed`, `### Fixed`,
|
|
|
|
|
etc., following Keep a Changelog style);
|
|
|
|
|
- **`### Breaking`** — this exact heading, this exact spelling — for any
|
|
|
|
|
change that meets the "what counts as breaking" rules above. This heading
|
|
|
|
|
is not decorative: the CI check below greps for it by name.
|
|
|
|
|
- enough detail that a consumer can tell what to change, in the style of the
|
|
|
|
|
1.0.60 entry (name the removed/changed symbol and its replacement).
|
|
|
|
|
|
|
|
|
|
A version with no changelog entry at all fails the check below, the same as
|
|
|
|
|
a mislabelled one — an undocumented release is not a smaller sin than a
|
|
|
|
|
mislabelled one.
|
|
|
|
|
|
2026-10-03 15:02:55 +02:00
|
|
|
### First release of a package
|
|
|
|
|
|
|
|
|
|
A package with no `CHANGELOG.md` yet cannot release: the check refuses with
|
|
|
|
|
"changelog not found". Its first release writes the file at the declared path,
|
|
|
|
|
`packages/<package>/CHANGELOG.md`, with one section for the version being
|
|
|
|
|
released. That version must still be exactly the next one after the package's
|
|
|
|
|
nearest ancestor tag. As of 2026-10-03 the valid next versions are:
|
|
|
|
|
|
|
|
|
|
- `cultcache-ts`, after `cultcache-ts-v0.14.0`: `0.14.1`, `0.15.0` or `1.0.0`;
|
|
|
|
|
- `cultcache-py`, after `cultcache-py-v0.3.0`: `0.3.1`, `0.4.0` or `1.0.0`;
|
|
|
|
|
- `cultnet-py` and `cultmesh-py`, which have no tags: `0.1.0`, their manifest
|
|
|
|
|
version, as a first release. Run it from a clone with the tags fetched, or
|
|
|
|
|
the check cannot tell a first release from a missing record.
|
|
|
|
|
|
|
|
|
|
On `0.y.z` a `### Breaking` section needs at least a minor bump.
|
|
|
|
|
|
Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its
own changelog says "Breaking" and the version number lied. Operator ruling:
semver is policy, strictly, for every published package family.
docs/semver-policy.md states what counts as breaking per surface (managed
API, Unity inspector attributes, CultCache wire format/store compatibility,
CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag
naming, and required changelog shape. Records the 1.0.60 scar as history;
does not renumber it.
scripts/check-changelog-semver.mjs is the mechanical check: refuses a
changelog "### Breaking" section under too small a bump, and refuses a
version that skips, reverses, or repeats the previous published tag. Skips
re-litigating a version that is already tagged (rebuilding immutable
history is not a release action). Wired into publish-packages.yml for the
four npm/PyPI packages, and into build-unity-package.ps1 (cultlib),
packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new
scripts/verify-caching-unity-release.ps1 (caching.unity has no build script
of its own to hook into).
18 node:test cases cover the required table (breaking+major/minor/patch,
non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution.
Hand-mutated each rule (bump classification, pre-1.0 lane requirement,
breaking-heading detection, breaking early-return, already-published skip)
and confirmed each kill, then restored byte-exact. No mutation harness is
committed; CultLib has none today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:03:22 +02:00
|
|
|
## Enforcement
|
|
|
|
|
|
|
|
|
|
`scripts/check-changelog-semver.mjs` is the mechanical check. Given a
|
2026-10-03 02:36:58 +02:00
|
|
|
package name and the version being released, it reads the package's
|
|
|
|
|
declaration from `scripts/release-packages.mjs` in `--cwd` (an unknown package
|
|
|
|
|
or a missing declaration refuses), and:
|
|
|
|
|
|
|
|
|
|
1. derives the release record from `git tag -l` in `--cwd`. A same-prefix tag
|
|
|
|
|
is one that starts `<prefix>-v`; one that is not
|
|
|
|
|
`<prefix>-vMAJOR.MINOR.PATCH` refuses, naming the tag, and is never
|
|
|
|
|
skipped. The previous release is the newest same-prefix tag strictly older
|
2026-10-03 05:55:13 +02:00
|
|
|
than the version being released, by version order, that is also an ancestor
|
|
|
|
|
of the commit being checked: a backport tagged later on another branch is
|
|
|
|
|
not part of that history, so it never moves a rebuild's predecessor. A tag
|
|
|
|
|
equal to the version does not exempt
|
2026-10-03 02:36:58 +02:00
|
|
|
it and does not count as a previous release: there is no "already
|
|
|
|
|
published" skip, so a rebuild or a CI re-run at a tagged version is
|
|
|
|
|
measured against that version's predecessor, exactly as at release, and
|
|
|
|
|
gets the same verdict. With no older tag, a newer one refuses (the version
|
|
|
|
|
is older than every release), and a repository that holds no tag other than
|
|
|
|
|
the version's own refuses, because a first release cannot be told from a
|
2026-10-03 05:55:13 +02:00
|
|
|
missing record. Older tags of which none is an ancestor leave the
|
|
|
|
|
predecessor unknown and refuse. Otherwise it is a first release, which no
|
2026-10-03 08:57:06 +02:00
|
|
|
caller declares. A package that declares an assemblies directory is a first
|
|
|
|
|
release only when no tag in the repository, under any prefix, tracks a
|
|
|
|
|
managed assembly under that directory: such a tag means the package has
|
2026-10-03 08:01:30 +02:00
|
|
|
shipped, so the declared prefix is wrong and the check refuses, naming the
|
2026-10-03 08:57:06 +02:00
|
|
|
tag and the assembly. A tag snapshots the whole tree of this monorepo, so a
|
|
|
|
|
tag tracking a package's directory proves nothing; only a managed assembly in
|
|
|
|
|
the declared directory does. This is read from git, never from the changelog's
|
|
|
|
|
prose. The version's own tag does not count, so a first release can be
|
|
|
|
|
rebuilt. Only the declared directory counts: a package whose directory moved
|
|
|
|
|
after its releases has none of its old path's tags counted, so its release
|
|
|
|
|
under a misspelled prefix reads as new, and the move itself is caught only
|
|
|
|
|
under the right prefix, where the declared directory holds no managed assembly
|
|
|
|
|
at the previous tag. A package that declares no assemblies directory (the
|
|
|
|
|
Python and TypeScript packages and the Caching Unity package) has no such
|
|
|
|
|
proof: with no tag of its own prefix it is a first release. The suite test
|
2026-10-03 11:43:43 +02:00
|
|
|
that pins every declared prefix guards it: each prefix is declared once, each
|
|
|
|
|
workflow tag trigger must be a declared prefix, and each such package's prefix
|
|
|
|
|
must be a workflow trigger or, for the Caching Unity package the workflow does
|
|
|
|
|
not build, the prefix of tags that hold its own manifest at the tag's own
|
2026-10-03 13:43:47 +02:00
|
|
|
version, so a typo or a copied prefix fails the suite. Nothing releases
|
|
|
|
|
without the suite passing; see below for how each release path runs it.
|
2026-10-03 11:43:43 +02:00
|
|
|
Releases run from a full clone with every tag (`fetch-depth: 0` in CI), the
|
|
|
|
|
recorded `partial-tags-first-release`.
|
2026-10-03 08:01:30 +02:00
|
|
|
Tags that cannot be read (a `--cwd` that is not a git repository, or a tag
|
|
|
|
|
whose tree cannot be listed) refuse;
|
Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its
own changelog says "Breaking" and the version number lied. Operator ruling:
semver is policy, strictly, for every published package family.
docs/semver-policy.md states what counts as breaking per surface (managed
API, Unity inspector attributes, CultCache wire format/store compatibility,
CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag
naming, and required changelog shape. Records the 1.0.60 scar as history;
does not renumber it.
scripts/check-changelog-semver.mjs is the mechanical check: refuses a
changelog "### Breaking" section under too small a bump, and refuses a
version that skips, reverses, or repeats the previous published tag. Skips
re-litigating a version that is already tagged (rebuilding immutable
history is not a release action). Wired into publish-packages.yml for the
four npm/PyPI packages, and into build-unity-package.ps1 (cultlib),
packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new
scripts/verify-caching-unity-release.ps1 (caching.unity has no build script
of its own to hook into).
18 node:test cases cover the required table (breaking+major/minor/patch,
non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution.
Hand-mutated each rule (bump classification, pre-1.0 lane requirement,
breaking-heading detection, breaking early-return, already-published skip)
and confirmed each kill, then restored byte-exact. No mutation harness is
committed; CultLib has none today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:03:22 +02:00
|
|
|
2. requires a `## [<version>]` changelog entry to exist at all;
|
|
|
|
|
3. classifies the version bump (major/minor/patch) against the previous
|
|
|
|
|
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;
|
2026-10-02 17:08:36 +02:00
|
|
|
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
|
|
|
|
|
|
2026-10-03 02:36:58 +02:00
|
|
|
A package whose declaration names an `assemblies` directory is measured, and
|
|
|
|
|
the release must hand over one `--api-built <dll>` per assembly it built (plus
|
2026-10-02 17:08:36 +02:00
|
|
|
`--api-refs <dir>` for directories that hold the built assemblies'
|
2026-10-03 02:36:58 +02:00
|
|
|
dependencies). A package with no such directory takes no `--api-built`, and one
|
|
|
|
|
that has it cannot omit it. The measured set is every managed assembly tracked
|
|
|
|
|
under that directory, at any depth, at the previous tag, read as git blobs (no
|
|
|
|
|
worktree, no network, unaffected by what the working tree tracks now) and keyed
|
|
|
|
|
by file name. A DLL is managed when its PE header carries a CLI header; a
|
|
|
|
|
native DLL has none and is not measured, and a tracked `.dll` that is not a PE
|
|
|
|
|
image refuses. Two managed assemblies with one name refuse. The checker
|
|
|
|
|
compares each built assembly with its namesake using
|
|
|
|
|
`Microsoft.DotNet.ApiCompat.Tool`; each `CPnnnn` diagnostic is one measured
|
|
|
|
|
break. Every managed assembly the previous tag tracked that the build no
|
|
|
|
|
longer hands over is itself a measured break, whatever its name or place, so a
|
|
|
|
|
release script passes every assembly it ships. A built assembly with no
|
|
|
|
|
namesake at the tag is an addition and is not examined.
|
2026-10-02 19:52:17 +02:00
|
|
|
|
|
|
|
|
Measuring nothing is a refusal, never a pass. The check refuses, naming what
|
2026-10-03 02:36:58 +02:00
|
|
|
is missing, when the previous tag tracks no managed assembly under the
|
|
|
|
|
declared directory (a wrong or moved directory, or a tag that predates it);
|
|
|
|
|
when git cannot read the tracked assemblies; and when the tool fails to
|
|
|
|
|
measure (not installed, an unreadable assembly, a crash: a non-zero exit with
|
|
|
|
|
no diagnostic line). A first release measures nothing.
|
2026-10-02 19:52:17 +02:00
|
|
|
|
|
|
|
|
The tool and the `NETStandard.Library.Ref` 2.1.0 reference assemblies are
|
|
|
|
|
pinned in `scripts/api-gate/` (`.config/dotnet-tools.json` and
|
|
|
|
|
`ApiRefs.csproj`) and restored there by the check itself, so a release does
|
|
|
|
|
not depend on any other tool in the repository's manifest. NuGet is needed
|
|
|
|
|
until both are cached under `scripts/api-gate`. Both sides of every
|
|
|
|
|
comparison resolve netstandard types through those reference assemblies, and the
|
|
|
|
|
new side also through the built DLL's own directory and every `--api-refs`
|
|
|
|
|
directory. No suppression file is kept: a suppression would be a second, silent
|
|
|
|
|
declaration of a break, so a break is declared only under `### Breaking`.
|
|
|
|
|
|
|
|
|
|
A passing measured run prints that unmeasured changes still need declaring.
|
|
|
|
|
|
|
|
|
|
#### Unmeasured changes
|
|
|
|
|
|
|
|
|
|
ApiCompat does not report these changes to a public member, and the check
|
|
|
|
|
therefore passes them. Each is a breaking change under the rules above and is
|
|
|
|
|
declared under `### Breaking`, as behavioural breaks are:
|
|
|
|
|
|
|
|
|
|
- a member gaining or losing `static`;
|
|
|
|
|
- a parameter gaining or losing `ref`, `out` or `in`;
|
|
|
|
|
- a `const` field's value changing;
|
|
|
|
|
- an enum member's underlying value changing;
|
|
|
|
|
- a field gaining or losing `readonly`;
|
|
|
|
|
- a default parameter value changing.
|
2026-10-02 17:08:36 +02:00
|
|
|
|
|
|
|
|
The release scripts run the check once the assemblies under judgement exist,
|
|
|
|
|
after the build and before anything is copied into the package.
|
Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its
own changelog says "Breaking" and the version number lied. Operator ruling:
semver is policy, strictly, for every published package family.
docs/semver-policy.md states what counts as breaking per surface (managed
API, Unity inspector attributes, CultCache wire format/store compatibility,
CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag
naming, and required changelog shape. Records the 1.0.60 scar as history;
does not renumber it.
scripts/check-changelog-semver.mjs is the mechanical check: refuses a
changelog "### Breaking" section under too small a bump, and refuses a
version that skips, reverses, or repeats the previous published tag. Skips
re-litigating a version that is already tagged (rebuilding immutable
history is not a release action). Wired into publish-packages.yml for the
four npm/PyPI packages, and into build-unity-package.ps1 (cultlib),
packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new
scripts/verify-caching-unity-release.ps1 (caching.unity has no build script
of its own to hook into).
18 node:test cases cover the required table (breaking+major/minor/patch,
non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution.
Hand-mutated each rule (bump classification, pre-1.0 lane requirement,
breaking-heading detection, breaking early-return, already-published skip)
and confirmed each kill, then restored byte-exact. No mutation harness is
committed; CultLib has none today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:03:22 +02:00
|
|
|
|
|
|
|
|
Run it directly: `node scripts/check-changelog-semver.mjs --package <name>
|
2026-10-03 02:36:58 +02:00
|
|
|
--version <x.y.z> [--cwd <dir>] [--api-built <dll>... [--api-refs <dir>...]]`.
|
|
|
|
|
Any other option is a usage error (exit 2). Its own tests live in
|
|
|
|
|
`scripts/check-changelog-semver.test.mjs` (`node --test
|
Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its
own changelog says "Breaking" and the version number lied. Operator ruling:
semver is policy, strictly, for every published package family.
docs/semver-policy.md states what counts as breaking per surface (managed
API, Unity inspector attributes, CultCache wire format/store compatibility,
CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag
naming, and required changelog shape. Records the 1.0.60 scar as history;
does not renumber it.
scripts/check-changelog-semver.mjs is the mechanical check: refuses a
changelog "### Breaking" section under too small a bump, and refuses a
version that skips, reverses, or repeats the previous published tag. Skips
re-litigating a version that is already tagged (rebuilding immutable
history is not a release action). Wired into publish-packages.yml for the
four npm/PyPI packages, and into build-unity-package.ps1 (cultlib),
packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new
scripts/verify-caching-unity-release.ps1 (caching.unity has no build script
of its own to hook into).
18 node:test cases cover the required table (breaking+major/minor/patch,
non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution.
Hand-mutated each rule (bump classification, pre-1.0 lane requirement,
breaking-heading detection, breaking early-return, already-published skip)
and confirmed each kill, then restored byte-exact. No mutation harness is
committed; CultLib has none today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:03:22 +02:00
|
|
|
scripts/check-changelog-semver.test.mjs`).
|
|
|
|
|
|
|
|
|
|
It is wired into:
|
|
|
|
|
|
Release guard: the checks run in a clean env, and every per-tag check runs in the guard
release-after-suite.mjs now takes the run's git ref and, from one process, checks
the tag against the declared prefix and manifest version, runs the release-checks
suite, the semver policy check and the package's own tests, then releases. The
checks run without NODE_OPTIONS, NODE_TEST_*, or NODE_AUTH_TOKEN; only the release
action gets the token. The workflow's separate tag, semver and test steps are gone,
and so is the pin's continue-on-error/shell/defaults ban, which no release check
needs any more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 14:05:28 +02:00
|
|
|
- `.github/workflows/publish-packages.yml`, through
|
|
|
|
|
`scripts/release-after-suite.mjs`, for `cultcache-ts`, `cultcache-py`,
|
2026-10-03 02:36:58 +02:00
|
|
|
`cultnet-py`, and `cultmesh-py`, with `fetch-depth: 0` so the checkout holds
|
|
|
|
|
the tags;
|
Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its
own changelog says "Breaking" and the version number lied. Operator ruling:
semver is policy, strictly, for every published package family.
docs/semver-policy.md states what counts as breaking per surface (managed
API, Unity inspector attributes, CultCache wire format/store compatibility,
CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag
naming, and required changelog shape. Records the 1.0.60 scar as history;
does not renumber it.
scripts/check-changelog-semver.mjs is the mechanical check: refuses a
changelog "### Breaking" section under too small a bump, and refuses a
version that skips, reverses, or repeats the previous published tag. Skips
re-litigating a version that is already tagged (rebuilding immutable
history is not a release action). Wired into publish-packages.yml for the
four npm/PyPI packages, and into build-unity-package.ps1 (cultlib),
packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new
scripts/verify-caching-unity-release.ps1 (caching.unity has no build script
of its own to hook into).
18 node:test cases cover the required table (breaking+major/minor/patch,
non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution.
Hand-mutated each rule (bump classification, pre-1.0 lane requirement,
breaking-heading detection, breaking early-return, already-published skip)
and confirmed each kill, then restored byte-exact. No mutation harness is
committed; CultLib has none today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:03:22 +02:00
|
|
|
- `scripts/build-unity-package.ps1`, for `org.gamecult.cultlib`;
|
|
|
|
|
- `packages/cultmath/scripts/build-unity-package.ps1`, for
|
|
|
|
|
`org.gamecult.cultmath`;
|
|
|
|
|
- `scripts/verify-caching-unity-release.ps1`, for `org.gamecult.caching.unity`.
|
|
|
|
|
|
2026-10-03 13:43:47 +02:00
|
|
|
Each of those runs the check's own tests before it releases. The Unity release
|
|
|
|
|
scripts run them before they call the check, and the tests pin that order. In
|
Release guard: the checks run in a clean env, and every per-tag check runs in the guard
release-after-suite.mjs now takes the run's git ref and, from one process, checks
the tag against the declared prefix and manifest version, runs the release-checks
suite, the semver policy check and the package's own tests, then releases. The
checks run without NODE_OPTIONS, NODE_TEST_*, or NODE_AUTH_TOKEN; only the release
action gets the token. The workflow's separate tag, semver and test steps are gone,
and so is the pin's continue-on-error/shell/defaults ban, which no release check
needs any more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 14:05:28 +02:00
|
|
|
`publish-packages.yml` the release step runs `scripts/release-after-suite.mjs`
|
|
|
|
|
with the run's git ref. From one process it checks that a tag ref is the
|
|
|
|
|
package's declared prefix and manifest version, runs these tests, runs this
|
|
|
|
|
check for the tagged version, runs the package's own tests (`npm test`, or the
|
|
|
|
|
Python package's unit tests), and only then the release action: `npm publish`
|
|
|
|
|
for `cultcache-ts`, which it refuses on any ref but a release tag, and for the
|
|
|
|
|
Python packages the sdist and wheel build, whose output is all the PyPI upload
|
2026-10-03 15:02:55 +02:00
|
|
|
step uploads. The checks run with the known test-runner and token variables
|
|
|
|
|
removed (`NODE_OPTIONS`, `NODE_TEST_CONTEXT`, any other `NODE_TEST_*` variable,
|
|
|
|
|
and `NODE_AUTH_TOKEN`), so none of those can make the tests run nothing and
|
|
|
|
|
pass, and only the release action receives the token. That list names known
|
|
|
|
|
variables; it does not prove that no other inherited variable can affect a
|
2026-10-03 15:16:57 +02:00
|
|
|
check. The npm Publish step and the Python sdist and wheel build step start the
|
|
|
|
|
guard under `env -u NODE_OPTIONS`, so a `NODE_OPTIONS` preload set in the
|
|
|
|
|
workflow's or job's `env:` cannot run code inside the guard before its first
|
|
|
|
|
line.
|
2026-10-03 14:37:38 +02:00
|
|
|
The gate is that call, not a step condition, so no `if:` or `continue-on-error`
|
|
|
|
|
can release what a check refused. The tests compare each job's release steps
|
2026-10-03 15:02:55 +02:00
|
|
|
with their expected text exactly, less the steps' `if:` lines. Removing the call
|
|
|
|
|
is still possible, but only as a visible edit to those steps and to the tests.
|
|
|
|
|
|
|
|
|
|
The guard and that pin stop accidental workflow edits. They do not stop a
|
|
|
|
|
deliberate one: an extra step that publishes, a workflow-level `defaults:` or
|
|
|
|
|
`env:`, or a fake `node` on `$GITHUB_PATH` all pass them. Deliberate edits are
|
|
|
|
|
review's job, as ruled in `suite-gate-structural-not-parsed`, and the pin
|
|
|
|
|
does not try to parse the workflow for them. For the same reason it has no ban
|
|
|
|
|
on `shell` or `defaults`: such a ban defends only against a deliberate edit,
|
|
|
|
|
and it also refused harmless settings such as `defaults.run.working-directory`.
|
|
|
|
|
A step-level `shell:` on a release step still fails the exact comparison.
|
|
|
|
|
A recommendation for the operator, not something this repository can set: the
|
|
|
|
|
Python job already runs in the `pypi` GitHub environment, as PyPI trusted
|
|
|
|
|
publishing expects. Putting the npm job in an environment with required
|
|
|
|
|
reviewers would make every npm release wait for a person's approval. That is a
|
|
|
|
|
GitHub settings change.
|
2026-10-03 13:02:58 +02:00
|
|
|
|
2026-10-03 12:25:18 +02:00
|
|
|
`org.gamecult.cultlib` compiles CultMath from source and ships beside
|
|
|
|
|
`org.gamecult.cultmath`, so its build refuses unless it declares exactly the
|
|
|
|
|
CultMath package's version and every `CultMath.dll` it built equals the one
|
|
|
|
|
that package tracks, byte for byte. Any CultMath source change therefore means:
|
|
|
|
|
release `org.gamecult.cultmath`, declare its new version in
|
2026-10-03 13:00:54 +02:00
|
|
|
`org.gamecult.cultlib`, then release `org.gamecult.cultlib`. The bytes also
|
2026-10-03 13:27:18 +02:00
|
|
|
depend on the checkout and the toolchain. `.gitattributes` pins CultMath
|
|
|
|
|
source to CRLF on every checkout, because the tracked DLL was built from CRLF
|
|
|
|
|
source and line endings reach the DLL through the pdb's document checksums; a
|
|
|
|
|
clone checked out before that rule may still hold LF files (`git ls-files
|
|
|
|
|
--eol packages/cultmath/src` shows `w/lf`) until they are checked out again.
|
|
|
|
|
The build needs a git checkout, and refuses up front without `.git`: the repo
|
|
|
|
|
root maps to `/_/` through git, so an archive or source zip would write its
|
|
|
|
|
own path into the DLL. No `global.json` pins the .NET SDK, so the same refusal
|
|
|
|
|
at unchanged source and CRLF checkout means this SDK or host differs from the
|
|
|
|
|
one that built the tracked DLL.
|
2026-10-03 12:25:18 +02:00
|
|
|
|
Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its
own changelog says "Breaking" and the version number lied. Operator ruling:
semver is policy, strictly, for every published package family.
docs/semver-policy.md states what counts as breaking per surface (managed
API, Unity inspector attributes, CultCache wire format/store compatibility,
CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag
naming, and required changelog shape. Records the 1.0.60 scar as history;
does not renumber it.
scripts/check-changelog-semver.mjs is the mechanical check: refuses a
changelog "### Breaking" section under too small a bump, and refuses a
version that skips, reverses, or repeats the previous published tag. Skips
re-litigating a version that is already tagged (rebuilding immutable
history is not a release action). Wired into publish-packages.yml for the
four npm/PyPI packages, and into build-unity-package.ps1 (cultlib),
packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new
scripts/verify-caching-unity-release.ps1 (caching.unity has no build script
of its own to hook into).
18 node:test cases cover the required table (breaking+major/minor/patch,
non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution.
Hand-mutated each rule (bump classification, pre-1.0 lane requirement,
breaking-heading detection, breaking early-return, already-published skip)
and confirmed each kill, then restored byte-exact. No mutation harness is
committed; CultLib has none today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:03:22 +02:00
|
|
|
### Coverage gaps
|
|
|
|
|
|
|
|
|
|
`publish-packages.yml` only triggers on `cultcache-ts-v*`, `cultcache-py-v*`,
|
|
|
|
|
`cultnet-py-v*`, and `cultmesh-py-v*` tag pushes — it does not build or gate
|
|
|
|
|
the Unity UPM packages at all; those are consumed directly by git tag
|
|
|
|
|
(`unity/org.gamecult.cultlib`, `packages/cultmath/unity/org.gamecult.cultmath`,
|
|
|
|
|
`src/GameCult.Unity/Assets/Caching`), so a workflow trigger on their tags
|
|
|
|
|
cannot be added to that file without also giving it a reason to check out and
|
|
|
|
|
build Unity content it currently has no job for. The check is instead wired
|
|
|
|
|
into the scripts that already run at Unity-package release time:
|
|
|
|
|
`build-unity-package.ps1` and `packages/cultmath/scripts/build-unity-package.ps1`
|
|
|
|
|
already verify version consistency between the built assembly and
|
|
|
|
|
`package.json`, so the semver check runs alongside that as one more release
|
|
|
|
|
gate. `org.gamecult.caching.unity` has no build script at all today — it is
|
|
|
|
|
pure C# source tagged directly with no compile step — so
|
|
|
|
|
`scripts/verify-caching-unity-release.ps1` exists solely to run this check
|
|
|
|
|
before that package is tagged; it is not a build script and does not become
|
|
|
|
|
one.
|
|
|
|
|
|
|
|
|
|
None of this is enforced by a git hook or branch protection rule: it runs
|
|
|
|
|
when someone remembers to run the Unity release scripts, the same way the
|
|
|
|
|
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.
|
|
|
|
|
|
2026-10-02 17:08:36 +02:00
|
|
|
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.
|
|
|
|
|
|
Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its
own changelog says "Breaking" and the version number lied. Operator ruling:
semver is policy, strictly, for every published package family.
docs/semver-policy.md states what counts as breaking per surface (managed
API, Unity inspector attributes, CultCache wire format/store compatibility,
CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag
naming, and required changelog shape. Records the 1.0.60 scar as history;
does not renumber it.
scripts/check-changelog-semver.mjs is the mechanical check: refuses a
changelog "### Breaking" section under too small a bump, and refuses a
version that skips, reverses, or repeats the previous published tag. Skips
re-litigating a version that is already tagged (rebuilding immutable
history is not a release action). Wired into publish-packages.yml for the
four npm/PyPI packages, and into build-unity-package.ps1 (cultlib),
packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new
scripts/verify-caching-unity-release.ps1 (caching.unity has no build script
of its own to hook into).
18 node:test cases cover the required table (breaking+major/minor/patch,
non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution.
Hand-mutated each rule (bump classification, pre-1.0 lane requirement,
breaking-heading detection, breaking early-return, already-published skip)
and confirmed each kill, then restored byte-exact. No mutation harness is
committed; CultLib has none today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:03:22 +02:00
|
|
|
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 —
|
|
|
|
|
described above, not mechanically checked. Extending the checker to a Rust
|
|
|
|
|
crate's `Cargo.toml`/`CHANGELOG.md` or to an automated CultNet interop
|
|
|
|
|
compatibility gate is future work, not part of this change.
|
|
|
|
|
|
2026-10-03 02:36:58 +02:00
|
|
|
`@gamecult/cultcache-ts`, `cultcache-py`, `cultnet-py`, and `cultmesh-py`
|
|
|
|
|
currently have **no `CHANGELOG.md` file at all**; the check refuses their next
|
|
|
|
|
tagged release ("changelog not found at <declared path>") until each
|
|
|
|
|
package's file exists. A release with no changelog is not a smaller problem
|
|
|
|
|
than a mislabelled one. The next release of each creates its file:
|
|
|
|
|
`packages/cultcache-ts/CHANGELOG.md` arrives with the document-variants work
|
|
|
|
|
(`hands/variants-c2a`), and whoever cuts the next release of `cultcache-py`,
|
|
|
|
|
`cultnet-py` or `cultmesh-py` creates that package's file. Backfilling
|
|
|
|
|
changelog content for past releases would mean inventing release-note prose
|
|
|
|
|
for history nobody here knows first-hand, so the gap stays named.
|