use std::sync::Arc;
use arrow::array::{Array, Float64Array, RecordBatch, StringArray, UInt8Array};
use arrow::datatypes::{DataType, Field, Schema};
pub const COGNITIVE_PARAMS_SCHEMA_VERSION: &str = "1.0.0";
pub mod param_col {
pub const PARAM_ID: usize = 0;
pub const CATEGORY: usize = 1;
pub const VALUE_F64: usize = 2;
pub const VALUE_STR: usize = 3;
pub const MIN_BOUND: usize = 4;
pub const MAX_BOUND: usize = 5;
pub const AUTONOMY_TIER: usize = 6;
pub const MODIFIED_BY: usize = 7;
}
pub mod category {
pub const SIGNAL_WEIGHT: &str = "signal_weight";
pub const THRESHOLD: &str = "threshold";
pub const LEARNING_CONFIG: &str = "learning_config";
pub const TRIGGER: &str = "trigger";
pub const LOSS_COEFFICIENT: &str = "loss_coefficient";
pub const CONSOLIDATION_TRIGGER: &str = "consolidation_trigger";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum AutonomyTier {
Auto = 1,
BeingApproved = 2,
CaptainOnly = 3,
}
impl AutonomyTier {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
1 => Some(Self::Auto),
2 => Some(Self::BeingApproved),
3 => Some(Self::CaptainOnly),
_ => None,
}
}
}
pub fn cognitive_params_schema() -> Schema {
Schema::new(vec![
Field::new("param_id", DataType::Utf8, false),
Field::new("category", DataType::Utf8, false),
Field::new("value_f64", DataType::Float64, true),
Field::new("value_str", DataType::Utf8, true),
Field::new("min_bound", DataType::Float64, true),
Field::new("max_bound", DataType::Float64, true),
Field::new("autonomy_tier", DataType::UInt8, false),
Field::new("modified_by", DataType::Utf8, false),
])
}
#[derive(Debug, Clone, PartialEq)]
pub struct CognitiveParameter {
pub param_id: String,
pub category: String,
pub value_f64: Option<f64>,
pub value_str: Option<String>,
pub min_bound: Option<f64>,
pub max_bound: Option<f64>,
pub autonomy_tier: AutonomyTier,
pub modified_by: String,
}
#[derive(Debug, thiserror::Error)]
pub enum ParamStoreError {
#[error("parameter not found: {0}")]
NotFound(String),
#[error("value {value} out of bounds [{min}, {max}] for parameter {param_id}")]
OutOfBounds {
param_id: String,
value: f64,
min: f64,
max: f64,
},
#[error(
"autonomy tier violation: parameter {param_id} requires tier {required:?}, caller is {caller:?}"
)]
TierViolation {
param_id: String,
required: AutonomyTier,
caller: AutonomyTier,
},
#[error("arrow error: {0}")]
Arrow(#[from] arrow::error::ArrowError),
#[error("parquet error: {0}")]
Parquet(#[from] parquet::errors::ParquetError),
#[error("invalid schema: {0}")]
InvalidSchema(String),
}
#[derive(Debug, Clone)]
pub struct CognitiveParameterStore {
batch: RecordBatch,
}
impl CognitiveParameterStore {
pub fn new() -> Self {
let schema = Arc::new(cognitive_params_schema());
let batch = RecordBatch::new_empty(schema);
Self { batch }
}
pub fn from_batch(batch: RecordBatch) -> Result<Self, ParamStoreError> {
let expected = cognitive_params_schema();
if batch.schema().fields().len() != expected.fields().len() {
return Err(ParamStoreError::InvalidSchema(format!(
"expected {} columns, got {}",
expected.fields().len(),
batch.schema().fields().len()
)));
}
let actual_schema = batch.schema();
for (i, field) in expected.fields().iter().enumerate() {
let actual = actual_schema.field(i);
if actual.data_type() != field.data_type() {
return Err(ParamStoreError::InvalidSchema(format!(
"column {}: expected {:?}, got {:?}",
i,
field.data_type(),
actual.data_type()
)));
}
}
Ok(Self { batch })
}
pub fn len(&self) -> usize {
self.batch.num_rows()
}
pub fn is_empty(&self) -> bool {
self.batch.num_rows() == 0
}
pub fn get(&self, param_id: &str) -> Option<CognitiveParameter> {
let ids = self.param_id_col();
for i in 0..self.batch.num_rows() {
if ids.value(i) == param_id {
return Some(self.row_to_param(i));
}
}
None
}
pub fn list(&self, cat: &str) -> Vec<CognitiveParameter> {
let categories = self.category_col();
let mut result = Vec::new();
for i in 0..self.batch.num_rows() {
if categories.value(i) == cat {
result.push(self.row_to_param(i));
}
}
result
}
pub fn list_all(&self) -> Vec<CognitiveParameter> {
(0..self.batch.num_rows())
.map(|i| self.row_to_param(i))
.collect()
}
pub fn set(
&mut self,
param_id: &str,
value: f64,
modified_by: &str,
caller_tier: AutonomyTier,
) -> Result<(), ParamStoreError> {
let idx = self.find_index(param_id)?;
let param = self.row_to_param(idx);
if caller_tier < param.autonomy_tier {
return Err(ParamStoreError::TierViolation {
param_id: param_id.to_string(),
required: param.autonomy_tier,
caller: caller_tier,
});
}
if let (Some(min), Some(max)) = (param.min_bound, param.max_bound)
&& (value < min || value > max)
{
return Err(ParamStoreError::OutOfBounds {
param_id: param_id.to_string(),
value,
min,
max,
});
}
self.batch = self.rebuild_with_f64_update(idx, value, modified_by)?;
Ok(())
}
pub fn insert(&mut self, param: &CognitiveParameter) -> Result<(), ParamStoreError> {
let existing_idx = {
let ids = self.param_id_col();
(0..self.batch.num_rows()).find(|&i| ids.value(i) == param.param_id)
};
let batch = if let Some(idx) = existing_idx {
self.remove_row(idx)?
} else {
self.batch.clone()
};
self.batch = append_row(&batch, param)?;
Ok(())
}
pub fn snapshot(&self) -> &RecordBatch {
&self.batch
}
pub fn into_batch(self) -> RecordBatch {
self.batch
}
fn param_id_col(&self) -> &StringArray {
self.batch
.column(param_col::PARAM_ID)
.as_any()
.downcast_ref::<StringArray>()
.expect("param_id column is StringArray")
}
fn category_col(&self) -> &StringArray {
self.batch
.column(param_col::CATEGORY)
.as_any()
.downcast_ref::<StringArray>()
.expect("category column is StringArray")
}
fn find_index(&self, param_id: &str) -> Result<usize, ParamStoreError> {
let ids = self.param_id_col();
for i in 0..self.batch.num_rows() {
if ids.value(i) == param_id {
return Ok(i);
}
}
Err(ParamStoreError::NotFound(param_id.to_string()))
}
fn row_to_param(&self, idx: usize) -> CognitiveParameter {
let ids = self.param_id_col();
let categories = self.category_col();
let values_f64 = self
.batch
.column(param_col::VALUE_F64)
.as_any()
.downcast_ref::<Float64Array>()
.expect("value_f64 column");
let values_str = self
.batch
.column(param_col::VALUE_STR)
.as_any()
.downcast_ref::<StringArray>()
.expect("value_str column");
let min_bounds = self
.batch
.column(param_col::MIN_BOUND)
.as_any()
.downcast_ref::<Float64Array>()
.expect("min_bound column");
let max_bounds = self
.batch
.column(param_col::MAX_BOUND)
.as_any()
.downcast_ref::<Float64Array>()
.expect("max_bound column");
let tiers = self
.batch
.column(param_col::AUTONOMY_TIER)
.as_any()
.downcast_ref::<UInt8Array>()
.expect("autonomy_tier column");
let modified = self
.batch
.column(param_col::MODIFIED_BY)
.as_any()
.downcast_ref::<StringArray>()
.expect("modified_by column");
CognitiveParameter {
param_id: ids.value(idx).to_string(),
category: categories.value(idx).to_string(),
value_f64: if values_f64.is_null(idx) {
None
} else {
Some(values_f64.value(idx))
},
value_str: if values_str.is_null(idx) {
None
} else {
Some(values_str.value(idx).to_string())
},
min_bound: if min_bounds.is_null(idx) {
None
} else {
Some(min_bounds.value(idx))
},
max_bound: if max_bounds.is_null(idx) {
None
} else {
Some(max_bounds.value(idx))
},
autonomy_tier: AutonomyTier::from_u8(tiers.value(idx))
.unwrap_or(AutonomyTier::CaptainOnly),
modified_by: modified.value(idx).to_string(),
}
}
fn rebuild_with_f64_update(
&self,
idx: usize,
new_value: f64,
new_modified_by: &str,
) -> Result<RecordBatch, ParamStoreError> {
let n = self.batch.num_rows();
let ids = self.param_id_col();
let cats = self.category_col();
let vals = self
.batch
.column(param_col::VALUE_F64)
.as_any()
.downcast_ref::<Float64Array>()
.expect("value_f64");
let strs = self
.batch
.column(param_col::VALUE_STR)
.as_any()
.downcast_ref::<StringArray>()
.expect("value_str");
let mins = self
.batch
.column(param_col::MIN_BOUND)
.as_any()
.downcast_ref::<Float64Array>()
.expect("min_bound");
let maxs = self
.batch
.column(param_col::MAX_BOUND)
.as_any()
.downcast_ref::<Float64Array>()
.expect("max_bound");
let tiers = self
.batch
.column(param_col::AUTONOMY_TIER)
.as_any()
.downcast_ref::<UInt8Array>()
.expect("autonomy_tier");
let mods = self
.batch
.column(param_col::MODIFIED_BY)
.as_any()
.downcast_ref::<StringArray>()
.expect("modified_by");
let new_ids: Vec<&str> = (0..n).map(|i| ids.value(i)).collect();
let new_cats: Vec<&str> = (0..n).map(|i| cats.value(i)).collect();
let new_vals: Vec<Option<f64>> = (0..n)
.map(|i| {
if i == idx {
Some(new_value)
} else if vals.is_null(i) {
None
} else {
Some(vals.value(i))
}
})
.collect();
let new_strs: Vec<Option<&str>> = (0..n)
.map(|i| {
if strs.is_null(i) {
None
} else {
Some(strs.value(i))
}
})
.collect();
let new_mins: Vec<Option<f64>> = (0..n)
.map(|i| {
if mins.is_null(i) {
None
} else {
Some(mins.value(i))
}
})
.collect();
let new_maxs: Vec<Option<f64>> = (0..n)
.map(|i| {
if maxs.is_null(i) {
None
} else {
Some(maxs.value(i))
}
})
.collect();
let new_tiers: Vec<u8> = (0..n).map(|i| tiers.value(i)).collect();
let new_mods: Vec<&str> = (0..n)
.map(|i| {
if i == idx {
new_modified_by
} else {
mods.value(i)
}
})
.collect();
Ok(RecordBatch::try_new(
Arc::new(cognitive_params_schema()),
vec![
Arc::new(StringArray::from(new_ids)),
Arc::new(StringArray::from(new_cats)),
Arc::new(Float64Array::from(new_vals)),
Arc::new(StringArray::from(new_strs)),
Arc::new(Float64Array::from(new_mins)),
Arc::new(Float64Array::from(new_maxs)),
Arc::new(UInt8Array::from(new_tiers)),
Arc::new(StringArray::from(new_mods)),
],
)?)
}
fn remove_row(&self, idx: usize) -> Result<RecordBatch, ParamStoreError> {
let n = self.batch.num_rows();
if n == 0 {
return Ok(self.batch.clone());
}
let ids = self.param_id_col();
let cats = self.category_col();
let vals = self
.batch
.column(param_col::VALUE_F64)
.as_any()
.downcast_ref::<Float64Array>()
.expect("value_f64");
let strs = self
.batch
.column(param_col::VALUE_STR)
.as_any()
.downcast_ref::<StringArray>()
.expect("value_str");
let mins = self
.batch
.column(param_col::MIN_BOUND)
.as_any()
.downcast_ref::<Float64Array>()
.expect("min_bound");
let maxs = self
.batch
.column(param_col::MAX_BOUND)
.as_any()
.downcast_ref::<Float64Array>()
.expect("max_bound");
let tiers = self
.batch
.column(param_col::AUTONOMY_TIER)
.as_any()
.downcast_ref::<UInt8Array>()
.expect("autonomy_tier");
let mods = self
.batch
.column(param_col::MODIFIED_BY)
.as_any()
.downcast_ref::<StringArray>()
.expect("modified_by");
let keep: Vec<usize> = (0..n).filter(|&i| i != idx).collect();
if keep.is_empty() {
return Ok(RecordBatch::new_empty(Arc::new(cognitive_params_schema())));
}
let new_ids: Vec<&str> = keep.iter().map(|&i| ids.value(i)).collect();
let new_cats: Vec<&str> = keep.iter().map(|&i| cats.value(i)).collect();
let new_vals: Vec<Option<f64>> = keep
.iter()
.map(|&i| {
if vals.is_null(i) {
None
} else {
Some(vals.value(i))
}
})
.collect();
let new_strs: Vec<Option<&str>> = keep
.iter()
.map(|&i| {
if strs.is_null(i) {
None
} else {
Some(strs.value(i))
}
})
.collect();
let new_mins: Vec<Option<f64>> = keep
.iter()
.map(|&i| {
if mins.is_null(i) {
None
} else {
Some(mins.value(i))
}
})
.collect();
let new_maxs: Vec<Option<f64>> = keep
.iter()
.map(|&i| {
if maxs.is_null(i) {
None
} else {
Some(maxs.value(i))
}
})
.collect();
let new_tiers: Vec<u8> = keep.iter().map(|&i| tiers.value(i)).collect();
let new_mods: Vec<&str> = keep.iter().map(|&i| mods.value(i)).collect();
Ok(RecordBatch::try_new(
Arc::new(cognitive_params_schema()),
vec![
Arc::new(StringArray::from(new_ids)),
Arc::new(StringArray::from(new_cats)),
Arc::new(Float64Array::from(new_vals)),
Arc::new(StringArray::from(new_strs)),
Arc::new(Float64Array::from(new_mins)),
Arc::new(Float64Array::from(new_maxs)),
Arc::new(UInt8Array::from(new_tiers)),
Arc::new(StringArray::from(new_mods)),
],
)?)
}
pub fn insert_signal_weight(
&mut self,
dimension: &str,
path: &str,
value: f64,
) -> Result<(), ParamStoreError> {
let clipped = value.clamp(-1.5, 1.5);
let param = CognitiveParameter {
param_id: format!("sw.{dimension}.{path}"),
category: category::SIGNAL_WEIGHT.to_string(),
value_f64: Some(clipped),
value_str: None,
min_bound: Some(-1.5),
max_bound: Some(1.5),
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".to_string(),
};
self.insert(¶m)
}
pub fn get_signal_weight(&self, dimension: &str, path: &str) -> Option<f64> {
let param_id = format!("sw.{dimension}.{path}");
self.get(¶m_id).and_then(|p| p.value_f64)
}
pub fn signal_weights(&self) -> Vec<(String, String, f64)> {
self.list(category::SIGNAL_WEIGHT)
.into_iter()
.filter_map(|p| {
if let Some(rest) = p.param_id.strip_prefix("sw.") {
if let Some(dot_pos) = rest.find('.') {
let dimension = rest[..dot_pos].to_string();
let path = rest[dot_pos + 1..].to_string();
return p.value_f64.map(|v| (dimension, path, v));
}
}
None
})
.collect()
}
pub fn signal_weight_count(&self) -> usize {
self.list(category::SIGNAL_WEIGHT)
.iter()
.filter(|p| p.param_id.starts_with("sw."))
.count()
}
pub fn insert_loss_coefficient(
&mut self,
name: &str,
value: f64,
) -> Result<(), ParamStoreError> {
let param = CognitiveParameter {
param_id: format!("loss.{name}"),
category: category::LOSS_COEFFICIENT.to_string(),
value_f64: Some(value),
value_str: None,
min_bound: Some(0.0),
max_bound: Some(2.0),
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".to_string(),
};
self.insert(¶m)
}
pub fn get_loss_coefficient(&self, name: &str) -> Option<f64> {
let param_id = format!("loss.{name}");
self.get(¶m_id).and_then(|p| p.value_f64)
}
pub fn loss_coefficients(&self) -> Vec<(String, f64)> {
self.list(category::LOSS_COEFFICIENT)
.into_iter()
.filter_map(|p| {
let name = p.param_id.strip_prefix("loss.")?.to_string();
p.value_f64.map(|v| (name, v))
})
.collect()
}
pub fn insert_consolidation_trigger(
&mut self,
name: &str,
value: f64,
) -> Result<(), ParamStoreError> {
let param = CognitiveParameter {
param_id: format!("consol.{name}"),
category: category::CONSOLIDATION_TRIGGER.to_string(),
value_f64: Some(value),
value_str: None,
min_bound: Some(0.0),
max_bound: None,
autonomy_tier: AutonomyTier::BeingApproved,
modified_by: "init".to_string(),
};
self.insert(¶m)
}
pub fn get_consolidation_trigger(&self, name: &str) -> Option<f64> {
let param_id = format!("consol.{name}");
self.get(¶m_id).and_then(|p| p.value_f64)
}
pub fn consolidation_triggers(&self) -> Vec<(String, f64)> {
self.list(category::CONSOLIDATION_TRIGGER)
.into_iter()
.filter_map(|p| {
let name = p.param_id.strip_prefix("consol.")?.to_string();
p.value_f64.map(|v| (name, v))
})
.collect()
}
}
impl Default for CognitiveParameterStore {
fn default() -> Self {
Self::new()
}
}
fn append_row(
batch: &RecordBatch,
param: &CognitiveParameter,
) -> Result<RecordBatch, ParamStoreError> {
let n = batch.num_rows();
if n == 0 {
return Ok(RecordBatch::try_new(
Arc::new(cognitive_params_schema()),
vec![
Arc::new(StringArray::from(vec![param.param_id.as_str()])),
Arc::new(StringArray::from(vec![param.category.as_str()])),
Arc::new(Float64Array::from(vec![param.value_f64])),
Arc::new(StringArray::from(vec![param.value_str.as_deref()])),
Arc::new(Float64Array::from(vec![param.min_bound])),
Arc::new(Float64Array::from(vec![param.max_bound])),
Arc::new(UInt8Array::from(vec![param.autonomy_tier as u8])),
Arc::new(StringArray::from(vec![param.modified_by.as_str()])),
],
)?);
}
let ids = batch
.column(param_col::PARAM_ID)
.as_any()
.downcast_ref::<StringArray>()
.expect("param_id");
let cats = batch
.column(param_col::CATEGORY)
.as_any()
.downcast_ref::<StringArray>()
.expect("category");
let vals = batch
.column(param_col::VALUE_F64)
.as_any()
.downcast_ref::<Float64Array>()
.expect("value_f64");
let strs = batch
.column(param_col::VALUE_STR)
.as_any()
.downcast_ref::<StringArray>()
.expect("value_str");
let mins = batch
.column(param_col::MIN_BOUND)
.as_any()
.downcast_ref::<Float64Array>()
.expect("min_bound");
let maxs = batch
.column(param_col::MAX_BOUND)
.as_any()
.downcast_ref::<Float64Array>()
.expect("max_bound");
let tiers = batch
.column(param_col::AUTONOMY_TIER)
.as_any()
.downcast_ref::<UInt8Array>()
.expect("autonomy_tier");
let mods = batch
.column(param_col::MODIFIED_BY)
.as_any()
.downcast_ref::<StringArray>()
.expect("modified_by");
let mut new_ids: Vec<&str> = (0..n).map(|i| ids.value(i)).collect();
new_ids.push(¶m.param_id);
let mut new_cats: Vec<&str> = (0..n).map(|i| cats.value(i)).collect();
new_cats.push(¶m.category);
let mut new_vals: Vec<Option<f64>> = (0..n)
.map(|i| {
if vals.is_null(i) {
None
} else {
Some(vals.value(i))
}
})
.collect();
new_vals.push(param.value_f64);
let value_str_owned: Vec<Option<String>> = (0..n)
.map(|i| {
if strs.is_null(i) {
None
} else {
Some(strs.value(i).to_string())
}
})
.chain(std::iter::once(param.value_str.clone()))
.collect();
let new_strs: Vec<Option<&str>> = value_str_owned.iter().map(|s| s.as_deref()).collect();
let mut new_mins: Vec<Option<f64>> = (0..n)
.map(|i| {
if mins.is_null(i) {
None
} else {
Some(mins.value(i))
}
})
.collect();
new_mins.push(param.min_bound);
let mut new_maxs: Vec<Option<f64>> = (0..n)
.map(|i| {
if maxs.is_null(i) {
None
} else {
Some(maxs.value(i))
}
})
.collect();
new_maxs.push(param.max_bound);
let mut new_tiers: Vec<u8> = (0..n).map(|i| tiers.value(i)).collect();
new_tiers.push(param.autonomy_tier as u8);
let mut new_mods: Vec<&str> = (0..n).map(|i| mods.value(i)).collect();
new_mods.push(¶m.modified_by);
Ok(RecordBatch::try_new(
Arc::new(cognitive_params_schema()),
vec![
Arc::new(StringArray::from(new_ids)),
Arc::new(StringArray::from(new_cats)),
Arc::new(Float64Array::from(new_vals)),
Arc::new(StringArray::from(new_strs)),
Arc::new(Float64Array::from(new_mins)),
Arc::new(Float64Array::from(new_maxs)),
Arc::new(UInt8Array::from(new_tiers)),
Arc::new(StringArray::from(new_mods)),
],
)?)
}
pub fn default_cognitive_params() -> CognitiveParameterStore {
let mut store = CognitiveParameterStore::new();
let lc = |id: &str, val: f64, min: f64, max: f64| CognitiveParameter {
param_id: format!("learning.{id}"),
category: category::LEARNING_CONFIG.to_string(),
value_f64: Some(val),
value_str: None,
min_bound: Some(min),
max_bound: Some(max),
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".to_string(),
};
let params = vec![
lc("initial_learning_rate", 0.1, 0.001, 1.0),
lc("decayed_learning_rate", 0.01, 0.0001, 0.5),
lc("error_spike_learning_rate", 0.05, 0.001, 0.5),
lc("decay_after_decisions", 100.0, 10.0, 10000.0),
lc("weight_decay_lambda", 0.001, 0.0, 0.1),
lc("max_delta_per_step", 0.1, 0.01, 1.0),
lc("min_weight", -1.5, -10.0, 0.0),
lc("max_weight", 1.5, 0.0, 10.0),
lc("hebbian_lr_multiplier", 0.5, 0.0, 2.0),
lc("error_spike_window", 20.0, 5.0, 100.0),
lc("error_spike_threshold", 0.5, 0.1, 1.0),
];
for p in ¶ms {
store
.insert(p)
.expect("default learning config insert should not fail");
}
let th = |id: &str, val: f64, min: f64, max: f64| CognitiveParameter {
param_id: format!("fusion.{id}"),
category: category::THRESHOLD.to_string(),
value_f64: Some(val),
value_str: None,
min_bound: Some(min),
max_bound: Some(max),
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".to_string(),
};
let thresholds = vec![
th("softmax_temperature", 1.0, 0.01, 10.0),
th("min_signal_strength", 0.01, 0.0, 1.0),
th("refuse_low_coverage_threshold", 0.7, 0.0, 1.0),
];
for p in &thresholds {
store
.insert(p)
.expect("default threshold insert should not fail");
}
let sm_auto = |id: &str, val: f64, min: f64, max: f64| CognitiveParameter {
param_id: format!("schema_match.{id}"),
category: category::THRESHOLD.to_string(),
value_f64: Some(val),
value_str: None,
min_bound: Some(min),
max_bound: Some(max),
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".to_string(),
};
let sm_being = |id: &str, val: f64, min: f64, max: f64| CognitiveParameter {
param_id: format!("schema_match.{id}"),
category: category::THRESHOLD.to_string(),
value_f64: Some(val),
value_str: None,
min_bound: Some(min),
max_bound: Some(max),
autonomy_tier: AutonomyTier::BeingApproved,
modified_by: "init".to_string(),
};
let schema_thresholds = vec![
sm_auto("assimilate_threshold", 0.7, 0.0, 1.0),
sm_auto("accommodate_threshold", 0.3, 0.0, 1.0),
sm_being("novelty_high_threshold", 0.8, 0.0, 1.0),
sm_auto("assimilation_boost", 0.05, 0.0, 0.5),
sm_auto("coverage_saturation", 5.0, 1.0, 50.0),
];
for p in &schema_thresholds {
store
.insert(p)
.expect("default schema threshold insert should not fail");
}
let tr = |id: &str, val: f64, min: f64, max: f64| CognitiveParameter {
param_id: format!("consolidation.{id}"),
category: category::TRIGGER.to_string(),
value_f64: Some(val),
value_str: None,
min_bound: Some(min),
max_bound: Some(max),
autonomy_tier: AutonomyTier::BeingApproved,
modified_by: "init".to_string(),
};
let triggers = vec![
tr("min_triples_for_training", 200.0, 10.0, 10000.0),
tr("consolidation_cycle_interval_hours", 24.0, 1.0, 168.0),
tr("kl_divergence_budget", 0.5, 0.01, 5.0),
];
for p in &triggers {
store
.insert(p)
.expect("default trigger insert should not fail");
}
let sw_store = default_signal_weights();
for p in sw_store.list_all() {
store
.insert(&p)
.expect("default signal weight insert should not fail");
}
store
.insert_loss_coefficient("alpha_lm", 1.0)
.expect("default loss coefficient insert");
store
.insert_loss_coefficient("alpha_rel", 0.1)
.expect("default loss coefficient insert");
store
.insert_loss_coefficient("alpha_causal", 0.05)
.expect("default loss coefficient insert");
store
.insert_loss_coefficient("rel_warmup_fraction", 0.1)
.expect("default loss coefficient insert");
store
.insert_loss_coefficient("causal_warmup_fraction", 0.2)
.expect("default loss coefficient insert");
store
.insert_consolidation_trigger("dedup_threshold", 0.95)
.expect("default consolidation trigger insert");
store
.insert_consolidation_trigger("max_triples_per_cycle", 500.0)
.expect("default consolidation trigger insert");
store
.insert_consolidation_trigger("min_promoted_triples", 50.0)
.expect("default consolidation trigger insert");
store
.insert_consolidation_trigger("training_threshold", 200.0)
.expect("default consolidation trigger insert");
store
.insert_consolidation_trigger("max_pairs_per_batch", 500.0)
.expect("default consolidation trigger insert");
store
}
pub fn default_signal_weights() -> CognitiveParameterStore {
let mut store = CognitiveParameterStore::new();
let mut add = |dim: &str, path: &str, weight: f64| {
store
.insert_signal_weight(dim, path, weight)
.expect("default signal weight insert should not fail");
};
add("fractal_confidence", "FAST", 0.9);
add("fractal_confidence", "STANDARD", 0.3);
add("fractal_confidence", "DEEP", -0.5);
add("fractal_confidence", "REFUSE_LOW_COVERAGE", -0.5);
add("fractal_ambiguity", "FAST", -0.5);
add("fractal_ambiguity", "STANDARD", 0.2);
add("fractal_ambiguity", "DEEP", 0.8);
add("novelty_surprise", "FAST", -0.3);
add("novelty_surprise", "STANDARD", 0.1);
add("novelty_surprise", "DEEP", 0.7);
add("novelty_surprise", "FAST_LEARNING", 0.5);
add("fov_temperature", "FAST", 0.4);
add("fov_temperature", "STANDARD", 0.2);
add("fov_temperature", "DEEP", -0.1);
add("emotion_confusion", "FAST", -0.2);
add("emotion_confusion", "STANDARD", 0.3);
add("emotion_confusion", "DEEP", 0.5);
add("state_importance", "FAST", -0.1);
add("state_importance", "STANDARD", 0.2);
add("state_importance", "DEEP", 0.5);
add("is_document", "TRAINING", 1.0);
add("urgency", "FAST_LEARNING", 0.8);
add("provenance_support", "FAST", 0.8);
add("provenance_support", "STANDARD", 0.2);
add("provenance_support", "DEEP", -0.3);
add("provenance_coverage", "FAST", 0.5);
add("provenance_coverage", "STANDARD", 0.3);
add("provenance_coverage", "DEEP", -0.2);
add("provenance_coverage", "FAST_LEARNING", -0.4);
add("action_pending", "FAST", -0.2);
add("action_pending", "STANDARD", 0.4);
add("action_pending", "DEEP", 0.3);
add("action_intent_level", "FAST", 0.6);
add("action_intent_level", "STANDARD", 0.1);
add("action_intent_level", "DEEP", -0.3);
add("action_coverage", "FAST", 0.5);
add("action_coverage", "STANDARD", 0.2);
add("action_coverage", "DEEP", -0.2);
add("kbdd_coverage", "FAST", 0.7);
add("kbdd_coverage", "STANDARD", 0.1);
add("kbdd_coverage", "DEEP", -0.4);
add("kbdd_coverage", "FAST_LEARNING", -0.3);
add("kbdd_gap_density", "FAST", -0.5);
add("kbdd_gap_density", "STANDARD", 0.2);
add("kbdd_gap_density", "DEEP", 0.6);
add("kbdd_gap_density", "TRAINING", 0.4);
add("kbdd_gap_density", "CRYSTALLIZE", 0.6);
add("query_complexity", "FAST", -0.4);
add("query_complexity", "STANDARD", 0.2);
add("query_complexity", "DEEP", 0.6);
add("query_expected_depth", "FAST", -0.3);
add("query_expected_depth", "STANDARD", 0.1);
add("query_expected_depth", "DEEP", 0.5);
add("query_domain_match", "FAST", 0.3);
add("query_domain_match", "STANDARD", 0.1);
add("query_domain_match", "REFUSE_LOW_COVERAGE", -0.3);
add("entity_gap", "REFUSE_LOW_COVERAGE", 0.9);
add("entity_gap", "CRYSTALLIZE", 0.3);
add("entity_gap", "FAST", -0.4);
add("entity_gap", "STANDARD", -0.2);
add("coverage_gap", "REFUSE_LOW_COVERAGE", 0.7);
add("coverage_gap", "CRYSTALLIZE", 0.4);
add("coverage_gap", "FAST", -0.3);
add("prose_available", "CRYSTALLIZE", 0.8);
add("prose_available", "REFUSE_LOW_COVERAGE", -0.7);
add("entity_grounding", "FAST", 0.3);
add("entity_grounding", "REFUSE_LOW_COVERAGE", -0.5);
add("competency_match", "FAST", 0.2);
add("competency_match", "STANDARD", 0.3);
add("competency_match", "REFUSE_LOW_COVERAGE", -0.6);
add("competency_expected_quality", "FAST", 0.3);
add("competency_expected_quality", "STANDARD", 0.1);
add("competency_expected_quality", "DEEP", -0.2);
add("pattern_applicability", "FAST", -0.3);
add("pattern_applicability", "STANDARD", 0.1);
add("pattern_applicability", "DEEP", 0.5);
add("pattern_structured_reasoning", "FAST", -0.2);
add("pattern_structured_reasoning", "DEEP", 0.4);
add("goal_alignment", "FAST", -0.1);
add("goal_alignment", "STANDARD", 0.4);
add("goal_alignment", "REFUSE_LOW_COVERAGE", -0.3);
add("goal_response_priority", "STANDARD", 0.3);
add("goal_response_priority", "DEEP", 0.2);
add("conv_turn_depth", "STANDARD", 0.2);
add("conv_turn_depth", "DEEP", 0.3);
add("conv_entity_continuity", "FAST", 0.3);
add("conv_entity_continuity", "STANDARD", 0.1);
add("conv_entity_continuity", "REFUSE_LOW_COVERAGE", -0.3);
add("conv_topic_repetition", "DEEP", 0.4);
add("conv_topic_repetition", "STANDARD", 0.1);
add("conv_is_followup", "STANDARD", 0.3);
add("conv_is_followup", "FAST", 0.1);
add("conv_is_followup", "REFUSE_LOW_COVERAGE", -0.3);
add("growth_depth", "FAST", -0.3);
add("growth_depth", "STANDARD", 0.2);
add("growth_depth", "DEEP", 0.7);
add("growth_richness", "FAST", -0.2);
add("growth_richness", "STANDARD", 0.4);
add("growth_richness", "DEEP", 0.5);
add("growth_layers", "FAST", -0.1);
add("growth_layers", "STANDARD", 0.5);
add("growth_layers", "DEEP", 0.3);
add("growth_connections", "FAST", -0.2);
add("growth_connections", "STANDARD", 0.3);
add("growth_connections", "DEEP", 0.5);
add("llm_explore_confidence", "STANDARD", 0.3);
add("llm_explore_confidence", "DEEP", 0.4);
add("llm_explore_confidence", "CRYSTALLIZE", 0.3);
add("llm_structure_signal", "STANDARD", 0.2);
add("llm_structure_signal", "DEEP", 0.5);
add("llm_domain_signal", "STANDARD", 0.3);
add("llm_domain_signal", "DEEP", 0.3);
add("llm_relationship_richness", "STANDARD", 0.3);
add("llm_relationship_richness", "DEEP", 0.4);
add("llm_expansion_signal", "STANDARD", 0.2);
add("llm_expansion_signal", "DEEP", 0.3);
add("llm_expansion_signal", "CRYSTALLIZE", 0.2);
add("llm_unknown_structure", "DEEP", 0.5);
add("llm_unknown_structure", "CRYSTALLIZE", 0.4);
add("schema_match", "FAST", 0.5);
add("schema_match", "STANDARD", 0.2);
add("schema_match", "DEEP", -0.3);
add("schema_match", "REFUSE_LOW_COVERAGE", -0.3);
add("schema_novelty", "FAST", -0.4);
add("schema_novelty", "STANDARD", 0.1);
add("schema_novelty", "DEEP", 0.6);
add("schema_novelty", "FAST_LEARNING", 0.4);
add("schema_novelty", "TRAINING", 0.3);
add("tool_needed", "TOOL_USE", 0.9);
add("tool_needed", "FAST", -0.2);
add("tool_needed", "STANDARD", -0.1);
add("tool_needed", "REFUSE_LOW_COVERAGE", -0.7);
add("tool_computation", "TOOL_USE", 0.7);
add("tool_computation", "DEEP", -0.3);
add("tool_action", "TOOL_USE", 0.6);
add("tool_match", "TOOL_USE", 0.8);
add("tool_match", "REFUSE_LOW_COVERAGE", -0.5);
add("tool_count", "TOOL_USE", 0.3);
add("tool_auto_approve", "TOOL_USE", 0.4);
add("tool_auto_approve", "FAST", 0.2);
add("tool_safety_score", "TOOL_USE", 0.5);
add("tool_sandbox_ok", "TOOL_USE", 0.3);
add("embedding_computation", "TOOL_USE", 0.8);
add("embedding_computation", "FAST", -0.2);
add("embedding_computation", "REFUSE_LOW_COVERAGE", -0.5);
add("embedding_mean_similarity", "TOOL_USE", 0.3);
add("embedding_tool_match", "TOOL_USE", 0.8);
add("embedding_tool_match", "FAST", -0.2);
add("embedding_tool_match", "REFUSE_LOW_COVERAGE", -0.5);
add("embedding_tool_mean", "TOOL_USE", 0.3);
add("recency_weight", "FAST", 0.3);
add("recency_weight", "STANDARD", 0.1);
add("wm_repetition", "FAST", 0.4);
add("wm_repetition", "STANDARD", -0.1);
add("wm_topic_continuity", "STANDARD", 0.3);
add("wm_topic_continuity", "DEEP", 0.2);
add("calibration_accuracy", "FAST", 0.6);
add("calibration_accuracy", "STANDARD", 0.4);
add("calibration_accuracy", "REFUSE_LOW_COVERAGE", -2.5);
add("calibration_confidence", "FAST", 0.3);
add("calibration_confidence", "DEEP", -0.2);
add("calibration_confidence", "REFUSE_LOW_COVERAGE", -0.6);
add("certification_gap", "FAST", -0.5);
add("certification_gap", "STANDARD", 0.2);
add("certification_gap", "REFUSE_LOW_COVERAGE", 0.3);
add("calibration_maturity", "FAST", 0.2);
add("calibration_maturity", "REFUSE_LOW_COVERAGE", -0.3);
store
}
pub fn save_params_to_parquet(
store: &CognitiveParameterStore,
path: &std::path::Path,
) -> Result<(), ParamStoreError> {
use parquet::arrow::ArrowWriter;
use std::fs;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).map_err(|e| {
ParamStoreError::InvalidSchema(format!("failed to create directory: {e}"))
})?;
}
let file = fs::File::create(path)
.map_err(|e| ParamStoreError::InvalidSchema(format!("failed to create file: {e}")))?;
let batch = store.snapshot();
let schema = Arc::new(cognitive_params_schema());
let mut writer = ArrowWriter::try_new(file, schema, None)?;
if batch.num_rows() > 0 {
writer.write(batch)?;
}
writer.close()?;
Ok(())
}
pub fn load_params_from_parquet(
path: &std::path::Path,
) -> Result<Option<CognitiveParameterStore>, ParamStoreError> {
use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
if !path.exists() {
return Ok(None);
}
let file = std::fs::File::open(path)
.map_err(|e| ParamStoreError::InvalidSchema(format!("failed to open file: {e}")))?;
let reader = ParquetRecordBatchReaderBuilder::try_new(file)?.build()?;
let mut batches: Vec<RecordBatch> = Vec::new();
for batch_result in reader {
batches.push(batch_result?);
}
if batches.is_empty() {
return Ok(Some(CognitiveParameterStore::new()));
}
if batches.len() == 1 {
return Ok(Some(CognitiveParameterStore::from_batch(
batches.into_iter().next().unwrap(),
)?));
}
let schema = batches[0].schema();
let combined = arrow::compute::concat_batches(&schema, &batches)?;
Ok(Some(CognitiveParameterStore::from_batch(combined)?))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_schema_has_correct_columns() {
let schema = cognitive_params_schema();
assert_eq!(schema.fields().len(), 8);
assert_eq!(schema.field(param_col::PARAM_ID).name(), "param_id");
assert_eq!(schema.field(param_col::CATEGORY).name(), "category");
assert_eq!(schema.field(param_col::VALUE_F64).name(), "value_f64");
assert_eq!(schema.field(param_col::VALUE_STR).name(), "value_str");
assert_eq!(schema.field(param_col::MIN_BOUND).name(), "min_bound");
assert_eq!(schema.field(param_col::MAX_BOUND).name(), "max_bound");
assert_eq!(
schema.field(param_col::AUTONOMY_TIER).name(),
"autonomy_tier"
);
assert_eq!(schema.field(param_col::MODIFIED_BY).name(), "modified_by");
}
#[test]
fn test_empty_store() {
let store = CognitiveParameterStore::new();
assert!(store.is_empty());
assert_eq!(store.len(), 0);
assert!(store.get("anything").is_none());
assert!(store.list(category::SIGNAL_WEIGHT).is_empty());
}
#[test]
fn test_insert_and_get() {
let mut store = CognitiveParameterStore::new();
let param = CognitiveParameter {
param_id: "signal.novelty.weight".into(),
category: category::SIGNAL_WEIGHT.into(),
value_f64: Some(0.15),
value_str: None,
min_bound: Some(0.0),
max_bound: Some(1.0),
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".into(),
};
store.insert(¶m).unwrap();
assert_eq!(store.len(), 1);
let got = store.get("signal.novelty.weight").unwrap();
assert_eq!(got.value_f64, Some(0.15));
assert_eq!(got.category, category::SIGNAL_WEIGHT);
assert_eq!(got.autonomy_tier, AutonomyTier::Auto);
}
#[test]
fn test_insert_replaces_existing() {
let mut store = CognitiveParameterStore::new();
let p1 = CognitiveParameter {
param_id: "test.param".into(),
category: category::THRESHOLD.into(),
value_f64: Some(1.0),
value_str: None,
min_bound: None,
max_bound: None,
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".into(),
};
store.insert(&p1).unwrap();
assert_eq!(store.len(), 1);
let p2 = CognitiveParameter {
value_f64: Some(2.0),
modified_by: "hdd_loop".into(),
..p1.clone()
};
store.insert(&p2).unwrap();
assert_eq!(store.len(), 1);
assert_eq!(store.get("test.param").unwrap().value_f64, Some(2.0));
assert_eq!(store.get("test.param").unwrap().modified_by, "hdd_loop");
}
#[test]
fn test_set_with_bounds_checking() {
let mut store = CognitiveParameterStore::new();
let param = CognitiveParameter {
param_id: "bounded.param".into(),
category: category::THRESHOLD.into(),
value_f64: Some(0.5),
value_str: None,
min_bound: Some(0.0),
max_bound: Some(1.0),
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".into(),
};
store.insert(¶m).unwrap();
store
.set("bounded.param", 0.8, "hdd_loop", AutonomyTier::Auto)
.unwrap();
assert_eq!(store.get("bounded.param").unwrap().value_f64, Some(0.8));
let err = store
.set("bounded.param", 1.5, "hdd_loop", AutonomyTier::Auto)
.unwrap_err();
assert!(matches!(err, ParamStoreError::OutOfBounds { .. }));
let err = store
.set("bounded.param", -0.1, "hdd_loop", AutonomyTier::Auto)
.unwrap_err();
assert!(matches!(err, ParamStoreError::OutOfBounds { .. }));
assert_eq!(store.get("bounded.param").unwrap().value_f64, Some(0.8));
}
#[test]
fn test_set_not_found() {
let mut store = CognitiveParameterStore::new();
let err = store
.set("nonexistent", 1.0, "test", AutonomyTier::Auto)
.unwrap_err();
assert!(matches!(err, ParamStoreError::NotFound(_)));
}
#[test]
fn test_autonomy_tier_enforcement() {
let mut store = CognitiveParameterStore::new();
let param = CognitiveParameter {
param_id: "captain.safety_rule".into(),
category: category::THRESHOLD.into(),
value_f64: Some(0.5),
value_str: None,
min_bound: Some(0.0),
max_bound: Some(1.0),
autonomy_tier: AutonomyTier::CaptainOnly,
modified_by: "init".into(),
};
store.insert(¶m).unwrap();
let err = store
.set("captain.safety_rule", 0.8, "hdd_loop", AutonomyTier::Auto)
.unwrap_err();
assert!(matches!(err, ParamStoreError::TierViolation { .. }));
let err = store
.set(
"captain.safety_rule",
0.8,
"being",
AutonomyTier::BeingApproved,
)
.unwrap_err();
assert!(matches!(err, ParamStoreError::TierViolation { .. }));
store
.set(
"captain.safety_rule",
0.8,
"captain",
AutonomyTier::CaptainOnly,
)
.unwrap();
assert_eq!(
store.get("captain.safety_rule").unwrap().value_f64,
Some(0.8)
);
}
#[test]
fn test_list_by_category() {
let mut store = CognitiveParameterStore::new();
let sw = |id: &str, val: f64| CognitiveParameter {
param_id: id.into(),
category: category::SIGNAL_WEIGHT.into(),
value_f64: Some(val),
value_str: None,
min_bound: None,
max_bound: None,
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".into(),
};
store.insert(&sw("w1", 0.1)).unwrap();
store.insert(&sw("w2", 0.2)).unwrap();
store
.insert(&CognitiveParameter {
param_id: "t1".into(),
category: category::THRESHOLD.into(),
value_f64: Some(0.5),
value_str: None,
min_bound: None,
max_bound: None,
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".into(),
})
.unwrap();
let weights = store.list(category::SIGNAL_WEIGHT);
assert_eq!(weights.len(), 2);
let thresholds = store.list(category::THRESHOLD);
assert_eq!(thresholds.len(), 1);
let all = store.list_all();
assert_eq!(all.len(), 3);
}
#[test]
fn test_default_params_populated() {
let store = default_cognitive_params();
assert!(!store.is_empty());
let lr = store.get("learning.initial_learning_rate").unwrap();
assert_eq!(lr.value_f64, Some(0.1));
assert_eq!(lr.category, category::LEARNING_CONFIG);
assert_eq!(lr.autonomy_tier, AutonomyTier::Auto);
assert_eq!(lr.min_bound, Some(0.001));
assert_eq!(lr.max_bound, Some(1.0));
let softmax = store.get("fusion.softmax_temperature").unwrap();
assert_eq!(softmax.value_f64, Some(1.0));
let assim = store.get("schema_match.assimilate_threshold").unwrap();
assert_eq!(assim.autonomy_tier, AutonomyTier::Auto);
let novelty = store.get("schema_match.novelty_high_threshold").unwrap();
assert_eq!(novelty.autonomy_tier, AutonomyTier::BeingApproved);
let min_triples = store.get("consolidation.min_triples_for_training").unwrap();
assert_eq!(min_triples.value_f64, Some(200.0));
assert_eq!(min_triples.autonomy_tier, AutonomyTier::BeingApproved);
}
#[test]
fn test_default_params_bounds_enforced() {
let mut store = default_cognitive_params();
let err = store
.set(
"learning.initial_learning_rate",
5.0,
"test",
AutonomyTier::Auto,
)
.unwrap_err();
assert!(matches!(err, ParamStoreError::OutOfBounds { .. }));
store
.set(
"learning.initial_learning_rate",
0.5,
"hdd_loop",
AutonomyTier::Auto,
)
.unwrap();
assert_eq!(
store
.get("learning.initial_learning_rate")
.unwrap()
.value_f64,
Some(0.5)
);
}
#[test]
fn test_from_batch_validates_schema() {
let bad_schema = Arc::new(Schema::new(vec![
Field::new("a", DataType::Utf8, false),
Field::new("b", DataType::Utf8, false),
]));
let bad_batch = RecordBatch::try_new(
bad_schema,
vec![
Arc::new(StringArray::from(vec!["x"])),
Arc::new(StringArray::from(vec!["y"])),
],
)
.unwrap();
let err = CognitiveParameterStore::from_batch(bad_batch).unwrap_err();
assert!(matches!(err, ParamStoreError::InvalidSchema(_)));
}
#[test]
fn test_snapshot_roundtrip() {
let mut store = CognitiveParameterStore::new();
let param = CognitiveParameter {
param_id: "test.roundtrip".into(),
category: category::THRESHOLD.into(),
value_f64: Some(0.42),
value_str: Some("test_value".into()),
min_bound: Some(0.0),
max_bound: Some(1.0),
autonomy_tier: AutonomyTier::BeingApproved,
modified_by: "test".into(),
};
store.insert(¶m).unwrap();
let batch = store.snapshot().clone();
let store2 = CognitiveParameterStore::from_batch(batch).unwrap();
let got = store2.get("test.roundtrip").unwrap();
assert_eq!(got.value_f64, Some(0.42));
assert_eq!(got.value_str.as_deref(), Some("test_value"));
assert_eq!(got.autonomy_tier, AutonomyTier::BeingApproved);
}
#[test]
fn test_parquet_roundtrip() {
let store = default_cognitive_params();
let original_count = store.len();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("self_params.parquet");
save_params_to_parquet(&store, &path).unwrap();
assert!(path.exists());
let loaded = load_params_from_parquet(&path).unwrap().unwrap();
assert_eq!(loaded.len(), original_count);
let lr = loaded.get("learning.initial_learning_rate").unwrap();
assert_eq!(lr.value_f64, Some(0.1));
assert_eq!(lr.min_bound, Some(0.001));
assert_eq!(lr.autonomy_tier, AutonomyTier::Auto);
}
#[test]
fn test_parquet_load_missing_file() {
let result =
load_params_from_parquet(std::path::Path::new("/nonexistent/params.parquet")).unwrap();
assert!(result.is_none());
}
#[test]
fn test_value_str_parameter() {
let mut store = CognitiveParameterStore::new();
let param = CognitiveParameter {
param_id: "fusion.default_path".into(),
category: category::THRESHOLD.into(),
value_f64: None,
value_str: Some("STANDARD".into()),
min_bound: None,
max_bound: None,
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".into(),
};
store.insert(¶m).unwrap();
let got = store.get("fusion.default_path").unwrap();
assert!(got.value_f64.is_none());
assert_eq!(got.value_str.as_deref(), Some("STANDARD"));
}
#[test]
fn test_autonomy_tier_ordering() {
assert!(AutonomyTier::Auto < AutonomyTier::BeingApproved);
assert!(AutonomyTier::BeingApproved < AutonomyTier::CaptainOnly);
}
#[test]
fn test_set_updates_modified_by() {
let mut store = CognitiveParameterStore::new();
let param = CognitiveParameter {
param_id: "track.modifier".into(),
category: category::THRESHOLD.into(),
value_f64: Some(0.5),
value_str: None,
min_bound: Some(0.0),
max_bound: Some(1.0),
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".into(),
};
store.insert(¶m).unwrap();
assert_eq!(store.get("track.modifier").unwrap().modified_by, "init");
store
.set("track.modifier", 0.7, "hdd_loop_v2", AutonomyTier::Auto)
.unwrap();
assert_eq!(
store.get("track.modifier").unwrap().modified_by,
"hdd_loop_v2"
);
}
#[test]
fn test_unbounded_param_accepts_any_value() {
let mut store = CognitiveParameterStore::new();
let param = CognitiveParameter {
param_id: "unbounded.param".into(),
category: category::THRESHOLD.into(),
value_f64: Some(0.0),
value_str: None,
min_bound: None,
max_bound: None,
autonomy_tier: AutonomyTier::Auto,
modified_by: "init".into(),
};
store.insert(¶m).unwrap();
store
.set("unbounded.param", 999.0, "test", AutonomyTier::Auto)
.unwrap();
assert_eq!(store.get("unbounded.param").unwrap().value_f64, Some(999.0));
store
.set("unbounded.param", -999.0, "test", AutonomyTier::Auto)
.unwrap();
assert_eq!(
store.get("unbounded.param").unwrap().value_f64,
Some(-999.0)
);
}
#[test]
fn test_insert_signal_weight() {
let mut store = CognitiveParameterStore::new();
store
.insert_signal_weight("fractal_confidence", "FAST", 0.9)
.unwrap();
let val = store.get_signal_weight("fractal_confidence", "FAST");
assert_eq!(val, Some(0.9));
let param = store.get("sw.fractal_confidence.FAST").unwrap();
assert_eq!(param.category, category::SIGNAL_WEIGHT);
assert_eq!(param.min_bound, Some(-1.5));
assert_eq!(param.max_bound, Some(1.5));
assert_eq!(param.autonomy_tier, AutonomyTier::Auto);
}
#[test]
fn test_signal_weights_list() {
let mut store = CognitiveParameterStore::new();
store
.insert_signal_weight("fractal_confidence", "FAST", 0.9)
.unwrap();
store
.insert_signal_weight("fractal_confidence", "DEEP", -0.5)
.unwrap();
store
.insert_signal_weight("novelty_surprise", "STANDARD", 0.1)
.unwrap();
let weights = store.signal_weights();
assert_eq!(weights.len(), 3);
assert_eq!(store.signal_weight_count(), 3);
let fractal_fast = weights
.iter()
.find(|(d, p, _)| d == "fractal_confidence" && p == "FAST");
assert!(fractal_fast.is_some());
assert!((fractal_fast.unwrap().2 - 0.9).abs() < 1e-10);
}
#[test]
fn test_default_signal_weights() {
let store = default_signal_weights();
let count = store.signal_weight_count();
assert!(
count >= 140,
"should have >= 140 signal weight entries, got {}",
count
);
assert_eq!(
store.get_signal_weight("fractal_confidence", "FAST"),
Some(0.9)
);
assert_eq!(
store.get_signal_weight("fractal_confidence", "DEEP"),
Some(-0.5)
);
assert_eq!(
store.get_signal_weight("tool_needed", "TOOL_USE"),
Some(0.9)
);
assert_eq!(store.get_signal_weight("recency_weight", "FAST"), Some(0.3));
}
#[test]
fn test_default_params_includes_signal_weights() {
let store = default_cognitive_params();
let sw_count = store.signal_weight_count();
assert!(
sw_count >= 140,
"default_cognitive_params should include signal weights, got {}",
sw_count
);
let lr = store.get("learning.initial_learning_rate");
assert!(lr.is_some());
let sw = store.get_signal_weight("fractal_confidence", "FAST");
assert_eq!(sw, Some(0.9));
}
#[test]
fn test_signal_weight_bounds_enforced() {
let mut store = CognitiveParameterStore::new();
store.insert_signal_weight("test_dim", "FAST", 2.0).unwrap();
assert_eq!(
store.get_signal_weight("test_dim", "FAST"),
Some(1.5),
"value above 1.5 should be clipped"
);
store
.insert_signal_weight("test_dim2", "FAST", -3.0)
.unwrap();
assert_eq!(
store.get_signal_weight("test_dim2", "FAST"),
Some(-1.5),
"value below -1.5 should be clipped"
);
let err = store
.set("sw.test_dim.FAST", 2.0, "test", AutonomyTier::Auto)
.unwrap_err();
assert!(matches!(err, ParamStoreError::OutOfBounds { .. }));
}
#[test]
fn test_signal_weight_parquet_roundtrip() {
let store = default_signal_weights();
let original_count = store.signal_weight_count();
assert!(original_count > 0);
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("signal_weights.parquet");
save_params_to_parquet(&store, &path).unwrap();
assert!(path.exists());
let loaded = load_params_from_parquet(&path).unwrap().unwrap();
assert_eq!(loaded.signal_weight_count(), original_count);
assert_eq!(
loaded.get_signal_weight("fractal_confidence", "FAST"),
Some(0.9)
);
assert_eq!(
loaded.get_signal_weight("tool_needed", "TOOL_USE"),
Some(0.9)
);
}
#[test]
fn test_insert_loss_coefficient() {
let mut store = CognitiveParameterStore::new();
store.insert_loss_coefficient("alpha_lm", 1.0).unwrap();
assert_eq!(store.get_loss_coefficient("alpha_lm"), Some(1.0));
}
#[test]
fn test_loss_coefficients_list() {
let mut store = CognitiveParameterStore::new();
store.insert_loss_coefficient("alpha_lm", 1.0).unwrap();
store.insert_loss_coefficient("alpha_rel", 0.1).unwrap();
store.insert_loss_coefficient("alpha_causal", 0.05).unwrap();
let coeffs = store.loss_coefficients();
assert_eq!(coeffs.len(), 3);
}
#[test]
fn test_default_loss_coefficients_present() {
let store = default_cognitive_params();
assert_eq!(store.get_loss_coefficient("alpha_lm"), Some(1.0));
assert_eq!(store.get_loss_coefficient("alpha_rel"), Some(0.1));
assert_eq!(store.get_loss_coefficient("alpha_causal"), Some(0.05));
assert_eq!(store.get_loss_coefficient("rel_warmup_fraction"), Some(0.1));
assert_eq!(
store.get_loss_coefficient("causal_warmup_fraction"),
Some(0.2)
);
}
#[test]
fn test_loss_coefficients_are_tier_auto() {
let store = default_cognitive_params();
let param = store.get("loss.alpha_lm").unwrap();
assert_eq!(param.autonomy_tier, AutonomyTier::Auto);
}
#[test]
fn test_insert_consolidation_trigger() {
let mut store = CognitiveParameterStore::new();
store
.insert_consolidation_trigger("dedup_threshold", 0.95)
.unwrap();
assert_eq!(
store.get_consolidation_trigger("dedup_threshold"),
Some(0.95)
);
}
#[test]
fn test_consolidation_triggers_list() {
let mut store = CognitiveParameterStore::new();
store
.insert_consolidation_trigger("dedup_threshold", 0.95)
.unwrap();
store
.insert_consolidation_trigger("max_triples_per_cycle", 500.0)
.unwrap();
let triggers = store.consolidation_triggers();
assert_eq!(triggers.len(), 2);
}
#[test]
fn test_default_consolidation_triggers_present() {
let store = default_cognitive_params();
assert_eq!(
store.get_consolidation_trigger("dedup_threshold"),
Some(0.95)
);
assert_eq!(
store.get_consolidation_trigger("max_triples_per_cycle"),
Some(500.0)
);
assert_eq!(
store.get_consolidation_trigger("min_promoted_triples"),
Some(50.0)
);
assert_eq!(
store.get_consolidation_trigger("training_threshold"),
Some(200.0)
);
}
#[test]
fn test_consolidation_triggers_are_tier_being_approved() {
let store = default_cognitive_params();
let param = store.get("consol.dedup_threshold").unwrap();
assert_eq!(param.autonomy_tier, AutonomyTier::BeingApproved);
}
#[test]
fn test_loss_and_consolidation_parquet_roundtrip() {
let store = default_cognitive_params();
let original_loss_count = store.loss_coefficients().len();
let original_trigger_count = store.consolidation_triggers().len();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("params.parquet");
save_params_to_parquet(&store, &path).unwrap();
let loaded = load_params_from_parquet(&path).unwrap().unwrap();
assert_eq!(loaded.loss_coefficients().len(), original_loss_count);
assert_eq!(
loaded.consolidation_triggers().len(),
original_trigger_count
);
assert_eq!(loaded.get_loss_coefficient("alpha_lm"), Some(1.0));
assert_eq!(
loaded.get_consolidation_trigger("dedup_threshold"),
Some(0.95)
);
}
}