monetize-product 0.3.0

The product plugin trait for monetize: how a metered product reports Usage and receives an Entitlement. Types and one trait, serde only — no ledger, no vendor, no network.
Documentation
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
//! **Rebuild the main from its twin — the guard** (Gunnar ca38566f, `gunnar-repair`
//! `rebuild::judge`, owner 2026-10-12: the twin is never promoted; a dead main is REBUILT:
//! its data is checked, reformatted only when it is destroyed, and filled from the twin).
//! Shared here by lane G11 (2026-10-21) so EVERY product's guardian — Gunnar's and Holger's,
//! linked and as signed guests — asks the same judgement before its plan destroys a main.
//! Pure, and in no product's types: each guardian builds a [`Seen`] from what its boxes
//! reported ([`seen_of`]) and its twin's own words ([`Twin`]).
//!
//! **What may destroy a main's data** is the provider deleting its data storage (the
//! runner's `Fence` step gives the old box back WHOLE), or a wipe medium. The guard sits in
//! front of both:
//!
//! * **R1 — evidence comes from where the data lives, never from silence.** Destroyed is
//!   the provider saying the box is GONE, the box itself answering `fresh`/`lost`, or the
//!   installer refusing its data set under the KEEP medium. A main that only does not
//!   report is not destroyed.
//! * **R2 — a silent box that stands is booted again first** (start it, or once from its
//!   keep medium): good data comes back as it was.
//! * **R3 — the twin can give it back**: it says it can, and it does not still hear a
//!   primary.
//! * **R4 — the main is not ahead**: a main that last reported a log head above the
//!   twin's holds entries the twin lacks; only a human accepting the loss goes on.
//! * **R5 — bound to what was seen**: a reformat names the storage it was judged on;
//!   a confirmation naming another storage is no confirmation.
//! * **R6 — auto-fix never reformats a standing disk.** It may lay a box that is GONE and
//!   fill a box that SAYS it is empty; a human decides the rest.
//!
//! **Over the reports** (what a guardian has): the provider's word is the main's
//! `BoxView::at_provider`, which the runner fills for a silent main monetize laid
//! (`active` / `stopped` / `gone`; empty: monetize laid no box there, or has no cloud to
//! ask — an operator's estate, whose kit asks the provider and runs this same judgement
//! with a typed confirmation before it destroys anything). The main's log head is its
//! last reported `data_head`. A guardian cannot boot a box again and has no typed
//! confirmation, so from the ONE box a main is laid again only once it is GONE
//! ([`new_server`]); the rest is a person's, by name.

use serde::{Deserialize, Serialize};

use super::{role, EstateView};

/// What the provider says about the main's server.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Provider {
    /// The server (or its data storage) is gone: 404.
    Gone,
    /// Stopped (or maintenance): it was not asked to run.
    Stopped,
    /// Started.
    Running,
    /// The provider could not be read, or said something else (in words).
    Unknown(String),
}

/// What the main itself last said, or that it says nothing.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Main {
    /// It does not answer (no report within the silence bound, no control plane).
    Silent,
    /// It answers: its `data` health word (`ok` / `fresh` / `lost` / `diverged`).
    Says { data: String },
}

/// A human's confirmation of a reformat: the data storage they read, typed back.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Confirm {
    /// Who (for the record).
    pub by: String,
    /// The storage uuid they confirmed destroying.
    pub storage: String,
    /// They accept losing what the twin never got (R4).
    #[serde(default)]
    pub accept_lost: bool,
}

/// **The twin, as the guard needs it** — in neither product's types (Gunnar's
/// `TwinReview`, Holger's `TwinStatus`, or the twin's health words).
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct Twin {
    /// Where it answers (for the words, and the plan's `twin`).
    pub addr: String,
    /// It still hears a primary (R3: a main it hears is not dead).
    pub hears_primary: bool,
    /// It can give the estate back now (R3).
    pub recharge_possible: bool,
    /// Its own words: why not, or how level it is.
    pub verdict: String,
    /// The highest log entry it holds, when the product has a log (R4); `None`: no log
    /// head to compare (Holger), never "ahead".
    pub holds: Option<u64>,
}

