chappi-core 0.1.0

Typed cognitive graph, patch, reentry, projection, and measurement engine for Chappi
Documentation
use chappi_core::{
    BudgetUnit, CapsuleBudget, CognitiveState, ProjectionFormat, build_capsule, project,
    snapshot_source, state_hash, validate,
};
use chappi_syntax::parse_document;

fn decode(source: &str) -> CognitiveState {
    CognitiveState::try_from(parse_document(source).expect("fixture syntax should parse"))
        .expect("fixture semantics should decode")
}

#[test]
fn state_hash_is_stable_across_source_formatting_and_semantic_order() {
    let ordered = decode(
        r#"(chappi 1)
(entity self self persistent 100 person (name "Self"))
(claim c self working 70 likely (text "same"))
(rel about c self)
(focus thought 0 self evoked none (fg c) (bg self) (residue) (trigger start))
"#,
    );
    let reordered = decode(
        r#"; different spans and top-level order
(focus thought 0 self evoked none (fg c) (bg self) (residue) (trigger start))
(rel about c self)
(claim c self working 070 likely (text "same"))
(chappi 1)
(entity self self persistent 100 person (name "Self"))
"#,
    );

    assert_eq!(state_hash(&ordered), state_hash(&reordered));
}

#[test]
fn state_hash_includes_node_revisions_and_focus_streams() {
    let state = decode(
        r#"(chappi 1)
(entity self self persistent 100 person (self))
(claim c self working 70 likely (claim))
"#,
    );
    let mut revised = state.clone();
    revised.nodes[1].revision = 1;
    let with_focus = decode(
        r#"(chappi 1)
(entity self self persistent 100 person (self))
(claim c self working 70 likely (claim))
(focus thought 0 self evoked none (fg c) (bg) (residue) (trigger start))
"#,
    );

    assert_ne!(state_hash(&state), state_hash(&revised));
    assert_ne!(state_hash(&state), state_hash(&with_focus));
}

#[test]
fn state_hash_normalizes_canonically_equivalent_unicode_symbols() {
    let composed = decode("(chappi 1)\n(entity café café persistent 100 person (self))\n");
    let decomposed =
        decode("(chappi 1)\n(entity cafe\u{301} cafe\u{301} persistent 100 person (self))\n");

    assert_eq!(state_hash(&composed), state_hash(&decomposed));
}

#[test]
fn focus_buckets_are_semantic_sets_in_equality_and_canonical_source() {
    let left = decode(
        r#"(chappi 1)
(entity self self persistent 100 person (self))
(claim a self working 70 likely (a))
(claim b self working 60 likely (b))
(focus thought 0 self evoked none (fg b a) (bg self) (residue) (trigger start))
"#,
    );
    let right = decode(
        r#"(chappi 1)
(entity self self persistent 100 person (self))
(claim a self working 70 likely (a))
(claim b self working 60 likely (b))
(focus thought 0 self evoked none (fg a b) (bg self) (residue) (trigger start))
"#,
    );

    assert!(validate(&left).is_empty());
    assert!(validate(&right).is_empty());
    assert_eq!(state_hash(&left), state_hash(&right));
    assert_eq!(left, right);
    assert_eq!(snapshot_source(&left), snapshot_source(&right));

    let mut programmatic = right.clone();
    programmatic.focus_streams[0].frames[0].foreground.reverse();
    assert_eq!(programmatic, right);
    assert_eq!(snapshot_source(&programmatic), snapshot_source(&right));
    assert_eq!(
        project(&programmatic, ProjectionFormat::Json),
        project(&right, ProjectionFormat::Json)
    );
    let budget = CapsuleBudget {
        limit: 10_000,
        unit: BudgetUnit::Bytes,
    };
    assert_eq!(
        build_capsule(&programmatic, budget).canonical_content(),
        build_capsule(&right, budget).canonical_content()
    );
    assert!(
        std::str::from_utf8(&snapshot_source(&left))
            .unwrap()
            .contains("(fg a b)")
    );
}