Skip to main content

aidens_repair_kit/
lib.rs

1//! Thin repair facade over canonical verification and Forge repair artifacts.
2
3pub mod canonical_stack {
4    pub use semantic_memory_forge::{RetractionRecordV1, RETRACTION_RECORD_V1_SCHEMA};
5    pub use verification_control::{
6        BoundaryArtifactKind, BoundaryRepairRecord, BOUNDARY_REPAIR_RECORD_V1_SCHEMA,
7    };
8}
9
10pub use canonical_stack::BoundaryRepairRecord as CanonicalBoundaryRepairRecord;
11pub use canonical_stack::RetractionRecordV1 as CanonicalRetractionRecordV1;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum BoundaryRepairAdmissionDisposition {
15    Accepted,
16    Rejected,
17    Quarantined,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct BoundaryRepairAdmissionReceipt {
22    pub disposition: BoundaryRepairAdmissionDisposition,
23    pub accepted: bool,
24    pub replay_required: bool,
25    pub schema_version: String,
26    pub reason_codes: Vec<String>,
27}
28
29impl BoundaryRepairAdmissionReceipt {
30    pub fn accepted(record: &CanonicalBoundaryRepairRecord) -> Self {
31        Self {
32            disposition: BoundaryRepairAdmissionDisposition::Accepted,
33            accepted: true,
34            replay_required: true,
35            schema_version: record.schema_version.clone(),
36            reason_codes: vec![
37                "boundary-repair-record-admitted".into(),
38                "repair-replay-required".into(),
39            ],
40        }
41    }
42
43    pub fn rejected(reason: impl Into<String>) -> Self {
44        Self {
45            disposition: BoundaryRepairAdmissionDisposition::Rejected,
46            accepted: false,
47            replay_required: true,
48            schema_version: canonical_stack::BOUNDARY_REPAIR_RECORD_V1_SCHEMA.into(),
49            reason_codes: vec!["boundary-repair-record-rejected".into(), reason.into()],
50        }
51    }
52
53    pub fn quarantined(reason: impl Into<String>) -> Self {
54        Self {
55            disposition: BoundaryRepairAdmissionDisposition::Quarantined,
56            accepted: false,
57            replay_required: true,
58            schema_version: canonical_stack::BOUNDARY_REPAIR_RECORD_V1_SCHEMA.into(),
59            reason_codes: vec!["boundary-repair-record-quarantined".into(), reason.into()],
60        }
61    }
62}
63
64#[derive(Debug, Clone, Default, PartialEq, Eq)]
65pub struct CanonicalRepairAdapter;
66
67impl CanonicalRepairAdapter {
68    #[allow(clippy::too_many_arguments)]
69    pub fn boundary_repair_record(
70        &self,
71        artifact_kind: canonical_stack::BoundaryArtifactKind,
72        artifact_schema_version: impl Into<String>,
73        repair_kind: impl Into<String>,
74        field_path: impl Into<String>,
75        original_value: Option<serde_json::Value>,
76        repaired_value: serde_json::Value,
77        rationale: impl Into<String>,
78    ) -> CanonicalBoundaryRepairRecord {
79        canonical_stack::BoundaryRepairRecord::new(
80            artifact_kind,
81            artifact_schema_version,
82            repair_kind,
83            field_path,
84            original_value,
85            repaired_value,
86            rationale,
87        )
88    }
89
90    pub fn validate_retraction(&self, record: &CanonicalRetractionRecordV1) -> Result<(), String> {
91        record.validate()
92    }
93
94    pub fn admit_boundary_repair_record(
95        &self,
96        record: &CanonicalBoundaryRepairRecord,
97    ) -> BoundaryRepairAdmissionReceipt {
98        if record.schema_version != canonical_stack::BOUNDARY_REPAIR_RECORD_V1_SCHEMA {
99            return BoundaryRepairAdmissionReceipt::rejected("schema-version-mismatch");
100        }
101        if record.field_path.trim().is_empty() || record.rationale.trim().is_empty() {
102            return BoundaryRepairAdmissionReceipt::quarantined(
103                "repair-record-missing-material-field",
104            );
105        }
106        BoundaryRepairAdmissionReceipt::accepted(record)
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn creates_canonical_boundary_repair_record() {
116        let record = CanonicalRepairAdapter.boundary_repair_record(
117            canonical_stack::BoundaryArtifactKind::ControlReceipt,
118            "control_receipt_v1",
119            "field_normalization",
120            "$.actor",
121            Some(serde_json::json!("")),
122            serde_json::json!("forge-pilot"),
123            "canonical verification-control repair record",
124        );
125
126        assert_eq!(
127            record.schema_version,
128            canonical_stack::BOUNDARY_REPAIR_RECORD_V1_SCHEMA
129        );
130    }
131
132    #[test]
133    fn boundary_repair_admission_accepts_rejects_and_quarantines() {
134        let accepted = CanonicalRepairAdapter.boundary_repair_record(
135            canonical_stack::BoundaryArtifactKind::ControlReceipt,
136            "control_receipt_v1",
137            "field_normalization",
138            "$.actor",
139            Some(serde_json::json!("")),
140            serde_json::json!("forge-pilot"),
141            "canonical verification-control repair record",
142        );
143        let accepted_receipt = CanonicalRepairAdapter.admit_boundary_repair_record(&accepted);
144        assert_eq!(
145            accepted_receipt.disposition,
146            BoundaryRepairAdmissionDisposition::Accepted
147        );
148        assert!(accepted_receipt.accepted);
149        assert!(accepted_receipt.replay_required);
150
151        let mut rejected = accepted.clone();
152        rejected.schema_version = "wrong-schema".into();
153        let rejected_receipt = CanonicalRepairAdapter.admit_boundary_repair_record(&rejected);
154        assert_eq!(
155            rejected_receipt.disposition,
156            BoundaryRepairAdmissionDisposition::Rejected
157        );
158        assert!(!rejected_receipt.accepted);
159
160        let mut quarantined = accepted;
161        quarantined.field_path = String::new();
162        let quarantined_receipt = CanonicalRepairAdapter.admit_boundary_repair_record(&quarantined);
163        assert_eq!(
164            quarantined_receipt.disposition,
165            BoundaryRepairAdmissionDisposition::Quarantined
166        );
167        assert!(!quarantined_receipt.accepted);
168        assert!(quarantined_receipt
169            .reason_codes
170            .contains(&"repair-record-missing-material-field".into()));
171    }
172}