diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 2c5fa1f39..601f04ac5 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -136,7 +136,7 @@ Publish in tier order so a dependent never lands before its dependency. **`forge version against **every** package in the set (it used to check only the cli), and `bun run prerelease` skips burned numbers when choosing an iteration. -## Versioning policy (pre-1.0) +## Versioning policy **The version number's only mechanical meaning today is npm's caret rule.** For `0.y.z`, `^0.19.3` resolves `>=0.19.3 <0.20.0`, so: **PATCH is auto-adopted by every @@ -164,10 +164,46 @@ bytes**, not "did output change." | New **attribute** on an existing type/subtype | `@intValueMap`, `@lenient`, `@maxTokens` | **PATCH** | MINOR | | New **subtype** of an existing type | `field.uri`, `index.lookup`, `attr.intMap` | **PATCH** when inert (see the vocabulary rule below); **MINOR** when it changes existing metadata's meaning/output, narrows something previously permitted, or headlines a feature | MINOR | | New top-level metadata **type** | `requirement.*`, `index.*`, `api.*` | **MINOR** | MINOR | -| Wire-contract / conformance behavior change of already-valid deployments | FR-036 enforcement | **MINOR**, loud notice (pre-1.0 MINOR *is* the breaking slot) | MAJOR | +| **Breaking** metamodel-vocabulary change (retire an attr/subtype/type; narrow what is permitted) | FR-037 `@readOnly`, FR-038 `@verifiedBy`, ADR-0052 `@promptStyle` re-homing | **MINOR** (pre-1.0 MINOR *is* the breaking slot) | **`metamodelVersion` MAJOR**, package MINOR (ADR-0035 Am. 2) | +| Wire-contract / conformance behavior change of already-valid deployments | FR-036 enforcement | **MINOR**, loud notice (pre-1.0 MINOR *is* the breaking slot) | **`metamodelVersion` MAJOR** (Metamodel 2.0) + package MINOR — see the two-contracts rule below | | Wire behavior fixed to match the documented/conformance contract | 0.19.1 `@min` clamp | PATCH | PATCH | | No changed product file in a port | PyPI/NuGet/Maven at 0.20.14 | **Version-parity bump at the shared patch number** — publish identical content at the new version; never skip a registry (single-shared-patch policy, standing since 0.20.13) | same | +### The two-contracts rule (post-1.0; ADR-0035 Amendment 2) + +**Package 1.0 does not freeze the metamodel.** After the cut, the project versions two +contracts on two numbers: + +- **Package version** (npm/PyPI/NuGet `1.x`, Maven `8.x`) promises the SOFTWARE surface — + exports, CLI flags, generated-code shape, runtime helpers. A break here is `2.0.0` / + `9.0.0`. +- **`metamodelVersion`** (`"0.9"` today, `"1.0"` at the cut; the first key of the + byte-gated `expected-registry.json`) promises the METADATA contract — registered + vocabulary, canonical/interchange format, wire contract. A break here moves ITS major, + and **does not force a package major.** + +That severance is the whole point: under the pre-amendment rule one vocabulary retirement +dragged npm to `2.0.0` and Maven to `9.0.0`, so the package majors became a running count +of metamodel edits. Measured cadence at the time of the amendment: **19 minor lines in 87 +days**. + +**When you cut a release that moves `metamodelVersion`:** + +1. Bump `METAMODEL_VERSION` in all five ports (it is byte-gated — `registry-conformance` + fails until every port agrees) and regenerate `expected-registry.json`. +2. The changelog entry MUST say the metamodel version moved, and to what. Post-1.0 the + caret rule is no longer a gate (`^1.0.0` accepts `1.1.0`), so **the changelog is the + adopter's only signal** until the deferred loader check exists. +3. Ship a migration guide under `docs/features/migrations/`, as every breaking metamodel + change already does. + +Design + deferral triggers: +[`docs/superpowers/specs/2026-08-20-two-contracts-versioning-design.md`](superpowers/specs/2026-08-20-two-contracts-versioning-design.md). + +**Cadence is a separate lever, and it is free.** Nothing forces one release per merged +change; batching a fortnight of work into one coordinated cut removes most of the number +pressure without touching policy at all. + ### The vocabulary rule (corrected 2026-08-17) **Adding registry vocabulary does NOT, by itself, force a MINOR.** The rule used to read diff --git a/docs/compatibility-policy.md b/docs/compatibility-policy.md index 8816d55bd..bb96df875 100644 --- a/docs/compatibility-policy.md +++ b/docs/compatibility-policy.md @@ -22,10 +22,22 @@ carries the compatibility promise. (This is the OpenTelemetry / Protobuf-edition model. The Java `7.x → 8.0` step is a forward major — package versions never move backward, a hard rule on every registry.) -## What is covered (breaking change ⇒ next MAJOR) +## Two contracts, two numbers -After 1.0, a breaking change to any of the following requires a **new major of the -Metamodel spec version** (Metamodel 2.0) and a major bump of every affected package: +**Package 1.0 does not freeze the metamodel.** Those are two different promises to two +different parts of your project, and each has its own number +([ADR-0035 Amendment 2](../spec/decisions/ADR-0035-one-zero-stability-commitment-and-version-unification.md)): + +| Number | Promises | A break moves | +|---|---|---| +| **Package version** (npm/PyPI/NuGet `1.x`, Maven `8.x`) | the SOFTWARE surface — your **build** depends on it | the package major (`2.0.0` / `9.0.0`) | +| **`metamodelVersion`** (`"1.0"` at the cut) | the METADATA contract — your **model** depends on it | the metamodel major (Metamodel 2.0) | + +Reading it the other way round: a package major means *your imports, CLI invocations or +generated-code shape may need work*. A metamodel major means *your metadata may need +work*. A release can move one without the other, and most releases move neither. + +### Covered by the METAMODEL version (breaking ⇒ Metamodel 2.0) - **The metamodel vocabulary** — the registered type / subtype / attribute set, enforced by `registry-conformance`. This is the durable spine. @@ -33,11 +45,22 @@ Metamodel spec version** (Metamodel 2.0) and a major bump of every affected pack rules, sigil-free YAML, the `extends` / `@via` grammar, package `::` syntax. - **The wire / normalization contract** — the cross-port serialized form (currency minor units, pagination, the native-return-type contract, jsonb parsed-value). + +### Covered by the PACKAGE version (breaking ⇒ package MAJOR) + - **The CLI command surface** — `init` / `gen` / `verify` and their *documented* flags, per port (`meta`, `dotnet meta`, `mvn metaobjects:*`, `metaobjects`). - **The scaffold-and-own contract** — what `meta init` scaffolds and the `Generator` interface owned templates implement. +> **What this costs you, stated plainly.** Post-1.0 the caret rule stops being a gate — +> `^1.0.0` accepts `1.1.0` — so a metamodel change can reach you on a routine update +> without a package major to refuse it. Today the project's answer is that every adopter +> is reachable and gets told; a mechanical gate (declaring which Metamodel version your +> metadata targets, and having the loader check it) is deferred until that stops being +> true. **Every release that moves `metamodelVersion` says so in the changelog** — that +> is the signal to read. + ## What is NOT covered (may change in a MINOR) - **Generator internals and the reference templates themselves.** Generated code is @@ -53,7 +76,9 @@ Metamodel spec version** (Metamodel 2.0) and a major bump of every affected pack The trigger is **new public surface, not code size**: - **MINOR** — adds surface a consumer can newly depend on: a new generated artifact, - a new CLI flag, or a newly-supported metamodel member. Additive; never breaking. + a new CLI flag, or a newly-supported metamodel member. Additive; never breaking **on + the software surface**. A release that breaks the *metadata* contract also moves + `metamodelVersion` — the package coordinate is not where that fact lives. - **PATCH** — a bug fix or internal refactor with no new surface. - **Metamodel spec-version bump** — only when the shared vocabulary or wire/canonical contract itself changes. Most releases are per-port package moves that do *not* diff --git a/docs/superpowers/specs/2026-08-20-two-contracts-versioning-design.md b/docs/superpowers/specs/2026-08-20-two-contracts-versioning-design.md new file mode 100644 index 000000000..9f5257acc --- /dev/null +++ b/docs/superpowers/specs/2026-08-20-two-contracts-versioning-design.md @@ -0,0 +1,140 @@ +# Two contracts, two numbers — versioning after 1.0 + +**Status:** design, for ratification. Amends [ADR-0035](../../../spec/decisions/ADR-0035-one-zero-stability-commitment-and-version-unification.md) §1–§2 and `docs/compatibility-policy.md`. +**Date:** 2026-08-20 +**Scope:** policy only. No loader work, no deprecation windows, no new vocabulary. + +## The problem, measured + +`v0.5.0` (2026-05-22) → `0.23.2` (2026-08-17) is **19 minor lines in 87 days** across +90 tags — a new minor roughly every 4–5 days. Doug's summary of where that leads — +*"we'll be on 100.100.0"* — is not hyperbole; it is the current rate extended. + +The rate itself is not the defect. Most of those minors were correct calls under the +rule in force at the time. The defect is **what the rule attaches the number to.** + +Today, one number carries two unrelated promises: + +1. **The software surface** — the imports, CLI flags, generated-code shape and runtime + helpers an adopter's *build* depends on. +2. **The metamodel** — the registered vocabulary, canonical format and wire contract an + adopter's *metadata* depends on. + +ADR-0035 §1 binds them: a breaking change to the metamodel "bumps the spec version's +major (Metamodel 2.0) **and forces a major on every affected package**." So one +vocabulary retirement — `@readOnly`, `@verifiedBy`, `@unique` — drags npm to `2.0.0` and +Maven to `9.0.0`, and the package majors become a running count of metamodel edits +rather than a statement about the software. + +That is already shaping decisions. The ADR-0052 roadmap correction (2026-08-19) had to +rule that a post-1.0 `1.1` MINOR **cannot** carry FR-037's or FR-038's vocabulary +retirements, because ADR-0035 §1 makes each a 2.0 event. The rule is working exactly as +written; what it produces is a project that reaches 1.0 and then has to spend a major on +its next housekeeping edit. + +## The decision + +**Split the promises, and give each its own number.** + +| Number | Promises | Moves when | +|---|---|---| +| **Package version** (npm/PyPI/NuGet `1.x`, Maven `8.x`) | the SOFTWARE surface: exports, CLI flags, generated-code shape, runtime helpers | that surface changes — full SemVer, `2.0.0` / `9.0.0` for a break | +| **`metamodelVersion`** (`"0.9"` today, `"1.0"` at the cut) | the METADATA contract: registered vocabulary, canonical authoring + interchange format, wire/normalization contract | that contract changes — its own major for a break | + +**A metamodel-vocabulary break moves `metamodelVersion`, and does NOT force a package +major.** That clause of ADR-0035 §1 is severed. + +`metamodelVersion` is not new. It has shipped in all five ports since PR #145 as the +first key of the byte-gated `expected-registry.json` +(`RegistryManifest.METAMODEL_VERSION` / `METAMODEL_VERSION` / `metamodelVersion`). ADR-0035 +§2 already decoupled it from package coordinates and made it the cross-language parity +signal. This design does one further thing: it makes that number **load-bearing for +compatibility**, not merely descriptive of it. + +### What a package MINOR means, restated + +`docs/compatibility-policy.md` currently says a MINOR is "Additive; never breaking." That +becomes: + +> **MINOR** — adds surface a consumer can newly depend on. Never breaking **on the +> software surface**. A release that breaks the METADATA contract also moves +> `metamodelVersion`, and says so in the changelog; the package coordinate is not where +> that fact lives. + +### Cadence is a separate lever, and it is free + +Nothing forces one release per merged change. Batching a fortnight of work into one +coordinated cut costs nothing and removes most of the number pressure on its own. The +19-minors-in-87-days figure is as much a cadence artifact as a policy one, and cadence +needs no ADR to change. + +## What this costs, stated plainly + +**It trades a mechanical gate for a social one.** + +Pre-1.0, the caret rule is a real gate: `^0.22.x` resolves `<0.23.0`, so a consumer +adopts a MINOR deliberately. That is why `0.21.0` and `0.23.x` could ship metamodel +changes safely. **Post-1.0 that gate disappears** — `^1.0.0` accepts `1.1.0` — and the +package MAJOR becomes the only coordinate a resolver will refuse to cross. Severing the +"metamodel break ⇒ package major" link therefore removes the only *mechanical* protection +an adopter has against auto-adopting a metadata break on a routine update. + +What replaces it, today, is that **every adopter is reachable**: six projects, all +Doug's, all upgradeable by hand. That is a true statement about 2026 and a coherent basis +for the trade. It is also a premise with an expiry date, so it is written down here rather +than left implicit. + +**Why no loader check today.** The obvious mechanical replacement — the loader refusing +metadata declared for an incompatible metamodel version — is deferred deliberately, and it +is more expensive than it looks: `metamodelVersion` is currently a property of the +LIBRARY, and there is nowhere an adopter declares *"my metadata targets Metamodel 1.0."* +Adding that declaration is new vocabulary in all five ports plus a compatibility matrix — +real work, gating a hazard that six reachable adopters do not have. + +### Deferred, with triggers + +| Deferred | Adopt when | +|---|---| +| **A declared metadata target + loader/verify compatibility check** — the mechanical gate above | the adopter set stops being enumerable and reachable: a metamodel break ships to someone who cannot be told in advance | +| **Per-item `experimental` / `stable` markers** (the Kubernetes model) | a member needs to ship for feedback without entering the frozen set — today the reserved-not-registered treatment (ADR-0007 Am. 2, ADR-0040) already covers this | +| **Editions** (the Rust model) — a per-model opt-in that pins old semantics | two metamodel majors coexist in one estate and pinning per model beats upgrading per repo | + +## Prior art + +- **OpenTelemetry** — spec version (1.5x) is the coordinating contract; each language SDK + versions independently against it, with a compliance matrix. Already the model ADR-0035 + §2 adopted; this design finishes the job by letting the two numbers move independently + in *both* directions. +- **Rust editions** — the language breaks; the compiler version does not. A per-crate + `edition` key opts in. The precedent for "a breaking change to the *language* is not a + breaking change to the *tool*." +- **Kubernetes** — `alpha`/`beta`/`stable` per API object with a published deprecation + window per tier, rather than one project-wide major. Rejected for now as the wrong shape + at six adopters; retained as a trigger above. +- **TypeScript** — explicitly does not follow SemVer, and says so, because every release + can break inference. The honest-labelling precedent: it is better to state the real + contract than to encode a false one in the number. + +## What changes in the repo + +Policy only — no product code. + +1. **ADR-0035 §1** — sever "and forces a major on every affected package"; state the split + and its cost; add the deferral triggers as an amendment. +2. **`docs/compatibility-policy.md`** — the two-number table, the restated MINOR, and an + explicit sentence that **package 1.0 does not freeze the metamodel**. +3. **`docs/RELEASING.md`** — the post-1.0 column of the versioning table stops reading + MAJOR for metamodel changes; add the `metamodelVersion` row. +4. **`spec/roadmap.md`** — the "what the next breaking MINOR carries" section (added + 2026-08-19) gets the post-1.0 rule beside it. + +## Open question for ratification + +**Does the next breaking batch go before or after the 1.0 cut?** + +Before is cheaper: pre-1.0 the caret rule still gives adopters a real gate, so FR-037, +FR-038, ADR-0052 and ADR-0053 can all land in one deliberate MINOR with the mechanism the +project already trusts. After means the first exercise of this new policy is also the +first time the mechanical gate is absent. + +This design does not settle it — it is a schedule call, not a policy one. diff --git a/spec/decisions/ADR-0035-one-zero-stability-commitment-and-version-unification.md b/spec/decisions/ADR-0035-one-zero-stability-commitment-and-version-unification.md index 749efcca0..5e909da5b 100644 --- a/spec/decisions/ADR-0035-one-zero-stability-commitment-and-version-unification.md +++ b/spec/decisions/ADR-0035-one-zero-stability-commitment-and-version-unification.md @@ -212,13 +212,63 @@ nobody can observe is a gate you no longer have when something real needs it. This amendment changes cadence policy only. It does not touch §1's post-1.0 compat promise: after 1.0, a **breaking** change to the metamodel vocabulary still requires a -MAJOR, whether the break is an attribute, a subtype, or a type. Operational form of the -rule (with worked rows): `docs/RELEASING.md` → "Versioning policy (pre-1.0)". +MAJOR, whether the break is an attribute, a subtype, or a type — **but see Amendment 2 +for WHICH major: the metamodel's own, not the package's.** Operational form of the +rule (with worked rows): `docs/RELEASING.md` → "Versioning policy". + +## Amendment 2 (2026-08-20) — two contracts, two numbers + +**Severs §1's clause "and forces a major on every affected package."** A breaking change +to the METAMODEL moves `metamodelVersion` and does **not**, by itself, move a package +major. Design: [`docs/superpowers/specs/2026-08-20-two-contracts-versioning-design.md`](../../docs/superpowers/specs/2026-08-20-two-contracts-versioning-design.md). + +§1 bound two unrelated promises to one number: the SOFTWARE surface (exports, CLI flags, +generated-code shape) and the METADATA contract (registered vocabulary, canonical format, +wire contract). Under that binding, one vocabulary retirement drags npm to `2.0.0` and +Maven to `9.0.0`, and the package majors become a running count of metamodel edits. The +measured cadence — **19 minor lines in 87 days** across 90 tags — is what makes the +consequence concrete rather than theoretical, and the ADR-0052 roadmap correction +(2026-08-19) is where it first bit: a post-1.0 `1.1` could not carry FR-037's or FR-038's +vocabulary retirements *because of this clause*. + +After this amendment: + +- **Package version** (npm/PyPI/NuGet `1.x`, Maven `8.x`) promises the software surface. + Full SemVer; a break is `2.0.0` / `9.0.0`. +- **`metamodelVersion`** (`"0.9"` today, `"1.0"` at the cut; the first key of the + byte-gated `expected-registry.json`, shipped in all five ports since #145) promises the + metadata contract. A break moves ITS major. +- §1's covered set is unchanged in *content*. What changes is which number carries it: + the metamodel vocabulary, the canonical/interchange format and the wire contract are now + borne by `metamodelVersion`; the CLI surface and the scaffold-and-own contract stay on + the package version, where they belong. +- **A package MINOR is "never breaking ON THE SOFTWARE SURFACE."** A release that breaks + the metadata contract also moves `metamodelVersion` and says so in the changelog. + +**The cost, stated so it is not discovered later.** Post-1.0 the caret rule stops being a +gate — `^1.0.0` accepts `1.1.0` — so the package MAJOR is the only coordinate a resolver +refuses to cross. Severing this link removes the only *mechanical* protection against +auto-adopting a metadata break. What replaces it today is that the adopter set is +**enumerable and reachable** (six projects). That is a true statement about 2026 and a +coherent basis for the trade; it is written here because it is a premise that expires. + +**Deferred, with triggers.** A declared metadata target plus a loader/`verify` +compatibility check — the mechanical replacement — is deferred until the adopter set stops +being reachable. It is more expensive than it looks: `metamodelVersion` is a property of +the LIBRARY, and there is nowhere an adopter declares *"my metadata targets Metamodel +1.0"*; adding that is new vocabulary in five ports plus a compatibility matrix. +Per-item `experimental`/`stable` markers (Kubernetes) and editions (Rust) are likewise +deferred with written triggers in the design doc. + +**Cadence is a separate lever and is free.** Nothing forces one release per merged change; +batching removes most of the number pressure without any policy change at all. ## Consequences -- After 1.0, a breaking change to the metamodel vocabulary, the canonical/wire - format, the CLI surface, or the scaffold-and-own contract requires a **2.0**. The +- After 1.0, a breaking change to the CLI surface or the scaffold-and-own contract + requires a package **2.0** / **9.0**; a breaking change to the metamodel vocabulary, + the canonical/wire format or the interchange format moves **`metamodelVersion`'s** + major instead (**Amendment 2**). The small stable surface (a consequence of scaffold-and-own) makes that a realistic commitment, not a straitjacket. - The deprecation removal and the version-line re-baselining (npm/PyPI/NuGet `→1.0.0`, diff --git a/spec/roadmap.md b/spec/roadmap.md index 3ceb7aaf5..c824653c6 100644 --- a/spec/roadmap.md +++ b/spec/roadmap.md @@ -48,7 +48,7 @@ under **Shipped**; planned FRs under **Planned** + the **Release plan**. ✅ shi | FR-034 | Ecosystem tier — connected systems (`system`/`container`/`surface`/`environment`) | 📋 designed (**draft, deferred post-1.0**) | 1.1 | — | | FR-035 | Present-key PATCH tristate (mutation surface) | 🟢 shipped 5 ports (absent→untouched / present-null→clears / null-on-`@required`→400); coordinated **breaking** release held for FR-036 | 1.0 | — | | FR-036 | Constraint-validation enforcement + semantic pins | 🟢 shipped 5 ports in the coordinated `0.16.0`/`7.8.0` breaking release (required-string = non-empty·accept-whitespace · `@Pattern` = full-match · strictest-wins length; HTTP-tier enforcement all 5 ports; TPH tristate) | 1.0 | — | -| FR-037 | Projection expressiveness (`origin.rank`) + field write-access modes (`@mutability`) | ⚪ requirements (pre-design), decisions resolved. **R1/R2 retire registered vocabulary (`@readOnly`), so the breaking half must ride a coordinated pre-1.0 breaking MINOR** — post-1.0 a metamodel-vocabulary break is a 2.0 event (ADR-0035 §1), not a 1.1 minor. `origin.rank` + the rest are additive → 1.1. Design: `docs/superpowers/specs/2026-08-10-fr-037-projection-expressiveness-and-write-once-design.md` | 1.0 · 1.1 | — | +| FR-037 | Projection expressiveness (`origin.rank`) + field write-access modes (`@mutability`) | ⚪ requirements (pre-design), decisions resolved. **R1/R2 retire registered vocabulary (`@readOnly`), so the breaking half must ride a coordinated pre-1.0 breaking MINOR** — post-1.0 a metamodel-vocabulary break moves `metamodelVersion`'s major (ADR-0035 §1 + Am. 2) and should still ride the pre-1.0 slot, where the caret rule is a real gate. `origin.rank` + the rest are additive → 1.1. Design: `docs/superpowers/specs/2026-08-10-fr-037-projection-expressiveness-and-write-once-design.md` | 1.0 · 1.1 | — | | FR-038 | Requirement-derived test stubs (inverts `@verifiedBy`) | ⚪ proposed. Generate the test from the requirement so the link is structural, not a name the author picks — an audit of one 19-name ledger found 4 names that did not verify their claim. **The `@verifiedBy` retirement is breaking and rides the SAME pre-1.0 MINOR as FR-037's R1/R2, not a second one**; the stub generator is additive → 1.1. Design: `docs/superpowers/specs/2026-08-15-fr-038-requirement-derived-test-stubs-design.md` | 1.0 · 1.1 | — | _(FR-001 was the original metamodel foundation — pre-dates the FR-numbered tracking.)_ @@ -239,7 +239,7 @@ breaking change ships**, because `^0.20.x` does not resolve `0.21.0` and so the deliberately rather than on a routine `npm update`. **This is a rule, not a preference.** After the renumbering, a breaking change to the metamodel -vocabulary requires a **2.0** (ADR-0035 §1 + Amendment 1, Consequences). So a breaking metamodel +vocabulary requires a **major** (ADR-0035 §1 + Amendment 1, Consequences). So a breaking metamodel change cannot be targeted at `1.1` — that target is unreachable for the breaking half of any FR that retires registered vocabulary. Chartered to ride the next such MINOR: @@ -267,6 +267,16 @@ Group-B entry above: it rides the breaking batch "while 1.0 stays unscheduled", scheduled imminently" listed as the reversal trigger). Note that ADR-0035's own §C3 text still names `0.15.1` as the last breaking move — stale; `0.21.0` superseded it. +**What ADR-0035 Amendment 2 changes here, and what it does not.** Amendment 2 (2026-08-20) +severs "a metamodel break forces a PACKAGE major": post-1.0 such a break moves +**`metamodelVersion`'s** major, and the package rides a MINOR. That removes the "`1.1` is +unreachable" arithmetic above — but it does **not** make the pre-1.0 slot unnecessary, and the +batch should still ride it. Pre-1.0, `^0.x` gives adopters a real, mechanical gate; post-1.0 +`^1.0.0` accepts `1.1.0`, so the same break arrives on a routine update with only a changelog +line to announce it. Landing this batch before the cut is landing it with the stronger mechanism +still available. See +[`docs/superpowers/specs/2026-08-20-two-contracts-versioning-design.md`](../docs/superpowers/specs/2026-08-20-two-contracts-versioning-design.md). + **The open tension, named:** `CLAUDE.md` and the "Before 1.0" section above both say GA is the next release move and that nothing outstanding blocks the promotion. This section says a breaking MINOR comes first. Those are reconcilable only by deciding which goes first — and