惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
SegmentFault 最新的问题
A
About on SuperTechFans
NISL@THU
NISL@THU
V
Visual Studio Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog RSS Feed
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
美团技术团队
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Google DeepMind News
Google DeepMind News
小众软件
小众软件
博客园 - Franky
罗磊的独立博客
The Cloudflare Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
酷 壳 – CoolShell
酷 壳 – CoolShell
量子位
Hugging Face - Blog
Hugging Face - Blog
云风的 BLOG
云风的 BLOG
MongoDB | Blog
MongoDB | Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
B
Blog
The GitHub Blog
The GitHub Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Proofpoint News Feed
有赞技术团队
有赞技术团队
Hacker News - Newest:
Hacker News - Newest: "LLM"
Cyberwarzone
Cyberwarzone
C
CXSECURITY Database RSS Feed - CXSecurity.com
Project Zero
Project Zero
Security Latest
Security Latest
L
Lohrmann on Cybersecurity
AWS News Blog
AWS News Blog
The Hacker News
The Hacker News
I
Intezer
J
Java Code Geeks
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Privacy International News Feed
月光博客
月光博客
A
Arctic Wolf
Apple Machine Learning Research
Apple Machine Learning Research
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园_首页
WordPress大学
WordPress大学
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Tor Project blog
博客园 - 三生石上(FineUI控件)
The Last Watchdog
The Last Watchdog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org

Lobsters

CIFSwitch: a non-universal Linux local root vulnerability RIPE NCC session fixation: poaching logins with an Atlas probe GNOME 2.20 but its Web Components Agentic Search for Context Engineering – Leonie Monigatti Garnix is shutting down [not OC] akashina.tngl.sh/jjc Concerning Emacs (and Jazz) Nitpicking the shell history scene in ‘Tron: Legacy’ What's cooking on SourceHut? Q2 2026 The tenth OpenPGP email summit Package managers that package package managers Clojure on Fennel part three: parsing WordPress at 23 Finding Miscompiles for Fun, Not Profit GitHub - creusot-rs/creusot: Creusot helps you prove your Rust code is correct. Announcing Rust 1.96.0 | Rust Blog A Love Letter to Neovim sqlite AGENTS.md Am I a Bad Friend? CSS vs. JavaScript • Josh W. Comeau Erlang Ecosystem Foundation - Supporting the BEAM community A brief note about slot access cost in Common Lisp Keyboard latency probe Rethinking the GNOME clipboard issues Back to the Building Blocks’ Building Blocks Tech Notes: Theseus: translating win32 to wasm Fast is better than slow Content-addressed Rust builds (or, what kache actually caches) Intent to Prototype: Embedding API Canada’s Bill C-22 and the security cost of collecting more data 5 PostgreSQL locking behaviors that trip people up okmij.org Stop advertising in your commits! | AksDev GitHub - mplsllc/macsurf: A modern web browser for Classic Mac OS 9 PowerPC. Real CSS3, ES5 JavaScript, native HTTPS — built with CodeWarrior on the Carbon API. Introducing DoomBench - Can Your Data Stack Run DOOM? What are some of your favourite developer tools? Building a Scalable Ingestion Pipeline with Temporal (Part 1) Converting shallow Git bundles into normal repositories Are you a member of any professional associations? What is a harmonic? An interactive comic about additive synthesis How Virtual Tables Work in the Itanium C++ ABI Using SwiftUI to Build a Mac-assed App in 2026 Rust (and Slint) on a jailbroken Kindle. ~jack/lambda-on-lambda - Serverless Haskell on AWS - sourcehut git Human proof for FOSS contributions Extremely simple internet radio controlled via IRC Announcing BABLR Splitting Konsole views from Helix to run tools | AksDev GitHub - yugr/rust-slides Serving files over HTTP three ways: synchronous, epoll, and io_uring update docs with information about building with build.py (#979) · astral-sh/python-build-standalone@c9c40c5 A Simple Makefile Tutorial On C extensions, portability, and alternative compilers Switching to Colemak | Pedro Alves Just How Bad Was The Intel IAPX432? Nix's Substituter List Is Not a Routing Table Accelerating copy_if using SIMD Lambda on Lambda: Serverless Haskell on AWS | Blog Announcing feed-repeat v1.0 Scaling Akvorado BMP RIB with sharding EYG news: A host of CLI improvements, new guides and new effects The social contract of writing JS Crossword C array types are weird; and related topics Flatpak will depend on systemd – OSnews Migrating from Go to Rust | corrode Rust Consulting A portentous reunion Vivado Licensing Options How my minimal, memory-safe Go rsync steers clear of vulnerabilities the entropy layer of a wavelet codec, on its own GitHub - nferhat/fht-compositor: A dynamic tiling Wayland compositor. Debian SE Linux and PinTheft Does bulk memmove speed up std::remove_if? (No.) 声明式部分更新 | Blog | Chrome for Developers Fully in-browser container builds Dianne Skoll's Web Site - Remind The Architecture of Open Source Applications (Volume 1)Berkeley DB Pardon MIE? - ironPeak Blog “Long-Term Support” doesn’t mean what you think Jira IS Turing-Complete May I recommend thinking of Emacs as your Fortress of Solitude hershey Floodgap Gopher-HTTP gateway gopher://thelambdalab.xyz/1cuneiforth/ HP QuickWeb, Singular And Pointless That one time I used Go panics for flow control A new suite of modern tools coming for editing and publishing RFCs From the Tabletop… The Digital Antiquarian Building a Host-Tuned GCC to Make GCC Compile Faster Are we self-sovereign PKI yet? Claw Patrol: an open-source security firewall for agents | Deno Revised^7 Report on Scheme, Large: Procedural Fascicle Draft is now public A Network Allow-List Won't Stop Exfiltration — André Graf From AFSK to Goertzel – µArt.cz Software For My New Home Server Introducing Neptune: Direct3D virtualization for QEMU AI Agent Bankrupted Their Operator While Trying to Scan DN42 - Lan Tian @ Blog mimalloc: A new, high-performance, scalable memory allocator for the modern era Making wl_shm fast The Soul of Maintaining a New Machine - Third Draft | Books in Progress What is Git made of?
PaceVer — Pace Versioning
pacever.org · 2026-06-03 · via Lobsters

