From f0510a599613ef6715ef78b738dab15a6cb40c1c Mon Sep 17 00:00:00 2001 From: naps62-yolo Date: Tue, 26 May 2026 17:43:55 +0000 Subject: [PATCH] feat(maestro): add ResolveAttach + RelaySetup/RelayAck wire variants (NAP-242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DaemonOutbound: ResolveAttach (daemon → registry request) and RelayAck (target → registry decision). DaemonInbound: ResolveAttachResult wrapping RpcResult, and RelaySetup (registry → target signal). All correlated via reply_to, matching the existing CreateSession pattern. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/maestro-protocol/src/lib.rs | 100 +++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/crates/maestro-protocol/src/lib.rs b/crates/maestro-protocol/src/lib.rs index 57f47a1..d866a01 100644 --- a/crates/maestro-protocol/src/lib.rs +++ b/crates/maestro-protocol/src/lib.rs @@ -122,6 +122,20 @@ pub enum DaemonOutbound { reply_to: ReplyTo, result: RpcResult<()>, }, + /// Home daemon asks the registry to resolve a session it doesn't own: + /// "where is session X and how do I reach it?" The registry replies with + /// [`DaemonInbound::ResolveAttachResult`] containing [`AttachCandidates`]. + ResolveAttach { + reply_to: ReplyTo, + session_id: Uuid, + }, + /// Target daemon answers a [`DaemonInbound::RelaySetup`]: whether it will + /// accept relay traffic for the requested session. Registry only allocates + /// relay capacity when `accepted == true`. + RelayAck { + reply_to: ReplyTo, + accepted: bool, + }, } /// Messages the registry sends back to a daemon over the same WS. @@ -138,6 +152,20 @@ pub enum DaemonInbound { reply_to: ReplyTo, id: Uuid, }, + /// Registry's response to [`DaemonOutbound::ResolveAttach`]. `Err` covers + /// "session not found", "no daemon owns this session", etc. + ResolveAttachResult { + reply_to: ReplyTo, + result: RpcResult, + }, + /// Registry signals a target daemon that a home daemon wants to attach via + /// the registry-mediated relay (T3). Target evaluates `allow_inbound` and + /// replies with [`DaemonOutbound::RelayAck`]. + RelaySetup { + reply_to: ReplyTo, + home_daemon: String, + session_id: Uuid, + }, } // --------------------------------------------------------------------------- @@ -541,6 +569,78 @@ mod tests { assert_eq!(json["type"], "DaemonRemoved"); } + #[test] + fn daemon_outbound_resolve_attach_round_trip_and_tag() { + let msg = DaemonOutbound::ResolveAttach { + reply_to: Uuid::from_u128(11), + session_id: Uuid::from_u128(22), + }; + let v = serde_json::to_value(&msg).unwrap(); + assert_eq!(v["type"], "ResolveAttach"); + assert_eq!(msg, round_trip(&msg)); + } + + #[test] + fn daemon_outbound_relay_ack_round_trip_and_tag() { + let msg = DaemonOutbound::RelayAck { + reply_to: Uuid::from_u128(33), + accepted: true, + }; + let v = serde_json::to_value(&msg).unwrap(); + assert_eq!(v["type"], "RelayAck"); + assert_eq!(v["accepted"], true); + assert_eq!(msg, round_trip(&msg)); + + let denied = DaemonOutbound::RelayAck { + reply_to: Uuid::from_u128(34), + accepted: false, + }; + assert_eq!(denied, round_trip(&denied)); + } + + #[test] + fn daemon_inbound_resolve_attach_result_round_trip() { + let ok = DaemonInbound::ResolveAttachResult { + reply_to: Uuid::from_u128(11), + result: RpcResult::Ok { + value: AttachCandidates { + direct_wss: None, + perimeter: Some(PerimeterCandidate { + perimeter_id: "wg-naps62".into(), + private_addr: "10.0.0.7:7070".into(), + }), + relay: None, + jwt: "eyJ...".into(), + }, + }, + }; + let err = DaemonInbound::ResolveAttachResult { + reply_to: Uuid::from_u128(12), + result: RpcResult::Err { + error: ErrorInfo { + code: "unknown_session".into(), + message: "no daemon owns session".into(), + }, + }, + }; + for v in [ok, err] { + assert_eq!(v, round_trip(&v)); + } + } + + #[test] + fn daemon_inbound_relay_setup_round_trip_and_tag() { + let msg = DaemonInbound::RelaySetup { + reply_to: Uuid::from_u128(33), + home_daemon: "laptop".into(), + session_id: Uuid::from_u128(44), + }; + let v = serde_json::to_value(&msg).unwrap(); + assert_eq!(v["type"], "RelaySetup"); + assert_eq!(v["home_daemon"], "laptop"); + assert_eq!(msg, round_trip(&msg)); + } + #[test] fn perimeter_candidate_round_trips() { let p = PerimeterCandidate {