Skip to content

Conversation

ljharb
Copy link
Contributor

@ljharb ljharb commented Mar 9, 2023

Per #915 (comment)

Fixes #915. Closes #916. Closes #127.

Additionally, I notice the document often, but not consistently, hard-wraps at 80 chars. While I find soft-wrapping immeasurably superior for diff clarity and editability, consistency is also important, so I'd be happy to make a separate PR that updates the entire document to consistently soft-wrap or hard-wrap.

@steveklabnik
Copy link
Member

I'd be happy to make a separate PR that updates the entire document to consistently soft-wrap or hard-wrap.

I am unsure how I feel about this personally, we can sort it at some point.

Copy link
Member

@steveklabnik steveklabnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for this.

I am unsure how I feel about it right this moment, but I deeply appreciate starting to tackle this. You took a different approach than maybe I was thinking of, I am unsure if that's good or bad! I am unlikely to be able to devote significant time on this until next week.

@@ -1,4 +1,4 @@
Semantic Versioning 2.0.0
Semantic Versioning 2.1.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could argue this means 3.0.0. I dunno. Something to think about. Semver has never really been super consistent with versioning itself. Maybe it's a good time to start that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i figured we'd iterate on this line :-) you could argue this is technically loosening constraints on v0, or increasing them. Making it v3 would certainly be a safer approach.

@ljharb
Copy link
Contributor Author

ljharb commented Mar 9, 2023

I'd love to hear more about what approach you were thinking of. No rush; happy to talk about it next week. I'm also available for a video call if that's easier for you.

@jwdonahue
Copy link
Contributor

jwdonahue commented Mar 9, 2023

This is definitely a breaking change. Your favorite packaging tools are not the only bits that depend on this spec. If you are going to ensconce just one convention that was traditionally an allowed overlay on top of the spec, I think you should use mine <grin>.

You seem to be trying to say that 0.y.z is no longer a prerelease form on its own, which was not the original intent of versions 1 & 2 of the spec. Version 2 of the spec and a reading of the FAQ indicate that the 0.y.z form precedes the 1.y.z initial release form. I am sure there are many explanations of this in semver/semver issues and probably StackOverflow showing typical version histories of the form:

0.1.0 // implied prerelease as the API is not defined until 1.0.0.
0.1.1 // implied prerelease as anything might break.
0.2.0 // same here.
1.0.0 // First release.

As far as I can recall nobody ever raised any objections to this in the past. Nothing about the original wording precludes you from adopting your proposed workflow oriented overlays, but no sane external consumer of your product would expect a 0.1.0 version to be complete, build or runtime safe.

@ljharb
Copy link
Contributor Author

ljharb commented Mar 10, 2023

That's correct, and has always been the case, since you can have a prerelease of a v0. v0 versions are and have always been full releases.

@jwdonahue
Copy link
Contributor

jwdonahue commented Mar 10, 2023

That's correct, and has always been the case, since you can have a prerelease of a v0. v0 versions are and have always been full releases.

Which is it? A prerelease or a full release? I do agree that you can also add a -pr tag as well, but that doesn't change the fact that a 0.x.y without the -pr tag is in fact a prerelease.

According to the v2 spec:

  1. Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.

Sounds like a prerelease to me. Initial develop precedes release, so clearly this is one form of a prerelease version. Notice there is no 0.x.y in this spec, the author(s) clearly intended 0.minor.patch. The mere presence of the 0 in the major field literally means any publication can contain breaking changes so the same rules associated with the pr tag apply.

But then we get 5 (emphasis is mine):

  1. Version 1.0.0 defines the public API. The way in which the version number is incremented after this release is dependent on this public API and how it changes.

So now we have the definition for a release version which follows the initial pre-release definition. Yet another indication that 0.x.y really is something that comes before the first release, clearly making it a prerelease. Hence this proposed change is breaking and I see no language therein which provides a means for implementors to determine which semantics apply to a particular version string.

Then we eventually get to 9:

  1. A pre-release version MAY be denoted by appending a hyphen and a series of dot separated identifiers immediately following the patch version. ... A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version.

This is the second definition of a "prerelease" version, and it allows us to go back into development mode without having to drop back to the 0.x.y series. I should point out that having a dev branch perpetually churning out 0.x.y prereleases that land in a series of release branches:

1.0.0 // After 0.7.23 was tested.
1.1.0 // After 0.8.0 was tested.
2.0.0 // Oh maybe dev's are as far as 333.9999 by now.

