1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
//! Beta-distribution beliefs — epistemic confidence with uncertainty.
//!
//! Ported and adapted from ChimeraLang's `cir/nodes.py::BetaDist`. A scalar
//! confidence cannot distinguish "succeeded 1/1" from "succeeded 50/50" — both
//! read as 1.0. A Beta belief carries the *estimate* (mean) **and** the
//! *uncertainty* (variance): little evidence ⇒ high variance, even at a high
//! mean. This is what lets immunity confidence mature honestly (an established
//! fix needs both a high mean and low variance) and wane into *uncertainty*
//! rather than a lowered estimate.
//!
//! Three operations matter for Axiom's swarm immunity:
//! * [`BetaBelief::reinforce`] / [`BetaBelief::penalize`] — accumulate evidence.
//! * [`BetaBelief::combine_ds`] — Dempster-Shafer evidence combination with
//! *conflict detection*: irreconcilable peers raise [`DsConflict`] instead of
//! silently averaging to mush.
//! * [`BetaBelief::decayed`] — regress toward the uniform prior `Beta(1,1)` as a
//! belief ages (staleness becomes uncertainty).
use serde::{Deserialize, Serialize};
/// Mean threshold above which a belief is "confident" (matches ChimeraLang's
/// guard: `mean >= 1 - max_risk` with max_risk ≈ 0.4 → 0.6).
pub const ESTABLISHED_MEAN: f32 = 0.6;
/// Variance ceiling for an *established* belief (ChimeraLang's guard variance).
pub const ESTABLISHED_VARIANCE: f32 = 0.05;
/// DS conflict mass above which two sources are deemed irreconcilable.
pub const DS_CONFLICT_THRESHOLD: f32 = 0.8;
/// Raised when two beliefs are too contradictory to combine.
#[derive(Debug, Clone, PartialEq)]
pub struct DsConflict {
pub conflict_mass: f32,
pub mean_a: f32,
pub mean_b: f32,
}
/// A belief as `Beta(alpha, beta)`: alpha ~ successes, beta ~ failures.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct BetaBelief {
pub alpha: f32,
pub beta: f32,
}
impl Default for BetaBelief {
fn default() -> Self {
Self::uniform()
}
}
impl BetaBelief {
/// The uniform prior `Beta(1,1)` — maximum uncertainty, mean 0.5.
pub fn uniform() -> Self {
Self { alpha: 1.0, beta: 1.0 }
}
/// Build from a scalar confidence in [0,1] with a pseudocount `strength`
/// (higher strength ⇒ more evidence ⇒ lower variance). Used to migrate
/// legacy scalar-confidence records.
pub fn from_confidence(conf: f32, strength: f32) -> Self {
let conf = conf.clamp(1e-6, 1.0 - 1e-6);
let strength = strength.max(2.0);
Self {
alpha: conf * strength,
beta: (1.0 - conf) * strength,
}
}
pub fn mean(&self) -> f32 {
self.alpha / (self.alpha + self.beta)
}
pub fn variance(&self) -> f32 {
let s = self.alpha + self.beta;
(self.alpha * self.beta) / (s * s * (s + 1.0))
}
/// Total evidence (pseudocount mass).
pub fn evidence(&self) -> f32 {
self.alpha + self.beta
}
/// A belief is *established* only with both a high mean and low variance —
/// so a single 1/1 success (high mean, high variance) is not yet trusted.
pub fn is_established(&self) -> bool {
self.mean() >= ESTABLISHED_MEAN && self.variance() <= ESTABLISHED_VARIANCE
}
/// Human label spanning the evidence/uncertainty space.
pub fn label(&self) -> &'static str {
if self.is_established() {
"established"
} else if self.mean() >= ESTABLISHED_MEAN {
"proven" // high mean but still uncertain (needs more evidence)
} else if self.mean() >= 0.3 {
"tentative"
} else {
"faded"
}
}
/// Add one success pseudocount.
pub fn reinforce(&mut self) {
self.alpha += 1.0;
}
/// Add one failure pseudocount.
pub fn penalize(&mut self) {
self.beta += 1.0;
}
/// Regress toward the uniform prior by `factor` ∈ [0,1] (0 = unchanged,
/// 1 = fully uniform). Increases variance — staleness as uncertainty.
pub fn decayed(&self, factor: f32) -> Self {
let f = factor.clamp(0.0, 1.0);
Self {
alpha: self.alpha * (1.0 - f) + 1.0 * f,
beta: self.beta * (1.0 - f) + 1.0 * f,
}
}
/// Dempster-Shafer-inspired evidence combination. Detects conflict (one
/// source strongly yes, the other strongly no) and returns [`DsConflict`]
/// rather than fabricating a midpoint. This is the swarm-merge trust rule:
/// agreeing peers compound evidence; irreconcilable peers are flagged.
pub fn combine_ds(&self, other: &BetaBelief) -> Result<BetaBelief, DsConflict> {
let t1 = self.alpha + self.beta;
let t2 = other.alpha + other.beta;
let (m1_yes, m1_no) = (self.alpha / t1, self.beta / t1);
let (m2_yes, m2_no) = (other.alpha / t2, other.beta / t2);
let conflict = m1_yes * m2_no + m1_no * m2_yes;
if conflict > DS_CONFLICT_THRESHOLD {
return Err(DsConflict {
conflict_mass: conflict,
mean_a: self.mean(),
mean_b: other.mean(),
});
}
Ok(BetaBelief {
alpha: (self.alpha + other.alpha - 1.0).max(1e-6),
beta: (self.beta + other.beta - 1.0).max(1e-6),
})
}
/// Discount this belief's evidence toward the uniform prior by a reliability
/// coefficient in [0,1]: `reliability=1` keeps it intact, `reliability=0`
/// collapses it to `Beta(1,1)` (no influence). Used to down-weight a peer by
/// its trust (Beta-mean / FLTrust score) *before* combining, so a low-trust
/// peer cannot dominate a swarm merge.
pub fn discounted(&self, reliability: f32) -> BetaBelief {
let r = reliability.clamp(0.0, 1.0);
BetaBelief {
alpha: 1.0 + (self.alpha - 1.0) * r,
beta: 1.0 + (self.beta - 1.0) * r,
}
}
/// Reliability-weighted Dempster-Shafer combination: discount `other` by its
/// trust, then [`combine_ds`]. Still raises [`DsConflict`] on irreconcilable
/// (post-discount) evidence.
pub fn combine_ds_reliable(
&self,
other: &BetaBelief,
reliability: f32,
) -> Result<BetaBelief, DsConflict> {
self.combine_ds(&other.discounted(reliability))
}
/// Murphy's-rule fusion: average the two sources' mass functions instead of
/// Dempster-normalizing through the conflict mass. This avoids Zadeh's
/// paradox — where two near-certain *conflicting* sources yield a verdict
/// neither supports — and is the recommended fallback when conflict is high.
pub fn murphy_average(&self, other: &BetaBelief) -> BetaBelief {
let t1 = self.evidence();
let t2 = other.evidence();
let m_yes = 0.5 * (self.alpha / t1 + other.alpha / t2);
let m_no = 0.5 * (self.beta / t1 + other.beta / t2);
let t = 0.5 * (t1 + t2);
BetaBelief {
alpha: (m_yes * t).max(1e-6),
beta: (m_no * t).max(1e-6),
}
}
/// Conflict-aware swarm merge that never errors: discount `other` by
/// `reliability`, Dempster-combine when reconcilable, else fall back to
/// [`murphy_average`]. Returns the fused belief plus `Some(conflict)` when the
/// Murphy fallback fired (so the caller can still log/alarm on the conflict).
pub fn combine_ds_conflict_aware(
&self,
other: &BetaBelief,
reliability: f32,
) -> (BetaBelief, Option<DsConflict>) {
let discounted = other.discounted(reliability);
match self.combine_ds(&discounted) {
Ok(combined) => (combined, None),
Err(conflict) => (self.murphy_average(&discounted), Some(conflict)),
}
}
/// True when the belief's parameters are well-formed and within sane bounds.
/// Byzantine defence: a peer cannot inject NaN/∞/negative or absurdly large
/// pseudocounts (fabricated certainty) into a swarm merge.
pub fn is_plausible(&self, max_evidence: f32) -> bool {
self.alpha.is_finite()
&& self.beta.is_finite()
&& self.alpha > 0.0
&& self.beta > 0.0
&& self.evidence() <= max_evidence
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_confidence_clamps_extremes_and_stays_finite() {
for c in [0.0f32, 1.0, -5.0, 9.0] {
let b = BetaBelief::from_confidence(c, 10.0);
assert!(b.alpha > 0.0 && b.beta > 0.0);
assert!(b.mean().is_finite() && b.variance().is_finite());
}
}
#[test]
fn uncertainty_separates_one_success_from_many() {
let mut few = BetaBelief::uniform();
few.reinforce(); // 1/1 → Beta(2,1): high mean, high variance
let mut many = BetaBelief::uniform();
for _ in 0..10 {
many.reinforce();
}
assert!(few.mean() > 0.6 && !few.is_established(), "1 success is not established");
assert!(many.is_established(), "many successes → established");
assert!(many.variance() < few.variance(), "more evidence → less uncertainty");
}
#[test]
fn decay_regresses_toward_uniform() {
let mut b = BetaBelief::uniform();
for _ in 0..20 {
b.reinforce();
}
assert!(b.is_established());
let stale = b.decayed(1.0);
assert!((stale.mean() - 0.5).abs() < 1e-5, "full decay → uniform mean");
assert!(!stale.is_established(), "fully decayed belief is no longer trusted");
assert!(stale.variance() > b.variance(), "decay raises uncertainty");
}
#[test]
fn ds_combination_compounds_agreement() {
let a = BetaBelief { alpha: 4.0, beta: 1.0 };
let b = BetaBelief { alpha: 5.0, beta: 1.0 };
let c = a.combine_ds(&b).unwrap();
assert!(c.evidence() > a.evidence(), "agreeing evidence compounds");
assert!(c.mean() > 0.8);
}
#[test]
fn ds_combination_flags_conflict() {
let yes = BetaBelief { alpha: 9.0, beta: 1.0 }; // strongly yes
let no = BetaBelief { alpha: 1.0, beta: 9.0 }; // strongly no
let err = yes.combine_ds(&no).unwrap_err();
assert!(err.conflict_mass > DS_CONFLICT_THRESHOLD);
}
#[test]
fn reliability_discount_reduces_peer_influence() {
let local = BetaBelief { alpha: 5.0, beta: 1.0 }; // confident yes
let peer = BetaBelief { alpha: 1.0, beta: 9.0 }; // confident no
// Full trust: the peer pulls the mean down hard.
let full = local.combine_ds_reliable(&peer, 1.0);
// Zero trust: the peer is collapsed to the uniform prior → no influence.
let none = local.combine_ds_reliable(&peer, 0.0).unwrap();
assert!((none.mean() - local.mean()).abs() < 1e-6, "reliability 0 ⇒ peer ignored");
if let Ok(full) = full {
assert!(full.mean() < none.mean(), "trusted disagreeing peer lowers the mean");
}
}
#[test]
fn conflict_aware_falls_back_to_murphy_without_erroring() {
let yes = BetaBelief { alpha: 9.0, beta: 1.0 };
let no = BetaBelief { alpha: 1.0, beta: 9.0 };
// Raw combine_ds errors on this conflict...
assert!(yes.combine_ds(&no).is_err());
// ...but the conflict-aware merge returns a finite Murphy average + flag.
let (fused, conflict) = yes.combine_ds_conflict_aware(&no, 1.0);
assert!(conflict.is_some(), "high conflict is still surfaced");
assert!(fused.mean().is_finite() && fused.alpha > 0.0 && fused.beta > 0.0);
// Murphy average of symmetric yes/no sits near 0.5 — supported by neither
// extreme, but not the paradoxical Dempster result.
assert!((fused.mean() - 0.5).abs() < 0.2, "murphy fusion is a sane midpoint");
}
#[test]
fn plausibility_rejects_byzantine_values() {
assert!(BetaBelief { alpha: 5.0, beta: 2.0 }.is_plausible(1000.0));
assert!(!BetaBelief { alpha: f32::NAN, beta: 1.0 }.is_plausible(1000.0));
assert!(!BetaBelief { alpha: -1.0, beta: 1.0 }.is_plausible(1000.0));
assert!(!BetaBelief { alpha: 1e9, beta: 1.0 }.is_plausible(1000.0), "fabricated certainty rejected");
}
}