-
Notifications
You must be signed in to change notification settings - Fork 725
[spec] document real-world v0 semantics #923
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
I am unsure how I feel about this personally, we can sort it at some point. |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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. |
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 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. 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. |
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:
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):
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:
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. 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. |
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. |
Looks like a duck, sounds like a duck, it's a duck. |
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. |
@steveklabnik gentle ping, since it's been a few weeks :-) |
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 :) |
Thanks for the update :-) I'd be more than happy to make any changes whenever you get to describing them. |
@steveklabnik another gentle ping, if you want to take some time away from governance drama <3 |
There was a problem hiding this 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?
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? |
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. |
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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 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 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. |
@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. |
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? |
@jwdonahue what highly adopted implementations would it impact? |
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" |
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. |
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 |
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. |
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 |
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:
|
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? |
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. |
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:
The combination of these makes semver a richer tool for communication. When you see 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:
This would ensure that more package authors implicitly follow the model expected by npm and cargo, without forcing them into undesirable stability guarantees. |
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. |
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. |
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 |
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. |
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. |
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. |
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. |
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. |
If people who rely on that distinction don't exist, you are then telling me that I don't exist. Please stop doing that. |
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. |
As a super informal survey lets look at the home page of npm. Every single one of the top 10 packages is Of the 36 recently published packages, 26 of them are
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 I'm all in favor of changing the spec so that a library that is at However. The idea that the |
Lets look at how the spec made the distinction without being able to enforce it (I'm paraphrasing):
Taken together these parts of the spec language create strong system of incentives for authors to publish |
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. |
@conartist6, I think you've made your stance very clear. |
I love it! And thanks for the data. Oops ;( satire ); Well I'll just add a 0ver schema to VersionSchema. |
The real problem is that right now is that some users of what you might call 0ver are supported by 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 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 |
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. |
Here it is: a concrete proposal for filling the hole in the specification which currently affects users shipping production code under a |
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 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. |
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 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 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 |
To clarify, that language is already in the existing SemVer 2 definition - the definition that would be changed by the original proposal |
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.