Isn't prohibited by the spec. While this may not be sound, there's many other potential permutations on this for organizations that do lots of pre-flighting of prereleases in various A/B scenarios. Fixing that loop-hole is technically a breaking change, but unlikely to have widespread impact, especially among the current set of package types represented by the maintainers of SemVer. I've certainly never seen anybody doing this, but there must be tens of thousands of bespoke and ad-hoc implementations in the world today.

I think the spec should stick to the minimum required to define a common syntax and semantics, and leave the tool makers and workflow designers with as much freedom to innovate as possible.

@ljharb
Copy link
Contributor Author

ljharb commented Mar 10, 2023

A prerelease is only something with the prerelease suffix - it has no other requirements. It may sound like a prerelease, but it’s still not one. That the things that a prerelease indicates may also apply elsewhere doesn’t mean it’s a two-way implication.

@jwdonahue
Copy link
Contributor

Looks like a duck, sounds like a duck, it's a duck.

@ljharb
Copy link
Contributor Author

ljharb commented Mar 10, 2023

Unfortunately, the map remains not the territory.

@jwdonahue
Copy link
Contributor

Unfortunately, the map remains not the territory.

Ya I was just trying to find Preston's original repo. I remember reading through all of the issues before he handed it off to Phil and they moved the official site here.

@ljharb
Copy link
Contributor Author

ljharb commented Apr 4, 2023

@steveklabnik gentle ping, since it's been a few weeks :-)

@steveklabnik
Copy link
Member

I did not manage to get to this this weekend but I do care about it, my intention is to merge something semantically the same as this, or maybe possibly this, but I need some more time to get my full thoughts down but also didn't want to continue radio silence :)

@ljharb
Copy link
Contributor Author

ljharb commented May 15, 2023

Thanks for the update :-) I'd be more than happy to make any changes whenever you get to describing them.

@ljharb
Copy link
Contributor Author

ljharb commented Jun 1, 2023

@steveklabnik another gentle ping, if you want to take some time away from governance drama <3

Copy link
Member

@steveklabnik steveklabnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, once again, for taking forever. Sadly the governance drama doesn't let me get distracted with other work, it just robs me of the desire to do anything :(

Okay. After sitting on this for a while, and even passing it by @indirect a while back, I am comfortable with the way that you changed this.

The only real question to me is the "semver of the spec itself" problem. Which is its own can of worms. I am not sure the proper way to move forward on that, but I also don't want to block this on solving a bigger issue. Do you have any thoughts there?

@ljharb
Copy link
Contributor Author

ljharb commented Jul 13, 2023

Arguably this is a patch in that it loosens the semantics of v0 versions from “always breaking” to “only sometimes breaking”. You could also interpret it as major because it applies semantics that weren’t there before.

Are there any important ecosystems that don’t follow this already, where this change would break them?

@steveklabnik
Copy link
Member

Are there any important ecosystems that don’t follow this already, where this change would break them?

As far as I know, no, it is purely a "bring the spec in line with practice" thing. but i wish we had better test coverage here.

So I think this implies we should do it.

@FichteFoll
Copy link

FichteFoll commented Jul 13, 2023

It may be reflective of what is used in practice, but semver isn't about "how an API is used". It's about "how an API is defined" and if we consider the rules that semver defines to be the API, then this is definitely a breaking change that requires a major bump.

Example:

With semver 2.0.0, you may include breaking changes in the update from 0.1.0 to 0.1.1. With this change, that would be illegal as any breaking change for a 0.X.y (X > 0) MUST increment X.

compatible functionality is introduced to the public API. It MUST be
incremented if any public API functionality is marked as deprecated. It MAY be
incremented if substantial new functionality or improvements are introduced
within the private code. It MAY include patch level changes. Patch version
MUST be reset to 0 when minor version is incremented.

1. Major version X (X.y.z | X > 0) MUST be incremented if any backward
1. Major version X (0.0.X | 0.X.y | X.y.z) MUST be incremented if any backward
Copy link

@FichteFoll FichteFoll Jul 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to prevent overlaps between 0.X.y and 0.0.X, a requirement for X > 0 should be added here as well. Note that this would not cover 0.0.0, which … doesn't really fit into any bucket here anyway. Maybe it should be disallowed?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this would not cover 0.0.0, which … doesn't really fit into any bucket here anyway. Maybe it should be disallowed?

