Skip to content

docs(decisions): only a Changed — BREAKING forces a minor (ADR-0061) - #307

Merged
Harol-Reina merged 1 commit into
mainfrom
docs/adr-0061-breaking-tier
Sep 24, 2026
Merged

Harol-Reina merged 1 commit into
mainfrom
docs/adr-0061-breaking-tier

Conversation

@Harol-Reina

Copy link
Copy Markdown
Contributor

The contradiction

Three accepted records say a behavioural break takes a minor:

  • ADR-0028 (2026-04-20), post-v2.0 commitment 3: "Patch cadence: mensual o por needs,
    no-breaking por definición."
    In force since v2.0.0 shipped 2026-05-06.
  • ADR-0050 (2026-08-17): "It ships in a minor with an explicit callout, never in a patch."
  • ADR-0052 F4 (2026-08-19): "gets a BREAKING CHANGELOG entry and a minor bump."

The shipped history contradicts them twice. Counted across every BREAKING heading in
CHANGELOG.md on 2026-09-24:

Release Tier Fixed — BREAKING Changed — BREAKING
2.5.0 (2026-08-24) minor 10 3
2.5.2 (2026-09-13) patch 2 0
2.5.3 (2026-09-13) patch 2 0
[Unreleased] — 2 2

Both patches landed after all three records.

The pattern nobody had written down

All three Changed — BREAKING are in 2.5.0, a minor; none has ever shipped in a patch. Fourteen
Fixed — BREAKING shipped in both tiers. And 2.5.0's ten did not cause its minor — the three
Changed in the same release already forced it. No release has ever taken a minor for a
Fixed — BREAKING.

The four BREAKING markers that appear outside a heading (:532, :1415, :1488, :1848) each
sit under a Fixed — BREAKING heading and elaborate it, so the headings are a complete index.

The split is not arbitrary. Fixed says the documented behaviour was not being delivered and now
is — the consumer relied on something never promised. Changed withdraws a promise that was kept.

The decision

  • D1 — Only a Changed — BREAKING forces a minor. A release whose breaking entries are all
    Fixed — BREAKING may be a patch.
  • D2 — This amends ADR-0028's commitment 3, and says so. The rest of that ADR stands: the
    8–12 minors/year cap, the annual major, the LTS line, and the migration-guide obligation on a
    breaking minor. ADR-0027's constraint on removing public MIT types is untouched.
  • D3 — The label is the decision, made by the author against what the SDK documented before the
    change. An entry that cannot say which documented behaviour it restores is a Changed.
  • D4 — The tier binds the release, not the diff (ADR-0055).
  • D5 — 2.5.2 and 2.5.3 are applications of the rule, not errors.

The cost is stated rather than mitigated: a label now carries the release tier and nothing
verifies it.
No test parses CHANGELOG.md, and publish.yml reads only ## [<version>] headings.
The mitigation is D3 plus the callout text a reviewer can check against the diff — deliberately a
review property, because a parser over prose headings would fail toward blocking a correct release.

Also in this PR

#286's CHANGELOG entry gains its BREAKING label — the ruling that was left open. A save in
flight at shutdown is now cut short when the host's budget expires instead of running under a token
nothing could cancel; that is observable to an operator, which is the threshold #284 was labelled
under. It is a Fixed, so it does not move the tier: 2.6.0 is already a minor from its two
Changed — BREAKING.

Plus the serial coupling every ADR-adding PR carries: README.md 58 → 59 ADRs, its claim-registry
row, and the catalog row in docs/decisions/README.md.

One apparent counterexample, resolved

ADR-0048 D7 reads "a route fix that changes the meaning of a public property is an API decision,
not a patch"
. Its next sentence settles the sense — "never applied inline" — so "patch" there is
an ad-hoc code change, not a release tier. The case it names resolves it anyway:
SpeechmaticsOptions.BaseUri shipped as one of 2.5.0's three Changed — BREAKING, in a minor. A
fourth time the unwritten rule was followed.

Debt this surfaces, recorded not resolved

ADR-0028 obliges a minor carrying a breaking change to ship a migration guide. 2.6.0 will be one and
only docs/guides/audiosocket-wire-format-migration.md exists, written for #302. #291's
AriOutboundListener change has none.

Verification

  • Tests/Verbara.Sdk.OpenTelemetry.Tests — 31 passed, including ThePublishedAdrCount_ShouldMatchTheDecisionsOnDisk and TheDecisionCatalog_ShouldListEveryAdrOnDisk
  • Tests/Verbara.Sdk.Governance.Tests — 129 passed
  • openspec validate --all --strict — 13 passed, 0 failed
  • ADRs on disk vs catalog: 59 / 59, set-equal both ways

🤖 Generated with Claude Code

Three accepted records say a behavioural break takes a minor, and the shipped
history contradicts them twice. ADR-0028's post-v2.0 commitment 3 calls a patch
"no-breaking por definición"; ADR-0050 says "never in a patch"; ADR-0052 F4 says
a breaking entry "gets a minor bump". But 2.5.2 and 2.5.3, both 2026-09-13 and
both after all three, each shipped two `Fixed — BREAKING` entries as a patch.

Counted across every `BREAKING` heading in CHANGELOG.md: all three
`Changed — BREAKING` live in 2.5.0, a minor, and none has ever shipped in a
patch; fourteen `Fixed — BREAKING` shipped in both tiers. And 2.5.0's ten did
not cause its minor — the three `Changed` in the same release already did. No
release has ever taken a minor *for* a `Fixed — BREAKING`.

The split is not arbitrary: `Fixed` says the documented behaviour was not being
delivered and now is, so the consumer relied on something never promised;
`Changed` withdraws a promise that was kept.

D1 records that. D2 states the amendment to ADR-0028 commitment 3 explicitly
rather than leaving it implicit — the rest of that ADR stands, including the
8-12 minors/year cap and the migration guide a breaking minor owes. D5 records
that 2.5.2 and 2.5.3 are applications of the rule, not errors.

The cost is stated, not mitigated: a label now carries the release tier and
nothing verifies it. No test parses CHANGELOG.md and publish.yml never reads
`[Unreleased]`.

Also applied here, since it is the same ruling: #286's entry gains its
`BREAKING` label. A save in flight at shutdown is now cut short when the budget
expires instead of running under a token nothing could cancel — observable to
an operator, which is the threshold #284 was labelled under. It is a `Fixed`,
so it does not move the pending release's tier; 2.6.0 is already a minor from
the two `Changed — BREAKING` it carries.

ADR-0048 D7 reads like a third tier rule and is not: its "not a patch" means an
inline code change, settled by its own next sentence, and the case it names
shipped as a `Changed — BREAKING` in a minor anyway. ADR-0027's constraint on
removing public MIT types is untouched.
@Harol-Reina
Harol-Reina added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit 282f8ef Sep 24, 2026
14 checks passed
@Harol-Reina
Harol-Reina deleted the docs/adr-0061-breaking-tier branch September 24, 2026 12:25
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