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
//! What a resumable run was recorded under, so a resume can refuse to replay it
//! under anything else.
//!
//! A run store's records are keyed by [`StepKey`], which is a digest of the
//! tenant and the step path. That answers "which step is this", and it answers
//! nothing about the three other things a recorded value is only meaningful
//! under: the definition that produced it, the input it was given, and the codec
//! its value is in. A task edited, a caller passing a different input, and a
//! step whose return type changed all keep every step path identical — so a
//! resume under any of them finds the record, and returns a value the new
//! definition never produced.
//!
//! [`DefinitionIdentity`] is the four facts that close that gap, and
//! [`Drift`] is what a disagreement between a recorded one and a live one is
//! called. They live beside the store rather than in it because the store's job
//! is to keep the bytes and the chain; what the bytes *mean* is the caller's
//! vocabulary, and a store that named the drift kinds would be naming a task's
//! schema.
//!
//! # Order and the step path
//!
//! The step path carries order (`alpha/beta` runs `beta` after `alpha`) but not
//! depth-by-depth reordering. Two adjacent durable steps swapped in the
//! definition keep every path, every key and every recorded value — each step
//! still returns the value it returned last time — so there is no identity
//! difference for the store to detect, and a `Drift` that claimed otherwise
//! would be refusing a resume that is in fact sound. The honest form of that
//! half of T15 is that reordering two whole *blocks* changes the paths, and that
//! is caught as [`Drift::Order`]: a definition revision that declares a
//! different step count is a different shape of flow, whether it arrived by
//! renaming or by insertion. What is deliberately not claimed: a same-revision
//! edit that permutes two siblings without changing the step count, which the
//! store cannot see and which is named here rather than left to be discovered.
//!
//! # Schema drift, and where the codec id comes from
//!
//! [`DefinitionIdentity::codec`] is a declared string, never
//! `std::any::type_name`: Rust documents `type_name` as diagnostic, so a module
//! rename would silently give every step a new identity. It is taken from
//! [`RunStore::with_codec`], which is what a caller passes the schema name a
//! step's durable values are written under; the default says "unversioned", and
//! a definition that adopts a real codec name does so as an explicit schema
//! change. `lgwks.bot.schema.v1.*` is the convention this crate already uses
//! for [`crate::effect::InputIdentity`].
//!
//! # Migration
//!
//! These fields are appended to the stored record, and the store's header
//! version is bumped with them: [`STORE_FORMAT`] is `\x02` where `\x01` named the
//! record without a definition identity. There is no migration path and there is
//! none planned, deliberately. An old file is refused at open as
//! [`StoreError::FormatVersion`], naming the
//! version it declares and the version this build reads, rather than half-read:
//! the only alternative is to read a record with no definition identity as one
//! that had the default identity — which would make every pre-version resume look
//! like an exactly-compatible one. It is a typed refusal of its own rather than
//! [`StoreError::NotAStore`], because that arm says
//! the bytes were never this store's and these were written by an earlier
//! version of this crate: telling an operator their data is not their own is
//! what makes someone delete a file a system is still relying on. The format has
//! never shipped a version that could lose a record, so there is nothing to
//! convert; a deployment that needs its records keeps its own copy and re-runs.
use fmt;
use ;
use crateSaturatingFrom;
/// What part of a replay's identity does not match the recorded one.
///
/// One typed arm per axis, because each names a different repair: a changed
/// definition wants a new run, a changed input wants a decision about whether
/// the same work applies, a changed codec wants a migration and a changed shape
/// wants an author. A single "incompatible" would leave the caller to guess
/// which of those it was looking at.
/// The schema id a record carries when its caller declared none.
///
/// "Unversioned" rather than the empty string, so a caller that reads it back
/// cannot mistake it for a schema it forgot to declare.
pub const UNVERSIONED_CODEC: &str = "lgwks.bot.schema.unversioned";
/// The four facts a recorded step value is only meaningful under.
///
/// Carried in every record, checked before anything new runs, and never derived
/// from a value that could change without the author meaning it to: the task
/// name is validated at declaration, the revision is declared by the caller,
/// the input digest is content-addressed, and the codec is a declared string.
/// The current on-disk format of a run store.
///
/// `\x02` is the record *with* a definition identity. `\x01` named a record that
/// carried the run, key, tenant, path and value only, and a file in that format
/// is refused at open rather than read as a record whose definition identity was
/// [`UNVERSIONED_CODEC`]: there is no migration, and inventing one would make
/// every pre-version resume look compatible rather than unprovable.
pub const STORE_FORMAT: u8 = 2;