A backwards-compatibility paradigm that actually solves the problem of API breakage
Given a version number MAJOR.MINOR.PATCH, increment the:
In the world of software dependency management, there exists a special place of suffering. It's the place where your perfectly functional system breaks because someone, somewhere, decided their v2.0.0 was too important to maintain backwards compatibility. We call this place "dependency hell," but as Sartre might have observed: (Dependency) Hell is Other People.
The Semantic Versioning specification was created to bring sanity to this chaos. It promised a world where version numbers would telegraph safety and danger. But it got things exactly backwards.
Solecistic Versioning (SolVer) takes a different approach: What if we developed, updated, and versioned software based on how it actually affects users, not how we wish they'd adapt to our changes?
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
This isn't about being clever or contrarian. Look at the software that actually works at scale:
These projects understand a fundamental truth: Your users' code is more important than your code.
In the YouTube era, we've learned about the "Pin of Shame"—that mark of condemnation when a comment violates the community's standards. As library maintainers, we need to recognize that every version pin in a dependency file represents a similar vote of no confidence.
When a user writes "your-library": "^2.3.4" instead of "your-library": "*", they're telling you:
"I don't trust you not to break my code."
But this isn't just about individual maintainers. Users have been trained, through bitter experience across the entire ecosystem, that updates mean breakage. Each pin is a small monument to every breaking change that has been forced upon them, every hour lost debugging mysterious failures after routine updates. It's a learned defensive behavior in an ecosystem where compatibility is the exception rather than the rule.
SolVer aims for a world without pins, where "solecistic-library": "*" is not an act of reckless optimism but reasonable trust. The Pinless World is a painless world.
Version pinning isn't just a symptom of poor compatibility practices—it's a security vulnerability. Every pinned dependency is a potential unpatched security hole waiting to be exploited.
Yes, sometimes updates introduce new problems. A security fix might have its own vulnerability, a bug fix might cause a regression. When this happens, users will pin versions—and we'll deserve that Pin of Shame. No versioning system can prevent human error.
But here's why SolVer's vision matters: when pins are the exception rather than the rule, they become manageable. In a world where most dependencies can float freely, the few necessary pins stand out. They're documented, they're temporary, and they're removed as soon as fixes arrive. Security updates flow through the ecosystem by default, reaching everyone on the next npm install, cargo update, or go get -u.
The irony remains sharp: the very pins we add for "safety" become unsafety pins, keeping out the security updates we desperately need. The solution isn't perfect pinning—it's building an ecosystem where pinning is rarely necessary.
Mature libraries need to innovate. Limiting experimentation to just the 0.x.y version range is impractical—well-established projects must still explore new ideas and gather community feedback.
By their nature, experimental features can't guarantee compatibility, so they technically fall outside SolVer's scope. However, there are proven patterns for responsible experimentation:
your-library-experimental can break all it wants without affecting your-libraryenableExperimentalAPI: true make dependencies clearRemember: your users will eventually depend on "unstable" features just as much as stable ones. If you're gathering insights from the community (which of course you are, right?), be transparent about the trial process. Provide a roadmap showing which experiments are approaching stability versus those headed for removal.
The goal is simple: let your stable API remain stable while still leaving room to innovate.
While eternal backwards compatibility is the ideal, the real world sometimes demands change. The masters of stability show us how it's done:
Microsoft Windows can still run many executables from the 1990s. Raymond Chen's blog "The Old New Thing" documents countless stories of maintaining compatibility, including keeping bugs that applications depend on. When Windows can't maintain perfect compatibility, it offers compatibility modes and shims.
The Linux Kernel officially promises to maintain support for any hardware or feature that still has active users. Linus Torvalds famously said, "We do not break userspace!" Only completely unused features may eventually stop working.
The Java Platform treats backward compatibility as sacred. APIs deprecated in Java 1.1 still function today. When Java must evolve (like introducing the module system or restricting reflection), it provides multi-year migration paths with specific compatibility flags and detailed migration guides.
GNU Emacs does deprecate features, but follows a deprecation cycle that can take years or even decades:
When introducing deprecation warnings, remember that some users treat warnings as errors in their CI pipelines. Provide mechanisms to control warning visibility, such as environment variables or configuration flags. The warning itself should mention how to suppress it, giving users an immediate workaround while they plan their migration.
In extreme cases, some APIs must be removed due to security concerns. This is essentially the only valid reason to break compatibility with actively used features. Even then, strive to:
The beauty of maintaining compatibility is that version numbers become merely informational. As long as the latest version is always safe to use, you can adopt any versioning scheme that allows package managers to determine what's newest:
YYYY.MM.DD — Every release is just a date1, 2, 3... — Pure simplicityaardvark, bison, caribou... — Ubuntu used these to great effectalpha, bravo, charlie... — The NATO phonetic alphabet never goes out of styleThe version string doesn't matter when every version is compatible.
The beautiful thing about SolVer? It sorts lexically after SemVer. So when you get tired of your v7→v8 migration fragmenting your user community yet again, you can safely switch. Your users will thank you.
A: No. SemVer is a formalized system for announcing when you're going to break your users' code. SolVer is a philosophy that says you shouldn't break their code. SemVer tells you how to communicate breakage; SolVer asks why you're breaking things at all. Three-number version strings existed long before SemVer—what matters is the commitment they represent, and SolVer represents the commitment to put users first.
A: Bug fixes are, by definition, behavior changes. If your code worked around a bug, fixing the bug could break your code. If you inadvertently relied on broken behavior, fixing that bug will definitely break your code. SemVer actually calls patch versions "backward compatible bug fixes," but this is an oxymoron—there's no such thing as a behavior change that's guaranteed compatible. SolVer just admits this reality.
A: Even deprecation warnings should be introduced thoughtfully. Provide flags to control warning visibility (e.g., --no-deprecation-warnings) and mention these flags in the warning messages themselves. This gives pipeline maintainers a quick fix while still nudging them toward addressing deprecations. Remember: even with such affordances, deprecation should remain a rare last resort.
A: See "The Rare Art of Deprecation" above. The bytes you save are worth less than the trust you lose.
A: Use 0.x.y versions for experimentation. Once you hit 1.0.0, you're making a commitment. For mature projects, see "Experimental Features" above.
A: Documentation, compile-time warnings, runtime warnings, migration guides. Give your users every tool except breakage.
A: Then your new API is a new feature, not a replacement. Ship both. Let users migrate when they're ready, not when you are.
A: There's no one-size-fits-all approach, but successful strategies include: maintaining a comprehensive test suite that covers real usage patterns; using feature flags or optional parameters for new functionality; and designing APIs with extension points from the start.
In extreme cases where truly incompatible paradigms are needed, consider the Basecamp approach: when version 3 launched, they committed to maintaining Basecamp Classic and Basecamp 2 "until the end of the internet"—a promise they're still keeping nearly a decade later. Sometimes the answer is to treat your new version as a new product.
The Solecistic Versioning specification is authored by Ohad Livne, who has spent too many hours fixing broken builds and applications after innocuous-looking updates.
If you'd like to contribute, please open an issue or PR.