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, Deserialize)]
148pub struct AuthorityPermit {
149 pub principal: String,
150 pub caller_id: String,
151 pub capability: String,
152 pub admission: AuthorityAdmission,
153 #[serde(default)]
155 pub origin_authority: Option<crate::OriginAuthorityLabelV1>,
156}
157
158#[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 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 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 pub fn with_origin(mut self, origin: crate::OriginAuthorityLabelV1) -> Self {
225 self.origin_authority = Some(origin);
226 self
227 }
228}
229
230#[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#[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#[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 #[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}