/// Everything the guard reads. Built by each caller from its own channels.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Seen {
    pub provider: Provider,
    pub main: Main,
    /// The highest log head the main ever reported (`data_head`), if known.
    #[serde(default)]
    pub main_head: Option<u64>,
    /// The main was already booted again (started, or once from its keep medium) since it
    /// went silent, and stays silent (R2).
    #[serde(default)]
    pub booted_again: bool,
    /// The installer, booted with the keep medium, refused the main's data set (its words).
    #[serde(default)]
    pub installer_refused: Option<String>,
    /// The twin; `None` when no twin answered.
    pub twin: Option<Twin>,
    /// The main's data storage as the provider lists it (R5); empty when unknown.
    #[serde(default)]
    pub data_storage: String,
    /// A human's confirmation, if one was given.
    #[serde(default)]
    pub confirm: Option<Confirm>,
    /// The caller is the estate's auto-fix (no human in the loop).
    #[serde(default)]
    pub auto: bool,
}

/// What to do, in words a person reads.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "do")]
pub enum Rebuild {
    /// The main serves its data: nothing is rebuilt.
    Healthy { why: String },
    /// The server is stopped: start it. It keeps its disk and rejoins as it was.
    StartIt { why: String },
    /// The server runs and is silent: boot it once more (keep medium) and look again.
    BootAgain { why: String },
    /// The main answers with no data set: fill it from the twin. Nothing is destroyed.
    Fill { twin: String, why: String },
    /// The main is gone at the provider: lay it anew, then fill it. Nothing is left to destroy.
    LayAgain { twin: String, why: String },
    /// The main's data is destroyed: give its storage `storage` back (reformat), lay it
    /// anew, fill it from `twin`.
    Reformat { storage: String, twin: String, why: String },
    /// Not without a person, or not at all: why, by name.
    Refused { code: String, why: String },
}

impl Rebuild {
    /// The one word for logs and health (`healthy`, `start_it`, …).
    pub fn word(&self) -> &'static str {
        match self {
            Rebuild::Healthy { .. } => "healthy",
            Rebuild::StartIt { .. } => "start_it",
            Rebuild::BootAgain { .. } => "boot_again",
            Rebuild::Fill { .. } => "fill",
            Rebuild::LayAgain { .. } => "lay_again",
            Rebuild::Reformat { .. } => "reformat",
            Rebuild::Refused { .. } => "refused",
        }
    }

    /// Does this step destroy anything at the main?
    pub fn destroys(&self) -> bool {
        matches!(self, Rebuild::Reformat { .. })
    }

    /// The words.
    pub fn why(&self) -> &str {
        match self {
            Rebuild::Healthy { why }
            | Rebuild::StartIt { why }
            | Rebuild::BootAgain { why }
            | Rebuild::Fill { why, .. }
            | Rebuild::LayAgain { why, .. }
            | Rebuild::Reformat { why, .. }
            | Rebuild::Refused { why, .. } => why,
        }
    }
}

/// Refusal codes, stable for callers and tests (Gunnar's).
pub mod code {
    /// No twin answered: nothing to rebuild from.
    pub const NO_TWIN: &str = "no_twin";
    /// The twin still hears a primary: a second main would be split brain.
    pub const TWIN_HEARS_A_MAIN: &str = "twin_hears_a_main";
    /// The twin cannot give everything back now (its own verdict).
    pub const TWIN_NOT_READY: &str = "twin_not_ready";
    /// The main holds entries the twin lacks (R4).
    pub const MAIN_AHEAD: &str = "main_ahead";
    /// The main reports a diverged data set.
    pub const DIVERGED: &str = "diverged";
    /// A standing disk is reformatted only by a person (R6).
    pub const AUTO_NEVER_REFORMATS: &str = "auto_never_reformats";
    /// A person must confirm by typing the storage back (R5).
    pub const CONFIRM_STORAGE: &str = "confirm_storage";
    /// The provider's word is not one the guard can act on.
    pub const PROVIDER_UNKNOWN: &str = "provider_unknown";
}

