subc-protocol 0.32.2

Shared wire contract for subc <-> modules: the 21-byte envelope, the Frame (header + opaque body), channel-0 control bodies, route.bind/RouteTarget session shapes, and the capability manifest. Single source of truth, depended on by subc-core and AFT.
Documentation
use subc_protocol::{
    scope::{validate_bind_proof_sha256, ScopeKind, ScopeRecord, ScopeSelector},
    Principal,
};

const PROOF: &str = "BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc";
const HASH: &str = "4bb06f8e4e3a7715d201d573d0aa423762e55dabd61a2c02278fa56cc6d294e0";

#[test]
fn scope_bind_proof_hash_requires_lowercase_sha256() {
    assert!(validate_bind_proof_sha256(HASH).is_ok());
    for hash in [HASH.to_uppercase(), HASH[..63].to_string(), "g".repeat(64)] {
        assert!(validate_bind_proof_sha256(&hash).is_err());
        let json = serde_json::json!({"ref":"s", "scope_epoch":1, "kind":"head", "bind_proof_sha256":hash});
        let err = serde_json::from_value::<ScopeRecord>(json).unwrap_err();
        assert!(err.to_string().contains("bind_proof_sha256"));
    }
}

#[test]
fn scope_bind_proof_unset_fields_keep_original_bytes() {
    assert_eq!(
        serde_json::to_string(&ScopeRecord::new("s", 1, ScopeKind::Head)).unwrap(),
        r#"{"ref":"s","scope_epoch":1,"kind":"head"}"#
    );
    assert_eq!(
        serde_json::to_string(&ScopeSelector::new(Principal::Direct, "s", Some(1))).unwrap(),
        r#"{"owner":{"kind":"direct"},"ref":"s","scope_epoch":1}"#
    );
}

#[test]
fn scope_bind_proof_debug_and_display_are_redacted() {
    let selector =
        ScopeSelector::new(Principal::Direct, "s", Some(1)).with_bind_proof(Some(PROOF.into()));
    for output in [format!("{selector:?}"), selector.to_string()] {
        assert!(output.contains("<redacted>"));
        assert!(!output.contains(PROOF));
    }
}

#[test]
fn scope_bind_proof_codes_are_terminal() {
    for (code, expected) in [
        (
            subc_protocol::error_codes::SCOPE_BIND_PROOF_REQUIRED,
            "scope_bind_proof_required",
        ),
        (
            subc_protocol::error_codes::SCOPE_BIND_PROOF_MISMATCH,
            "scope_bind_proof_mismatch",
        ),
    ] {
        assert_eq!(code, expected);
        assert!(!subc_protocol::error_codes::is_retryable_route_open(code));
    }
}