sim-lib-view-bridge 0.1.1

Reversible BRIDGE packet review surface for SIM Web.
Documentation
use sim_codec_bridge::{
    BridgeFramePayload, BridgeHeader, BridgePacket, BridgePart, BridgePatchPayload,
    BridgeProvenance, BridgeReceiptPayload, BridgeReviewPayload, BridgeScore, BridgeVotePayload,
    packet_to_expr, stamp_packet_cid,
};
use sim_kernel::{Expr, Symbol, testing::eager_cx as cx};
use sim_lib_intent::Origin;
use sim_lib_view::SurfaceCodec;
use sim_value::access::field;
use sim_value::build::{entry, int, list, map, text};

use crate::{
    BridgePacketSurfaceCodec, bridge_packet_edit, bridge_packet_view, patch_edit_intent,
    receipt_edit_intent, review_edit_intent, vote_edit_intent,
};

fn packet() -> BridgePacket {
    stamp_packet_cid(&BridgePacket {
        header: BridgeHeader {
            cid: None,
            move_kind: Symbol::new("reply"),
            from: "model:drafter".to_owned(),
            to: vec!["human:reviewer".to_owned(), "model:judge".to_owned()],
            role: Symbol::new("implementer"),
            parents: vec!["core/sha256-bridge-v1:root".to_owned()],
            task: Symbol::new("T1"),
            output: Symbol::new("O1"),
            ceiling: Vec::new(),
            context: Vec::new(),
            provenance: BridgeProvenance::default(),
        },
        body: vec![
            BridgePart {
                id: Symbol::new("T1"),
                kind: Symbol::qualified("bridge", "Frame"),
                payload: BridgeFramePayload::new(Symbol::qualified("bridge", "answer")).to_expr(),
            },
            BridgePart {
                id: Symbol::new("O1"),
                kind: Symbol::qualified("bridge", "Return"),
                payload: Expr::Map(vec![
                    entry("codec", Expr::Symbol(Symbol::qualified("codec", "bridge"))),
                    entry("shape", Expr::Symbol(Symbol::qualified("core", "Map"))),
                ]),
            },
        ],
        warrant: None,
    })
    .unwrap()
}

fn part_payload(part: &Expr) -> &Expr {
    field(part, "payload").unwrap()
}

fn part_kind(part: &Expr) -> Symbol {
    match field(part, "kind").unwrap() {
        Expr::Symbol(symbol) => symbol.clone(),
        other => panic!("unexpected part kind {other:?}"),
    }
}

fn edit_intent(target: Expr, path: Expr, value: Expr) -> Expr {
    sim_lib_intent::intent(
        "edit-field",
        Origin::human(9),
        vec![("target", target), ("path", path), ("value", value)],
    )
}

fn collab_path(action: &str) -> Expr {
    list(vec![text("bridge-collab"), text(action)])
}

fn walk(expr: &Expr, visit: &mut impl FnMut(&Expr)) {
    visit(expr);
    match expr {
        Expr::Map(entries) => {
            for (_, value) in entries {
                walk(value, visit);
            }
        }
        Expr::List(items) | Expr::Vector(items) | Expr::Set(items) => {
            for item in items {
                walk(item, visit);
            }
        }
        _ => {}
    }
}

fn kind_count(expr: &Expr, name: &str) -> usize {
    let mut count = 0;
    walk(expr, &mut |item| {
        if matches!(
            field(item, "kind"),
            Some(Expr::Symbol(symbol))
                if symbol.namespace.as_deref() == Some("scene") && symbol.name.as_ref() == name
        ) {
            count += 1;
        }
    });
    count
}

fn role_count(expr: &Expr, role: &str) -> usize {
    let mut count = 0;
    walk(expr, &mut |item| {
        if matches!(
            field(item, "role"),
            Some(Expr::Symbol(symbol)) if symbol.name.as_ref() == role
        ) {
            count += 1;
        }
    });
    count
}

#[test]
fn bridge_packet_surface_codec_encodes_a_valid_scene() {
    let mut cx = cx();
    let codec = BridgePacketSurfaceCodec::new();
    let value = packet_to_expr(&packet());
    let caps = sim_lib_view::surface::SurfaceCaps::from_preset("desktop", "test").unwrap();
    let scene = codec.encode(&mut cx, &value, &caps).unwrap();

    sim_lib_scene::validate_scene(&scene).unwrap();
}

#[test]
fn bridge_packet_view_renders_valid_scene() {
    let mut cx = cx();
    let caps = sim_lib_view::surface::SurfaceCaps::from_preset("desktop", "test").unwrap();
    let scene = bridge_packet_view(&mut cx, &packet(), &caps).unwrap();

    sim_lib_scene::validate_scene(&scene).unwrap();
}

#[test]
fn bridge_packet_view_renders_collaboration_forms() {
    let mut cx = cx();
    let caps = sim_lib_view::surface::SurfaceCaps::from_preset("desktop", "test").unwrap();
    let scene = bridge_packet_view(&mut cx, &packet(), &caps).unwrap();

    assert_eq!(role_count(&scene, "edit-form"), 4);
    assert!(kind_count(&scene, "field") >= 10);
    assert_eq!(kind_count(&scene, "button"), 4);
}

#[test]
fn human_patch_and_model_patch_decode_identically() {
    let mut cx = cx();
    let packet = packet();
    let human = bridge_packet_edit(
        &mut cx,
        &packet,
        &patch_edit_intent(
            "body/O1/payload",
            Expr::String("accepted answer".to_owned()),
            Origin::human(1),
        ),
    )
    .unwrap();
    let model = bridge_packet_edit(
        &mut cx,
        &packet,
        &patch_edit_intent(
            "body/O1/payload",
            Expr::String("accepted answer".to_owned()),
            Origin::agent(1),
        ),
    )
    .unwrap();
    let human_patch = BridgePatchPayload::from_expr(part_payload(&human)).unwrap();
    let model_patch = BridgePatchPayload::from_expr(part_payload(&model)).unwrap();

    assert_eq!(part_kind(&human), Symbol::qualified("bridge", "Patch"));
    assert_eq!(human_patch, model_patch);
}

#[test]
fn edits_decode_to_typed_review_vote_and_receipt_parts() {
    let mut cx = cx();
    let packet = packet();
    let review = bridge_packet_edit(
        &mut cx,
        &packet,
        &review_edit_intent("body/O1/payload", "looks correct", Origin::human(2)),
    )
    .unwrap();
    let vote = bridge_packet_edit(
        &mut cx,
        &packet,
        &vote_edit_intent(
            "body/O1/payload",
            vec![BridgeScore::new(
                Symbol::new("correctness"),
                1,
                "keeps the packet valid",
            )],
            Origin::agent(3),
        ),
    )
    .unwrap();
    let receipt = bridge_packet_edit(
        &mut cx,
        &packet,
        &receipt_edit_intent(
            Symbol::new("accepted"),
            vec!["body/O1/payload".to_owned()],
            Origin::agent(4),
        ),
    )
    .unwrap();

    assert_eq!(part_kind(&review), Symbol::qualified("bridge", "Review"));
    assert_eq!(part_kind(&vote), Symbol::qualified("bridge", "Vote"));
    assert_eq!(part_kind(&receipt), Symbol::qualified("bridge", "Receipt"));
    assert_eq!(
        BridgeReviewPayload::from_expr(part_payload(&review))
            .unwrap()
            .target,
        "body/O1/payload"
    );
    assert_eq!(
        BridgeVotePayload::from_expr(part_payload(&vote))
            .unwrap()
            .scores[0]
            .axis,
        Symbol::new("correctness")
    );
    assert_eq!(
        BridgeReceiptPayload::from_expr(part_payload(&receipt))
            .unwrap()
            .status,
        Symbol::new("accepted")
    );
}

#[test]
fn direct_codec_decode_uses_same_packet_value() {
    let mut cx = cx();
    let codec = BridgePacketSurfaceCodec::new();
    let packet = packet();
    let value = packet_to_expr(&packet);
    let draft = codec
        .decode(
            &mut cx,
            &value,
            &patch_edit_intent(
                "body/O1/payload",
                Expr::String("accepted answer".to_owned()),
                Origin::human(5),
            ),
        )
        .unwrap();
    let op = codec.commit(&mut cx, &draft).unwrap();

    assert_eq!(draft.base, value);
    assert!(matches!(op.form, Expr::Map(_)));
}

#[test]
fn bridge_packet_decode_rejects_non_edit_or_unscoped_intents() {
    let mut cx = cx();
    let codec = BridgePacketSurfaceCodec::new();
    let packet = packet();
    let value = packet_to_expr(&packet);
    let tap = sim_lib_intent::intent(
        "tap",
        Origin::human(9),
        vec![
            ("target", Expr::Symbol(Symbol::new("bridge-packet"))),
            ("control", Expr::Symbol(Symbol::new("submit"))),
        ],
    );
    let empty_path = edit_intent(
        Expr::Symbol(Symbol::new("bridge-packet")),
        list(vec![]),
        Expr::String("free value".to_owned()),
    );
    let wrong_path = edit_intent(
        Expr::Symbol(Symbol::new("bridge-packet")),
        list(vec![text("body"), text("O1")]),
        map(vec![("target", text("body/O1/payload"))]),
    );
    let wrong_target = edit_intent(
        Expr::Symbol(Symbol::new("not-the-packet")),
        collab_path("review"),
        map(vec![
            ("target", text("body/O1/payload")),
            ("body", text("looks correct")),
        ]),
    );

    assert!(codec.decode(&mut cx, &value, &tap).is_err());
    assert!(codec.decode(&mut cx, &value, &empty_path).is_err());
    assert!(codec.decode(&mut cx, &value, &wrong_path).is_err());
    assert!(codec.decode(&mut cx, &value, &wrong_target).is_err());
}

#[test]
fn bridge_packet_decode_accepts_explicit_packet_cid_target() {
    let mut cx = cx();
    let packet = packet();
    let target = Expr::String(packet.header.cid.clone().unwrap());
    let part = bridge_packet_edit(
        &mut cx,
        &packet,
        &edit_intent(
            target,
            collab_path("review"),
            map(vec![
                ("target", text("body/O1/payload")),
                ("body", text("looks correct")),
            ]),
        ),
    )
    .unwrap();

    assert_eq!(part_kind(&part), Symbol::qualified("bridge", "Review"));
}

#[test]
fn browser_shaped_vote_and_receipt_values_become_valid_parts() {
    let mut cx = cx();
    let packet = packet();
    let vote = bridge_packet_edit(
        &mut cx,
        &packet,
        &edit_intent(
            Expr::String(packet.header.cid.clone().unwrap()),
            collab_path("vote"),
            map(vec![
                ("target", text("body/O1/payload")),
                (
                    "scores",
                    Expr::Vector(vec![map(vec![
                        ("axis", text("correctness")),
                        ("value", int(1)),
                        ("reason", text("checked packet shape")),
                    ])]),
                ),
            ]),
        ),
    )
    .unwrap();
    let receipt = bridge_packet_edit(
        &mut cx,
        &packet,
        &edit_intent(
            Expr::String(packet.header.cid.clone().unwrap()),
            collab_path("receipt"),
            map(vec![
                ("status", text("accepted")),
                ("refs", list(vec![text("body/O1/payload")])),
            ]),
        ),
    )
    .unwrap();

    assert_eq!(part_kind(&vote), Symbol::qualified("bridge", "Vote"));
    assert_eq!(part_kind(&receipt), Symbol::qualified("bridge", "Receipt"));
    BridgeVotePayload::from_expr(part_payload(&vote)).unwrap();
    BridgeReceiptPayload::from_expr(part_payload(&receipt)).unwrap();
}

#[test]
fn bridge_packet_decode_validates_generated_collaboration_payloads() {
    let mut cx = cx();
    let packet = packet();
    let invalid_vote = bridge_packet_edit(
        &mut cx,
        &packet,
        &edit_intent(
            Expr::Symbol(Symbol::new("bridge-packet")),
            collab_path("vote"),
            map(vec![
                ("target", text("body/O1/payload")),
                ("scores", Expr::Vector(vec![])),
            ]),
        ),
    );
    let invalid_receipt = bridge_packet_edit(
        &mut cx,
        &packet,
        &edit_intent(
            Expr::Symbol(Symbol::new("bridge-packet")),
            collab_path("receipt"),
            map(vec![
                ("status", text("")),
                ("refs", list(vec![text("body/O1/payload")])),
            ]),
        ),
    );

    assert!(invalid_vote.is_err());
    assert!(invalid_receipt.is_err());
}