Skip to main content

semantic_memory/
authority_contracts.rs

1//! Versioned wire contracts for authority, witnessed retrieval, and injection governance.
2
3use crate::StateView;
4use serde::{Deserialize, Deserializer, Serialize};
5
6macro_rules! bounded_f64 {
7    ($name:ident, $min:expr, $max:expr) => {
8        #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize)]
9        #[serde(transparent)]
10        pub struct $name(f64);
11
12        impl $name {
13            pub fn new(value: f64) -> Result<Self, String> {
14                if !value.is_finite() || value < $min || value > $max {
15                    return Err(format!(
16                        "{} must be finite and within [{}, {}]",
17                        stringify!($name),
18                        $min,
19                        $max
20                    ));
21                }
22                Ok(Self(value))
23            }
24
25            pub fn get(self) -> f64 {
26                self.0
27            }
28        }
29
30        impl<'de> Deserialize<'de> for $name {
31            fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
32                let value = f64::deserialize(deserializer)?;
33                Self::new(value).map_err(serde::de::Error::custom)
34            }
35        }
36    };
37}
38
39bounded_f64!(Probability, 0.0, 1.0);
40bounded_f64!(Confidence, 0.0, 1.0);
41bounded_f64!(CosineSimilarity, -1.0, 1.0);
42bounded_f64!(NonNegativeWeight, 0.0, f64::MAX);
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45#[serde(transparent)]
46pub struct AuthoritySnapshotId(pub String);
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(transparent)]
50pub struct RetrievalEpoch(pub u64);
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct MemoryEnvelopeV1 {
54    pub schema_version: String,
55    pub memory_id: String,
56    pub namespace: String,
57    pub content: String,
58    pub source: Option<String>,
59    pub valid_at: String,
60    pub recorded_at: String,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct CapabilityManifestV1 {
65    pub schema_version: String,
66    pub principal: String,
67    pub capabilities: Vec<String>,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(rename_all = "snake_case")]
72pub enum StageOutcomeV1 {
73    NotPlanned,
74    Skipped,
75    AnalysisOnly,
76    Applied,
77    Degraded,
78    Failed,
79    BudgetExceeded,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct RetrievalWitnessV1 {
84    pub schema_version: String,
85    pub request_id: String,
86    pub evaluated_at: String,
87    pub authority_snapshot_id: AuthoritySnapshotId,
88    pub retrieval_epoch: RetrievalEpoch,
89    pub query_digest: String,
90    pub config_digest: String,
91    pub ordered_result_ids: Vec<String>,
92    pub ordered_result_digests: Vec<String>,
93    pub stage_outcomes: Vec<(String, StageOutcomeV1)>,
94    pub degradations: Vec<String>,
95    pub cached_witness_parent: Option<String>,
96}
97
98/// Snapshot metadata used by governing systems for cache validation and replay integrity.
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct AuthorityStateV1 {
101    pub snapshot_id: AuthoritySnapshotId,
102    pub retrieval_epoch: RetrievalEpoch,
103}
104
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106pub struct RetrievalResponseV1<T> {
107    pub schema_version: String,
108    pub state_view: StateView,
109    pub results: Vec<T>,
110    pub witness: RetrievalWitnessV1,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "snake_case")]
115pub enum InjectionDisposition {
116    Admitted,
117    PartiallyAdmitted,
118    Rejected,
119    FailedClosed,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123pub struct InjectionDecisionV1 {
124    pub schema_version: String,
125    pub retrieval_receipt_id: String,
126    pub principal: String,
127    pub host: String,
128    pub policy_digest: String,
129    pub admitted_ids: Vec<String>,
130    pub rejected_ids: Vec<String>,
131    pub disposition: InjectionDisposition,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
135pub struct SupersessionReceiptV1 {
136    pub schema_version: String,
137    pub operation_id: String,
138    pub caller_idempotency_key: String,
139    pub superseded_id: String,
140    pub replacement_id: String,
141    pub authority_snapshot_id: AuthoritySnapshotId,
142    pub retrieval_epoch: RetrievalEpoch,
143    pub committed_at: String,
144}
145
146/// Capability-bearing caller permit for the governed authority mutation lane.
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
148pub struct AuthorityPermit {
149    pub principal: String,
150    pub caller_id: String,
151    pub capability: String,
152    pub admission: AuthorityAdmission,
153    /// Immutable origin proposed for the governed write. Missing origin fails closed.
154    #[serde(default)]
155    pub origin_authority: Option<crate::OriginAuthorityLabelV1>,
156}
157
158/// Admission basis carried by a governed authority permit.
159#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
160#[serde(rename_all = "snake_case")]
161pub enum AuthorityAdmission {
162    Unspecified,
163    Evidence { evidence_refs: Vec<String> },
164    OperatorSystem,
165}
166
167impl AuthorityPermit {
168    pub const APPEND_CAPABILITY: &'static str = "memory.authority.append";
169    pub const SUPERSEDE_CAPABILITY: &'static str = "memory.authority.supersede";
170    pub const REDACT_CAPABILITY: &'static str = "memory.authority.redact";
171    pub const REVOKE_ORIGIN_CAPABILITY: &'static str = "memory.authority.revoke_origin";
172    pub const FORGET_CAPABILITY: &'static str = "memory.authority.forget";
173
174    pub fn new(
175        principal: impl Into<String>,
176        caller_id: impl Into<String>,
177        capability: impl Into<String>,
178    ) -> Self {
179        Self {
180            principal: principal.into(),
181            caller_id: caller_id.into(),
182            capability: capability.into(),
183            admission: AuthorityAdmission::Unspecified,
184            origin_authority: None,
185        }
186    }
187
188    /// Construct a permit for a fact proposal supported by explicit evidence.
189    pub fn with_evidence(
190        principal: impl Into<String>,
191        caller_id: impl Into<String>,
192        capability: impl Into<String>,
193        evidence_refs: Vec<String>,
194    ) -> Self {
195        Self {
196            principal: principal.into(),
197            caller_id: caller_id.into(),
198            capability: capability.into(),
199            admission: AuthorityAdmission::Evidence { evidence_refs },
200            origin_authority: None,
201        }
202    }
203
204    /// Construct the explicit operator/system bypass used for trusted inserts.
205    pub fn operator_system(
206        principal: impl Into<String>,
207        caller_id: impl Into<String>,
208        capability: impl Into<String>,
209    ) -> Self {
210        let principal = principal.into();
211        let caller_id = caller_id.into();
212        Self {
213            origin_authority: Some(crate::OriginAuthorityLabelV1::operator_system(
214                &principal, &caller_id,
215            )),
216            principal,
217            caller_id,
218            capability: capability.into(),
219            admission: AuthorityAdmission::OperatorSystem,
220        }
221    }
222
223    /// Bind an immutable origin label to this governed write permit.
224    pub fn with_origin(mut self, origin: crate::OriginAuthorityLabelV1) -> Self {
225        self.origin_authority = Some(origin);
226        self
227    }
228}
229
230/// Authority mutation kind recorded in the operation journal and receipt.
231#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
232#[serde(rename_all = "snake_case")]
233pub enum AuthorityOperationKind {
234    Append,
235    Supersede,
236    Redact,
237}
238
239/// Typed fault-injection points for atomic authority tests.
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
241#[serde(rename_all = "snake_case")]
242pub enum AuthorityFaultStage {
243    BeforeAppend,
244    AfterAppend,
245    BeforeLineage,
246    AfterLineage,
247    BeforeJournal,
248    AfterJournal,
249    BeforeEpoch,
250    AfterEpoch,
251    BeforeReceipt,
252    AfterReceipt,
253    BeforeForgettingMutation,
254    AfterForgettingMutation,
255    BeforeForgettingReceipt,
256    AfterForgettingReceipt,
257    BeforeShadowPromotion,
258    AfterShadowPromotion,
259}
260
261/// Durable receipt for one governed authority mutation.
262#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
263pub struct AuthorityReceiptV1 {
264    pub schema_version: String,
265    pub receipt_id: String,
266    pub operation_id: String,
267    pub caller_idempotency_key: String,
268    pub principal: String,
269    pub caller_id: String,
270    pub operation_kind: AuthorityOperationKind,
271    pub before_snapshot_id: AuthoritySnapshotId,
272    pub after_snapshot_id: AuthoritySnapshotId,
273    pub before_epoch: RetrievalEpoch,
274    pub after_epoch: RetrievalEpoch,
275    pub affected_ids: Vec<String>,
276    pub content_digest: String,
277    /// Digest of the immutable origin label persisted for the written fact.
278    #[serde(default)]
279    pub origin_label_digest: Option<String>,
280    pub receipt_digest: String,
281    pub committed_at: String,
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    #[test]
289    fn bounded_numbers_reject_non_finite_and_out_of_range() {
290        for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, -0.1, 1.1] {
291            assert!(Probability::new(value).is_err());
292            assert!(serde_json::from_str::<Probability>(&value.to_string()).is_err());
293        }
294        assert!(CosineSimilarity::new(-1.0).is_ok());
295        assert!(CosineSimilarity::new(1.0).is_ok());
296        assert!(NonNegativeWeight::new(-f64::EPSILON).is_err());
297    }
298
299    #[test]
300    fn bounded_number_serde_round_trips_boundaries() {
301        for value in [0.0, 0.5, 1.0] {
302            let bounded = Probability::new(value).unwrap();
303            let json = serde_json::to_string(&bounded).unwrap();
304            assert_eq!(serde_json::from_str::<Probability>(&json).unwrap(), bounded);
305        }
306    }
307}