Summary

A version number takes the form MARKETING.NATIVE.OTA:

  1   .   4   .   12
  │       │       │
  │       │       └─ OTA        bumped on every over-the-air update   (fast)
  │       └───────── NATIVE     bumped on every new store binary      (slow)
  └───────────────── MARKETING  yours to define: era, year, redesign, or never
  • MARKETING: the free number. Bump it for a redesign, a new year, a rebrand, a rewrite, a milestone, or never. It carries no compatibility promise. If you care about signalling the magnitude of a change, this is the only place it lives. 0 is reserved for the pre-release era; ship your first stable release as 1.0.0 and go from there.
  • NATIVE: bump it when a release needs a new binary installed from a store (anything OTA can't deliver: native code, new dependencies, SDK/runtime upgrades, permission changes). Slow: gated behind store review. Resets OTA to 0.
  • OTA: bump it for every release pushed over the air to an existing build (JavaScript, styling, content, assets 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 NATIVE and OTA too, so a redesign goes to 2.0.0; a team may instead let the numbers keep climbing to 2.6.2 (see the FAQ). Pick one convention and stay consistent.

Introduction

Mobile apps don't ship the way libraries do. A library has one release channel and one question that matters: did the contract break? That's what Semantic Versioning answers, and it answers it well.

An app shipped with React Native, Expo, or a similar framework has two release channels moving at two speeds. Some changes (a new native module, an SDK bump, a new permission) can only reach users as a fresh binary through a distribution channel (App Store, Play Store, TestFlight, enterprise/MDM): slow, gated behind redistribution, impossible to roll back instantly. Other changes (a copy fix, a restyled screen, a JS-only feature) can be pushed over the air and land on devices in minutes, independently, with no store review.

SemVer can't see this. Its MAJOR.MINOR.PATCH describes what changed; it says nothing about how the change reaches the user, which on mobile is the fact that governs review latency, rollout speed, and rollback. PaceVer makes that fact the version.

PaceVer deliberately says nothing about the size of a change. A one-word fix and a screen rewrite both bump OTA by one, because they ship the same way, at the same pace. If your team wants to signal that a release is a big deal, that signal goes in MARKETING, which is yours to spend however you like.

PaceVer Specification

The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" are to be interpreted as described in RFC 2119.

  1. A project using PaceVer MUST distinguish two kinds of releases: native releases, which require a new binary to be installed through a distribution channel (App Store, Play Store, TestFlight, enterprise/MDM, or equivalent), and OTA releases, which are delivered to an already-installed binary without redistribution.
  2. A normal version number MUST take the form X.Y.Z, where X (MARKETING), Y (NATIVE), and Z (OTA) are non-negative integers and MUST NOT contain leading zeroes. Each successive version MUST increase in precedence, as defined in rule 10. Individual elements need not increase one by one: incrementing an element resets the elements to its right (rule 6), so the version as a whole always orders forward even though a reset element drops to 0.
  3. MARKETING (X) is reserved at 0 to mean the app is pre-release, not yet considered stable or shipped to the public. Once MARKETING reaches 1 or higher, the reason and magnitude of an increment carry no required meaning and no compatibility promise: a team MAY increment it for any reason (a new year, a redesign, a rebrand, a rewrite, a milestone, the magnitude of a change) or never increment it again. Its one fixed constraint is direction: MARKETING MUST be monotonically non-decreasing over the life of the project.
  4. NATIVE (Y) MUST be incremented when a release requires a new binary to be installed, that is, any change that cannot be delivered over the air. This includes, but is not limited to: native code or native dependency changes, runtime or SDK upgrades, permission or entitlement changes, and changes to compiled application configuration.
  5. OTA (Z) MUST be incremented for each release delivered over the air to an existing native build, that is, any change deliverable without a new binary, such as JavaScript, styling, content, and assets running within the current native runtime.
  6. When NATIVE is incremented, OTA MUST be reset to 0, because a new native build begins a fresh OTA lineage. When MARKETING is incremented, NATIVE and OTA SHOULD be reset to 0; this is the recommended default. A project MAY instead let NATIVE and OTA keep climbing across a MARKETING bump (see the FAQ), but it MUST pick one convention and apply it consistently.
  7. A freshly installed native build, before any OTA release has been applied, is at OTA 0. The JavaScript and assets bundled inside a native build are its baseline, not an OTA release, and MUST NOT be counted as OTA 1.
  8. The version a build reports SHOULD be composed at runtime: MARKETING and NATIVE are fixed by the installed binary; OTA reflects the over-the-air release currently applied, or 0 if none has been applied.
  9. An OTA release MUST be compatible with the native build it targets, and MUST NOT be delivered to a native build it was not produced for. (In Expo/EAS terms, OTA releases are scoped to a matching runtime version.)
  10. Precedence is determined by comparing MARKETING, then NATIVE, then OTA, numerically (1.2.0 < 1.2.7 < 1.3.0 < 2.0.0).
  11. One version line, all platforms in lockstep. MARKETING, NATIVE, and OTA are single project-wide numbers, not tracked per platform. When NATIVE is incremented, a new binary MUST be released to every native distribution channel the app ships through (App Store, Play Store, and so on), even for a platform with no functional change, so that all platforms share the same NATIVE build. This keeps every platform on a common runtime, so a single OTA release applies to all of them under one OTA number. Platforms therefore never diverge: there is always exactly one current version for the whole app.
  12. Scope. PaceVer is for user-facing application clients that ship through a native channel and an OTA channel. It is NOT for libraries, packages, or APIs that expose a programmatic contract to other software. Version those with SemVer.
  13. Identifiers MAY be appended after OTA: a hyphen introduces pre-release identifiers (e.g. 1.4.0-rc.1) and a plus sign introduces build metadata (e.g. 1.4.0+ios). PaceVer assigns these suffixes no channel or pace meaning; their interpretation is left to the team. Build metadata MUST be ignored when determining precedence.
  14. Initial version and going stable. A project SHOULD begin at 0.1.0, where MARKETING 0 marks the pre-release era. NATIVE and OTA increment as usual through the 0.x era. When the team considers the app stable and ready for the public, it increments MARKETING to 1. With the recommended reset (rule 6) the first stable release is 1.0.0; a team that does not reset carries its running numbers instead, so 0.5.3 becomes 1.5.3. The exact moment of going stable is the team's call.

Why use PaceVer

  • The number tells you the pace. 2.5.0 → 2.5.3 shipped over the air and is already on devices. 2.5.3 → 2.6.0 is a new binary waiting on store review. You know the deployment story at a glance.
  • It fits store rules for free. The binary carries MARKETING.NATIVE.0 as its marketing version (the three integers the stores expect, with OTA pinned at 0 as the baseline); the live OTA number is composed on top at runtime.
  • It stays SemVer-compatible in form. A PaceVer version is still an X.Y.Z string ordered by the same left-to-right precedence as SemVer, so tooling that sorts or compares SemVer-shaped versions keeps working. PaceVer changes what the three positions mean, not the format or the ordering.
  • It ends the MAJOR argument. No more debating whether a redesign "deserves" a major bump. MARKETING is a free number you point at whatever your team cares about.
  • It matches the tooling you already use. NATIVE lines up with your binary / runtime version; OTA lines up with your update channel (EAS Update, CodePush).

When NOT to use PaceVer

PaceVer is for user-facing application clients that ship through a native channel and an OTA channel (rule 12). It is the wrong tool when:

  • You are versioning a library, package, or API. Anything that exposes a programmatic contract to other software should use SemVer, whose whole job is to signal compatibility. PaceVer says nothing about compatibility.
  • Your app has effectively one delivery channel. A web app or PWA where every deploy goes out the same way has no native/OTA split for PaceVer to capture. Version it on its own terms.
  • Your audience keys off recency or magnitude. If readers of your version number primarily want "how recent" (CalVer) or "how big" (SemVer MAJOR) rather than "which channel," PaceVer puts the signal they want in the wrong place.

Adopting PaceVer means giving up SemVer's compatibility contract and CalVer's at-a-glance recency in exchange for an at-a-glance pace signal. Make that trade on purpose.

Example: one app's version history

Version Channel What shipped
0.1.0NATIVEFirst build. The pre-release era begins.
0.1.1OTABeta copy tweak, pushed over the air.
0.2.0NATIVEAdded a camera module, new binary to testers, OTA resets.
1.0.0NATIVEFirst public release on the App Store and Play Store. MARKETING → 1, everything resets.
1.0.1OTATypo fix, on devices in minutes.
1.0.2OTAJavaScript crash fix.
1.1.0NATIVEExpo SDK upgrade, new binary to both stores, OTA resets.
1.1.1OTARestyled a screen.
2.0.0NATIVEA full seasonal redesign the team wants to flag. MARKETING bump, new binary to both stores, everything resets.

Every row tells you how it shipped from the number alone: a middle-digit change means a trip through the store; a last-digit change means it was already on devices in minutes.

FAQ

Where does the OTA number physically live?

Not in the binary's store version. An OTA update can't rewrite that, and that's fine. The app composes its version at runtime: it reads MARKETING and NATIVE from the binary and OTA from the applied update's manifest (e.g. Updates.manifest / extra in Expo). A fresh install with no update applied reports OTA 0.

Does a higher OTA number mean a bigger change?

No. OTA counts over-the-air releases, not their size. A typo fix and a new screen both bump OTA by one. If you care about magnitude, spend the MARKETING number on it.

Does bumping MARKETING reset NATIVE and OTA to zero?

By default, yes. A MARKETING bump goes to X.0.0, exactly as a NATIVE bump resets OTA, so a redesign from 1.6.2 lands on 2.0.0. This is the recommended convention, but resetting on a MARKETING bump is not mandatory; see the next question.

Can I skip the reset and let the numbers keep climbing?

Yes. Instead of resetting, you may let MARKETING float as a pure label while NATIVE and OTA keep climbing, so 1.6.2 becomes 2.6.2 rather than 2.0.0. The tradeoff: you give up the clean X.0.0 era boundary, but you keep a single monotonic native and OTA count across the whole life of the app, which some teams find easier to reason about. Precedence still works either way, since versions compare left to right. Whichever you pick, apply it consistently. (A new NATIVE build always resets OTA regardless; that part is not optional, because a new binary starts a fresh OTA lineage.)

Can I bump MARKETING or NATIVE with an over-the-air update?

No. MARKETING and NATIVE live in the installed binary (rule 8), so changing either one means shipping a new native build. Only OTA moves without a new binary, which is why a MARKETING or NATIVE change always lands with OTA back at 0.

Can I roll back a native release?

No, and that's by design: like the app stores themselves, PaceVer has no concept of a native rollback. NATIVE only ever moves forward (rule 2's precedence), and an OTA release is scoped to the native build it was produced for (rule 9), so you can't drop users back onto a previous binary's OTA lineage. To recover from a bad native build you re-release the previous good build's content forward under a new NATIVE number, for example shipping what was NATIVE 5 again as NATIVE 7, and go from there. OTA, by contrast, can be rolled back within the same native build, as fast as it ships.

A native change only affects iOS. Do I have to release Android too?

Yes, and on purpose. A NATIVE bump ships a new binary to every platform, even one with no functional change, so all platforms stay on the same NATIVE build. This is for OTA's sake: OTA releases are scoped to a runtime, so keeping every platform on a common runtime lets one OTA release land everywhere under a single OTA number. The seemingly redundant Android build usually isn't empty anyway: bumping NATIVE bumps the runtime, so it's a real new binary, just without feature changes. The cost is one extra store submission; the payoff is one coherent version line for the whole app.

My app is also on the web. There's no native build there.

PaceVer doesn't cover web. Web (and PWAs) has effectively one channel (every deploy is over the air), so the native/OTA split doesn't apply. Version web on its own terms.

Can I use PaceVer for my library or backend API?

No. Use SemVer. PaceVer says nothing about API compatibility, which is the whole point of versioning a library. PaceVer is for shipping apps, not contracts.

Why isn't PaceVer versioned with PaceVer?

Because PaceVer is for apps, and this is a document. A spec has no native build and no over-the-air channel. It has a contract that other people rely on, which is exactly what the Scope rule says to version with SemVer. So PaceVer eats its own rule and ships as SemVer. SemVer versions itself; PaceVer versions itself with SemVer. Each tool to its job.

What about the very first release?

Start at 0.1.0. MARKETING 0 is the pre-release era, and you build through 0.x until the app is ready. When it's ready for the public, bump MARKETING to 1. With the recommended reset that lands you on 1.0.0; if you don't reset, you carry your running numbers to 1.y.z. The recommended path is simply 0.1.0 → 1.0.0 → onward. The exact moment doesn't really matter; it's a flag you raise when it feels true.

I already ship under SemVer. How do I migrate?

Don't reset downward. The App Store and Play Store reject a marketing version that decreases, so an app already at, say, 3.4.1 cannot adopt PaceVer by starting over at 1.0.0. Because MARKETING is a free, monotonically non-decreasing number, keep your current leading number as MARKETING and start applying PaceVer's NATIVE and OTA semantics from where you are. The 0.1.0 start and the reset rules are for new projects; an existing app carries its history forward.

About

PaceVer was created in 2026 by Pavlos Vinieratos.

This specification is itself versioned with SemVer, not PaceVer. A spec is a document with a contract people rely on, not an app shipping through native and OTA channels, so by PaceVer's own Scope rule (rule 12) it uses the right tool for the job.

Current version: 1.0.0 (SemVer).

License

PaceVer, the Pace Versioning specification, is licensed under a Creative Commons Attribution 4.0 International License (CC BY 4.0). You are free to share and adapt it, including commercially, as long as you give appropriate credit.

Copyright © 2026 Pavlos Vinieratos.