1# Edition Naming 2 3**Author:** [@mkruskal-google](https://github.com/mkruskal-google) 4 5**Approved:** 2023-08-25 6 7## Background 8 9One of the things [Life of an Edition](life-of-an-edition.md) lays out is a very 10loose scheme for naming editions. It defines an ordering rule and "." delimiter, 11but otherwise imposes no constraints on the edition name. By *convention*, we 12decided to use the year followed by an optional revision number. 13 14One of the decisions in 15[Editions: Life of a FeatureSet](editions-life-of-a-featureset.md) was that 16feature resolution would, at least partially, need to be duplicated across every 17supported language. 18 19## Problem Description 20 21As discussed in *Life of a FeatureSet*, the minimal operations we need to 22duplicate are edition comparison and proto merging. While edition comparison 23isn't *very* complicated today, it has *a lot* of edge cases we may miss due to 24the loose constraints on edition names. It would also be really nice if we could 25reduce it down to a lexicographical string comparison, which can be easily done 26in any language. 27 28There's also a very real Hyrum's law risk when we permit an infinite number of 29edition names that we never expect to exist in practice. For example, `2023.a` 30is currently valid, and its relative ordering to `2023.10` (for example) is not 31obvious. Notably, our initial editions tests used an edition `very-cool`, which 32doesn't seem like something we need to support. Ideally, our edition names would 33be as simple as possible, with enforceable and documented constraints. 34 35There has been a lot of confusion when looking at the interaction between 36editions and releases. Specifically, the fact that editions *look* like calver 37numbers has led to us calling revisions "patch editions", which suggests that 38they're bug fixes to an earlier one. This was not the original intention though, 39which to summarize was: 40 41* **Editions are strictly time-ordered**. Revisions were simply a mechanism to 42 release more than one edition per calendar year, but new editions can't be 43 inserted into earlier slots. 44 45* **New editions can be added at any time**. As long as they're ordered 46 *after* the pre-existing ones, this is a non-breaking change and can be done 47 in a patch release. 48 49* **New features can be added at any time without changing the edition**. 50 Since they're a non-breaking change by definition, this can be done in a 51 patch release. 52 53* **Features can only be deleted in a breaking release**. The editions model 54 doesn't support the deletion of features, which is always a breaking change. 55 This will only be done in a major version bump of protobuf. 56 57* **Feature defaults can only be changed in a new edition**. Once the default 58 is chosen for a feature, we can only change it by releasing a new edition 59 with an updated default. This is a non-breaking change though, and can be 60 done in patch releases. 61 62## Recommended Solution 63 64We want the following properties out of editions: 65 66* Discrete allowable values controlled by us 67* Easily comparable 68* Cross-language support 69* Relatively small set (<100 for the next century) 70* Slow growth (~once per year) 71 72The simplest solution here is to just make an `Edition` enum for specifying the 73edition. We will continue to use strings in proto files, but the parser will 74quickly convert them and all code from then on will treat them as enums. This 75way, we will have a centralized cross-language list of all valid editions: 76 77``` 78enum Edition { 79 EDITION_UNKNOWN = 0; 80 EDITION_2023 = 1; 81 EDITION_2024 = 2; 82 // ... 83} 84``` 85 86We will document that these are *intended* to be comparable numerically for 87finding the time ordering of editions. Proto files will specify the edition in 88exactly the same way as in the original decision: 89 90``` 91edition = "2023"; 92``` 93 94Ideally, this would be an open enum to avoid ever having the edition thrown into 95the unknown field set. However, since it needs to exist in `descriptor.proto`, 96we won't be able to make it open until the end of our edition zero migration. 97Until then, we can take advantage of the fact that `syntax` gets set to 98`editions` by the parser when an edition is found. In this case, an unset 99edition should be treated as an error. Once we migrate to an open enum we can 100replace this with a simpler validity check. 101 102Additionally, we've decided to punt on the question of revisions and just not 103allow them for now. If we ever need more than that it means we've made a huge 104mistake, and we can bikeshed on naming at that time (e.g. `EDITION_2023_OOPS`). 105We will plan to have exactly one edition every year. 106 107#### Pros 108 109* Edition comparison becomes **even simpler**, as it's just an integer 110 comparison 111 112 * Feature resolution becomes trivial in every language 113 114* Automatic rejection of unknown editions. 115 116 * This includes both future and dropped editions, and also unknown 117 revisions 118 * Other solutions would need custom logic in protoc to enforce these 119 120* Doesn't look like calver, avoiding that confusion 121 122* Lack of revisions simplifies our documentation and makes editions easier to 123 understand/maintain 124 125#### Cons 126 127* Might prove to be a challenge when we go to migrate `descriptor.proto` to 128 editions 129 130* Might be a bit tricky to implement in the parser (but Prototiller does this 131 just fine) 132 133## Considered Alternatives 134 135### Edition Enums 136 137Instead of using a string, editions could be represented as an enum. This would 138give us a lot of enforcement for free and be maximally constraining. For 139example: 140 141``` 142enum Edition { 143 E2023 = 1; 144 E2023A = 2; 145 E2024 = 3; 146} 147``` 148 149#### Pros 150 151* Edition comparison becomes **even simpler**, as it's just an integer 152 comparison 153 154* Arbitrary number of revisions within a year 155 156* Automatic rejection of unknown editions (with closed enums). 157 158 * This includes both future and dropped editions, and also unknown 159 revisions 160 * Other solutions would need custom logic in protoc to enforce these 161 162* Doesn't look like calver, avoiding that confusion 163 164#### Cons 165 166* Edition specification in every proto file becomes arguably less readable. 167 `edition = "2023.1"` is easier to read than `edition = E2023A`. 168 169* Might require parser changes to get `descriptor.proto` onto editions 170 171* This is a pretty big change and will require documentation/comms updates 172 173* Plugin owners can't pre-release upcoming features 174 175 * Do we even want to allow this? It could help coordinating editions 176 releases 177 178#### Neutral 179 180* Edition must be strictly time-ordered. We can't go back and add a revision 181 to an older revision, but the original plan didn't really allow for this 182 anyway. 183 184* Edition ordering is not directly linked to the name *at all*. We'd have to 185 write a reflection test that enforces that they're strictly increasing. 186 187### Truncated Revisions 188 189The simplest solution that's consistent with the original plan would be to limit 190ourselves to no more than 9 intermediate editions per year. This means editions 191would either be a simple year (e.g. `2023`), or have a single digit revision 192number (e.g. `2024.3`). The revision number would be constrained to `(0,9]` and 193the year would have to be an integer `>=2023`. 194 195With these constraints in place, edition ordering becomes a simple 196lexicographical string ordering. 197 198#### Pros 199 200* Tightly constrains edition names and avoids unexpected edge cases 201* Edition comparison code is easily duplicated (basic string comparison) 202* Removes ambiguity around revision 0 (e.g. `2023.0` isn't allowed) 203 204#### Cons 205 206* Limits us to no more than 10 editions per year. This *seems* reasonable 207 today, and since protoc is the one enforcing this we can always revisit it 208 later. As long as the ordering stays lexicographical we can expand it 209 arbitrarily. 210 211### Fixed length editions 212 213This is similar to the recommended solution, but instead of allowing year 214editions we would always start with a `.0` release. This means that Edition Zero 215would become `2023.0`. This has the same pros/cons as the solution above, with 216the addition of: 217 218#### Pros 219 220* Makes revisions less jarring. Users won't be confused by `2023.1` if they're 221 used to `2023.0`. 222 223#### Cons 224 225* We've already published comms and documentation calling the first edition 226 `2023`. We'd have to go update those and communicate the change. 227* We have no real use-case for revisions yet, and this adds complexity to the 228 "typical" case. 229 230### Edition Messages 231 232Instead of using a string, editions could be represented as a message. This 233would allow us to model the constraints better. For example: 234 235``` 236message Edition { 237 uint32 year = 1; 238 uint32 revision = 2; 239} 240``` 241 242#### Pros 243 244* The schema itself would automatically enforce a lot of our constraints 245* Arbitrary number of revisions would be allowed 246* Can't accidentally use string comparison 247 248#### Cons 249 250* Custom comparison code would still be needed in every language (marginally 251 more expensive than string comparison) 252* We would either need to convert the edition string into this message during 253 parsing or radically change the edition specification 254 255### Do Nothing 256 257#### Pros 258 259* Easier today, since we don't have to do anything 260 261#### Cons 262 263* Harder tomorrow when we have to duplicate this comparison code into every 264 language 265* Leaves us open to Hyrum's law and unexpected abuse 266