nordisk-pairing 0.5.28

The boxes of an estate find each other: a seed each box is installed with, a one-time join against the front, a tunnel (WireGuard by default) they meet inside, roles decided once, a pairing loop each box runs itself, and the signal it reports. Shared by gunnar and holger; the laptop's xtask only lays machines, writes seeds and reads the signal. Design: .nornir/pairing-design.md (2026-09-24).
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
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
//! **The front does the update its shop opened** (0.5.26, P4a; A22 W8, A28 §4 "update step"):
//! the ONE update step every product's front runs, moved here from Holger's front
//! (`holger-estate-front/src/update.rs`, the newer of the two: lane B5's [`Declared`] and its keep
//! fallback) and Gunnar's (`gunnar-front/src/update.rs`), which carried the same `decide`,
//! `on_trial`, `staged_of` and `wait_confirmed`. Feature `frontside-update`.
//!
//! The operator opens an update at the ONE box (`EstateOrder.UpdateEstate`); the front PULLS it
//! (`SoldEstate.UpdateTake`, behind [`Shop`]) and does it one box at a time, in the shop's order
//! twin → main → front, reporting each (`SoldEstate.UpdateReport`) before the next is handed out:
//!
//! ```text
//!   twin / main:  GET /live/running on the box's log door (the kept installer material)
//!     runs the release's app already         → done:nothing
//!     same BASE, live on, the release's app   → fetched (sha256, length, pin checked),
//!                                               POST /live/stage, waited for until confirmed
//!                                                                         → done:live
//!                                               the old app back, no trial  → failed (rolled back)
//!     a stage refused (korp's legs)           → failed (nothing moved)
//!     a stage refused for a keep boot only    → SoldEstate.Upgrade (keep medium) → done:keep
//!       (lane B5: the release env moved, a declaration the box keeps does not ride, or
//!        the box takes none live — `crate::live::falls_to_keep`)
//!     BASE moved, live off, no live app       → SoldEstate.Upgrade (keep medium) → done:keep
//!   front (itself, its own slot):
//!     runs it already (staged here before)    → done:live (else done:nothing)
//!     same BASE, live on                      → staged into its own slot: it re-execs, and
//!                                               reports on its next look, running the new app
//!     its own stage rolled back (the marker,  → failed (rolled back), never staged again
//!       the old app, no trial)
//!     else                                    → needs-keep (the shop keep-boots the front)
//! ```
//!
//! [`step`] is the decision and the sequence over [`Hands`] (the boxes' live doors, the fetch,
//! the keep upgrade, a clock) — pure enough for tests; [`look`] is one look over a [`Shop`].
//! A take the shop refuses (out of turn, no update open) is its words. Nothing here names a
//! product: the front's keep medium is the product's words ([`Hands::front_keep`]).

use std::time::Duration;

use crate::live::{Declared, Running, Stage};

/// How often a staged box is read again.
pub const POLL_EVERY: Duration = Duration::from_secs(10);
/// How long a staged box has to confirm (its trial is 60 s of serving; korp-start rolls a box
/// that never serves back at 600 s).
pub const CONFIRM_BUDGET: Duration = Duration::from_secs(15 * 60);
/// A box running its OLD app with no trial this long after the stage was rolled back.
pub const ROLLED_BACK_AFTER: Duration = Duration::from_secs(60);
/// The marker the front leaves before it stages ITSELF: after its re-exec it reports `live`.
pub const STAGED_SELF_REL: &str = "update/staged-self";

/// One live app as the shop hands it (`EstateLiveApp`).
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LiveApp {
    pub base: String,
    pub app: String,
    pub app_len: u64,
    /// hex
    pub sig: String,
    pub url: String,
    pub sha256: String,
    /// Lane B5: the release declaration's bundle and its signature, hex (empty: none / unsigned).
    pub release: String,
    pub release_sig: String,
    /// Lane B5: the release's `korp.env.*` tokens (empty: none declared).
    pub env: Vec<String>,
}

/// The update the shop handed out (`EstateUpdateTakeResponse`).
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Take {
    pub open: bool,
    pub release: String,
    pub live: bool,
    /// `twin` | `main` | `front`.
    pub role: String,
    /// The live app main and twin run (the release's product app), while live is on.
    pub app: Option<LiveApp>,
    /// The front's own live app.
    pub front: Option<LiveApp>,
    pub words: String,
}

/// The ONE box's update door, as this front calls it.
pub trait Shop {
    /// `SoldEstate.UpdateTake`.
    fn take(&self) -> Result<Take, String>;
    /// `SoldEstate.UpdateReport`: `role`'s step, `outcome` and `words` signed together.
    fn report(&self, role: &str, outcome: &str, words: &str) -> Result<String, String>;
}

/// What this front can do to a box.
pub trait Hands {
    /// `GET /live/running` of `role` (the front: its own slot).
    fn running(&self, role: &str) -> Result<Running, String>;
    /// `POST /live/stage` to `role` with what the release declares (the front: its own slot —
    /// it then re-execs).
    fn stage(&self, role: &str, stage: &Stage, declared: &Declared) -> Result<String, String>;
    /// The archive at the presigned url.
    fn fetch(&self, url: &str) -> Result<Vec<u8>, String>;
    /// `role` (main or twin) upgraded from the release's keep medium and answering again.
    fn keep(&self, role: &str) -> Result<String, String>;
    /// Wait `d` (a test's clock moves instead).
    fn wait(&self, d: Duration);
    /// Was the front's own stage of `release` left here before its re-exec? (and forget it)
    fn staged_self(&self, release: &str) -> bool;
    /// Leave the marker before the front stages itself.
    fn mark_staged_self(&self, release: &str) -> Result<(), String>;
    /// **The product's words for the front's keep boot** (`needs-keep`): what the shop
    /// keep-boots, and from which medium (`<product>'s front from <product>-front-keep`).
    fn front_keep(&self) -> String {
        "the front from its keep medium".into()
    }
    /// `roles` (main, twin or both) RE-ADMITTED from the kept bundle where they stand, in ONE
    /// run (`crate::kept::readmit_together`): the second half of a keep upgrade, for boxes that
    /// booted their upgraded system and were never re-admitted (Gunnar's front, gunnar.rs
    /// 2026-10-25; every product's since T16). A front that keeps no bundle refuses by name.
    fn readmit(&self, roles: &[&str]) -> Result<String, String> {
        Err(format!("REFUSED [readmit-unsupported] this front re-admits no box ({})", roles.join(" and ")))
    }
}

