terminals-core 0.1.0

Core runtime primitives for Terminals OS: phase dynamics, AXON wire protocol, substrate engine, and sematonic types
Documentation
//! Fold/Unfold — HVM γ/δ for Sematon serialization.
//!
//! Fold compresses a Sematon into a portable binary representation
//! suitable for AXON wire relay, mesh handoff, or persistence.
//! Unfold reverses the process.

use serde::{Deserialize, Serialize};
use super::sematon::Sematon;

/// Folded (serializable) representation of a Sematon.
/// Payload is JSON-stringified for portability across surfaces.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FoldedSematon {
    pub id: String,
    pub payload_json: String,
    pub witness_r: f32,
    pub witness_entropy: f32,
    pub witness_converged: bool,
    pub witness_step: u32,
    pub address_base: u32,
    pub address_coeff0: u16,
    pub address_coeff1: u16,
    pub entropy: f32,
    pub density: f32,
    pub impedance: f32,
    pub shape_hash: u32,
    pub constructive: bool,
    pub source: String,
}

/// Fold a Sematon into a portable representation (HVM γ — construct).
pub fn fold_sematon<T: Clone + Serialize>(sematon: &Sematon<T>) -> FoldedSematon {
    FoldedSematon {
        id: sematon.id.clone(),
        payload_json: serde_json::to_string(&sematon.payload).unwrap_or_default(),
        witness_r: sematon.witness.r,
        witness_entropy: sematon.witness.entropy,
        witness_converged: sematon.witness.converged,
        witness_step: sematon.witness.step,
        address_base: sematon.address.base,
        address_coeff0: sematon.address.coeff0,
        address_coeff1: sematon.address.coeff1,
        entropy: sematon.entropy,
        density: sematon.density,
        impedance: sematon.impedance,
        shape_hash: sematon.shape_hash,
        constructive: sematon.constructive,
        source: sematon.source.clone(),
    }
}

/// Unfold a portable representation back into a Sematon (HVM δ — duplicate).
pub fn unfold_sematon<T: Clone + for<'de> Deserialize<'de>>(
    folded: &FoldedSematon,
) -> Result<Sematon<T>, String> {
    let payload: T = serde_json::from_str(&folded.payload_json)
        .map_err(|e| format!("Failed to deserialize payload: {}", e))?;

    Ok(Sematon {
        id: folded.id.clone(),
        payload,
        witness: super::witness::ConvergenceWitness {
            r: folded.witness_r,
            entropy: folded.witness_entropy,
            converged: folded.witness_converged,
            step: folded.witness_step,
        },
        address: super::graph::PadicAddr {
            base: folded.address_base,
            coeff0: folded.address_coeff0,
            coeff1: folded.address_coeff1,
        },
        entropy: folded.entropy,
        density: folded.density,
        impedance: folded.impedance,
        shape_hash: folded.shape_hash,
        constructive: folded.constructive,
        source: folded.source.clone(),
    })
}

/// Fold a sematon to a JSON string (for AXON wire or mesh relay).
pub fn fold_to_json<T: Clone + Serialize>(sematon: &Sematon<T>) -> String {
    let folded = fold_sematon(sematon);
    serde_json::to_string(&folded).unwrap_or_default()
}

/// Unfold a sematon from a JSON string.
pub fn unfold_from_json<T: Clone + for<'de> Deserialize<'de>>(
    json: &str,
) -> Result<Sematon<T>, String> {
    let folded: FoldedSematon =
        serde_json::from_str(json).map_err(|e| format!("Failed to parse FoldedSematon: {}", e))?;
    unfold_sematon(&folded)
}

#[cfg(test)]
mod tests {
    use super::*;
    use super::super::witness::ConvergenceWitness;
    use super::super::graph::PadicAddr;

    #[test]
    fn test_fold_unfold_roundtrip() {
        let w = ConvergenceWitness {
            r: 0.95,
            entropy: 0.3,
            converged: true,
            step: 42,
        };
        let original = Sematon::new(
            vec![1.0f32, 2.0, 3.0],
            w,
            PadicAddr { base: 3, coeff0: 1, coeff1: 7 },
            "test-surface",
        );

        let folded = fold_sematon(&original);
        let restored: Sematon<Vec<f32>> = unfold_sematon(&folded).unwrap();

        assert_eq!(restored.id, original.id);
        assert_eq!(restored.payload, original.payload);
        assert!((restored.witness.r - original.witness.r).abs() < 1e-6);
        assert_eq!(restored.witness.converged, original.witness.converged);
        assert_eq!(restored.address.base, original.address.base);
        assert_eq!(restored.shape_hash, original.shape_hash);
        assert_eq!(restored.constructive, original.constructive);
    }

    #[test]
    fn test_fold_to_json_roundtrip() {
        let w = ConvergenceWitness { r: 0.8, entropy: 0.5, converged: false, step: 10 };
        let original = Sematon::new("hello world".to_string(), w, PadicAddr::default(), "test");

        let json = fold_to_json(&original);
        let restored: Sematon<String> = unfold_from_json(&json).unwrap();

        assert_eq!(restored.payload, "hello world");
        assert_eq!(restored.id, original.id);
    }

    #[test]
    fn test_unfold_bad_json_errors() {
        let result: Result<Sematon<u32>, _> = unfold_from_json("not valid json");
        assert!(result.is_err());
    }
}