Skip to content

docs(versioning)!: two contracts, two numbers — a metamodel break stops forcing a package major - #321

Merged
dmealing merged 1 commit into
mainfrom
docs/versioning-two-contracts
Aug 21, 2026
Merged

docs(versioning)!: two contracts, two numbers — a metamodel break stops forcing a package major#321
dmealing merged 1 commit into
mainfrom
docs/versioning-two-contracts

Conversation

@dmealing

Copy link
Copy Markdown
Member

What this is

Policy only — no product code. It amends ADR-0035 §1 so that a breaking change to the
METAMODEL moves metamodelVersion and does not, by itself, force a package major.

This is the task Doug scoped separately from PR #318; the brainstorming had reached the
approved-shape stage and this writes it up.

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 minor every 4–5 days. Doug's summary of where that leads, "we'll be on
100.100.0"
, is the current rate extended.

The rate is not the defect. What the rule attaches the number to is. One number
currently carries two unrelated promises:

  1. the software surface an adopter's build depends on (exports, CLI flags,
    generated-code shape);
  2. the metamodel an adopter's metadata depends on (vocabulary, canonical format,
    wire contract).

ADR-0035 §1 binds them — a metamodel break "bumps the spec version's major and forces a
major on every affected package
" — so 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.

That clause first bit on 2026-08-19: the ADR-0052 roadmap correction had to rule that a
post-1.0 1.1 MINOR cannot carry FR-037's or FR-038's vocabulary retirements
because of it. The rule works exactly as written; what it produces is a project that
reaches 1.0 and then spends a major on its next housekeeping edit.

The decision

Number Promises A break moves
Package version (npm/PyPI/NuGet 1.x, Maven 8.x) the SOFTWARE surface — your build the package major (2.0.0 / 9.0.0)
metamodelVersion ("0.9" today, "1.0" at the cut) the METADATA contract — your model the metamodel major (Metamodel 2.0)

metamodelVersion is not new — it has shipped in all five ports since #145 as the first
key of the byte-gated expected-registry.json, and ADR-0035 §2 already decoupled it from
package coordinates. This does one further thing: it makes that number load-bearing for
compatibility
rather than merely descriptive of it.

A package MINOR is restated as "never breaking on the software surface."

What it costs, stated so it is not discovered later

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. Post-1.0 that gate
disappears
^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 on a routine update.

What replaces it today is that every adopter is enumerable and reachable (six
projects). That is a true statement about 2026 and a coherent basis for the trade. It is
written down because it is a premise that expires.

Why no loader check now, and what it would cost. The mechanical replacement is
deferred deliberately and 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 —
real work, gating a hazard six reachable adopters do not have.

Deferred with written triggers: the declared target + loader check (adopt when the
adopter set stops being reachable), per-item experimental/stable markers (Kubernetes),
and editions (Rust).

Cadence is a separate lever and is free. Nothing forces one release per merged change;
batching removes most of the number pressure without touching policy at all.

What it does NOT change

The roadmap records this explicitly: Amendment 2 removes the "1.1 is unreachable"
arithmetic, but the ADR-0052 / FR-037 / FR-038 batch should still ride the pre-1.0
slot
. Landing it before the cut is landing it while the stronger mechanism still exists.

Files

  • Newdocs/superpowers/specs/2026-08-20-two-contracts-versioning-design.md
    (design, prior art, deferral triggers)
  • ADR-0035 — Amendment 2 + corrected Consequences; Amendment 1's closing sentence now
    points at it
  • docs/compatibility-policy.md — the two-number table, split covered-sets, the
    restated MINOR, and an explicit "package 1.0 does not freeze the metamodel"
  • docs/RELEASING.md — a two-contracts rule with the release checklist for a
    metamodelVersion bump; two table rows corrected; section renamed (it is no longer
    pre-1.0 only)
  • spec/roadmap.md — what the amendment changes for the next breaking MINOR, and what
    it does not

Open for ratification

Does the breaking batch go before or after the 1.0 cut? Before is cheaper — the caret
rule still gives a real gate. This PR does not settle it; it is a schedule call, not a
policy one.

ci-local --quick green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DhpswkF1NvwxhFWMmdAT15

…ps forcing a package major

ADR-0035 §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.

Measured, so the consequence is not theoretical: `v0.5.0` → `0.23.2` is **19 minor lines
in 87 days** across 90 tags — a minor every 4–5 days. Doug's summary of where that leads,
"we'll be on 100.100.0", is the current rate extended.

**Amendment 2 severs the clause.** A metamodel break moves `metamodelVersion` — which has
shipped in all five ports since #145 as the first key of the byte-gated
`expected-registry.json` — and the package rides a MINOR. The package version keeps the
CLI surface and the scaffold-and-own contract; the metamodel version takes the vocabulary,
the canonical/interchange format and the wire contract. A package MINOR is now "never
breaking ON THE SOFTWARE SURFACE".

This is the clause that first bit on 2026-08-19: the ADR-0052 roadmap correction had to
rule that a post-1.0 `1.1` could not carry FR-037's or FR-038's retirements *because of
it*. The roadmap now records what Amendment 2 changes there and — importantly — what it
does NOT: the batch should still ride the pre-1.0 slot, because pre-1.0 `^0.x` is a real
mechanical gate and post-1.0 `^1.0.0` accepts `1.1.0`.

**The cost is written down rather than discovered later.** Post-1.0 the package MAJOR is
the only coordinate a resolver refuses to cross, so 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 — a true statement about 2026, and a
premise with an expiry date. The mechanical replacement (an adopter-declared metadata
target plus a loader/verify check) is deferred with a written trigger, and costed: today
`metamodelVersion` is a property of the LIBRARY, and there is nowhere an adopter declares
which Metamodel version their metadata targets.

Design doc records the prior art the decision rests on (OpenTelemetry spec-vs-SDK, Rust
editions, Kubernetes per-item tiers, TypeScript's explicit non-semver stance) and defers
per-item stability markers and editions with their own triggers.

Policy only — no product code. Open for ratification: whether the breaking batch goes
before or after the 1.0 cut. That is a schedule call, not a policy one.

ci-local --quick green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhpswkF1NvwxhFWMmdAT15
@dmealing
dmealing merged commit 0994d6e into main Aug 21, 2026
1 check passed
@dmealing
dmealing deleted the docs/versioning-two-contracts branch August 21, 2026 00:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant