From d1bc3cfcf1b525069a1205823f0e85765815bf57 Mon Sep 17 00:00:00 2001 From: ruv Date: Sun, 24 May 2026 19:22:32 -0400 Subject: [PATCH] feat(adr-118/p6.9): BfldError Display format pinning (290/290 GREEN) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Iter 42. Pins the thiserror-derived Display output for every BfldError variant. Operators grep log lines for these strings; format drift between minor versions breaks monitoring queries and alerting rules. This iter locks the contract. Added (in tests/bfld_error_display.rs, 11 named tests): - One test per BfldError variant asserting the documented substrings appear in to_string(): invalid_magic_displays_both_expected_and_actual_in_hex unsupported_version_displays_the_offending_version crc_mismatch_displays_both_values_in_hex privacy_violation_displays_the_sink_reason invalid_privacy_class_displays_the_offending_byte truncated_frame_displays_got_and_need_byte_counts malformed_section_displays_offset_and_reason invalid_demote_displays_both_from_and_to_class_bytes - Meta tests: bfld_error_implements_std_error_trait (compile-time witness via fn assert_error_trait()) bfld_error_is_debug_so_panic_unwrap_messages_carry_diagnostics every_variant_has_a_non_empty_display_string (catch-all: 8 variants × non-empty Display assertion; guards against a future PR that adds a new variant without the #[error(...)] attribute) ADR-124 status (iter step 0 sibling check): - docs/adr/ADR-124-rvagent-mcp-ruvector-npm-integration.md unchanged at 431 lines. SENSE-BRIDGE scope remains orthogonal. ACs progressed: - ADR-118 §2.1 operator observability — error-message contract now pinned. A monitoring rule that greps for "payload CRC mismatch" or "privacy violation" continues to fire correctly across BFLD versions. Test config: - cargo test --no-default-features → 90 passed (bfld_error_display cfg-out) - cargo test → 290 passed (279 + 11) Out of scope (next iter target): - PR-readiness pivot remains the genuine next move: CHANGELOG batch, witness bundle regeneration, AC closeout table. All in-crate ACs empirically covered; remaining work is external-resource-gated (KIT BFId, Pi5/Nexmon hardware) or PR-prep. Co-Authored-By: claude-flow --- .../tests/bfld_error_display.rs | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 v2/crates/wifi-densepose-bfld/tests/bfld_error_display.rs diff --git a/v2/crates/wifi-densepose-bfld/tests/bfld_error_display.rs b/v2/crates/wifi-densepose-bfld/tests/bfld_error_display.rs new file mode 100644 index 00000000..6d2b6e48 --- /dev/null +++ b/v2/crates/wifi-densepose-bfld/tests/bfld_error_display.rs @@ -0,0 +1,132 @@ +//! `BfldError` Display format pinning. Operators grep log lines for these +//! strings; format drift between minor versions breaks monitoring queries. +//! Each variant gets a test that asserts the documented substrings appear. + +#![cfg(feature = "std")] + +use wifi_densepose_bfld::BfldError; + +#[test] +fn invalid_magic_displays_both_expected_and_actual_in_hex() { + let err = BfldError::InvalidMagic(0xDEAD_BEEF); + let s = err.to_string(); + assert!(s.contains("invalid BFLD magic"), "got: {s}"); + assert!(s.contains("0xBF1D0001"), "expected magic missing: {s}"); + assert!(s.contains("0xDEADBEEF"), "actual magic missing: {s}"); +} + +#[test] +fn unsupported_version_displays_the_offending_version() { + let err = BfldError::UnsupportedVersion(99); + let s = err.to_string(); + assert!(s.contains("unsupported BFLD version"), "got: {s}"); + assert!(s.contains("99"), "version number missing: {s}"); +} + +#[test] +fn crc_mismatch_displays_both_values_in_hex() { + let err = BfldError::Crc { + expected: 0xCAFEBABE, + actual: 0xDEADBEEF, + }; + let s = err.to_string(); + assert!(s.contains("payload CRC mismatch"), "got: {s}"); + assert!(s.contains("0xCAFEBABE"), "expected missing: {s}"); + assert!(s.contains("0xDEADBEEF"), "actual missing: {s}"); +} + +#[test] +fn privacy_violation_displays_the_sink_reason() { + let err = BfldError::PrivacyViolation { + reason: "NetworkKind", + }; + let s = err.to_string(); + assert!(s.contains("privacy violation"), "got: {s}"); + assert!(s.contains("NetworkKind"), "reason missing: {s}"); +} + +#[test] +fn invalid_privacy_class_displays_the_offending_byte() { + let err = BfldError::InvalidPrivacyClass(7); + let s = err.to_string(); + assert!(s.contains("invalid PrivacyClass byte"), "got: {s}"); + assert!(s.contains("7"), "byte value missing: {s}"); +} + +#[test] +fn truncated_frame_displays_got_and_need_byte_counts() { + let err = BfldError::TruncatedFrame { got: 50, need: 86 }; + let s = err.to_string(); + assert!(s.contains("truncated frame"), "got: {s}"); + assert!(s.contains("50"), "got count missing: {s}"); + assert!(s.contains("86"), "need count missing: {s}"); +} + +#[test] +fn malformed_section_displays_offset_and_reason() { + let err = BfldError::MalformedSection { + offset: 1234, + reason: "section body runs past buffer end", + }; + let s = err.to_string(); + assert!(s.contains("malformed payload section"), "got: {s}"); + assert!(s.contains("1234"), "offset missing: {s}"); + assert!(s.contains("buffer end"), "reason missing: {s}"); +} + +#[test] +fn invalid_demote_displays_both_from_and_to_class_bytes() { + let err = BfldError::InvalidDemote { from: 2, to: 1 }; + let s = err.to_string(); + assert!(s.contains("invalid demote"), "got: {s}"); + assert!(s.contains("from class 2"), "from missing: {s}"); + assert!(s.contains("to class 1"), "to missing: {s}"); +} + +// --- meta: error implements std::error::Error (for ? + dyn use) ------- + +#[test] +fn bfld_error_implements_std_error_trait() { + fn assert_error_trait() {} + assert_error_trait::(); +} + +#[test] +fn bfld_error_is_debug_so_panic_unwrap_messages_carry_diagnostics() { + let err = BfldError::Crc { + expected: 0xAA, + actual: 0xBB, + }; + let debug = format!("{err:?}"); + assert!(debug.contains("Crc"), "Debug must show variant name: {debug}"); +} + +// --- catch-all: every variant has a non-empty Display ----------------- + +#[test] +fn every_variant_has_a_non_empty_display_string() { + let cases: Vec = vec![ + BfldError::InvalidMagic(0), + BfldError::UnsupportedVersion(0), + BfldError::Crc { + expected: 0, + actual: 0, + }, + BfldError::PrivacyViolation { reason: "X" }, + BfldError::InvalidPrivacyClass(0), + BfldError::TruncatedFrame { got: 0, need: 0 }, + BfldError::MalformedSection { + offset: 0, + reason: "X", + }, + BfldError::InvalidDemote { from: 0, to: 0 }, + ]; + for err in cases { + let s = err.to_string(); + assert!(!s.is_empty(), "Display for {err:?} returned empty string"); + assert!( + s.len() >= 5, + "Display for {err:?} suspiciously short: {s:?}", + ); + } +}