1pub 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}