In practice I think I've only seen this used for reserving a name in a registry, not for any actual release. In other words 0.0.0 releases typically don't even have an API yet to version. So I guess the general usage already assumes that 0.0.0 is invalid?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why 0.0.0 would be invalid, it's a valid starting point afaict.

Copy link
Contributor

@jwdonahue jwdonahue Aug 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than it being far more likely, that an existing feature could be removed or substantially broken without notice in a 0.y.z publication, I have never treated them any differently than any other prerelease.

I do find it useful that 0.y.z preserves the "feature added" and "fixed bug" semantics, rather than the 0.x.y form that munges patch and feature semantics in y, while giving me zero useful information in x. Our pre-ingestion tests are going to tell me whether something is broken, that's why we run them before we ingest them. We assume breakage, but that doesn't stop us from running whatever tests we can, to continuously track our progress.

Given that this proposed change still allows breakage on the y bump, as consumers, we still have to assume that all prereleases may be broken, so this change in the spec is not an improvement. The fact that we would lose the feature/patch signals, implies a loss of data, hence a step backwards, at least for some of us who pay attention to these sorts of things.

Under the current interpretation of the spec, I know that a passing ingestion test on a y bump is meaningless until I add tests for whatever new features were added or removed. I also know that a z bump should have fixed at least some of the test case failures. I also know that when a non-prerelease 1.x.y is finally released, it's highly unlikely to be a breaking change from whatever the last 0.x.y was. Under the current spec, we don't have to have special logic for two classes of prerelease.

I think that the 0.major.minor|patch and 0.0.major schemes simply allows publishers to claim semantic versioning while never really committing to keeping un-signaled breakage to a minimum, or really signaling anything useful at all. It also introduces the weird inconsistency that the value of major can go from x > 1 to x = 1 with each shift to the left.

I also think that we should not consider breaking SemVer without first establishing how to disambiguate between SemVer 2 and all future versions, including patches and etc.

@Lxstr
Copy link

Lxstr commented Feb 17, 2024

Even setting aside how much transition confusion this would introduce, I think this PR would cause far too much cognitive load when you consider how millions of people use this concept of semver MAJOR.MINOR.PATCH. Even one second extra is thousands of extra hours spent decoding what semver a package is using, maybe guessing based on it's release date and then trying to understand what changes have occurred. Also, having to communicate about changes in a <1.0.0 version
becomes too hard and I suspect you'll get people simply not comply with semver, or semi-comply and use 1.0.0 as their starting point.

Perhaps the problem statement and intent could be clarified, but I suspect the intent is to reduce ambiguity on whether a release is stable or in initial development, and that would be a good idea, but I don't think this is the way to go about it. One idea might be that the Development Status of the product could be more clearly separate from semver altogether. For example Pypi has the underused ability to mark Development Status:

1 - Planning
2 - Pre-Alpha
3 - Alpha
4 - Beta
5 - Production/Stable
6 - Mature
7 - Inactive

Note decisions regarding this are often not made WITHIN a release, the clearest example would be deprecating a package, but you can apply it to the others. You wouldn't cut a new release to mark something as deprecated, you would flag it as deprecated and archive the repo.

I think it would be very neat if this was used more ubiquitously while semver eventually removed the notion of <1.0.0 being initial development. Think of packages that need to make a breaking change but don't consider themselves beyond a alpha status for their product overall. And of course as you illustrate, there are many sub 1.0.0 products that have become defacto Production/Stable, cutting a new release just to indicate the product is stable seems strange.

@ljharb
Copy link
Contributor Author

ljharb commented Feb 17, 2024

@Lxstr this is already how npm (and possibly bundler?) has always operated, and the number of people using npm is quite significant compared to all other users of semver, so I think this would reduce, not increase, confusion.

The intent is to update the spec to match what npm does.

@jwdonahue
Copy link
Contributor

The intent is to update the spec to match what npm does.

That is exactly why this PR should not be accepted. It would impact a large number of valid implementations. There are many other implementations that do not and probably will not conform to how NPM does it. Keep in mind that SemVer predates NPM. There is nothing in the spec that precludes NPM's behaviors, so why change the spec to match what they are doing?

@ljharb
Copy link
Contributor Author

ljharb commented Feb 21, 2024

@jwdonahue what highly adopted implementations would it impact?

@conartist6
Copy link

conartist6 commented Aug 24, 2024

