rhi

Coordinated trade for connected markets
git clone https://radroots.dev/git/rhi.git
Log | Files | Refs | README | LICENSE

config_v1.rs (55596B)


      1 //! Strict, bounded RHI configuration document v1 admission.
      2 
      3 use std::collections::{BTreeMap, BTreeSet};
      4 use std::error::Error;
      5 use std::fmt;
      6 use std::net::SocketAddr;
      7 
      8 use nostr::PublicKey;
      9 use serde::Serialize;
     10 use serde_json::{Map, Value, json};
     11 use url::Url;
     12 
     13 const CONFIG_SCHEMA: &str = include_str!("../contracts/services_hardening/config.v1.schema.json");
     14 
     15 /// Exact schema identity for the production RHI configuration document.
     16 pub const RHI_CONFIG_SCHEMA: &str = "radroots.rhi.config";
     17 
     18 /// Exact supported RHI configuration schema version.
     19 pub const RHI_CONFIG_SCHEMA_VERSION: u32 = 1;
     20 
     21 /// Hard cap applied to original bytes before UTF-8 or TOML parsing.
     22 pub const RHI_CONFIG_DOCUMENT_MAX_UTF8_BYTES: usize = 1_048_576;
     23 
     24 /// Hard cap applied to the deterministic redacted effective projection.
     25 pub const RHI_CONFIG_EFFECTIVE_MAX_UTF8_BYTES: usize = 786_432;
     26 
     27 /// Bootstrap-selected network posture used during relay admission.
     28 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     29 pub enum RhiConfigProfile {
     30     /// Production and ordinary service-host configurations require WSS relays.
     31     Production,
     32     /// Explicit repository-local development may also use loopback WS relays.
     33     RepoLocal,
     34 }
     35 
     36 /// Stable origin classification for one effective configuration value.
     37 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
     38 #[serde(rename_all = "snake_case")]
     39 pub enum RhiConfigValueSource {
     40     BootstrapCli,
     41     Toml,
     42     SafeDefault,
     43     DerivedPath,
     44 }
     45 
     46 /// Exact governed authority behind a safely defaulted configuration leaf.
     47 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
     48 #[serde(rename_all = "snake_case")]
     49 pub enum RhiConfigDefaultAuthority {
     50     RadrootsServiceHost,
     51     RadrootsServiceSqlite,
     52     RadrootsEvent,
     53     RhiEvidencePolicy,
     54     AcceptedServiceAuthority,
     55     EngineeringSafety,
     56 }
     57 
     58 /// Stable source-free classification for configuration admission failures.
     59 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     60 pub enum RhiConfigV1ErrorKind {
     61     TooLarge,
     62     InvalidUtf8,
     63     MalformedToml,
     64     MissingSchema,
     65     InvalidSchema,
     66     SchemaMismatch,
     67     MissingSchemaVersion,
     68     InvalidSchemaVersion,
     69     UnsupportedSchemaVersion,
     70     InvalidDocument,
     71     InvalidRelationship,
     72     Encoding,
     73 }
     74 
     75 impl RhiConfigV1ErrorKind {
     76     const fn message(self) -> &'static str {
     77         match self {
     78             Self::TooLarge => "configuration document exceeds its size limit",
     79             Self::InvalidUtf8 => "configuration document is not valid UTF-8",
     80             Self::MalformedToml => "configuration document is not valid TOML",
     81             Self::MissingSchema => "configuration document schema is missing",
     82             Self::InvalidSchema => "configuration document schema is invalid",
     83             Self::SchemaMismatch => "configuration document schema is unsupported",
     84             Self::MissingSchemaVersion => "configuration document schema version is missing",
     85             Self::InvalidSchemaVersion => "configuration document schema version is invalid",
     86             Self::UnsupportedSchemaVersion => {
     87                 "configuration document schema version is unsupported"
     88             }
     89             Self::InvalidDocument => "configuration document fields are invalid",
     90             Self::InvalidRelationship => "configuration document relationships are invalid",
     91             Self::Encoding => "effective configuration could not be encoded",
     92         }
     93     }
     94 }
     95 
     96 /// One source-free configuration admission failure.
     97 #[derive(Clone, Copy, PartialEq, Eq)]
     98 pub struct RhiConfigV1Error {
     99     kind: RhiConfigV1ErrorKind,
    100 }
    101 
    102 impl RhiConfigV1Error {
    103     const fn new(kind: RhiConfigV1ErrorKind) -> Self {
    104         Self { kind }
    105     }
    106 
    107     /// Returns the stable failure classification.
    108     #[must_use]
    109     pub const fn kind(self) -> RhiConfigV1ErrorKind {
    110         self.kind
    111     }
    112 }
    113 
    114 impl fmt::Debug for RhiConfigV1Error {
    115     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    116         formatter
    117             .debug_struct("RhiConfigV1Error")
    118             .field("kind", &self.kind)
    119             .finish()
    120     }
    121 }
    122 
    123 impl fmt::Display for RhiConfigV1Error {
    124     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    125         formatter.write_str(self.kind.message())
    126     }
    127 }
    128 
    129 impl Error for RhiConfigV1Error {}
    130 
    131 /// Deterministic redacted effective configuration with exact leaf provenance.
    132 #[derive(Clone, PartialEq, Eq)]
    133 pub struct RhiEffectiveConfigV1 {
    134     canonical_json: Box<str>,
    135     field_count: usize,
    136 }
    137 
    138 impl RhiEffectiveConfigV1 {
    139     /// Returns compact JSON in deterministic path order.
    140     #[must_use]
    141     pub fn canonical_json(&self) -> &str {
    142         &self.canonical_json
    143     }
    144 
    145     /// Returns the number of projected effective leaf values.
    146     #[must_use]
    147     pub const fn field_count(&self) -> usize {
    148         self.field_count
    149     }
    150 }
    151 
    152 impl fmt::Debug for RhiEffectiveConfigV1 {
    153     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    154         formatter
    155             .debug_struct("RhiEffectiveConfigV1")
    156             .field("canonical_json", &"[redacted]")
    157             .field("field_count", &self.field_count)
    158             .finish()
    159     }
    160 }
    161 
    162 /// Validated thread counts for the sole binary-owned Tokio runtime.
    163 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    164 pub struct RhiRuntimeThreadLimitsV1 {
    165     worker_threads: usize,
    166     blocking_threads: usize,
    167 }
    168 
    169 impl RhiRuntimeThreadLimitsV1 {
    170     /// Returns the configured asynchronous worker count.
    171     #[must_use]
    172     pub const fn worker_threads(self) -> usize {
    173         self.worker_threads
    174     }
    175 
    176     /// Returns the configured blocking worker ceiling.
    177     #[must_use]
    178     pub const fn blocking_threads(self) -> usize {
    179         self.blocking_threads
    180     }
    181 }
    182 
    183 /// A validated immutable RHI configuration document v1.
    184 pub struct RhiConfigDocumentV1 {
    185     profile: RhiConfigProfile,
    186     normalized: Value,
    187     effective: RhiEffectiveConfigV1,
    188     runtime_thread_limits: RhiRuntimeThreadLimitsV1,
    189 }
    190 
    191 impl RhiConfigDocumentV1 {
    192     /// Returns the exact admitted schema identity.
    193     #[must_use]
    194     pub const fn schema(&self) -> &'static str {
    195         RHI_CONFIG_SCHEMA
    196     }
    197 
    198     /// Returns the exact admitted schema version.
    199     #[must_use]
    200     pub const fn schema_version(&self) -> u32 {
    201         RHI_CONFIG_SCHEMA_VERSION
    202     }
    203 
    204     /// Returns the bootstrap-selected network posture used during admission.
    205     #[must_use]
    206     pub const fn profile(&self) -> RhiConfigProfile {
    207         self.profile
    208     }
    209 
    210     /// Returns the deterministic redacted effective configuration projection.
    211     #[must_use]
    212     pub const fn effective(&self) -> &RhiEffectiveConfigV1 {
    213         &self.effective
    214     }
    215 
    216     pub(crate) const fn normalized(&self) -> &Value {
    217         &self.normalized
    218     }
    219 
    220     /// Returns the exact number of configured relay bindings.
    221     #[must_use]
    222     pub fn relay_count(&self) -> usize {
    223         self.normalized
    224             .pointer("/relays")
    225             .and_then(Value::as_array)
    226             .map_or(0, Vec::len)
    227     }
    228 
    229     /// Returns the exact number of configured evidence sources.
    230     #[must_use]
    231     pub fn evidence_source_count(&self) -> usize {
    232         self.normalized
    233             .pointer("/evidence/sources")
    234             .and_then(Value::as_array)
    235             .map_or(0, Vec::len)
    236     }
    237 
    238     /// Returns the validated limits for the sole binary-owned Tokio runtime.
    239     #[must_use]
    240     pub const fn runtime_thread_limits(&self) -> RhiRuntimeThreadLimitsV1 {
    241         self.runtime_thread_limits
    242     }
    243 }
    244 
    245 impl fmt::Debug for RhiConfigDocumentV1 {
    246     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    247         formatter
    248             .debug_struct("RhiConfigDocumentV1")
    249             .field("schema", &RHI_CONFIG_SCHEMA)
    250             .field("schema_version", &RHI_CONFIG_SCHEMA_VERSION)
    251             .field("profile", &self.profile)
    252             .field("effective", &self.effective)
    253             .field("runtime_thread_limits", &self.runtime_thread_limits)
    254             .finish()
    255     }
    256 }
    257 
    258 /// Parses and semantically validates one complete RHI configuration document.
    259 pub fn parse_rhi_config_v1(
    260     bytes: &[u8],
    261     profile: RhiConfigProfile,
    262 ) -> Result<RhiConfigDocumentV1, RhiConfigV1Error> {
    263     if bytes.len() > RHI_CONFIG_DOCUMENT_MAX_UTF8_BYTES {
    264         return Err(error(RhiConfigV1ErrorKind::TooLarge));
    265     }
    266     let source =
    267         std::str::from_utf8(bytes).map_err(|_| error(RhiConfigV1ErrorKind::InvalidUtf8))?;
    268     let original = source
    269         .parse::<toml::Table>()
    270         .map_err(|_| error(RhiConfigV1ErrorKind::MalformedToml))?;
    271     validate_header(&original)?;
    272 
    273     let mut normalized = serde_json::to_value(toml::Value::Table(original.clone()))
    274         .map_err(|_| error(RhiConfigV1ErrorKind::InvalidDocument))?;
    275     let schema: Value =
    276         serde_json::from_str(CONFIG_SCHEMA).map_err(|_| error(RhiConfigV1ErrorKind::Encoding))?;
    277     let validator =
    278         jsonschema::validator_for(&schema).map_err(|_| error(RhiConfigV1ErrorKind::Encoding))?;
    279     if !validator.is_valid(&normalized) {
    280         return Err(error(RhiConfigV1ErrorKind::InvalidDocument));
    281     }
    282     apply_defaults(&mut normalized)?;
    283     if !validator.is_valid(&normalized) {
    284         return Err(error(RhiConfigV1ErrorKind::InvalidDocument));
    285     }
    286     validate_relationships(&normalized, profile)?;
    287     let effective = build_effective(&normalized, &original)?;
    288     let runtime_thread_limits = RhiRuntimeThreadLimitsV1 {
    289         worker_threads: usize::try_from(integer(
    290             &normalized,
    291             "/resource_limits/runtime/worker_threads",
    292         )?)
    293         .map_err(|_| error(RhiConfigV1ErrorKind::InvalidRelationship))?,
    294         blocking_threads: usize::try_from(integer(
    295             &normalized,
    296             "/resource_limits/runtime/blocking_threads",
    297         )?)
    298         .map_err(|_| error(RhiConfigV1ErrorKind::InvalidRelationship))?,
    299     };
    300     Ok(RhiConfigDocumentV1 {
    301         profile,
    302         normalized,
    303         effective,
    304         runtime_thread_limits,
    305     })
    306 }
    307 
    308 fn validate_header(header: &toml::Table) -> Result<(), RhiConfigV1Error> {
    309     let schema = header
    310         .get("schema")
    311         .ok_or_else(|| error(RhiConfigV1ErrorKind::MissingSchema))?
    312         .as_str()
    313         .ok_or_else(|| error(RhiConfigV1ErrorKind::InvalidSchema))?;
    314     if !valid_schema_id(schema) {
    315         return Err(error(RhiConfigV1ErrorKind::InvalidSchema));
    316     }
    317     if schema != RHI_CONFIG_SCHEMA {
    318         return Err(error(RhiConfigV1ErrorKind::SchemaMismatch));
    319     }
    320     let version = header
    321         .get("schema_version")
    322         .ok_or_else(|| error(RhiConfigV1ErrorKind::MissingSchemaVersion))?
    323         .as_integer()
    324         .ok_or_else(|| error(RhiConfigV1ErrorKind::InvalidSchemaVersion))?;
    325     let version =
    326         u32::try_from(version).map_err(|_| error(RhiConfigV1ErrorKind::InvalidSchemaVersion))?;
    327     if version == 0 {
    328         return Err(error(RhiConfigV1ErrorKind::InvalidSchemaVersion));
    329     }
    330     if version != RHI_CONFIG_SCHEMA_VERSION {
    331         return Err(error(RhiConfigV1ErrorKind::UnsupportedSchemaVersion));
    332     }
    333     Ok(())
    334 }
    335 
    336 fn valid_schema_id(value: &str) -> bool {
    337     let mut bytes = value.bytes();
    338     !value.is_empty()
    339         && value.len() <= 128
    340         && bytes
    341             .next()
    342             .is_some_and(|byte| byte.is_ascii_alphanumeric())
    343         && bytes
    344             .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b':' | b'-'))
    345 }
    346 
    347 #[derive(Clone, Copy)]
    348 struct DefaultEntry {
    349     path: &'static str,
    350     value: DefaultValue,
    351     authority: RhiConfigDefaultAuthority,
    352     enabled_pointer: Option<&'static str>,
    353 }
    354 
    355 #[derive(Clone, Copy)]
    356 enum DefaultValue {
    357     Integer(u64),
    358     String(&'static str),
    359 }
    360 
    361 const DEFAULTS: &[DefaultEntry] = &[
    362     default(
    363         "/service/shutdown_grace_ms",
    364         30_000,
    365         RhiConfigDefaultAuthority::AcceptedServiceAuthority,
    366     ),
    367     default_string(
    368         "/logging/level",
    369         "info",
    370         RhiConfigDefaultAuthority::EngineeringSafety,
    371     ),
    372     default_string(
    373         "/logging/format",
    374         "json",
    375         RhiConfigDefaultAuthority::EngineeringSafety,
    376     ),
    377     conditional_default(
    378         "/operations/limits/header_count",
    379         32,
    380         RhiConfigDefaultAuthority::RadrootsServiceHost,
    381         "/operations/enabled",
    382     ),
    383     conditional_default(
    384         "/operations/limits/header_bytes",
    385         16_384,
    386         RhiConfigDefaultAuthority::RadrootsServiceHost,
    387         "/operations/enabled",
    388     ),
    389     conditional_default(
    390         "/operations/limits/response_body_utf8_bytes",
    391         1_048_576,
    392         RhiConfigDefaultAuthority::RadrootsServiceHost,
    393         "/operations/enabled",
    394     ),
    395     conditional_default(
    396         "/operations/limits/concurrent_connections",
    397         32,
    398         RhiConfigDefaultAuthority::RadrootsServiceHost,
    399         "/operations/enabled",
    400     ),
    401     conditional_default(
    402         "/operations/limits/request_deadline_ms",
    403         15_000,
    404         RhiConfigDefaultAuthority::RadrootsServiceHost,
    405         "/operations/enabled",
    406     ),
    407     conditional_default(
    408         "/operations/limits/idle_timeout_ms",
    409         30_000,
    410         RhiConfigDefaultAuthority::RadrootsServiceHost,
    411         "/operations/enabled",
    412     ),
    413     default(
    414         "/database/busy_timeout_ms",
    415         5_000,
    416         RhiConfigDefaultAuthority::RadrootsServiceSqlite,
    417     ),
    418     default(
    419         "/database/max_connections",
    420         8,
    421         RhiConfigDefaultAuthority::RadrootsServiceSqlite,
    422     ),
    423     default(
    424         "/network/connect_deadline_ms",
    425         10_000,
    426         RhiConfigDefaultAuthority::EngineeringSafety,
    427     ),
    428     default(
    429         "/network/dns_answer_limit",
    430         16,
    431         RhiConfigDefaultAuthority::EngineeringSafety,
    432     ),
    433     default(
    434         "/reconciliation/concurrency",
    435         8,
    436         RhiConfigDefaultAuthority::EngineeringSafety,
    437     ),
    438     default(
    439         "/reconciliation/queue_capacity",
    440         4_096,
    441         RhiConfigDefaultAuthority::EngineeringSafety,
    442     ),
    443     default(
    444         "/reconciliation/lease_ms",
    445         30_000,
    446         RhiConfigDefaultAuthority::EngineeringSafety,
    447     ),
    448     default(
    449         "/reconciliation/lease_renewal_ms",
    450         10_000,
    451         RhiConfigDefaultAuthority::EngineeringSafety,
    452     ),
    453     default(
    454         "/reconciliation/max_attempts",
    455         10,
    456         RhiConfigDefaultAuthority::EngineeringSafety,
    457     ),
    458     default(
    459         "/reconciliation/initial_backoff_ms",
    460         250,
    461         RhiConfigDefaultAuthority::EngineeringSafety,
    462     ),
    463     default(
    464         "/reconciliation/maximum_backoff_ms",
    465         30_000,
    466         RhiConfigDefaultAuthority::EngineeringSafety,
    467     ),
    468     default(
    469         "/reconciliation/attempt_deadline_ms",
    470         30_000,
    471         RhiConfigDefaultAuthority::EngineeringSafety,
    472     ),
    473     conditional_default(
    474         "/publication/retry/max_attempts",
    475         10,
    476         RhiConfigDefaultAuthority::EngineeringSafety,
    477         "/publication/mode",
    478     ),
    479     conditional_default(
    480         "/publication/retry/initial_backoff_ms",
    481         250,
    482         RhiConfigDefaultAuthority::EngineeringSafety,
    483         "/publication/mode",
    484     ),
    485     conditional_default(
    486         "/publication/retry/maximum_backoff_ms",
    487         30_000,
    488         RhiConfigDefaultAuthority::EngineeringSafety,
    489         "/publication/mode",
    490     ),
    491     conditional_default(
    492         "/publication/retry/attempt_deadline_ms",
    493         15_000,
    494         RhiConfigDefaultAuthority::EngineeringSafety,
    495         "/publication/mode",
    496     ),
    497     default(
    498         "/resource_limits/admin/header_count",
    499         32,
    500         RhiConfigDefaultAuthority::RadrootsServiceHost,
    501     ),
    502     default(
    503         "/resource_limits/admin/header_bytes",
    504         16_384,
    505         RhiConfigDefaultAuthority::RadrootsServiceHost,
    506     ),
    507     default(
    508         "/resource_limits/admin/request_body_utf8_bytes",
    509         65_536,
    510         RhiConfigDefaultAuthority::RadrootsServiceHost,
    511     ),
    512     default(
    513         "/resource_limits/admin/response_body_utf8_bytes",
    514         1_048_576,
    515         RhiConfigDefaultAuthority::RadrootsServiceHost,
    516     ),
    517     default(
    518         "/resource_limits/admin/concurrent_connections",
    519         32,
    520         RhiConfigDefaultAuthority::RadrootsServiceHost,
    521     ),
    522     default(
    523         "/resource_limits/admin/request_deadline_ms",
    524         15_000,
    525         RhiConfigDefaultAuthority::RadrootsServiceHost,
    526     ),
    527     default(
    528         "/resource_limits/admin/idle_timeout_ms",
    529         30_000,
    530         RhiConfigDefaultAuthority::RadrootsServiceHost,
    531     ),
    532     default(
    533         "/resource_limits/admin/query_items",
    534         100,
    535         RhiConfigDefaultAuthority::AcceptedServiceAuthority,
    536     ),
    537     default(
    538         "/resource_limits/events/wire_bytes",
    539         262_144,
    540         RhiConfigDefaultAuthority::RadrootsEvent,
    541     ),
    542     default(
    543         "/resource_limits/events/content_bytes",
    544         131_072,
    545         RhiConfigDefaultAuthority::RadrootsEvent,
    546     ),
    547     default(
    548         "/resource_limits/events/tag_count",
    549         1_024,
    550         RhiConfigDefaultAuthority::RadrootsEvent,
    551     ),
    552     default(
    553         "/resource_limits/events/tag_total_elements",
    554         4_096,
    555         RhiConfigDefaultAuthority::RadrootsEvent,
    556     ),
    557     default(
    558         "/resource_limits/events/tag_element_bytes",
    559         4_096,
    560         RhiConfigDefaultAuthority::RadrootsEvent,
    561     ),
    562     default(
    563         "/resource_limits/events/tag_total_bytes",
    564         131_072,
    565         RhiConfigDefaultAuthority::RadrootsEvent,
    566     ),
    567     default(
    568         "/resource_limits/source_results/events",
    569         4_096,
    570         RhiConfigDefaultAuthority::RhiEvidencePolicy,
    571     ),
    572     default(
    573         "/resource_limits/source_results/bytes",
    574         8_388_608,
    575         RhiConfigDefaultAuthority::RhiEvidencePolicy,
    576     ),
    577     default(
    578         "/resource_limits/queues/ingress",
    579         1_024,
    580         RhiConfigDefaultAuthority::EngineeringSafety,
    581     ),
    582     default(
    583         "/resource_limits/queues/reconciliation",
    584         4_096,
    585         RhiConfigDefaultAuthority::EngineeringSafety,
    586     ),
    587     default(
    588         "/resource_limits/queues/publication",
    589         4_096,
    590         RhiConfigDefaultAuthority::EngineeringSafety,
    591     ),
    592     default(
    593         "/resource_limits/queues/presence",
    594         64,
    595         RhiConfigDefaultAuthority::EngineeringSafety,
    596     ),
    597     default(
    598         "/resource_limits/metrics/descriptors",
    599         64,
    600         RhiConfigDefaultAuthority::RadrootsServiceHost,
    601     ),
    602     default(
    603         "/resource_limits/metrics/samples",
    604         512,
    605         RhiConfigDefaultAuthority::RadrootsServiceHost,
    606     ),
    607     default(
    608         "/resource_limits/metrics/labels_per_sample",
    609         8,
    610         RhiConfigDefaultAuthority::RadrootsServiceHost,
    611     ),
    612     default(
    613         "/resource_limits/metrics/render_utf8_bytes",
    614         1_048_576,
    615         RhiConfigDefaultAuthority::RadrootsServiceHost,
    616     ),
    617     default(
    618         "/resource_limits/runtime/worker_threads",
    619         4,
    620         RhiConfigDefaultAuthority::EngineeringSafety,
    621     ),
    622     default(
    623         "/resource_limits/runtime/blocking_threads",
    624         8,
    625         RhiConfigDefaultAuthority::EngineeringSafety,
    626     ),
    627 ];
    628 
    629 const fn default(
    630     path: &'static str,
    631     value: u64,
    632     authority: RhiConfigDefaultAuthority,
    633 ) -> DefaultEntry {
    634     DefaultEntry {
    635         path,
    636         value: DefaultValue::Integer(value),
    637         authority,
    638         enabled_pointer: None,
    639     }
    640 }
    641 
    642 const fn conditional_default(
    643     path: &'static str,
    644     value: u64,
    645     authority: RhiConfigDefaultAuthority,
    646     enabled_pointer: &'static str,
    647 ) -> DefaultEntry {
    648     DefaultEntry {
    649         path,
    650         value: DefaultValue::Integer(value),
    651         authority,
    652         enabled_pointer: Some(enabled_pointer),
    653     }
    654 }
    655 
    656 const fn default_string(
    657     path: &'static str,
    658     value: &'static str,
    659     authority: RhiConfigDefaultAuthority,
    660 ) -> DefaultEntry {
    661     DefaultEntry {
    662         path,
    663         value: DefaultValue::String(value),
    664         authority,
    665         enabled_pointer: None,
    666     }
    667 }
    668 
    669 fn default_is_enabled(document: &Value, entry: &DefaultEntry) -> bool {
    670     match entry.enabled_pointer {
    671         Some("/operations/enabled") => {
    672             document.pointer("/operations/enabled") == Some(&Value::Bool(true))
    673         }
    674         Some("/publication/mode") => {
    675             document
    676                 .pointer("/publication/mode")
    677                 .and_then(Value::as_str)
    678                 == Some("required")
    679         }
    680         Some(_) => false,
    681         None => true,
    682     }
    683 }
    684 
    685 fn apply_defaults(document: &mut Value) -> Result<(), RhiConfigV1Error> {
    686     for entry in DEFAULTS {
    687         if !default_is_enabled(document, entry) || document.pointer(entry.path).is_some() {
    688             continue;
    689         }
    690         insert_json_pointer(document, entry.path, entry.value)?;
    691     }
    692     Ok(())
    693 }
    694 
    695 fn insert_json_pointer(
    696     root: &mut Value,
    697     pointer: &str,
    698     value: DefaultValue,
    699 ) -> Result<(), RhiConfigV1Error> {
    700     let mut parts = pointer
    701         .split('/')
    702         .filter(|part| !part.is_empty())
    703         .peekable();
    704     let mut current = root;
    705     while let Some(part) = parts.next() {
    706         let object = current
    707             .as_object_mut()
    708             .ok_or_else(|| error(RhiConfigV1ErrorKind::InvalidDocument))?;
    709         if parts.peek().is_none() {
    710             object.insert(
    711                 part.to_owned(),
    712                 match value {
    713                     DefaultValue::Integer(value) => Value::Number(value.into()),
    714                     DefaultValue::String(value) => Value::String(value.to_owned()),
    715                 },
    716             );
    717             return Ok(());
    718         }
    719         current = object
    720             .entry(part.to_owned())
    721             .or_insert_with(|| Value::Object(Map::new()));
    722     }
    723     document_error()
    724 }
    725 
    726 fn validate_relationships(
    727     document: &Value,
    728     profile: RhiConfigProfile,
    729 ) -> Result<(), RhiConfigV1Error> {
    730     validate_utf8_byte_limits(document)?;
    731     validate_identity(document)?;
    732     let relays = validate_relays(document, profile)?;
    733     validate_evidence(document, relays)?;
    734     validate_reconciliation(document)?;
    735     validate_publication(document, relays)?;
    736     validate_presence(document, relays)?;
    737     validate_operations(document)?;
    738     validate_retention(document)?;
    739     validate_exact_policy_limits(document)
    740 }
    741 
    742 fn validate_utf8_byte_limits(document: &Value) -> Result<(), RhiConfigV1Error> {
    743     for (pointer, minimum, maximum) in [
    744         ("/identity/service/envelope_path", 1, 4_096),
    745         ("/identity/service/credential_reference", 1, 128),
    746         ("/identity/service/expected_public_key", 64, 64),
    747         ("/evidence/policy_id", 1, 64),
    748     ] {
    749         validate_string_bytes(string(document, pointer)?, minimum, maximum)?;
    750     }
    751     for relay in array(document, "/relays")? {
    752         validate_string_bytes(string_at(relay, "/id")?, 1, 64)?;
    753         validate_string_bytes(string_at(relay, "/url")?, 1, 2_048)?;
    754     }
    755     for source in array(document, "/evidence/sources")? {
    756         validate_string_bytes(string_at(source, "/source_id")?, 1, 64)?;
    757         validate_string_bytes(string_at(source, "/relay_id")?, 1, 64)?;
    758     }
    759     if bool_value(document, "/operations/enabled")? {
    760         validate_string_bytes(string(document, "/operations/listen")?, 1, 256)?;
    761     }
    762     Ok(())
    763 }
    764 
    765 fn validate_string_bytes(
    766     value: &str,
    767     minimum: usize,
    768     maximum: usize,
    769 ) -> Result<(), RhiConfigV1Error> {
    770     if (minimum..=maximum).contains(&value.len()) {
    771         Ok(())
    772     } else {
    773         document_error()
    774     }
    775 }
    776 
    777 fn validate_identity(document: &Value) -> Result<(), RhiConfigV1Error> {
    778     let raw_path = string(document, "/identity/service/envelope_path")?;
    779     if raw_path.as_bytes().contains(&0)
    780         || raw_path == "/"
    781         || !raw_path.starts_with('/')
    782         || raw_path.ends_with('/')
    783         || raw_path
    784             .split('/')
    785             .skip(1)
    786             .any(|component| component.is_empty() || matches!(component, "." | ".."))
    787         || !valid_nostr_public_key(string(document, "/identity/service/expected_public_key")?)
    788     {
    789         return relationship_error();
    790     }
    791     Ok(())
    792 }
    793 
    794 fn valid_nostr_public_key(value: &str) -> bool {
    795     PublicKey::from_hex(value).is_ok_and(|public_key| public_key.xonly().is_ok())
    796 }
    797 
    798 fn validate_relays(
    799     document: &Value,
    800     profile: RhiConfigProfile,
    801 ) -> Result<&[Value], RhiConfigV1Error> {
    802     let relays = array(document, "/relays")?;
    803     let mut ids = BTreeSet::new();
    804     let mut urls = BTreeSet::new();
    805     for relay in relays {
    806         let id = string_at(relay, "/id")?;
    807         let raw_url = string_at(relay, "/url")?;
    808         canonical_relay_url(raw_url, profile)?;
    809         let read = bool_at(relay, "/read")?;
    810         let write = bool_at(relay, "/write")?;
    811         if !ids.insert(id) || !urls.insert(raw_url) || (!read && !write) {
    812             return relationship_error();
    813         }
    814     }
    815     Ok(relays)
    816 }
    817 
    818 fn canonical_relay_url(value: &str, profile: RhiConfigProfile) -> Result<Url, RhiConfigV1Error> {
    819     let parsed = Url::parse(value).map_err(|_| error(RhiConfigV1ErrorKind::InvalidDocument))?;
    820     if parsed.as_str() != value
    821         || !parsed.username().is_empty()
    822         || parsed.password().is_some()
    823         || parsed.fragment().is_some()
    824     {
    825         return document_error();
    826     }
    827     let allowed = match profile {
    828         RhiConfigProfile::Production => parsed.scheme() == "wss",
    829         RhiConfigProfile::RepoLocal => match parsed.scheme() {
    830             "wss" => true,
    831             "ws" => parsed
    832                 .host_str()
    833                 .is_some_and(|host| matches!(host, "localhost" | "127.0.0.1" | "[::1]" | "::1")),
    834             _ => false,
    835         },
    836     };
    837     if !allowed {
    838         return relationship_error();
    839     }
    840     Ok(parsed)
    841 }
    842 
    843 fn validate_evidence(document: &Value, relays: &[Value]) -> Result<(), RhiConfigV1Error> {
    844     let sources = array(document, "/evidence/sources")?;
    845     let mut source_ids = BTreeSet::new();
    846     let mut bindings = BTreeSet::new();
    847     let mut required = false;
    848     let mut prior_source_id = None;
    849     for source in sources {
    850         let source_id = string_at(source, "/source_id")?;
    851         let kind = string_at(source, "/kind")?;
    852         let relay_id = string_at(source, "/relay_id")?;
    853         let selector = string_at(source, "/selector")?;
    854         if prior_source_id.is_some_and(|prior| prior >= source_id)
    855             || !source_ids.insert(source_id)
    856             || !bindings.insert((kind, relay_id, selector))
    857             || !relays.iter().any(|relay| {
    858                 string_at(relay, "/id") == Ok(relay_id) && bool_at(relay, "/read") == Ok(true)
    859             })
    860             || integer_at(source, "/overlap_seconds")? > integer_at(source, "/lookback_seconds")?
    861         {
    862             return relationship_error();
    863         }
    864         prior_source_id = Some(source_id);
    865         required |= bool_at(source, "/required")?;
    866     }
    867     if !required {
    868         return relationship_error();
    869     }
    870     Ok(())
    871 }
    872 
    873 fn validate_reconciliation(document: &Value) -> Result<(), RhiConfigV1Error> {
    874     if integer(document, "/reconciliation/lease_renewal_ms")?
    875         >= integer(document, "/reconciliation/lease_ms")?
    876         || integer(document, "/reconciliation/initial_backoff_ms")?
    877             > integer(document, "/reconciliation/maximum_backoff_ms")?
    878     {
    879         return relationship_error();
    880     }
    881     let attempt_deadline = integer(document, "/reconciliation/attempt_deadline_ms")?;
    882     if array(document, "/evidence/sources")?.iter().any(|source| {
    883         integer_at(source, "/deadline_ms").map_or(true, |value| value > attempt_deadline)
    884     }) {
    885         return relationship_error();
    886     }
    887     Ok(())
    888 }
    889 
    890 fn validate_publication(document: &Value, relays: &[Value]) -> Result<(), RhiConfigV1Error> {
    891     if string(document, "/publication/mode")? == "disabled" {
    892         return Ok(());
    893     }
    894     validate_relay_targets(document, "/publication/target_relay_ids", relays)?;
    895     if integer(document, "/publication/retry/initial_backoff_ms")?
    896         > integer(document, "/publication/retry/maximum_backoff_ms")?
    897     {
    898         return relationship_error();
    899     }
    900     Ok(())
    901 }
    902 
    903 fn validate_presence(document: &Value, relays: &[Value]) -> Result<(), RhiConfigV1Error> {
    904     if !bool_value(document, "/presence/enabled")? {
    905         if bool_value(document, "/presence/profile")?
    906             || bool_value(document, "/presence/application_handler")?
    907         {
    908             return relationship_error();
    909         }
    910         return Ok(());
    911     }
    912     if !bool_value(document, "/presence/profile")?
    913         && !bool_value(document, "/presence/application_handler")?
    914     {
    915         return relationship_error();
    916     }
    917     validate_relay_targets(document, "/presence/target_relay_ids", relays)
    918 }
    919 
    920 fn validate_relay_targets(
    921     document: &Value,
    922     pointer: &str,
    923     relays: &[Value],
    924 ) -> Result<(), RhiConfigV1Error> {
    925     for relay_id in string_set(document, pointer)? {
    926         if !relays.iter().any(|relay| {
    927             string_at(relay, "/id") == Ok(relay_id) && bool_at(relay, "/write") == Ok(true)
    928         }) {
    929             return relationship_error();
    930         }
    931     }
    932     Ok(())
    933 }
    934 
    935 fn validate_operations(document: &Value) -> Result<(), RhiConfigV1Error> {
    936     if !bool_value(document, "/operations/enabled")? {
    937         return Ok(());
    938     }
    939     let address = string(document, "/operations/listen")?
    940         .parse::<SocketAddr>()
    941         .map_err(|_| error(RhiConfigV1ErrorKind::InvalidDocument))?;
    942     if address.port() == 0
    943         || (!address.ip().is_loopback()
    944             && string(document, "/operations/bind_policy")? != "explicit_public")
    945     {
    946         return relationship_error();
    947     }
    948     Ok(())
    949 }
    950 
    951 fn validate_retention(document: &Value) -> Result<(), RhiConfigV1Error> {
    952     let audit = integer(document, "/retention/audit_ms")?;
    953     for field in [
    954         "duplicate_observations_ms",
    955         "completed_jobs_ms",
    956         "terminal_publication_attempts_ms",
    957         "terminal_presence_attempts_ms",
    958         "operation_dedup_ms",
    959     ] {
    960         if integer(document, &format!("/retention/{field}"))? > audit {
    961             return relationship_error();
    962         }
    963     }
    964     Ok(())
    965 }
    966 
    967 fn validate_exact_policy_limits(document: &Value) -> Result<(), RhiConfigV1Error> {
    968     if integer(document, "/resource_limits/source_results/events")? != 4_096
    969         || integer(document, "/resource_limits/source_results/bytes")? != 8_388_608
    970     {
    971         return relationship_error();
    972     }
    973     Ok(())
    974 }
    975 
    976 fn array<'a>(value: &'a Value, pointer: &str) -> Result<&'a [Value], RhiConfigV1Error> {
    977     value
    978         .pointer(pointer)
    979         .and_then(Value::as_array)
    980         .map(Vec::as_slice)
    981         .ok_or_else(|| error(RhiConfigV1ErrorKind::InvalidDocument))
    982 }
    983 
    984 fn string<'a>(value: &'a Value, pointer: &str) -> Result<&'a str, RhiConfigV1Error> {
    985     value
    986         .pointer(pointer)
    987         .and_then(Value::as_str)
    988         .ok_or_else(|| error(RhiConfigV1ErrorKind::InvalidDocument))
    989 }
    990 
    991 fn string_at<'a>(value: &'a Value, pointer: &str) -> Result<&'a str, RhiConfigV1Error> {
    992     string(value, pointer)
    993 }
    994 
    995 fn string_set<'a>(value: &'a Value, pointer: &str) -> Result<BTreeSet<&'a str>, RhiConfigV1Error> {
    996     array(value, pointer)?
    997         .iter()
    998         .map(|entry| {
    999             entry
   1000                 .as_str()
   1001                 .ok_or_else(|| error(RhiConfigV1ErrorKind::InvalidDocument))
   1002         })
   1003         .collect()
   1004 }
   1005 
   1006 fn bool_value(value: &Value, pointer: &str) -> Result<bool, RhiConfigV1Error> {
   1007     bool_at(value, pointer)
   1008 }
   1009 
   1010 fn bool_at(value: &Value, pointer: &str) -> Result<bool, RhiConfigV1Error> {
   1011     value
   1012         .pointer(pointer)
   1013         .and_then(Value::as_bool)
   1014         .ok_or_else(|| error(RhiConfigV1ErrorKind::InvalidDocument))
   1015 }
   1016 
   1017 fn integer(value: &Value, pointer: &str) -> Result<u64, RhiConfigV1Error> {
   1018     integer_at(value, pointer)
   1019 }
   1020 
   1021 fn integer_at(value: &Value, pointer: &str) -> Result<u64, RhiConfigV1Error> {
   1022     value
   1023         .pointer(pointer)
   1024         .and_then(Value::as_u64)
   1025         .ok_or_else(|| error(RhiConfigV1ErrorKind::InvalidDocument))
   1026 }
   1027 
   1028 fn document_error<T>() -> Result<T, RhiConfigV1Error> {
   1029     Err(error(RhiConfigV1ErrorKind::InvalidDocument))
   1030 }
   1031 
   1032 fn relationship_error<T>() -> Result<T, RhiConfigV1Error> {
   1033     Err(error(RhiConfigV1ErrorKind::InvalidRelationship))
   1034 }
   1035 
   1036 const fn error(kind: RhiConfigV1ErrorKind) -> RhiConfigV1Error {
   1037     RhiConfigV1Error::new(kind)
   1038 }
   1039 
   1040 #[derive(Serialize)]
   1041 struct EffectiveProjection {
   1042     schema: &'static str,
   1043     schema_version: u32,
   1044     fields: Vec<EffectiveField>,
   1045 }
   1046 
   1047 #[derive(Serialize)]
   1048 struct EffectiveField {
   1049     path: String,
   1050     source: RhiConfigValueSource,
   1051     #[serde(skip_serializing_if = "Option::is_none")]
   1052     default_authority: Option<RhiConfigDefaultAuthority>,
   1053     value: Value,
   1054 }
   1055 
   1056 fn build_effective(
   1057     normalized: &Value,
   1058     original: &toml::Table,
   1059 ) -> Result<RhiEffectiveConfigV1, RhiConfigV1Error> {
   1060     let mut flattened = BTreeMap::new();
   1061     flatten_value("", normalized, &mut flattened);
   1062     let original = toml::Value::Table(original.clone());
   1063     let fields = flattened
   1064         .into_iter()
   1065         .map(|(path, value)| {
   1066             let default_authority = default_entry(&path).and_then(|entry| {
   1067                 if toml_path(&original, &path).is_none() {
   1068                     Some(entry.authority)
   1069                 } else {
   1070                     None
   1071                 }
   1072             });
   1073             EffectiveField {
   1074                 source: if default_authority.is_some() {
   1075                     RhiConfigValueSource::SafeDefault
   1076                 } else {
   1077                     RhiConfigValueSource::Toml
   1078                 },
   1079                 default_authority,
   1080                 value: redacted_value(&path, value),
   1081                 path,
   1082             }
   1083         })
   1084         .collect::<Vec<_>>();
   1085     let field_count = fields.len();
   1086     let canonical_json = serde_json::to_string(&EffectiveProjection {
   1087         schema: "radroots.rhi.effective-config",
   1088         schema_version: 1,
   1089         fields,
   1090     })
   1091     .map_err(|_| error(RhiConfigV1ErrorKind::Encoding))?;
   1092     if canonical_json.len() > RHI_CONFIG_EFFECTIVE_MAX_UTF8_BYTES {
   1093         return Err(error(RhiConfigV1ErrorKind::Encoding));
   1094     }
   1095     Ok(RhiEffectiveConfigV1 {
   1096         canonical_json: canonical_json.into_boxed_str(),
   1097         field_count,
   1098     })
   1099 }
   1100 
   1101 fn flatten_value(path: &str, value: &Value, fields: &mut BTreeMap<String, Value>) {
   1102     match value {
   1103         Value::Object(object) => {
   1104             for (key, child) in object {
   1105                 flatten_value(&format!("{path}/{key}"), child, fields);
   1106             }
   1107         }
   1108         Value::Array(array) if array.iter().all(Value::is_object) => {
   1109             for (index, child) in array.iter().enumerate() {
   1110                 flatten_value(&format!("{path}/{index}"), child, fields);
   1111             }
   1112         }
   1113         _ => {
   1114             fields.insert(path.to_owned(), value.clone());
   1115         }
   1116     }
   1117 }
   1118 
   1119 fn redacted_value(path: &str, value: Value) -> Value {
   1120     let scalar_redaction = if path.ends_with("/envelope_path") {
   1121         Some("[redacted-path]")
   1122     } else if path.ends_with("/credential_reference") {
   1123         Some("[redacted-credential-reference]")
   1124     } else if path.ends_with("/expected_public_key") {
   1125         Some("[redacted-public-key]")
   1126     } else if path == "/operations/listen" {
   1127         Some("[redacted-address]")
   1128     } else if path == "/evidence/policy_id" {
   1129         Some("[redacted-policy-id]")
   1130     } else if path.starts_with("/relays/") && path.ends_with("/id") {
   1131         Some("[redacted-relay-id]")
   1132     } else if path.starts_with("/relays/") && path.ends_with("/url") {
   1133         Some("[redacted-url]")
   1134     } else if path.starts_with("/evidence/sources/") && path.ends_with("/source_id") {
   1135         Some("[redacted-source-id]")
   1136     } else if path.starts_with("/evidence/sources/") && path.ends_with("/relay_id") {
   1137         Some("[redacted-relay-id]")
   1138     } else {
   1139         None
   1140     };
   1141     if let Some(redaction) = scalar_redaction {
   1142         return Value::String(redaction.to_owned());
   1143     }
   1144     if matches!(
   1145         path,
   1146         "/publication/target_relay_ids" | "/presence/target_relay_ids"
   1147     ) {
   1148         return json!({
   1149             "count": value.as_array().map_or(0, Vec::len),
   1150             "values": "[redacted]"
   1151         });
   1152     }
   1153     value
   1154 }
   1155 
   1156 fn default_entry(path: &str) -> Option<&'static DefaultEntry> {
   1157     DEFAULTS.iter().find(|entry| entry.path == path)
   1158 }
   1159 
   1160 fn toml_path<'a>(root: &'a toml::Value, path: &str) -> Option<&'a toml::Value> {
   1161     path.split('/')
   1162         .filter(|part| !part.is_empty())
   1163         .try_fold(root, |value, part| {
   1164             if let Ok(index) = part.parse::<usize>() {
   1165                 value.as_array()?.get(index)
   1166             } else {
   1167                 value.as_table()?.get(part)
   1168             }
   1169         })
   1170 }
   1171 
   1172 #[cfg(test)]
   1173 mod tests {
   1174     use super::*;
   1175 
   1176     const EXAMPLE: &str = include_str!("../contracts/services_hardening/config.v1.example.toml");
   1177 
   1178     fn parse(source: &str) -> Result<RhiConfigDocumentV1, RhiConfigV1Error> {
   1179         parse_rhi_config_v1(source.as_bytes(), RhiConfigProfile::Production)
   1180     }
   1181 
   1182     fn replace(source: &str, old: &str, new: &str) -> String {
   1183         assert!(source.contains(old), "missing fixture fragment: {old}");
   1184         source.replacen(old, new, 1)
   1185     }
   1186 
   1187     #[test]
   1188     fn canonical_example_is_deterministic_bounded_and_redacted() {
   1189         let first = parse(EXAMPLE).expect("canonical example");
   1190         let second = parse(EXAMPLE).expect("canonical example again");
   1191         assert_eq!(first.schema(), RHI_CONFIG_SCHEMA);
   1192         assert_eq!(first.schema_version(), RHI_CONFIG_SCHEMA_VERSION);
   1193         assert_eq!(first.profile(), RhiConfigProfile::Production);
   1194         assert_eq!(first.relay_count(), 2);
   1195         assert_eq!(first.evidence_source_count(), 1);
   1196         assert_eq!(first.runtime_thread_limits().worker_threads(), 4);
   1197         assert_eq!(first.runtime_thread_limits().blocking_threads(), 8);
   1198         assert_eq!(first.effective(), second.effective());
   1199         assert!(first.effective().field_count() > 75);
   1200         let output = first.effective().canonical_json();
   1201         assert!(output.starts_with(
   1202             "{\"schema\":\"radroots.rhi.effective-config\",\"schema_version\":1,\"fields\":["
   1203         ));
   1204         assert!(output.len() <= RHI_CONFIG_EFFECTIVE_MAX_UTF8_BYTES);
   1205         let projection: Value = serde_json::from_str(output).expect("effective projection");
   1206         let fields = projection["fields"].as_array().expect("effective fields");
   1207         assert_eq!(fields.len(), first.effective().field_count());
   1208         let paths = fields
   1209             .iter()
   1210             .map(|field| field["path"].as_str().expect("effective path"))
   1211             .collect::<Vec<_>>();
   1212         assert!(paths.windows(2).all(|pair| pair[0] < pair[1]));
   1213         assert!(fields.iter().all(|field| {
   1214             field["source"] == "toml" && field.get("default_authority").is_none()
   1215         }));
   1216         for forbidden in [
   1217             "/var/lib/radroots",
   1218             "2222222222222222",
   1219             "relay.example.com",
   1220             "relay-primary",
   1221             "production-primary",
   1222             "trade-primary",
   1223             "service_wrapping_key",
   1224         ] {
   1225             assert!(!output.contains(forbidden), "leaked {forbidden}");
   1226             assert!(!format!("{first:?}").contains(forbidden));
   1227         }
   1228     }
   1229 
   1230     #[test]
   1231     fn exact_document_bound_precedes_utf8_and_toml_parsing() {
   1232         let mut exact = EXAMPLE.as_bytes().to_vec();
   1233         exact.extend_from_slice(b"\n#");
   1234         exact.resize(RHI_CONFIG_DOCUMENT_MAX_UTF8_BYTES, b'a');
   1235         assert!(parse_rhi_config_v1(&exact, RhiConfigProfile::Production).is_ok());
   1236         exact.push(0xff);
   1237         assert_eq!(
   1238             parse_rhi_config_v1(&exact, RhiConfigProfile::Production)
   1239                 .unwrap_err()
   1240                 .kind(),
   1241             RhiConfigV1ErrorKind::TooLarge
   1242         );
   1243     }
   1244 
   1245     #[test]
   1246     fn invalid_utf8_duplicate_null_unknown_and_malformed_wire_fail() {
   1247         assert_eq!(
   1248             parse_rhi_config_v1(&[0xff], RhiConfigProfile::Production)
   1249                 .unwrap_err()
   1250                 .kind(),
   1251             RhiConfigV1ErrorKind::InvalidUtf8
   1252         );
   1253         for source in [
   1254             format!("schema = \"radroots.rhi.config\"\n{EXAMPLE}"),
   1255             replace(
   1256                 EXAMPLE,
   1257                 "shutdown_grace_ms = 30000",
   1258                 "shutdown_grace_ms = null",
   1259             ),
   1260             replace(
   1261                 EXAMPLE,
   1262                 "shutdown_grace_ms = 30000",
   1263                 "shutdown_grace_ms = [null]",
   1264             ),
   1265             replace(
   1266                 EXAMPLE,
   1267                 "busy_timeout_ms = 5000",
   1268                 "busy_timeout_ms = 5000\nbusy_timeout_ms = 5000",
   1269             ),
   1270         ] {
   1271             assert_eq!(
   1272                 parse(&source).unwrap_err().kind(),
   1273                 RhiConfigV1ErrorKind::MalformedToml
   1274             );
   1275         }
   1276         let unknown = replace(
   1277             EXAMPLE,
   1278             "shutdown_grace_ms = 30000",
   1279             "shutdown_grace_ms = 30000\nsecret = \"do-not-render\"",
   1280         );
   1281         assert_eq!(
   1282             parse(&unknown).unwrap_err().kind(),
   1283             RhiConfigV1ErrorKind::InvalidDocument
   1284         );
   1285     }
   1286 
   1287     #[test]
   1288     fn header_failures_are_classified_before_document_admission() {
   1289         for (source, expected) in [
   1290             (
   1291                 EXAMPLE.replace("schema = \"radroots.rhi.config\"\n", ""),
   1292                 RhiConfigV1ErrorKind::MissingSchema,
   1293             ),
   1294             (
   1295                 replace(EXAMPLE, "schema = \"radroots.rhi.config\"", "schema = 1"),
   1296                 RhiConfigV1ErrorKind::InvalidSchema,
   1297             ),
   1298             (
   1299                 replace(EXAMPLE, "radroots.rhi.config", "radroots.myc.config"),
   1300                 RhiConfigV1ErrorKind::SchemaMismatch,
   1301             ),
   1302             (
   1303                 EXAMPLE.replace("schema_version = 1\n", ""),
   1304                 RhiConfigV1ErrorKind::MissingSchemaVersion,
   1305             ),
   1306             (
   1307                 replace(EXAMPLE, "schema_version = 1", "schema_version = \"1\""),
   1308                 RhiConfigV1ErrorKind::InvalidSchemaVersion,
   1309             ),
   1310             (
   1311                 replace(EXAMPLE, "schema_version = 1", "schema_version = 2"),
   1312                 RhiConfigV1ErrorKind::UnsupportedSchemaVersion,
   1313             ),
   1314         ] {
   1315             assert_eq!(parse(&source).unwrap_err().kind(), expected);
   1316         }
   1317     }
   1318 
   1319     #[test]
   1320     fn production_and_repo_local_relay_postures_are_distinct() {
   1321         let local = replace(EXAMPLE, "wss://relay.example.com/", "ws://127.0.0.1:7777/");
   1322         assert_eq!(
   1323             parse(&local).unwrap_err().kind(),
   1324             RhiConfigV1ErrorKind::InvalidRelationship
   1325         );
   1326         assert!(parse_rhi_config_v1(local.as_bytes(), RhiConfigProfile::RepoLocal).is_ok());
   1327         let remote = replace(&local, "ws://127.0.0.1:7777/", "ws://relay.example.com/");
   1328         assert_eq!(
   1329             parse_rhi_config_v1(remote.as_bytes(), RhiConfigProfile::RepoLocal)
   1330                 .unwrap_err()
   1331                 .kind(),
   1332             RhiConfigV1ErrorKind::InvalidRelationship
   1333         );
   1334     }
   1335 
   1336     #[test]
   1337     fn identity_relay_and_evidence_relationships_fail_closed() {
   1338         let cases = [
   1339             replace(
   1340                 EXAMPLE,
   1341                 "/var/lib/radroots/services/rhi/default/secrets/service.identity.ncrypt",
   1342                 "/var/lib/radroots/../escape.ncrypt",
   1343             ),
   1344             replace(
   1345                 EXAMPLE,
   1346                 "2222222222222222222222222222222222222222222222222222222222222222",
   1347                 "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
   1348             ),
   1349             replace(
   1350                 EXAMPLE,
   1351                 "id = \"relay-secondary\"",
   1352                 "id = \"relay-primary\"",
   1353             ),
   1354             replace(
   1355                 EXAMPLE,
   1356                 "wss://relay-secondary.example.com/",
   1357                 "wss://relay.example.com/",
   1358             ),
   1359             replace(
   1360                 EXAMPLE,
   1361                 "read = true\nwrite = true",
   1362                 "read = false\nwrite = false",
   1363             ),
   1364             replace(
   1365                 EXAMPLE,
   1366                 "required = true\nselector",
   1367                 "required = false\nselector",
   1368             ),
   1369             replace(
   1370                 EXAMPLE,
   1371                 "relay_id = \"relay-primary\"",
   1372                 "relay_id = \"missing\"",
   1373             ),
   1374             replace(EXAMPLE, "overlap_seconds = 300", "overlap_seconds = 86401"),
   1375             replace(
   1376                 EXAMPLE,
   1377                 "attempt_deadline_ms = 30000",
   1378                 "attempt_deadline_ms = 9999",
   1379             ),
   1380         ];
   1381         for source in cases {
   1382             assert!(matches!(
   1383                 parse(&source).unwrap_err().kind(),
   1384                 RhiConfigV1ErrorKind::InvalidDocument | RhiConfigV1ErrorKind::InvalidRelationship
   1385             ));
   1386         }
   1387 
   1388         let out_of_order = replace(
   1389             &replace(
   1390                 EXAMPLE,
   1391                 "read = false\nwrite = true",
   1392                 "read = true\nwrite = true",
   1393             ),
   1394             "[reconciliation]",
   1395             "[[evidence.sources]]\nsource_id = \"a-secondary\"\nkind = \"nostr_relay\"\nrelay_id = \"relay-secondary\"\nrequired = false\nselector = \"trade_mutation_lineage_v1\"\ndeadline_ms = 10000\nlookback_seconds = 86400\noverlap_seconds = 300\n\n[reconciliation]",
   1396         );
   1397         assert_eq!(
   1398             parse(&out_of_order).unwrap_err().kind(),
   1399             RhiConfigV1ErrorKind::InvalidRelationship
   1400         );
   1401     }
   1402 
   1403     #[test]
   1404     fn publication_presence_operations_retention_and_policy_limits_fail_closed() {
   1405         let cases = [
   1406             replace(
   1407                 EXAMPLE,
   1408                 "initial_backoff_ms = 250",
   1409                 "initial_backoff_ms = 30001",
   1410             ),
   1411             replace(
   1412                 EXAMPLE,
   1413                 "target_relay_ids = [\"relay-primary\", \"relay-secondary\"]",
   1414                 "target_relay_ids = [\"missing\"]",
   1415             ),
   1416             replace(
   1417                 EXAMPLE,
   1418                 "profile = true\napplication_handler = true",
   1419                 "profile = false\napplication_handler = false",
   1420             ),
   1421             replace(EXAMPLE, "audit_ms = 31536000000", "audit_ms = 1000"),
   1422             replace(EXAMPLE, "events = 4096", "events = 4095"),
   1423             replace(EXAMPLE, "bytes = 8388608", "bytes = 8388607"),
   1424         ];
   1425         for source in cases {
   1426             assert!(matches!(
   1427                 parse(&source).unwrap_err().kind(),
   1428                 RhiConfigV1ErrorKind::InvalidDocument | RhiConfigV1ErrorKind::InvalidRelationship
   1429             ));
   1430         }
   1431 
   1432         let public_loopback_policy = replace(
   1433             EXAMPLE,
   1434             "[operations]\nenabled = false",
   1435             "[operations]\nenabled = true\nlisten = \"0.0.0.0:9460\"\nbind_policy = \"loopback_only\"\n\n[operations.limits]",
   1436         );
   1437         assert_eq!(
   1438             parse(&public_loopback_policy).unwrap_err().kind(),
   1439             RhiConfigV1ErrorKind::InvalidRelationship
   1440         );
   1441     }
   1442 
   1443     #[test]
   1444     fn conditional_sections_remain_closed_when_disabled_and_default_when_enabled() {
   1445         let mut disabled = EXAMPLE.parse::<toml::Table>().expect("example TOML");
   1446         let publication = disabled
   1447             .get_mut("publication")
   1448             .and_then(toml::Value::as_table_mut)
   1449             .expect("publication table");
   1450         publication.insert(
   1451             "mode".to_owned(),
   1452             toml::Value::String("disabled".to_owned()),
   1453         );
   1454         publication.remove("target_relay_ids");
   1455         publication.remove("retry");
   1456         let presence = disabled
   1457             .get_mut("presence")
   1458             .and_then(toml::Value::as_table_mut)
   1459             .expect("presence table");
   1460         presence.insert("enabled".to_owned(), toml::Value::Boolean(false));
   1461         presence.insert("profile".to_owned(), toml::Value::Boolean(false));
   1462         presence.insert(
   1463             "application_handler".to_owned(),
   1464             toml::Value::Boolean(false),
   1465         );
   1466         presence.remove("target_relay_ids");
   1467         let parsed =
   1468             parse(&toml::to_string(&disabled).expect("disabled TOML")).expect("disabled sections");
   1469         let effective = parsed.effective().canonical_json();
   1470         assert!(!effective.contains("/publication/retry/"));
   1471         assert!(!effective.contains("/operations/limits/"));
   1472 
   1473         let enabled_operations = replace(
   1474             EXAMPLE,
   1475             "[operations]\nenabled = false",
   1476             "[operations]\nenabled = true\nlisten = \"127.0.0.1:9460\"\nbind_policy = \"loopback_only\"\n\n[operations.limits]",
   1477         );
   1478         let parsed = parse(&enabled_operations).expect("enabled operations defaults");
   1479         assert!(
   1480             parsed
   1481                 .effective()
   1482                 .canonical_json()
   1483                 .contains("/operations/limits/header_count")
   1484         );
   1485     }
   1486 
   1487     #[test]
   1488     fn all_frozen_defaults_are_applied_with_exact_provenance() {
   1489         assert_eq!(
   1490             serde_json::to_value([
   1491                 RhiConfigValueSource::BootstrapCli,
   1492                 RhiConfigValueSource::Toml,
   1493                 RhiConfigValueSource::SafeDefault,
   1494                 RhiConfigValueSource::DerivedPath,
   1495             ])
   1496             .expect("serialize provenance vocabulary"),
   1497             json!(["bootstrap_cli", "toml", "safe_default", "derived_path"])
   1498         );
   1499         assert_eq!(DEFAULTS.len(), 51);
   1500         let schema: Value = serde_json::from_str(CONFIG_SCHEMA).expect("embedded schema");
   1501         assert_eq!(count_schema_defaults(&schema), DEFAULTS.len());
   1502         for entry in DEFAULTS {
   1503             let pointer = schema_default_pointer(entry.path);
   1504             let schema_default = schema
   1505                 .pointer(&format!("{pointer}/default"))
   1506                 .expect("schema default");
   1507             let expected_default = match entry.value {
   1508                 DefaultValue::Integer(value) => json!(value),
   1509                 DefaultValue::String(value) => json!(value),
   1510             };
   1511             assert_eq!(schema_default, &expected_default, "{}", entry.path);
   1512             let expected_authority =
   1513                 serde_json::to_value(entry.authority).expect("serialize default authority");
   1514             assert_eq!(
   1515                 schema.pointer(&format!("{pointer}/x-radroots-default-source")),
   1516                 Some(&expected_authority),
   1517                 "{}",
   1518                 entry.path
   1519             );
   1520         }
   1521         let mut table = EXAMPLE.parse::<toml::Table>().expect("example TOML");
   1522         for entry in DEFAULTS {
   1523             remove_toml_path(&mut table, entry.path);
   1524         }
   1525         let minimal = toml::to_string(&table).expect("minimal TOML");
   1526         let parsed = parse(&minimal).expect("defaults admitted");
   1527         let output = parsed.effective().canonical_json();
   1528         for source in [
   1529             "accepted_service_authority",
   1530             "engineering_safety",
   1531             "radroots_service_sqlite",
   1532             "radroots_service_host",
   1533             "radroots_event",
   1534             "rhi_evidence_policy",
   1535         ] {
   1536             assert!(output.contains(&format!("\"default_authority\":\"{source}\"")));
   1537         }
   1538         assert!(output.contains("\"source\":\"safe_default\""));
   1539         let projection: Value = serde_json::from_str(output).expect("effective projection");
   1540         assert!(
   1541             projection["fields"]
   1542                 .as_array()
   1543                 .expect("effective fields")
   1544                 .iter()
   1545                 .all(|field| match field["source"].as_str() {
   1546                     Some("safe_default") => field.get("default_authority").is_some(),
   1547                     Some("toml") => field.get("default_authority").is_none(),
   1548                     _ => false,
   1549                 })
   1550         );
   1551         let explicit = parse(EXAMPLE).expect("explicit example");
   1552         assert!(
   1553             !explicit
   1554                 .effective()
   1555                 .canonical_json()
   1556                 .contains("default_authority")
   1557         );
   1558         assert!(
   1559             explicit
   1560                 .effective()
   1561                 .canonical_json()
   1562                 .matches("\"source\":\"toml\"")
   1563                 .count()
   1564                 > output.matches("\"source\":\"toml\"").count()
   1565         );
   1566     }
   1567 
   1568     #[test]
   1569     fn byte_limits_errors_and_debug_are_safe() {
   1570         let exact_reference = "a".repeat(128);
   1571         let exact = replace(EXAMPLE, "service_wrapping_key", &exact_reference);
   1572         assert!(parse(&exact).is_ok());
   1573         let over_reference = "a".repeat(129);
   1574         let over = replace(EXAMPLE, "service_wrapping_key", &over_reference);
   1575         assert_eq!(
   1576             parse(&over).unwrap_err().kind(),
   1577             RhiConfigV1ErrorKind::InvalidDocument
   1578         );
   1579 
   1580         let exact_path = format!("/{}a", "é".repeat(2_047));
   1581         assert_eq!(exact_path.len(), 4_096);
   1582         let exact = replace(
   1583             EXAMPLE,
   1584             "/var/lib/radroots/services/rhi/default/secrets/service.identity.ncrypt",
   1585             &exact_path,
   1586         );
   1587         assert!(parse(&exact).is_ok());
   1588         let over_path = format!("/{}", "é".repeat(2_048));
   1589         assert_eq!(over_path.len(), 4_097);
   1590         let over = replace(
   1591             EXAMPLE,
   1592             "/var/lib/radroots/services/rhi/default/secrets/service.identity.ncrypt",
   1593             &over_path,
   1594         );
   1595         assert_eq!(
   1596             parse(&over).unwrap_err().kind(),
   1597             RhiConfigV1ErrorKind::InvalidDocument
   1598         );
   1599 
   1600         let secret = "credential-secret-value";
   1601         let source = replace(
   1602             EXAMPLE,
   1603             "shutdown_grace_ms = 30000",
   1604             &format!("unknown = \"{secret}\""),
   1605         );
   1606         let failure = parse(&source).unwrap_err();
   1607         let rendered = format!("{failure} {failure:?}");
   1608         assert!(!rendered.contains(secret));
   1609         assert!(Error::source(&failure).is_none());
   1610     }
   1611 
   1612     fn remove_toml_path(table: &mut toml::Table, pointer: &str) {
   1613         let mut parts = pointer
   1614             .split('/')
   1615             .filter(|part| !part.is_empty())
   1616             .peekable();
   1617         let mut current = table;
   1618         while let Some(part) = parts.next() {
   1619             if parts.peek().is_none() {
   1620                 current.remove(part);
   1621                 return;
   1622             }
   1623             let Some(next) = current.get_mut(part).and_then(toml::Value::as_table_mut) else {
   1624                 return;
   1625             };
   1626             current = next;
   1627         }
   1628     }
   1629 
   1630     fn count_schema_defaults(value: &Value) -> usize {
   1631         match value {
   1632             Value::Object(object) => {
   1633                 usize::from(object.contains_key("default"))
   1634                     + object.values().map(count_schema_defaults).sum::<usize>()
   1635             }
   1636             Value::Array(array) => array.iter().map(count_schema_defaults).sum(),
   1637             _ => 0,
   1638         }
   1639     }
   1640 
   1641     fn schema_default_pointer(path: &str) -> String {
   1642         for (prefix, definition) in [
   1643             ("/operations/limits/", "operations_limits"),
   1644             ("/publication/retry/", "retry"),
   1645             ("/resource_limits/admin/", "admin_limits"),
   1646             ("/resource_limits/events/", "event_limits"),
   1647             ("/resource_limits/source_results/", "source_result_limits"),
   1648             ("/resource_limits/queues/", "queue_limits"),
   1649             ("/resource_limits/metrics/", "metrics_limits"),
   1650             ("/resource_limits/runtime/", "runtime_limits"),
   1651         ] {
   1652             if let Some(field) = path.strip_prefix(prefix) {
   1653                 return format!("/$defs/{definition}/properties/{field}");
   1654             }
   1655         }
   1656         let mut parts = path.split('/').filter(|part| !part.is_empty());
   1657         let definition = parts.next().expect("default definition");
   1658         let field = parts.next().expect("default field");
   1659         assert!(parts.next().is_none(), "unmapped default path: {path}");
   1660         format!("/$defs/{definition}/properties/{field}")
   1661     }
   1662 }