auths_sdk/context.rs
1//! Runtime dependency container for auths-sdk operations.
2//!
3//! [`AuthsContext`] carries all injected infrastructure adapters. Config structs
4//! (e.g. [`crate::types::CreateDeveloperIdentityConfig`]) remain Plain Old Data with no
5//! trait objects.
6
7use std::sync::Arc;
8
9use auths_core::ports::clock::ClockProvider;
10use auths_core::ports::id::{SystemUuidProvider, UuidProvider};
11use auths_core::signing::PassphraseProvider;
12use auths_core::storage::keychain::KeyStorage;
13use auths_id::attestation::export::AttestationSink;
14use auths_id::ports::registry::RegistryBackend;
15use auths_id::storage::attestation::AttestationSource;
16use auths_id::storage::identity::IdentityStorage;
17
18use crate::ports::agent::{AgentSigningPort, NoopAgentProvider};
19
20/// Re-export the canonical `EventSink` trait from `auths-telemetry`.
21pub use auths_telemetry::EventSink;
22
23struct NoopSink;
24
25impl EventSink for NoopSink {
26 fn emit(&self, _payload: &str) {}
27 fn flush(&self) {}
28}
29
30struct NoopPassphraseProvider;
31
32impl PassphraseProvider for NoopPassphraseProvider {
33 fn get_passphrase(
34 &self,
35 _prompt: &str,
36 ) -> Result<zeroize::Zeroizing<String>, auths_core::AgentError> {
37 Err(auths_core::AgentError::SigningFailed(
38 "no passphrase provider configured — call .passphrase_provider(...) on AuthsContextBuilder".into(),
39 ))
40 }
41}
42
43/// All runtime dependencies for auths-sdk operations.
44///
45/// Construct via [`AuthsContext::builder()`]. Config structs carry serializable
46/// data; `AuthsContext` carries injected infrastructure adapters. This separation
47/// allows the SDK to operate as a headless, storage-agnostic library that can be
48/// embedded in cloud SaaS, WASM, or C-FFI runtimes without pulling in tokio,
49/// git2, or std::fs.
50///
51/// Usage:
52/// ```ignore
53/// use std::sync::Arc;
54/// use auths_sdk::context::AuthsContext;
55///
56/// let ctx = AuthsContext::builder()
57/// .registry(Arc::new(my_registry))
58/// .key_storage(Arc::new(my_keychain))
59/// .clock(Arc::new(SystemClock))
60/// .identity_storage(Arc::new(my_identity_storage))
61/// .attestation_sink(Arc::new(my_store.clone()))
62/// .attestation_source(Arc::new(my_store))
63/// .build();
64/// sdk::initialize(config, &ctx)?;
65/// ```
66pub struct AuthsContext {
67 /// Pre-initialized registry storage backend.
68 pub registry: Arc<dyn RegistryBackend + Send + Sync>,
69 /// Platform keychain or test fake for key material storage.
70 pub key_storage: Arc<dyn KeyStorage + Send + Sync>,
71 /// Wall-clock provider for deterministic testing.
72 pub clock: Arc<dyn ClockProvider + Send + Sync>,
73 /// Telemetry sink (defaults to `NoopSink` when not specified).
74 pub event_sink: Arc<dyn EventSink>,
75 /// Identity storage adapter (load/save managed identity).
76 pub identity_storage: Arc<dyn IdentityStorage + Send + Sync>,
77 /// Attestation sink for writing signed attestations.
78 pub attestation_sink: Arc<dyn AttestationSink + Send + Sync>,
79 /// Attestation source for reading existing attestations.
80 pub attestation_source: Arc<dyn AttestationSource + Send + Sync>,
81 /// Passphrase provider for key decryption during signing operations.
82 /// Defaults to `NoopPassphraseProvider` — set via `.passphrase_provider(...)` when
83 /// SDK functions need to sign with encrypted key material.
84 pub passphrase_provider: Arc<dyn PassphraseProvider + Send + Sync>,
85 /// UUID generator port. Defaults to [`SystemUuidProvider`] (random v4 UUIDs).
86 /// Override with a deterministic stub in tests.
87 pub uuid_provider: Arc<dyn UuidProvider + Send + Sync>,
88 /// Agent-based signing port for delegating operations to a running agent process.
89 /// Defaults to [`NoopAgentProvider`] (all operations return `Unavailable`)
90 /// when not called. Set this on Unix platforms where the auths-agent daemon
91 /// is available.
92 pub agent_signing: Arc<dyn AgentSigningPort + Send + Sync>,
93 /// Witness configuration for KEL event receipting.
94 /// When set and enabled, ixn events are submitted to witnesses after commit.
95 pub witness_config: Option<auths_id::witness_config::WitnessConfig>,
96 /// Path to the registry repository (needed for witness receipt storage).
97 pub repo_path: Option<std::path::PathBuf>,
98 /// Optional revocation-freshness source. When set, a credential presentation is refused if the
99 /// locally-held copy of the issuer's logs is too stale to trust its revocation status (a `rev`
100 /// may have landed unseen). Default `None` — no staleness gate. Set via
101 /// [`AuthsContext::with_revocation_freshness`].
102 pub revocation_freshness:
103 Option<Arc<dyn crate::domains::credentials::RevocationFreshnessSource>>,
104}
105
106impl AuthsContext {
107 /// Set the revocation-freshness source (a fluent post-build override). When set, a credential
108 /// presentation is refused if the issuer's locally-held logs are too stale to trust their
109 /// revocation status.
110 ///
111 /// Args:
112 /// * `source`: the per-delegator staleness oracle (e.g. a `PolicyBoundRefresh`).
113 pub fn with_revocation_freshness(
114 mut self,
115 source: Arc<dyn crate::domains::credentials::RevocationFreshnessSource>,
116 ) -> Self {
117 self.revocation_freshness = Some(source);
118 self
119 }
120
121 /// Build witness params from the context's configuration.
122 ///
123 /// Returns `WitnessParams::Enabled` when both `witness_config` and `repo_path`
124 /// are set, `WitnessParams::Disabled` otherwise.
125 pub fn witness_params(&self) -> auths_id::witness_config::WitnessParams<'_> {
126 match (&self.witness_config, &self.repo_path) {
127 (Some(config), Some(path)) => auths_id::witness_config::WitnessParams::Enabled {
128 config,
129 repo_path: path,
130 },
131 _ => auths_id::witness_config::WitnessParams::Disabled,
132 }
133 }
134}
135
136impl AuthsContext {
137 /// Creates a builder for [`AuthsContext`].
138 ///
139 /// All six required fields (`registry`, `key_storage`, `clock`,
140 /// `identity_storage`, `attestation_sink`, `attestation_source`) are enforced
141 /// at compile time — the `build()` method is only available once all six are set.
142 ///
143 /// Usage:
144 /// ```ignore
145 /// let ctx = AuthsContext::builder()
146 /// .registry(Arc::new(my_registry))
147 /// .key_storage(Arc::new(my_keychain))
148 /// .clock(Arc::new(SystemClock))
149 /// .identity_storage(Arc::new(my_identity_storage))
150 /// .attestation_sink(Arc::new(my_store.clone()))
151 /// .attestation_source(Arc::new(my_store))
152 /// .build();
153 /// ```
154 pub fn builder() -> AuthsContextBuilder<Missing, Missing, Missing, Missing, Missing, Missing> {
155 AuthsContextBuilder {
156 registry: Missing,
157 key_storage: Missing,
158 clock: Missing,
159 identity_storage: Missing,
160 attestation_sink: Missing,
161 attestation_source: Missing,
162 event_sink: None,
163 passphrase_provider: None,
164 uuid_provider: None,
165 agent_signing: None,
166 witness_config: None,
167 repo_path: None,
168 }
169 }
170}
171
172/// Typestate marker: required field not yet set.
173pub struct Missing;
174
175/// Typestate marker: required field has been set.
176pub struct Set<T>(T);
177
178/// Typestate builder for [`AuthsContext`].
179///
180/// Call [`AuthsContext::builder()`] to obtain an instance. The `build()` method
181/// is only available once all six required fields have been supplied — omitting any
182/// produces a compile-time error.
183pub struct AuthsContextBuilder<R, K, C, IS, AS, ASrc> {
184 registry: R,
185 key_storage: K,
186 clock: C,
187 identity_storage: IS,
188 attestation_sink: AS,
189 attestation_source: ASrc,
190 event_sink: Option<Arc<dyn EventSink>>,
191 passphrase_provider: Option<Arc<dyn PassphraseProvider + Send + Sync>>,
192 uuid_provider: Option<Arc<dyn UuidProvider + Send + Sync>>,
193 agent_signing: Option<Arc<dyn AgentSigningPort + Send + Sync>>,
194 witness_config: Option<auths_id::witness_config::WitnessConfig>,
195 repo_path: Option<std::path::PathBuf>,
196}
197
198// ── Required field setters (each transitions one typestate slot) ──────────────
199
200impl<K, C, IS, AS, ASrc> AuthsContextBuilder<Missing, K, C, IS, AS, ASrc> {
201 /// Set the registry storage backend.
202 ///
203 /// Args:
204 /// * `registry`: Pre-initialized registry backend.
205 ///
206 /// Usage:
207 /// ```ignore
208 /// builder.registry(Arc::new(my_git_backend))
209 /// ```
210 pub fn registry(
211 self,
212 registry: Arc<dyn RegistryBackend + Send + Sync>,
213 ) -> AuthsContextBuilder<Set<Arc<dyn RegistryBackend + Send + Sync>>, K, C, IS, AS, ASrc> {
214 AuthsContextBuilder {
215 registry: Set(registry),
216 key_storage: self.key_storage,
217 clock: self.clock,
218 identity_storage: self.identity_storage,
219 attestation_sink: self.attestation_sink,
220 attestation_source: self.attestation_source,
221 event_sink: self.event_sink,
222 passphrase_provider: self.passphrase_provider,
223 uuid_provider: self.uuid_provider,
224 agent_signing: self.agent_signing,
225 witness_config: self.witness_config,
226 repo_path: self.repo_path,
227 }
228 }
229}
230
231impl<R, C, IS, AS, ASrc> AuthsContextBuilder<R, Missing, C, IS, AS, ASrc> {
232 /// Set the key storage backend.
233 ///
234 /// Args:
235 /// * `key_storage`: Platform keychain or in-memory test fake.
236 ///
237 /// Usage:
238 /// ```ignore
239 /// builder.key_storage(Arc::new(my_keychain))
240 /// ```
241 pub fn key_storage(
242 self,
243 key_storage: Arc<dyn KeyStorage + Send + Sync>,
244 ) -> AuthsContextBuilder<R, Set<Arc<dyn KeyStorage + Send + Sync>>, C, IS, AS, ASrc> {
245 AuthsContextBuilder {
246 registry: self.registry,
247 key_storage: Set(key_storage),
248 clock: self.clock,
249 identity_storage: self.identity_storage,
250 attestation_sink: self.attestation_sink,
251 attestation_source: self.attestation_source,
252 event_sink: self.event_sink,
253 passphrase_provider: self.passphrase_provider,
254 uuid_provider: self.uuid_provider,
255 agent_signing: self.agent_signing,
256 witness_config: self.witness_config,
257 repo_path: self.repo_path,
258 }
259 }
260}
261
262impl<R, K, IS, AS, ASrc> AuthsContextBuilder<R, K, Missing, IS, AS, ASrc> {
263 /// Set the clock provider.
264 ///
265 /// Args:
266 /// * `clock`: Wall-clock implementation (`SystemClock` in production,
267 /// `MockClock` in tests).
268 ///
269 /// Usage:
270 /// ```ignore
271 /// builder.clock(Arc::new(SystemClock))
272 /// ```
273 pub fn clock(
274 self,
275 clock: Arc<dyn ClockProvider + Send + Sync>,
276 ) -> AuthsContextBuilder<R, K, Set<Arc<dyn ClockProvider + Send + Sync>>, IS, AS, ASrc> {
277 AuthsContextBuilder {
278 registry: self.registry,
279 key_storage: self.key_storage,
280 clock: Set(clock),
281 identity_storage: self.identity_storage,
282 attestation_sink: self.attestation_sink,
283 attestation_source: self.attestation_source,
284 event_sink: self.event_sink,
285 passphrase_provider: self.passphrase_provider,
286 uuid_provider: self.uuid_provider,
287 agent_signing: self.agent_signing,
288 witness_config: self.witness_config,
289 repo_path: self.repo_path,
290 }
291 }
292}
293
294impl<R, K, C, AS, ASrc> AuthsContextBuilder<R, K, C, Missing, AS, ASrc> {
295 /// Set the identity storage adapter.
296 ///
297 /// Args:
298 /// * `storage`: Pre-initialized identity storage implementation.
299 ///
300 /// Usage:
301 /// ```ignore
302 /// builder.identity_storage(Arc::new(my_identity_storage))
303 /// ```
304 pub fn identity_storage(
305 self,
306 storage: Arc<dyn IdentityStorage + Send + Sync>,
307 ) -> AuthsContextBuilder<R, K, C, Set<Arc<dyn IdentityStorage + Send + Sync>>, AS, ASrc> {
308 AuthsContextBuilder {
309 registry: self.registry,
310 key_storage: self.key_storage,
311 clock: self.clock,
312 identity_storage: Set(storage),
313 attestation_sink: self.attestation_sink,
314 attestation_source: self.attestation_source,
315 event_sink: self.event_sink,
316 passphrase_provider: self.passphrase_provider,
317 uuid_provider: self.uuid_provider,
318 agent_signing: self.agent_signing,
319 witness_config: self.witness_config,
320 repo_path: self.repo_path,
321 }
322 }
323}
324
325impl<R, K, C, IS, ASrc> AuthsContextBuilder<R, K, C, IS, Missing, ASrc> {
326 /// Set the attestation sink adapter.
327 ///
328 /// Args:
329 /// * `sink`: Pre-initialized attestation sink implementation.
330 ///
331 /// Usage:
332 /// ```ignore
333 /// builder.attestation_sink(Arc::new(my_attestation_store))
334 /// ```
335 pub fn attestation_sink(
336 self,
337 sink: Arc<dyn AttestationSink + Send + Sync>,
338 ) -> AuthsContextBuilder<R, K, C, IS, Set<Arc<dyn AttestationSink + Send + Sync>>, ASrc> {
339 AuthsContextBuilder {
340 registry: self.registry,
341 key_storage: self.key_storage,
342 clock: self.clock,
343 identity_storage: self.identity_storage,
344 attestation_sink: Set(sink),
345 attestation_source: self.attestation_source,
346 event_sink: self.event_sink,
347 passphrase_provider: self.passphrase_provider,
348 uuid_provider: self.uuid_provider,
349 agent_signing: self.agent_signing,
350 witness_config: self.witness_config,
351 repo_path: self.repo_path,
352 }
353 }
354}
355
356impl<R, K, C, IS, AS> AuthsContextBuilder<R, K, C, IS, AS, Missing> {
357 /// Set the attestation source adapter.
358 ///
359 /// Args:
360 /// * `source`: Pre-initialized attestation source implementation.
361 ///
362 /// Usage:
363 /// ```ignore
364 /// builder.attestation_source(Arc::new(my_attestation_store))
365 /// ```
366 pub fn attestation_source(
367 self,
368 source: Arc<dyn AttestationSource + Send + Sync>,
369 ) -> AuthsContextBuilder<R, K, C, IS, AS, Set<Arc<dyn AttestationSource + Send + Sync>>> {
370 AuthsContextBuilder {
371 registry: self.registry,
372 key_storage: self.key_storage,
373 clock: self.clock,
374 identity_storage: self.identity_storage,
375 attestation_sink: self.attestation_sink,
376 attestation_source: Set(source),
377 event_sink: self.event_sink,
378 passphrase_provider: self.passphrase_provider,
379 uuid_provider: self.uuid_provider,
380 agent_signing: self.agent_signing,
381 witness_config: self.witness_config,
382 repo_path: self.repo_path,
383 }
384 }
385}
386
387// ── Optional field setters (available at any typestate, return Self) ──────────
388
389impl<R, K, C, IS, AS, ASrc> AuthsContextBuilder<R, K, C, IS, AS, ASrc> {
390 /// Set an optional event sink.
391 ///
392 /// Defaults to a no-op sink (all events discarded) when not called.
393 ///
394 /// Args:
395 /// * `sink`: Any type implementing [`EventSink`].
396 ///
397 /// Usage:
398 /// ```ignore
399 /// builder.event_sink(Arc::new(my_sink))
400 /// ```
401 pub fn event_sink(mut self, sink: Arc<dyn EventSink>) -> Self {
402 self.event_sink = Some(sink);
403 self
404 }
405
406 /// Set the passphrase provider for key decryption during signing operations.
407 ///
408 /// Defaults to a noop provider that returns an error. Set this when SDK
409 /// workflow functions will perform signing with encrypted key material.
410 ///
411 /// Args:
412 /// * `provider`: Any type implementing [`PassphraseProvider`].
413 ///
414 /// Usage:
415 /// ```ignore
416 /// builder.passphrase_provider(Arc::new(PrefilledPassphraseProvider::new(passphrase)))
417 /// ```
418 pub fn passphrase_provider(
419 mut self,
420 provider: Arc<dyn PassphraseProvider + Send + Sync>,
421 ) -> Self {
422 self.passphrase_provider = Some(provider);
423 self
424 }
425
426 /// Set the UUID provider.
427 ///
428 /// Defaults to [`SystemUuidProvider`] (random v4 UUIDs) when not called.
429 /// Override with a deterministic stub in tests.
430 ///
431 /// Args:
432 /// * `provider`: Any type implementing [`UuidProvider`].
433 ///
434 /// Usage:
435 /// ```ignore
436 /// builder.uuid_provider(Arc::new(my_uuid_stub))
437 /// ```
438 pub fn uuid_provider(mut self, provider: Arc<dyn UuidProvider + Send + Sync>) -> Self {
439 self.uuid_provider = Some(provider);
440 self
441 }
442
443 /// Set the agent signing port for delegating signing to a running agent process.
444 ///
445 /// Defaults to a noop provider (all operations return `Unavailable`)
446 /// when not called. Set this on Unix platforms where the auths-agent daemon
447 /// is available.
448 ///
449 /// Args:
450 /// * `provider`: Any type implementing [`AgentSigningPort`].
451 ///
452 /// Usage:
453 /// ```ignore
454 /// builder.agent_signing(Arc::new(CliAgentAdapter::new(socket_path)))
455 /// ```
456 pub fn agent_signing(mut self, provider: Arc<dyn AgentSigningPort + Send + Sync>) -> Self {
457 self.agent_signing = Some(provider);
458 self
459 }
460
461 /// Set witness configuration for KEL event receipting.
462 pub fn witness_config(mut self, config: auths_id::witness_config::WitnessConfig) -> Self {
463 self.witness_config = Some(config);
464 self
465 }
466
467 /// Set the repository path (needed for witness receipt storage).
468 pub fn repo_path(mut self, path: std::path::PathBuf) -> Self {
469 self.repo_path = Some(path);
470 self
471 }
472}
473
474// ── Infallible build — only available when all six required fields are set ────
475
476impl
477 AuthsContextBuilder<
478 Set<Arc<dyn RegistryBackend + Send + Sync>>,
479 Set<Arc<dyn KeyStorage + Send + Sync>>,
480 Set<Arc<dyn ClockProvider + Send + Sync>>,
481 Set<Arc<dyn IdentityStorage + Send + Sync>>,
482 Set<Arc<dyn AttestationSink + Send + Sync>>,
483 Set<Arc<dyn AttestationSource + Send + Sync>>,
484 >
485{
486 /// Build the [`AuthsContext`].
487 ///
488 /// Infallible — only callable once all six required fields are set.
489 /// Omitting any required field is a compile-time error.
490 ///
491 /// Usage:
492 /// ```ignore
493 /// let ctx = AuthsContext::builder()
494 /// .registry(Arc::new(my_registry))
495 /// .key_storage(Arc::new(my_keychain))
496 /// .clock(Arc::new(SystemClock))
497 /// .identity_storage(Arc::new(my_identity_storage))
498 /// .attestation_sink(Arc::new(my_store.clone()))
499 /// .attestation_source(Arc::new(my_store))
500 /// .build();
501 /// ```
502 pub fn build(self) -> AuthsContext {
503 AuthsContext {
504 registry: self.registry.0,
505 key_storage: self.key_storage.0,
506 clock: self.clock.0,
507 identity_storage: self.identity_storage.0,
508 attestation_sink: self.attestation_sink.0,
509 attestation_source: self.attestation_source.0,
510 event_sink: self.event_sink.unwrap_or_else(|| Arc::new(NoopSink)),
511 passphrase_provider: self
512 .passphrase_provider
513 .unwrap_or_else(|| Arc::new(NoopPassphraseProvider)),
514 uuid_provider: self
515 .uuid_provider
516 .unwrap_or_else(|| Arc::new(SystemUuidProvider)),
517 agent_signing: self
518 .agent_signing
519 .unwrap_or_else(|| Arc::new(NoopAgentProvider)),
520 witness_config: self.witness_config,
521 repo_path: self.repo_path,
522 revocation_freshness: None,
523 }
524 }
525}