Why though? In aviation we have a saying, "The three most useless things to a pilot are altitude above, runway behind, and fuel you left on the ground"

@ljharb
Copy link
Contributor Author

ljharb commented Aug 24, 2024

Because the only purpose of publishing something to npm is for people to use it; only stable things should be used; therefore only stable things should be published. If they want to test out something unstable, they can install it from github.

@conartist6
Copy link

conartist6 commented Aug 24, 2024

That's the waterfall philosophy in a nutshell. I know you work mostly on mature projects and those very close to the stable core of the Javascript spec, but I work out in the agile boonies. I ship code to figure out what code to build next. Often I ship an unstable library so that I can try integrating it with other parts of my systems so that I can gather the feedback on how the library fulfills its purpose so that I can stabilize the API design -- all of this in service of a single goal: reach 1.0.0.

This is particularly important when a system you are designing is intended to be implemented through the integration of many stable packages

@jwdonahue
Copy link
Contributor

The true SemVer way is to start with 0.1.0, then if that first round of features needs a bug fix, you have 0.1.n, until you're ready to release another batch of features in 0.2.0, rinse and repeat. Maybe you get to 0.100.n before you promote to 1.0.0 and get serious about publishing stable versions. The whole point of the fixed Broken.Feature.Patch pattern is to telegraph to your consumers the kinds of changes you're intending to make, whether you just dropped bug fixes, new features or made breaking changes. The entire 0.Y.Z series are for that unpredictable phase where you might abandon, replace or just accidently break some API's, but you still use major and minor for their intended purpose.

@conartist6
Copy link

conartist6 commented Aug 27, 2024

Just saw a blog post that expands on on this topic in some more detail: https://www.jvt.me/posts/2024/08/26/v0/ (I also invited the author to this thread)

Something that it reminds me of is that 1.0.0 may be the same release as some other numbered release (0.12.3, say). This is because stability is indicated not by the making of changes, but when it becomes apparent through a lack of ongoing changes that a library is already stable.

@conartist6
Copy link

Another piece of prior art on the kind of engineering which embraces instability: https://paulgraham.com/hp.html

I've always loved Paul's description of the work we do, and these bits are particularly relevant:

What and how should not be kept too separate. You're asking for trouble if you try to decide what to do without understanding how to do it. But hacking can certainly be more than just deciding how to implement some spec. At its best, it's creating the spec-- though it turns out the best way to do that is to implement it. [...]

For example, I was taught in college that one ought to figure out a program completely on paper before even going near a computer. I found that I did not program this way. I found that I liked to program sitting in front of a computer, not a piece of paper. Worse still, instead of patiently writing out a complete program and assuring myself it was correct, I tended to just spew out code that was hopelessly broken, and gradually beat it into shape. Debugging, I was taught, was a kind of final pass where you caught typos and oversights. The way I worked, it seemed like programming consisted of debugging.

For a long time I felt bad about this, just as I once felt bad that I didn't hold my pencil the way they taught me to in elementary school. If I had only looked over at the other makers, the painters or the architects, I would have realized that there was a name for what I was doing: sketching. As far as I can tell, the way they taught me to program in college was all wrong. You should figure out programs as you're writing them, just as writers and painters and architects do.

Realizing this has real implications for software design. It means that a programming language should, above all, be malleable. A programming language is for thinking of programs, not for expressing programs you've already thought of. It should be a pencil, not a pen. Static typing would be a fine idea if people actually did write programs the way they taught me to in college. But that's not how any of the hackers I know write programs. We need a language that lets us scribble and smudge and smear, not a language where you have to sit with a teacup of types balanced on your knee and make polite conversation with a strict old aunt of a compiler.

@conartist6
Copy link

conartist6 commented Aug 30, 2024

I'm really sorry if I'm beating a dead horse here, but this latest wave of pressure and (hopefully constructive) feedback is a direct response to hearing from Jordan that there was no question as to whether this change would land, even in spite of the already-then-significant criticism. Is that true?

@steveklabnik
Copy link
Member

To be honest, it's been hard to get anyone to care about changing stuff around here.

I still believe the correct path for the specification is to bring it in line with practice. The arguments from theory don't really compel me otherwise, personally. Specifications are a way to bring implementations together, and a specification that does not describe reality is not a useful specification.

@bvisness
Copy link

bvisness commented Sep 4, 2024

My previous message was not just an "argument from theory". I believe that this new framework actually does disagree with practice.

