PaceVer — Pace Versioning
Summary
A version number takes the form MARKETING.NATIVE.OTA:
1 . 4 . 12<br>│ │ │<br>│ │ └─ OTA bumped on every over-the-air update (fast)<br>│ └───────── NATIVE bumped on every new store binary (slow)<br>└───────────────── MARKETING yours to define: era, year, redesign, or never
MARKETING : the free number. Bump it for a redesign, a<br>new year, a rebrand, a rewrite, a milestone, or never. It carries no compatibility<br>promise. If you care about signalling the magnitude of a change, this is the only<br>place it lives. 0 is reserved for the pre-release era; ship your first stable<br>release as 1.0.0 and go from there.
NATIVE : bump it when a release needs a<br>new binary installed from a store (anything OTA can't deliver: native<br>code, new dependencies, SDK/runtime upgrades, permission changes). Slow: gated behind<br>store review. Resets OTA to 0.
OTA : bump it for every release pushed<br>over the air to an existing build (JavaScript, styling, content, assets<br>within the current runtime). Fast: lands in minutes, no store review.
A new native build always resets OTA to 0. By default a MARKETING bump resets<br>NATIVE and OTA too, so a redesign goes to 2.0.0; a team may instead let the<br>numbers keep climbing to 2.6.2 (see the FAQ). Pick one convention and stay<br>consistent.
Introduction
Mobile apps don't ship the way libraries do. A library has one release channel and one<br>question that matters: did the contract break? That's what Semantic Versioning<br>answers, and it answers it well.
An app shipped with React Native, Expo, or a similar framework has two<br>release channels moving at two speeds . Some changes (a new native module,<br>an SDK bump, a new permission) can only reach users as a fresh binary through a distribution<br>channel (App Store, Play Store, TestFlight, enterprise/MDM): slow, gated behind<br>redistribution, impossible to roll back instantly. Other changes (a copy fix, a restyled<br>screen, a JS-only feature) can be pushed over the air and land on devices in minutes,<br>independently, with no store review.
SemVer can't see this. Its MAJOR.MINOR.PATCH describes what changed; it<br>says nothing about how the change reaches the user, which on mobile is the fact<br>that governs review latency, rollout speed, and rollback. PaceVer makes that fact the<br>version.
PaceVer deliberately says nothing about the size of a change. A one-word fix and a screen<br>rewrite both bump OTA by one, because they ship the same way, at the same pace. If your team<br>wants to signal that a release is a big deal, that signal goes in MARKETING, which is yours<br>to spend however you like.
PaceVer Specification
The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" are to be interpreted as<br>described in RFC 2119.
A project using PaceVer MUST distinguish two kinds of releases:<br>native releases , which require a new binary to be installed through a<br>distribution channel (App Store, Play Store, TestFlight, enterprise/MDM, or equivalent),<br>and OTA releases , which are delivered to an already-installed binary<br>without redistribution.
A normal version number MUST take the form X.Y.Z, where X<br>(MARKETING), Y (NATIVE), and Z (OTA) are non-negative integers<br>and MUST NOT contain leading zeroes. Each successive version MUST increase in precedence,<br>as defined in rule 10. Individual elements need not increase one by one: incrementing an<br>element resets the elements to its right (rule 6), so the version as a whole always orders<br>forward even though a reset element drops to 0.
MARKETING (X) is reserved at 0<br>to mean the app is pre-release, not yet considered stable or shipped to the public. Once<br>MARKETING reaches 1 or higher, the reason and magnitude of<br>an increment carry no required meaning and no compatibility promise: a team MAY increment<br>it for any reason (a new year, a redesign, a rebrand, a rewrite, a milestone, the magnitude<br>of a change) or never increment it again. Its one fixed constraint is direction: MARKETING<br>MUST be monotonically non-decreasing over the life of the project.
NATIVE (Y) MUST be incremented when a release<br>requires a new binary to be installed, that is, any change that cannot be delivered over<br>the air. This includes, but is not limited to: native code or native dependency changes,<br>runtime or SDK upgrades, permission or entitlement changes, and changes to compiled<br>application configuration.
OTA (Z) MUST be incremented for each release<br>delivered over the air to an existing native build, that is, any change deliverable without<br>a new binary, such as JavaScript, styling, content, and assets running within the current<br>native runtime.
When NATIVE is incremented, OTA MUST be reset to 0, because a new native build<br>begins a fresh OTA lineage. When MARKETING is incremented, NATIVE and OTA SHOULD be reset<br>to 0; this is the recommended default. A project MAY instead let NATIVE and OTA<br>keep climbing across a MARKETING bump (see the FAQ), but it MUST pick one convention and<br>apply it consistently.
A freshly installed native build, before any OTA...