From 18c4924ad7b64c6dca871034ce816f940e75fc39 Mon Sep 17 00:00:00 2001 From: "glm-5.2" Date: Mon, 17 Aug 2026 11:23:31 +0000 Subject: [PATCH] test: add BAST drift detection for the StreamType enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parse docs/architecture/tty-bast.md at test time, extract the BAST JSON block, and assert the StreamType enum values match wire.rs's STREAM_STDIN/STREAM_STDOUT/STREAM_STDERR/STREAM_CTRL_IN/ STREAM_CTRL_OUT constants (the index of each name in the enum's values array is the integer on the wire). Catches the common drift case — a new stream_type added to wire.rs but not the BAST, or vice versa — without requiring alktype as a dep, just serde_json which alktty already has. This is the mitigation listed in the project setup plan's "Risk: BAST schema drift" section. The test lives in src/wire.rs mod tests alongside the STREAM_* constants it asserts against, so a developer editing the constants sees the test fail locally before the BAST doc and the wire format drift apart. Verified the test catches drift by temporarily renaming CtrlOut -> CtrlOutX in the BAST: the test fails with a clear diagnostic ("StreamType[4] is 'CtrlOutX' in BAST but 'CtrlOut' in wire.rs"), then restored. Verification: - cargo test --all-features -> 123 tests pass (104 lib + 19 integration; +1 from the new drift test) - cargo clippy --all-targets --all-features -- -D warnings -> clean - cargo clippy --target wasm32-unknown-unknown -- -D warnings -> clean - cargo fmt --check -> clean --- src/wire.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/wire.rs b/src/wire.rs index 9668ef0..de5bfb2 100644 --- a/src/wire.rs +++ b/src/wire.rs @@ -432,4 +432,70 @@ mod tests { let err = reader.read_chunk().await.unwrap_err(); assert!(matches!(err, RawError::ConnectionClosed)); } + + /// Drift detection: the `StreamType` enum in the BAST document + /// (`docs/architecture/tty-bast.md`) MUST list the stream types in + /// the same order as the `STREAM_*` constants in this module, so + /// the BAST's index→name table matches the on-wire `uint8` values. + /// Catches the common drift case (a new stream_type added to + /// `wire.rs` but not the BAST, or vice versa) without requiring + /// alktype as a dep — just `serde_json`, which alktty already has. + /// See the project setup plan's "Risk: BAST schema drift" + /// mitigation. + #[test] + fn bast_stream_type_enum_matches_wire_constants() { + let bast_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("docs/architecture/tty-bast.md"); + let markdown = std::fs::read_to_string(&bast_path) + .unwrap_or_else(|e| panic!("failed to read {}: {e}", bast_path.display())); + + let json_start = markdown + .find("```json\n") + .expect("BAST markdown has a ```json block") + + "```json\n".len(); + let json_end = markdown[json_start..] + .find("\n```") + .expect("BAST json block is terminated") + + json_start; + let bast: serde_json::Value = serde_json::from_str(&markdown[json_start..json_end]) + .expect("BAST json block parses"); + + let stream_type = bast + .get("$defs") + .and_then(|d| d.get("StreamType")) + .expect("BAST has $defs.StreamType"); + assert_eq!( + stream_type["kind"].as_str(), + Some("enum"), + "StreamType is a BAST enum" + ); + let values = stream_type["values"] + .as_array() + .expect("StreamType.values is an array") + .iter() + .map(|v| v.as_str().expect("enum value is a string")) + .collect::>(); + + let expected = [ + ("Stdin", STREAM_STDIN), + ("Stdout", STREAM_STDOUT), + ("Stderr", STREAM_STDERR), + ("CtrlIn", STREAM_CTRL_IN), + ("CtrlOut", STREAM_CTRL_OUT), + ]; + assert_eq!( + values.len(), + expected.len(), + "StreamType enum has {} values, wire.rs has {} — drift", + values.len(), + expected.len() + ); + for (i, (name, value)) in expected.iter().enumerate() { + assert_eq!( + values[i], *name, + "StreamType[{i}] is {:?} in BAST but {:?} in wire.rs (value {value})", + values[i], name + ); + } + } }