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 {
182 _private: (),
183}
184
185#[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 #[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 #[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 #[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#[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#[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#[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 #[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}