From deb335aada893016c781522593a13ccfcc8375d8 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 23 Aug 2026 15:19:45 +0800 Subject: [PATCH] test(protocol): pin the wire spelling of every simple enum variant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `rename_all` derives these strings from the Rust variant names, so a rename in a refactor changes what goes on the wire without changing a string in the source. The dialect number guards a variant being *added*; nothing guarded one being renamed. Probed by giving each variant a `#[serde(rename)]` and running the suite: 43 of them changed spelling with nothing failing. Written as the tag an older peer sends, decoded here, because that is the direction that breaks people: a build that no longer recognises `"dir"` does not degrade — it fails the frame, and every directory in a remote listing disappears. Covers the enums where the tag is the whole message. The data-carrying ones are deliberately left out: their payloads move with them, so renaming one is a wider change than a string and will not pass silently. Re-swept afterwards — none of the 21 unit variants changes spelling now without failing. --- crates/tty7-core/src/daemon/protocol.rs | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/crates/tty7-core/src/daemon/protocol.rs b/crates/tty7-core/src/daemon/protocol.rs index 61854625..67ecf56f 100644 --- a/crates/tty7-core/src/daemon/protocol.rs +++ b/crates/tty7-core/src/daemon/protocol.rs @@ -2322,6 +2322,52 @@ mod tests { assert_eq!(info.title, ""); } + /// The wire spelling of every simple enum variant, pinned. + /// + /// These strings are derived from the Rust variant names by `rename_all`, + /// so a rename in a refactor changes what goes on the wire without + /// changing a single string in the source. The dialect number guards a + /// variant being *added* — `a_new_wire_variant_has_to_move_the_dialect_ + /// number` in `control.rs` — and nothing guarded one being renamed. + /// + /// Written as the tag an older peer sends, decoded here: that is the + /// direction that breaks people. A newer build that no longer recognises + /// `"dir"` does not degrade, it fails the frame, and every directory in a + /// remote listing disappears. + /// + /// The data-carrying enums (`SftpOp`, `SftpOpResult`, `WorkspaceOp`, + /// `SshProxy`, `ForwardStatus`) are not here: their payloads move with + /// them, so a rename is a wider change than a string. This covers the ones + /// where the tag is the whole message. + #[test] + fn the_wire_spelling_of_every_simple_enum_variant_is_pinned() { + fn decodes(what: &str, tags: &[&str]) { + for tag in tags { + let json = format!("\"{tag}\""); + serde_json::from_str::(&json).unwrap_or_else(|e| { + panic!("{what} no longer accepts {json}, which an older peer sends: {e}") + }); + } + } + + decodes::("RemoteKind", &["ssh", "native-ssh", "wsl"]); + decodes::( + "SshAuthMode", + &[ + "auto", + "gssapi", + "password", + "public-key", + "agent", + "keyboard-interactive", + ], + ); + decodes::("SshForwardKind", &["local", "remote", "dynamic"]); + decodes::("SftpEntryKind", &["file", "dir", "symlink"]); + decodes::("SftpTransferKind", &["upload", "download"]); + decodes::("SftpJobState", &["running", "done", "error", "cancelled"]); + } + /// The wire structs an older peer sends short still decode. /// /// `#[serde(default)]` on a non-`Option` field is load-bearing: serde