-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
888 lines (780 loc) · 41.2 KB
/
Copy pathindex.html
File metadata and controls
888 lines (780 loc) · 41.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BFIP Versioning — a four-field alternative to Semantic Versioning</title>
<meta name="description" content="BFIP Versioning splits version numbers into Breaking, Feature, Internal, and Patch fields, so a version number tells you exactly what kind of change you're getting.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--ink: #1A1D23;
--ink-soft: #4A4F5A;
--ink-faint: #8A8F99;
--paper: #FBFAF7;
--paper-raised: #F2F0EA;
--rule: #DEDBD2;
--b: #C1432E;
--f: #2E7D5B;
--i: #3F5EA8;
--p: #8A6D1F;
--b-bg: #FBEEEC;
--f-bg: #EBF4EF;
--i-bg: #ECF0F8;
--p-bg: #F7F1E4;
--serif: 'Fraunces', Georgia, serif;
--sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
--max-w: 780px;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
* { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
body {
margin: 0;
background: var(--paper);
color: var(--ink);
font-family: var(--sans);
font-size: 17px;
line-height: 1.65;
-webkit-font-smoothing: antialiased;
}
a { color: var(--i); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--ink); }
code, .mono { font-family: var(--mono); }
.version-string {
font-family: var(--mono);
font-weight: 600;
letter-spacing: -0.01em;
}
/* ---------- layout shell ---------- */
.shell {
display: grid;
grid-template-columns: 240px minmax(0, var(--max-w));
gap: 64px;
max-width: 1120px;
margin: 0 auto;
padding: 0 32px;
}
@media (max-width: 980px) {
.shell { grid-template-columns: 1fr; gap: 0; padding: 0 20px; }
}
/* ---------- sidebar / TOC ---------- */
.sidebar {
position: sticky;
top: 40px;
align-self: start;
height: fit-content;
padding-top: 96px;
padding-bottom: 40px;
}
@media (max-width: 980px) {
.sidebar { display: none; }
}
.toc-title {
font-size: 12px;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--ink-faint);
margin: 0 0 14px;
}
.toc-list { list-style: none; margin: 0; padding: 0; }
.toc-list li { margin-bottom: 2px; }
.toc-list a {
display: block;
padding: 5px 0 5px 14px;
border-left: 2px solid var(--rule);
color: var(--ink-soft);
font-size: 14px;
text-decoration: none;
transition: border-color 0.15s ease, color 0.15s ease;
}
.toc-list a:hover { color: var(--ink); border-left-color: var(--ink-faint); }
.toc-list a.active { color: var(--ink); border-left-color: var(--ink); font-weight: 500; }
.toc-field-tag {
display: inline-block;
width: 8px; height: 8px;
border-radius: 50%;
margin-right: 8px;
}
/* ---------- main column ---------- */
main { min-width: 0; padding-bottom: 120px; }
header.hero {
padding: 88px 0 56px;
border-bottom: 1px solid var(--rule);
}
.eyebrow-version {
display: inline-flex;
align-items: baseline;
gap: 8px;
font-family: var(--mono);
font-size: 13px;
color: var(--ink-faint);
background: var(--paper-raised);
border: 1px solid var(--rule);
border-radius: 4px;
padding: 5px 10px;
margin-bottom: 28px;
}
h1.title {
font-family: var(--serif);
font-optical-sizing: auto;
font-weight: 600;
font-size: clamp(2.4rem, 5vw, 3.4rem);
line-height: 1.08;
letter-spacing: -0.015em;
margin: 0 0 20px;
color: var(--ink);
}
p.dek {
font-size: 19px;
line-height: 1.55;
color: var(--ink-soft);
max-width: 62ch;
margin: 0 0 36px;
}
.hero-links { display: flex; gap: 20px; flex-wrap: wrap; font-size: 15px; }
.hero-links a { font-weight: 500; }
/* ---------- interactive breakdown ---------- */
.breakdown {
margin-top: 48px;
border: 1px solid var(--rule);
border-radius: 10px;
background: white;
overflow: hidden;
}
.breakdown-input-row {
display: flex;
align-items: center;
gap: 10px;
padding: 18px 20px;
border-bottom: 1px solid var(--rule);
background: var(--paper-raised);
}
.breakdown-input-row label {
font-size: 13px;
color: var(--ink-faint);
font-weight: 500;
}
#version-input {
font-family: var(--mono);
font-size: 17px;
font-weight: 600;
border: 1px solid var(--rule);
border-radius: 6px;
padding: 8px 12px;
background: white;
color: var(--ink);
flex: 1;
min-width: 0;
}
#version-input:focus {
outline: 2px solid var(--i);
outline-offset: 1px;
}
.breakdown-display {
padding: 28px 20px 8px;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 4px;
font-family: var(--mono);
}
.field-block {
display: flex;
flex-direction: column;
align-items: center;
min-width: 62px;
}
.field-num {
font-size: clamp(1.6rem, 5vw, 2.4rem);
font-weight: 700;
padding: 4px 10px;
border-radius: 6px;
transition: background 0.2s ease, color 0.2s ease;
}
.field-label {
margin-top: 10px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--ink-faint);
}
.field-sep {
font-size: clamp(1.6rem, 5vw, 2.4rem);
font-weight: 400;
color: var(--ink-faint);
align-self: center;
padding-bottom: 26px;
}
.field-num.b { color: var(--b); }
.field-num.f { color: var(--f); }
.field-num.i { color: var(--i); }
.field-num.p { color: var(--p); }
.breakdown-explain {
padding: 20px 24px 26px;
font-size: 14.5px;
color: var(--ink-soft);
text-align: center;
min-height: 1.6em;
}
.breakdown-explain strong { color: var(--ink); }
.breakdown-error {
padding: 24px;
text-align: center;
color: var(--b);
font-size: 14.5px;
}
/* ---------- prose sections ---------- */
section { padding: 56px 0; border-bottom: 1px solid var(--rule); }
section:last-of-type { border-bottom: none; }
section h2 {
font-family: var(--serif);
font-weight: 600;
font-size: 1.9rem;
letter-spacing: -0.01em;
margin: 0 0 22px;
scroll-margin-top: 32px;
}
section h3 {
font-family: var(--serif);
font-weight: 600;
font-size: 1.3rem;
margin: 32px 0 12px;
scroll-margin-top: 32px;
}
section p, section li { color: var(--ink-soft); }
section p { margin: 0 0 18px; }
section ul, section ol { padding-left: 22px; margin: 0 0 18px; }
section li { margin-bottom: 8px; }
section p strong, section li strong { color: var(--ink); font-weight: 600; }
blockquote {
margin: 24px 0;
padding: 4px 0 4px 18px;
border-left: 3px solid var(--rule);
color: var(--ink-soft);
font-style: italic;
}
/* rule cards for the four fields */
.field-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 14px;
margin: 28px 0 8px;
}
@media (max-width: 620px) { .field-grid { grid-template-columns: 1fr; } }
.field-card {
border: 1px solid var(--rule);
border-radius: 8px;
padding: 18px 20px;
background: white;
}
.field-card-head {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.field-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.field-card-head .letter {
font-family: var(--mono);
font-weight: 700;
font-size: 15px;
}
.field-card-head .name {
font-weight: 600;
font-size: 14.5px;
color: var(--ink);
}
.field-card p { font-size: 14px; margin: 0; color: var(--ink-soft); }
.field-card.b { border-top: 3px solid var(--b); } .field-card.b .letter { color: var(--b); } .field-card.b .field-dot { background: var(--b); }
.field-card.f { border-top: 3px solid var(--f); } .field-card.f .letter { color: var(--f); } .field-card.f .field-dot { background: var(--f); }
.field-card.i { border-top: 3px solid var(--i); } .field-card.i .letter { color: var(--i); } .field-card.i .field-dot { background: var(--i); }
.field-card.p { border-top: 3px solid var(--p); } .field-card.p .letter { color: var(--p); } .field-card.p .field-dot { background: var(--p); }
/* rule list styling for the spec */
.rule {
display: flex;
gap: 16px;
padding: 18px 0;
border-top: 1px solid var(--rule);
}
.rule:first-of-type { border-top: none; }
.rule-num {
font-family: var(--mono);
font-weight: 600;
font-size: 13px;
color: var(--ink-faint);
flex-shrink: 0;
width: 24px;
padding-top: 2px;
}
.rule-body p, .rule-body li { margin: 0 0 10px; }
.rule-body ol { margin: 10px 0 0; padding-left: 20px; }
/* compat table */
table {
width: 100%;
border-collapse: collapse;
margin: 24px 0;
font-size: 14.5px;
}
th, td {
text-align: left;
padding: 10px 14px;
border-bottom: 1px solid var(--rule);
}
th {
font-weight: 600;
color: var(--ink);
background: var(--paper-raised);
font-size: 13px;
}
td { color: var(--ink-soft); }
td .mono, th .mono { color: var(--ink); }
/* code / grammar block */
pre {
background: #16181D;
color: #E4E2DA;
padding: 22px 24px;
border-radius: 8px;
overflow-x: auto;
font-size: 13.5px;
line-height: 1.7;
margin: 20px 0;
}
pre code { font-family: var(--mono); background: none; }
code.inline {
background: var(--paper-raised);
border: 1px solid var(--rule);
border-radius: 4px;
padding: 1px 6px;
font-size: 0.88em;
color: var(--ink);
}
/* FAQ */
.faq-item { padding: 22px 0; border-top: 1px solid var(--rule); }
.faq-item:first-child { border-top: none; }
.faq-item h3 {
font-family: var(--sans);
font-weight: 600;
font-size: 16px;
margin: 0 0 10px;
color: var(--ink);
}
.faq-item p { font-size: 15px; margin: 0; }
/* footer */
footer {
padding: 40px 0 0;
font-size: 13.5px;
color: var(--ink-faint);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
}
footer a { color: var(--ink-faint); }
footer a:hover { color: var(--ink); }
/* skip link for a11y */
.skip-link {
position: absolute;
left: -9999px;
top: 0;
background: var(--ink);
color: var(--paper);
padding: 10px 16px;
border-radius: 0 0 6px 0;
z-index: 100;
}
.skip-link:focus { left: 0; }
:focus-visible {
outline: 2px solid var(--i);
outline-offset: 2px;
}
</style>
</head>
<body>
<a href="#main" class="skip-link">Skip to content</a>
<div class="shell">
<nav class="sidebar" aria-label="Table of contents">
<p class="toc-title">BFIP Versioning</p>
<ul class="toc-list" id="toc">
<li><a href="#summary">Summary</a></li>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#the-four-fields">The four fields</a></li>
<li><a href="#public-api">Public API</a></li>
<li><a href="#spec">Specification</a></li>
<li><a href="#probation">Probation</a></li>
<li><a href="#compatibility">Compatibility</a></li>
<li><a href="#constraints">Constraints</a></li>
<li><a href="#grammar">Grammar</a></li>
<li><a href="#faq">FAQ</a></li>
</ul>
</nav>
<main id="main">
<header class="hero">
<span class="eyebrow-version">bfip-versioning <span style="color:var(--ink-faint)">is at</span> <span style="color:var(--ink)">0.1.0.0</span></span>
<h1 class="title">A version number should tell you what changed before you read the changelog.</h1>
<p class="dek">BFIP splits Semantic Versioning's minor and patch fields into four: Breaking, Feature, Internal, and Patch. So "you get something new" and "nothing changed for you, we just moved things around" are never the same digit.</p>
<div class="hero-links">
<a href="#spec">Read the specification →</a>
<a href="https://github.com/UniquePython/bfip-versioning" id="repo-link">View on GitHub</a>
</div>
<div class="breakdown" role="group" aria-label="Interactive version breakdown">
<div class="breakdown-input-row">
<label for="version-input">Try a version:</label>
<input id="version-input" class="mono" type="text" value="2.4.1.0" spellcheck="false" autocomplete="off" inputmode="text">
</div>
<div id="breakdown-content">
<!-- filled by JS -->
</div>
</div>
</header>
<section id="summary">
<h2>Summary</h2>
<p>Given a version number B.F.I.P, increment the:</p>
<ol>
<li><strong>B</strong> (breaking) version when you change the public API in a way that breaks existing user code</li>
<li><strong>F</strong> (feature) version when you add new functionality to the public API without breaking existing user code</li>
<li><strong>I</strong> (internal) version when you change how something works internally, without touching the public API in any way</li>
<li><strong>P</strong> (patch) version when you fix a known bug without touching the public API in any way</li>
</ol>
<p>A probation tag is available as an extension to the B.F.I.P format, for trialing a new B before committing to it. Chronological constraints (<code class="inline">=</code>, <code class="inline">></code>, <code class="inline">>=</code>, <code class="inline"><</code>, <code class="inline"><=</code>) describe where a version falls in publication order, independent of compatibility.</p>
</section>
<section id="introduction">
<h2>Introduction</h2>
<p>Semantic Versioning asks a maintainer to fold two different questions into one number. The minor version is supposed to mean "a feature was added," but it is also where "the internals were reworked, nothing user-facing changed" ends up, because there is nowhere else for it to go. The patch version has the same problem: it is meant for bug fixes, but any internal change that isn't a new feature and isn't quite a bug fix tends to get patched in there too. Once two different kinds of change share one number, that number stops telling you which kind of change actually happened, and you're back to reading the changelog to find out.</p>
<p>BFIP starts from the same goal as Semantic Versioning: a version number should tell you, without reading a changelog, what you are getting relative to what you had. It answers that goal with four fields instead of three, so that "the public API got bigger" and "the internals moved but nothing you can see changed" are never the same digit.</p>
<p>For BFIP to mean anything, you have to declare a public API first. What counts as a public API is not the same for a library, a language, a file format, or a service, so BFIP defines a default for each and lets a project state its own boundary if the default doesn't fit. Once that boundary is drawn, every change to the project falls into exactly one of four buckets, and each bucket has its own field.</p>
</section>
<section id="the-four-fields">
<h2>The four fields</h2>
<p>Every change to a project's public API falls into exactly one of these four buckets.</p>
<div class="field-grid">
<div class="field-card b">
<div class="field-card-head"><span class="field-dot"></span><span class="letter">B</span><span class="name">Breaking</span></div>
<p>The public API changed in a way that would stop existing user code from working.</p>
</div>
<div class="field-card f">
<div class="field-card-head"><span class="field-dot"></span><span class="letter">F</span><span class="name">Feature</span></div>
<p>Something new was added to the public API. Existing user code still works, unmodified.</p>
</div>
<div class="field-card i">
<div class="field-card-head"><span class="field-dot"></span><span class="letter">I</span><span class="name">Internal</span></div>
<p>The implementation changed — performance, structure, dependencies — but the public API is untouched.</p>
</div>
<div class="field-card p">
<div class="field-card-head"><span class="field-dot"></span><span class="letter">P</span><span class="name">Patch</span></div>
<p>A known bug was fixed. Behavior was wrong and is now correct; the public API didn't change.</p>
</div>
</div>
</section>
<section id="public-api">
<h2>Public API</h2>
<p>In the absence of a project's own declaration, the public API is:</p>
<ul>
<li><strong>Library or module</strong> — its exported symbols and their signatures and observable behavior.</li>
<li><strong>Language, compiler, or interpreter</strong> — its accepted syntax and the semantics of that syntax.</li>
<li><strong>File format or protocol</strong> — the structure of the data it reads or writes, or the messages it sends or receives, and their meaning.</li>
<li><strong>Service or command-line tool</strong> — its externally visible interface: endpoints, commands, flags, and their observable effects.</li>
</ul>
<p>A project may narrow or widen this default with its own published declaration. In its absence, the default applies.</p>
</section>
<section id="spec">
<h2>Specification</h2>
<p style="font-size:14px;color:var(--ink-faint);margin-bottom:28px;">The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL are to be interpreted as described in <a href="https://tools.ietf.org/html/rfc2119">RFC 2119</a>.</p>
<div class="rule">
<div class="rule-num">1</div>
<div class="rule-body"><p>Software using BFIP Versioning MUST declare a public API, either directly or by adopting the default definition (see <a href="#public-api">Public API</a>, above).</p></div>
</div>
<div class="rule">
<div class="rule-num">2</div>
<div class="rule-body"><p>A normal version number MUST take the form <span class="version-string">B.F.I.P</span> where B, F, I, and P are non-negative integers, and MUST NOT contain leading zeroes. Each element MUST increase numerically. For instance: <span class="mono">1.9.0.0</span> → <span class="mono">1.10.0.0</span> → <span class="mono">1.11.0.0</span>.</p></div>
</div>
<div class="rule">
<div class="rule-num">3</div>
<div class="rule-body"><p>Once a versioned release has been published, its contents MUST NOT be modified. Any modification MUST be published as a new version.</p></div>
</div>
<div class="rule">
<div class="rule-num">4</div>
<div class="rule-body"><p><strong>Public API.</strong> Covered above — see <a href="#public-api">Public API</a>.</p></div>
</div>
<div class="rule">
<div class="rule-num">5</div>
<div class="rule-body"><p><strong>BFIP's own public API.</strong> This specification is itself versioned under BFIP, and declares its own public API rather than relying on the default: the grammar for a valid version string (including the probation tag); the increment triggers for B, F, I, and P; the Field Reset, Monotonicity, Development Phase, Initial Public Release, and No Re-entry rules; the Compatibility and Precedence relations; and the Chronological and Dependency constraint languages built on them. Editorial wording, examples, the Introduction, the "Why Use BFIP" section, and the FAQ are not part of this public API, and may be revised without a version increment, provided no rule's normative meaning changes as a result.</p></div>
</div>
<div class="rule">
<div class="rule-num">6</div>
<div class="rule-body"><p>Breaking version zero (<span class="mono">0.f.i.p</span>) is for initial development. No public API is defined, and none is promised, at any F.I.P within it. B, F, I, and P still increment according to the rules below throughout this phase, but none of those increments carries any compatibility meaning until the project leaves it.</p></div>
</div>
<div class="rule">
<div class="rule-num">7</div>
<div class="rule-body"><p>The transition out of breaking version zero MUST land on exactly <span class="mono">1.0.0.0</span>, and MUST occur as a deliberate act of declaring the public API for the first time. It is not an ordinary B increment, since an ordinary B increment presupposes a public API already existed to break. Once B has reached 1 or higher, it MUST NOT return to 0.</p></div>
</div>
<div class="rule">
<div class="rule-num">8</div>
<div class="rule-body"><p>Patch version P (<span class="mono">b.f.i.P | b > 0</span>) MUST be incremented if a known bug is fixed without any change to the public API. A bug fix is defined as a change that corrects incorrect behavior without adding to, removing from, or otherwise altering the public API.</p></div>
</div>
<div class="rule">
<div class="rule-num">9</div>
<div class="rule-body"><p>Internal version I (<span class="mono">b.f.I.p | b > 0</span>) MUST be incremented if the implementation changes in a way that does not add to, remove from, or otherwise alter the public API — including changes made for performance, clarity, or maintainability. It MUST NOT be incremented for a change a user of the public API could observe. P MUST be reset to 0 when I is incremented.</p></div>
</div>
<div class="rule">
<div class="rule-num">10</div>
<div class="rule-body"><p>Feature version F (<span class="mono">b.F.i.p | b > 0</span>) MUST be incremented if new functionality is added to the public API without altering or removing any existing part of it. Existing user code MUST continue to work unmodified after this increment. I and P MUST be reset to 0 when F is incremented.</p></div>
</div>
<div class="rule">
<div class="rule-num">11</div>
<div class="rule-body"><p>Breaking version B (<span class="mono">B.f.i.p | B > 0</span>) MUST be incremented if any change to the public API would cause existing user code to stop working. F, I, and P MUST be reset to 0 when B is incremented.</p></div>
</div>
<div class="rule">
<div class="rule-num">12</div>
<div class="rule-body">
<p><strong>Monotonicity.</strong> B MUST NOT decrease across the life of a project. For a fixed B, F MUST NOT decrease. For a fixed B.F, I MUST NOT decrease across successive releases of that B.F. For a fixed B.F.I, P MUST NOT decrease across successive releases of that B.F.I.</p>
<p>Different B.F pairs may carry on independent, concurrently maintained I.P histories, published in any order relative to each other — BFIP does not require a single global publication order across separate B.F lines.</p>
</div>
</div>
</section>
<section id="probation">
<h2>Probation</h2>
<div class="rule" style="border-top:none; padding-top:0;">
<div class="rule-num">13</div>
<div class="rule-body">
<p>A version of the form <span class="mono">b.0.0.0</span> immediately following a B increment may be tagged as being on probation, by appending a number sign and a positive integer immediately following the patch version: <span class="version-string">b.0.0.0#n</span>. This tag MUST only be applied to a version with F, I, and P all equal to 0 immediately following a B increment. It MUST NOT be applied to any other version.</p>
<ol>
<li>n MUST NOT decrease across successive probation releases of the same <span class="mono">b.0.0.0</span>.</li>
<li>A probation-tagged version has strictly lower precedence than the plain version it decorates: <span class="mono">b.0.0.0#n < b.0.0.0</span>.</li>
<li>Between two probation-tagged releases of the same <span class="mono">b.0.0.0</span>, the one with the larger n has higher precedence: <span class="mono">b.0.0.0#n < b.0.0.0#m</span> whenever n < m.</li>
<li>A probation-tagged version MUST NOT be considered compatible with any other version, including the plain <span class="mono">b.0.0.0</span> it is on probation for and any version of a different breaking generation.</li>
<li>Probation ends by publishing the plain <span class="mono">b.0.0.0</span>, as a deliberate release act — not an automatic consequence of any increment rule. Once probation ends, the patch version begins its own, separate count from 0, independent of how high n climbed during probation.</li>
</ol>
</div>
</div>
<p style="margin-top:24px;">Probation exists for a narrower reason than the rest of the spec: moving to a new B declares a new, non-fungible generation of the public API — one that by definition cannot be compatible with what came before. A project that wants to trial that new generation before standing behind it now has an explicit way to say so.</p>
</section>
<section id="compatibility">
<h2>Compatibility & Precedence</h2>
<h3>Compatibility</h3>
<div class="rule" style="border-top:none;">
<div class="rule-num">14</div>
<div class="rule-body">
<p>Given a required version and an actual version, the actual version satisfies the required version if and only if:</p>
<ol>
<li>actual.breaking is equal to required.breaking, and</li>
<li>actual.feature is greater than or equal to required.feature.</li>
</ol>
<p>The internal and patch fields carry no compatibility meaning and MUST NOT affect this determination. A probation-tagged actual or required version MUST NOT be considered to satisfy, or be satisfied by, any version under this rule.</p>
</div>
</div>
<h3>Precedence</h3>
<div class="rule" style="border-top:none;">
<div class="rule-num">15</div>
<div class="rule-body">
<ol>
<li>Precedence MUST be calculated by separating the version into breaking, feature, internal, patch, and probation number, in that order.</li>
<li>Precedence is determined by the first difference when comparing breaking, feature, internal, and patch from left to right, each compared numerically. Example: <span class="mono">1.0.0.0 < 2.0.0.0 < 2.1.0.0 < 2.1.1.0 < 2.1.1.1</span>.</li>
<li>When breaking, feature, internal, and patch are all equal, a probation-tagged version has lower precedence than the plain version. Example: <span class="mono">2.0.0.0#1 < 2.0.0.0</span>.</li>
<li>When breaking, feature, internal, and patch are all equal and both versions are on probation, the one with the smaller probation number has lower precedence. Example: <span class="mono">2.0.0.0#1 < 2.0.0.0#2</span>.</li>
</ol>
</div>
</div>
</section>
<section id="constraints">
<h2>Constraints</h2>
<h3>Chronological constraints</h3>
<div class="rule" style="border-top:none;">
<div class="rule-num">16</div>
<div class="rule-body">
<p>Chronological constraints describe where a version falls relative to one or more fixed points in Precedence. A chronological constraint makes no claim about compatibility; it answers only where a version sits on the ordered line defined by Precedence — a version on probation is bound by the same constraint as any other version, with no exception.</p>
<table>
<tr><th>Form</th><th>Meaning</th></tr>
<tr><td class="mono">= V</td><td>satisfied only by V itself</td></tr>
<tr><td class="mono">> V / >= V</td><td>strictly greater / greater-or-equal precedence than V</td></tr>
<tr><td class="mono">< V / <= V</td><td>strictly lesser / lesser-or-equal precedence than V</td></tr>
</table>
<p>A bounded interval is the conjunction of one lower-bound and one upper-bound constraint. Example: <span class="mono">>= 1.2.0.0, < 2.0.0.0</span> is satisfied by <span class="mono">1.2.0.0</span>, <span class="mono">1.9.0.0</span>, and <span class="mono">1.9.0.0#4</span> — and is not satisfied by <span class="mono">1.2.0.0#1</span> or <span class="mono">2.0.0.0</span>.</p>
<p>Chronological constraints MUST NOT be used as a substitute for the Compatibility relation (Rule 14). A version satisfying a chronological constraint carries no guarantee of compatibility with any other version, including other versions satisfying the same constraint.</p>
</div>
</div>
<h3>Dependency constraints</h3>
<div class="rule" style="border-top:none;">
<div class="rule-num">17</div>
<div class="rule-body">
<p>Dependency constraints describe what a project may safely build against. Unlike a chronological constraint, a dependency constraint MUST reflect the Compatibility relation (Rule 14) wherever it makes a safety claim. No dependency constraint of any kind may be satisfied by a probation-tagged version.</p>
<ol>
<li><strong>Compatible-with</strong> (<span class="mono">~ V</span>) — satisfied by any actual version that satisfies V under Compatibility. This is the sole floor-and-ceiling primitive in BFIP: the ceiling is the next breaking version, implied automatically by Rule 14; the floor is V's feature version. It MUST NOT be given a second, artificial ceiling — intersect it with a chronological upper bound instead.</li>
<li><strong>Exact-version</strong> (<span class="mono">= V</span>) — the chronological exact constraint applied in a dependency context. Satisfied only by V itself; V must not be probation-tagged.</li>
<li>A bare minimum-version request is a compatible-with constraint. A bare maximum-version request, with no safety claim, is a chronological upper bound; one that must also preserve safety is the intersection of both.</li>
<li><strong>Intersection</strong> (<span class="mono">X AND Y</span>) — satisfied only by a version satisfying both. Intersecting two compatible-with constraints with different required breaking versions is always unsatisfiable — a legitimate outcome to detect at resolution time, not a malformed constraint.</li>
<li><strong>Union</strong> (<span class="mono">X OR Y</span>) — satisfied by a version satisfying X, Y, or both. The only combinator capable of expressing support for more than one breaking version at once.</li>
<li><strong>Negation</strong> (<span class="mono">NOT X</span>) — satisfied by any non-probation version not satisfying X.</li>
<li><strong>Exclusion</strong> (<span class="mono">X BUT NOT Y</span>) — shorthand for <span class="mono">X AND (NOT Y)</span>. Not an independent combinator.</li>
</ol>
</div>
</div>
</section>
<section id="grammar">
<h2>Grammar</h2>
<p>Backus–Naur form for a valid BFIP version string.</p>
<pre><code><valid bfip> ::= <version core>
| <version core> "#" <probation number>
<version core> ::= <breaking> "." <feature> "." <internal> "." <patch>
<breaking> ::= <numeric identifier>
<feature> ::= <numeric identifier>
<internal> ::= <numeric identifier>
<patch> ::= <numeric identifier>
<probation number> ::= <positive digit>
| <positive digit> <digits>
<numeric identifier> ::= "0"
| <positive digit>
| <positive digit> <digits>
<digits> ::= <digit>
| <digit> <digits>
<digit> ::= "0"
| <positive digit>
<positive digit> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"</code></pre>
</section>
<section id="faq">
<h2>FAQ</h2>
<div class="faq-item">
<h3>How should I handle revisions during breaking version zero?</h3>
<p>There is no wrong way, since nothing during this phase is a compatibility promise. A common approach is to start at <span class="mono">0.1.0.0</span> and bump F for each subsequent round of development, using I and P as you would after 1.0.0.0, purely for your own bookkeeping.</p>
</div>
<div class="faq-item">
<h3>How do I know when to release 1.0.0.0?</h3>
<p>If people are depending on your public API and expect it to keep working, you are already past the point where 1.0.0.0 should have shipped. Staying in 0.f.i.p past that point just means BFIP isn't yet telling your users anything about compatibility, even though they're relying on it as if it were.</p>
</div>
<div class="faq-item">
<h3>Won't projects with fast-moving public APIs blow through B quickly?</h3>
<p>Only if the public API is genuinely breaking that often, in which case that's useful information, not noise. Projects going through rapid, still-unstable churn have breaking version zero for exactly that reason, and probation exists for the case where a single upcoming B needs a trial period before it's trusted.</p>
</div>
<div class="faq-item">
<h3>What is the difference between an I bump and a P bump, concretely?</h3>
<p>A P bump corrects behavior that was wrong. An I bump changes how correct behavior is achieved — performance work, restructuring, dependency swaps, anything that leaves the public API's inputs and outputs exactly as they were. If nothing was broken to begin with, it isn't a patch; it's internal.</p>
</div>
<div class="faq-item">
<h3>What if I add something to the public API but also happen to fix a bug in the same release?</h3>
<p>Release it as an F bump. F increments already permit any amount of I- and P-level change riding along, since F is the higher field of the two — the reset rule takes care of I and P automatically.</p>
</div>
<div class="faq-item">
<h3>What if I accidentally ship a breaking change under F instead of B?</h3>
<p>Fix it and release the correction under a proper B bump as soon as you notice. Don't edit the offending release after the fact — publish the correction as a new version, and document the mistake if people may already depend on the broken F release.</p>
</div>
<div class="faq-item">
<h3>Can two different B.F lines really both keep releasing at once?</h3>
<p>Yes. Once a B.F pair exists, BFIP allows it to keep accumulating its own I and P history indefinitely, in parallel with newer B.F pairs, with no requirement that releases across different lines interleave in any particular order.</p>
</div>
<div class="faq-item">
<h3>If I write <span class="mono" style="font-size:0.92em;">< 2.0.0.0</span>, why does that also match 1.9.0.0's probation builds?</h3>
<p>Because a chronological constraint only asks where a version sits in publication order, and every probation build of 1.9.0.0 or earlier sorts below 2.0.0.0 by definition. It isn't asking whether that version is safe to depend on — for that, use the Compatibility relation, or a dependency constraint built on it.</p>
</div>
<div class="faq-item">
<h3>When would I reach for a chronological constraint instead of compatibility?</h3>
<p>Whenever the question is about the timeline rather than about safety: which versions have shipped since a given point, sorting a release history, deciding what "latest" means, or enforcing Monotonicity during publishing. The moment the question becomes "can I depend on this instead of that," you want Compatibility.</p>
</div>
<div class="faq-item">
<h3>Why doesn't a compatible-with constraint let me set my own upper bound?</h3>
<p>Because Rule 14 already gives it one for free: the next breaking version. Letting a dependency constraint override that would mean "compatible with" no longer always means the same thing. If you want a tighter ceiling, intersect a compatible-with constraint with a chronological upper bound instead — the guarantee stays intact, and the extra caution is visible in the constraint.</p>
</div>
<div class="faq-item">
<h3>Why can't I depend on an exact probation build?</h3>
<p>Because a dependency constraint is a promise about safety, and a probation build carries no such promise for anyone — Rule 13.4 already says it satisfies no compatibility relation at all. Testing against a specific probation build is legitimate, but it belongs to a project's own test tooling, not to a dependency declaration other projects might inherit unknowingly.</p>
</div>
<div class="faq-item">
<h3>Does BFIP have build metadata like Semantic Versioning?</h3>
<p>No. BFIP has exactly one extension to the plain four-field format — the probation tag — reserved for trialing an unreleased B. There's no mechanism for embedding build or provenance information in a BFIP version string.</p>
</div>
</section>
<footer>
<span>BFIP Versioning was created by Ananyo Bhattacharya. Licensed <a href="https://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a>.</span>
<span><a href="https://github.com/UniquePython/bfip-versioning">Source on GitHub</a></span>
</footer>
</main>
</div>
<script>
(function () {
var FIELD_NAMES = { b: 'Breaking', f: 'Feature', i: 'Internal', p: 'Patch' };
var FIELD_EXPLAIN = {
b: 'changes the public API in a way that breaks existing user code.',
f: 'adds new functionality to the public API without breaking existing user code.',
i: 'changes internals without touching the public API in any way.',
p: 'fixes a known bug without touching the public API in any way.'
};
var input = document.getElementById('version-input');
var content = document.getElementById('breakdown-content');
// grammar: B.F.I.P optionally followed by #n
var VERSION_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(#([1-9]\d*))?$/;
function render(raw) {
var trimmed = raw.trim();
var match = VERSION_RE.exec(trimmed);
if (!match) {
content.innerHTML = '<div class="breakdown-error">' +
(trimmed === '' ? 'Type a version number, e.g. 2.4.1.0' : '"' + escapeHtml(trimmed) + '" is not a valid BFIP version. Expected B.F.I.P, optionally followed by #n while on probation.') +
'</div>';
return;
}
var b = match[1], f = match[2], i = match[3], p = match[4], probation = match[6];
var html = '<div class="breakdown-display">';
html += fieldBlock('b', b);
html += '<span class="field-sep">.</span>';
html += fieldBlock('f', f);
html += '<span class="field-sep">.</span>';
html += fieldBlock('i', i);
html += '<span class="field-sep">.</span>';
html += fieldBlock('p', p);
if (probation) {
html += '<span class="field-sep">#</span>';
html += fieldBlock('probation', probation, true);
}
html += '</div>';
var explain = '<div class="breakdown-explain">';
if (b === '0') {
explain += '<strong>Breaking version zero.</strong> This is initial development — no public API is promised yet at any F.I.P within it.';
} else if (probation) {
explain += 'This is a <strong>probation build</strong> (#' + probation + ') trialing breaking version ' + b + ' before it is committed to. It is not compatible with anything, including the plain ' + b + '.0.0.0 it is on probation for.';
} else {
explain += 'Breaking generation <strong>' + b + '</strong>, feature level <strong>' + f + '</strong>' +
(i !== '0' || p !== '0' ? ', with internal/patch history <strong>' + i + '.' + p + '</strong>' : '') +
'. A dependency on <span class="mono" style="font-size:0.95em;">~' + b + '.' + f + '.0.0</span> is satisfied by this version.';
}
explain += '</div>';
content.innerHTML = html + explain;
}
function fieldBlock(key, value, isProbation) {
var cls = isProbation ? '' : key;
var label = isProbation ? 'Probation №' : FIELD_NAMES[key];
return '<div class="field-block">' +
'<span class="field-num ' + cls + '"' + (isProbation ? ' style="color:var(--ink-faint)"' : '') + '>' + escapeHtml(value) + '</span>' +
'<span class="field-label">' + label + '</span>' +
'</div>';
}
function escapeHtml(s) {
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
}
input.addEventListener('input', function () { render(input.value); });
render(input.value);
// ---------- TOC scrollspy ----------
var tocLinks = Array.prototype.slice.call(document.querySelectorAll('#toc a'));
var sections = tocLinks.map(function (a) {
return document.getElementById(a.getAttribute('href').slice(1));
});
function updateActive() {
var pos = window.scrollY + 120;
var activeIdx = 0;
for (var idx = 0; idx < sections.length; idx++) {
if (sections[idx] && sections[idx].offsetTop <= pos) activeIdx = idx;
}
tocLinks.forEach(function (a, idx) {
a.classList.toggle('active', idx === activeIdx);
});
}
window.addEventListener('scroll', updateActive, { passive: true });
updateActive();
})();
</script>
</body>
</html>