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
//! RFC 119 track C — Gate G1, pre-1.0 form: a release does not *silently* break what earlier
//! releases wrote.
//!
//! **This file is `format_stability_gate.rs`'s missing sibling** (RFC 119 §10 track C, §2 of the
//! handoff): that gate guards *format version bumps*; `0.23.0`'s `Tag` break went through the hole
//! beside it -- an in-place payload amendment at the *same* schema version, for which no format
//! bump occurs, so Gate B never fires. Same three-layer shape, same discipline ("a gate is only
//! trusted once it has been observed failing," `rfc111_index_decode_cost_gate.rs`), same
//! frozen-fixture precedent (`crates/prikk-cli/tests/fixtures/dc55_pre_swap_repo`).
//!
//! **The guarantee is against silence, not against breaking.** Breaking is permitted when
//! authorized and declared -- `0.23.0`'s own `Tag` amendment was exactly that. The outcome per
//! persisted object type is ternary:
//! - **compatible** -- decodes cleanly under current code;
//! - **breaking, declared** -- does not, and [`DECLARED_BREAKS`] names it, with a reason and a
//! remedy;
//! - **breaking, undeclared** -- the only failure.
//!
//! **The reverse direction -- an old binary reading data current code wrote -- is declined, not
//! deferred** (RFC 119 `reverse-direction-test-declined` handoff v1). Forward is the direction
//! that protects users, since users upgrade; a schema bump breaks the reverse *by design* (a
//! deliberate format change, not an accident -- [`DeclaredBreak`]'s own doc names the concrete
//! example); and a test built to fail on every deliberate format change would train its own
//! allowlist to grow unexamined -- "recorded, not rejected," the pattern this project already
//! rejects elsewhere. Declining rests on one verified fact: the failure mode is fail-closed.
//! `format.rs::validate_format2_schema` refuses an unadmitted schema with a specific `Integrity`
//! error naming the object type, the schema found, and the accepted set -- an old binary *refuses*
//! unrecognized data, it does not misread it. `CHANGELOG.md`'s per-release reverse-break statement
//! already tells a downgrading user what changed; building and caching an old binary per platform,
//! in CI, for the direction that matters least, is not worth that cost. **What this gives up**: an
//! *accidental* reverse break goes undetected until someone downgrades, surfacing then as a clear,
//! specific refusal -- never silent corruption. Acceptable at pre-1.0, not forever. **Void if**:
//! prikk ever supports downgrade as a documented workflow, or a future format change lets an old
//! binary *misinterpret* new bytes rather than refuse them -- the second voids this outright, since
//! the whole ruling rests on failing closed.
//!
//! **Fixture**: `crates/prikk-cli/tests/fixtures/rfc119_g1_0_25_0_repo`, a real repository written
//! by the real `0.25.0` binary (built from the `0.25.0` git tag in an isolated worktree, never from
//! this working tree). **Do not regenerate it.** See the RFC 119 track C report for the original
//! construction technique, and the G1-fixture-refresh-`0.25.0` report for this specific fixture.
//!
//! **Replaced, not accumulated (RFC 119 track C's own follow-up, `g1-fixture-refresh-0-24-0`):**
//! this gate holds exactly one fixture, from the last release, and each release's own refresh
//! replaces it rather than adding a second baseline to check alongside it. Pre-1.0, no production
//! users, and G1's own form is *declare*, not *prevent* -- one baseline is proportionate. **What
//! this means a reader should not assume**: a future release passing this gate proves it reads the
//! *immediately preceding* release's data, not every retained release's data -- transitivity does
//! not hold (`0.26.0` reading `0.25.0`, and `0.25.0` reading `0.24.0`, does not imply `0.26.0` reads
//! `0.24.0`). The `0.24.0`-vintage fixture this replaced is gone; its own coverage and controls are
//! recorded in the G1-fixture-refresh-`0.24.0` report, not restated here.
//!
//! **Schemas unchanged from `0.24.0`'s own fixture** (`admitted_schemas` has not moved since): this
//! refresh's schema-version arrays are identical to the ones it replaced --
//! `last_release_fixture_coverage_matches_the_committed_counts`'s committed values did not need to
//! change, only the fixture bytes and this file's own path constant did. **That makes provenance
//! the only evidence this refresh happened at all** -- nothing in this test suite can distinguish a
//! genuinely rebuilt fixture from the old one with its directory renamed, since a schema-array
//! change (like `0.24.0`'s own first coverage of `Patch` schema 2, `PATCH_PARENT_IDS_RETIRED_SCHEMA`)
//! is not available this time to serve as incidental evidence. The G1-fixture-refresh-`0.25.0`
//! report is where that provenance is recorded (worktree commit, `--version` output).
//! **Asserted, not only claimed**: `last_release_fixture_coverage_matches_the_committed_counts`
//! pins every persisted type's observed `schema_version`s, not only a record count -- a future
//! fixture rebuild that silently regressed to all-schema-1 `Patch` records would fail that test.
use ;
use ;
use cratedecode_container_records;
use crateread_file_if_exists;
use crate;
use cratedecode_patch_operations;
/// The release this gate's fixture was built from. [`last_release_fixture_root`] derives its path
/// from this rather than carrying the version as a second, independent literal; every
/// `DeclaredBreak`'s [`DeclaredBreak::older_version`] is checked against this too (RFC 119
/// `g1-declared-break-version-scope` handoff v1 §4) -- the fixture is always *the last release*
/// and the gate always compares it against *current* code, so `<this constant> -> current` is the
/// only version pair a declared break can ever describe here.
const LAST_RELEASE_FIXTURE_VERSION: &str = "0.25.0";
/// One declared **forward-direction** compatibility break between two adjacent releases -- the
/// shape of Gate A's `frozen`/`RFC114_ADMITTED_BUT_UNWRITTEN` pair and
/// `format_stability_gate.rs`'s `FORMATS_WITH_MIGRATION_COVERAGE`: a committed list, never
/// satisfiable by editing alone (the object type must be real, checked below), always carrying a
/// reason and a remedy.
///
/// **Forward direction only, deliberately** (RFC 119 `g1-fixture-refresh-0-24-0` handoff §2): this
/// gate's own mechanism only ever checks *newer code reading an older fixture* -- there is no
/// mechanism here for the reverse (an old binary reading new data), which would need an actual old
/// binary invoked as a subprocess, not this gate's Rust-function-call shape (deferred, see this
/// module's own report). A **reverse** break -- `0.24.0`'s own `Patch` schema 2 is exactly one: it
/// reads `0.23.0` fine, but `0.23.0` cannot read `0.24.0` -- must **not** be added here: doing so
/// would assert a forward break that does not exist, and this gate would then look for a failure
/// its own fixture can never produce. Reverse breaks are recorded in `CHANGELOG.md`'s own "Breaking
/// change" section instead, where every one to date already is.
///
/// **Version-scoped, not just object-type-scoped** (RFC 119 `g1-declared-break-version-scope`
/// handoff v1 §1/§3): an entry only excuses a failure when [`DeclaredBreak::older_version`] equals
/// [`LAST_RELEASE_FIXTURE_VERSION`] *and* the object type matches. The fixture is always the last
/// release and the gate always compares it against current code, so an entry whose older side is
/// any other version cannot apply to anything this gate actually checks -- keeping one anyway
/// would let it silently excuse an unrelated live failure on a matching object type forever, which
/// is the defect this version scoping exists to close. `every_declared_break_applies_to_the_current_fixture`
/// makes a stale entry fail loudly rather than sit inert.
/// **Empty, deliberately** (RFC 119 `g1-declared-break-version-scope` handoff v1 §4 adjudication:
/// retire, don't relocate). `0.23.0`'s own `Tag` break (RFC 119 track C handoff §5) was this list's
/// first and, until this refresh, only entry -- but its `older_version` is `0.22.1`, which no
/// longer equals [`LAST_RELEASE_FIXTURE_VERSION`], so it can no longer apply to anything this gate
/// checks. Keeping it as a second, gate-adjacent historical record -- rather than retiring it --
/// would be exactly the "system reasoning about itself" duplication RFC 119 exists to remove:
/// `CHANGELOG.md`'s own `0.23.0` "Breaking change" section is already the complete, load-bearing
/// record of that break (this entry's own `reason`/`remedy` text was quoted from it verbatim, not
/// independently derived), so nothing is lost by not also carrying it here. **Empty-but-ready**,
/// the same shape `format_stability_gate.rs`'s own `FORMATS_WITH_MIGRATION_COVERAGE` starts in --
/// this becomes non-empty the day a release breaks a persisted type against *this gate's own*
/// fixture version, not on a general "some day" basis.
const DECLARED_BREAKS: & = &;
/// The frozen last-release fixture's root (the directory *containing* `.prikk`, matching
/// `RepositoryLayout::open`'s own expectation). Derived from [`LAST_RELEASE_FIXTURE_VERSION`]
/// rather than carrying the version as a second literal inside the path string.
/// Read one persisted object type's live (slot A -- compaction never runs on object containers,
/// per `ContainerSlot`'s own doc) container file and decode every record with the *same* decoder
/// each type's real production read path uses -- `Patch` via `decode_patch_operations` (the real
/// general decoder, patch-schema-2 handoff), everything else via its own `XPayload::decode_canonical`.
/// Returns each decoded record's own `schema_version`, in container order, or the first decode
/// failure's message. **The returned `Vec`'s length is the record count** -- callers that only need
/// the count (this file has none left; both former call sites now use the schema list itself, per
/// `g1-fixture-refresh-0-24-0`'s review condition) read `.len()` rather than a separate count.
/// Layer 1 (mirroring `format_stability_gate.rs`'s own layer 2): every declared break must name a
/// real persisted object type, so a typo or a stale entry cannot silently exempt nothing.
/// Layer 2 (RFC 119 `g1-declared-break-version-scope` handoff v1 §4): every declared break must
/// apply to *this* fixture, not merely name a real object type. The fixture is always the last
/// release and the gate always compares it against current code, so `older_version` must equal
/// [`LAST_RELEASE_FIXTURE_VERSION`] -- an entry that fails this check cannot excuse anything the
/// gate actually verifies and must be retired or updated, not left to sit inert (§1/§3: sitting
/// inert is how it silently excuses an unrelated future break on a matching object type).
/// The real conformance check: every persisted object type in the last-release fixture must either
/// decode cleanly under current code, or have its failure covered by [`DECLARED_BREAKS`] for *this*
/// fixture's own version. This is the test the four controls exercise (RFC 119 track C handoff §7,
/// `g1-fixture-refresh-0-25-0` handoff §7); it currently passes because nothing has changed any
/// persisted type's decode contract since `0.25.0` shipped -- the same "nothing to test yet" state
/// `format_stability_gate.rs`'s own layers 1/2 start in.
/// Coverage is the gate's real specification (handoff §4/§8): committed literal counts per
/// persisted object type, so removing coverage is caught here, distinctly from a decode failure --
/// the same "committed, never generated at test time" discipline `dc55_identity_evidence.rs`'s own
/// `every_frozen_object_id_matches_its_own_filename` count uses.
///
/// **Pins the observed `schema_version` of every record, not only the count**
/// (`g1-fixture-refresh-0-24-0` review condition): a record count alone cannot tell a schema-1
/// `Patch` from a schema-2 one, so it could not have caught a future fixture rebuild that silently
/// stopped covering schema 2 -- exactly the coverage this fixture exists to add (this module's own
/// top doc). Each slice's length *is* the expected count; there is no separate count to keep in
/// sync. Values derived by running the real decode against the committed fixture and reading back
/// what it reported, not hand-computed -- the same "committed, never generated at test time"
/// discipline applies to the *values* here, not just their presence.