Ultimate

Stability

From v31: 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: opens on 2027-02-08, 90 days after 31.0.0 (2026-11-10). The bump that ships a major rewrites this line (scripts/release.ts).

What is covered

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

SurfaceWhere it is defined
the eight primitive shapes: entity, policy, action, mutator, query, job, route, taskThe eight primitives, PRIMITIVE_KINDS in packages/core/src/registrar.ts
the x CLI: every command, every flag, and the --json envelope each printsCLI reference
every shipped X_* code: its name and its meaning. A code is never renamed or reusedError codes
the package tier tablescripts/lib/tiers.ts, Project layout
app.config.ts field names: renaming or removing oneConfiguration
every export a package's src/index.ts names and its README documentseach package's README.md
the Bun floorengines in every package.json; it moves only in a scheduled major, or by the security exception

What is not covered

Not coveredWhy
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, snapshotsregenerated by the CLI; never hand-edited, never read by an app
the exact wording of a cause: or fix: linethe code is the contract; the text improves in any release
a gate verdict getting strictera new x verify rule that reds an app waits on next like any break
performance figures and budgetsmeasurements, each dated in its committed JSON under scripts/bench/results/, never promises. bench.yml is meant to re-measure the first-hour and HTTP figures weekly and on each release, as a pull request; As of 2026-11-10 its one run measured and could not open that pull request, so the committed figures are older. Budgets: Routes and render modes

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

BumpMeansExamples
majora covered surface changed incompatiblya removed config field, a renamed CLI flag, a changed primitive field, a narrowed tier
minoradditive: old code still compiles and still passes x verifya new optional field, a new command, a new driver behind an existing interface, a new @deprecated alias
patchno surface changea bug fix, a perf change, a corrected fix: line

Cadence

ReleaseWhenBranchnpm dist-tag
majorat most once per 90 days from v31, counted from the previous major's CHANGELOG.md heading datenext, merged into main when the window openslatest
minorweekly, when something shippedmainlatest
patch to the current majoras neededmainlatest
patch to the previous majorsecurity fixes only, until 6 months after its successor shippedrelease/<major>.xmaint-<major>
prerelease of the next major — planned, nothing ships one yetrelease-tag.yml cuts only X.Y.Z release commits, so no workflow publishes from nextnextnext (scripts/dist-tag.ts already names it)

v31 is the stable line, dated 2026-11-10 by its CHANGELOG.md heading. It was the exception that starts the clock: it carried every break queued before the promise bound anyone (74, Upgrading), and scripts/release.ts does not refuse it (WINDOW_OPENS_AT_MAJOR in scripts/lib/major-window.ts). From it on, a BREAKING — entry waits on next and a removal ships its deprecation minor first.

Ruling 2026-11-09: the clock moved from v30 to v31. v30 shipped 2026-11-07 while only our own apps ran Ultimate in production, so the window protected no one yet; v31 carries the breaks of plan 101-final-hardening-before-v31, and the window protects adopters from there.

The support window

MajorStatusSecurity fixes until
the current major (npm view @ultimat3/core version)every fixits successor ships, then 6 months more
the previous majorsecurity fixes, as patches on release/<major>.x6 months after the current major's ## X.0.0 date
anything oldernoneUpgrading, 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

BranchHolds
mainminors and patches only while the window is closed; a BREAKING — changelog entry there is refused
nextevery 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>.xthe 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:** \`<id>\`, and the bump commit's body carries Security-Exception: <id>. 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

RuleRefused byCode
one major per 90 daysscripts/release.ts --bump, --check, and the gate's boundaries stepX_RELEASE_MAJOR_WINDOW
no break on main while the window is closedbun run changelog-check and the gate, on the pull request's base branchX_BREAKING_ON_MAIN
a removal was deprecated firstbun run scripts/deprecation-window.ts and the gateX_RELEASE_UNDEPRECATED_REMOVAL
every release from 30.0.0 opens with Highlights, the Release bodybun run changelog-check and the gateX_CHANGELOG_HIGHLIGHTS_MISSING
a maintenance publish never moves latestscripts/dist-tag.ts in release.yml; scripts/registry-audit.ts audits latest and each maint-* tagX_REGISTRY_LATEST_DRIFT

Edit this page on GitHubThis page as markdown