1use 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#[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#[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 #[serde(default)]
167 pub(crate) origin_authority: Option<crate::OriginAuthorityLabelV1>,
168}
169
170#[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
179pub struct AuthorityIssuer {
186 _private: (),
187}
188
189impl Clone for AuthorityIssuer {
190 fn clone(&self) -> Self {
191 Self { _private: () }
192 }
193}
194
195impl AuthorityIssuer {
196 #[cfg(feature = "testing")]
197 pub(crate) fn trusted() -> Self {
198 Self { _private: () }
199 }
200
201 pub fn from_operator_token(token: &str) -> Option<Self> {
211 let token = token.trim();
212 if token.is_empty() || token.chars().any(char::is_whitespace) {
213 return None;
214 }
215 Some(Self { _private: () })
216 }
217
218 pub fn mint_operator_system(
219 &self,
220 principal: impl Into<String>,
221 caller_id: impl Into<String>,
222 capability: impl Into<String>,
223 ) -> AuthorityPermit {
224 let principal = principal.into();
225 let caller_id = caller_id.into();
226 AuthorityPermit {
227 origin_authority: Some(crate::OriginAuthorityLabelV1::operator_system(
228 &principal, &caller_id,
229 )),
230 principal,
231 caller_id,
232 capability: capability.into(),
233 admission: AuthorityAdmission::OperatorSystem,
234 }
235 }
236
237 pub fn mint_with_resolved_evidence(
238 &self,
239 principal: impl Into<String>,
240 caller_id: impl Into<String>,
241 capability: impl Into<String>,
242 evidence: Vec<ResolvedEvidenceDigest>,
243 origin_authority: crate::OriginAuthorityLabelV1,
244 ) -> AuthorityPermit {
245 AuthorityPermit {
246 principal: principal.into(),
247 caller_id: caller_id.into(),
248 capability: capability.into(),
249 admission: AuthorityAdmission::Evidence {
250 evidence_refs: evidence.into_iter().map(|digest| digest.0).collect(),
251 },
252 origin_authority: Some(origin_authority),
253 }
254 }
255}
256
257#[derive(Debug, Clone, PartialEq, Eq)]
259pub struct ResolvedEvidenceDigest(String);
260
261impl ResolvedEvidenceDigest {
262 pub fn from_resolver(digest: String) -> Option<Self> {
263 let hex = digest.strip_prefix("blake3:")?;
264 (hex.len() == 64 && hex.bytes().all(|byte| byte.is_ascii_hexdigit()))
265 .then_some(Self(digest))
266 }
267}
268
269impl AuthorityPermit {
270 pub const APPEND_CAPABILITY: &'static str = "memory.authority.append";
271 pub const SUPERSEDE_CAPABILITY: &'static str = "memory.authority.supersede";
272 pub const REDACT_CAPABILITY: &'static str = "memory.authority.redact";
273 pub const REVOKE_ORIGIN_CAPABILITY: &'static str = "memory.authority.revoke_origin";
274 pub const FORGET_CAPABILITY: &'static str = "memory.authority.forget";
275
276 #[cfg(feature = "testing")]
277 pub fn new(
278 principal: impl Into<String>,
279 caller_id: impl Into<String>,
280 capability: impl Into<String>,
281 ) -> Self {
282 Self {
283 principal: principal.into(),
284 caller_id: caller_id.into(),
285 capability: capability.into(),
286 admission: AuthorityAdmission::Unspecified,
287 origin_authority: None,
288 }
289 }
290
291 #[cfg(feature = "testing")]
293 pub fn with_evidence(
294 principal: impl Into<String>,
295 caller_id: impl Into<String>,
296 capability: impl Into<String>,
297 evidence_refs: Vec<String>,
298 ) -> Self {
299 Self {
300 principal: principal.into(),
301 caller_id: caller_id.into(),
302 capability: capability.into(),
303 admission: AuthorityAdmission::Evidence { evidence_refs },
304 origin_authority: None,
305 }
306 }
307
308 #[cfg(feature = "testing")]
310 pub fn operator_system(
311 principal: impl Into<String>,
312 caller_id: impl Into<String>,
313 capability: impl Into<String>,
314 ) -> Self {
315 AuthorityIssuer::trusted().mint_operator_system(principal, caller_id, capability)
316 }
317
318 #[cfg(feature = "testing")]
320 pub fn with_origin(mut self, origin: crate::OriginAuthorityLabelV1) -> Self {
321 self.origin_authority = Some(origin);
322 self
323 }
324}
325
326#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
328#[serde(rename_all = "snake_case")]
329pub enum AuthorityOperationKind {
330 Append,
331 Supersede,
332 Redact,
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
337#[serde(rename_all = "snake_case")]
338pub enum AuthorityFaultStage {
339 BeforeAppend,
340 AfterAppend,
341 BeforeLineage,
342 AfterLineage,
343 BeforeJournal,
344 AfterJournal,
345 BeforeEpoch,
346 AfterEpoch,
347 BeforeReceipt,
348 AfterReceipt,
349 BeforeForgettingMutation,
350 AfterForgettingMutation,
351 BeforeForgettingReceipt,
352 AfterForgettingReceipt,
353 BeforeShadowPromotion,
354 AfterShadowPromotion,
355}
356
357#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
359pub struct AuthorityReceiptV1 {
360 pub schema_version: String,
361 pub receipt_id: String,
362 pub operation_id: String,
363 pub caller_idempotency_key: String,
364 pub principal: String,
365 pub caller_id: String,
366 pub operation_kind: AuthorityOperationKind,
367 pub before_snapshot_id: AuthoritySnapshotId,
368 pub after_snapshot_id: AuthoritySnapshotId,
369 pub before_epoch: RetrievalEpoch,
370 pub after_epoch: RetrievalEpoch,
371 pub affected_ids: Vec<String>,
372 pub content_digest: String,
373 #[serde(default)]
375 pub origin_label_digest: Option<String>,
376 pub receipt_digest: String,
377 pub committed_at: String,
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[test]
385 fn bounded_numbers_reject_non_finite_and_out_of_range() {
386 for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, -0.1, 1.1] {
387 assert!(Probability::new(value).is_err());
388 assert!(serde_json::from_str::<Probability>(&value.to_string()).is_err());
389 }
390 assert!(CosineSimilarity::new(-1.0).is_ok());
391 assert!(CosineSimilarity::new(1.0).is_ok());
392 assert!(NonNegativeWeight::new(-f64::EPSILON).is_err());
393 }
394
395 #[test]
396 fn bounded_number_serde_round_trips_boundaries() {
397 for value in [0.0, 0.5, 1.0] {
398 let bounded = Probability::new(value).unwrap();
399 let json = serde_json::to_string(&bounded).unwrap();
400 assert_eq!(serde_json::from_str::<Probability>(&json).unwrap(), bounded);
401 }
402 }
403}