/// The refusal of a box's OPEN install listener (`crate::bootstrap::respond`: no `/live/*`
/// there): the box booted a fresh system and waits for its installer's feed.
pub const OPEN_INSTALL_REFUSAL: &str = "[install-path]";

/// Pure: **does `role`'s live door answer as a box waiting for its re-admission?** A main or
/// twin whose keep medium ran and whose upgraded system booted (its pairing state cleared, its
/// data kept) answers `GET /live/running` with the open listener's refusal until the front feeds
/// it (gunnar.rs main, 2026-10-25). Moved from Gunnar's front (T16).
pub fn awaits_readmission(role: &str, door_said: &str) -> bool {
    role != "front" && door_said.contains(OPEN_INSTALL_REFUSAL)
}

/// What the re-admission done first left: the box read again, the re-admission's words, and
/// whether this step's own box was among the re-admitted.
type Readmitted = (Result<Running, String>, Option<String>, bool);

/// **Boxes left waiting for their re-admission are re-admitted FIRST, and TOGETHER** (Gunnar's
/// front, gunnar.rs 2026-10-25; T16 for every product): this box and the other box of the
/// estate alike, in one [`Hands::readmit`]. A re-admission re-feeds every other box of the kept
/// plan, and a box at its open install listener never takes a re-feed (the twin's keep upgrade
/// waited 3606 s on such a main). Then this box is read again; the step goes on from what it
/// runs. A door that answers anything else is left to the step as it answered.
fn readmit_first(h: &dyn Hands, role: &str, first: Result<Running, String>) -> Result<Readmitted, Report> {
    let peer = if role == "main" { "twin" } else { "main" };
    let mut waiting: Vec<&str> = Vec::new();
    let mut said = String::new();
    match &first {
        Err(e) if awaits_readmission(role, e) => {
            waiting.push(role);
            said = e.clone();
        }
        Err(_) => return Ok((first, None, false)),
        Ok(_) => {}
    }
    let peer_busy = crate::frontside::busy::on(peer);
    if let Err(e) = h.running(peer) {
        if awaits_readmission(peer, &e) {
            waiting.push(peer);
            if said.is_empty() {
                said = e;
            }
        }
    }
    if waiting.is_empty() {
        return Ok((first, None, false));
    }
    let back = h.readmit(&waiting).map_err(|why| (FAILED.to_string(), format!("{} answer(s) as a box never re-admitted after its keep boot ({said}); the re-admission: {why}", waiting.join(" and "))))?;
    drop(peer_busy);
    let me = waiting.contains(&role);
    let again = if me { h.running(role) } else { first };
    match again {
        Ok(r) => Ok((Ok(r), Some(back), me)),
        Err(again) => Err((FAILED.to_string(), format!("its keep boot was never re-admitted: {back}; its live door did not answer after: {again}"))),
    }
}

pub const DONE_KEEP: &str = "done:keep";
pub const DONE_LIVE: &str = "done:live";
pub const DONE_NOTHING: &str = "done:nothing";
pub const NEEDS_KEEP: &str = "needs-keep";
pub const FAILED: &str = "failed";

/// What is done to one box ([`decide`]).
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Plan {
    /// It runs the release's app already.
    Nothing,
    /// The release's app staged live into its slot.
    Live,
    /// Its BASE moved, live is off, or the release has no live app: the keep medium.
    Keep(String),
    /// A trial is still open on it: asked again on the next look.
    Wait(String),
}

/// Whether a slot word still has a trial open (`b trial`, `b booted`).
fn on_trial(slot: &str) -> bool {
    slot.split_whitespace().nth(1).is_some()
}

/// Pure: **what one box gets** — its running app against the release's live app.
pub fn decide(r: &Running, app: Option<&LiveApp>, live: bool) -> Plan {
    let Some(app) = app else {
        return Plan::Keep(if live { "the release carries no live app for it".into() } else { "live updates are OFF for this estate".into() });
    };
    if on_trial(&r.slot) {
        return Plan::Wait(format!("[live-trial-open] slot {}: its last update is not confirmed yet", r.slot));
    }
    if r.app.trim() == app.app.trim() {
        return Plan::Nothing;
    }
    if !live {
        return Plan::Keep("live updates are OFF for this estate".into());
    }
    if !r.live_update {
        return Plan::Keep("live update is OFF on the box (korp.live_update=off, its BASE)".into());
    }
    if r.base.trim() != app.base.trim() {
        return Plan::Keep(format!("its BASE moved ({} → {}): a base update", short(&r.base), short(&app.base)));
    }
    Plan::Live
}

fn short(h: &str) -> &str {
    &h[..h.len().min(12)]
}

fn unhex(s: &str) -> Result<Vec<u8>, String> {
    let s = s.trim();
    if !s.len().is_multiple_of(2) {
        return Err("odd hex".into());
    }
    (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| e.to_string())).collect()
}

/// **The release's app, fetched and proven** — its sha256 as the shop named it, its length and
/// its pin as the release recorded — and the stage the box takes.
pub fn staged_of(h: &dyn Hands, app: &LiveApp) -> Result<Stage, String> {
    if app.sig.trim().is_empty() {
        return Err("[update-unsigned] the release's live app carries no signature: never staged".into());
    }
    let archive = h.fetch(&app.url)?;
    let sha: String = ring::digest::digest(&ring::digest::SHA256, &archive).as_ref().iter().map(|b| format!("{b:02x}")).collect();
    if !app.sha256.trim().is_empty() && sha != app.sha256.trim() {
        return Err(format!("[update-fetch] the archive's sha256 is {}, the release says {}", short(&sha), short(&app.sha256)));
    }
    if archive.len() as u64 != app.app_len {
        return Err(format!("[update-fetch] the archive is {} bytes, the release says {}", archive.len(), app.app_len));
    }
    let pin = blake3::hash(&archive).to_hex().to_string();
    if pin != app.app.trim() {
        return Err(format!("[update-fetch] the archive's pin is {}, the release says {}", short(&pin), short(&app.app)));
    }
    Ok(Stage { pin_hex: app.app.trim().to_string(), signature: unhex(&app.sig).map_err(|e| format!("[update-unsigned] the signature: {e}"))?, base: app.base.trim().to_string(), archive })
}

