nordisk-pairing 0.5.29

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
//! **The kept installer material** (feature `kept`): what a SOLD estate keeps so that
//! its front can be the estate's installer after the sale.
//!
//! The shop's pairing leaves one bundle (`installer-material/1`, JSON: every file of the
//! order's material, the door's ledger, the plan it paired by). The estate keeps it in its own
//! primary's sealed slot; when `main` or `twin` is LOST, the front parses it here, mints a
//! fresh seed for the box the shop lays again, and re-admits that box over the installer's
//! link ([`readmit`], feature `installer-wireguard`). After an in-place upgrade it re-admits a
//! box that kept its address ([`readmit_upgraded`]), or several at once ([`readmit_together`]).
//!
//! Product-neutral: Gunnar's and Holger's fronts read the same bundle, each naming its own
//! seed file (`seed.gunnar.toml`, `seed.holger.toml`).

use std::collections::BTreeMap;
use std::net::Ipv4Addr;
use std::sync::Mutex;
use std::time::Duration;

use crate::door::Door;
use crate::installer::{Material, Plan, Sink};
use crate::mtls::{InstallerCa, Issued};
use crate::seed::Seed;

/// The bundle's schema word (monetize-shop-pairing `BUNDLE_V`).
pub const BUNDLE_V: &str = "installer-material/1";
/// What a seed file is named in the bundle: `seed.<name>.toml`.
pub const SEED_FILE_PREFIX: &str = "seed.";
/// How long material minted from the kept CA lives (monetize-shop-pairing `MATERIAL_VALID`).
pub const MATERIAL_VALID: Duration = Duration::from_secs(180 * 24 * 3600);
/// A re-admission's budget: the box installs from its medium and a main restores from its twin.
pub const READMIT_BUDGET_S: u64 = 2 * 3600;

/// **The kept bundle**, as the slot holds it.
#[derive(Clone)]
pub struct Kept {
    pub files: BTreeMap<String, String>,
    pub ledger: String,
    pub plan: Plan,
}

impl std::fmt::Debug for Kept {
    /// Never a key.
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Kept").field("files", &self.files.keys().collect::<Vec<_>>()).field("estate", &self.plan.estate).finish_non_exhaustive()
    }
}

/// The seed file of a box named `name` (`holger` → `seed.holger.toml`).
pub fn seed_file(name: &str) -> String {
    format!("{SEED_FILE_PREFIX}{name}.toml")
}

impl Kept {
    pub fn parse(bytes: &[u8]) -> Result<Kept, String> {
        let v: serde_json::Value = serde_json::from_slice(bytes).map_err(|e| format!("the kept bundle is not JSON: {e}"))?;
        if v["v"] != BUNDLE_V {
            return Err(format!("the kept bundle is {:?}, not {BUNDLE_V}", v["v"]));
        }
        Ok(Kept {
            files: serde_json::from_value(v["files"].clone()).map_err(|e| format!("the kept files: {e}"))?,
            ledger: v["ledger"].as_str().ok_or("the kept bundle holds no ledger")?.to_string(),
            plan: serde_json::from_value(v["plan"].clone()).map_err(|e| format!("the kept plan: {e}"))?,
        })
    }

    pub fn render(&self) -> Result<Vec<u8>, String> {
        serde_json::to_vec(&serde_json::json!({"v": BUNDLE_V, "files": self.files, "ledger": self.ledger, "plan": self.plan})).map_err(|e| e.to_string())
    }

    fn file(&self, name: &str) -> Result<&str, String> {
        self.files.get(name).map(String::as_str).ok_or_else(|| format!("the kept bundle holds no {name}"))
    }

    /// The estate's installer CA.
    pub fn ca(&self) -> Result<InstallerCa, String> {
        Ok(InstallerCa { cert_pem: self.file(crate::installer::CA_CERT)?.to_string(), key_pem: self.file(crate::installer::CA_KEY)?.to_string() })
    }

    /// **The installer material** — the kept CA and link keys, and an installer certificate
    /// minted FRESH from the CA (the kept one may be near its end; the boxes trust the CA).
    pub fn material(&self) -> Result<Material, String> {
        let ca = self.ca()?;
        let me = ca.issue_installer(MATERIAL_VALID).map_err(|e| e.to_string())?;
        let link = Material::link_of(self.file(crate::installer::LINK_KEYS)?)?;
        Ok(Material { ca, me, link: Some(link) })
    }

