chappi-core 0.1.0

Typed cognitive graph, patch, reentry, projection, and measurement engine for Chappi
Documentation
use sha2::{Digest, Sha256};
use unicode_normalization::UnicodeNormalization;

use chappi_syntax::{Document, Form, canonicalize};

use crate::{
    AffectIntensity, AttentionMode, CognitiveState, Confidence, ConstraintStrength, DesireForce,
    FocusFrame, GoalStatus, Lifetime, MetacognitionStatus, Node, NodeKind, PredictionStatus,
    QuestionStatus, Source, TensionStatus,
};

pub fn state_hash(state: &CognitiveState) -> [u8; 32] {
    Sha256::digest(semantic_bytes(state)).into()
}

fn semantic_bytes(state: &CognitiveState) -> Vec<u8> {
    let mut output = Vec::new();
    output.extend_from_slice(b"chappi-state\0");
    output.extend_from_slice(&state.version.to_be_bytes());

    let mut nodes = state.nodes.iter().map(node_bytes).collect::<Vec<_>>();
    nodes.sort();
    write_records(&mut output, &nodes);

    let mut relations = state
        .relations
        .iter()
        .map(|relation| {
            let mut bytes = Vec::new();
            write_str(&mut bytes, relation.kind.as_str());
            write_str(&mut bytes, &relation.source);
            write_str(&mut bytes, &relation.target);
            bytes
        })
        .collect::<Vec<_>>();
    relations.sort();
    write_records(&mut output, &relations);

    let mut streams = state
        .focus_streams
        .iter()
        .map(|stream| {
            let mut bytes = Vec::new();
            write_str(&mut bytes, &stream.id);
            let mut frames = stream.frames.iter().map(focus_bytes).collect::<Vec<_>>();
            frames.sort();
            write_records(&mut bytes, &frames);
            bytes
        })
        .collect::<Vec<_>>();
    streams.sort();
    write_records(&mut output, &streams);
    output
}

fn node_bytes(node: &Node) -> Vec<u8> {
    let mut bytes = Vec::new();
    write_str(&mut bytes, &node.id);
    bytes.extend_from_slice(&node.revision.to_be_bytes());
    write_str(&mut bytes, &node.owner);
    write_str(&mut bytes, lifetime(node.lifetime));
    bytes.push(node.salience);
    write_node_kind(&mut bytes, &node.kind);
    write_form(&mut bytes, &node.payload);
    bytes
}

fn write_node_kind(output: &mut Vec<u8>, kind: &NodeKind) {
    match kind {
        NodeKind::Entity { entity_kind } => {
            write_str(output, "entity");
            write_str(output, entity_kind);
        }
        NodeKind::Claim { confidence } => {
            write_str(output, "claim");
            write_str(output, confidence_text(*confidence));
        }
        NodeKind::Evidence {
            evidence_kind,
            source,
        } => {
            write_str(output, "evidence");
            write_str(output, evidence_kind);
            match source {
                Source::Symbol(value) => {
                    write_str(output, "symbol");
                    write_str(output, value);
                }
                Source::String(value) => {
                    write_str(output, "string");
                    write_str(output, value);
                }
            }
        }
        NodeKind::Goal { status } => write_pair(output, "goal", goal_status(*status)),
        NodeKind::Constraint { strength } => {
            write_pair(output, "constraint", constraint_strength(*strength));
        }
        NodeKind::Question { status } => write_pair(output, "question", question_status(*status)),
        NodeKind::Relationship { target, status } => {
            write_str(output, "relationship");
            write_str(output, target);
            write_str(output, status);
        }
        NodeKind::Desire { force } => write_pair(output, "desire", desire_force(*force)),
        NodeKind::Attention { mode } => write_pair(output, "attention", attention_mode(*mode)),
        NodeKind::Association => write_str(output, "association"),
        NodeKind::Tension { status } => write_pair(output, "tension", tension_status(*status)),
        NodeKind::Prediction { confidence, status } => {
            write_str(output, "prediction");
            write_str(output, confidence_text(*confidence));
            write_str(output, prediction_status(*status));
        }
        NodeKind::Perspective(value) => {
            write_str(output, "perspective");
            write_str(output, &value.target);
            write_str(output, confidence_text(value.confidence));
        }
        NodeKind::Affect {
            target,
            affect_kind,
            intensity,
        } => {
            write_str(output, "affect");
            write_str(output, target);
            write_str(output, affect_kind);
            write_str(output, affect_intensity(*intensity));
        }
        NodeKind::Metacognition { status } => {
            write_pair(output, "metacognition", metacognition_status(*status));
        }
    }
}

