use chrono::{DateTime, SubsecRound, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const Q32_32_SCALE: f64 = 4_294_967_296.0;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct FixedPoint(pub i64);
impl FixedPoint {
pub fn from_f64(v: f64) -> Self {
let scaled = v * Q32_32_SCALE;
let clamped = scaled.clamp(i64::MIN as f64, i64::MAX as f64);
FixedPoint(clamped as i64)
}
pub fn to_f64(self) -> f64 {
(self.0 as f64) / Q32_32_SCALE
}
pub fn vec_from_f64(v: &[f64]) -> Vec<FixedPoint> {
v.iter().copied().map(FixedPoint::from_f64).collect()
}
pub fn vec_to_f64(v: &[FixedPoint]) -> Vec<f64> {
v.iter().copied().map(FixedPoint::to_f64).collect()
}
}
impl From<f64> for FixedPoint {
fn from(v: f64) -> Self {
FixedPoint::from_f64(v)
}
}
impl From<FixedPoint> for f64 {
fn from(q: FixedPoint) -> Self {
q.to_f64()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemoryEntry {
pub key: String,
pub payload: Value,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub symbolic_refs: Vec<String>,
#[serde(with = "chrono::serde::ts_milliseconds")]
pub stored_at: DateTime<Utc>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CognitiveState {
pub session_id: String,
pub tenant_id: String,
pub flow_id: String,
#[serde(default)]
pub subject_user_id: Option<String>,
pub density_matrix: Vec<Vec<FixedPoint>>,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub belief_state: Value,
#[serde(default)]
pub short_term_memory: Vec<MemoryEntry>,
#[serde(with = "chrono::serde::ts_milliseconds")]
pub created_at: DateTime<Utc>,
#[serde(with = "chrono::serde::ts_milliseconds")]
pub last_updated_at: DateTime<Utc>,
}
impl CognitiveState {
pub fn new(
session_id: impl Into<String>,
tenant_id: impl Into<String>,
flow_id: impl Into<String>,
) -> Self {
let now = Utc::now().trunc_subsecs(3);
CognitiveState {
session_id: session_id.into(),
tenant_id: tenant_id.into(),
flow_id: flow_id.into(),
subject_user_id: None,
density_matrix: Vec::new(),
belief_state: Value::Null,
short_term_memory: Vec::new(),
created_at: now,
last_updated_at: now,
}
}
pub fn encode(&self) -> Vec<u8> {
serde_json::to_vec(self).expect("CognitiveState is always serialisable")
}
pub fn decode(bytes: &[u8]) -> Result<Self, StateDecodeError> {
serde_json::from_slice(bytes)
.map_err(|e| StateDecodeError(e.to_string()))
}
}
#[derive(Debug)]
pub struct StateDecodeError(pub String);
impl std::fmt::Display for StateDecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "cognitive state decode failed: {}", self.0)
}
}
impl std::error::Error for StateDecodeError {}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
use serde_json::json;
fn fixed_ts() -> DateTime<Utc> {
Utc.with_ymd_and_hms(2026, 4, 22, 12, 0, 0).unwrap()
}
#[test]
fn fixed_point_roundtrip_preserves_bit_pattern_across_n_hops() {
let originals = [0.0_f64, 1e-5, 0.25, 0.5, 0.9999, 1.0];
for v in originals {
let q = FixedPoint::from_f64(v);
let back = q.to_f64();
let q2 = FixedPoint::from_f64(back);
let back2 = q2.to_f64();
assert_eq!(q, q2, "value {v} drifts between cycles");
assert_eq!(back, back2);
}
}
#[test]
fn fixed_point_saturates_on_infinity() {
let q_pos = FixedPoint::from_f64(f64::INFINITY);
let q_neg = FixedPoint::from_f64(f64::NEG_INFINITY);
assert_eq!(q_pos.0, i64::MAX);
assert_eq!(q_neg.0, i64::MIN);
}
#[test]
fn fixed_point_vec_helpers() {
let v = vec![0.1, 0.25, 0.5];
let q = FixedPoint::vec_from_f64(&v);
let back = FixedPoint::vec_to_f64(&q);
let q2 = FixedPoint::vec_from_f64(&back);
assert_eq!(q, q2);
}
#[test]
fn fixed_point_representable_precision_is_about_2e_minus_10() {
let a = 0.5_f64;
let b = a + 1e-11;
assert_eq!(FixedPoint::from_f64(a), FixedPoint::from_f64(b));
}
#[test]
fn encode_decode_roundtrip() {
let mut s = CognitiveState::new("sess-1", "alpha", "flow-1");
s.created_at = fixed_ts();
s.last_updated_at = fixed_ts();
s.density_matrix = vec![FixedPoint::vec_from_f64(&[0.1, 0.9])];
s.belief_state = json!({"confidence": 0.73});
s.short_term_memory.push(MemoryEntry {
key: "last_user_msg".into(),
payload: json!({"text": "hi"}),
symbolic_refs: vec!["audio-buf-17".into()],
stored_at: fixed_ts(),
});
let bytes = s.encode();
let decoded = CognitiveState::decode(&bytes).expect("decode");
assert_eq!(decoded, s);
}
#[test]
fn density_matrix_roundtrips_bit_identical_across_multiple_cycles() {
let mut s = CognitiveState::new("sess", "alpha", "f");
let original = vec![vec![0.1, 0.5, 0.9], vec![0.2, 0.3, 0.8]];
s.density_matrix = original
.iter()
.map(|row| FixedPoint::vec_from_f64(row))
.collect();
let mut current = s.clone();
for _ in 0..3 {
let bytes = current.encode();
current = CognitiveState::decode(&bytes).expect("decode");
}
assert_eq!(current.density_matrix, s.density_matrix);
}
#[test]
fn decode_rejects_garbage() {
let err = CognitiveState::decode(b"not json").unwrap_err();
assert!(err.0.contains("decode failed") || !err.0.is_empty());
}
#[test]
fn optional_fields_default_cleanly() {
let minimal = r#"{
"session_id": "sess",
"tenant_id": "alpha",
"flow_id": "f",
"density_matrix": [],
"created_at": 1700000000000,
"last_updated_at": 1700000000000
}"#;
let decoded = CognitiveState::decode(minimal.as_bytes()).unwrap();
assert_eq!(decoded.subject_user_id, None);
assert_eq!(decoded.belief_state, Value::Null);
assert!(decoded.short_term_memory.is_empty());
}
}