Skip to main content

aidens_kernel_kit/
mechanism.rs

1//! P18 — Mechanism/theory search, experiment runtime, and falsifiable model library.
2//!
3//! Candidate mechanisms can be fit, refuted, versioned, superseded, and replayed.
4//! Mechanisms are non-authoritative hypotheses — they never promote without
5//! refuter suites and governance disposition.
6
7use serde::{Deserialize, Serialize};
8
9/// A candidate mechanism/theory artifact.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct MechanismBundleV1 {
12    pub mechanism_id: String,
13    pub name: String,
14    pub description: String,
15    pub fit_score: f64,
16    pub refuter_suite: Option<RefuterSuiteV1>,
17    pub status: MechanismStatus,
18    pub version: u32,
19    pub superseded_by: Option<String>,
20    pub timestamp: String,
21}
22
23/// A refuter suite — attempts to disprove a mechanism.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct RefuterSuiteV1 {
26    pub suite_id: String,
27    pub refuters: Vec<RefuterV1>,
28    pub all_passed: bool,
29}
30
31/// A single refuter attempt.
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct RefuterV1 {
34    pub refuter_id: String,
35    pub refutation_method: String,
36    pub result: RefutationResult,
37    pub evidence: String,
38}
39
40/// Result of a refutation attempt.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42pub enum RefutationResult {
43    /// Could not refute — mechanism survives.
44    Survived,
45    /// Successfully refuted — mechanism is falsified.
46    Refuted,
47    /// Inconclusive — more evidence needed.
48    Inconclusive,
49}
50
51/// Status of a mechanism in the publication lifecycle.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53pub enum MechanismStatus {
54    /// Just fit, no refuters run yet.
55    Candidate,
56    /// Refuters run, all survived.
57    Tested,
58    /// Refuted by at least one refuter.
59    Refuted,
60    /// Superseded by a newer version.
61    Superseded,
62    /// Published (requires refuter suite + governance approval).
63    Published,
64}
65
66/// Adapter for mechanism search and lifecycle.
67pub struct MechanismAdapter;
68
69impl MechanismAdapter {
70    /// Fit a candidate mechanism to data.
71    pub fn fit_mechanism(name: &str, description: &str, fit_score: f64) -> MechanismBundleV1 {
72        MechanismBundleV1 {
73            mechanism_id: format!("mech:{}:{}", name, chrono::Utc::now().timestamp()),
74            name: name.to_string(),
75            description: description.to_string(),
76            fit_score,
77            refuter_suite: None,
78            status: MechanismStatus::Candidate,
79            version: 1,
80            superseded_by: None,
81            timestamp: chrono::Utc::now().to_rfc3339(),
82        }
83    }
84
85    /// Run a refuter suite against a mechanism.
86    pub fn refute_mechanism(mechanism: &mut MechanismBundleV1, refuters: Vec<RefuterV1>) {
87        let all_passed = refuters
88            .iter()
89            .all(|r| r.result == RefutationResult::Survived);
90        let any_refuted = refuters
91            .iter()
92            .any(|r| r.result == RefutationResult::Refuted);
93
94        mechanism.refuter_suite = Some(RefuterSuiteV1 {
95            suite_id: format!(
96                "rs:{}:{}",
97                mechanism.mechanism_id,
98                chrono::Utc::now().timestamp()
99            ),
100            refuters,
101            all_passed,
102        });
103
104        mechanism.status = if any_refuted {
105            MechanismStatus::Refuted
106        } else if all_passed {
107            MechanismStatus::Tested
108        } else {
109            MechanismStatus::Candidate
110        };
111    }
112
113    /// Create a new version of a mechanism, superseding the old one.
114    pub fn supersede_mechanism(
115        old: &mut MechanismBundleV1,
116        new_name: &str,
117        new_description: &str,
118        new_fit_score: f64,
119    ) -> MechanismBundleV1 {
120        old.status = MechanismStatus::Superseded;
121        let new_version = old.version + 1;
122        let new_id = format!("mech:{}:{}", new_name, chrono::Utc::now().timestamp());
123        old.superseded_by = Some(new_id.clone());
124
125        MechanismBundleV1 {
126            mechanism_id: new_id,
127            name: new_name.to_string(),
128            description: new_description.to_string(),
129            fit_score: new_fit_score,
130            refuter_suite: None,
131            status: MechanismStatus::Candidate,
132            version: new_version,
133            superseded_by: None,
134            timestamp: chrono::Utc::now().to_rfc3339(),
135        }
136    }
137
138    /// Check if a mechanism can be promoted to published.
139    pub fn can_promote(mechanism: &MechanismBundleV1) -> bool {
140        // Mechanism must have a refuter suite where all refuters survived
141        match &mechanism.refuter_suite {
142            Some(suite) => suite.all_passed && mechanism.status == MechanismStatus::Tested,
143            None => false,
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn fit_mechanism_starts_as_candidate() {
154        let m = MechanismAdapter::fit_mechanism("test-mech", "a test", 0.85);
155        assert_eq!(m.status, MechanismStatus::Candidate);
156        assert!(m.refuter_suite.is_none());
157    }
158
159    #[test]
160    fn mechanism_fit_never_promotes_without_refuter_suite() {
161        let m = MechanismAdapter::fit_mechanism("test-mech", "a test", 0.99);
162        assert!(!MechanismAdapter::can_promote(&m));
163    }
164
165    #[test]
166    fn refuted_mechanism_cannot_promote() {
167        let mut m = MechanismAdapter::fit_mechanism("test-mech", "a test", 0.85);
168        MechanismAdapter::refute_mechanism(
169            &mut m,
170            vec![RefuterV1 {
171                refuter_id: "r1".into(),
172                refutation_method: "counterexample".into(),
173                result: RefutationResult::Refuted,
174                evidence: "found counterexample".into(),
175            }],
176        );
177        assert_eq!(m.status, MechanismStatus::Refuted);
178        assert!(!MechanismAdapter::can_promote(&m));
179    }
180
181    #[test]
182    fn survived_mechanism_can_promote() {
183        let mut m = MechanismAdapter::fit_mechanism("test-mech", "a test", 0.85);
184        MechanismAdapter::refute_mechanism(
185            &mut m,
186            vec![RefuterV1 {
187                refuter_id: "r1".into(),
188                refutation_method: "counterexample".into(),
189                result: RefutationResult::Survived,
190                evidence: "no counterexample found".into(),
191            }],
192        );
193        assert_eq!(m.status, MechanismStatus::Tested);
194        assert!(MechanismAdapter::can_promote(&m));
195    }
196
197    #[test]
198    fn supersede_creates_new_version() {
199        let mut old = MechanismAdapter::fit_mechanism("mech-v1", "first", 0.7);
200        let new = MechanismAdapter::supersede_mechanism(&mut old, "mech-v2", "second", 0.9);
201        assert_eq!(old.status, MechanismStatus::Superseded);
202        assert_eq!(new.version, 2);
203        assert_eq!(
204            old.superseded_by.as_deref(),
205            Some(new.mechanism_id.as_str())
206        );
207    }
208
209    #[test]
210    fn inconclusive_refuter_keeps_candidate() {
211        let mut m = MechanismAdapter::fit_mechanism("test", "test", 0.5);
212        MechanismAdapter::refute_mechanism(
213            &mut m,
214            vec![RefuterV1 {
215                refuter_id: "r1".into(),
216                refutation_method: "fuzz".into(),
217                result: RefutationResult::Inconclusive,
218                evidence: "need more data".into(),
219            }],
220        );
221        assert_eq!(m.status, MechanismStatus::Candidate);
222        assert!(!MechanismAdapter::can_promote(&m));
223    }
224}