fn refused(code: &str, why: impl Into<String>) -> Rebuild {
    Rebuild::Refused { code: code.into(), why: why.into() }
}

/// **R3 + R4**: may the twin fill a new main now? `Ok(twin address)`, or the refusal.
fn twin_may_fill(s: &Seen) -> Result<String, Rebuild> {
    let Some(t) = s.twin.as_ref() else {
        return Err(refused(code::NO_TWIN, "no twin answered: there is nothing to rebuild the main from — a human looks first (provider console, last receipts)"));
    };
    if t.hears_primary {
        return Err(refused(
            code::TWIN_HEARS_A_MAIN,
            format!("the twin at {} still hears its primary: a main it can hear is not dead — a rebuild beside it would be a second writable main", t.addr),
        ));
    }
    if !t.recharge_possible {
        return Err(refused(code::TWIN_NOT_READY, format!("the twin at {} cannot give the estate back now: {}", t.addr, t.verdict)));
    }
    let accepted = s.confirm.as_ref().is_some_and(|c| c.accept_lost);
    if let (Some(head), Some(twin_has)) = (s.main_head, t.holds) {
        if head > twin_has && !accepted {
            return Err(refused(
                code::MAIN_AHEAD,
                format!("the main last reported log head {head}, the twin holds {twin_has}: {} entr(y/ies) exist only on the main's disk — a person accepts that loss (accept_lost), or the main's disk is brought back", head - twin_has),
            ));
        }
    }
    Ok(t.addr.clone())
}

/// **The guard**: what may be done about the main, given what was seen (Gunnar's
/// `rebuild::judge`, rule for rule).
pub fn judge(s: &Seen) -> Rebuild {
    match &s.main {
        Main::Says { data } => match data.as_str() {
            "ok" => Rebuild::Healthy { why: "the main answers and reports its data whole: nothing is rebuilt".into() },
            "diverged" => refused(code::DIVERGED, "the main reports its data diverged from the twin's log: a person compares the two before either is touched"),
            // The box itself says it holds no data set (R1): fill it, nothing to destroy.
            "fresh" | "lost" => match twin_may_fill(s) {
                Ok(twin) => Rebuild::Fill { twin, why: format!("the main answers and reports data={data}: it is filled from the twin, nothing on it is destroyed") },
                Err(r) => r,
            },
            other => refused(code::PROVIDER_UNKNOWN, format!("the main reports data={other:?}, a word the guard does not act on")),
        },
        Main::Silent => match &s.provider {
            Provider::Stopped => Rebuild::StartIt { why: "the main is stopped at the provider: start it — it keeps its disk and rejoins as it was; nothing is rebuilt".into() },
            Provider::Gone => match twin_may_fill(s) {
                Ok(twin) => Rebuild::LayAgain { twin, why: "the main is GONE at the provider: it is laid anew from its medium and filled from the twin (nothing of it is left to destroy)".into() },
                Err(r) => r,
            },
            Provider::Unknown(w) => refused(code::PROVIDER_UNKNOWN, format!("the provider's word on the main is not known ({w}): nothing is destroyed on a guess")),
            Provider::Running if !s.booted_again && s.installer_refused.is_none() => {
                Rebuild::BootAgain { why: "the main runs at the provider and is silent: it is booted once more from its keep medium — a disk with its data comes back as it was; only a box that stays silent is judged again".into() }
            }
            Provider::Running => {
                let twin = match twin_may_fill(s) {
                    Ok(t) => t,
                    Err(r) => return r,
                };
                if s.auto {
                    return refused(code::AUTO_NEVER_REFORMATS, "the main's server stands with its disk: auto-fix never reformats a standing disk — a person confirms it (the kit, the ONE box, or the console)");
                }
                let evidence = match &s.installer_refused {
                    Some(w) => format!("the installer refused its data set under the keep medium ({w})"),
                    None => "it was booted again and stays silent".to_string(),
                };
                match s.confirm.as_ref() {
                    Some(c) if !s.data_storage.is_empty() && c.storage.trim() == s.data_storage => Rebuild::Reformat {
                        storage: s.data_storage.clone(),
                        twin,
                        why: format!("the main's data is destroyed: {evidence}; {} confirmed storage {} — it is given back, the main laid anew and filled from the twin", c.by, s.data_storage),
                    },
                    Some(c) => refused(code::CONFIRM_STORAGE, format!("the confirmation names storage {:?}, the main's data storage is {:?}: nothing is destroyed", c.storage, s.data_storage)),
                    None => refused(code::CONFIRM_STORAGE, format!("the main's data looks destroyed ({evidence}); a person confirms by typing its data storage back: {}", if s.data_storage.is_empty() { "(unknown)" } else { &s.data_storage })),
                }
            }
        },
    }
}