The fundamental value of a semantic version number is not really in specifying how npm resolves version ranges. It's in standardizing how humans communicate about versions.

In the past we just had arbitrary globs of numbers like 1.324.20419-3523. All you can really infer from such a number is that bigger number = newer. But building a package manager obviously requires us to automate some amount of reasoning about versions, and automation requires standardization.

But package managers are garbage in, garbage out. Package managers have to take version numbers at their word, and that means humans have to write good version numbers. The value semver provides is not simply mechanical reasoning about breakage, it's a larger framework for package authors to use to communicate the status of their package.

After all, if you really wanted to design a version system exclusively to satisfy the mechanical needs of package managers, you would only need two numbers: major and minor. The major version increments on breakage, the minor version increments on non-breakage. But semver provides much more than that, and that is a good thing—because there is much more that humans want to communicate about their packages.

For example:

  • Having separate minor and patch versions allows a package author to indicate the size of a change, even when there are no breaking changes. This is useful for indicating bug fixes.
  • Having 0.x be special allows a package author to indicate that a package is early in development. This is useful because it allows package authors to communicate frequent future breakage, which users may want to avoid.
  • Having explicit prerelease syntax allows package authors to signal that a new version is hopefully stable but needs testing.

The combination of these makes semver a richer tool for communication. When you see 0.3.14 in the wild, you think "early in development, but frequently updated". When you see 1.0.0 you think "the author says it's ready to use, but I bet there are still bugs". When you see 1.5.12 you think "nice, this package is stable but actively maintained, and likely has some quality-of-life features too". When you see 2.0.0-beta you think "interesting, maybe I'll try this out before settling on the stable release".

This new framework destroys all this meaning. This is what I was trying to communicate before. It reduces semver to this dry, mechanical binary of breaking / non-breaking. And by taking away humans' ability to communicate about versions, you ruin semver, and will cause many more garbage versions to be entered into package managers in the first place.


I will say, I sympathize with the posters of #127 and #915. But I think the correct solution is simply to provide some non-normative guidance for how to use the minor and patch versions when on 0.x. For example, perhaps we could give guidance like the following:

While on major version 0, the minor version SHOULD be incremented for large changes and the patch should SHOULD be incremented for bug fixes, with the understanding that any update still MAY cause breakage since the library is still in initial development.

This would ensure that more package authors implicitly follow the model expected by npm and cargo, without forcing them into undesirable stability guarantees.

@jwdonahue
Copy link
Contributor

Specifications are a way to bring implementations together, and a specification that does not describe reality is not a useful specification.

I agree, and for the most part, that's the effect that the SemVer spec has had. Nuget and hundreds of other implementations are compliant with the current spec, including retention of build meta. In fact, my team (at the time) in Windows build, fought hard to get Nuget brought into compliance and I created a compliant, in-house packaging tool that was still in use by everyone who builds Windows, the last time I looked. I have also reviewed code for dozens of other more or less compliant implementations.

The fact that NPM has never been compliant to the SemVer standard, is no reason to change the spec. Doing so, weakens it as a unifying element.

There will always be some level of non-compliant or compliant++ implementations with any spec, as businesses and developers innovate to optimize profits or performance. You can't write a single spec to cover every variant, but you can have one that is close to the most common denominator. If you get in the habit of modifying the spec to comply with just a handful of those variants, while ignoring the many that are compliant, you lose that connection to reality, as well as the motivation to bring implementations closer together.

@jwdonahue
Copy link
Contributor

While on major version 0, the minor version SHOULD be incremented for large changes and the patch should SHOULD be incremented for bug fixes, with the understanding that any update still MAY cause breakage since the library is still in initial development.

This would ensure that more package authors implicitly follow the model expected by npm and cargo, without forcing them into undesirable stability guarantees.

That's definitely inline with the letter and the intent of the current spec, but I don't think that's exactly how NPM does it. I don't recall how cargo handles it, but the current PR basically shifts Major to the right one or two fields and requires you do not break anybody without bumping major. So you lose information about some types of changes being made, in order to signal breakage with highest non-zero field.

It boggles my mind, why it's okay to release a series for breaking changes as 1.x.y-broken, but not as 0.1.y, in NPM, when the spec has clearly said that the later form is for initial development, when any release may or may not break something, intentionally or otherwise.

@conartist6
Copy link

