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
//! **A re-laid main is filled from its twin by the FRONT** (Gunnar c7c9e11a, 2026-10-12;
//! shared here by lane G11, 2026-10-21, so Gunnar's and Holger's fronts decide it alike).
//!
//! A main laid again (its server gone, or its data emptied) is re-admitted fresh and holds
//! itself back: it AWAITS its twin (Gunnar `Bootstrap.Status.recharge_from`, Holger
//! `RechargeStatus.awaiting_from`). The front is the one place that reaches both boxes over
//! the tunnel with a key both accept (the console's), so when the estate opted into
//! auto-fix (the front's feed says `repair_auto = true`) the front does the operator's
//! [Recharge from twin] by itself: the product's ONE recharge path, into a main that SAYS
//! it awaits. A box that holds data is never streamed into.
//!
//! What is shared is the decision ([`due`]) and the words; each front asks its own boxes
//! (Gunnar: `gunnar_repair::recharge_from_twin`; Holger: `nordisk_twin::recharge::run`) and
//! runs the look as its own background job.

use std::time::Duration;

/// The fed opt-in: the front's feed carries `repair_auto = true` (exactly that word).
pub const FEED_REPAIR_AUTO: &str = "repair_auto";

/// How often a front looks (Gunnar's 20 s).
pub const LOOK_EVERY: Duration = Duration::from_secs(20);

/// **What the main says of itself**, in neither product's types.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Awaits<'a> {
    /// It holds nothing of its own (a recharge may start).
    pub fresh: bool,
    /// A recharge began and is not whole (the product's recharge resumes it).
    pub restoring: bool,
    /// The twin its re-admission named, empty when none did (a first install).
    pub recharge_from: &'a str,
}

/// Pure: **is auto-fix fed?** Only the exact word `true` (trimmed) says yes.
pub fn auto_on(auto: Option<&str>) -> bool {
    auto.map(str::trim) == Some("true")
}

/// Pure: **may the front fill the main now?** Auto-fix fed exactly `true`, and the main
/// SAYS it awaits its twin: fresh (or a restore it began) and naming the twin.
pub fn due(auto: Option<&str>, main: &Awaits<'_>) -> bool {
    auto_on(auto) && !main.recharge_from.trim().is_empty() && (main.fresh || main.restoring)
}

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

    const TWIN: &str = "10.44.0.3:50051";

    /// Gunnar's five cases (gunnar-front `autofill::due`, c7c9e11a): the front fills only a
    /// main that SAYS it awaits its twin, and only with the opt-in fed.
    #[test]
    fn the_front_fills_only_an_awaiting_main_with_auto_fix_fed() {
        let awaiting = Awaits { fresh: true, restoring: false, recharge_from: TWIN };
        assert!(due(Some("true"), &awaiting));
        assert!(due(Some(" true\n"), &awaiting), "the word is trimmed");
        assert!(!due(None, &awaiting) && !due(Some("yes"), &awaiting) && !due(Some("TRUE"), &awaiting), "auto-fix not fed");
        let restoring = Awaits { fresh: false, restoring: true, ..awaiting };
        assert!(due(Some("true"), &restoring), "a restore it began is resumed");
        let whole = Awaits { fresh: false, restoring: false, ..awaiting };
        assert!(!due(Some("true"), &whole), "a main with data is never streamed into");
        let first_install = Awaits { fresh: true, restoring: false, recharge_from: "  " };
        assert!(!due(Some("true"), &first_install), "a fresh main nobody re-admitted as a recharge is not filled");
    }
}