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///
148/// Ordinary callers cannot construct or deserialize this credential:
149/// ```compile_fail
150/// use semantic_memory::{AuthorityAdmission, AuthorityPermit};
151/// let _forged = AuthorityPermit {
152///     principal: "attacker".into(),
153///     caller_id: "attacker".into(),
154///     capability: AuthorityPermit::APPEND_CAPABILITY.into(),
155///     admission: AuthorityAdmission::OperatorSystem,
156///     origin_authority: None,
157/// };
158/// ```
159#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
160pub struct AuthorityPermit {
161    pub(crate) principal: String,
162    pub(crate) caller_id: String,
163    pub(crate) capability: String,
164    pub(crate) admission: AuthorityAdmission,
165    /// Immutable origin proposed for the governed write. Missing origin fails closed.
166    #[serde(default)]
167    pub(crate) origin_authority: Option<crate::OriginAuthorityLabelV1>,
168}
169
170/// Admission basis carried by a governed authority permit.
171#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
172#[serde(rename_all = "snake_case")]
173pub enum AuthorityAdmission {
174    Unspecified,
175    Evidence { evidence_refs: Vec<String> },
176    OperatorSystem,
177}
178
179/// Trusted in-process issuer. It has no public constructor, so ordinary callers cannot mint
180/// capability-bearing permits or supply caller identities as request data.
181pub struct AuthorityIssuer {
182    _private: (),
183}
184
185/// Resolver-produced immutable evidence identity.
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub struct ResolvedEvidenceDigest(String);
188
189impl AuthorityIssuer {
190    #[cfg(feature = "testing")]
191    pub(crate) fn trusted() -> Self {
192        Self { _private: () }
193    }
194
195    pub fn mint_operator_system(
196        &self,
197        principal: impl Into<String>,
198        caller_id: impl Into<String>,
199        capability: impl Into<String>,
200    ) -> AuthorityPermit {
201        let principal = principal.into();
202        let caller_id = caller_id.into();
203        AuthorityPermit {
204            origin_authority: Some(crate::OriginAuthorityLabelV1::operator_system(
205                &principal, &caller_id,
206            )),
207            principal,
208            caller_id,
209            capability: capability.into(),
210            admission: AuthorityAdmission::OperatorSystem,
211        }
212    }
213
214    pub fn mint_with_resolved_evidence(
215        &self,
216        principal: impl Into<String>,
217        caller_id: impl Into<String>,
218        capability: impl Into<String>,
219        evidence: Vec<ResolvedEvidenceDigest>,
220        origin_authority: crate::OriginAuthorityLabelV1,
221    ) -> AuthorityPermit {
222        AuthorityPermit {
223            principal: principal.into(),
224            caller_id: caller_id.into(),
225            capability: capability.into(),
226            admission: AuthorityAdmission::Evidence {
227                evidence_refs: evidence.into_iter().map(|digest| digest.0).collect(),
228            },
229            origin_authority: Some(origin_authority),
230        }
231    }
232}
233
234impl ResolvedEvidenceDigest {
235    pub fn from_resolver(digest: String) -> Option<Self> {
236        let hex = digest.strip_prefix("blake3:")?;
237        (hex.len() == 64 && hex.bytes().all(|byte| byte.is_ascii_hexdigit()))
238            .then_some(Self(digest))
239    }
240}
241
242impl AuthorityPermit {
243    pub const APPEND_CAPABILITY: &'static str = "memory.authority.append";
244    pub const SUPERSEDE_CAPABILITY: &'static str = "memory.authority.supersede";
245    pub const REDACT_CAPABILITY: &'static str = "memory.authority.redact";
246    pub const REVOKE_ORIGIN_CAPABILITY: &'static str = "memory.authority.revoke_origin";
247    pub const FORGET_CAPABILITY: &'static str = "memory.authority.forget";
248
249    #[cfg(feature = "testing")]
250    pub fn new(
251        principal: impl Into<String>,
252        caller_id: impl Into<String>,
253        capability: impl Into<String>,
254    ) -> Self {
255        Self {
256            principal: principal.into(),
257            caller_id: caller_id.into(),
258            capability: capability.into(),
259            admission: AuthorityAdmission::Unspecified,
260            origin_authority: None,
261        }
262    }
263
264    /// Construct a permit for a fact proposal supported by explicit evidence.
265    #[cfg(feature = "testing")]
266    pub fn with_evidence(
267        principal: impl Into<String>,
268        caller_id: impl Into<String>,
269        capability: impl Into<String>,
270        evidence_refs: Vec<String>,
271    ) -> Self {
272        Self {
273            principal: principal.into(),
274            caller_id: caller_id.into(),
275            capability: capability.into(),
276            admission: AuthorityAdmission::Evidence { evidence_refs },
277            origin_authority: None,
278        }
279    }
280
281    /// Construct the explicit operator/system bypass used for trusted inserts.
282    #[cfg(feature = "testing")]
283    pub fn operator_system(
284        principal: impl Into<String>,
285        caller_id: impl Into<String>,
286        capability: impl Into<String>,
287    ) -> Self {
288        AuthorityIssuer::trusted().mint_operator_system(principal, caller_id, capability)
289    }
290
291    /// Bind an immutable origin label to this governed write permit.
292    #[cfg(feature = "testing")]
293    pub fn with_origin(mut self, origin: crate::OriginAuthorityLabelV1) -> Self {
294        self.origin_authority = Some(origin);
295        self
296    }
297}
298
299/// Authority mutation kind recorded in the operation journal and receipt.
300#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
301#[serde(rename_all = "snake_case")]
302pub enum AuthorityOperationKind {
303    Append,
304    Supersede,
305    Redact,
306}
307
308/// Typed fault-injection points for atomic authority tests.
309#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
310#[serde(rename_all = "snake_case")]
311pub enum AuthorityFaultStage {
312    BeforeAppend,
313    AfterAppend,
314    BeforeLineage,
315    AfterLineage,
316    BeforeJournal,
317    AfterJournal,
318    BeforeEpoch,
319    AfterEpoch,
320    BeforeReceipt,
321    AfterReceipt,
322    BeforeForgettingMutation,
323    AfterForgettingMutation,
324    BeforeForgettingReceipt,
325    AfterForgettingReceipt,
326    BeforeShadowPromotion,
327    AfterShadowPromotion,
328}
329
330/// Durable receipt for one governed authority mutation.
331#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
332pub struct AuthorityReceiptV1 {
333    pub schema_version: String,
334    pub receipt_id: String,
335    pub operation_id: String,
336    pub caller_idempotency_key: String,
337    pub principal: String,
338    pub caller_id: String,
339    pub operation_kind: AuthorityOperationKind,
340    pub before_snapshot_id: AuthoritySnapshotId,
341    pub after_snapshot_id: AuthoritySnapshotId,
342    pub before_epoch: RetrievalEpoch,
343    pub after_epoch: RetrievalEpoch,
344    pub affected_ids: Vec<String>,
345    pub content_digest: String,
346    /// Digest of the immutable origin label persisted for the written fact.
347    #[serde(default)]
348    pub origin_label_digest: Option<String>,
349    pub receipt_digest: String,
350    pub committed_at: String,
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    #[test]
358    fn bounded_numbers_reject_non_finite_and_out_of_range() {
359        for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, -0.1, 1.1] {
360            assert!(Probability::new(value).is_err());
361            assert!(serde_json::from_str::<Probability>(&value.to_string()).is_err());
362        }
363        assert!(CosineSimilarity::new(-1.0).is_ok());
364        assert!(CosineSimilarity::new(1.0).is_ok());
365        assert!(NonNegativeWeight::new(-f64::EPSILON).is_err());
366    }
367
368    #[test]
369    fn bounded_number_serde_round_trips_boundaries() {
370        for value in [0.0, 0.5, 1.0] {
371            let bounded = Probability::new(value).unwrap();
372            let json = serde_json::to_string(&bounded).unwrap();
373            assert_eq!(serde_json::from_str::<Probability>(&json).unwrap(), bounded);
374        }
375    }
376}