    /// **A fresh seed for the box named `name`** (`seed.<name>.toml`): the kept one (its
    /// estate, CA, link keys), its bootstrap certificate minted anew from the CA — the kept one
    /// expires with the material.
    pub fn fresh_seed(&self, name: &str) -> Result<String, String> {
        let file = seed_file(name);
        let mut seed = Seed::parse(self.file(&file)?).map_err(|e| format!("the kept {file}: {e}"))?;
        let boot: Issued = self.ca()?.issue_bootstrap(MATERIAL_VALID).map_err(|e| e.to_string())?;
        seed.bootstrap_cert_pem = boot.cert_pem;
        seed.bootstrap_key_pem = boot.key_pem;
        seed.check().map_err(|e| e.to_string())?;
        seed.render().map_err(|e| e.to_string())
    }

    /// The door's ledger as the pairing left it.
    pub fn door(&self) -> Result<Door, String> {
        toml::from_str(&self.ledger).map_err(|e| format!("the kept ledger: {e}"))
    }
}

/// **The plan with `role` moved** to its new addresses, and the budget a re-admission needs.
/// The other boxes stand where they stood.
///
/// A moved box is a NEW machine with a new WireGuard key, so the PSK of its link to the front
/// rotates with it (lane WG-PSK, 2026-10-30): a fresh one ([`crate::installer::mint_wg_psk`])
/// in the box's own feed and the front's (`wg_psk` / `wg_psk.<role>`). The re-admission feeds
/// both ends; the bundle kept after it carries the new key.
pub fn moved(plan: &Plan, role: &str, public: Ipv4Addr, utility: Ipv4Addr) -> Result<Plan, String> {
    use crate::installer::{feed_wg_psk_for, mint_wg_psk, FEED_WG_PSK};
    use crate::seed::Kind;
    let mut p = plan.clone();
    let vm = p.vms.iter_mut().find(|v| v.name == role).ok_or_else(|| format!("the kept plan names no {role}"))?;
    vm.public = public;
    vm.utility = utility;
    if vm.kind != Kind::Front {
        let psk = mint_wg_psk()?;
        vm.feed.insert(FEED_WG_PSK.to_string(), psk.clone());
        if let Some(front) = p.vms.iter_mut().find(|v| v.kind == Kind::Front) {
            front.feed.insert(feed_wg_psk_for(role), psk);
        }
    }
    p.budget_s = p.budget_s.max(READMIT_BUDGET_S);
    p.check()?;
    Ok(p)
}

/// **What a front's re-admissions have said, process-wide** (gunnar.rs 2026-10-25: the front's
/// log ring held 1.9 M nested `readmit:` lines, every run of a re-admission that kept failing
/// saying its boxes' whole logs and every status again). A line, a status or a narration is said
/// ONCE: again only when it changes. Bounded: past [`ReadmitLog::KEEP`] remembered lines it
/// forgets them all and says each once more.
pub struct ReadmitLog {
    said: Mutex<std::collections::BTreeSet<u64>>,
    status: Mutex<BTreeMap<String, String>>,
}

impl ReadmitLog {
    /// How many said lines are remembered before they are forgotten at once.
    pub const KEEP: usize = 20_000;

    pub const fn new() -> ReadmitLog {
        ReadmitLog { said: Mutex::new(std::collections::BTreeSet::new()), status: Mutex::new(BTreeMap::new()) }
    }

    /// Whether `line` is new (and remember it).
    pub fn once(&self, line: &str) -> bool {
        use std::hash::{Hash, Hasher};
        let mut h = std::collections::hash_map::DefaultHasher::new();
        line.hash(&mut h);
        let mut said = self.said.lock().unwrap_or_else(|p| p.into_inner());
        if said.len() >= Self::KEEP {
            said.clear();
        }
        said.insert(h.finish())
    }

    /// Whether `vm`'s status `now` differs from the last one said (and remember it).
    pub fn changed(&self, vm: &str, now: &str) -> bool {
        let mut status = self.status.lock().unwrap_or_else(|p| p.into_inner());
        if status.get(vm).is_some_and(|was| was == now) {
            return false;
        }
        status.insert(vm.to_string(), now.to_string());
        true
    }
}

