1use std::sync::Arc;
2
3use ed25519_dalek::VerifyingKey;
4
5use crate::audit::{AuditSink, NoopAuditSink};
6use crate::chain::{AuthorizedAction, BatchAuthorizeResult, Clock, DyoloChain, SystemClock};
7use crate::error::A1Error;
8use crate::intent::{IntentHash, MerkleProof};
9use crate::policy::PolicySet;
10use crate::registry::{MemoryNonceStore, MemoryRevocationStore, NonceStore, RevocationStore};
11
12pub struct A1Context {
33 pub revocation: Arc<dyn RevocationStore>,
34 pub nonces: Arc<dyn NonceStore>,
35 pub clock: Arc<dyn Clock + Send + Sync>,
36 pub policy: Option<PolicySet>,
37 pub audit: Arc<dyn AuditSink>,
38 pub namespace: Option<String>,
39}
40
41impl A1Context {
42 pub fn builder() -> A1ContextBuilder {
43 A1ContextBuilder::default()
44 }
45
46 pub fn authorize(
47 &self,
48 chain: &DyoloChain,
49 agent_pk: &VerifyingKey,
50 intent: &IntentHash,
51 proof: &MerkleProof,
52 ) -> Result<AuthorizedAction, A1Error> {
53 chain.authorize_with_options(
54 agent_pk,
55 intent,
56 proof,
57 self.clock.as_ref(),
58 self.revocation.as_ref(),
59 self.nonces.as_ref(),
60 self.policy.as_ref(),
61 self.audit.as_ref(),
62 )
63 }
64
65 pub fn authorize_batch(
66 &self,
67 chain: &DyoloChain,
68 agent_pk: &VerifyingKey,
69 intents: &[(IntentHash, MerkleProof)],
70 ) -> BatchAuthorizeResult {
71 chain.authorize_batch(
72 agent_pk,
73 intents,
74 self.clock.as_ref(),
75 self.revocation.as_ref(),
76 self.nonces.as_ref(),
77 )
78 }
79
80 pub fn health_check(&self) -> Result<(), A1Error> {
85 self.revocation
86 .health_check()
87 .map_err(|e| A1Error::StorageUnhealthy(format!("revocation: {e}")))?;
88 self.nonces
89 .health_check()
90 .map_err(|e| A1Error::StorageUnhealthy(format!("nonces: {e}")))?;
91 Ok(())
92 }
93}
94
95#[cfg(feature = "async")]
98pub struct AsyncA1Context {
99 pub revocation: Arc<dyn crate::registry::r#async::AsyncRevocationStore>,
100 pub nonces: Arc<dyn crate::registry::r#async::AsyncNonceStore>,
101 pub clock: Arc<dyn Clock + Send + Sync>,
102 pub policy: Option<PolicySet>,
103 pub audit: Arc<dyn AuditSink>,
104 pub namespace: Option<String>,
105}
106
107#[cfg(feature = "async")]
108impl AsyncA1Context {
109 pub fn builder() -> AsyncA1ContextBuilder {
110 AsyncA1ContextBuilder::default()
111 }
112
113 pub async fn authorize(
114 &self,
115 chain: &DyoloChain,
116 agent_pk: &VerifyingKey,
117 intent: &IntentHash,
118 proof: &MerkleProof,
119 ) -> Result<AuthorizedAction, A1Error> {
120 chain
121 .authorize_async_with_options(
122 agent_pk,
123 intent,
124 proof,
125 self.clock.as_ref(),
126 self.revocation.as_ref(),
127 self.nonces.as_ref(),
128 self.policy.as_ref(),
129 self.audit.as_ref(),
130 )
131 .await
132 }
133
134 pub async fn authorize_batch(
135 &self,
136 chain: &DyoloChain,
137 agent_pk: &VerifyingKey,
138 intents: &[(IntentHash, MerkleProof)],
139 ) -> BatchAuthorizeResult {
140 chain
141 .authorize_batch_async(
142 agent_pk,
143 intents,
144 self.clock.as_ref(),
145 self.revocation.as_ref(),
146 self.nonces.as_ref(),
147 )
148 .await
149 }
150
151 pub async fn health_check(&self) -> Result<(), A1Error> {
152 self.revocation
153 .health_check()
154 .await
155 .map_err(|e| A1Error::StorageUnhealthy(format!("revocation: {e}")))?;
156 self.nonces
157 .health_check()
158 .await
159 .map_err(|e| A1Error::StorageUnhealthy(format!("nonces: {e}")))?;
160 Ok(())
161 }
162}
163
164#[derive(Default)]
167pub struct A1ContextBuilder {
168 revocation: Option<Arc<dyn RevocationStore>>,
169 nonces: Option<Arc<dyn NonceStore>>,
170 clock: Option<Arc<dyn Clock + Send + Sync>>,
171 policy: Option<PolicySet>,
172 audit: Option<Arc<dyn AuditSink>>,
173 namespace: Option<String>,
174}
175
176impl A1ContextBuilder {
177 pub fn revocation(mut self, store: impl RevocationStore + 'static) -> Self {
178 self.revocation = Some(Arc::new(store));
179 self
180 }
181
182 pub fn nonces(mut self, store: impl NonceStore + 'static) -> Self {
183 self.nonces = Some(Arc::new(store));
184 self
185 }
186
187 pub fn clock(mut self, clock: impl Clock + Send + Sync + 'static) -> Self {
188 self.clock = Some(Arc::new(clock));
189 self
190 }
191
192 pub fn policy(mut self, policy: PolicySet) -> Self {
193 self.policy = Some(policy);
194 self
195 }
196
197 pub fn audit(mut self, sink: impl AuditSink + 'static) -> Self {
198 self.audit = Some(Arc::new(sink));
199 self
200 }
201
202 pub fn namespace(mut self, ns: impl Into<String>) -> Self {
203 self.namespace = Some(ns.into());
204 self
205 }
206
207 pub fn build(self) -> A1Context {
208 A1Context {
209 revocation: self
210 .revocation
211 .unwrap_or_else(|| Arc::new(MemoryRevocationStore::new())),
212 nonces: self
213 .nonces
214 .unwrap_or_else(|| Arc::new(MemoryNonceStore::new())),
215 clock: self.clock.unwrap_or_else(|| Arc::new(SystemClock)),
216 policy: self.policy,
217 audit: self.audit.unwrap_or_else(|| Arc::new(NoopAuditSink)),
218 namespace: self.namespace,
219 }
220 }
221}
222
223#[cfg(feature = "async")]
226#[derive(Default)]
227pub struct AsyncA1ContextBuilder {
228 revocation: Option<Arc<dyn crate::registry::r#async::AsyncRevocationStore>>,
229 nonces: Option<Arc<dyn crate::registry::r#async::AsyncNonceStore>>,
230 clock: Option<Arc<dyn Clock + Send + Sync>>,
231 policy: Option<PolicySet>,
232 audit: Option<Arc<dyn AuditSink>>,
233 namespace: Option<String>,
234}
235
236#[cfg(feature = "async")]
237impl AsyncA1ContextBuilder {
238 pub fn revocation(
239 mut self,
240 store: impl crate::registry::r#async::AsyncRevocationStore + 'static,
241 ) -> Self {
242 self.revocation = Some(Arc::new(store));
243 self
244 }
245
246 pub fn nonces(
247 mut self,
248 store: impl crate::registry::r#async::AsyncNonceStore + 'static,
249 ) -> Self {
250 self.nonces = Some(Arc::new(store));
251 self
252 }
253
254 pub fn clock(mut self, clock: impl Clock + Send + Sync + 'static) -> Self {
255 self.clock = Some(Arc::new(clock));
256 self
257 }
258
259 pub fn policy(mut self, policy: PolicySet) -> Self {
260 self.policy = Some(policy);
261 self
262 }
263
264 pub fn audit(mut self, sink: impl AuditSink + 'static) -> Self {
265 self.audit = Some(Arc::new(sink));
266 self
267 }
268
269 pub fn namespace(mut self, ns: impl Into<String>) -> Self {
270 self.namespace = Some(ns.into());
271 self
272 }
273
274 pub fn build(self) -> AsyncA1Context {
275 use crate::registry::r#async::{SyncNonceAdapter, SyncRevocationAdapter};
276
277 AsyncA1Context {
278 revocation: self.revocation.unwrap_or_else(|| {
279 Arc::new(SyncRevocationAdapter(
280 Arc::new(MemoryRevocationStore::new()),
281 ))
282 }),
283 nonces: self
284 .nonces
285 .unwrap_or_else(|| Arc::new(SyncNonceAdapter(Arc::new(MemoryNonceStore::new())))),
286 clock: self.clock.unwrap_or_else(|| Arc::new(SystemClock)),
287 policy: self.policy,
288 audit: self.audit.unwrap_or_else(|| Arc::new(NoopAuditSink)),
289 namespace: self.namespace,
290 }
291 }
292}
293
294#[cfg(test)]
297mod tests {
298 use super::*;
299 #[allow(deprecated)]
300 use crate::{
301 cert::CertBuilder,
302 identity::DyoloIdentity,
303 intent::{intent_hash, IntentTree},
304 };
305
306 #[test]
307 #[allow(deprecated)]
308 fn context_builder_defaults_and_authorizes() {
309 let ctx = A1Context::builder().build();
310 let human = DyoloIdentity::generate();
311 let agent = DyoloIdentity::generate();
312 let trade = intent_hash("trade.equity", b"");
313 let tree = IntentTree::build(vec![trade]).unwrap();
314 let root = tree.root();
315 let now = SystemClock.unix_now();
316 let cert =
317 CertBuilder::new(agent.verifying_key(), root, now, now + 86400 * 365).sign(&human);
318
319 let mut chain = DyoloChain::new(human.verifying_key(), root).with_drift_tolerance(9999999);
320 chain.push(cert);
321
322 let proof = tree.prove(&trade).unwrap();
323 let result = ctx.authorize(&chain, &agent.verifying_key(), &trade, &proof);
324 assert!(
325 result.is_ok(),
326 "context builder authorization failed: {:?}",
327 result.err()
328 );
329 }
330
331 #[test]
332 fn sync_context_health_check_returns_ok() {
333 let ctx = A1Context::builder().build();
334 assert!(ctx.health_check().is_ok());
335 }
336}