use serde_json::Value as JsonValue;
use std::fmt;
use crate::emcp::EpistemicTaint;
use crate::store::filter::SqlValue;
use crate::store::postgres_backend::StoreRow;
pub const CONFIDENCE_COLUMN: &str = "_confidence";
fn parse_confidence_str(s: &str) -> Option<f64> {
s.trim().parse::<f64>().ok().filter(|f| f.is_finite())
}
pub fn confidence_of_json(value: &JsonValue) -> Option<f64> {
match value {
JsonValue::Number(n) => n.as_f64().filter(|f| f.is_finite()),
JsonValue::String(s) => parse_confidence_str(s),
_ => None,
}
}
pub fn confidence_of_sql(value: &SqlValue) -> Option<f64> {
match value {
SqlValue::Float(f) => f.is_finite().then_some(*f),
SqlValue::Integer(n) => Some(*n as f64),
SqlValue::Text(s) => parse_confidence_str(s),
SqlValue::Boolean(_) | SqlValue::Null => None,
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RetrievedRow {
pub row: StoreRow,
pub taint: EpistemicTaint,
pub confidence: Option<f64>,
}
pub fn mark_retrieved(rows: Vec<StoreRow>) -> Vec<RetrievedRow> {
rows.into_iter()
.map(|row| {
let confidence =
row.get(CONFIDENCE_COLUMN).and_then(confidence_of_json);
RetrievedRow {
row,
taint: EpistemicTaint::Untrusted,
confidence,
}
})
.collect()
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct FloorOutcome {
pub trusted: Vec<RetrievedRow>,
pub below_floor: Vec<RetrievedRow>,
}
pub fn enforce_retrieve_floor(
rows: Vec<RetrievedRow>,
floor: Option<f64>,
) -> FloorOutcome {
match floor {
None => FloorOutcome {
trusted: rows,
below_floor: Vec::new(),
},
Some(f) => {
let mut outcome = FloorOutcome::default();
for r in rows {
if r.confidence.unwrap_or(0.0) >= f {
outcome.trusted.push(r);
} else {
outcome.below_floor.push(r);
}
}
outcome
}
}
}
pub fn retrieve_envelope(outcome: &FloorOutcome, floor: Option<f64>) -> JsonValue {
let rows: Vec<JsonValue> =
outcome.trusted.iter().map(|r| r.row.to_json()).collect();
serde_json::json!({
"taint": EpistemicTaint::Untrusted.as_str(),
"confidence_floor": floor,
"trusted_rows": outcome.trusted.len(),
"below_floor_filtered": outcome.below_floor.len(),
"rows": rows,
})
}
#[derive(Debug, Clone, PartialEq)]
pub enum EpistemicError {
UnelevatedWrite { store: String, floor: f64 },
SubFloorWrite { store: String, confidence: f64, floor: f64 },
MalformedConfidence { store: String, value: String },
}
impl fmt::Display for EpistemicError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EpistemicError::UnelevatedWrite { store, floor } => write!(
f,
"persist into axonstore `{store}` blocked: the store \
declares confidence_floor {floor} but the row carries \
no `{CONFIDENCE_COLUMN}` value — an un-elevated (⊥) \
write. Elevate the value before persisting."
),
EpistemicError::SubFloorWrite { store, confidence, floor } => {
write!(
f,
"persist into axonstore `{store}` blocked: row \
confidence {confidence} is below the store's \
confidence_floor {floor}"
)
}
EpistemicError::MalformedConfidence { store, value } => write!(
f,
"persist into axonstore `{store}` blocked: the \
`{CONFIDENCE_COLUMN}` value `{value}` is not a number"
),
}
}
}
impl std::error::Error for EpistemicError {}
pub fn enforce_persist_floor(
row: &[(String, SqlValue)],
floor: Option<f64>,
store_name: &str,
) -> Result<(), EpistemicError> {
let Some(f) = floor else {
return Ok(());
};
let Some((_, value)) = row.iter().find(|(k, _)| k == CONFIDENCE_COLUMN)
else {
return Err(EpistemicError::UnelevatedWrite {
store: store_name.to_string(),
floor: f,
});
};
let Some(confidence) = confidence_of_sql(value) else {
return Err(EpistemicError::MalformedConfidence {
store: store_name.to_string(),
value: format!("{value:?}"),
});
};
if confidence < f {
return Err(EpistemicError::SubFloorWrite {
store: store_name.to_string(),
confidence,
floor: f,
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn row(pairs: &[(&str, JsonValue)]) -> StoreRow {
StoreRow {
columns: pairs
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect(),
}
}
#[test]
fn confidence_of_json_handles_number_and_numeric_string() {
assert_eq!(confidence_of_json(&serde_json::json!(0.75)), Some(0.75));
assert_eq!(confidence_of_json(&serde_json::json!(1)), Some(1.0));
assert_eq!(
confidence_of_json(&JsonValue::String("0.9".into())),
Some(0.9)
);
assert_eq!(
confidence_of_json(&JsonValue::String(" 0.42 ".into())),
Some(0.42)
);
}
#[test]
fn confidence_of_json_rejects_non_numeric() {
assert_eq!(confidence_of_json(&JsonValue::Null), None);
assert_eq!(confidence_of_json(&JsonValue::Bool(true)), None);
assert_eq!(confidence_of_json(&JsonValue::String("high".into())), None);
}
#[test]
fn confidence_of_sql_handles_each_variant() {
assert_eq!(confidence_of_sql(&SqlValue::Float(0.6)), Some(0.6));
assert_eq!(confidence_of_sql(&SqlValue::Integer(1)), Some(1.0));
assert_eq!(
confidence_of_sql(&SqlValue::Text("0.85".into())),
Some(0.85)
);
assert_eq!(confidence_of_sql(&SqlValue::Text("nope".into())), None);
assert_eq!(confidence_of_sql(&SqlValue::Boolean(true)), None);
assert_eq!(confidence_of_sql(&SqlValue::Null), None);
}
#[test]
fn every_retrieved_row_is_born_untrusted() {
let rows = vec![
row(&[("id", serde_json::json!(1))]),
row(&[("id", serde_json::json!(2)), ("_confidence", serde_json::json!(0.9))]),
];
let marked = mark_retrieved(rows);
assert_eq!(marked.len(), 2);
for r in &marked {
assert_eq!(r.taint, EpistemicTaint::Untrusted);
}
assert_eq!(marked[0].confidence, None);
assert_eq!(marked[1].confidence, Some(0.9));
}
#[test]
fn no_floor_keeps_every_row_trusted() {
let marked = mark_retrieved(vec![
row(&[("id", serde_json::json!(1))]),
row(&[("id", serde_json::json!(2))]),
]);
let outcome = enforce_retrieve_floor(marked, None);
assert_eq!(outcome.trusted.len(), 2);
assert!(outcome.below_floor.is_empty());
}
#[test]
fn floor_partitions_rows_by_stored_confidence() {
let marked = mark_retrieved(vec![
row(&[("id", serde_json::json!(1)), ("_confidence", serde_json::json!(0.95))]),
row(&[("id", serde_json::json!(2)), ("_confidence", serde_json::json!(0.50))]),
row(&[("id", serde_json::json!(3)), ("_confidence", serde_json::json!(0.80))]),
]);
let outcome = enforce_retrieve_floor(marked, Some(0.8));
assert_eq!(outcome.trusted.len(), 2);
assert_eq!(outcome.below_floor.len(), 1);
assert_eq!(outcome.below_floor[0].confidence, Some(0.50));
}
#[test]
fn a_row_with_no_confidence_is_below_any_positive_floor() {
let marked = mark_retrieved(vec![row(&[("id", serde_json::json!(1))])]);
let outcome = enforce_retrieve_floor(marked, Some(0.01));
assert!(outcome.trusted.is_empty());
assert_eq!(outcome.below_floor.len(), 1);
}
#[test]
fn envelope_carries_taint_and_filter_counts() {
let marked = mark_retrieved(vec![
row(&[("id", serde_json::json!(1)), ("_confidence", serde_json::json!(0.9))]),
row(&[("id", serde_json::json!(2)), ("_confidence", serde_json::json!(0.3))]),
]);
let outcome = enforce_retrieve_floor(marked, Some(0.8));
let env = retrieve_envelope(&outcome, Some(0.8));
assert_eq!(env["taint"], "untrusted");
assert_eq!(env["confidence_floor"], 0.8);
assert_eq!(env["trusted_rows"], 1);
assert_eq!(env["below_floor_filtered"], 1);
assert_eq!(env["rows"].as_array().unwrap().len(), 1);
}
#[test]
fn envelope_with_no_floor_has_null_confidence_floor() {
let outcome = enforce_retrieve_floor(Vec::new(), None);
let env = retrieve_envelope(&outcome, None);
assert_eq!(env["taint"], "untrusted");
assert_eq!(env["confidence_floor"], JsonValue::Null);
assert_eq!(env["below_floor_filtered"], 0);
}
fn binding(k: &str, v: &str) -> (String, SqlValue) {
(k.to_string(), SqlValue::Text(v.to_string()))
}
#[test]
fn persist_with_no_floor_is_always_allowed() {
let row = [binding("name", "Alice")];
assert!(enforce_persist_floor(&row, None, "s").is_ok());
}
#[test]
fn persist_above_floor_is_allowed() {
let row = [binding("name", "Alice"), binding("_confidence", "0.9")];
assert!(enforce_persist_floor(&row, Some(0.8), "tenants").is_ok());
}
#[test]
fn persist_at_the_floor_is_allowed() {
let row = [binding("_confidence", "0.8")];
assert!(enforce_persist_floor(&row, Some(0.8), "tenants").is_ok());
}
#[test]
fn persist_below_floor_is_a_subfloor_error() {
let row = [binding("_confidence", "0.6")];
match enforce_persist_floor(&row, Some(0.8), "tenants") {
Err(EpistemicError::SubFloorWrite { confidence, floor, store }) => {
assert_eq!(confidence, 0.6);
assert_eq!(floor, 0.8);
assert_eq!(store, "tenants");
}
other => panic!("expected SubFloorWrite, got {other:?}"),
}
}
#[test]
fn persist_with_no_confidence_into_a_floored_store_is_unelevated() {
let row = [binding("name", "Alice")];
assert!(matches!(
enforce_persist_floor(&row, Some(0.8), "tenants"),
Err(EpistemicError::UnelevatedWrite { .. })
));
}
#[test]
fn persist_with_malformed_confidence_errors() {
let row = [binding("_confidence", "very-sure")];
assert!(matches!(
enforce_persist_floor(&row, Some(0.8), "tenants"),
Err(EpistemicError::MalformedConfidence { .. })
));
}
#[test]
fn every_epistemic_error_has_a_non_empty_display() {
let errors = [
EpistemicError::UnelevatedWrite { store: "s".into(), floor: 0.8 },
EpistemicError::SubFloorWrite {
store: "s".into(),
confidence: 0.5,
floor: 0.8,
},
EpistemicError::MalformedConfidence {
store: "s".into(),
value: "x".into(),
},
];
for e in errors {
assert!(!e.to_string().is_empty());
}
}
}