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
//! **The version preface** (REMOTE §3, bl-a670): the first frame each end
//! writes on a connection, carrying the one fact it states about itself.
//!
//! Until the four-component split (REMOTE §12) one crate shipped both ends of
//! every connection, so the wire could not skew and needed no version. Four
//! separately installed components can skew, so it carries one.
//!
//! **Three properties, each a refusal of something easier.**
//!
//! - **Both ends state, and either end may refuse.** Each writes its preface
//! before it reads the peer's, so neither waits on the other and a mismatch
//! is nameable from whichever side notices first. An engine refusing a seat
//! and a seat refusing an engine are one rule with one sentence, not two
//! halves that can disagree.
//! - **No negotiation.** No version list, no capability probe, no compat shim:
//! a mismatch is fail-closed and the sentence — which names *both* versions —
//! is the upgrade prompt. Negotiation is the mechanism that makes every later
//! version carry every earlier one's shape forever, and it buys nothing here
//! because the operator installs both ends.
//! - **The request frame is untouched.** The preface rides *beside* the gesture
//! envelope, never inside it, so the frame the wire carries stays byte for
//! byte the frame the `gestures/` inbox carries (REMOTE §3: the wire adds
//! nothing to the boundary) and the codec gains no field.
//!
//! **ALPN was the alternative, and it cannot say this.** rustls will refuse a
//! handshake whose application protocol does not match, at no cost and with no
//! frame — but that refusal is a TLS alert, so neither end learns the other's
//! version and an operator reads a transport error where a sentence belongs.
//! Naming both versions is the requirement, so the preface is in band.
//!
//! **It costs no round trip.** Each end writes its preface and only then reads
//! the peer's, and the seat writes its request in the same breath as its
//! preface — so the check travels with bytes that were already going, and the
//! only connection it stops is one that was going to be refused anyway.
use ;
use json;
use frame;
/// The protocol this build speaks.
///
/// **One integer, and a new verb is not a bump.** A `Query`, an `Action` or a
/// reply kind the peer has not heard of already refuses in band, naming it
/// (REMOTE §3's strict decode) — which is the boundary correcting itself, not
/// two protocols meeting. This changes when the *existing* shape changes
/// meaning: the framing, the envelope, or what a spelling already in use is
/// taken to say.
///
/// 4 → 5 (bl-e654): the `governing` reply's `branch` key — *the lineage whose
/// tip the frozen commit still is* — is gone, replaced by `follows` and
/// `diverged_lineages`, and the `oid` beside them stopped meaning the fork
/// commit and started meaning the commit control resolves from. Same verb,
/// same question, a different thing said: exactly the case above.
///
/// 5 → 6 (bl-23bd): `reply/providers` rows gained `effort` and `priority`, the
/// two per-row tuning capabilities a seat decides a control's existence by. The
/// **two new ops beside them cost nothing** — `/effort` and `/priority` are new
/// spellings in an existing vocabulary, and §3's rule is that a peer which has
/// not heard of one already refuses it in band by name. One bump for the row,
/// and nothing else shape-changing is batched behind it: clients re-vendor per
/// bump, and this number walked 2 → 5 inside one unreleased cycle already.
/// 6 → 7 (bl-8758): every `reply/help` row gained `surface`, the word saying
/// whether a seat-class client owes that op a control (`docs/PARITY.md` §2).
/// The ledger would have let it through — help's signature last moved at 1, so
/// its one free move at 6 was unspent — but §3's rule is the authority and is
/// stricter than the mechanism: any wire-visible shape change, *gained
/// included*, bumps the version, and the ledger cannot see what has shipped
/// (REMOTE §9.9's correction). It is also the bump that pays for itself: the
/// classification is the artifact clients vendor and judge themselves against,
/// so a client must re-vendor to read it, and a bump is exactly what makes it.
/// 7 → 8 (bl-66d4): `reply/advertised` gained `wrote`, the word saying whether
/// this engine WROTE the advertised set or found it identical and compared. It
/// is required rather than optional-absent-reads-false, because absent would
/// read as *"nothing was restored"* — the reassuring answer — on exactly the
/// build that cannot tell, and the field exists to make one event audible.
/// 8 → 9 (bl-015b): `reply/transcript` gained the `wounded` entry — the §8.5
/// settled-failure notice, the third virtual entry — and `reply/steps` LOST
/// `auth_failed`, the §8.3 affordance now being the `refused` arm of the wound
/// vocabulary both shapes spell. A gain and a loss on two shapes, which is
/// two of the four things §3 says bump the version; the ledger's one free move
/// would have covered neither, since it cannot see what has shipped.
/// 9 → 10 (bl-09aa): **no field moved, and that is why this bump is the rule
/// rather than an exception to it.** `attention` became follow-class (REMOTE
/// §14.1): the same ask, the same reply shape, but a *sequence* — the first
/// frame at connect, a further frame whenever the answer changes, the
/// terminator when the hold ends. That is precisely "what a spelling already in
/// use is taken to say", and it is the one class of change the corpus ledger
/// cannot see, since frame count is not a field signature. A seat built against
/// 9 would read the first frame and then wait on a terminator up to a hold
/// away; strict equality here is what turns that into an upgrade sentence.
/// 10 → 11 (bl-4d81): `reply/ops` gained the three readings a §7.3 failure
/// banner is made of — `failed`, `exit_label` and `standing` — so the row
/// answers what it *is* and not only what was logged. A field gained on a shape
/// already in use, which §3's rule bumps outright; and the bump is the point
/// rather than a tax, since the whole gain is a classification that reaches a
/// client only through a re-vendor.
/// 11 → 12 (bl-09ef): every queue row — `reply/attention` and the
/// `reply/acknowledged` remainder that spells rows the same way — gained
/// `says`, the firing rules **in words**. The escalation those words existed
/// for was ruled a **seat's** act (DESIGN §6: a desktop notification belongs on
/// the box the operator is looking at), and `AttentionKind::says` stays this
/// engine's one home for the sentence, so the sentence has to cross rather than
/// be re-worded per seat. A gain on two shapes, which the ledger sees and §3
/// bumps for regardless.
/// 12 → 13 (bl-dc3f): `reply/config` gained `settings` — the file's own schema
/// applied to the text answered beside it (§9.5), so a config destination is
/// read as the typed thing it is and not as bytes a seat must parse. A field
/// gained on a shape in use, which §3's rule bumps outright; **the ledger could
/// not have caught it**, since `reply/config`'s signature had stood at 1 and
/// its one free move would have covered exactly this — REMOTE §9.15's
/// correction, applied again. It does **not** batch onto 12 the way §9.15
/// batched two reads: 12 landed on `main` ahead of this, and whether a release
/// captures it before this lands is a race no reader could resolve later —
/// batching is the exception, and an exception taken against a running release
/// train is how a version comes to mean two things.
pub const PROTOCOL: u32 = 13;
/// The preface's one key, and the whole of its shape.
const KEY: &str = "protocol";
/// What a peer that stated no version is called in the sentence. An
/// unversioned build, a peer that hung up mid-preface and noise are one case
/// on purpose: none of them can be served, and telling them apart would be
/// three sentences for one outcome.
const UNSTATED: &str = "no version";
/// Write this build's preface. Called before either end reads, which is what
/// makes the exchange deadlock-free without an ordering rule to remember.
pub
/// The version the peer stated, or `None` when it stated none — a frame that
/// never arrived, a frame that is not an object, and an object without the key
/// collapsing to the one answer a reader can act on.
/// Whether what the peer stated is this build's own protocol.
/// The refusal, said the same way at both ends: both versions, and what to do
/// about it. It is the upgrade prompt, so it names the remedy rather than
/// leaving an operator to infer one from a number.
/// **The engine's half**: state, read, and either admit the peer or refuse it
/// in band on the connection it opened.
///
/// `false` is the whole of the refusal — the caller drops the connection and
/// never decodes a frame of another protocol, so no gesture of a version this
/// build does not speak is ever adjudicated. A refusal that could not be
/// written (a peer already gone) changes nothing: the answer is the same.
pub
/// **The seat's half**: read the engine's preface and refuse a mismatch to the
/// caller, as one `Err(String)`.
///
/// `cfg(test)` since bl-7942: no seat ships in this crate any more, and the one
/// client left in the tree is the suite's own
/// ([`test_support::wire::Seat`](crate::test_support::wire)), which has to
/// speak the whole protocol or it would prove the listener against a dialect.
/// The *rule* it implements is still the server's — [`stated`] writes the same
/// preface — so it belongs beside it rather than in the harness.
pub