/// **What the release declares beside `app`** (lane B5): its release declaration and its env,
/// as the box is handed them. A declaration that is not hex is refused by name.
pub fn declared_of(app: &LiveApp) -> Result<Declared, String> {
    let release = match app.release.trim() {
        "" => None,
        b => Some((unhex(b).map_err(|e| format!("[update-release] the release declaration: {e}"))?, unhex(&app.release_sig).map_err(|e| format!("[update-release] its signature: {e}"))?)),
    };
    Ok(Declared { release, env: (!app.env.is_empty()).then(|| app.env.clone()) })
}

/// **Wait until `role` runs `app` confirmed**; the old app back with no trial is a rollback.
pub fn wait_confirmed(h: &dyn Hands, role: &str, app: &str, old: &str) -> Result<String, String> {
    let mut waited = Duration::ZERO;
    let mut last = String::new();
    while waited < CONFIRM_BUDGET {
        h.wait(POLL_EVERY);
        waited += POLL_EVERY;
        match h.running(role) {
            Ok(r) if r.app.trim() == app.trim() && !on_trial(&r.slot) => return Ok(format!("the {role} runs app {} confirmed (slot {})", short(app), r.slot)),
            Ok(r) if r.app.trim() == old.trim() && !on_trial(&r.slot) && waited >= ROLLED_BACK_AFTER => {
                return Err(format!("[update-rolled-back] the {role} runs its old app {} again (slot {}): the new one never confirmed", short(old), r.slot))
            }
            Ok(r) => last = format!("slot {}, app {}", r.slot, short(&r.app)),
            // The box is down while it re-execs: asked again.
            Err(e) => last = e,
        }
    }
    Err(format!("[update-not-confirmed] the {role} did not confirm within {} min ({last})", CONFIRM_BUDGET.as_secs() / 60))
}

/// What a step reports: the outcome word and the words.
pub type Report = (String, String);

/// **One box's step**: the box whose turn it is, updated, and what to report — `None` when
/// nothing is to be reported now (no update open; a trial still open; the front staged itself
/// and reports after its re-exec).
pub fn step(h: &dyn Hands, t: &Take) -> Option<Report> {
    if !t.open || t.role.is_empty() {
        return None;
    }
    let role = t.role.as_str();
    if !matches!(role, "twin" | "main" | "front") {
        return Some((FAILED.into(), format!("this front updates twin, main and front, not {role:?}")));
    }
    // The box this step works on is BUSY until it ends: the recharge watcher leaves it alone
    // (lane G5, A7 row 10; Gunnar aa14357b).
    let _busy = crate::frontside::busy::on(role);
    let done = |how: &str, words: String| Some((format!("done:{how}"), words));
    let failed = |words: String| Some((FAILED.to_string(), words));
    let front = role == "front";
    let app = if front { t.front.as_ref() } else { t.app.as_ref() };
    let mut first = h.running(role);
    let (mut readmitted, mut self_readmitted) = (None, false);
    if !front {
        match readmit_first(h, role, first) {
            Ok((again, back, me)) => (first, readmitted, self_readmitted) = (again, back, me),
            Err(report) => return Some(report),
        }
    }
    // The words of a re-admission done first, before what this step did.
    let first_words = readmitted.as_ref().map(|b| format!("re-admitted first: {b}; ")).unwrap_or_default();
    let running = match first {
        Ok(r) => r,
        // A box with no live door answers nothing live: its keep medium (the fallback).
        Err(e) if !front && e.contains("[live-unsupported]") => {
            return match h.keep(role) {
                Ok(w) => done("keep", format!("its live door: {e}; {w}")),
                Err(k) => failed(format!("its live door: {e}; the keep upgrade: {k}")),
            }
        }
        Err(e) if front => return Some((NEEDS_KEEP.into(), format!("the front's own slot: {e}"))),
        Err(e) => return failed(format!("its live door did not answer: {e}")),
    };
    match decide(&running, app, t.live) {
        Plan::Wait(_) => None,
        Plan::Nothing => {
            if let Some(back) = readmitted.filter(|_| self_readmitted) {
                return done("keep", format!("its keep boot was never re-admitted: {back}; the {role} runs app {}", short(&running.app)));
            }
            let how = if front && h.staged_self(&t.release) { "live" } else { "nothing" };
            done(how, format!("{first_words}the {role} runs app {}", short(&running.app)))
        }
        Plan::Keep(why) if front => Some((NEEDS_KEEP.into(), format!("{why}: the shop keep-boots {}", h.front_keep()))),
        Plan::Keep(why) => match h.keep(role) {
            Ok(w) => done("keep", format!("{first_words}{why}: {w}")),
            Err(e) => failed(format!("{first_words}{why}; the keep upgrade of the {role}: {e}")),
        },
        Plan::Live => {
            let app = app.expect("Live is decided on an app");
            // A32-8: the front staged THIS release into its own slot before (the marker), and runs
            // its old app again with no trial: korp rolled the new one back. Said once, as a box's
            // rollback is; never staged again on every look.
            if front && h.staged_self(&t.release) {
                return failed(format!(
                    "[update-rolled-back] the front runs its old app {} again (slot {}): its own stage of release {} never confirmed",
                    short(&running.app),
                    running.slot,
                    t.release
                ));
            }
            let (stage, declared) = match staged_of(h, app).and_then(|s| declared_of(app).map(|d| (s, d))) {
                Ok(s) => s,
                Err(e) => return failed(e),
            };
            if front {
                if let Err(e) = h.mark_staged_self(&t.release) {
                    return failed(format!("the marker before its own stage: {e}"));
                }
                return match h.stage(role, &stage, &declared) {
                    // It re-execs now; its next look reports.
                    Ok(_) => None,
                    // Only its keep boot lays what the release moved: no live update of its own.
                    Err(e) if crate::live::falls_to_keep(&e) => {
                        h.staged_self(&t.release);
                        Some((NEEDS_KEEP.into(), format!("its own slot refused the live stage ({}): the shop keep-boots {}", e.trim(), h.front_keep())))
                    }
                    Err(e) => failed(format!("its own stage was refused: {e}")),
                };
            }
            match h.stage(role, &stage, &declared) {
                Ok(_) => {}
                // Only a keep boot lays what the release moved (lane B5).
                Err(e) if crate::live::falls_to_keep(&e) => {
                    let why = format!("the {role} refused the live stage ({})", e.trim());
                    return match h.keep(role) {
                        Ok(w) => done("keep", format!("{why}: {w}")),
                        Err(k) => failed(format!("{why}; the keep upgrade of the {role}: {k}")),
                    };
                }
                Err(e) => return failed(format!("the stage was refused: {e}")),
            }
            match wait_confirmed(h, role, &app.app, &running.app) {
                Ok(w) => done("live", w),
                Err(e) => failed(e),
            }
        }
    }
}