There are two constituencies here, the 1.0.0-means-nothing folks and the 1.0.0-means-something folks. Both philosophies are valid and have clearly guided people's real decision making. The ask is that there be no change to the spec that excludes one group's needs by exclusively favoring the other interpretation.

If you could tell us that a proposal would have to win the approval of both groups to be accepted it would go a long ways to turn down the temperature and allow us to start working to write new spec language

@conartist6
Copy link

If the 1.0.0-means-something group isn't even going to have a seat at the table or a say in the decision (and the argument I'm hearing is that we should not), then to keep this productive we should accept the fork and go write ourselves a better spec -- one that clarifies the things that have lacked clarity and is actually backwards compatible with Semver 2.0.0.

@steveklabnik
Copy link
Member

Sorry, reading the recent comments, it seems like people are suggesting that somehow changes here would make existing libraries incompliant. That's not the case. The spec wouldn't mandate any special "1.0.0" handling or before, it would be like the concept of "Implementation defined behavior."

Right now, these operators are not in the spec at all, the goal is to properly document the various ways in which the various implementations decided to implement various aspects of ranges/matchers, so that new libraries have some sort of guidance about which semantics they want to choose, and so that users can understand differences between ecosystems.

@steveklabnik
Copy link
Member

The fundamental value of a semantic version number is not really in specifying how npm resolves version ranges. It's in standardizing how humans communicate about versions.

This is theory. You are trying to impose one set of semantics on every single library that uses this spec. But in practice, that is now how multiple ecosystems use these numbers. Describing what implementations do today is standardizing how humans communicate about versions: it's just that some folks don't do it in a way that you think is correct. Trying to bring them into line with what you desire is not practice. It's also just not going to happen on a practical level: npm and cargo have different semantics around what "x.y.z" means as a range, for example, and neither ecosystem is going to make a backwards incompatible change to their large ecosystems based on what the words say here. Nor should they. The specification didn't provide any guidance, so they did what they thought was best. This is true for every implementation, not just these two. The goal is to provide said guidance to reduce proliferation of different semantics in the future. But the "there's different semantics" ship has sailed, years ago.

@jwdonahue
Copy link
Contributor

jwdonahue commented Sep 5, 2024

@steveklabnik

Right now, these operators are not in the spec at all, the goal is to properly document the various ways in which the various implementations decided to implement various aspects of ranges/matchers, so that new libraries have some sort of guidance about which semantics they want to choose, and so that users can understand differences between ecosystems.

Steve, I think you may have responded on the wrong thread or something. This isn't the PR for range specifiers (#584), it's the discussion on making a breaking change to the way 0.y.z works. I think you know my views on that, wrt whether it should be part and parcel to the SemVer spec.

@steveklabnik
Copy link
Member

steveklabnik commented Sep 5, 2024

It's an analogy to a different part of the spec to talk about the overall way in which (if ever) the spec will change: to bring the text on semver.org to reflect how semver is used in practice, by all of the various libraries that use it.

Demonstrably, many ecosystems have production level libraries with x=0. People do not actually rely on x=1 as being special in any specific way, they rely on the differences between various version numbers to determine what has changed between them.

