feat(maestro): DaemonInbound::CatalogEvent + RegistryEvent::for_peer (NAP-246)
New control-link message wrapping a registry event for daemons that want a local catalog mirror. `RegistryEvent::for_peer(self_id)` strips events that concern the recipient itself — daemons mirror peer state, not self. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -174,6 +174,14 @@ pub enum DaemonInbound {
|
|||||||
home_daemon: String,
|
home_daemon: String,
|
||||||
session_id: Uuid,
|
session_id: Uuid,
|
||||||
},
|
},
|
||||||
|
/// Catalog delta forwarded from the registry so each connected daemon can
|
||||||
|
/// maintain a local mirror of the global catalog (peer daemons + their
|
||||||
|
/// sessions). The registry filters out events originating at the
|
||||||
|
/// recipient daemon — a daemon only sees *peer* deltas. The first event
|
||||||
|
/// a freshly-connected daemon receives is a `RegistryEvent::Snapshot`.
|
||||||
|
CatalogEvent {
|
||||||
|
event: RegistryEvent,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -278,6 +286,46 @@ pub enum RegistryEvent {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RegistryEvent {
|
||||||
|
/// Filter this event for a daemon recipient that should only see *peer*
|
||||||
|
/// state. Returns `None` if the entire event concerns the recipient,
|
||||||
|
/// otherwise a possibly-pruned event with self-references removed.
|
||||||
|
///
|
||||||
|
/// Used by the registry's daemon fan-out (NAP-246) so each daemon's
|
||||||
|
/// local catalog mirror reflects every other daemon, not itself.
|
||||||
|
pub fn for_peer(self, self_daemon_id: &str) -> Option<RegistryEvent> {
|
||||||
|
match self {
|
||||||
|
RegistryEvent::Snapshot { daemons, sessions } => {
|
||||||
|
let daemons = daemons
|
||||||
|
.into_iter()
|
||||||
|
.filter(|d| d.daemon_id != self_daemon_id)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let sessions = sessions
|
||||||
|
.into_iter()
|
||||||
|
.filter(|s| s.daemon_id != self_daemon_id)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
Some(RegistryEvent::Snapshot { daemons, sessions })
|
||||||
|
}
|
||||||
|
RegistryEvent::DaemonAdded { ref daemon } if daemon.daemon_id == self_daemon_id => None,
|
||||||
|
RegistryEvent::DaemonRemoved { ref daemon_id } if daemon_id == self_daemon_id => None,
|
||||||
|
RegistryEvent::SessionAdded { ref session } if session.daemon_id == self_daemon_id => {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
RegistryEvent::SessionRemoved { ref daemon_id, .. } if daemon_id == self_daemon_id => {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
RegistryEvent::SessionUpdated { ref session }
|
||||||
|
if session.daemon_id == self_daemon_id =>
|
||||||
|
{
|
||||||
|
None
|
||||||
|
}
|
||||||
|
// Everything else (Lagged, peer adds/removes/updates) is forwarded
|
||||||
|
// as-is.
|
||||||
|
other => Some(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Client ↔ Daemon control messages (text frames on the attach WS)
|
// Client ↔ Daemon control messages (text frames on the attach WS)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -711,6 +759,120 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn registry_event_for_peer_filters_self() {
|
||||||
|
let mine = sample_session(); // daemon_id = "laptop"
|
||||||
|
let mut peer_session = sample_session();
|
||||||
|
peer_session.daemon_id = "konishi".into();
|
||||||
|
peer_session.id = Uuid::from_u128(99);
|
||||||
|
|
||||||
|
let snapshot = RegistryEvent::Snapshot {
|
||||||
|
daemons: vec![
|
||||||
|
DaemonInfo {
|
||||||
|
daemon_id: "laptop".into(),
|
||||||
|
hostname: "naps-laptop".into(),
|
||||||
|
version: "0.1.0".into(),
|
||||||
|
daemon_url: "ws://10.0.0.1:7070".into(),
|
||||||
|
},
|
||||||
|
DaemonInfo {
|
||||||
|
daemon_id: "konishi".into(),
|
||||||
|
hostname: "konishi".into(),
|
||||||
|
version: "0.1.0".into(),
|
||||||
|
daemon_url: "ws://10.0.0.2:7070".into(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sessions: vec![mine.clone(), peer_session.clone()],
|
||||||
|
};
|
||||||
|
let filtered = snapshot.clone().for_peer("laptop").unwrap();
|
||||||
|
match filtered {
|
||||||
|
RegistryEvent::Snapshot { daemons, sessions } => {
|
||||||
|
assert_eq!(daemons.len(), 1);
|
||||||
|
assert_eq!(daemons[0].daemon_id, "konishi");
|
||||||
|
assert_eq!(sessions.len(), 1);
|
||||||
|
assert_eq!(sessions[0].daemon_id, "konishi");
|
||||||
|
}
|
||||||
|
other => panic!("unexpected {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn registry_event_for_peer_drops_self_deltas() {
|
||||||
|
let mine = sample_session(); // daemon_id "laptop"
|
||||||
|
assert!(RegistryEvent::SessionAdded {
|
||||||
|
session: mine.clone()
|
||||||
|
}
|
||||||
|
.for_peer("laptop")
|
||||||
|
.is_none());
|
||||||
|
assert!(RegistryEvent::SessionUpdated {
|
||||||
|
session: mine.clone()
|
||||||
|
}
|
||||||
|
.for_peer("laptop")
|
||||||
|
.is_none());
|
||||||
|
assert!(RegistryEvent::SessionRemoved {
|
||||||
|
daemon_id: "laptop".into(),
|
||||||
|
id: mine.id,
|
||||||
|
}
|
||||||
|
.for_peer("laptop")
|
||||||
|
.is_none());
|
||||||
|
assert!(RegistryEvent::DaemonAdded {
|
||||||
|
daemon: DaemonInfo {
|
||||||
|
daemon_id: "laptop".into(),
|
||||||
|
hostname: "x".into(),
|
||||||
|
version: "y".into(),
|
||||||
|
daemon_url: "z".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.for_peer("laptop")
|
||||||
|
.is_none());
|
||||||
|
assert!(RegistryEvent::DaemonRemoved {
|
||||||
|
daemon_id: "laptop".into()
|
||||||
|
}
|
||||||
|
.for_peer("laptop")
|
||||||
|
.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn registry_event_for_peer_forwards_peer_deltas_and_lagged() {
|
||||||
|
let mut peer = sample_session();
|
||||||
|
peer.daemon_id = "konishi".into();
|
||||||
|
assert!(matches!(
|
||||||
|
RegistryEvent::SessionAdded {
|
||||||
|
session: peer.clone()
|
||||||
|
}
|
||||||
|
.for_peer("laptop"),
|
||||||
|
Some(RegistryEvent::SessionAdded { .. })
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
RegistryEvent::Lagged { skipped: 3 }.for_peer("laptop"),
|
||||||
|
Some(RegistryEvent::Lagged { skipped: 3 })
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn daemon_inbound_catalog_event_round_trip_and_tag() {
|
||||||
|
let snapshot = DaemonInbound::CatalogEvent {
|
||||||
|
event: RegistryEvent::Snapshot {
|
||||||
|
daemons: vec![DaemonInfo {
|
||||||
|
daemon_id: "peer".into(),
|
||||||
|
hostname: "peer-host".into(),
|
||||||
|
version: "0.1.0".into(),
|
||||||
|
daemon_url: "ws://peer:7070".into(),
|
||||||
|
}],
|
||||||
|
sessions: vec![sample_session()],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let v = serde_json::to_value(&snapshot).unwrap();
|
||||||
|
assert_eq!(v["type"], "CatalogEvent");
|
||||||
|
assert_eq!(snapshot, round_trip(&snapshot));
|
||||||
|
|
||||||
|
let delta = DaemonInbound::CatalogEvent {
|
||||||
|
event: RegistryEvent::SessionAdded {
|
||||||
|
session: sample_session(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert_eq!(delta, round_trip(&delta));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn daemon_inbound_relay_setup_round_trip_and_tag() {
|
fn daemon_inbound_relay_setup_round_trip_and_tag() {
|
||||||
let msg = DaemonInbound::RelaySetup {
|
let msg = DaemonInbound::RelaySetup {
|
||||||
|
|||||||
Reference in New Issue
Block a user