Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar

crates/gunnar-server/tests/the_pairing_loop_keeps_the_latest_feed.rs

// The pairing loop and its door are `wg-client` code; the gix engine arm builds without it.
#![cfg(feature = "wg-client")]
//! **The pairing loop never puts back a feed older than the one the installer
//! fed since** (cov-A r3 on oden 2026-10-06).
//!
//! MEASURED: the twin's first join answer carried no clock (the front had not
//! offered it yet); the installer fed the twin twice more, the front's clock
//! offer among it, and `Installed::fed` published those. But the pairing loop
//! handed `take_feed` the copy it learned at ITS join on every turn, so each
//! turn published the clock-less copy back over the installer's newer feed —
//! and after the loop rested the twin's clock read "no clock source is
//! configured" for the life of the process (its timer and its skew trigger
//! read the published feed). The loop hands over what it learned when it
//! learned it, not again on every turn.
//!
//! **A feed that changes only another key reaches its subscriber** (L10,
//! 2026-10-20): the feed is published on the shared bus (nordisk-pairing
//! `feedbus`), which hands every CHANGED feed to every subscriber. Gunnar's own
//! bus was handed a feed only when its clock or monetize key changed, so a
//! `fleet` fed later beside the same clock never reached `repair::fleet_when_fed`.
//!
//! One test in this file: the published feed is the process's own.

use std::sync::{Arc, Mutex};

use gunnar_server::feed::{Fed, FEED_CLOCK_KEY, FEED_CLOCK_PEER, FEED_FLEET};
use gunnar_server::pairing::{take_feed, take_learned_feed};
use nordisk_pairing::feedbus::{self, Feed};

fn feed(pairs: &[(&str, &str)]) -> Feed {
    pairs
        .iter()
        .map(|(k, v)| (k.to_string(), v.to_string()))
        .collect()
}

fn now() -> Option<Fed> {
    Fed::current()
}

#[test]
fn a_turn_of_the_loop_does_not_put_its_older_join_answer_back_over_the_installers_newer_feed() {
    let key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGNvdi1hLXRlc3Qta2V5LW5vdC1hLXJlYWwtb25lAA";
    // Every fleet door this process is fed, as the pull channel's subscriber sees it.
    let fleets = Arc::new(Mutex::new(Vec::new()));
    let seen = Arc::clone(&fleets);
    feedbus::on_published("test: the fleet door", move |f: &Feed| {
        seen.lock().unwrap().push(Fed::from_feed(f).fleet)
    });

    // The loop's join answer: no clock yet.
    let joined = feed(&[]);
    let mut taken = None;
    take_learned_feed(&mut taken, Some(&joined));
    // The installer feeds again, the front's clock offer among it (`Installed::fed`).
    let newer = feed(&[
        (FEED_CLOCK_KEY, key),
        (FEED_CLOCK_PEER, "peer://10.44.0.1:50123"),
    ]);
    take_feed(Some(&newer));
    assert_eq!(now(), Some(Fed::from_feed(&newer)));
    // The loop turns on, still holding its join answer.
    for _ in 0..3 {
        take_learned_feed(&mut taken, Some(&joined));
    }
    assert_eq!(now(), Some(Fed::from_feed(&newer)), "the loop put its older join answer back over the installer's newer feed: the clock this box was fed is gone");
    // A NEW join answer is handed over as before.
    let rejoined = feed(&[
        (FEED_CLOCK_KEY, key),
        (FEED_CLOCK_PEER, "peer://10.44.0.9:50123"),
    ]);
    take_learned_feed(&mut taken, Some(&rejoined));
    assert_eq!(now(), Some(Fed::from_feed(&rejoined)));

    // ★ L10: the installer feeds the SAME clock and, now, the fleet door — the clock and the
    // monetize key did not change, the fleet did: its subscriber gets it at once.
    let with_fleet = feed(&[
        (FEED_CLOCK_KEY, key),
        (FEED_CLOCK_PEER, "peer://10.44.0.9:50123"),
        (FEED_FLEET, "https://monetize.example:8443"),
    ]);
    take_feed(Some(&with_fleet));
    assert_eq!(
        now().and_then(|f| f.fleet).as_deref(),
        Some("https://monetize.example:8443"),
        "the feed in force is the newest, whichever key changed"
    );
    assert_eq!(
        fleets.lock().unwrap().last().cloned().flatten().as_deref(),
        Some("https://monetize.example:8443"),
        "a fleet fed beside an unchanged clock never reached the pull channel's subscriber"
    );
    // The same feed again reaches nobody.
    let n = fleets.lock().unwrap().len();
    take_feed(Some(&with_fleet));
    assert_eq!(
        fleets.lock().unwrap().len(),
        n,
        "the same feed again was handed to the subscriber again"
    );
}