impl Default for ReadmitLog {
    fn default() -> ReadmitLog {
        ReadmitLog::new()
    }
}

/// The re-admissions of this process (every front's [`MemSink`]).
pub static READMIT_LOG: ReadmitLog = ReadmitLog::new();

fn to_stderr(line: &str) {
    eprintln!("{line}");
}

/// **The installer's sink, in memory**, for every front's re-admission: the ledger as it moves
/// (to go back into the slot); what is said goes to stderr (the front's log) through
/// [`READMIT_LOG`], each line once. Nothing is written to disk.
pub struct MemSink {
    pub ledger: Mutex<Option<String>>,
    log: &'static ReadmitLog,
    out: fn(&str),
}

impl Default for MemSink {
    fn default() -> MemSink {
        MemSink::with(&READMIT_LOG, to_stderr)
    }
}

impl MemSink {
    /// A sink saying through `log` to `out` (a test's own log and collector).
    pub fn with(log: &'static ReadmitLog, out: fn(&str)) -> MemSink {
        MemSink { ledger: Mutex::new(None), log, out }
    }

    fn say_once(&self, line: String) {
        if self.log.once(&line) {
            (self.out)(&line);
        }
    }

    /// The ledger the re-admission left.
    pub fn take(&self) -> Result<String, String> {
        self.ledger.lock().unwrap_or_else(|p| p.into_inner()).clone().ok_or_else(|| "the re-admission left no ledger".to_string())
    }
}

/// Pure: **is `line` a sink's own line, read back?** (`readmit <vm>: …` or `readmit: …`,
/// anywhere in it, behind whatever stamp a log put before it). The front is a box of its own
/// plan and its boot log captures its fd 2, where [`MemSink`] says what it reads: a line said
/// came back on the next read as a new line one level deeper, `readmit front: … readmit
/// front: …` (gunnar.rs 2026-10-25, 1.9 M lines), which no once-only memory stops.
pub fn is_echo(line: &str) -> bool {
    line.match_indices("readmit").any(|(at, _)| {
        let rest = &line[at + "readmit".len()..];
        if rest.starts_with(": ") {
            return true;
        }
        let Some(rest) = rest.strip_prefix(' ') else { return false };
        let vm = rest.split(": ").next().unwrap_or("");
        rest.len() > vm.len() && !vm.is_empty() && vm.chars().all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c))
    })
}

impl Sink for MemSink {
    /// A box's lines, each once; a line that is this sink's own, read back from the front's
    /// own log ([`is_echo`]), is never said again.
    fn lines(&self, vm: &str, lines: &[String]) {
        for l in lines.iter().filter(|l| !is_echo(l)) {
            self.say_once(format!("readmit {vm}: {l}"));
        }
    }
    fn status(&self, vm: &str, signal: Option<&crate::signal::Signal>, last_line: &str) {
        let now = format!("{} | {last_line}", signal.map(|s| s.line()).unwrap_or_else(|| "no signal".into()));
        if self.log.changed(vm, &now) {
            (self.out)(&format!("readmit {vm}: status {now}"));
        }
    }
    fn ledger(&self, door: &Door) {
        if let Ok(t) = toml::to_string(door) {
            *self.ledger.lock().unwrap_or_else(|p| p.into_inner()) = Some(t);
        }
    }
    fn say(&self, line: &str) {
        self.say_once(format!("readmit: {line}"));
    }
    fn secret_offer(&self, vm: &str, _offer: &crate::bootstrap::SecretOffer) -> Result<(), String> {
        // A front's secret offer stays with the front; a product box hands none.
        self.say_once(format!("readmit {vm}: a secret offer (kept by its box)"));
        Ok(())
    }
}

/// How the front reaches one box as its installer.
#[cfg(feature = "installer-wireguard")]
pub type BoxPath = Box<dyn crate::installer_link::Path>;

