Skip to main content

systemprompt_config/bootstrap/
mod.rs

1//! Bootstrap sequence orchestration.
2//!
3//! This module provides type-safe bootstrap sequencing that enforces
4//! initialization dependencies at compile time. The sequence ensures
5//! that secrets cannot be initialized without a profile.
6
7use std::marker::PhantomData;
8use std::path::Path;
9
10use crate::error::ConfigResult;
11
12mod manifest;
13mod profile;
14mod secrets;
15
16pub use manifest::{MANIFEST_SIGNING_SEED_BYTES, decode_seed, generate_seed, persist_seed};
17pub use profile::{ProfileBootstrap, ProfileBootstrapError};
18pub use secrets::{
19    JWT_SECRET_MIN_LENGTH, SecretsBootstrap, SecretsBootstrapError, build_loaded_secrets_message,
20    load_secrets_from_path, log_secrets_issue, log_secrets_skip, log_secrets_warn,
21};
22
23pub trait BootstrapState {}
24
25#[derive(Debug, Clone, Copy)]
26pub struct Uninitialized;
27impl BootstrapState for Uninitialized {}
28
29#[derive(Debug, Clone, Copy)]
30pub struct ProfileInitialized;
31impl BootstrapState for ProfileInitialized {}
32
33#[derive(Debug, Clone, Copy)]
34pub struct SecretsInitialized;
35impl BootstrapState for SecretsInitialized {}
36
37#[derive(Debug)]
38pub struct BootstrapSequence<S: BootstrapState> {
39    _state: PhantomData<S>,
40}
41
42impl Default for BootstrapSequence<Uninitialized> {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48impl BootstrapSequence<Uninitialized> {
49    #[must_use]
50    pub const fn new() -> Self {
51        Self {
52            _state: PhantomData,
53        }
54    }
55
56    pub fn with_profile(self, path: &Path) -> ConfigResult<BootstrapSequence<ProfileInitialized>> {
57        let Self { _state: _ } = self;
58        ProfileBootstrap::init_from_path(path)?;
59
60        Ok(BootstrapSequence {
61            _state: PhantomData,
62        })
63    }
64
65    #[must_use]
66    pub const fn skip_profile(self) -> Self {
67        self
68    }
69}
70
71impl BootstrapSequence<ProfileInitialized> {
72    pub fn with_secrets(self) -> ConfigResult<BootstrapSequence<SecretsInitialized>> {
73        let Self { _state: _ } = self;
74        SecretsBootstrap::init()?;
75
76        Ok(BootstrapSequence {
77            _state: PhantomData,
78        })
79    }
80
81    #[must_use]
82    pub const fn skip_secrets(self) -> Self {
83        self
84    }
85}
86
87impl BootstrapSequence<SecretsInitialized> {
88    #[must_use]
89    pub const fn complete(self) -> BootstrapComplete {
90        let Self { _state: _ } = self;
91        BootstrapComplete { _private: () }
92    }
93}
94
95#[derive(Debug, Clone, Copy)]
96pub struct BootstrapComplete {
97    _private: (),
98}
99
100pub mod presets {
101    use std::path::Path;
102
103    use super::{BootstrapSequence, ProfileInitialized, SecretsInitialized, Uninitialized};
104    use crate::error::ConfigResult;
105
106    pub fn profile_and_secrets(
107        profile_path: &Path,
108    ) -> ConfigResult<BootstrapSequence<SecretsInitialized>> {
109        BootstrapSequence::<Uninitialized>::new()
110            .with_profile(profile_path)?
111            .with_secrets()
112    }
113
114    pub fn profile_only(
115        profile_path: &Path,
116    ) -> ConfigResult<BootstrapSequence<ProfileInitialized>> {
117        BootstrapSequence::<Uninitialized>::new().with_profile(profile_path)
118    }
119}