fix(maestro): tolerate unknown WS frame types instead of dropping the connection (NAP-269)
ci / rust (push) Successful in 14m9s
ci / rust (push) Successful in 14m9s
`dispatch_text` (registry) and `handle_inbound` (daemon) treated any frame that didn't deserialize into a known `DaemonOutbound`/`DaemonInbound` variant as fatal: the registry returned `false` (breaking the handler loop and deregistering the daemon, wiping its catalog), and the daemon propagated the serde error out of the read loop. A newer peer emitting a not-yet-merged variant would therefore get stuck in a tight disconnect/reconnect loop. - Add a `#[serde(other)] Unknown` catch-all to both enums so unknown `type` tags decode instead of erroring. - Log-and-skip the `Unknown` variant on both sides. - Also log-and-skip genuinely malformed frames rather than tearing down the connection, so one bad frame can't wipe a daemon's catalog. Tested: unknown/malformed frames keep the connection alive (registry handler), and unknown `type` decodes to `Unknown` (protocol round-trip). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -144,6 +144,11 @@ pub enum DaemonOutbound {
|
||||
reply_to: ReplyTo,
|
||||
accepted: bool,
|
||||
},
|
||||
/// Forward-compat catch-all: a frame whose `type` this build doesn't
|
||||
/// recognize (e.g. a newer daemon emitting a not-yet-merged variant).
|
||||
/// Readers log and skip it instead of tearing the connection down (NAP-269).
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Messages the registry sends back to a daemon over the same WS.
|
||||
@@ -182,6 +187,11 @@ pub enum DaemonInbound {
|
||||
CatalogEvent {
|
||||
event: RegistryEvent,
|
||||
},
|
||||
/// Forward-compat catch-all: a frame whose `type` this build doesn't
|
||||
/// recognize (e.g. a newer registry emitting a not-yet-merged variant).
|
||||
/// Readers log and skip it instead of tearing the connection down (NAP-269).
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -589,10 +599,21 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_message_type_is_rejected() {
|
||||
let bad = serde_json::json!({ "type": "Bogus" });
|
||||
let err = serde_json::from_value::<DaemonOutbound>(bad).unwrap_err();
|
||||
assert!(err.to_string().contains("Bogus"), "{}", err);
|
||||
fn unknown_message_type_deserializes_to_unknown() {
|
||||
// A newer peer can introduce a frame `type` this build has never heard
|
||||
// of. It must decode to the forward-compat `Unknown` variant rather
|
||||
// than erroring, so the reader skips it instead of dropping the WS
|
||||
// (NAP-269).
|
||||
let bad = serde_json::json!({ "type": "Bogus", "extra": 1 });
|
||||
assert_eq!(
|
||||
serde_json::from_value::<DaemonOutbound>(bad).unwrap(),
|
||||
DaemonOutbound::Unknown
|
||||
);
|
||||
let bad_inbound = serde_json::json!({ "type": "AlsoBogus" });
|
||||
assert_eq!(
|
||||
serde_json::from_value::<DaemonInbound>(bad_inbound).unwrap(),
|
||||
DaemonInbound::Unknown
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user