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"
);
}