// ---------------------------------------------------------------------------------------
// Over the reports: what a guardian has.
// ---------------------------------------------------------------------------------------

/// The provider's word on a box (`BoxView::at_provider`), as the guard reads it. Empty is
/// "not asked" — never a guess.
pub fn provider_of(word: &str) -> Provider {
    match word.trim() {
        "gone" => Provider::Gone,
        "stopped" | "maintenance" => Provider::Stopped,
        "active" | "started" | "running" | "provisioning" => Provider::Running,
        "" => Provider::Unknown("the provider was not asked".into()),
        other => Provider::Unknown(other.into()),
    }
}

/// The main as the guard reads its reports: silent, or its `data` word (a restore under
/// way is `lost`, Gunnar's `main_of`), and the last log head it reported (`data_head`,
/// kept from its last report while it is silent).
pub fn main_of(v: &EstateView) -> (Main, Option<u64>) {
    let b = v.get(role::MAIN);
    let head = b.and_then(|b| b.word("data_head").trim().parse().ok());
    if v.silent(role::MAIN) {
        return (Main::Silent, head);
    }
    let data = match b.map(|b| b.word("data")).unwrap_or("") {
        "restoring" => "lost",
        w => w,
    };
    (Main::Says { data: data.into() }, head)
}

/// The twin's address as a plan names it (its `serves_at`, else where it was laid).
pub fn twin_addr(v: &EstateView) -> String {
    v.get(role::TWIN).map(|b| if b.word("serves_at").is_empty() { b.laid_ipv4.clone() } else { b.word("serves_at").to_string() }).unwrap_or_default()
}

/// **The guard's [`Seen`] over an estate's reports.** `twin` is the product's reading of its
/// twin (`None` when no twin reports); the guardian has no confirmation and cannot boot a box.
pub fn seen_of(v: &EstateView, twin: Option<Twin>, auto: bool) -> Seen {
    let (main, main_head) = main_of(v);
    let provider = provider_of(v.get(role::MAIN).map(|b| b.at_provider.as_str()).unwrap_or(""));
    Seen { provider, main, main_head, booted_again: false, installer_refused: None, twin, data_storage: String::new(), confirm: None, auto }
}

/// **May auto-fix lay the main again?** Only where the guard says [`Rebuild::LayAgain`]:
/// the main is GONE at the provider and the twin can give it back (R1, R3, R4, R6).
pub fn auto_new_server(v: &EstateView, twin: Option<Twin>) -> Rebuild {
    judge(&seen_of(v, twin, true))
}

