A semver violation is a breaking change shipped in a release whose version number does not signal it — most commonly, a backwards-incompatible API change published as a patch or minor release instead of a major one.
Semantic versioning is a promise: the version number tells you whether an upgrade is safe. MAJOR means something broke; MINOR and PATCH mean it did not. A semver violation breaks that promise — a consumer upgrades on the assurance that nothing changed, and their integration fails anyway. The failure is quiet, because the version said it was safe.
More common than the promise implies. An analysis behind cargo-semver-checks, a linter for the Rust ecosystem, found that roughly one in six of the top 1,000 crates had violated semver at least once. It is not a Rust problem. Documented community lists exist for Node, Rails, Python, TypeScript, and Next.js — large, well-run projects have all shipped breaking changes outside a major release at some point. The pattern is structural: humans set the version number by hand, and a hand-judged number drifts from what actually changed.
Tools already catch violations at build time. cargo-semver-checks compares two crate versions; oasdiff compares two OpenAPI specs; both classify each change as breaking or non-breaking and can fail your CI. That is detection, and it is valuable. But detection is ephemeral: it lives in a CI log that rotates, a comment that gets edited, a memory that fades.
The gap shows up in a dispute. A vendor ships a minor release that breaks your integration. You say it was breaking; they say it was not, or that they warned you. Now it is your changelog against theirs, your recollection against a support rep’s. Detection tells you it happened; a signed receipt proves when — and that nobody rewrote the story afterward. That is the difference between a warning and a record.
A provable semver violation is deterministic at every step. A spec diff finds the breaking change. A version comparison finds the bump class. Breaking change plus a non-major bump equals a violation — no judgment, no model, the same inputs always yield the same answer. That finding is recorded in a canonical change record (an intermediate representation, or IR), and the IR’s hash is covered by an Ed25519 signature with a signed timestamp. The result is a fact you can hand to a skeptic: here is what changed, here is the version it shipped under, here is the signature, verify it yourself.
The 0.x exception is real. Before version 1.0.0, the SemVer specification explicitly permits breaking changes in any release — 0.x is understood to be unstable. A correct detector honors that: pre-1.0, it flags a violation only when there is no version bump at all, never for a normal 0.2.0 → 0.3.0 breaking change. Flagging those would be crying wolf.
It fails quiet. If either version is missing or unparseable, the detector emits nothing. It never guesses, and it never fabricates a violation to look thorough. No evidence is better than invented evidence.
The finding is forensic, not a gate. The semver violation does not change the merge verdict or the risk score. The breaking change itself drives the decision; the semver finding only records the fact that the version number failed to signal it. It is there to be cited later, not to block you now.
And the framing is honest: this is a tamper-evident, dispute-grade fact — it proves the record was not altered after it was signed, and nothing more. What weight that carries in any given argument is for the people in the argument to decide.
The keyless diff endpoint returns a signed verdict for any two specs. The spec below removes a response field in a patch bump (1.2.0 → 1.2.1) — a semver violation. Run it, then change info.version in the after spec to 2.0.0 and re-run: the semver finding disappears and finding_count drops from 2 to 1.
Verified finding — run it yourself
curl -s -X POST https://app.coderifts.com/api/v1/diff \
-H "Content-Type: application/json" \
-d '{"before":"{\"openapi\":\"3.0.0\",\"info\":{\"title\":\"t\",\"version\":\"1.2.0\"},\"paths\":{\"/users\":{\"get\":{\"responses\":{\"200\":{\"description\":\"ok\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"email\":{\"type\":\"string\"}}}}}}}}}}}","after":"{\"openapi\":\"3.0.0\",\"info\":{\"title\":\"t\",\"version\":\"1.2.1\"},\"paths\":{\"/users\":{\"get\":{\"responses\":{\"200\":{\"description\":\"ok\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"}}}}}}}}}}}"}'
| Version bump (2026-07-21) | decision | finding_count | ir_hash |
|---|---|---|---|
| 1.2.0 → 1.2.1 (patch — violation) | BLOCK | 2 | sha256:6c97cb7c…2ac754 |
| 1.2.0 → 2.0.0 (major — control) | BLOCK | 1 | sha256:d2dd659c…f8c1bd |
The extra finding is the synthetic BREAKING_IN_NONMAJOR semver violation, present only on the non-major bump. The signed receipt binds the IR hash, so the violation is covered by the signature. Open-source verifier · Receipt format
Vendor and SLA disputes. When a provider ships a breaking change in a minor release and denies it, a signed receipt from the moment of the diff settles the question without a changelog-archaeology dig.
Dependency-upgrade triage. Before you bump a dependency, a violation finding on its release tells you the version number is lying and the upgrade needs real testing, not a rubber stamp.
CI awareness without a hard block. Because the finding is forensic, you can surface semver violations on every pull request for visibility — a record for later — without failing builds over a version-number judgment call.
A semver violation is a breaking change released under a version number that does not signal it: typically a backwards-incompatible change shipped as a patch or minor version instead of a major one. Consumers upgrade expecting safety and their integration breaks.
Yes. The SemVer specification (item 4) treats anything below 1.0.0 as unstable and permits breaking changes in any 0.x release. A correct detector reflects this: before 1.0.0 it flags a violation only when there is no version bump at all, not for a normal 0.2.0 to 0.3.0 breaking change.
Diff the two API specs to find whether a breaking change occurred, compare the two version numbers to find the bump class, and combine them. A breaking change with a non-major bump (at 1.0.0 and above) is a violation. It is deterministic: the same inputs always produce the same result.
Yes. When the verdict is recorded as a finding whose hash is covered by an Ed25519-signed receipt with a signed timestamp, you have a tamper-evident, independently verifiable record of what changed and when, rather than a changelog you have to trust.
No. The semver finding is forensic: it does not change the merge decision or the risk score. The breaking change itself drives the verdict; the semver finding only records that the version number failed to signal it, so you can cite it later.
Those tools detect violations at build time and are excellent at it: cargo-semver-checks for Rust crates, oasdiff for OpenAPI specs. They work at a different layer, telling you a violation happened in a CI log. Neither produces a signed, timestamped receipt you can use as evidence afterward.
CodeRifts detects semver violations on every pull request and signs the verdict, so a breaking change shipped under the wrong version number becomes a tamper-evident, verifiable fact. See a live demo PR, or read what agent-safe API governance means.