subc-protocol 0.29.4

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 serde_json::{json, Value};
use subc_protocol::{
    error_codes,
    session::{OperatorConfirmReply, OperatorConfirmRequest},
};

#[test]
fn operator_confirm_request_has_the_wire_shape_and_round_trips() {
    let request = OperatorConfirmRequest::new("Retire the agent", 7, 42);
    let wire = r#"{"op":"operator.confirm","summary":"Retire the agent","route_channel":7,"route_epoch":42}"#;
    assert_eq!(serde_json::to_string(&request).unwrap(), wire);
    assert_eq!(
        serde_json::from_str::<OperatorConfirmRequest>(wire).unwrap(),
        request
    );
}

#[test]
fn operator_confirm_reply_has_the_wire_shape_and_round_trips() {
    let reply = OperatorConfirmReply::confirmed();
    let wire = r#"{"op":"operator.confirm","outcome":"confirmed"}"#;
    assert_eq!(serde_json::to_string(&reply).unwrap(), wire);
    assert_eq!(
        serde_json::from_str::<OperatorConfirmReply>(wire).unwrap(),
        reply
    );
}

#[test]
fn operator_confirm_request_requires_the_op_and_all_fields() {
    let valid = json!({
        "op": "operator.confirm",
        "summary": "Retire the agent",
        "route_channel": 7,
        "route_epoch": 42
    });
    for field in ["op", "summary", "route_channel", "route_epoch"] {
        let mut missing = valid.clone();
        missing.as_object_mut().unwrap().remove(field);
        assert!(
            serde_json::from_value::<OperatorConfirmRequest>(missing).is_err(),
            "missing {field} must not decode"
        );
    }
    for invalid_op in [json!("scope.describe"), Value::Null, json!(7)] {
        let mut invalid = valid.clone();
        invalid["op"] = invalid_op;
        assert!(serde_json::from_value::<OperatorConfirmRequest>(invalid).is_err());
    }
    assert!(serde_json::from_value::<OperatorConfirmRequest>(json!({
        "op": "operator.confirm"
    }))
    .is_err());
}

#[test]
fn operator_confirm_reply_requires_the_op_and_outcome() {
    for invalid in [
        json!({"op": "operator.confirm"}),
        json!({"outcome": "confirmed"}),
        json!({"op": "scope.describe", "outcome": "confirmed"}),
        json!({"op": "operator.confirm", "outcome": null}),
    ] {
        assert!(serde_json::from_value::<OperatorConfirmReply>(invalid).is_err());
    }
}

#[test]
fn operator_confirm_bodies_allow_additive_unknown_fields() {
    let request: OperatorConfirmRequest = serde_json::from_value(json!({
        "op": "operator.confirm",
        "summary": "Retire the agent",
        "route_channel": 7,
        "route_epoch": 42,
        "future_field": true
    }))
    .unwrap();
    assert_eq!(
        request,
        OperatorConfirmRequest::new("Retire the agent", 7, 42)
    );
    let reply: OperatorConfirmReply = serde_json::from_value(json!({
        "op": "operator.confirm",
        "outcome": "confirmed",
        "future_field": true
    }))
    .unwrap();
    assert_eq!(reply, OperatorConfirmReply::confirmed());
}

#[test]
fn operator_confirm_error_codes_are_terminal_and_not_route_open_refusals() {
    let table: Value = serde_json::from_str(include_str!("golden/decision_tables.json")).unwrap();
    let route_open = table["route_open_retryable"].as_object().unwrap();
    for (code, spelling) in [
        (error_codes::OPERATOR_DECLINED, "operator_declined"),
        (
            error_codes::OPERATOR_PRESENCE_UNAVAILABLE,
            "operator_presence_unavailable",
        ),
        (
            error_codes::OPERATOR_SUMMARY_INVALID,
            "operator_summary_invalid",
        ),
        (
            error_codes::OPERATOR_REQUEST_NOT_PERMITTED,
            "operator_request_not_permitted",
        ),
    ] {
        assert_eq!(code, spelling);
        assert!(
            !error_codes::is_retryable_route_open(code),
            "{code} must not be retried"
        );
        assert!(
            !route_open.contains_key(code),
            "{code} is not a route.open refusal"
        );
    }
}