Skip to content

Stability

github-actions[bot] edited this page Nov 3, 2026 · 1 revision

Stability

From v30: at most one major every 90 days, the previous major gets security fixes for 6 months after its successor, and a removal is deprecated for at least one minor first. Each rule is refused by a script, not remembered by a person — the table at the bottom names the code.

Next major window: 90 days after the date on the ## 30.0.0 heading in CHANGELOG.md. Until v30 ships, this line names no date; bun run scripts/release-due.ts --json answers it as .data.windowOpensOn while a major waits.

What is covered

A breaking change to any of these needs a major, and a major waits for its window.

Surface Where it is defined
the eight primitive shapes: entity, policy, action, mutator, query, job, route, task The eight primitives, PRIMITIVE_KINDS in packages/core/src/registrar.ts
the x CLI: every command, every flag, and the --json envelope each prints CLI reference
every shipped X_* code: its name and its meaning. A code is never renamed or reused Error codes
the package tier table scripts/lib/tiers.ts, Project layout
app.config.ts field names: renaming or removing one Configuration
every export a package's src/index.ts names and its README documents each package's README.md
the Bun floor engines in every package.json; it moves only in a scheduled major, or by the security exception

What is not covered

Not covered Why
an export no README documents, and any deep import (@ultimat3/x/src/...) not a public surface; it can change in a minor
.x/ — the build cache, generated manifests, snapshots regenerated by the CLI; never hand-edited, never read by an app
the exact wording of a cause: or fix: line the code is the contract; the text improves in any release
a gate verdict getting stricter a new x verify rule that reds an app waits on next like any break
performance figures and budgets measurements, re-measured each release (Routes and render modes)

Never "stable forever": a covered surface can change, in a scheduled major, with its entry in Upgrading.

Bump Means Examples
major a covered surface changed incompatibly a removed config field, a renamed CLI flag, a changed primitive field, a narrowed tier
minor additive: old code still compiles and still passes x verify a new optional field, a new command, a new driver behind an existing interface, a new @deprecated alias
patch no surface change a bug fix, a perf change, a corrected fix: line

Cadence

Release When Branch npm dist-tag
major at most once per 90 days from v30, counted from the previous major's CHANGELOG.md heading date next, merged into main when the window opens latest
minor weekly, when something shipped main latest
patch to the current major as needed main latest
patch to the previous major security fixes only, until 6 months after its successor shipped release/<major>.x maint-<major>
prerelease of the next major — planned, nothing ships one yet release-tag.yml cuts only X.Y.Z release commits, so no workflow publishes from next next next (scripts/dist-tag.ts already names it)

v30 is the exception that starts the clock: it carries every break queued before the promise existed, and scripts/release.ts does not refuse it.

The support window

Major Status Security fixes until
the current major (npm view @ultimat3/core version) every fix its successor ships, then 6 months more
the previous major security fixes, as patches on release/<major>.x 6 months after the current major's ## X.0.0 date
anything older none Upgrading, one major per x upgrade run

Convention, not enforced: release/<major>.x is cut by hand at each major and kept at that major's newest minor; no script cuts or checks it. Once it exists, a patch is latest while its major is current and maint-<major> once its successor ships. scripts/dist-tag.ts decides the tag from the registry's latest, and npm publish never runs without it.

Deprecation

A removal or a rename ships its alias, marked @deprecated, on main for at least one minor before a major takes it away. The rule compares each package's src/index.ts export names against the previous release tag: a name that is gone must have carried @deprecated there. Runtime deprecation logging is not part of this — it was deleted once, for good reason (Upgrading).

Where a break waits

Branch Holds
main minors and patches only while the window is closed; a BREAKING — changelog entry there is refused
next every breaking change waiting for the window. Convention, not enforced: it merges main weekly, by hand, so it never falls behind a fix; ci.yml gates every push to it
release/<major>.x the previous major's security patches

A change with both kinds of edit splits into two pull requests: the non-breaking half to main, the break to next.

The security exception

A security fix that cannot be made non-breaking may ship a major inside the window. It is the only exception — "critical bug" is not one, or it would be used every week.

bun run scripts/release.ts --bump major --security GHSA-xxxx-xxxx-xxxx --dry-run --json

The advisory id (a GHSA-… or CVE-…) is recorded under the section heading in CHANGELOG.md as > **Security exception:** \`, and the bump commit's body carries Security-Exception: `. The same recorded line lets a removal skip its deprecation minor.

Who decides

The maintainer bot judges whether a release is due and which kind (.dz/briefs/release-due.md), inside this window: it cannot cut a major the window refuses, because scripts/release.ts refuses it before any file moves, release.ts --check refuses it again before a tag is cut, and the gate refuses a dated section that broke it.

How each rule is enforced

Rule Refused by Code
one major per 90 days scripts/release.ts --bump, --check, and the gate's boundaries step X_RELEASE_MAJOR_WINDOW
no break on main while the window is closed bun run changelog-check and the gate, on the pull request's base branch X_BREAKING_ON_MAIN
a removal was deprecated first bun run scripts/deprecation-window.ts and the gate X_RELEASE_UNDEPRECATED_REMOVAL
every release from 30.0.0 opens with Highlights, the Release body bun run changelog-check and the gate X_CHANGELOG_HIGHLIGHTS_MISSING
a maintenance publish never moves latest scripts/dist-tag.ts in release.yml; scripts/registry-audit.ts audits latest and each maint-* tag X_REGISTRY_LATEST_DRIFT

Clone this wiki locally