Skip to content

[Docs] Two of the three patch releases shipped a change marked BREAKING while the version policy promises patches carry no breaking changes, so a consumer on a caret range auto-installs an API break #1004

Description

@pathosDev

Where

docs/src/content/docs/reference/version-policy.mdx:38-42 (and its German mirror),
against CHANGELOG.md:540-591 (0.12.2) and CHANGELOG.md:593-604 (0.12.1).

What is wrong

The published policy promises that a patch release carries no breaking change:

docs/src/content/docs/reference/version-policy.mdx:36-42
## Versioning until 1.0

Pre-1.0:

- **Patch versions (0.x.Y)** — bug fixes, no breaking changes.
- **Minor versions (0.X.0)** — new features, may include
  breaking changes.

The project has cut three patch releases — 0.9.1, 0.12.1 and 0.12.2. Two of the three
carry an explicit BREAKING marker in the CHANGELOG, both of them in the current minor
line.

0.12.1 broke a constructor:

CHANGELOG.md:593-600
## [0.12.1] — 2026-08-03

### Changed

- **BREAKING — `ReplicatedEventSourcedActor` no longer takes a `Cluster`, and
  `replicaId` now has a default** (#833).  Both existed only because the actor
  could not reach its own cluster; now that it can, they are boilerplate every
  subclass was copying:

0.12.2 changed the stored payload encoding on every backend, and the entry states the
forward-incompatibility itself:

CHANGELOG.md:540-561
## [0.12.2] — 2026-08-04

### Fixed

- **BREAKING — persistence stores no longer silently corrupt rich payload
  types** (#888).  Every journal, snapshot store and durable-state store wrote
  payloads with bare `JSON.stringify`, so a persisted `Set`/`Map` recovered as
  `{}`, a `Date` as a string, a `Uint8Array` as an index-keyed object, and a
  `bigint` threw — and because the write path folds the original object, the
  corruption only surfaced on the next recovery.  Payloads now use the tagged
  JSON tree format `JsonSerializer` already used (`__date__`, `__bytes__`,
  `__map__`, `__set__`, `__bigint__`, plus a new `__literal__` escape so user
  data shaped like a tag round-trips as data), on every backend.
  **Migration:** none for readers — rows written by older versions decode
  unchanged.  Rows written from this version on carry tag objects where plain
  JSON would corrupt the value, so *older* framework versions (and
  non-actor-ts JSON consumers) reading *new* rows see the tag shape instead of
  a bare value.

The second is the sharper one for an operator. "Older framework versions reading new
rows see the tag shape instead of a bare value" is precisely the state a rolling deploy
occupies: while pods on 0.12.1 and 0.12.2 coexist, the old pods read rows the new pods
wrote. A change with that property arriving under a patch version arrives by default
^0.12.0 and ~0.12.0 both resolve it without the operator choosing it.

Two further gaps follow from the same section, and are the reason this cannot be closed
by editing one bullet:

  • No supported-versions statement. The policy says nothing about which lines receive
    fixes. In practice there is no patch-only maintenance line: a security fix landing on
    develop reaches a consumer only through the next release off develop, which — by
    the project's own minor-version rule — may include breaking changes. So "take the
    security fix" and "take an API break" are currently the same action.
  • ## After 1.0 is the only place strictness is promised, which leaves the pre-1.0
    reader with a rule the release history contradicts rather than an honest caveat.

Fix

Make the document describe what the project actually does, rather than tightening the
practice to match a rule the project has twice chosen not to follow — pre-1.0, a hard cut
is deliberate policy (AGENTS.md, Breaking changes), and that is fine as long as it is
stated:

  1. Reword the pre-1.0 patch bullet: a patch is bug fixes and does not add features,
    but pre-1.0 it may carry a breaking fix when the alternative is leaving data
    corruption in place. Link the two precedents by version so the claim is checkable.
  2. Add a Supported versions section — realistically "latest minor only, no
    backports" — so a consumer pinning ~0.12.0 knows they are pinning to a line that
    receives nothing.
  3. Add a one-line rolling-deploy caveat to the patch bullet pointing at
    operations/upgrades/rolling-migration, since the 0.12.2 change is exactly the case
    where reading the CHANGELOG before a patch bump is load-bearing.
  4. Mirror all of it into docs/src/content/docs/de/reference/version-policy.mdx.

If the project would rather keep the strict rule, the equivalent fix is a documented
maintenance line — but that is a process commitment, not a docs edit, and should be
decided explicitly rather than implied by a bullet.

Acceptance sketch

  • The pre-1.0 patch bullet matches the release history, with 0.12.1 and 0.12.2 named.
  • A Supported versions section states which lines receive fixes.
  • The rolling-deploy hazard from the 0.12.2 encoding change is linked from the policy page.
  • EN and DE are updated together.

Verification status

Confirmed by reading, and quantified: the CHANGELOG's three patch sections were
sliced by heading offset and the BREAKING marker counted per section — 0.9.1: 0,
0.12.1: 1, 0.12.2: 1. Both sides are quoted verbatim above. A grep for
support|security|maintenance|backport over version-policy.mdx returns nothing, which
is the basis for the "no supported-versions statement" half.

Not reproduced by execution — this is a policy contradiction between two documents, and
there is nothing to run.

Part of the production-readiness review batch — tracked in #913.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationpriority: highTop priority — high impact, plan nextproduction-goalBlocks or defines the path to production readiness

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions