kmp-domain 0.18.9

Domain model of the Kernel Memory Protocol: aggregates, value objects, repositories and projections, with no IO
Documentation
use super::*;
use crate::{
    NodeBodyDescriptor, NodeCardPresentation, NodeCardStamp, NodeProjection,
    NodeRelationProjection, RelationExplanation, RelationSemanticClass, TraceBodyState,
    TraceProofObject,
};

fn object(id: &str, bytes: u64, status: NodeCardStatus) -> TraceProofObject {
    TraceProofObject {
        node: NodeProjection {
            node_id: id.into(),
            node_kind: "entry".into(),
            title: id.into(),
            summary: String::new(),
            status: "ACTIVE".into(),
            labels: vec!["entry".into()],
            properties: BTreeMap::new(),
            provenance: None,
        },
        descriptor: Some(NodeBodyDescriptor {
            node_id: id.into(),
            revision: 7,
            content_hash: "public".into(),
            record_bytes: bytes + 90,
            body_bytes: bytes,
            record_digest: format!("sha256:{id}"),
        }),
        body: None,
        body_state: TraceBodyState::NotRequested,
        coordinates: vec![],
        card: Some(NodeCardPresentation {
            status,
            text: None,
            stored: (status != NodeCardStatus::Absent).then(|| NodeCardStamp {
                source_revision: 6,
                source_content_hash: "old-public".into(),
                source_record_digest: "old-digest".into(),
                source_body_bytes: bytes,
                card_revision: 13,
                authored_by: "reader".into(),
                authored_at: "2026-09-01T00:00:00Z".into(),
                text_bytes: 9,
            }),
        }),
    }
}

fn support(source: &str, entry: &str) -> NodeRelationProjection {
    NodeRelationProjection {
        source_node_id: source.into(),
        target_node_id: entry.into(),
        relation_type: "supports".into(),
        explanation: RelationExplanation::new(RelationSemanticClass::Evidential),
    }
}

#[test]
fn shared_sources_count_once_per_route_and_rank_before_larger_singletons() {
    let proof = TraceProofResult {
        objects: vec![
            object("a", 9000, NodeCardStatus::Absent),
            object("b", 8000, NodeCardStatus::Absent),
            object("shared", 2048, NodeCardStatus::Stale),
        ],
        supports: vec![support("shared", "a"), support("shared", "b")],
        ..Default::default()
    };
    let groups = vec![["a".into(), "b".into()].into(), ["b".into()].into()];
    let result = recommend(&proof, &groups);
    assert_eq!(
        result
            .items
            .iter()
            .map(|c| (c.descriptor.node_id.as_str(), c.shared_by))
            .collect::<Vec<_>>(),
        vec![("b", 2), ("shared", 2), ("a", 1)]
    );
    let shared = &result.items[1];
    assert_eq!(shared.expect, NodeCardExpectation::CardRevision(13));
    assert_eq!(
        shared.descriptor,
        proof.objects[2]
            .descriptor
            .clone()
            .expect("valid candidate fixture")
    );
    assert_eq!(result.items[2].expect, NodeCardExpectation::Absent);
}

#[test]
fn counts_exclusions_once_and_never_offers_a_missing_body_or_card_revision() {
    let mut missing = object("missing", 5000, NodeCardStatus::Absent);
    missing.descriptor = None;
    let mut malformed = object("bad-stale", 5000, NodeCardStatus::Stale);
    malformed
        .card
        .as_mut()
        .expect("valid candidate fixture")
        .stored = None;
    let mut no_compact = object("plain", 5000, NodeCardStatus::Absent);
    no_compact.card = None;
    let proof = TraceProofResult {
        objects: vec![
            object("small", 1023, NodeCardStatus::Absent),
            object("valid", 500, NodeCardStatus::Valid),
            object("future", 500, NodeCardStatus::AfterCut),
            object("boundary", 1024, NodeCardStatus::Absent),
            missing,
            malformed,
            no_compact,
        ],
        ..Default::default()
    };
    let result = recommend(&proof, &[]);
    assert_eq!(
        (result.below_floor, result.valid, result.after_cut),
        (1, 1, 1)
    );
    assert_eq!(result.items.len(), 1);
    assert_eq!(result.items[0].descriptor.node_id, "boundary");
    assert_eq!(result.items[0].shared_by, 0);
}

#[test]
fn equal_costs_use_ref_order_and_cap_reports_the_full_eligible_tail() {
    let mut proof = TraceProofResult {
        objects: (0..12)
            .rev()
            .map(|i| object(&format!("r{i:02}"), 2048, NodeCardStatus::Absent))
            .collect(),
        ..Default::default()
    };
    let result = recommend(&proof, &[]);
    assert_eq!(result.items.len(), 8);
    assert_eq!(result.omitted_count, 4);
    assert_eq!(
        result
            .items
            .first()
            .expect("valid candidate fixture")
            .descriptor
            .node_id,
        "r00"
    );
    assert_eq!(
        result
            .items
            .last()
            .expect("valid candidate fixture")
            .descriptor
            .node_id,
        "r07"
    );
    proof.objects.reverse();
    assert_eq!(recommend(&proof, &[]), result);
}