/// **How the front reaches each box as their installer**: its own door direct (its link
/// address is its own), every other box over the installer's link to its UTILITY address.
#[cfg(feature = "installer-wireguard")]
pub fn paths_for(plan: &Plan, link: &crate::installer_link::LinkKeys) -> Result<Vec<(crate::installer::PlanVm, BoxPath)>, String> {
    use std::net::SocketAddrV4;
    let mut out: Vec<(crate::installer::PlanVm, BoxPath)> = Vec::new();
    for vm in &plan.vms {
        let path: BoxPath = if vm.kind == crate::seed::Kind::Front {
            Box::new(crate::installer_link::DirectPath { at: SocketAddrV4::new(crate::installer_link::LINK_VM, crate::bootstrap::PORT) })
        } else {
            Box::new(crate::installer_link::WgPath::new(SocketAddrV4::new(vm.utility, plan.link_port), link.clone())?)
        };
        out.push((vm.clone(), path));
    }
    Ok(out)
}

/// **Re-admit `role` at its new addresses** from the kept material: the plan moved, the door
/// as the pairing left it, the front the installer. `seed_name` names the seed the box was
/// laid with (`seed.<seed_name>.toml`, kept fresh in the bundle). Answers the bundle as it
/// stands after (the new ledger and plan, the fresh seed) and what was done.
#[cfg(feature = "installer-wireguard")]
pub fn readmit(kept: &Kept, role: &str, public: Ipv4Addr, utility: Ipv4Addr, seed_name: &str, seed: &str) -> Result<(Kept, String), String> {
    let m = kept.material()?;
    let plan = moved(&kept.plan, role, public, utility)?;
    let paths = paths_for(&plan, m.link.as_ref().ok_or("the kept material holds no link keys")?)?;
    let sink = MemSink::default();
    let (readmitted, outcome) = crate::installer::readmit(&plan, kept.door()?, &m, role, paths, &sink)?;
    let mut after = kept.clone();
    after.ledger = sink.take()?;
    after.plan = plan;
    after.files.insert(seed_file(seed_name), seed.to_string());
    let words = format!(
        "{role} re-admitted by the front: from {} to {} / {}{}; {} box(es) closed",
        readmitted.was.0,
        readmitted.now.0,
        readmitted.now.1,
        readmitted.recharge_from.as_deref().map(|f| format!(", told to fill from its twin at {f}")).unwrap_or_default(),
        outcome.vms.iter().filter(|v| v.closed).count()
    );
    Ok((after, words))
}

/// **The box `role` re-admitted after an in-place upgrade** ([`crate::installer::readmit_kept`]):
/// the plan as kept (the box stands where it stood), its data kept. Answers the bundle with the
/// ledger as it moved.
#[cfg(feature = "installer-wireguard")]
pub fn readmit_upgraded(kept: &Kept, role: &str) -> Result<(Kept, String), String> {
    let m = kept.material()?;
    let paths = paths_for(&kept.plan, m.link.as_ref().ok_or("the kept material holds no link keys")?)?;
    let sink = MemSink::default();
    let (_readmitted, outcome) = crate::installer::readmit_kept(&kept.plan, kept.door()?, &m, role, paths, &sink)?;
    let mut after = kept.clone();
    after.ledger = sink.take()?;
    Ok((after, format!("{role} re-admitted by the front after its upgrade (its data kept); {} box(es) closed", outcome.vms.iter().filter(|v| v.closed).count())))
}

