sim-codec-bridge 0.1.3

BRIDGE packet line codec for SIM.
Documentation
use std::sync::Arc;

use sim_codec::{DecodeLimits, Input, decode_with_codec_and_limits, encode_with_codec};
use sim_kernel::{CodecId, ReadPolicy};
use sim_kernel::{DefaultFactory, EagerPolicy, EncodeOptions, Expr, Symbol};

use crate::{
    BridgeBook, BridgeCodecLib, BridgeFramePayload, BridgeHeader, BridgePacket, BridgePart,
    BridgeProvenance, BridgeReceiptPayload, BridgeWarrantPolicy, decode_bridge_text,
    decode_bridge_text_with_limits, encode_bridge_text, frame_book_content_id, packet_to_expr,
    warrant_for_packet,
};

fn brief_packet() -> BridgePacket {
    BridgePacket {
        header: BridgeHeader {
            cid: None,
            move_kind: Symbol::new("request"),
            from: "sim".to_owned(),
            to: vec!["model:drafter".to_owned()],
            role: Symbol::new("implementer"),
            parents: Vec::new(),
            task: Symbol::new("T1"),
            output: Symbol::new("O1"),
            ceiling: vec![Symbol::qualified("ai", "run")],
            context: vec![Symbol::new("C1")],
            provenance: BridgeProvenance::default(),
        },
        body: vec![
            BridgePart {
                id: Symbol::new("T1"),
                kind: Symbol::qualified("bridge", "Frame"),
                payload: BridgeFramePayload::new(Symbol::qualified("bridge", "proposal")).to_expr(),
            },
            BridgePart {
                id: Symbol::new("O1"),
                kind: Symbol::qualified("bridge", "Return"),
                payload: Expr::Map(vec![sim_value::build::entry(
                    "codec",
                    Expr::Symbol(Symbol::qualified("codec", "bridge")),
                )]),
            },
        ],
        warrant: None,
    }
}

fn cx() -> sim_kernel::Cx {
    let mut cx = sim_kernel::Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
    sim_test_support::register_core_classes(&mut cx);
    let lib = BridgeCodecLib::new(cx.registry_mut().fresh_codec_id());
    cx.load_lib(&lib).unwrap();
    cx
}

#[test]
fn bridge_book_accepts_standard_packet() {
    let book = BridgeBook::standard();
    let packet = brief_packet();
    let text = encode_bridge_text(&packet, &book).unwrap();
    let decoded = decode_bridge_text(&text, &book).unwrap();

    assert_eq!(decoded, packet);
}

#[test]
fn bridge_payload_json_honors_decode_collection_limit() {
    let book = BridgeBook::standard();
    let packet = brief_packet();
    let text = encode_bridge_text(&packet, &book).unwrap();
    let limits = DecodeLimits {
        max_collection_len: 0,
        ..DecodeLimits::default()
    };

    let line_err = decode_bridge_text_with_limits(&text, &book, CodecId(0), limits).unwrap_err();
    assert!(
        line_err.to_string().contains("collection length"),
        "expected collection-length budget error, got {line_err:?}"
    );

    let mut cx = cx();
    let codec_err = decode_with_codec_and_limits(
        &mut cx,
        &Symbol::qualified("codec", "bridge"),
        Input::Text(text),
        ReadPolicy::default(),
        limits,
    )
    .unwrap_err();
    assert!(
        codec_err.to_string().contains("collection length"),
        "expected collection-length budget error, got {codec_err:?}"
    );
}

#[test]
fn move_legality_rejects_missing_required_return() {
    let book = BridgeBook::standard();
    let mut packet = brief_packet();
    packet.body.pop();
    let err = encode_bridge_text(&packet, &book).unwrap_err();

    assert!(err.to_string().contains("requires"));
}

#[test]
fn move_legality_rejects_illegal_reply_for_opening_move() {
    let book = BridgeBook::standard();
    let mut packet = brief_packet();
    packet.header.parents = vec!["core/sha256-bridge-v1:parent#move=reply".to_owned()];
    let err = encode_bridge_text(&packet, &book).unwrap_err();

    assert!(err.to_string().contains("cannot reply"));
}

#[test]
fn parent_move_evidence_is_required_for_reply_packets() {
    let book = BridgeBook::standard();
    let mut packet = brief_packet();
    packet.header.parents = vec!["core/sha256-bridge-v1:parent".to_owned()];
    let err = encode_bridge_text(&packet, &book).unwrap_err();

    assert!(err.to_string().contains("missing #move=<intent> evidence"));
}

#[test]
fn unprofiled_body_fails_line_and_codec_encode() {
    let book = BridgeBook::standard();
    let mut packet = brief_packet();
    packet.body.push(BridgePart {
        id: Symbol::new("Rc1"),
        kind: Symbol::qualified("bridge", "Receipt"),
        payload: BridgeReceiptPayload::new(Symbol::new("accepted"), vec!["O1".to_owned()])
            .to_expr(),
    });
    let err = encode_bridge_text(&packet, &book).unwrap_err();

    assert!(err.to_string().contains("matches no standard profile"));

    let mut cx = cx();
    let expr = packet_to_expr(&packet);
    let codec_err = encode_with_codec(
        &mut cx,
        &Symbol::qualified("codec", "bridge"),
        &expr,
        EncodeOptions::default(),
    )
    .unwrap_err();

    assert!(
        codec_err
            .to_string()
            .contains("matches no standard profile")
    );
}

#[test]
fn verify_policy_rejects_missing_warrant() {
    let book = BridgeBook::standard().with_warrant_policy(BridgeWarrantPolicy::Verify);
    let err = book.validate_packet(&brief_packet()).unwrap_err();

    assert!(err.to_string().contains("warrant is required"));
}

#[test]
fn verify_policy_rejects_bad_warrant() {
    let book = BridgeBook::standard().with_warrant_policy(BridgeWarrantPolicy::Verify);
    let mut packet = brief_packet();
    let mut warrant = warrant_for_packet(&book, &packet).unwrap();
    warrant.moves = frame_book_content_id(&book.frames).unwrap();
    packet.warrant = Some(warrant);
    let err = book.validate_packet(&packet).unwrap_err();

    assert!(err.to_string().contains("warrant does not match"));
}

#[test]
fn verify_policy_accepts_matching_warrant() {
    let book = BridgeBook::standard().with_warrant_policy(BridgeWarrantPolicy::Verify);
    let mut packet = brief_packet();
    packet.warrant = Some(warrant_for_packet(&book, &packet).unwrap());
    let text = encode_bridge_text(&packet, &book).unwrap();
    let decoded = decode_bridge_text(&text, &book).unwrap();

    assert_eq!(decoded, packet);
}