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}
99
100impl AuthsContext {
101 /// Build witness params from the context's configuration.
102 ///
103 /// Returns `WitnessParams::Enabled` when both `witness_config` and `repo_path`
104 /// are set, `WitnessParams::Disabled` otherwise.
105 pub fn witness_params(&self) -> auths_id::witness_config::WitnessParams<'_> {
106 match (&self.witness_config, &self.repo_path) {
107 (Some(config), Some(path)) => auths_id::witness_config::WitnessParams::Enabled {
108 config,
109 repo_path: path,
110 },
111 _ => auths_id::witness_config::WitnessParams::Disabled,
112 }
113 }
114}
115
116impl AuthsContext {
117 /// Creates a builder for [`AuthsContext`].
118 ///
119 /// All six required fields (`registry`, `key_storage`, `clock`,
120 /// `identity_storage`, `attestation_sink`, `attestation_source`) are enforced
121 /// at compile time — the `build()` method is only available once all six are set.
122 ///
123 /// Usage:
124 /// ```ignore
125 /// let ctx = AuthsContext::builder()
126 /// .registry(Arc::new(my_registry))
127 /// .key_storage(Arc::new(my_keychain))
128 /// .clock(Arc::new(SystemClock))
129 /// .identity_storage(Arc::new(my_identity_storage))
130 /// .attestation_sink(Arc::new(my_store.clone()))
131 /// .attestation_source(Arc::new(my_store))
132 /// .build();
133 /// ```
134 pub fn builder() -> AuthsContextBuilder<Missing, Missing, Missing, Missing, Missing, Missing> {
135 AuthsContextBuilder {
136 registry: Missing,
137 key_storage: Missing,
138 clock: Missing,
139 identity_storage: Missing,
140 attestation_sink: Missing,
141 attestation_source: Missing,
142 event_sink: None,
143 passphrase_provider: None,
144 uuid_provider: None,
145 agent_signing: None,
146 witness_config: None,
147 repo_path: None,
148 }
149 }
150}
151
152/// Typestate marker: required field not yet set.
153pub struct Missing;
154
155/// Typestate marker: required field has been set.
156pub struct Set<T>(T);
157
158/// Typestate builder for [`AuthsContext`].
159///
160/// Call [`AuthsContext::builder()`] to obtain an instance. The `build()` method
161/// is only available once all six required fields have been supplied — omitting any
162/// produces a compile-time error.
163pub struct AuthsContextBuilder<R, K, C, IS, AS, ASrc> {
164 registry: R,
165 key_storage: K,
166 clock: C,
167 identity_storage: IS,
168 attestation_sink: AS,
169 attestation_source: ASrc,
170 event_sink: Option<Arc<dyn EventSink>>,
171 passphrase_provider: Option<Arc<dyn PassphraseProvider + Send + Sync>>,
172 uuid_provider: Option<Arc<dyn UuidProvider + Send + Sync>>,
173 agent_signing: Option<Arc<dyn AgentSigningPort + Send + Sync>>,
174 witness_config: Option<auths_id::witness_config::WitnessConfig>,
175 repo_path: Option<std::path::PathBuf>,
176}
177
178// ── Required field setters (each transitions one typestate slot) ──────────────
179
180impl<K, C, IS, AS, ASrc> AuthsContextBuilder<Missing, K, C, IS, AS, ASrc> {
181 /// Set the registry storage backend.
182 ///
183 /// Args:
184 /// * `registry`: Pre-initialized registry backend.
185 ///
186 /// Usage:
187 /// ```ignore
188 /// builder.registry(Arc::new(my_git_backend))
189 /// ```
190 pub fn registry(
191 self,
192 registry: Arc<dyn RegistryBackend + Send + Sync>,
193 ) -> AuthsContextBuilder<Set<Arc<dyn RegistryBackend + Send + Sync>>, K, C, IS, AS, ASrc> {
194 AuthsContextBuilder {
195 registry: Set(registry),
196 key_storage: self.key_storage,
197 clock: self.clock,
198 identity_storage: self.identity_storage,
199 attestation_sink: self.attestation_sink,
200 attestation_source: self.attestation_source,
201 event_sink: self.event_sink,
202 passphrase_provider: self.passphrase_provider,
203 uuid_provider: self.uuid_provider,
204 agent_signing: self.agent_signing,
205 witness_config: self.witness_config,
206 repo_path: self.repo_path,
207 }
208 }
209}
210
211impl<R, C, IS, AS, ASrc> AuthsContextBuilder<R, Missing, C, IS, AS, ASrc> {
212 /// Set the key storage backend.
213 ///
214 /// Args:
215 /// * `key_storage`: Platform keychain or in-memory test fake.
216 ///
217 /// Usage:
218 /// ```ignore
219 /// builder.key_storage(Arc::new(my_keychain))
220 /// ```
221 pub fn key_storage(
222 self,
223 key_storage: Arc<dyn KeyStorage + Send + Sync>,
224 ) -> AuthsContextBuilder<R, Set<Arc<dyn KeyStorage + Send + Sync>>, C, IS, AS, ASrc> {
225 AuthsContextBuilder {
226 registry: self.registry,
227 key_storage: Set(key_storage),
228 clock: self.clock,
229 identity_storage: self.identity_storage,
230 attestation_sink: self.attestation_sink,
231 attestation_source: self.attestation_source,
232 event_sink: self.event_sink,
233 passphrase_provider: self.passphrase_provider,
234 uuid_provider: self.uuid_provider,
235 agent_signing: self.agent_signing,
236 witness_config: self.witness_config,
237 repo_path: self.repo_path,
238 }
239 }
240}
241
242impl<R, K, IS, AS, ASrc> AuthsContextBuilder<R, K, Missing, IS, AS, ASrc> {
243 /// Set the clock provider.
244 ///
245 /// Args:
246 /// * `clock`: Wall-clock implementation (`SystemClock` in production,
247 /// `MockClock` in tests).
248 ///
249 /// Usage:
250 /// ```ignore
251 /// builder.clock(Arc::new(SystemClock))
252 /// ```
253 pub fn clock(
254 self,
255 clock: Arc<dyn ClockProvider + Send + Sync>,
256 ) -> AuthsContextBuilder<R, K, Set<Arc<dyn ClockProvider + Send + Sync>>, IS, AS, ASrc> {
257 AuthsContextBuilder {
258 registry: self.registry,
259 key_storage: self.key_storage,
260 clock: Set(clock),
261 identity_storage: self.identity_storage,
262 attestation_sink: self.attestation_sink,
263 attestation_source: self.attestation_source,
264 event_sink: self.event_sink,
265 passphrase_provider: self.passphrase_provider,
266 uuid_provider: self.uuid_provider,
267 agent_signing: self.agent_signing,
268 witness_config: self.witness_config,
269 repo_path: self.repo_path,
270 }
271 }
272}
273
274impl<R, K, C, AS, ASrc> AuthsContextBuilder<R, K, C, Missing, AS, ASrc> {
275 /// Set the identity storage adapter.
276 ///
277 /// Args:
278 /// * `storage`: Pre-initialized identity storage implementation.
279 ///
280 /// Usage:
281 /// ```ignore
282 /// builder.identity_storage(Arc::new(my_identity_storage))
283 /// ```
284 pub fn identity_storage(
285 self,
286 storage: Arc<dyn IdentityStorage + Send + Sync>,
287 ) -> AuthsContextBuilder<R, K, C, Set<Arc<dyn IdentityStorage + Send + Sync>>, AS, ASrc> {
288 AuthsContextBuilder {
289 registry: self.registry,
290 key_storage: self.key_storage,
291 clock: self.clock,
292 identity_storage: Set(storage),
293 attestation_sink: self.attestation_sink,
294 attestation_source: self.attestation_source,
295 event_sink: self.event_sink,
296 passphrase_provider: self.passphrase_provider,
297 uuid_provider: self.uuid_provider,
298 agent_signing: self.agent_signing,
299 witness_config: self.witness_config,
300 repo_path: self.repo_path,
301 }
302 }
303}
304
305impl<R, K, C, IS, ASrc> AuthsContextBuilder<R, K, C, IS, Missing, ASrc> {
306 /// Set the attestation sink adapter.
307 ///
308 /// Args:
309 /// * `sink`: Pre-initialized attestation sink implementation.
310 ///
311 /// Usage:
312 /// ```ignore
313 /// builder.attestation_sink(Arc::new(my_attestation_store))
314 /// ```
315 pub fn attestation_sink(
316 self,
317 sink: Arc<dyn AttestationSink + Send + Sync>,
318 ) -> AuthsContextBuilder<R, K, C, IS, Set<Arc<dyn AttestationSink + Send + Sync>>, ASrc> {
319 AuthsContextBuilder {
320 registry: self.registry,
321 key_storage: self.key_storage,
322 clock: self.clock,
323 identity_storage: self.identity_storage,
324 attestation_sink: Set(sink),
325 attestation_source: self.attestation_source,
326 event_sink: self.event_sink,
327 passphrase_provider: self.passphrase_provider,
328 uuid_provider: self.uuid_provider,
329 agent_signing: self.agent_signing,
330 witness_config: self.witness_config,
331 repo_path: self.repo_path,
332 }
333 }
334}
335
336impl<R, K, C, IS, AS> AuthsContextBuilder<R, K, C, IS, AS, Missing> {
337 /// Set the attestation source adapter.
338 ///
339 /// Args:
340 /// * `source`: Pre-initialized attestation source implementation.
341 ///
342 /// Usage:
343 /// ```ignore
344 /// builder.attestation_source(Arc::new(my_attestation_store))
345 /// ```
346 pub fn attestation_source(
347 self,
348 source: Arc<dyn AttestationSource + Send + Sync>,
349 ) -> AuthsContextBuilder<R, K, C, IS, AS, Set<Arc<dyn AttestationSource + Send + Sync>>> {
350 AuthsContextBuilder {
351 registry: self.registry,
352 key_storage: self.key_storage,
353 clock: self.clock,
354 identity_storage: self.identity_storage,
355 attestation_sink: self.attestation_sink,
356 attestation_source: Set(source),
357 event_sink: self.event_sink,
358 passphrase_provider: self.passphrase_provider,
359 uuid_provider: self.uuid_provider,
360 agent_signing: self.agent_signing,
361 witness_config: self.witness_config,
362 repo_path: self.repo_path,
363 }
364 }
365}
366
367// ── Optional field setters (available at any typestate, return Self) ──────────
368
369impl<R, K, C, IS, AS, ASrc> AuthsContextBuilder<R, K, C, IS, AS, ASrc> {
370 /// Set an optional event sink.
371 ///
372 /// Defaults to a no-op sink (all events discarded) when not called.
373 ///
374 /// Args:
375 /// * `sink`: Any type implementing [`EventSink`].
376 ///
377 /// Usage:
378 /// ```ignore
379 /// builder.event_sink(Arc::new(my_sink))
380 /// ```
381 pub fn event_sink(mut self, sink: Arc<dyn EventSink>) -> Self {
382 self.event_sink = Some(sink);
383 self
384 }
385
386 /// Set the passphrase provider for key decryption during signing operations.
387 ///
388 /// Defaults to a noop provider that returns an error. Set this when SDK
389 /// workflow functions will perform signing with encrypted key material.
390 ///
391 /// Args:
392 /// * `provider`: Any type implementing [`PassphraseProvider`].
393 ///
394 /// Usage:
395 /// ```ignore
396 /// builder.passphrase_provider(Arc::new(PrefilledPassphraseProvider::new(passphrase)))
397 /// ```
398 pub fn passphrase_provider(
399 mut self,
400 provider: Arc<dyn PassphraseProvider + Send + Sync>,
401 ) -> Self {
402 self.passphrase_provider = Some(provider);
403 self
404 }
405
406 /// Set the UUID provider.
407 ///
408 /// Defaults to [`SystemUuidProvider`] (random v4 UUIDs) when not called.
409 /// Override with a deterministic stub in tests.
410 ///
411 /// Args:
412 /// * `provider`: Any type implementing [`UuidProvider`].
413 ///
414 /// Usage:
415 /// ```ignore
416 /// builder.uuid_provider(Arc::new(my_uuid_stub))
417 /// ```
418 pub fn uuid_provider(mut self, provider: Arc<dyn UuidProvider + Send + Sync>) -> Self {
419 self.uuid_provider = Some(provider);
420 self
421 }
422
423 /// Set the agent signing port for delegating signing to a running agent process.
424 ///
425 /// Defaults to a noop provider (all operations return `Unavailable`)
426 /// when not called. Set this on Unix platforms where the auths-agent daemon
427 /// is available.
428 ///
429 /// Args:
430 /// * `provider`: Any type implementing [`AgentSigningPort`].
431 ///
432 /// Usage:
433 /// ```ignore
434 /// builder.agent_signing(Arc::new(CliAgentAdapter::new(socket_path)))
435 /// ```
436 pub fn agent_signing(mut self, provider: Arc<dyn AgentSigningPort + Send + Sync>) -> Self {
437 self.agent_signing = Some(provider);
438 self
439 }
440
441 /// Set witness configuration for KEL event receipting.
442 pub fn witness_config(mut self, config: auths_id::witness_config::WitnessConfig) -> Self {
443 self.witness_config = Some(config);
444 self
445 }
446
447 /// Set the repository path (needed for witness receipt storage).
448 pub fn repo_path(mut self, path: std::path::PathBuf) -> Self {
449 self.repo_path = Some(path);
450 self
451 }
452}
453
454// ── Infallible build — only available when all six required fields are set ────
455
456impl
457 AuthsContextBuilder<
458 Set<Arc<dyn RegistryBackend + Send + Sync>>,
459 Set<Arc<dyn KeyStorage + Send + Sync>>,
460 Set<Arc<dyn ClockProvider + Send + Sync>>,
461 Set<Arc<dyn IdentityStorage + Send + Sync>>,
462 Set<Arc<dyn AttestationSink + Send + Sync>>,
463 Set<Arc<dyn AttestationSource + Send + Sync>>,
464 >
465{
466 /// Build the [`AuthsContext`].
467 ///
468 /// Infallible — only callable once all six required fields are set.
469 /// Omitting any required field is a compile-time error.
470 ///
471 /// Usage:
472 /// ```ignore
473 /// let ctx = AuthsContext::builder()
474 /// .registry(Arc::new(my_registry))
475 /// .key_storage(Arc::new(my_keychain))
476 /// .clock(Arc::new(SystemClock))
477 /// .identity_storage(Arc::new(my_identity_storage))
478 /// .attestation_sink(Arc::new(my_store.clone()))
479 /// .attestation_source(Arc::new(my_store))
480 /// .build();
481 /// ```
482 pub fn build(self) -> AuthsContext {
483 AuthsContext {
484 registry: self.registry.0,
485 key_storage: self.key_storage.0,
486 clock: self.clock.0,
487 identity_storage: self.identity_storage.0,
488 attestation_sink: self.attestation_sink.0,
489 attestation_source: self.attestation_source.0,
490 event_sink: self.event_sink.unwrap_or_else(|| Arc::new(NoopSink)),
491 passphrase_provider: self
492 .passphrase_provider
493 .unwrap_or_else(|| Arc::new(NoopPassphraseProvider)),
494 uuid_provider: self
495 .uuid_provider
496 .unwrap_or_else(|| Arc::new(SystemUuidProvider)),
497 agent_signing: self
498 .agent_signing
499 .unwrap_or_else(|| Arc::new(NoopAgentProvider)),
500 witness_config: self.witness_config,
501 repo_path: self.repo_path,
502 }
503 }
504}