fn focus_bytes(frame: &FocusFrame) -> Vec<u8> {
    let mut bytes = Vec::new();
    write_str(&mut bytes, &frame.stream_id);
    bytes.extend_from_slice(&frame.sequence.to_be_bytes());
    write_str(&mut bytes, &frame.owner);
    write_str(&mut bytes, frame.transition.as_str());
    match frame.previous_sequence {
        None => bytes.push(0),
        Some(sequence) => {
            bytes.push(1);
            bytes.extend_from_slice(&sequence.to_be_bytes());
        }
    }
    write_sorted_strings(&mut bytes, &frame.foreground);
    write_sorted_strings(&mut bytes, &frame.background);
    write_sorted_strings(&mut bytes, &frame.residue);
    write_form(&mut bytes, &frame.trigger);
    bytes
}

fn write_sorted_strings(output: &mut Vec<u8>, values: &[String]) {
    let mut values = values
        .iter()
        .map(|value| value.nfc().collect::<String>())
        .collect::<Vec<_>>();
    values.sort_unstable();
    output.extend_from_slice(&(values.len() as u64).to_be_bytes());
    for value in values {
        write_bytes(output, value.as_bytes());
    }
}

fn write_form(output: &mut Vec<u8>, form: &Form) {
    let bytes = canonicalize(&Document {
        forms: vec![form.clone()],
    });
    write_bytes(output, &bytes);
}

fn write_pair(output: &mut Vec<u8>, left: &str, right: &str) {
    write_str(output, left);
    write_str(output, right);
}

fn write_records(output: &mut Vec<u8>, records: &[Vec<u8>]) {
    output.extend_from_slice(&(records.len() as u64).to_be_bytes());
    for record in records {
        write_bytes(output, record);
    }
}

fn write_str(output: &mut Vec<u8>, value: &str) {
    let normalized = value.nfc().collect::<String>();
    write_bytes(output, normalized.as_bytes());
}

fn write_bytes(output: &mut Vec<u8>, value: &[u8]) {
    output.extend_from_slice(&(value.len() as u64).to_be_bytes());
    output.extend_from_slice(value);
}

fn lifetime(value: Lifetime) -> &'static str {
    match value {
        Lifetime::Persistent => "persistent",
        Lifetime::Working => "working",
        Lifetime::Transient => "transient",
    }
}
fn confidence_text(value: Confidence) -> &'static str {
    match value {
        Confidence::Tentative => "tentative",
        Confidence::Plausible => "plausible",
        Confidence::Likely => "likely",
        Confidence::Settled => "settled",
    }
}
fn goal_status(value: GoalStatus) -> &'static str {
    match value {
        GoalStatus::Considering => "considering",
        GoalStatus::Active => "active",
        GoalStatus::Committed => "committed",
        GoalStatus::Done => "done",
        GoalStatus::Dropped => "dropped",
    }
}
fn constraint_strength(value: ConstraintStrength) -> &'static str {
    match value {
        ConstraintStrength::Soft => "soft",
        ConstraintStrength::Hard => "hard",
    }
}
fn question_status(value: QuestionStatus) -> &'static str {
    match value {
        QuestionStatus::Open => "open",
        QuestionStatus::Answered => "answered",
        QuestionStatus::Deferred => "deferred",
    }
}
fn desire_force(value: DesireForce) -> &'static str {
    match value {
        DesireForce::Weak => "weak",
        DesireForce::Moderate => "moderate",
        DesireForce::Strong => "strong",
    }
}
fn attention_mode(value: AttentionMode) -> &'static str {
    match value {
        AttentionMode::Foreground => "foreground",
        AttentionMode::Background => "background",
    }
}
fn tension_status(value: TensionStatus) -> &'static str {
    match value {
        TensionStatus::Open => "open",
        TensionStatus::Resolved => "resolved",
    }
}
fn prediction_status(value: PredictionStatus) -> &'static str {
    match value {
        PredictionStatus::Pending => "pending",
        PredictionStatus::Matched => "matched",
        PredictionStatus::Surprised => "surprised",
    }
}
fn affect_intensity(value: AffectIntensity) -> &'static str {
    match value {
        AffectIntensity::Weak => "weak",
        AffectIntensity::Moderate => "moderate",
        AffectIntensity::Strong => "strong",
    }
}
fn metacognition_status(value: MetacognitionStatus) -> &'static str {
    match value {
        MetacognitionStatus::Uncertain => "uncertain",
        MetacognitionStatus::Monitoring => "monitoring",
        MetacognitionStatus::Resolved => "resolved",
    }
}