This change conceptually (though I haven't read the text lately) is backwards compatible, in that it's a loosening of what is acceptable. If you want to declare that in your implementation, 1.0.0 has some sort of special meaning, that's fine. If you as a user of semver want to declare that x=0 means "don't use this in production yet," that's fine. But semver is about communicating levels of breakage between version numbers. That's all.

But I'm just repeating the discussion from March 2023. I am not likely to engage with it further until I actually build up the will to actually pull folks together to actually do the work of bringing the spec in line with reality.

@conartist6
Copy link

Demonstrably, many ecosystems have production level libraries with x=0. People do not actually rely on x=1 as being special in any specific way, they rely on the differences between various version numbers to determine what has changed between them.

If people who rely on that distinction don't exist, you are then telling me that I don't exist. Please stop doing that.

@jwdonahue
Copy link
Contributor

jwdonahue commented Sep 6, 2024

This change conceptually (though I haven't read the text lately) is backwards compatible,

It is in fact, a breaking change. One that is likely never going to be complied with by most of the implementations that aren't already using the 0.x.y variant. Most of us, already wrote code that supports the 0.y.z form. All of my tools, always treat 0.x.y version strings as likely to be broken, and therefore never automagically sucked into production build systems, though I will look at z bumps to decide whether they are worth the cycles to test them, x bumps always get pushed down the priority queue.

With 0.x.y, the NPM way, I can't tell whether that y bump was intended to be a bug fix or a new feature, so it's not important enough for my attention, until I have some spare cycles available. This means that publishers who use the 0.x.y form of versioning, have a harder time driving uptake of pure security patches.

To be honest, I haven't used node more than a handful of painful times and would never use it voluntarily. I did experiment with the 0.x.y form for about a year on several projects, and will never do it again.

As a publisher, I like being able to advertise "here's a bug fix, and it will probably break some of you, but too bad, this is a 0.y.z patch that is perfectly inline with the intent and letter of the SemVer Spec. As a consumer, I am thinking "oh, it's a bug fix, maybe even a security fix, so I better evaluate whether I should go out of my way to ingest another version of an unstable product.

As an engineer, I try to isolate my systems from the fact that millions of developers do not understand the SemVer spec or frankly, few of the nuances of versioning in general. I think there are too many X.Y version schemes out there, masquerading as SemVer, simply because they appended a "0." to it. If all you really want is Major.Minor, don't bother calling it SemVer, your compliance is not required.

@conartist6
Copy link

conartist6 commented Sep 6, 2024

As a super informal survey lets look at the home page of npm. Every single one of the top 10 packages is >1.0.0 and <20.0.0.

Of the 36 recently published packages, 26 of them are >1.0.0 and <20.0.0. Of the remaining 10:

  • 2 are <1.0.0 but in a way that clearly indicates initial development (the author has bumped most of the other packages in the same namespace to 1.0.0 already)
  • 5 are part of unstable libraries (versions 0.94.x and 0.57.x)
  • 3 are not using semver (version 20240317.0.0)

That is to say (and I don't know how much more clearly it is possible to say this): at happenstance, 100% of the semver users were adhering to the distinction that >1.0.0 indicates an API-stable package. That includes the five packages which were clearly production grade but also clearly not API stable and chose to indicate it by not publishing 1.0.0.

I'm all in favor of changing the spec so that a library that is at 0.94.0 is not considered to be under "initial development." That much clearly is currently out of line with reality and we can offer that minority people better protection under the spec so that there is no question that use of ^ is safe and correct for users of libraries distributed this way.

However. The idea that the 1.0.0 does not (in a practical sense) mean anything in our ecosystem is dross which can be disproven by even the most basic direct observation. The hypothesis that there is no meaningful distinction exists predicts that 0.1.x should be just as common in these results as 1.x.x and 20.x.x should be as common as 0.20.x. It comes as no surprise to me that the numbers fit my hypothesis better than yours, because I know that even people who never read the semver spec were targeted by name-and-shame campaigns pressuring them to make the distinction (see: 0ver.org, now no longer naming and shaming).

@conartist6
Copy link

conartist6 commented Sep 6, 2024

Lets look at how the spec made the distinction without being able to enforce it (I'm paraphrasing):

  • It says, "Version 1.0.0 defines the public API"
  • It says, "If anyone is using your library in production, it should be >1.0.0"
  • It says, "If you are worried about your library hitting 50.0.0, you probably broke the single responsibility principle"
  • It says, "If you use a library <1.0.0, anything can break at any time"

Taken together these parts of the spec language create strong system of incentives for authors to publish 1.0.0, which combined with the social pressure is why we can observe that the intended effect seems to have been achieved in practice -- especially among the most serious vendors of OSS.

@conartist6
Copy link

conartist6 commented Sep 6, 2024

Not only that but semver has very good reason to want people behave in accordance with the single responsibility principle.

If a single library exports 50 functions with different APIs (as my library iter-tools does, for now), then when the major version number changes the consumer knows very little about where in my contracts a breakage has occurred. Semver says, "somehwere in there" and leaves the rest of the work to a changelog. Compare that to having 50 functions in individual packages, each with its own version number. In that scenario a semver breakage is a much more valuable signal. Most people know intuitively that alarms that often go off when they should not are not good alarms, and it is the same with semver breakages. The spec is most valuable to all involved when it creates an incentive structure for authors to apply the system in a fine-grained manner so that consumers can be pointed to an exact location where a contract broke.

In short the single responsibility principle makes it possible to bring contract breakage under accurate computational analysis, fulfilling the foundational goal of semver as I understand it.

@bvisness
Copy link

bvisness commented Sep 6, 2024

@conartist6, I think you've made your stance very clear.

@jwdonahue
Copy link
Contributor

jwdonahue commented Sep 6, 2024

@conartist6

...0ver.org...

I love it!


And thanks for the data.


Oops ;( satire );

Well I'll just add a 0ver schema to VersionSchema.

@conartist6
Copy link

conartist6 commented Sep 6, 2024

The real problem is that right now is that some users of what you might call 0ver are supported by npm in a manner that has no blessing from any (non-joke) specification.

I would like to write up a proposed for a semver-0 extension spec in the formal style, which when used would provide an extra guarantee about nonbreakage between patch versions and limit initial development releases to 0.0.x. Since it only provides stronger guarantees than the full spec, it should be a valid subset (which evidence bears out).

I will write the spec in such a way as to give it no force without opt-in so that it would meet my personal criteria of not making things worse for anyone than they are right now, yet giving people the opportunity to provide clarity and make things better. For example the author of a library at version 0.86.0 is able to say with authority, "I am and have always been releasing in accordance with the semver-0 specification." Npm has worked that way all along, so there is an excellent likelihood in that ecosystem that it would also be true in a practical sense.

@ljharb
Copy link
Contributor Author

ljharb commented Sep 6, 2024

npm has defaulted to v1.0.0 in npm init for a very very long time, so the real data to look at is who intentionally chose a v0 version after that default changed.

Most devs don’t read the spec anyways, whether it’s semver or JS or anything else. In the npm ecosystem, people learn how it works by seeing what npm does.

@conartist6
Copy link

Here it is: a concrete proposal for filling the hole in the specification which currently affects users shipping production code under a 0.x.y versioning scheme: https://gist.github.com/conartist6/37be2e179d286fe7be276e5abe32ff57

@FichteFoll
Copy link

Even if the SemVer-0 proposal seems somewhat of a joke, I actually do believe that it solves the issue that npm is having with SemVer 2.0.0 currently: It formalizes the communication of version increments in the major-0 range as an extension of the original SemVer definition with higher priority than SemVer itself for the affected version range without also invalidating all other existing usages of SemVer 2.0.0 (let's be real, there will probably not be another 2.x than 2.0.0). Applications following this scheme could then declare that they are using SemVer & SemVer-0 and npm can document that its default ^ operator behaves according to SemVer & SemVer-0.

As I have already mentioned last year this is a breaking change to SemVer itself and not something than can be taken lightly. I'll refrain from commenting on whether I believe this proposal to be better an improvement for now because I don't want to invest this time, especially when we're not even sure what the impact and cost of releasing a new SemVer major is.

@jorenbroekema
Copy link

jorenbroekema commented Sep 26, 2024

So from what I gather, overall the consensus is that the proposed specifics about version <1.0.0 are fine, but the hold-up is that we can't yet agree on whether this should happen in SemVer 3.0.0 or in SemVer 2.0.0 + SemVer-0 1.0.0 where it's an extension spec.

Main reason for not doing it in SemVer 3.0.0 seems to be that it would be quite a challenge to get other package managers/ecosystems to adopt it because it means that the managers need to start auto-bumping/installing/deduping installs for z within 0.y.z and that package maintainers need to avoid doing breaking changes in z bumps for 0.1.0 < 1.0.0 range? That seems like a very reasonable argument to me honestly, this would take time & effort and we'll have the situation where lots of tools will be stuck on SemVer 2.0.0 and we then split the ecosystem between 2.0.0 and 3.0.0 which by itself also creates ambiguity, which is exactly what we want to be reducing with this.

So yeah, I'd really like to see the SemVer-0 extension spec then, because at least it moves us forward and gets rid of the ambiguity around it which is creating all the problems at the moment, and makes reasonable suggestions (as opt-in) for folks wanting to implement SemVer and wondering what the hell to do about 0.y.z handling... One small suggestion from me would be to further clarify in the main SemVer spec that by default any 0.y.z is unstable and literally any bump should be considered potentially breaking, and that adoption of the SemVer-0 spec makes this "freedom" slightly less loose.

@Spiker985
Copy link

One small suggestion from me would be to further clarify in the main SemVer spec that by default any 0.y.z is unstable and literally any bump should be considered potentially breaking, and that adoption of the SemVer-0 spec makes this "freedom" slightly less loose.

To clarify, that language is already in the existing SemVer 2 definition - the definition that would be changed by the original proposal

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Could we clarifies better in the semver doc PATCH releases and initial development?