/// **May a person's New server run?** `Ok` with the guard's words, or why not. Monetize
/// laid the main (the provider's word is filled): only a main GONE at the provider is laid
/// again — a standing one is started or booted again first, and a standing disk is
/// reformatted only on a typed confirmation, which the ONE box's button does not carry
/// (the kit's `estate rebuild main` does). Monetize laid no main there (an operator's
/// estate; no provider word): the twin's half of the guard decides here, and the operator's
/// kit asks the provider and confirms before it destroys anything.
pub fn new_server(v: &EstateView, twin: Option<Twin>) -> Result<String, String> {
    let mut s = seen_of(v, twin, false);
    let operator = v.get(role::MAIN).is_none_or(|b| b.at_provider.trim().is_empty());
    if operator && s.main == Main::Silent {
        s.provider = Provider::Gone;
    }
    match judge(&s) {
        Rebuild::LayAgain { why, .. } if operator => Ok(format!("{why} — on this estate the operator's kit asks the provider and confirms before anything is destroyed")),
        Rebuild::LayAgain { why, .. } => Ok(why),
        Rebuild::Fill { why, .. } => Err(format!("{why}: press Recharge from twin — New server would destroy a box that answers")),
        other => Err(other.why().to_string()),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::guardian::BoxView;

    const STORAGE: &str = "01aa-data";
    const TWIN_AT: &str = "10.0.0.3:50051";

    fn twin(hears: bool, possible: bool, holds: u64) -> Twin {
        Twin { addr: TWIN_AT.into(), hears_primary: hears, recharge_possible: possible, verdict: if possible { "level".into() } else { "behind its primary".into() }, holds: Some(holds) }
    }

    fn seen(provider: Provider, main: Main) -> Seen {
        Seen { provider, main, main_head: Some(100), booted_again: false, installer_refused: None, twin: Some(twin(false, true, 100)), data_storage: STORAGE.into(), confirm: None, auto: false }
    }

    fn says(d: &str) -> Main {
        Main::Says { data: d.into() }
    }

    fn me(storage: &str) -> Option<Confirm> {
        Some(Confirm { by: "rickard".into(), storage: storage.into(), accept_lost: false })
    }

    /// R1: a main that answers with its data whole is never touched, whatever else is seen.
    #[test]
    fn a_main_with_its_data_is_never_rebuilt() {
        for p in [Provider::Running, Provider::Gone, Provider::Stopped] {
            let mut s = seen(p, says("ok"));
            s.confirm = me(STORAGE);
            assert_eq!(judge(&s).word(), "healthy");
            assert!(!judge(&s).destroys());
        }
    }

    /// R1 + R2: silence is never enough. A stopped box is started, a running one booted again.
    #[test]
    fn silence_alone_destroys_nothing() {
        assert_eq!(judge(&seen(Provider::Stopped, Main::Silent)).word(), "start_it");
        let mut s = seen(Provider::Running, Main::Silent);
        s.confirm = me(STORAGE);
        assert_eq!(judge(&s).word(), "boot_again", "even a confirmation does not skip booting it again");
        assert_eq!(judge(&seen(Provider::Unknown("timeout".into()), Main::Silent)).word(), "refused");
    }

    /// R1: a main that SAYS it is empty is filled; nothing is destroyed. Auto-fix may.
    #[test]
    fn a_main_that_says_it_is_empty_is_filled_and_auto_may() {
        for d in ["fresh", "lost"] {
            let mut s = seen(Provider::Running, says(d));
            s.auto = true;
            match judge(&s) {
                Rebuild::Fill { twin, .. } => assert_eq!(twin, TWIN_AT),
                other => panic!("{d}: {other:?}"),
            }
        }
        assert_eq!(judge(&seen(Provider::Running, says("diverged"))).word(), "refused");
    }

    /// A box gone at the provider is laid again; auto-fix may, nothing is left to destroy.
    #[test]
    fn a_gone_main_is_laid_again_and_auto_may() {
        let mut s = seen(Provider::Gone, Main::Silent);
        s.auto = true;
        assert_eq!(judge(&s).word(), "lay_again");
    }

    /// R6 + R5: a standing disk is reformatted only by a person who types its storage back.
    #[test]
    fn a_standing_disk_is_reformatted_only_on_a_typed_confirmation() {
        let mut s = seen(Provider::Running, Main::Silent);
        s.booted_again = true;
        let r = judge(&s);
        assert!(matches!(&r, Rebuild::Refused { code, .. } if code == code::CONFIRM_STORAGE), "{r:?}");
        assert!(r.why().contains(STORAGE), "the person is told which storage: {r:?}");
        s.confirm = me("01bb-other");
        assert!(matches!(judge(&s), Rebuild::Refused { code, .. } if code == code::CONFIRM_STORAGE), "another storage's name is no confirmation");
        s.confirm = me(STORAGE);
        match judge(&s) {
            Rebuild::Reformat { storage, twin, .. } => assert_eq!((storage.as_str(), twin.as_str()), (STORAGE, TWIN_AT)),
            other => panic!("{other:?}"),
        }
        s.auto = true;
        assert!(matches!(judge(&s), Rebuild::Refused { code, .. } if code == code::AUTO_NEVER_REFORMATS), "auto-fix never, even confirmed");
        let mut i = seen(Provider::Running, Main::Silent);
        i.installer_refused = Some("member 1 of 2 is missing".into());
        i.confirm = me(STORAGE);
        assert_eq!(judge(&i).word(), "reformat", "the installer's refusal of the data set is evidence too");
        i.data_storage.clear();
        assert_eq!(judge(&i).word(), "refused", "an unknown storage cannot be confirmed");
    }

    /// R3: no twin, a twin that still hears a primary, or a twin not ready — nothing moves.
    #[test]
    fn the_twin_must_be_able_to_give_it_back() {
        for (t, want) in [(None, code::NO_TWIN), (Some(twin(true, true, 100)), code::TWIN_HEARS_A_MAIN), (Some(twin(false, false, 90)), code::TWIN_NOT_READY)] {
            for (p, m) in [(Provider::Gone, Main::Silent), (Provider::Running, says("fresh"))] {
                let mut s = seen(p, m);
                s.twin = t.clone();
                assert!(matches!(judge(&s), Rebuild::Refused { ref code, .. } if code == want), "{want}: {:?}", judge(&s));
            }
        }
    }

    /// R4: a main that reported more than the twin holds is refused until a person accepts the loss.
    #[test]
    fn a_main_ahead_of_its_twin_needs_a_person_to_accept_the_loss() {
        let mut s = seen(Provider::Gone, Main::Silent);
        s.main_head = Some(120);
        let r = judge(&s);
        assert!(matches!(&r, Rebuild::Refused { code, .. } if code == code::MAIN_AHEAD), "{r:?}");
        assert!(r.why().contains("20 entr"), "{r:?}");
        s.confirm = Some(Confirm { by: "rickard".into(), storage: String::new(), accept_lost: true });
        assert_eq!(judge(&s).word(), "lay_again");
        s.main_head = None;
        s.confirm = None;
        assert_eq!(judge(&s).word(), "lay_again", "an unknown head is not 'ahead'");
        s.main_head = Some(120);
        s.twin.as_mut().unwrap().holds = None;
        assert_eq!(judge(&s).word(), "lay_again", "a twin with no log head to compare (Holger) is not 'behind'");
    }

    /// Only Reformat destroys (the JSON shape is held by the guests' parity tests, which
    /// carry a JSON codec; this crate has serde alone).
    #[test]
    fn only_a_reformat_destroys() {
        assert!(Rebuild::Reformat { storage: STORAGE.into(), twin: "t".into(), why: "w".into() }.destroys());
        for r in [Rebuild::Fill { twin: "t".into(), why: String::new() }, Rebuild::LayAgain { twin: "t".into(), why: String::new() }, Rebuild::BootAgain { why: String::new() }, Rebuild::StartIt { why: String::new() }] {
            assert!(!r.destroys());
        }
    }

    // --- over the reports -----------------------------------------------------------------

    const NOW: u64 = 1_800_000_000_000;

    fn estate(main_heard_ago: u64, main_words: &[(&str, &str)], at_provider: &str) -> EstateView {
        let health = main_words.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
        let main = BoxView { role: "main".into(), heard_unix_ms: NOW - main_heard_ago, health, at_provider: at_provider.into(), ..Default::default() };
        EstateView { product: "p".into(), instance: "acme".into(), now_unix_ms: NOW, silent_after_ms: 90_000, boxes: [("main".to_string(), main)].into_iter().collect() }
    }

    fn blind() -> Option<Twin> {
        Some(Twin { addr: TWIN_AT.into(), hears_primary: false, recharge_possible: true, verdict: "level".into(), holds: None })
    }

    /// The provider's word, read: empty is never a guess.
    #[test]
    fn the_providers_word_is_read_and_empty_is_not_asked() {
        assert_eq!(provider_of("gone"), Provider::Gone);
        assert_eq!(provider_of("stopped"), Provider::Stopped);
        assert_eq!(provider_of("active"), Provider::Running);
        assert!(matches!(provider_of(""), Provider::Unknown(_)));
        assert!(matches!(provider_of("unknown (timeout)"), Provider::Unknown(_)));
    }

    /// The main off its reports: silent keeps its last `data_head`; restoring reads as lost.
    #[test]
    fn the_main_is_read_off_its_reports() {
        assert_eq!(main_of(&estate(600_000, &[("data", "ok"), ("data_head", "900")], "")), (Main::Silent, Some(900)));
        assert_eq!(main_of(&estate(1_000, &[("data", "restoring")], "")), (says("lost"), None));
        assert_eq!(main_of(&estate(1_000, &[("data", "fresh"), ("data_head", "0")], "")), (says("fresh"), Some(0)));
    }

    /// ★ **Row 5's gap, closed for every guardian**: silence alone never lays a main again by
    /// auto-fix. Only a main GONE at the provider is (R1, R6); a standing one is booted
    /// again first (R2); an unasked provider is no evidence.
    #[test]
    fn auto_fix_lays_a_silent_main_again_only_when_the_provider_says_it_is_gone() {
        assert_eq!(auto_new_server(&estate(600_000, &[], "gone"), blind()).word(), "lay_again");
        assert_eq!(auto_new_server(&estate(600_000, &[], "active"), blind()).word(), "boot_again");
        assert_eq!(auto_new_server(&estate(600_000, &[], "stopped"), blind()).word(), "start_it");
        assert_eq!(auto_new_server(&estate(600_000, &[], ""), blind()).word(), "refused", "not asked is not gone");
        assert_eq!(auto_new_server(&estate(600_000, &[], "gone"), None).word(), "refused", "no twin");
    }

    /// A person's New server: a gone main is laid again, a standing one is not (no typed
    /// confirmation on the button), an answering one is recharged instead, and on an
    /// operator's estate (no provider word) the twin's half decides and the kit confirms.
    #[test]
    fn a_persons_new_server_destroys_no_standing_disk() {
        assert!(new_server(&estate(600_000, &[], "gone"), blind()).is_ok());
        let standing = new_server(&estate(600_000, &[], "active"), blind()).unwrap_err();
        assert!(standing.contains("booted once more"), "{standing}");
        let answers = new_server(&estate(1_000, &[("data", "fresh")], "active"), blind()).unwrap_err();
        assert!(answers.contains("Recharge from twin"), "{answers}");
        assert!(new_server(&estate(1_000, &[("data", "ok")], "active"), blind()).is_err(), "a healthy main is never laid again");
        let operator = new_server(&estate(600_000, &[], ""), blind()).unwrap();
        assert!(operator.contains("operator's kit"), "{operator}");
        let mut hears = blind();
        hears.as_mut().unwrap().hears_primary = true;
        assert!(new_server(&estate(600_000, &[], ""), hears).is_err(), "the twin still hears its main: not on an operator's estate either");
    }
}