• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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