test: add BAST drift detection for the StreamType enum

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
This commit is contained in:
glm-5.2 committed 2026-08-17 11:23:31 +00:00
1 parent da0d395ab3
commit 18c4924ad7
1 file changed
+66
+66
View File
@@ -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::<Vec<&str>>();
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
);
}
}
}