1pub type EpistemicResult<T> = Result<T, EpistemicError>;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum EpistemicError {
12 UtilityCannotUpgradeTruth,
14 UtilityCannotUpgradeProof,
16 UsefulnessOutOfRange,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum TruthStatus {
23 Unknown,
25 Hypothesis,
27 Supported,
29 Verified,
31 Rejected,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ProofStatus {
38 Unknown,
40 Broken,
42 Partial,
44 FullChainVerified,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum UtilitySignal {
51 Used,
53 OutcomeSucceeded,
55 OutcomeFailed,
57 OperatorMarkedUseful,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum TruthTransitionEvidence {
64 SourceAnchored,
66 OperatorConfirmed,
68 ContradictionOrRejection,
70 Utility(UtilitySignal),
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum ProofTransitionEvidence {
77 ProofClosureVerifier,
79 LineageRepair,
81 Utility(UtilitySignal),
83}
84
85#[derive(Debug, Clone, Copy, PartialEq)]
87pub struct Usefulness {
88 score: f32,
89}
90
91impl Usefulness {
92 pub const MIN: f32 = 0.0;
94 pub const MAX: f32 = 1.0;
96
97 pub fn new(score: f32) -> EpistemicResult<Self> {
99 if score.is_nan() || !(Self::MIN..=Self::MAX).contains(&score) {
100 return Err(EpistemicError::UsefulnessOutOfRange);
101 }
102
103 Ok(Self { score })
104 }
105
106 #[must_use]
108 pub const fn score(self) -> f32 {
109 self.score
110 }
111
112 #[must_use]
114 pub fn apply_signal(self, signal: UtilitySignal) -> Self {
115 let delta = match signal {
116 UtilitySignal::Used => 0.0,
117 UtilitySignal::OutcomeSucceeded => 0.10,
118 UtilitySignal::OutcomeFailed => -0.15,
119 UtilitySignal::OperatorMarkedUseful => 0.20,
120 };
121
122 Self {
123 score: (self.score + delta).clamp(Self::MIN, Self::MAX),
124 }
125 }
126}
127
128impl Default for Usefulness {
129 fn default() -> Self {
130 Self { score: 0.0 }
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq)]
136pub struct EpistemicState {
137 pub truth_status: TruthStatus,
139 pub proof_status: ProofStatus,
141 pub usefulness: Usefulness,
143}
144
145impl EpistemicState {
146 #[must_use]
148 pub const fn new(
149 truth_status: TruthStatus,
150 proof_status: ProofStatus,
151 usefulness: Usefulness,
152 ) -> Self {
153 Self {
154 truth_status,
155 proof_status,
156 usefulness,
157 }
158 }
159
160 #[must_use]
162 pub fn apply_utility_signal(self, signal: UtilitySignal) -> Self {
163 Self {
164 usefulness: self.usefulness.apply_signal(signal),
165 ..self
166 }
167 }
168
169 pub fn with_truth_status(
171 self,
172 truth_status: TruthStatus,
173 evidence: TruthTransitionEvidence,
174 ) -> EpistemicResult<Self> {
175 if matches!(evidence, TruthTransitionEvidence::Utility(_)) {
176 return Err(EpistemicError::UtilityCannotUpgradeTruth);
177 }
178
179 Ok(Self {
180 truth_status,
181 ..self
182 })
183 }
184
185 pub fn with_proof_status(
187 self,
188 proof_status: ProofStatus,
189 evidence: ProofTransitionEvidence,
190 ) -> EpistemicResult<Self> {
191 if matches!(evidence, ProofTransitionEvidence::Utility(_)) {
192 return Err(EpistemicError::UtilityCannotUpgradeProof);
193 }
194
195 Ok(Self {
196 proof_status,
197 ..self
198 })
199 }
200}
201
202#[must_use]
204pub fn apply_outcome_success(state: EpistemicState) -> EpistemicState {
205 state.apply_utility_signal(UtilitySignal::OutcomeSucceeded)
206}
207
208pub fn reject_utility_to_truth_transition(
210 _signal: UtilitySignal,
211 _target: TruthStatus,
212) -> EpistemicResult<()> {
213 Err(EpistemicError::UtilityCannotUpgradeTruth)
214}
215
216pub fn reject_utility_to_proof_transition(
218 _signal: UtilitySignal,
219 _target: ProofStatus,
220) -> EpistemicResult<()> {
221 Err(EpistemicError::UtilityCannotUpgradeProof)
222}
223
224pub fn assert_utility_preserved_epistemics(
226 before: EpistemicState,
227 after: EpistemicState,
228) -> EpistemicResult<()> {
229 if before.truth_status != after.truth_status {
230 return Err(EpistemicError::UtilityCannotUpgradeTruth);
231 }
232
233 if before.proof_status != after.proof_status {
234 return Err(EpistemicError::UtilityCannotUpgradeProof);
235 }
236
237 Ok(())
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 fn partial_hypothesis() -> EpistemicState {
245 EpistemicState::new(
246 TruthStatus::Hypothesis,
247 ProofStatus::Partial,
248 Usefulness::new(0.2).unwrap(),
249 )
250 }
251
252 #[test]
253 fn outcome_success_does_not_upgrade_truth_status() {
254 let before = partial_hypothesis();
255 let after = apply_outcome_success(before);
256
257 assert_eq!(after.truth_status, TruthStatus::Hypothesis);
258 assert_eq!(after.proof_status, ProofStatus::Partial);
259 assert!(after.usefulness.score() > before.usefulness.score());
260 assert!(assert_utility_preserved_epistemics(before, after).is_ok());
261 }
262
263 #[test]
264 fn repeated_successful_reuse_can_increase_usefulness_while_proof_remains_partial() {
265 let before = partial_hypothesis();
266 let after = (0..5).fold(before, |state, _| apply_outcome_success(state));
267
268 assert!(after.usefulness.score() > before.usefulness.score());
269 assert_eq!(after.proof_status, ProofStatus::Partial);
270 assert_eq!(after.truth_status, TruthStatus::Hypothesis);
271 }
272
273 #[test]
274 fn utility_to_truth_transition_fails_closed() {
275 assert_eq!(
276 reject_utility_to_truth_transition(
277 UtilitySignal::OutcomeSucceeded,
278 TruthStatus::Verified,
279 ),
280 Err(EpistemicError::UtilityCannotUpgradeTruth)
281 );
282
283 assert_eq!(
284 partial_hypothesis().with_truth_status(
285 TruthStatus::Verified,
286 TruthTransitionEvidence::Utility(UtilitySignal::OutcomeSucceeded),
287 ),
288 Err(EpistemicError::UtilityCannotUpgradeTruth)
289 );
290 }
291
292 #[test]
293 fn utility_to_proof_transition_fails_closed() {
294 assert_eq!(
295 reject_utility_to_proof_transition(
296 UtilitySignal::OutcomeSucceeded,
297 ProofStatus::FullChainVerified,
298 ),
299 Err(EpistemicError::UtilityCannotUpgradeProof)
300 );
301
302 assert_eq!(
303 partial_hypothesis().with_proof_status(
304 ProofStatus::FullChainVerified,
305 ProofTransitionEvidence::Utility(UtilitySignal::OutcomeSucceeded),
306 ),
307 Err(EpistemicError::UtilityCannotUpgradeProof)
308 );
309 }
310
311 #[test]
312 fn usefulness_rejects_nan_and_out_of_range_scores() {
313 assert_eq!(
314 Usefulness::new(f32::NAN),
315 Err(EpistemicError::UsefulnessOutOfRange)
316 );
317 assert_eq!(
318 Usefulness::new(1.01),
319 Err(EpistemicError::UsefulnessOutOfRange)
320 );
321 }
322}