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
//! T10 Task 10: the namespace repair pass — see the spec's own "`repair` and
//! adoption bootstrap" section
//! (`docs/superpowers/specs/26-08-10-unique-index-cas-reservation.md`) for
//! the design rationale, and `docs/schema-metadata.md`'s "Unique namespace"
//! section for the user-facing contract (declaration, guarantees,
//! boundaries, findings) this module implements.
//!
//! **A namespace is repaired by its own pass, never the generic one.** T8's
//! [`crate::projection::reconcile_carrier_projections`] builds its expected
//! set from *one* source's scan and deletes everything else in the target —
//! correct for an index fed by one producer, destructive for a namespace fed
//! by several. A namespace is not a [`crate::projection::ProjectableIndex`]
//! (see `unique/index.rs`'s own module doc for why), so `IndexTargets` cannot
//! even hold one and the generic reconcile can never reach it structurally —
//! `carrier_reconcile_never_touches_a_namespace` in
//! `armdb/tests/unique_validate_repair.rs` pins the observable half of that
//! at the call-site level (not just the type level), because the failure mode
//! of a future refactor accidentally reaching a namespace from there would be
//! silent.
//!
//! The expected set is the same union-of-every-declared-source
//! ([`super::expected::build_unique`]) `validate`'s own namespace pass reads,
//! and every key is decided with the very same rule the write path uses
//! ([`crate::unique::resolve::decide`]) — so `validate` and this pass can
//! never disagree about what a namespace should hold, only about what they do
//! once they know it: report, or write.
//!
//! `decide` is always called here with [`crate::txn::participant::ClaimKind::Claim`]
//! — repair only ever fills a *missing* claim, it never releases one — so of
//! its four possible decisions this pass reaches exactly three:
//!
//! - `Write` — the row is put through the crate-private [`crate::unique::ClaimTarget`]
//! seam (`write_row`), never a public API (`UniqueIndex` has none). Covers
//! both a fresh claim (adoption's own case: a namespace opens empty, every
//! claim reads as this) and a reservation this same claimant already holds
//! being promoted to a permanent claim.
//! - `Conflict` — refused: a winner cannot be chosen without the business
//! rule, so nothing is written for that key and the occurrence is recorded
//! — the same shape `Finding::UniqueConflict`/T8's `DbError::IndexKeyCollision`
//! use (the key plus every owner involved, see [`NamespaceConflict`]),
//! except a namespace conflict does not abort the whole repair, it only
//! skips that one key (every key is decided independently, mirroring
//! `validate`'s own per-key pass).
//! - `NoOp` — already correct; no stats bump.
//! - `Delete` — [`crate::unique::resolve::decide`]'s own doc: only
//! `ClaimKind::Release` ever decides this. Reaching it here would mean this
//! module started passing the wrong `ClaimKind`, so it is reported as
//! `DbError::Internal` rather than silently repaired or panicked on — the
//! same "must never crash on an unreachable state" discipline
//! `registry::run_decls` uses for the identical case in `validate`.
//!
//! A **plain orphan** (no declared claimant at all, on a *permanent* row) is
//! deleted only under [`super::SchemaRegistry::complete_namespace`] +
//! [`super::RepairOptions::delete_orphans`] — reusing the exact gate
//! `complete_index`/`IndexOrphan` already uses (same underlying
//! `complete_indices` set), because under partial adoption the other,
//! not-yet-declared sources' live claims are indistinguishable from orphans
//! even with the union rule.
//!
//! **A `Decl::Unique` whose source is not a registered [`super::registry::CollectionEntry`]
//! is never read as "this source claims nothing".** `expected::build_unique`
//! silently skips such a declaration (mirroring `expected::build`/
//! `build_counters` — a `validate`-only report-only tolerance), which on its
//! own would make every one of that source's live, permanent claims in its
//! declared namespace(s) look like a plain orphan against an empty expected
//! set. This pass additionally protects those namespaces the same way
//! `repair.rs`'s counter pass already protects an unregistered counter
//! source's targets — see the `src_entry` guard just above `Decl::Counter`'s
//! repair loop in `repair.rs`.
//!
//! A **reservation** with no declared claimant is never touched, in either
//! mode: an absent owner is its normal state, not drift, and its own bound is
//! the deadline — deleting it early would race a legitimate future claim.
//!
//! A **corrupt row** (fails [`crate::UniqueRow::is_well_formed`]) is excluded
//! from this pass entirely, exactly as `validate`'s own missing/conflict/
//! orphan pass excludes it — its `key_len` cannot be trusted, so it must
//! never reach `decide`. Repair leaves it exactly as `validate` reports it
//! (`Finding::UniqueRowCorrupt`); it is neither deleted nor overwritten, even
//! when a live source row declares a claim on the same key (a corrupt row
//! reads as "absent" to a naive per-key pass, which would otherwise silently
//! overwrite it via `ClaimDecision::Write` — this pass tracks corrupt keys
//! per namespace and removes them from scope entirely, the same technique
//! `registry::run_decls` uses).
//!
//! `UndecodableRows` holds back the destructive half exactly as it does for
//! `repair`'s index/counter passes: a source whose scan skipped rows makes
//! this namespace's expected set short, so its live, correctly-owned rows
//! would look like orphans — deletion is suppressed for the *whole* namespace
//! those rows feed, regardless of `complete_namespace`/`delete_orphans`.
//! Missing claims from *other*, fully-decodable sources into the same
//! namespace are still written — only the destructive half is held back.
//!
//! A namespace scan failure (`ClaimTarget::scan_rows` returning `Err`, an
//! I/O-level failure) propagates via `?` out of [`repair_namespaces`] and
//! therefore out of [`super::repair::repair`] — same as every other hard
//! failure in this module. Because this pass runs last (`repair`'s pass 5,
//! after the index and counter passes), any writes those earlier passes
//! already made have already landed on disk; this is not a new failure shape
//! for `repair` — the counter pass's own `expected::build_counters(reg)?`
//! already aborts the same way, after the index passes' writes.
use ;
use crate;
use crateClaimKind;
use crate;
use ;
use ;
use RepairOptions;
use hex;
/// Per-namespace repair counters (T10 Task 10) — see the module doc for what
/// drives each field. Mirrors [`super::IndexRepairStats`] in shape.
/// One namespace key `repair_namespaces` refused to write to, because more
/// than one live claimant disagreed about who owns it — either two or more
/// declared claimants, or one declared claimant against the row's actual
/// (foreign) owner. Mirrors `Finding::UniqueConflict`'s shape exactly.
/// Repair every declared unique namespace — the T10 twin of
/// [`super::repair::repair`]'s index/counter passes. Returns per-namespace
/// stats plus the sources whose scan skipped undecodable rows, which the
/// caller merges into [`super::repair::RepairReport::undecodable_sources`]
/// exactly as its own counter pass merges into the index pass's list (same
/// source may feed both an index/counter and a namespace).
pub