/// **One look**: take, do the box whose turn it is, report it. `Ok(None)`: nothing to report
/// now. Answers `(role, outcome, the shop's words)`.
pub fn look(shop: &dyn Shop, h: &dyn Hands) -> Result<Option<(String, String, String)>, String> {
    let t = shop.take()?;
    let Some((outcome, words)) = step(h, &t) else { return Ok(None) };
    let said = shop.report(&t.role, &outcome, &words)?;
    Ok(Some((t.role, outcome, said)))
}

/// **Where a box reaches a release object the bench named on its own loopback** (the mock's
/// guestfwd answers the host's ports at 10.0.2.100). Any other url is as given.
pub fn reachable(url: &str) -> String {
    match url.strip_prefix("http://127.0.0.1:") {
        Some(rest) => format!("http://10.0.2.100:{rest}"),
        None => url.to_string(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;
    use std::collections::BTreeMap;

    const OLD: &str = "0101010101010101010101010101010101010101010101010101010101010101";

    fn archive() -> Vec<u8> {
        (0..4096u32).map(|i| (i % 251) as u8).collect()
    }

    fn app_of(base: &str) -> LiveApp {
        let a = archive();
        LiveApp {
            base: base.into(),
            app: blake3::hash(&a).to_hex().to_string(),
            app_len: a.len() as u64,
            sig: "ab".repeat(80),
            url: "https://objsto.example/release/holger/03.live-app.znippy?X-Amz=…".into(),
            sha256: ring::digest::digest(&ring::digest::SHA256, &a).as_ref().iter().map(|b| format!("{b:02x}")).collect(),
            ..LiveApp::default()
        }
    }

    fn running(base: &str, app: &str, slot: &str) -> Running {
        Running { base: base.into(), app: app.into(), slot: slot.into(), live_update: true }
    }

    /// A fake estate: each box's running app, what a stage does to it, the keep upgrade.
    #[derive(Default)]
    struct Fake {
        boxes: RefCell<BTreeMap<String, Running>>,
        /// The app a staged box comes back with (`None`: it rolls back).
        comes_back: RefCell<Option<String>>,
        refuse_stage: Option<&'static str>,
        no_door: bool,
        staged: RefCell<Vec<String>>,
        declared: RefCell<Vec<Declared>>,
        kept: RefCell<Vec<String>>,
        marker: RefCell<Option<String>>,
        waited: RefCell<Duration>,
        down_polls: RefCell<u32>,
        /// Boxes whose live door is the OPEN install listener (a keep boot never re-admitted).
        open_door: RefCell<Vec<String>>,
        readmitted: RefCell<Vec<String>>,
        /// The re-admission refused with these words.
        readmit_refused: RefCell<Option<String>>,
    }

    impl Hands for Fake {
        fn running(&self, role: &str) -> Result<Running, String> {
            if self.no_door {
                return Err("REFUSED [live-unsupported] this VM takes no live update".into());
            }
            if *self.down_polls.borrow() > 0 {
                *self.down_polls.borrow_mut() -= 1;
                return Err("connection refused (re-exec)".into());
            }
            if self.open_door.borrow().iter().any(|r| r == role) {
                return Err(OPEN_LISTENER_404.into());
            }
            self.boxes.borrow().get(role).cloned().ok_or_else(|| "no such box".into())
        }
        fn readmit(&self, roles: &[&str]) -> Result<String, String> {
            if let Some(e) = self.readmit_refused.borrow().clone() {
                return Err(e);
            }
            // The re-admission re-feeds every OTHER box of the kept plan, and one at its open
            // install listener never takes a re-feed: the run ends at its budget.
            if let Some(other) = self.open_door.borrow().iter().find(|r| !roles.contains(&r.as_str())) {
                return Err(waited_on(other));
            }
            self.readmitted.borrow_mut().push(roles.join("+"));
            self.open_door.borrow_mut().retain(|r| !roles.contains(&r.as_str()));
            Ok(format!("{} re-admitted by the front after its upgrade (its data kept); 3 box(es) closed", roles.join(" and ")))
        }
        fn stage(&self, role: &str, stage: &Stage, declared: &Declared) -> Result<String, String> {
            self.declared.borrow_mut().push(declared.clone());
            if let Some(w) = self.refuse_stage {
                return Err(w.into());
            }
            self.staged.borrow_mut().push(role.into());
            // A box is down while it re-execs; the front reads its own slot, never down.
            if role != "front" {
                *self.down_polls.borrow_mut() = 2;
            }
            let mut b = self.boxes.borrow_mut();
            let r = b.get_mut(role).unwrap();
            if let Some(app) = self.comes_back.borrow().clone() {
                assert_eq!(app, stage.pin_hex);
                r.app = app;
            }
            r.slot = "a".into();
            Ok("staged".into())
        }
        fn fetch(&self, _: &str) -> Result<Vec<u8>, String> {
            Ok(archive())
        }
        fn keep(&self, role: &str) -> Result<String, String> {
            // The keep upgrade's second half is the same re-admission (Gunnar's `upgrade_one`).
            if let Some(other) = self.open_door.borrow().iter().find(|r| *r != role) {
                return Err(waited_on(other));
            }
            self.kept.borrow_mut().push(role.into());
            Ok(format!("the {role} was booted from the release's keep medium"))
        }
        fn wait(&self, d: Duration) {
            *self.waited.borrow_mut() += d;
        }
        fn staged_self(&self, release: &str) -> bool {
            self.marker.borrow_mut().take().is_some_and(|r| r == release)
        }
        fn mark_staged_self(&self, release: &str) -> Result<(), String> {
            *self.marker.borrow_mut() = Some(release.into());
            Ok(())
        }
    }

    /// What gunnar.rs's main answered on `GET /live/running` (2026-10-25 03:54Z): its keep medium
    /// had run, the cloud then put the server in error state, the box booted its upgraded system
    /// and waited for its re-admission — its door the OPEN install listener (`bootstrap::respond`).
    const OPEN_LISTENER_404: &str = "REFUSED [install-path] this listener has /install/hello, /install/log, /install/feed, /install/status, /install/offer, /install/secret-offer and /install/close";

    /// What the twin's keep upgrade answered on gunnar.rs (2026-10-25 11:08Z): its re-admission
    /// re-fed the main, which sat at its open install listener.
    fn waited_on(other: &str) -> String {
        format!("REFUSED [installer-incomplete] after 3606 s: front: no signal; {other}: never answered (last: {OPEN_LISTENER_404}); twin: pair   twin: tunnel wireguard up · joined · role twin · PAIRED")
    }


    fn take(role: &str, live: bool) -> Take {
        let base = "b".repeat(64);
        Take { open: true, release: "03".into(), live, role: role.into(), app: live.then(|| app_of(&base)), front: live.then(|| app_of(&base)), words: String::new() }
    }

    /// ★ Same BASE, live on: the twin gets the release's app live — fetched and proven, staged,
    /// waited for through its re-exec until it runs it confirmed.
    #[test]
    fn same_base_is_staged_live_and_waited_for_until_confirmed() {
        let f = Fake::default();
        let app = app_of(&"b".repeat(64));
        f.boxes.borrow_mut().insert("twin".into(), running(&"b".repeat(64), OLD, "a"));
        *f.comes_back.borrow_mut() = Some(app.app.clone());
        let (o, w) = step(&f, &take("twin", true)).expect("reported");
        assert_eq!(o, DONE_LIVE, "{w}");
        assert!(w.contains("confirmed"), "{w}");
        assert_eq!(*f.staged.borrow(), vec!["twin".to_string()]);
        assert!(f.kept.borrow().is_empty());
    }

    /// ★ The box comes back on its OLD app with no trial: rolled back — reported failed; a stage
    /// korp's legs refuse is failed too, and nothing is kept in its place.
    #[test]
    fn a_box_that_rolls_back_or_refuses_the_stage_is_reported_failed() {
        let f = Fake::default();
        f.boxes.borrow_mut().insert("main".into(), running(&"b".repeat(64), OLD, "a"));
        let (o, w) = step(&f, &take("main", true)).unwrap();
        assert_eq!(o, FAILED);
        assert!(w.contains("[update-rolled-back]"), "{w}");
        assert!(*f.waited.borrow() >= ROLLED_BACK_AFTER);
        let r = Fake { refuse_stage: Some("[live-verify] the signature leg"), ..Fake::default() };
        r.boxes.borrow_mut().insert("main".into(), running(&"b".repeat(64), OLD, "a"));
        let (o, w) = step(&r, &take("main", true)).unwrap();
        assert_eq!(o, FAILED);
        assert!(w.contains("[live-verify]") && r.kept.borrow().is_empty(), "{w}");
    }

    /// ★ BASE moved, live off, the box's own switch, or a box with no live door: the keep
    /// medium through the shop's Upgrade — never a stage.
    #[test]
    fn a_moved_base_live_off_the_boxs_switch_or_no_live_door_go_through_the_keep_medium() {
        for (t, r, no_door) in [
            (take("twin", true), running(&"c".repeat(64), OLD, "a"), false),
            (take("twin", false), running(&"b".repeat(64), OLD, "a"), false),
            (take("twin", true), Running { live_update: false, ..running(&"b".repeat(64), OLD, "a") }, false),
            (take("twin", true), running(&"b".repeat(64), OLD, "a"), true),
        ] {
            let f = Fake { no_door, ..Fake::default() };
            f.boxes.borrow_mut().insert("twin".into(), r);
            let (o, w) = step(&f, &t).unwrap();
            assert_eq!(o, DONE_KEEP, "{w}");
            assert!(f.staged.borrow().is_empty());
            assert_eq!(*f.kept.borrow(), vec!["twin".to_string()]);
        }
    }

    /// ★ The front stages ITSELF and reports after its re-exec (`live`); its BASE moved, it asks
    /// the shop to keep-boot it (`needs-keep`); a box on trial is left for the next look.
    #[test]
    fn the_front_stages_itself_and_reports_after_its_reexec_or_asks_for_its_keep_boot() {
        let f = Fake::default();
        let app = app_of(&"b".repeat(64));
        f.boxes.borrow_mut().insert("front".into(), running(&"b".repeat(64), OLD, "a"));
        *f.comes_back.borrow_mut() = Some(app.app.clone());
        assert_eq!(step(&f, &take("front", true)), None, "staged: it re-execs, nothing reported now");
        assert_eq!(f.marker.borrow().as_deref(), Some("03"));
        *f.down_polls.borrow_mut() = 0;
        let (o, _) = step(&f, &take("front", true)).unwrap();
        assert_eq!(o, DONE_LIVE, "the marker says it was this front's own live update");
        let moved = Fake::default();
        moved.boxes.borrow_mut().insert("front".into(), running(&"c".repeat(64), OLD, "a"));
        assert_eq!(step(&moved, &take("front", true)).unwrap().0, NEEDS_KEEP);
        assert!(moved.kept.borrow().is_empty(), "the front never upgrades itself through Upgrade");
        let trial = Fake::default();
        trial.boxes.borrow_mut().insert("twin".into(), running(&"b".repeat(64), OLD, "b booted"));
        assert_eq!(step(&trial, &take("twin", true)), None);
    }

    /// ★ **The front's own stage that rolls back is reported failed, not staged again** (A32-8):
    /// the front staged itself (the marker left), re-exec'd, its trial failed and korp rolled it
    /// back: it runs its OLD app with no trial. The next look must say so once, and must not
    /// stage the same release into its own slot on every look for ever.
    #[test]
    fn the_fronts_own_stage_that_rolls_back_is_reported_failed_not_restaged() {
        let f = Fake::default();
        f.boxes.borrow_mut().insert("front".into(), running(&"b".repeat(64), OLD, "a"));
        // `comes_back` None: the new app never confirms; korp rolls the slot back to OLD.
        assert_eq!(step(&f, &take("front", true)), None, "staged: it re-execs, nothing reported now");
        assert_eq!(f.staged.borrow().len(), 1);
        let (o, w) = step(&f, &take("front", true)).expect("the rollback is reported on the next look");
        assert_eq!(o, FAILED, "{w}");
        assert!(w.contains("[update-rolled-back]") && w.contains("03"), "{w}");
        assert_eq!(f.staged.borrow().len(), 1, "the release was not staged into its own slot again");
        assert!(f.marker.borrow().is_none(), "the marker is spent");
    }

    /// ★ **What the release declares rides the stage** (lane B5): the release declaration
    /// (bundle and signature, hex as the shop hands them) and the release's env reach the box;
    /// an app that declares neither stages neither.
    #[test]
    fn the_release_declaration_and_env_ride_the_stage() {
        let f = Fake::default();
        let mut app = app_of(&"b".repeat(64));
        app.release = "6b6f7270".into();
        app.release_sig = "77".repeat(64);
        app.env = vec!["korp.env.HOLGER_DATA=/data".into()];
        f.boxes.borrow_mut().insert("twin".into(), running(&"b".repeat(64), OLD, "a"));
        *f.comes_back.borrow_mut() = Some(app.app.clone());
        let t = Take { app: Some(app), ..take("twin", true) };
        assert_eq!(step(&f, &t).unwrap().0, DONE_LIVE);
        assert_eq!(f.declared.borrow()[0], Declared { release: Some((b"korp".to_vec(), vec![0x77; 64])), env: Some(vec!["korp.env.HOLGER_DATA=/data".into()]) });
        let plain = Fake::default();
        plain.boxes.borrow_mut().insert("twin".into(), running(&"b".repeat(64), OLD, "a"));
        step(&plain, &take("twin", true));
        assert_eq!(plain.declared.borrow()[0], Declared::default(), "nothing declared");
    }

    /// ★ **A box that refuses a stage only a keep boot answers** (its release env moved, its
    /// declaration cannot ride, it takes none live) is keep-booted: `done:keep` for main and
    /// twin, `needs-keep` for the front (its marker forgotten); a declaration that does not hold
    /// stays a failure.
    #[test]
    fn a_keep_refusal_of_the_stage_is_answered_with_a_keep_boot() {
        for w in ["REFUSED [live-env-moved] the release changes korp.env.*", "REFUSED [live-release-absent] none rides", "REFUSED [live-declared-unsupported] x"] {
            let f = Fake { refuse_stage: Some(w), ..Fake::default() };
            f.boxes.borrow_mut().insert("twin".into(), running(&"b".repeat(64), OLD, "a"));
            let (o, said) = step(&f, &take("twin", true)).unwrap();
            assert_eq!(o, DONE_KEEP, "{said}");
            assert_eq!(*f.kept.borrow(), vec!["twin".to_string()]);
            let front = Fake { refuse_stage: Some(w), ..Fake::default() };
            front.boxes.borrow_mut().insert("front".into(), running(&"b".repeat(64), OLD, "a"));
            let (o, said) = step(&front, &take("front", true)).unwrap();
            assert_eq!(o, NEEDS_KEEP, "{said}");
            assert!(front.marker.borrow().is_none(), "the front's marker is forgotten: its keep boot is no live update");
        }
        let f = Fake { refuse_stage: Some("REFUSED [release-sig] does not hold"), ..Fake::default() };
        f.boxes.borrow_mut().insert("twin".into(), running(&"b".repeat(64), OLD, "a"));
        assert_eq!(step(&f, &take("twin", true)).unwrap().0, FAILED);
        assert!(f.kept.borrow().is_empty());
    }

    /// ★ The fetched archive must be the release's — sha256, length, pin — and signed.
    #[test]
    fn an_archive_that_is_not_the_releases_is_never_staged() {
        let f = Fake::default();
        let mut bad = app_of(&"b".repeat(64));
        bad.app = "9".repeat(64);
        assert!(staged_of(&f, &bad).unwrap_err().contains("pin"));
        let mut unsigned = app_of(&"b".repeat(64));
        unsigned.sig.clear();
        assert!(staged_of(&f, &unsigned).unwrap_err().contains("[update-unsigned]"));
        let mut sha = app_of(&"b".repeat(64));
        sha.sha256 = "0".repeat(64);
        assert!(staged_of(&f, &sha).unwrap_err().contains("sha256"));
        assert!(step(&Fake::default(), &Take { open: false, ..take("twin", true) }).is_none());
    }

    /// The ONE box's update, in memory: its turn order twin → main → front, refusing a report
    /// out of turn by name, `open = false` once the front reported.
    struct FakeShop {
        turn: RefCell<Vec<&'static str>>,
        reported: RefCell<Vec<(String, String)>>,
        refuse_take: Option<&'static str>,
        live: bool,
    }

    impl FakeShop {
        fn open(live: bool) -> FakeShop {
            FakeShop { turn: RefCell::new(vec!["twin", "main", "front"]), reported: RefCell::default(), refuse_take: None, live }
        }
    }

    impl Shop for FakeShop {
        fn take(&self) -> Result<Take, String> {
            if let Some(w) = self.refuse_take {
                return Err(format!("refused: {w}"));
            }
            Ok(match self.turn.borrow().first() {
                Some(r) => take(r, self.live),
                None => Take::default(),
            })
        }
        fn report(&self, role: &str, outcome: &str, _: &str) -> Result<String, String> {
            let mut turn = self.turn.borrow_mut();
            if turn.first() != Some(&role) {
                return Err(format!("refused: [update-out-of-turn] {role} reported, the turn is {:?}", turn.first()));
            }
            turn.remove(0);
            self.reported.borrow_mut().push((role.into(), outcome.into()));
            Ok(format!("{role}: {outcome} recorded"))
        }
    }

    /// ★ Live off: twin then main are kept and the front needs its keep boot, in the shop's order.
    #[test]
    fn live_off_twin_then_main_are_kept_and_the_front_needs_keep_in_the_shops_order() {
        let shop = FakeShop::open(false);
        let h = Fake::default();
        for r in ["twin", "main", "front"] {
            h.boxes.borrow_mut().insert(r.into(), running(&"b".repeat(64), OLD, "a"));
        }
        for _ in 0..4 {
            look(&shop, &h).expect("every look is in turn");
        }
        assert_eq!(shop.reported.borrow().iter().map(|(r, o)| format!("{r} {o}")).collect::<Vec<_>>(), ["twin done:keep", "main done:keep", "front needs-keep"]);
        assert_eq!(h.kept.borrow().as_slice(), ["twin", "main"], "the front never upgrades itself");
        assert_eq!(look(&shop, &h).unwrap(), None, "nothing open: nothing reported");
    }

    /// ★ Live on: twin, main and front all take the release live, in the shop's order.
    #[test]
    fn live_on_twin_main_and_front_take_the_release_live() {
        let shop = FakeShop::open(true);
        let h = Fake::default();
        for r in ["twin", "main", "front"] {
            h.boxes.borrow_mut().insert(r.into(), running(&"b".repeat(64), OLD, "a"));
        }
        *h.comes_back.borrow_mut() = Some(app_of(&"b".repeat(64)).app);
        for _ in 0..4 {
            look(&shop, &h).expect("every look is in turn");
        }
        assert_eq!(shop.reported.borrow().iter().map(|(r, o)| format!("{r} {o}")).collect::<Vec<_>>(), ["twin done:live", "main done:live", "front done:live"]);
        assert!(h.kept.borrow().is_empty());
    }

    #[test]
    fn a_take_refused_or_an_unknown_role_is_said_by_name() {
        let shop = FakeShop { refuse_take: Some("[update-none-open] no update is open for acme"), ..FakeShop::open(false) };
        assert!(look(&shop, &Fake::default()).unwrap_err().contains("update-none-open"));
        assert_eq!(step(&Fake::default(), &Take { open: true, role: "nas".into(), ..Default::default() }).map(|r| r.0).as_deref(), Some(FAILED));
    }

    /// ★ The bench's loopback bucket is reached at the mock's guestfwd; a real url is as given.
    #[test]
    fn a_bench_loopback_release_object_is_reached_at_the_mocks_guestfwd() {
        assert_eq!(reachable("http://127.0.0.1:17463/objsto/x?X=1"), "http://10.0.2.100:17463/objsto/x?X=1");
        assert_eq!(reachable("https://objsto.example/holger-release/x?X=1"), "https://objsto.example/holger-release/x?X=1");
    }

    /// ★ **While a step runs its box is BUSY** — the recharge watcher leaves it alone — and only
    /// then (lane G5, A7 row 10; Gunnar aa14357b `a_box_is_busy_while_its_step_runs_and_only_then`).
    #[test]
    fn a_box_is_busy_while_its_step_runs_and_only_then() {
        struct Peek(Fake);
        impl Hands for Peek {
            fn running(&self, role: &str) -> Result<Running, String> {
                assert!(crate::frontside::busy::busy(Some(role)) && crate::frontside::busy::busy(None), "the step's own box is busy");
                self.0.running(role)
            }
            fn stage(&self, role: &str, s: &Stage, d: &Declared) -> Result<String, String> {
                self.0.stage(role, s, d)
            }
            fn fetch(&self, u: &str) -> Result<Vec<u8>, String> {
                self.0.fetch(u)
            }
            fn keep(&self, role: &str) -> Result<String, String> {
                assert!(crate::frontside::busy::busy(Some(role)), "busy through its keep boot");
                self.0.keep(role)
            }
            fn wait(&self, d: Duration) {
                self.0.wait(d)
            }
            fn staged_self(&self, r: &str) -> bool {
                self.0.staged_self(r)
            }
            fn mark_staged_self(&self, r: &str) -> Result<(), String> {
                self.0.mark_staged_self(r)
            }
        }
        let p = Peek(Fake { no_door: true, ..Fake::default() });
        assert_eq!(step(&p, &take("twin", true)).unwrap().0, DONE_KEEP);
    }

    /// ★ P4a: **the front's keep boot is said in the product's words** ([`Hands::front_keep`]);
    /// a product that names none gets the neutral words. Nothing else in the step names a product.
    #[test]
    fn the_fronts_keep_boot_is_said_in_the_products_words() {
        struct Named(Fake);
        impl Hands for Named {
            fn running(&self, role: &str) -> Result<Running, String> {
                self.0.running(role)
            }
            fn stage(&self, role: &str, s: &Stage, d: &Declared) -> Result<String, String> {
                self.0.stage(role, s, d)
            }
            fn fetch(&self, u: &str) -> Result<Vec<u8>, String> {
                self.0.fetch(u)
            }
            fn keep(&self, role: &str) -> Result<String, String> {
                self.0.keep(role)
            }
            fn wait(&self, d: Duration) {
                self.0.wait(d)
            }
            fn staged_self(&self, r: &str) -> bool {
                self.0.staged_self(r)
            }
            fn mark_staged_self(&self, r: &str) -> Result<(), String> {
                self.0.mark_staged_self(r)
            }
            fn front_keep(&self) -> String {
                "Acme's front from acme-front-keep".into()
            }
        }
        let named = Named(Fake::default());
        named.0.boxes.borrow_mut().insert("front".into(), running(&"c".repeat(64), OLD, "a"));
        let (o, w) = step(&named, &take("front", true)).unwrap();
        assert_eq!(o, NEEDS_KEEP);
        assert!(w.ends_with("the shop keep-boots Acme's front from acme-front-keep"), "{w}");
        let refused = Named(Fake { refuse_stage: Some("REFUSED [live-env-moved] x"), ..Fake::default() });
        refused.0.boxes.borrow_mut().insert("front".into(), running(&"b".repeat(64), OLD, "a"));
        let (o, w) = step(&refused, &take("front", true)).unwrap();
        assert_eq!(o, NEEDS_KEEP);
        assert!(w.contains("[live-env-moved]") && w.ends_with("the shop keep-boots Acme's front from acme-front-keep"), "{w}");
        let plain = Fake::default();
        plain.boxes.borrow_mut().insert("front".into(), running(&"c".repeat(64), OLD, "a"));
        assert!(step(&plain, &take("front", true)).unwrap().1.ends_with("the shop keep-boots the front from its keep medium"));
    }
    // ── T16: Gunnar's re-admission first (moved from gunnar-front/src/update.rs) ──

    /// Pure: only a main or twin at its OPEN install listener awaits its re-admission.
    #[test]
    fn only_a_box_at_its_open_install_listener_awaits_its_readmission() {
        assert!(awaits_readmission("main", OPEN_LISTENER_404));
        assert!(awaits_readmission("twin", OPEN_LISTENER_404));
        assert!(!awaits_readmission("front", OPEN_LISTENER_404), "the front is never re-admitted by itself");
        assert!(!awaits_readmission("main", "connection refused"));
    }

    /// ★ **A box whose keep boot was never re-admitted** (gunnar.rs main, 2026-10-25): the front
    /// re-admits it from the kept bundle, reads it again and goes on: it runs the release's app
    /// already → done:keep, nothing staged, no second keep.
    #[test]
    fn a_box_left_waiting_for_its_readmission_is_readmitted_then_read_again() {
        let f = Fake::default();
        let app = app_of(&"b".repeat(64));
        f.boxes.borrow_mut().insert("main".into(), running(&"b".repeat(64), &app.app, "a"));
        f.open_door.borrow_mut().push("main".into());
        let (o, w) = step(&f, &take("main", true)).expect("reported");
        assert_eq!(o, DONE_KEEP, "{w}");
        assert!(w.contains("re-admitted") && w.contains("never re-admitted"), "{w}");
        assert_eq!(*f.readmitted.borrow(), vec!["main".to_string()]);
        assert!(f.kept.borrow().is_empty(), "no second keep boot: {w}");
        assert!(f.staged.borrow().is_empty());
    }

    /// ★ Re-admitted, but not on the release (its BASE moved): on to the keep medium as any box.
    #[test]
    fn a_readmitted_box_that_is_not_on_the_release_goes_on_to_its_keep_upgrade() {
        let f = Fake::default();
        f.boxes.borrow_mut().insert("main".into(), running(&"c".repeat(64), OLD, "a"));
        f.open_door.borrow_mut().push("main".into());
        let (o, w) = step(&f, &take("main", true)).expect("reported");
        assert_eq!(o, DONE_KEEP, "{w}");
        assert_eq!(*f.readmitted.borrow(), vec!["main".to_string()]);
        assert_eq!(*f.kept.borrow(), vec!["main".to_string()]);
    }

    /// ★ A re-admission that fails is reported failed by name (the open listener and why); a door
    /// that does not answer at all is NOT taken for one waiting to be re-admitted.
    #[test]
    fn a_failed_readmission_and_a_silent_door_are_reported_failed() {
        let f = Fake::default();
        f.boxes.borrow_mut().insert("main".into(), running(&"b".repeat(64), OLD, "a"));
        f.open_door.borrow_mut().push("main".into());
        *f.readmit_refused.borrow_mut() = Some("the box did not close".into());
        let (o, w) = step(&f, &take("main", true)).unwrap();
        assert_eq!(o, FAILED);
        assert!(w.contains("[install-path]") && w.contains("the box did not close"), "{w}");
        let silent = Fake::default();
        *silent.down_polls.borrow_mut() = 1;
        silent.boxes.borrow_mut().insert("twin".into(), running(&"b".repeat(64), OLD, "a"));
        let (o, w) = step(&silent, &take("twin", true)).unwrap();
        assert_eq!(o, FAILED);
        assert!(w.contains("its live door did not answer"), "{w}");
        assert!(silent.readmitted.borrow().is_empty());
    }

    /// ★ **The twin's keep upgrade with a main left at its open install listener** (gunnar.rs
    /// 2026-10-25, the twin's re-admission waited 3606 s on the main): the main is re-admitted
    /// FIRST, then the twin goes through its keep medium.
    #[test]
    fn a_twin_whose_main_waits_for_its_readmission_readmits_the_main_first_then_keeps() {
        let f = Fake::default();
        f.boxes.borrow_mut().insert("twin".into(), running(&"c".repeat(64), OLD, "a"));
        f.boxes.borrow_mut().insert("main".into(), running(&"c".repeat(64), OLD, "a"));
        f.open_door.borrow_mut().push("main".into());
        let (o, w) = step(&f, &take("twin", true)).expect("reported");
        assert_eq!(o, DONE_KEEP, "{w}");
        assert_eq!(*f.readmitted.borrow(), vec!["main".to_string()], "the main first, on its own");
        assert_eq!(*f.kept.borrow(), vec!["twin".to_string()], "then the twin's keep upgrade");
        assert!(w.contains("main re-admitted"), "the words name the main's re-admission: {w}");
    }

    /// ★ **Both boxes at their open install listener**: ONE re-admission of both, then the twin
    /// is read again — it runs the release's app → done:keep, no second keep boot.
    #[test]
    fn both_boxes_left_waiting_are_readmitted_together_then_read_again() {
        let f = Fake::default();
        let app = app_of(&"b".repeat(64));
        f.boxes.borrow_mut().insert("twin".into(), running(&"b".repeat(64), &app.app, "a"));
        f.boxes.borrow_mut().insert("main".into(), running(&"b".repeat(64), &app.app, "a"));
        f.open_door.borrow_mut().extend(["main".to_string(), "twin".to_string()]);
        let (o, w) = step(&f, &take("twin", true)).expect("reported");
        assert_eq!(o, DONE_KEEP, "{w}");
        assert_eq!(*f.readmitted.borrow(), vec!["twin+main".to_string()], "one re-admission of both");
        assert!(f.open_door.borrow().is_empty());
        assert!(f.kept.borrow().is_empty() && f.staged.borrow().is_empty(), "{w}");
    }

    /// ★ **A front that keeps no bundle refuses the re-admission by name** (the default
    /// [`Hands::readmit`]): the box at its open install listener is reported failed with the
    /// listener's words and the refusal, never taken for a silent door.
    #[test]
    fn a_front_that_keeps_no_bundle_reports_the_open_listener_and_its_refusal() {
        struct NoBundle(Fake);
        impl Hands for NoBundle {
            fn running(&self, role: &str) -> Result<Running, String> {
                self.0.running(role)
            }
            fn stage(&self, role: &str, s: &Stage, d: &Declared) -> Result<String, String> {
                self.0.stage(role, s, d)
            }
            fn fetch(&self, u: &str) -> Result<Vec<u8>, String> {
                self.0.fetch(u)
            }
            fn keep(&self, role: &str) -> Result<String, String> {
                self.0.keep(role)
            }
            fn wait(&self, d: Duration) {
                self.0.wait(d)
            }
            fn staged_self(&self, r: &str) -> bool {
                self.0.staged_self(r)
            }
            fn mark_staged_self(&self, r: &str) -> Result<(), String> {
                self.0.mark_staged_self(r)
            }
        }
        let f = NoBundle(Fake::default());
        f.0.boxes.borrow_mut().insert("main".into(), running(&"b".repeat(64), OLD, "a"));
        f.0.open_door.borrow_mut().push("main".into());
        let (o, w) = step(&f, &take("main", true)).unwrap();
        assert_eq!(o, FAILED);
        assert!(w.contains("[install-path]") && w.contains("[readmit-unsupported]"), "{w}");
    }
}