/// **The boxes `roles` re-admitted TOGETHER after an in-place upgrade, in ONE run**
/// ([`crate::installer::readmit_kept_together`]; gunnar.rs 2026-10-25): the plan as kept (each
/// box stands where it stood), their data kept; only the boxes not named are re-fed. The update
/// step's way back for boxes whose keep boot ran and was never re-admitted
/// (`frontside::update::Hands::readmit`), for every product's front. Moved from Gunnar's front
/// (`gunnar-front/src/recharge.rs` `readmit_upgraded`, T16). Answers the bundle with the ledger
/// as it moved.
#[cfg(feature = "installer-wireguard")]
pub fn readmit_together(kept: &Kept, roles: &[&str]) -> Result<(Kept, String), String> {
    if roles.is_empty() {
        return Err("REFUSED [installer-readmit] no box is named to re-admit".into());
    }
    if let Some(r) = roles.iter().find(|r| !kept.plan.vms.iter().any(|v| v.name == **r)) {
        return Err(format!("REFUSED [installer-readmit] the kept plan names no {r}"));
    }
    let m = kept.material()?;
    let paths = paths_for(&kept.plan, m.link.as_ref().ok_or("the kept material holds no link keys")?)?;
    let sink = MemSink::default();
    let names: Vec<String> = roles.iter().map(|r| r.to_string()).collect();
    let (_readmitted, outcome) = crate::installer::readmit_kept_together(&kept.plan, kept.door()?, &m, &names, paths, &sink)?;
    let mut after = kept.clone();
    after.ledger = sink.take()?;
    Ok((after, format!("{} re-admitted by the front after its upgrade (its data kept); {} box(es) closed", roles.join(" and "), outcome.vms.iter().filter(|v| v.closed).count())))
}

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

    static LOOP_LOG: ReadmitLog = ReadmitLog::new();
    static SAID: Mutex<Vec<String>> = Mutex::new(Vec::new());

    fn collect(line: &str) {
        SAID.lock().unwrap().push(line.to_string());
    }

    /// ★ **A loop of N re-admission rounds says each line ONCE** (gunnar.rs 2026-10-25: 1.9 M
    /// nested `readmit:` lines in the front's log ring): every round's sink sees the boxes' same
    /// log lines, the same status and the same narration; only what is new, or a status that
    /// changed, is said again.
    #[test]
    fn a_loop_of_readmission_rounds_says_each_line_once() {
        const N: usize = 50;
        for round in 0..N {
            let sink = MemSink::with(&LOOP_LOG, collect);
            sink.lines("main", &["korp-installer: INSTALL-OK".into(), "REFUSED [install-path] this listener has /install/hello".into()]);
            sink.status("main", None, "REFUSED [install-path] this listener has /install/hello");
            sink.say("main: reaching 10.13.8.2:4443");
            sink.say("main: not yet — REFUSED [install-path]");
            if round == N - 1 {
                // The state changes once: said, once.
                sink.status("main", None, "gunnar: first boot");
                sink.status("main", None, "gunnar: first boot");
            }
        }
        let said = SAID.lock().unwrap().clone();
        assert_eq!(said.len(), 6, "each line once and the changed status once over {N} rounds: {said:#?}");
        assert_eq!(said.iter().filter(|l| l.contains("status")).count(), 2, "the status once, then once more when it changed");
        assert!(said.iter().all(|l| l.starts_with("readmit")), "{said:#?}");
    }

    /// The memory is bounded: past `KEEP` lines it forgets them all and says each once more.
    #[test]
    fn the_readmit_log_forgets_at_its_bound() {
        let log = ReadmitLog::new();
        assert!(log.once("a") && !log.once("a"));
        for i in 0..ReadmitLog::KEEP {
            log.once(&format!("line {i}"));
        }
        assert!(log.once("a"), "forgotten at the bound, said once more");
        assert!(log.changed("main", "x") && !log.changed("main", "x") && log.changed("main", "y") && log.changed("twin", "y"));
    }

    static ECHO_LOG: ReadmitLog = ReadmitLog::new();
    /// The front's own boot log: what its sink says goes to fd 2, and fd 2 is captured into
    /// the boot log (gunnar-common `stderr_capture`, Holger's `capture_stderr`) with a stamp.
    static FRONT_LOG: Mutex<Vec<String>> = Mutex::new(Vec::new());
    static ECHOED: Mutex<Vec<String>> = Mutex::new(Vec::new());

    fn into_front_log(line: &str) {
        ECHOED.lock().unwrap().push(line.to_string());
        FRONT_LOG.lock().unwrap().push(format!("2026-10-25T21:04:05.123Z  INFO stderr: {line}"));
    }

    /// ★ **The front's own log, read back by its re-admission, is never said again** (gunnar.rs
    /// 2026-10-25: the 1.9 M NESTED `readmit:` lines). The front is a box of its own plan, read
    /// over its own door, and its boot log captures its fd 2, where the sink says what it reads:
    /// every line said came back as a new line, `readmit front: … readmit front: …`, one level
    /// deeper on each read, which no once-only memory stops. Over 50 rounds the front's two
    /// real lines are said once each, and no echo of the sink is said.
    #[test]
    fn the_sinks_own_lines_read_back_from_the_fronts_log_are_not_said_again() {
        FRONT_LOG.lock().unwrap().extend(["2026-10-25T21:04:00.000Z  WARN gunnar_front::recharge: recharge: the box is gone; the front asks the shop to lay it again".to_string(), "korp-installer: INSTALL-OK".to_string()]);
        let mut cursor = 0;
        for _ in 0..50 {
            let sink = MemSink::with(&ECHO_LOG, into_front_log);
            let new: Vec<String> = FRONT_LOG.lock().unwrap()[cursor..].to_vec();
            cursor += new.len();
            sink.lines("front", &new);
            sink.say("front: reaching 10.13.8.1:4443");
        }
        let said = ECHOED.lock().unwrap().clone();
        assert_eq!(said.len(), 3, "the two real lines and the narration, once each: {said:#?}");
        assert!(said.iter().all(|l| l.matches("readmit").count() == 1), "nothing nested: {said:#?}");
        assert!(FRONT_LOG.lock().unwrap().len() < 10, "the front's log stops growing");
    }

    /// Pure: what a sink's own line looks like, wherever a log put it, and nothing else.
    #[test]
    fn an_echo_is_the_sinks_own_line_and_only_that() {
        for l in ["readmit main: korp-installer: INSTALL-OK", "readmit: main: reaching 10.13.8.2:4443", "2026-10-25T21:04:05Z  INFO stderr: readmit twin: status no signal | x", "<14>readmit front: a secret offer (kept by its box)"] {
            assert!(is_echo(l), "{l}");
        }
        for l in ["korp-installer: INSTALL-OK", "recharge: main re-admitted by the front: from 192.0.2.2 to 192.0.2.9", "REFUSED [installer-readmit] the kept plan names no x", "readmitted", "readmitting main now", "readmit  : x"] {
            assert!(!is_echo(l), "{l}");
        }
    }

    use crate::installer::PlanVm;
    use crate::seed::{Front, Kind, LinkSeed, TunnelKind};

    fn bundle() -> (Vec<u8>, InstallerCa) {
        let ca = InstallerCa::mint("acme.holger.example", MATERIAL_VALID).unwrap();
        let me = ca.issue_installer(MATERIAL_VALID).unwrap();
        let boot = ca.issue_bootstrap(MATERIAL_VALID).unwrap();
        let seed = Seed {
            estate: "acme.holger.example".into(),
            tunnel: TunnelKind::Wireguard,
            front: Front::default(),
            kind: Kind::Holger,
            installer_ca_pem: ca.cert_pem.clone(),
            bootstrap_cert_pem: boot.cert_pem.clone(),
            bootstrap_key_pem: boot.key_pem.clone(),
            peers_expected: 3,
            mullvad: None,
            tailscale: None,
            installer_link: Some(LinkSeed { installer_pubkey: "A".repeat(43) + "=", vm_private: "B".repeat(43) + "=", port: crate::installer_link::LINK_PORT, psk: None }),
        };
        let vm = |name: &str, kind: Kind, last: u8| PlanVm { name: name.into(), public: Ipv4Addr::new(192, 0, 2, last), utility: Ipv4Addr::new(10, 13, 8, last), overlay: Ipv4Addr::new(10, 44, 0, last), kind, feed: BTreeMap::new() };
        let plan = Plan {
            estate: "acme.holger.example".into(),
            tunnel: TunnelKind::Wireguard,
            vms: vec![vm("front", Kind::Front, 1), vm("main", Kind::Holger, 2), vm("twin", Kind::Holger, 3)],
            feed: BTreeMap::new(),
            front: None,
            port: crate::bootstrap::PORT,
            link_port: crate::installer_link::LINK_PORT,
            budget_s: 600,
            close_when: Default::default(),
            box_cert_valid_s: None,
            front_waits_for: Vec::new(),
        };
        let files: BTreeMap<String, String> = [
            (crate::installer::CA_CERT.to_string(), ca.cert_pem.clone()),
            (crate::installer::CA_KEY.to_string(), ca.key_pem.clone()),
            (crate::installer::INSTALLER_CERT.to_string(), me.cert_pem),
            (crate::installer::INSTALLER_KEY.to_string(), me.key_pem),
            (crate::installer::LINK_KEYS.to_string(), Material::link_file(&("C".repeat(43) + "="), &("D".repeat(43) + "="))),
            (seed_file("holger"), seed.render().unwrap()),
        ]
        .into();
        (serde_json::to_vec(&serde_json::json!({"v": BUNDLE_V, "files": files, "ledger": "", "plan": plan})).unwrap(), ca)
    }

    /// ★ **A box laid again gets a NEW pre-shared key, and the front the same one** (lane
    /// WG-PSK, 2026-10-30): a moved box is a new machine with a new WireGuard key, so its link's
    /// PSK rotates with it — minted afresh, fed to the box (`wg_psk`) and to the front
    /// (`wg_psk.<box>`), the plan's two ends one key. The other box's link and PSK stand. The
    /// bundle as kept (the twin's copy of the sealed slot) carries it back; an estate kept with
    /// no PSK gets one for the moved box.
    #[test]
    fn a_box_laid_again_gets_a_new_preshared_key_and_the_front_the_same_one() {
        use crate::installer::{feed_wg_psk_for, FEED_WG_PSK};
        let (b, _) = bundle();
        let k = Kept::parse(&b).unwrap();
        let (old_main, old_twin) = (crate::installer::mint_wg_psk().unwrap(), crate::installer::mint_wg_psk().unwrap());
        let plan = k.plan.clone().with_wg_psks(&BTreeMap::from([("main".to_string(), old_main.clone()), ("twin".to_string(), old_twin.clone())])).unwrap();
        plan.check().unwrap();
        let get = |p: &Plan, n: &str| p.vms.iter().find(|v| v.name == n).unwrap().feed.clone();
        let p = moved(&plan, "main", Ipv4Addr::new(192, 0, 2, 9), Ipv4Addr::new(10, 13, 8, 9)).unwrap();
        let new_main = get(&p, "main").get(FEED_WG_PSK).cloned().expect("the moved box is fed a PSK");
        assert_ne!(new_main, old_main, "a new machine, a new key: the PSK rotates");
        assert_eq!(get(&p, "front").get(&feed_wg_psk_for("main")), Some(&new_main), "the front holds the same one");
        assert_eq!(get(&p, "twin").get(FEED_WG_PSK), Some(&old_twin), "the twin's link stands");
        assert_eq!(get(&p, "front").get(&feed_wg_psk_for("twin")), Some(&old_twin));
        p.check().unwrap();
        let mut back = k.clone();
        back.plan = p.clone();
        let again = Kept::parse(&back.render().unwrap()).unwrap();
        assert_eq!(get(&again.plan, "main").get(FEED_WG_PSK), Some(&new_main), "the kept bundle carries it (the twin's copy)");
        assert!(!format!("{again:?}").contains(&new_main), "never a PSK in Debug");
        // Kept with no PSK at all (an estate laid before PSKs): the moved box gets one.
        let bare = moved(&k.plan, "main", Ipv4Addr::new(192, 0, 2, 9), Ipv4Addr::new(10, 13, 8, 9)).unwrap();
        let fed = get(&bare, "main").get(FEED_WG_PSK).cloned().expect("a PSK for the box laid again");
        assert_eq!(get(&bare, "front").get(&feed_wg_psk_for("main")), Some(&fed));
        assert!(!get(&bare, "twin").contains_key(FEED_WG_PSK), "the standing twin is untouched");
        bare.check().unwrap();
    }

    /// ★ **A box laid again from the kept bundle keeps the installer link's pre-shared key**
    /// (0.5.29, lane WG-PSK-3): the fresh seed is the kept one with a new bootstrap pair, its
    /// link's PSK unchanged, and the kept material's link keys hold the same one, so the front
    /// (the installer then) reaches the new box over a link with its PSK.
    #[test]
    fn a_fresh_seed_and_the_kept_material_keep_the_links_preshared_key() {
        let (b, _) = bundle();
        let mut k = Kept::parse(&b).unwrap();
        let psk = crate::installer::mint_wg_psk().unwrap();
        let mut seed = Seed::parse(&k.files[&seed_file("holger")]).unwrap();
        seed.installer_link.as_mut().unwrap().psk = Some(psk.clone());
        k.files.insert(seed_file("holger"), seed.render().unwrap());
        let (ip, vp) = (wg_appliance_core::keys::generate_keypair_b64().0, wg_appliance_core::keys::generate_keypair_b64().1);
        k.files.insert(crate::installer::LINK_KEYS.to_string(), Material::link_file_with_psk(&ip, &vp, Some(&psk)));
        let fresh = Seed::parse(&k.fresh_seed("holger").unwrap()).unwrap();
        assert_eq!(fresh.installer_link.unwrap().psk, Some(psk.clone()), "the fresh seed keeps the link's PSK");
        assert_eq!(k.material().unwrap().link.unwrap().psk, Some(wg_appliance_core::decode_key(&psk).unwrap()), "the kept material dials with it");
        let again = Kept::parse(&k.render().unwrap()).unwrap();
        assert_eq!(Seed::parse(&again.fresh_seed("holger").unwrap()).unwrap().installer_link.unwrap().psk, Some(psk), "the bundle as the twin keeps it");
    }

    /// ★ The bundle the shop's pairing leaves reads back; a fresh seed is the kept one with a
    /// NEW bootstrap certificate from the kept CA; the material mints a fresh installer
    /// certificate; a moved plan moves ONE box and gives the re-admission its budget.
    #[test]
    fn a_kept_bundle_gives_a_fresh_seed_fresh_material_and_a_moved_plan() {
        let (b, ca) = bundle();
        let k = Kept::parse(&b).expect("parses");
        assert!(!format!("{k:?}").contains("PRIVATE"), "never a key in Debug");
        let fresh = Seed::parse(&k.fresh_seed("holger").unwrap()).unwrap();
        let kept = Seed::parse(&k.files[&seed_file("holger")]).unwrap();
        assert_eq!((fresh.estate.as_str(), fresh.kind, fresh.installer_ca_pem.as_str()), (kept.estate.as_str(), Kind::Holger, ca.cert_pem.as_str()));
        assert_ne!(fresh.bootstrap_cert_pem, kept.bootstrap_cert_pem, "a NEW bootstrap certificate");
        assert!(k.fresh_seed("gunnar").unwrap_err().contains("seed.gunnar.toml"), "a seed the bundle does not hold is named");
        let m = k.material().unwrap();
        assert_ne!(m.me.cert_pem, k.files[crate::installer::INSTALLER_CERT], "a fresh installer certificate");
        assert!(m.link.is_some());
        let p = moved(&k.plan, "main", Ipv4Addr::new(192, 0, 2, 9), Ipv4Addr::new(10, 13, 8, 9)).unwrap();
        let main = p.vms.iter().find(|v| v.name == "main").unwrap();
        assert_eq!((main.public, main.utility), (Ipv4Addr::new(192, 0, 2, 9), Ipv4Addr::new(10, 13, 8, 9)));
        assert_eq!(p.vms.iter().find(|v| v.name == "twin").unwrap().utility, Ipv4Addr::new(10, 13, 8, 3), "the twin stands where it stood");
        assert_eq!(p.budget_s, READMIT_BUDGET_S);
        assert!(moved(&k.plan, "nobody", Ipv4Addr::LOCALHOST, Ipv4Addr::LOCALHOST).is_err());
        let again = Kept::parse(&k.render().unwrap()).unwrap();
        assert_eq!(again.files, k.files);
        assert!(Kept::parse(br#"{"v":"installer-material/0"}"#).unwrap_err().contains(BUNDLE_V));
    }

    /// ★ T16: **re-admitting boxes together** names what it cannot do before it dials anything:
    /// no box named, a box the kept plan does not hold, a bundle without its link keys.
    #[cfg(feature = "installer-wireguard")]
    #[test]
    fn a_together_readmission_refuses_by_name_before_it_dials() {
        let (b, _) = bundle();
        let k = Kept::parse(&b).unwrap();
        assert!(readmit_together(&k, &[]).unwrap_err().contains("[installer-readmit]"), "no box named");
        assert!(readmit_together(&k, &["nas"]).unwrap_err().contains("nas"), "a box the plan does not hold");
        let mut keyless = k.clone();
        keyless.files.remove(crate::installer::LINK_KEYS);
        assert!(readmit_together(&keyless, &["main", "twin"]).unwrap_err().contains(crate::installer::LINK_KEYS), "no link keys");
    }
}