use std::collections::{HashMap, VecDeque};
use std::fmt;
use std::str::FromStr;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SectionType {
Overview,
CoreModel,
BoundaryConditions,
Formalism,
OperationalGuidance,
Examples,
FailureModes,
ExpertLens,
References,
Other,
}
impl SectionType {
pub fn as_str(self) -> &'static str {
match self {
SectionType::Overview => "overview",
SectionType::CoreModel => "core_model",
SectionType::BoundaryConditions => "boundary_conditions",
SectionType::Formalism => "formalism",
SectionType::OperationalGuidance => "operational_guidance",
SectionType::Examples => "examples",
SectionType::FailureModes => "failure_modes",
SectionType::ExpertLens => "expert_lens",
SectionType::References => "references",
SectionType::Other => "other",
}
}
pub fn all() -> &'static [SectionType] {
&Self::ALL
}
pub const ALL: [SectionType; 10] = [
SectionType::Overview,
SectionType::CoreModel,
SectionType::BoundaryConditions,
SectionType::Formalism,
SectionType::OperationalGuidance,
SectionType::Examples,
SectionType::FailureModes,
SectionType::ExpertLens,
SectionType::References,
SectionType::Other,
];
}
impl fmt::Display for SectionType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for SectionType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"overview" => Ok(SectionType::Overview),
"core_model" => Ok(SectionType::CoreModel),
"boundary_conditions" => Ok(SectionType::BoundaryConditions),
"formalism" => Ok(SectionType::Formalism),
"operational_guidance" => Ok(SectionType::OperationalGuidance),
"examples" => Ok(SectionType::Examples),
"failure_modes" => Ok(SectionType::FailureModes),
"expert_lens" => Ok(SectionType::ExpertLens),
"references" => Ok(SectionType::References),
"other" => Ok(SectionType::Other),
_ => Err(format!("unknown SectionType: {s:?}")),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BetaPosterior {
pub alpha: f64,
pub beta: f64,
}
impl BetaPosterior {
pub fn new(alpha: f64, beta: f64) -> Self {
Self { alpha, beta }
}
pub fn mean(&self) -> f64 {
self.alpha / (self.alpha + self.beta)
}
pub fn variance(&self) -> f64 {
let n = self.alpha + self.beta;
(self.alpha * self.beta) / (n * n * (n + 1.0))
}
pub fn effective_sample_size(&self) -> f64 {
self.alpha + self.beta
}
pub fn update_success(&mut self) {
self.alpha += 1.0;
}
pub fn update_failure(&mut self) {
self.beta += 1.0;
}
pub fn update_success_weighted(&mut self, weight: f64) {
debug_assert!(
weight > 0.0,
"update_success_weighted: weight must be positive, got {weight}"
);
self.alpha += weight;
}
pub fn update_failure_weighted(&mut self, weight: f64) {
debug_assert!(
weight > 0.0,
"update_failure_weighted: weight must be positive, got {weight}"
);
self.beta += weight;
}
pub fn merge(&self, other: &BetaPosterior, prior: &BetaPosterior) -> BetaPosterior {
BetaPosterior {
alpha: self.alpha + other.alpha - prior.alpha,
beta: self.beta + other.beta - prior.beta,
}
}
pub fn apply_ess_cap(&mut self, prior: &BetaPosterior, cap: f64) {
let ess = self.effective_sample_size();
if ess > cap {
let prior_ess = prior.effective_sample_size();
let scale = (cap - prior_ess) / (ess - prior_ess);
self.alpha = prior.alpha + (self.alpha - prior.alpha) * scale;
self.beta = prior.beta + (self.beta - prior.beta) * scale;
}
}
pub fn floored_mean(&self, floor: f64) -> f64 {
self.mean().max(floor)
}
}
impl Default for BetaPosterior {
fn default() -> Self {
Self::new(1.0, 1.0)
}
}
pub struct EntityPosteriors {
map: HashMap<Uuid, BetaPosterior>,
order: VecDeque<Uuid>,
capacity: usize,
}
impl EntityPosteriors {
pub fn new(capacity: usize) -> Self {
Self {
map: HashMap::with_capacity(capacity),
order: VecDeque::with_capacity(capacity),
capacity,
}
}
pub fn get_or_insert(
&mut self,
id: Uuid,
default: impl FnOnce() -> BetaPosterior,
) -> &mut BetaPosterior {
if !self.map.contains_key(&id) {
if self.map.len() >= self.capacity {
if let Some(evicted) = self.order.pop_front() {
self.map.remove(&evicted);
}
}
self.map.insert(id, default());
self.order.push_back(id);
}
self.map.get_mut(&id).unwrap()
}
pub fn get(&self, id: &Uuid) -> Option<&BetaPosterior> {
self.map.get(id)
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn clear(&mut self) {
self.map.clear();
self.order.clear();
}
pub fn to_snapshot(&self) -> HashMap<Uuid, BetaPosterior> {
self.map.clone()
}
pub fn from_snapshot(snapshot: HashMap<Uuid, BetaPosterior>, capacity: usize) -> Self {
let mut ep = Self::new(capacity);
for (id, posterior) in snapshot {
ep.map.insert(id, posterior);
ep.order.push_back(id);
}
ep
}
}
pub struct BalancedRecallState {
pub relevance: BetaPosterior,
pub salience: BetaPosterior,
pub temporal: BetaPosterior,
pub entity_posteriors: EntityPosteriors,
pub total_events: u64,
pub exploration_epoch: u64,
}
impl BalancedRecallState {
pub fn new(entity_capacity: usize) -> Self {
Self {
relevance: BetaPosterior::new(7.0, 3.0),
salience: BetaPosterior::new(2.0, 8.0),
temporal: BetaPosterior::new(1.0, 9.0),
entity_posteriors: EntityPosteriors::new(entity_capacity),
total_events: 0,
exploration_epoch: 0,
}
}
pub fn reset_posteriors(&mut self) {
self.relevance = BetaPosterior::new(7.0, 3.0);
self.salience = BetaPosterior::new(2.0, 8.0);
self.temporal = BetaPosterior::new(1.0, 9.0);
self.entity_posteriors.clear();
self.exploration_epoch += 1;
}
pub fn to_snapshot(&self) -> BalancedRecallSnapshot {
BalancedRecallSnapshot {
relevance: self.relevance.clone(),
salience: self.salience.clone(),
temporal: self.temporal.clone(),
entity_posteriors: self.entity_posteriors.to_snapshot(),
total_events: self.total_events,
exploration_epoch: self.exploration_epoch,
}
}
pub fn from_snapshot(snapshot: BalancedRecallSnapshot, entity_capacity: usize) -> Self {
Self {
relevance: snapshot.relevance,
salience: snapshot.salience,
temporal: snapshot.temporal,
entity_posteriors: EntityPosteriors::from_snapshot(
snapshot.entity_posteriors,
entity_capacity,
),
total_events: snapshot.total_events,
exploration_epoch: snapshot.exploration_epoch,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BalancedRecallSnapshot {
pub relevance: BetaPosterior,
pub salience: BetaPosterior,
pub temporal: BetaPosterior,
pub entity_posteriors: HashMap<Uuid, BetaPosterior>,
pub total_events: u64,
pub exploration_epoch: u64,
}
pub const DEFAULT_ESS_CAP: f64 = 100.0;
pub const DEFAULT_EXPLORATION_EPOCH: u64 = 50;
pub const DEFAULT_TAU_0: f64 = 1.0;
pub const DEFAULT_TAU_EXPLOIT: f64 = 0.1;
pub const DEFAULT_SECTION_WEIGHT_FLOOR: f64 = 0.05;
pub struct SectionPosteriorState {
pub posteriors: HashMap<SectionType, BetaPosterior>,
pub priors: HashMap<SectionType, BetaPosterior>,
pub total_events: u64,
pub exploration_epoch: u64,
}
impl SectionPosteriorState {
pub fn new() -> Self {
let priors = Self::default_priors();
let posteriors = priors.clone();
Self {
posteriors,
priors,
total_events: 0,
exploration_epoch: DEFAULT_EXPLORATION_EPOCH,
}
}
pub fn from_priors(mut priors: HashMap<SectionType, BetaPosterior>) -> Self {
let neutral = BetaPosterior::new(2.0, 2.0);
for &st in SectionType::all() {
priors.entry(st).or_insert_with(|| neutral.clone());
}
let posteriors = priors.clone();
Self {
posteriors,
priors,
total_events: 0,
exploration_epoch: DEFAULT_EXPLORATION_EPOCH,
}
}
pub fn default_priors() -> HashMap<SectionType, BetaPosterior> {
let mut m = HashMap::new();
m.insert(SectionType::Overview, BetaPosterior::new(2.0, 2.0));
m.insert(SectionType::CoreModel, BetaPosterior::new(4.0, 2.0));
m.insert(
SectionType::BoundaryConditions,
BetaPosterior::new(2.0, 3.0),
);
m.insert(SectionType::Formalism, BetaPosterior::new(1.5, 4.0));
m.insert(
SectionType::OperationalGuidance,
BetaPosterior::new(6.0, 1.5),
);
m.insert(SectionType::Examples, BetaPosterior::new(5.0, 2.0));
m.insert(SectionType::FailureModes, BetaPosterior::new(3.0, 2.0));
m.insert(SectionType::ExpertLens, BetaPosterior::new(3.0, 2.0));
m.insert(SectionType::References, BetaPosterior::new(2.0, 2.0));
m.insert(SectionType::Other, BetaPosterior::new(2.0, 2.0));
m
}
pub fn to_snapshot(&self) -> SectionPosteriorSnapshot {
SectionPosteriorSnapshot {
posteriors: self.posteriors.clone(),
priors: self.priors.clone(),
total_events: self.total_events,
exploration_epoch: self.exploration_epoch,
}
}
pub fn from_snapshot(snapshot: SectionPosteriorSnapshot) -> Self {
Self {
posteriors: snapshot.posteriors,
priors: snapshot.priors,
total_events: snapshot.total_events,
exploration_epoch: snapshot.exploration_epoch,
}
}
pub fn weights(&self, rng: &mut impl rand::Rng) -> HashMap<SectionType, f64> {
if self.exploration_epoch > 0 {
self.sample_weights(rng)
} else {
self.deterministic_weights()
}
}
pub fn sample_weights(&self, rng: &mut impl rand::Rng) -> HashMap<SectionType, f64> {
let tau =
DEFAULT_TAU_0 * (self.exploration_epoch as f64 / DEFAULT_EXPLORATION_EPOCH as f64);
let tau = tau.max(1e-6);
let mut samples: HashMap<SectionType, f64> = HashMap::new();
for (&st, posterior) in &self.posteriors {
let theta = sample_beta_gamma(posterior.alpha.max(1e-6), posterior.beta.max(1e-6), rng);
samples.insert(st, theta / tau);
}
let max_val = samples.values().cloned().fold(f64::NEG_INFINITY, f64::max);
let mut raw: HashMap<SectionType, f64> = HashMap::new();
for (&st, &logit) in &samples {
raw.insert(st, (logit - max_val).exp());
}
apply_floor_and_renorm(&mut raw, DEFAULT_SECTION_WEIGHT_FLOOR);
raw
}
pub fn deterministic_weights(&self) -> HashMap<SectionType, f64> {
let tau = DEFAULT_TAU_EXPLOIT;
let logits: HashMap<SectionType, f64> = self
.posteriors
.iter()
.map(|(&st, p)| (st, p.mean() / tau))
.collect();
let max_val = logits.values().cloned().fold(f64::NEG_INFINITY, f64::max);
let mut raw: HashMap<SectionType, f64> = HashMap::new();
for (&st, &logit) in &logits {
raw.insert(st, (logit - max_val).exp());
}
apply_floor_and_renorm(&mut raw, DEFAULT_SECTION_WEIGHT_FLOOR);
raw
}
pub fn reset_posteriors(&mut self) {
self.posteriors = self.priors.clone();
}
}
impl Default for SectionPosteriorState {
fn default() -> Self {
Self::new()
}
}
fn apply_floor_and_renorm(weights: &mut HashMap<SectionType, f64>, floor: f64) {
let sum: f64 = weights.values().sum();
if sum > 0.0 {
for v in weights.values_mut() {
*v /= sum;
}
}
for _ in 0..20 {
let (pinned_sum, n_free) = weights.values().fold((0.0f64, 0usize), |(ps, nf), &w| {
if w <= floor {
(ps + floor, nf)
} else {
(ps, nf + 1)
}
});
if n_free == 0 {
let total: f64 = weights.values().sum();
if total > 0.0 {
for v in weights.values_mut() {
*v /= total;
}
}
break;
}
let free_mass = (1.0 - pinned_sum).max(0.0);
let free_sum: f64 = weights.values().filter(|&&w| w > floor).sum();
for v in weights.values_mut() {
if *v <= floor {
*v = floor;
} else if free_sum > 0.0 {
*v = (*v / free_sum) * free_mass;
}
}
if weights.values().all(|&w| w >= floor - 1e-12) {
break;
}
}
}
fn sample_beta_gamma(alpha: f64, beta: f64, rng: &mut impl rand::Rng) -> f64 {
let x = sample_gamma_mt(alpha, rng);
let y = sample_gamma_mt(beta, rng);
let s = x + y;
if s <= 0.0 {
0.5
} else {
x / s
}
}
fn sample_gamma_mt(shape: f64, rng: &mut impl rand::Rng) -> f64 {
if shape < 1.0 {
let g = sample_gamma_mt(shape + 1.0, rng);
let u: f64 = rng.gen();
return g * u.powf(1.0 / shape);
}
let d = shape - 1.0 / 3.0;
let c = 1.0 / (9.0 * d).sqrt();
loop {
let x: f64 = sample_standard_normal_bm(rng);
let v_raw = 1.0 + c * x;
if v_raw <= 0.0 {
continue;
}
let v = v_raw * v_raw * v_raw;
let u: f64 = rng.gen();
if u < 1.0 - 0.0331 * (x * x) * (x * x) {
return d * v;
}
if u.ln() < 0.5 * x * x + d * (1.0 - v + v.ln()) {
return d * v;
}
}
}
fn sample_standard_normal_bm(rng: &mut impl rand::Rng) -> f64 {
let u1: f64 = rng.gen::<f64>().max(f64::EPSILON);
let u2: f64 = rng.gen();
(-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SectionPosteriorSnapshot {
pub posteriors: HashMap<SectionType, BetaPosterior>,
pub priors: HashMap<SectionType, BetaPosterior>,
pub total_events: u64,
pub exploration_epoch: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProfileLifecycle {
Defined,
Registered,
Active,
Inactive,
Archived,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfileRecord {
pub id: String,
pub description: String,
pub consumer_kind: String,
pub state_class: String,
pub lifecycle: ProfileLifecycle,
pub created_at: DateTime<Utc>,
pub state_snapshot: Option<serde_json::Value>,
pub total_events: u64,
pub exploration_epoch: u64,
}
impl ProfileRecord {
pub fn new_balanced_recall(entity_capacity: usize) -> Self {
let state = BalancedRecallState::new(entity_capacity);
let snapshot = state.to_snapshot();
Self {
id: "balanced-recall-v1".into(),
description: "Default recall profile: three-scalar Beta posteriors (ADR-032 §5a)"
.into(),
consumer_kind: "recall".into(),
state_class: "Bayesian".into(),
lifecycle: ProfileLifecycle::Active,
created_at: Utc::now(),
state_snapshot: serde_json::to_value(snapshot).ok(),
total_events: 0,
exploration_epoch: 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfileBinding {
pub actor: String,
pub namespace: String,
pub consumer_kind: String,
pub profile_id: String,
pub priority: i32,
pub created_at: DateTime<Utc>,
}
pub struct BrainState {
pub profiles: HashMap<String, ProfileRecord>,
pub balanced_recall: BalancedRecallState,
pub profile_states: HashMap<String, BalancedRecallState>,
pub bindings: Vec<ProfileBinding>,
pub section_states: HashMap<String, SectionPosteriorState>,
}
impl BrainState {
pub fn new(entity_capacity: usize) -> Self {
let mut profiles = HashMap::new();
let record = ProfileRecord::new_balanced_recall(entity_capacity);
let profile_id = record.id.clone();
profiles.insert(profile_id.clone(), record);
Self {
profiles,
balanced_recall: BalancedRecallState::new(entity_capacity),
profile_states: HashMap::new(),
bindings: Vec::new(),
section_states: HashMap::new(),
}
}
pub fn to_snapshot(&self) -> BrainStateSnapshot {
let extra: HashMap<String, BalancedRecallSnapshot> = self
.profile_states
.iter()
.map(|(id, s)| (id.clone(), s.to_snapshot()))
.collect();
let section_states: HashMap<String, SectionPosteriorSnapshot> = self
.section_states
.iter()
.map(|(id, s)| (id.clone(), s.to_snapshot()))
.collect();
BrainStateSnapshot {
profiles: self.profiles.clone(),
balanced_recall: self.balanced_recall.to_snapshot(),
profile_states: extra,
bindings: self.bindings.clone(),
section_states,
}
}
pub fn from_snapshot(snapshot: BrainStateSnapshot, entity_capacity: usize) -> Self {
let extra: HashMap<String, BalancedRecallState> = snapshot
.profile_states
.into_iter()
.map(|(id, s)| (id, BalancedRecallState::from_snapshot(s, entity_capacity)))
.collect();
let section_states: HashMap<String, SectionPosteriorState> = snapshot
.section_states
.into_iter()
.map(|(id, s)| (id, SectionPosteriorState::from_snapshot(s)))
.collect();
Self {
profiles: snapshot.profiles,
balanced_recall: BalancedRecallState::from_snapshot(
snapshot.balanced_recall,
entity_capacity,
),
profile_states: extra,
bindings: snapshot.bindings,
section_states,
}
}
pub fn reset_posteriors(&mut self) {
self.balanced_recall.reset_posteriors();
if let Some(record) = self.profiles.get_mut("balanced-recall-v1") {
record.exploration_epoch = self.balanced_recall.exploration_epoch;
record.state_snapshot = serde_json::to_value(self.balanced_recall.to_snapshot()).ok();
}
if let Some(ss) = self.section_states.get_mut("balanced-recall-v1") {
ss.reset_posteriors();
}
}
pub fn reset_profile_posteriors(&mut self, profile_id: &str) {
if let Some(ps) = self.profile_states.get_mut(profile_id) {
ps.reset_posteriors();
let snap = serde_json::to_value(ps.to_snapshot()).ok();
let epoch = ps.exploration_epoch;
if let Some(record) = self.profiles.get_mut(profile_id) {
record.exploration_epoch = epoch;
record.state_snapshot = snap;
}
}
if let Some(ss) = self.section_states.get_mut(profile_id) {
ss.reset_posteriors();
}
}
pub fn resolve(
&self,
actor: Option<&str>,
namespace: Option<&str>,
consumer_kind: &str,
) -> Option<&ProfileRecord> {
self.resolve_with_match(actor, namespace, consumer_kind)
.map(|(record, _)| record)
}
pub fn resolve_with_match(
&self,
actor: Option<&str>,
namespace: Option<&str>,
consumer_kind: &str,
) -> Option<(&ProfileRecord, String)> {
let actor_val = actor.unwrap_or("*");
let namespace_val = namespace.unwrap_or("*");
let best = self
.bindings
.iter()
.filter(|b| {
(b.actor == "*" || b.actor == actor_val)
&& (b.namespace == "*" || b.namespace == namespace_val)
&& (b.consumer_kind == "*" || b.consumer_kind == consumer_kind)
&& self
.profiles
.get(&b.profile_id)
.is_some_and(|p| p.lifecycle != ProfileLifecycle::Archived)
})
.max_by_key(|b| {
let actor_score = if b.actor != "*" { 4 } else { 0 };
let ns_score = if b.namespace != "*" { 2 } else { 0 };
let kind_score = if b.consumer_kind != "*" { 1 } else { 0 };
(
actor_score + ns_score + kind_score,
b.priority,
-(b.created_at.timestamp()),
)
});
if let Some(binding) = best {
if let Some(record) = self.profiles.get(&binding.profile_id) {
return Some((record, binding.consumer_kind.clone()));
}
}
if let Some(default) = self.profiles.get("balanced-recall-v1") {
if default.lifecycle == ProfileLifecycle::Active
&& (default.consumer_kind == consumer_kind
|| consumer_kind == "*"
|| default.consumer_kind == "*")
{
return Some((default, default.consumer_kind.clone()));
}
}
self.profiles.values().find_map(|p| {
if p.consumer_kind == consumer_kind && p.lifecycle == ProfileLifecycle::Active {
Some((p, p.consumer_kind.clone()))
} else {
None
}
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrainStateSnapshot {
pub profiles: HashMap<String, ProfileRecord>,
pub balanced_recall: BalancedRecallSnapshot,
#[serde(default)]
pub profile_states: HashMap<String, BalancedRecallSnapshot>,
pub bindings: Vec<ProfileBinding>,
#[serde(default)]
pub section_states: HashMap<String, SectionPosteriorSnapshot>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn beta_posterior_mean() {
let p = BetaPosterior::new(7.0, 3.0);
assert!((p.mean() - 0.7).abs() < 1e-12);
}
#[test]
fn beta_posterior_variance() {
let p = BetaPosterior::new(7.0, 3.0);
let expected = 21.0 / 1100.0;
assert!((p.variance() - expected).abs() < 1e-12);
}
#[test]
fn beta_posterior_ess() {
let p = BetaPosterior::new(7.0, 3.0);
assert!((p.effective_sample_size() - 10.0).abs() < 1e-12);
}
#[test]
fn beta_posterior_update() {
let mut p = BetaPosterior::new(1.0, 1.0);
p.update_success();
p.update_success();
p.update_failure();
assert!((p.alpha - 3.0).abs() < 1e-12);
assert!((p.beta - 2.0).abs() < 1e-12);
assert!((p.mean() - 0.6).abs() < 1e-12);
}
#[test]
fn beta_posterior_merge() {
let prior = BetaPosterior::new(2.0, 8.0);
let a = BetaPosterior::new(5.0, 9.0); let b = BetaPosterior::new(4.0, 10.0); let merged = a.merge(&b, &prior);
assert!((merged.alpha - 7.0).abs() < 1e-12);
assert!((merged.beta - 11.0).abs() < 1e-12);
}
#[test]
fn beta_posterior_apply_ess_cap_noop() {
let prior = BetaPosterior::new(2.0, 2.0);
let mut p = BetaPosterior::new(7.0, 3.0);
p.apply_ess_cap(&prior, 50.0);
assert!((p.alpha - 7.0).abs() < 1e-12);
assert!((p.beta - 3.0).abs() < 1e-12);
}
#[test]
fn beta_posterior_apply_ess_cap_rescale() {
let prior = BetaPosterior::new(2.0, 2.0);
let mut p = BetaPosterior::new(30.0, 30.0);
p.apply_ess_cap(&prior, 50.0);
let scale = (50.0 - 4.0) / (60.0 - 4.0); let expected_excess = 28.0 * scale;
assert!((p.alpha - (2.0 + expected_excess)).abs() < 1e-10);
assert!((p.beta - (2.0 + expected_excess)).abs() < 1e-10);
assert!((p.effective_sample_size() - 50.0).abs() < 1e-10);
}
#[test]
fn beta_posterior_floored_mean() {
let p = BetaPosterior::new(1.0, 99.0); assert!((p.floored_mean(0.05) - 0.05).abs() < 1e-12);
let p2 = BetaPosterior::new(7.0, 3.0); assert!((p2.floored_mean(0.05) - 0.7).abs() < 1e-12);
}
#[test]
fn entity_posteriors_eviction() {
let mut ep = EntityPosteriors::new(3);
let ids: Vec<Uuid> = (0..5).map(|_| Uuid::new_v4()).collect();
for id in &ids {
ep.get_or_insert(*id, BetaPosterior::default);
}
assert_eq!(ep.len(), 3);
assert!(ep.get(&ids[0]).is_none());
assert!(ep.get(&ids[1]).is_none());
assert!(ep.get(&ids[2]).is_some());
assert!(ep.get(&ids[3]).is_some());
assert!(ep.get(&ids[4]).is_some());
}
#[test]
fn entity_posteriors_get_or_insert_existing() {
let mut ep = EntityPosteriors::new(10);
let id = Uuid::new_v4();
ep.get_or_insert(id, BetaPosterior::default)
.update_success();
let p = ep.get_or_insert(id, BetaPosterior::default);
assert!((p.alpha - 2.0).abs() < 1e-12);
}
#[test]
fn balanced_recall_state_snapshot_roundtrip() {
let mut state = BalancedRecallState::new(100);
state.relevance.update_success();
state.total_events = 42;
let id = Uuid::new_v4();
state
.entity_posteriors
.get_or_insert(id, BetaPosterior::default)
.update_success();
let snapshot = state.to_snapshot();
let json = serde_json::to_string(&snapshot).unwrap();
let back: BalancedRecallSnapshot = serde_json::from_str(&json).unwrap();
assert_eq!(back.total_events, 42);
assert!((back.relevance.alpha - 8.0).abs() < 1e-12);
assert!(back.entity_posteriors.contains_key(&id));
}
#[test]
fn balanced_recall_state_reset_preserves_epoch_increment() {
let mut state = BalancedRecallState::new(10);
state.total_events = 100;
state.reset_posteriors();
assert_eq!(state.total_events, 100);
assert_eq!(state.exploration_epoch, 1);
assert!((state.relevance.alpha - 7.0).abs() < 1e-12);
assert!((state.relevance.beta - 3.0).abs() < 1e-12);
}
#[test]
fn brain_state_has_balanced_recall_profile_by_default() {
let state = BrainState::new(100);
assert!(state.profiles.contains_key("balanced-recall-v1"));
let record = &state.profiles["balanced-recall-v1"];
assert_eq!(record.lifecycle, ProfileLifecycle::Active);
assert_eq!(record.consumer_kind, "recall");
assert_eq!(record.state_class, "Bayesian");
}
#[test]
fn brain_state_reset_posteriors_updates_record() {
let mut state = BrainState::new(10);
state.balanced_recall.relevance.update_success();
state.balanced_recall.total_events = 50;
state.reset_posteriors();
assert_eq!(state.balanced_recall.exploration_epoch, 1);
let record = &state.profiles["balanced-recall-v1"];
assert_eq!(record.exploration_epoch, 1);
}
#[test]
fn brain_state_resolve_falls_back_to_default() {
let state = BrainState::new(100);
let resolved = state.resolve(None, None, "recall");
assert!(resolved.is_some());
assert_eq!(resolved.unwrap().id, "balanced-recall-v1");
}
#[test]
fn brain_state_resolve_uses_explicit_binding() {
let mut state = BrainState::new(100);
let mut alt = ProfileRecord::new_balanced_recall(100);
alt.id = "alt-profile".into();
state.profiles.insert("alt-profile".into(), alt);
state.bindings.push(ProfileBinding {
actor: "agent-1".into(),
namespace: "*".into(),
consumer_kind: "recall".into(),
profile_id: "alt-profile".into(),
priority: 0,
created_at: Utc::now(),
});
let resolved = state.resolve(Some("agent-1"), None, "recall");
assert!(resolved.is_some());
assert_eq!(resolved.unwrap().id, "alt-profile");
let resolved_other = state.resolve(Some("agent-2"), None, "recall");
assert_eq!(resolved_other.unwrap().id, "balanced-recall-v1");
}
#[test]
fn brain_state_resolve_skips_archived_default() {
let mut state = BrainState::new(100);
state
.profiles
.get_mut("balanced-recall-v1")
.expect("default profile always exists")
.lifecycle = ProfileLifecycle::Archived;
let resolved = state.resolve(None, None, "recall");
assert!(
resolved.is_none(),
"archived default profile must not be returned by resolve"
);
}
#[test]
fn entity_posteriors_from_snapshot_rebuilds_map() {
let id1 = Uuid::new_v4();
let id2 = Uuid::new_v4();
let mut snapshot = HashMap::new();
snapshot.insert(id1, BetaPosterior::new(3.0, 2.0));
snapshot.insert(id2, BetaPosterior::new(5.0, 1.0));
let ep = EntityPosteriors::from_snapshot(snapshot, 100);
assert_eq!(ep.len(), 2);
let p1 = ep.get(&id1).unwrap();
assert!((p1.alpha - 3.0).abs() < 1e-12);
let p2 = ep.get(&id2).unwrap();
assert!((p2.alpha - 5.0).abs() < 1e-12);
}
#[test]
fn brain_state_snapshot_roundtrip() {
let mut state = BrainState::new(100);
state.balanced_recall.relevance.update_success();
state.balanced_recall.total_events = 55;
state.balanced_recall.exploration_epoch = 2;
let id = Uuid::new_v4();
state
.balanced_recall
.entity_posteriors
.get_or_insert(id, || BetaPosterior::new(4.0, 6.0))
.update_success();
let snap1 = state.to_snapshot();
let restored = BrainState::from_snapshot(snap1, 100);
let snap2 = restored.to_snapshot();
assert_eq!(snap2.balanced_recall.total_events, 55);
assert_eq!(snap2.balanced_recall.exploration_epoch, 2);
assert!((snap2.balanced_recall.relevance.alpha - 8.0).abs() < 1e-12);
let ep = snap2.balanced_recall.entity_posteriors.get(&id).unwrap();
assert!((ep.alpha - 5.0).abs() < 1e-12);
assert!((ep.beta - 6.0).abs() < 1e-12);
}
#[test]
fn profile_lifecycle_serde_roundtrip() {
let lc = ProfileLifecycle::Active;
let json = serde_json::to_string(&lc).unwrap();
let back: ProfileLifecycle = serde_json::from_str(&json).unwrap();
assert_eq!(back, ProfileLifecycle::Active);
}
#[test]
fn beta_posterior_default_has_uniform_prior() {
let p = BetaPosterior::default();
assert!((p.alpha - 1.0).abs() < 1e-12);
assert!((p.beta - 1.0).abs() < 1e-12);
assert!((p.mean() - 0.5).abs() < 1e-12);
}
#[test]
fn section_type_serde_roundtrip() {
for &st in SectionType::all() {
let json = serde_json::to_string(&st).unwrap();
let back: SectionType = serde_json::from_str(&json).unwrap();
assert_eq!(back, st, "roundtrip failed for {st}");
}
}
#[test]
fn section_type_display_and_from_str() {
for &st in SectionType::all() {
let s = st.to_string();
let parsed: SectionType = s.parse().expect("parse should succeed");
assert_eq!(parsed, st);
}
}
#[test]
fn section_type_from_str_unknown_rejected() {
let result = "unknown_section".parse::<SectionType>();
assert!(result.is_err());
}
#[test]
fn section_posterior_state_new_has_all_sections() {
let state = SectionPosteriorState::new();
assert_eq!(state.posteriors.len(), 10);
assert_eq!(state.priors.len(), 10);
for &st in SectionType::all() {
assert!(
state.posteriors.contains_key(&st),
"missing section {st} in posteriors"
);
assert!(
state.priors.contains_key(&st),
"missing section {st} in priors"
);
}
}
#[test]
fn section_posterior_state_snapshot_roundtrip() {
let state = SectionPosteriorState::new();
let snap = state.to_snapshot();
let json = serde_json::to_string(&snap).unwrap();
let back: SectionPosteriorSnapshot = serde_json::from_str(&json).unwrap();
assert_eq!(back.posteriors.len(), 10);
assert_eq!(back.total_events, 0);
assert_eq!(back.exploration_epoch, DEFAULT_EXPLORATION_EPOCH);
let restored = SectionPosteriorState::from_snapshot(back);
assert_eq!(restored.posteriors.len(), 10);
let op = &state.posteriors[&SectionType::OperationalGuidance];
let rp = &restored.posteriors[&SectionType::OperationalGuidance];
assert!((op.alpha - rp.alpha).abs() < 1e-12);
assert!((op.beta - rp.beta).abs() < 1e-12);
}
#[test]
fn section_posterior_state_deterministic_weights_normalized() {
let state = SectionPosteriorState::new();
let weights = state.deterministic_weights();
assert_eq!(weights.len(), 10);
let sum: f64 = weights.values().sum();
assert!((sum - 1.0).abs() < 1e-10, "weights sum = {sum}");
for (&st, &w) in &weights {
assert!(
w >= DEFAULT_SECTION_WEIGHT_FLOOR - 1e-12,
"weight for {st} below floor: {w}"
);
}
}
#[test]
fn section_posterior_state_from_priors_fills_missing() {
let mut partial: HashMap<SectionType, BetaPosterior> = HashMap::new();
partial.insert(SectionType::Overview, BetaPosterior::new(5.0, 1.0));
partial.insert(SectionType::CoreModel, BetaPosterior::new(4.0, 2.0));
partial.insert(SectionType::Formalism, BetaPosterior::new(3.0, 3.0));
let state = SectionPosteriorState::from_priors(partial);
assert_eq!(state.priors.len(), 10);
assert_eq!(state.posteriors.len(), 10);
assert!((state.priors[&SectionType::Overview].alpha - 5.0).abs() < 1e-12);
let neutral = &state.priors[&SectionType::Examples];
assert!((neutral.alpha - 2.0).abs() < 1e-12);
assert!((neutral.beta - 2.0).abs() < 1e-12);
}
#[test]
fn beta_posterior_weighted_success_adds_weight_to_alpha() {
let mut p = BetaPosterior::new(2.0, 8.0);
p.update_success_weighted(1.5);
assert!((p.alpha - 3.5).abs() < 1e-12);
assert!((p.beta - 8.0).abs() < 1e-12);
}
#[test]
fn beta_posterior_weighted_failure_adds_weight_to_beta() {
let mut p = BetaPosterior::new(2.0, 8.0);
p.update_failure_weighted(2.0);
assert!((p.alpha - 2.0).abs() < 1e-12);
assert!((p.beta - 10.0).abs() < 1e-12);
}
#[test]
fn beta_posterior_weighted_fractional_update() {
let mut p = BetaPosterior::new(1.0, 1.0);
p.update_success_weighted(0.5);
assert!((p.alpha - 1.5).abs() < 1e-12);
assert!((p.beta - 1.0).abs() < 1e-12);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "update_success_weighted: weight must be positive")]
fn beta_posterior_weighted_success_rejects_zero_weight() {
let mut p = BetaPosterior::new(1.0, 1.0);
p.update_success_weighted(0.0);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "update_failure_weighted: weight must be positive")]
fn beta_posterior_weighted_failure_rejects_negative_weight() {
let mut p = BetaPosterior::new(1.0, 1.0);
p.update_failure_weighted(-1.0);
}
}