Skip to main content

made_core/ports/
configuration.rs

1//! [`ConfigurationPort`] — read-only access to validated service
2//! configuration.
3
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6
7use crate::error::DomainError;
8
9/// Minimal configuration surface the core needs to reason about the
10/// service. Adapter implementations (env vars, Figment, Kubernetes
11/// downward API, …) map to this shape.
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct ServiceConfig {
14    pub grpc_port: u16,
15    pub http_port: u16,
16    pub nats_enabled: bool,
17    pub nats_url: String,
18    pub trigger_subject: String,
19    pub publish_prefix: String,
20    /// When set, deliberations persist to Postgres; otherwise the
21    /// in-memory repository is wired. Empty-string is treated as
22    /// unset so the chart can carry a placeholder default.
23    pub postgres_url: Option<String>,
24    /// When set, ceremony state, its audit journal and its outbox
25    /// persist to an embedded store at this path; otherwise they are
26    /// held in memory.
27    ///
28    /// Unset is a deliberate choice, not a default that happens to be
29    /// safe: step leases, idempotency keys and pending human guards
30    /// exist to survive failure, and in memory they survive nothing.
31    /// A server left volatile says so at startup.
32    pub ceremony_store_path: Option<String>,
33    /// Transport security for the gRPC server.
34    pub grpc_tls: GrpcTlsConfig,
35}
36
37/// Mode + paths for the gRPC server's transport security. Validated
38/// at load time so the adapter never sees an internally inconsistent
39/// state (e.g. mode=server with no cert path).
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub enum GrpcTlsConfig {
42    /// Plain HTTP/2 over TCP. Acceptable inside a private mesh; not
43    /// for cross-network deployments.
44    Disabled,
45    /// One-way TLS: the server presents an identity, the client
46    /// verifies it via a trust anchor it already has.
47    Server { cert_path: String, key_path: String },
48    /// Mutual TLS: client presents an identity that the server
49    /// validates against `client_ca_path`.
50    Mutual {
51        cert_path: String,
52        key_path: String,
53        client_ca_path: String,
54    },
55}
56
57impl GrpcTlsConfig {
58    /// Disabled by default — every other mode requires explicit
59    /// configuration with file paths the binary can actually read.
60    #[must_use]
61    pub fn disabled() -> Self {
62        Self::Disabled
63    }
64
65    /// Operator-friendly name for the active mode. Useful for
66    /// startup-log honesty so deployments expose what they're doing.
67    #[must_use]
68    pub fn mode_name(&self) -> &'static str {
69        match self {
70            Self::Disabled => "none",
71            Self::Server { .. } => "server",
72            Self::Mutual { .. } => "mutual",
73        }
74    }
75}
76
77impl Default for GrpcTlsConfig {
78    fn default() -> Self {
79        Self::disabled()
80    }
81}
82
83#[async_trait]
84pub trait ConfigurationPort: Send + Sync {
85    async fn load(&self) -> Result<ServiceConfig, DomainError>;
86}