pub type EpistemicResult<T> = Result<T, EpistemicError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EpistemicError {
UtilityCannotUpgradeTruth,
UtilityCannotUpgradeProof,
UsefulnessOutOfRange,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TruthStatus {
Unknown,
Hypothesis,
Supported,
Verified,
Rejected,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProofStatus {
Unknown,
Broken,
Partial,
FullChainVerified,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UtilitySignal {
Used,
OutcomeSucceeded,
OutcomeFailed,
OperatorMarkedUseful,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TruthTransitionEvidence {
SourceAnchored,
OperatorConfirmed,
ContradictionOrRejection,
Utility(UtilitySignal),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProofTransitionEvidence {
ProofClosureVerifier,
LineageRepair,
Utility(UtilitySignal),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Usefulness {
score: f32,
}
impl Usefulness {
pub const MIN: f32 = 0.0;
pub const MAX: f32 = 1.0;
pub fn new(score: f32) -> EpistemicResult<Self> {
if score.is_nan() || !(Self::MIN..=Self::MAX).contains(&score) {
return Err(EpistemicError::UsefulnessOutOfRange);
}
Ok(Self { score })
}
#[must_use]
pub const fn score(self) -> f32 {
self.score
}
#[must_use]
pub fn apply_signal(self, signal: UtilitySignal) -> Self {
let delta = match signal {
UtilitySignal::Used => 0.0,
UtilitySignal::OutcomeSucceeded => 0.10,
UtilitySignal::OutcomeFailed => -0.15,
UtilitySignal::OperatorMarkedUseful => 0.20,
};
Self {
score: (self.score + delta).clamp(Self::MIN, Self::MAX),
}
}
}
impl Default for Usefulness {
fn default() -> Self {
Self { score: 0.0 }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EpistemicState {
pub truth_status: TruthStatus,
pub proof_status: ProofStatus,
pub usefulness: Usefulness,
}
impl EpistemicState {
#[must_use]
pub const fn new(
truth_status: TruthStatus,
proof_status: ProofStatus,
usefulness: Usefulness,
) -> Self {
Self {
truth_status,
proof_status,
usefulness,
}
}
#[must_use]
pub fn apply_utility_signal(self, signal: UtilitySignal) -> Self {
Self {
usefulness: self.usefulness.apply_signal(signal),
..self
}
}
pub fn with_truth_status(
self,
truth_status: TruthStatus,
evidence: TruthTransitionEvidence,
) -> EpistemicResult<Self> {
if matches!(evidence, TruthTransitionEvidence::Utility(_)) {
return Err(EpistemicError::UtilityCannotUpgradeTruth);
}
Ok(Self {
truth_status,
..self
})
}
pub fn with_proof_status(
self,
proof_status: ProofStatus,
evidence: ProofTransitionEvidence,
) -> EpistemicResult<Self> {
if matches!(evidence, ProofTransitionEvidence::Utility(_)) {
return Err(EpistemicError::UtilityCannotUpgradeProof);
}
Ok(Self {
proof_status,
..self
})
}
}
#[must_use]
pub fn apply_outcome_success(state: EpistemicState) -> EpistemicState {
state.apply_utility_signal(UtilitySignal::OutcomeSucceeded)
}
pub fn reject_utility_to_truth_transition(
_signal: UtilitySignal,
_target: TruthStatus,
) -> EpistemicResult<()> {
Err(EpistemicError::UtilityCannotUpgradeTruth)
}
pub fn reject_utility_to_proof_transition(
_signal: UtilitySignal,
_target: ProofStatus,
) -> EpistemicResult<()> {
Err(EpistemicError::UtilityCannotUpgradeProof)
}
pub fn assert_utility_preserved_epistemics(
before: EpistemicState,
after: EpistemicState,
) -> EpistemicResult<()> {
if before.truth_status != after.truth_status {
return Err(EpistemicError::UtilityCannotUpgradeTruth);
}
if before.proof_status != after.proof_status {
return Err(EpistemicError::UtilityCannotUpgradeProof);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn partial_hypothesis() -> EpistemicState {
EpistemicState::new(
TruthStatus::Hypothesis,
ProofStatus::Partial,
Usefulness::new(0.2).unwrap(),
)
}
#[test]
fn outcome_success_does_not_upgrade_truth_status() {
let before = partial_hypothesis();
let after = apply_outcome_success(before);
assert_eq!(after.truth_status, TruthStatus::Hypothesis);
assert_eq!(after.proof_status, ProofStatus::Partial);
assert!(after.usefulness.score() > before.usefulness.score());
assert!(assert_utility_preserved_epistemics(before, after).is_ok());
}
#[test]
fn repeated_successful_reuse_can_increase_usefulness_while_proof_remains_partial() {
let before = partial_hypothesis();
let after = (0..5).fold(before, |state, _| apply_outcome_success(state));
assert!(after.usefulness.score() > before.usefulness.score());
assert_eq!(after.proof_status, ProofStatus::Partial);
assert_eq!(after.truth_status, TruthStatus::Hypothesis);
}
#[test]
fn utility_to_truth_transition_fails_closed() {
assert_eq!(
reject_utility_to_truth_transition(
UtilitySignal::OutcomeSucceeded,
TruthStatus::Verified,
),
Err(EpistemicError::UtilityCannotUpgradeTruth)
);
assert_eq!(
partial_hypothesis().with_truth_status(
TruthStatus::Verified,
TruthTransitionEvidence::Utility(UtilitySignal::OutcomeSucceeded),
),
Err(EpistemicError::UtilityCannotUpgradeTruth)
);
}
#[test]
fn utility_to_proof_transition_fails_closed() {
assert_eq!(
reject_utility_to_proof_transition(
UtilitySignal::OutcomeSucceeded,
ProofStatus::FullChainVerified,
),
Err(EpistemicError::UtilityCannotUpgradeProof)
);
assert_eq!(
partial_hypothesis().with_proof_status(
ProofStatus::FullChainVerified,
ProofTransitionEvidence::Utility(UtilitySignal::OutcomeSucceeded),
),
Err(EpistemicError::UtilityCannotUpgradeProof)
);
}
#[test]
fn usefulness_rejects_nan_and_out_of_range_scores() {
assert_eq!(
Usefulness::new(f32::NAN),
Err(EpistemicError::UsefulnessOutOfRange)
);
assert_eq!(
Usefulness::new(1.01),
Err(EpistemicError::UsefulnessOutOfRange)
);
}
}