rhi

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

services_hardening_config_contract.rs (29944B)


      1 #![forbid(unsafe_code)]
      2 
      3 use std::collections::{BTreeMap, BTreeSet};
      4 use std::net::SocketAddr;
      5 use std::path::{Component, Path};
      6 
      7 use nostr::PublicKey;
      8 use serde_json::{Map, Value, json};
      9 
     10 const CONFIG_SCHEMA: &str = include_str!("../contracts/services_hardening/config.v1.schema.json");
     11 const CONFIG_EXAMPLE: &str = include_str!("../contracts/services_hardening/config.v1.example.toml");
     12 const EVIDENCE_POLICY: &str =
     13     include_str!("../contracts/services_hardening/evidence_policy.v1.json");
     14 
     15 #[derive(Clone, Copy)]
     16 enum Profile {
     17     Production,
     18     RepoLocal,
     19 }
     20 
     21 fn schema() -> Value {
     22     serde_json::from_str(CONFIG_SCHEMA).expect("configuration schema must be valid JSON")
     23 }
     24 
     25 fn evidence_policy() -> Value {
     26     serde_json::from_str(EVIDENCE_POLICY).expect("evidence policy must be valid JSON")
     27 }
     28 
     29 fn example() -> Value {
     30     let value = toml::from_str::<toml::Value>(CONFIG_EXAMPLE)
     31         .expect("configuration example must be valid TOML");
     32     serde_json::to_value(value).expect("TOML value must convert to JSON")
     33 }
     34 
     35 fn schema_valid(value: &Value) -> bool {
     36     jsonschema::validator_for(&schema())
     37         .expect("configuration schema must compile")
     38         .is_valid(value)
     39 }
     40 
     41 fn string_array<'a>(value: &'a Value, pointer: &str) -> Vec<&'a str> {
     42     value
     43         .pointer(pointer)
     44         .and_then(Value::as_array)
     45         .expect("string array")
     46         .iter()
     47         .map(|value| value.as_str().expect("string item"))
     48         .collect()
     49 }
     50 
     51 fn semantic_valid(value: &Value, profile: Profile) -> bool {
     52     if !schema_valid(value) {
     53         return false;
     54     }
     55 
     56     let relays = value["relays"].as_array().expect("relays");
     57     let relay_ids = relays
     58         .iter()
     59         .map(|relay| relay["id"].as_str().expect("relay id"))
     60         .collect::<Vec<_>>();
     61     let relay_urls = relays
     62         .iter()
     63         .map(|relay| relay["url"].as_str().expect("relay URL"))
     64         .collect::<Vec<_>>();
     65     let parsed_relay_urls = relay_urls
     66         .iter()
     67         .map(|raw| {
     68             url::Url::parse(raw)
     69                 .ok()
     70                 .filter(|parsed| parsed.as_str() == *raw)
     71         })
     72         .collect::<Vec<_>>();
     73     if relay_ids.iter().collect::<BTreeSet<_>>().len() != relay_ids.len()
     74         || relay_urls.iter().collect::<BTreeSet<_>>().len() != relay_urls.len()
     75         || parsed_relay_urls.iter().any(Option::is_none)
     76         || parsed_relay_urls.iter().flatten().any(|url| {
     77             !url.username().is_empty() || url.password().is_some() || url.fragment().is_some()
     78         })
     79         || relays.iter().any(|relay| {
     80             !relay["read"].as_bool().expect("read") && !relay["write"].as_bool().expect("write")
     81         })
     82     {
     83         return false;
     84     }
     85     if parsed_relay_urls.iter().flatten().any(|url| match profile {
     86         Profile::Production => url.scheme() != "wss",
     87         Profile::RepoLocal => match url.scheme() {
     88             "wss" => false,
     89             "ws" => !matches!(
     90                 url.host_str(),
     91                 Some("127.0.0.1" | "localhost" | "[::1]" | "::1")
     92             ),
     93             _ => true,
     94         },
     95     }) {
     96         return false;
     97     }
     98     let relays_by_id = relays
     99         .iter()
    100         .map(|relay| (relay["id"].as_str().expect("relay id"), relay))
    101         .collect::<BTreeMap<_, _>>();
    102 
    103     let envelope_path = value["identity"]["service"]["envelope_path"]
    104         .as_str()
    105         .expect("envelope path");
    106     let path = Path::new(envelope_path);
    107     if envelope_path.len() > 4096
    108         || path == Path::new("/")
    109         || !path.is_absolute()
    110         || path
    111             .components()
    112             .any(|component| matches!(component, Component::CurDir | Component::ParentDir))
    113     {
    114         return false;
    115     }
    116 
    117     let Ok(public_key) = PublicKey::from_hex(
    118         value["identity"]["service"]["expected_public_key"]
    119             .as_str()
    120             .expect("public key"),
    121     ) else {
    122         return false;
    123     };
    124     if public_key.xonly().is_err() {
    125         return false;
    126     }
    127 
    128     let sources = value["evidence"]["sources"]
    129         .as_array()
    130         .expect("evidence sources");
    131     let source_ids = sources
    132         .iter()
    133         .map(|source| source["source_id"].as_str().expect("source id"))
    134         .collect::<Vec<_>>();
    135     let source_bindings = sources
    136         .iter()
    137         .map(|source| {
    138             (
    139                 source["kind"].as_str().expect("kind"),
    140                 source["relay_id"].as_str().expect("relay id"),
    141                 source["selector"].as_str().expect("selector"),
    142             )
    143         })
    144         .collect::<Vec<_>>();
    145     if source_ids.iter().collect::<BTreeSet<_>>().len() != source_ids.len()
    146         || source_bindings.iter().collect::<BTreeSet<_>>().len() != source_bindings.len()
    147         || !sources
    148             .iter()
    149             .any(|source| source["required"].as_bool() == Some(true))
    150         || sources.iter().any(|source| {
    151             source["overlap_seconds"].as_u64() > source["lookback_seconds"].as_u64()
    152                 || relays_by_id
    153                     .get(source["relay_id"].as_str().expect("relay id"))
    154                     .and_then(|relay| relay["read"].as_bool())
    155                     != Some(true)
    156         })
    157     {
    158         return false;
    159     }
    160 
    161     let reconciliation = &value["reconciliation"];
    162     let lease_ms = reconciliation["lease_ms"].as_u64().unwrap_or(30_000);
    163     let lease_renewal_ms = reconciliation["lease_renewal_ms"]
    164         .as_u64()
    165         .unwrap_or(10_000);
    166     let initial_backoff_ms = reconciliation["initial_backoff_ms"].as_u64().unwrap_or(250);
    167     let maximum_backoff_ms = reconciliation["maximum_backoff_ms"]
    168         .as_u64()
    169         .unwrap_or(30_000);
    170     let attempt_deadline_ms = reconciliation["attempt_deadline_ms"]
    171         .as_u64()
    172         .unwrap_or(30_000);
    173     if lease_renewal_ms >= lease_ms
    174         || initial_backoff_ms > maximum_backoff_ms
    175         || sources
    176             .iter()
    177             .any(|source| source["deadline_ms"].as_u64() > Some(attempt_deadline_ms))
    178     {
    179         return false;
    180     }
    181 
    182     let publication = &value["publication"];
    183     if publication["mode"] == "required"
    184         && (string_array(value, "/publication/target_relay_ids")
    185             .iter()
    186             .any(|id| {
    187                 relays_by_id
    188                     .get(id)
    189                     .and_then(|relay| relay["write"].as_bool())
    190                     != Some(true)
    191             })
    192             || publication["retry"]["initial_backoff_ms"]
    193                 .as_u64()
    194                 .unwrap_or(250)
    195                 > publication["retry"]["maximum_backoff_ms"]
    196                     .as_u64()
    197                     .unwrap_or(30_000))
    198     {
    199         return false;
    200     }
    201 
    202     let presence = &value["presence"];
    203     if presence["enabled"] == true {
    204         if !presence["profile"].as_bool().unwrap_or(false)
    205             && !presence["application_handler"].as_bool().unwrap_or(false)
    206         {
    207             return false;
    208         }
    209         if string_array(value, "/presence/target_relay_ids")
    210             .iter()
    211             .any(|id| {
    212                 relays_by_id
    213                     .get(id)
    214                     .and_then(|relay| relay["write"].as_bool())
    215                     != Some(true)
    216             })
    217         {
    218             return false;
    219         }
    220     } else if presence["profile"] != false || presence["application_handler"] != false {
    221         return false;
    222     }
    223 
    224     let operations = &value["operations"];
    225     if operations["enabled"] == true {
    226         let Ok(listen) = operations["listen"]
    227             .as_str()
    228             .unwrap_or_default()
    229             .parse::<SocketAddr>()
    230         else {
    231             return false;
    232         };
    233         if listen.port() == 0
    234             || (operations["bind_policy"] == "loopback_only" && !listen.ip().is_loopback())
    235         {
    236             return false;
    237         }
    238     }
    239     if value["resource_limits"]["source_results"]["events"]
    240         .as_u64()
    241         .unwrap_or(4_096)
    242         != 4_096
    243         || value["resource_limits"]["source_results"]["bytes"]
    244             .as_u64()
    245             .unwrap_or(8_388_608)
    246             != 8_388_608
    247     {
    248         return false;
    249     }
    250     true
    251 }
    252 
    253 fn assert_rejected(value: &Value, profile: Profile) {
    254     assert!(
    255         !semantic_valid(value, profile),
    256         "negative configuration vector unexpectedly passed"
    257     );
    258 }
    259 
    260 fn insert(value: &mut Value, pointer: &str, key: &str, replacement: Value) {
    261     value
    262         .pointer_mut(pointer)
    263         .and_then(Value::as_object_mut)
    264         .expect("object pointer")
    265         .insert(key.to_owned(), replacement);
    266 }
    267 
    268 fn defaults(value: &Value, pointer: &str, output: &mut BTreeMap<String, Value>) {
    269     match value {
    270         Value::Object(object) => {
    271             if let Some(default) = object.get("default") {
    272                 assert!(
    273                     object.contains_key("x-radroots-default-source"),
    274                     "default without provenance at {pointer}"
    275                 );
    276                 output.insert(pointer.to_owned(), default.clone());
    277             }
    278             for (key, child) in object {
    279                 defaults(child, &format!("{pointer}/{key}"), output);
    280             }
    281         }
    282         Value::Array(values) => {
    283             for (index, child) in values.iter().enumerate() {
    284                 defaults(child, &format!("{pointer}/{index}"), output);
    285             }
    286         }
    287         _ => {}
    288     }
    289 }
    290 
    291 fn assert_integer_bounds(value: &Value, pointer: &str) {
    292     match value {
    293         Value::Object(object) => {
    294             if object.get("type") == Some(&Value::String("integer".to_owned())) {
    295                 assert!(
    296                     object.contains_key("minimum"),
    297                     "missing minimum at {pointer}"
    298                 );
    299                 assert!(
    300                     object.contains_key("maximum"),
    301                     "missing maximum at {pointer}"
    302                 );
    303             }
    304             for (key, child) in object {
    305                 assert_integer_bounds(child, &format!("{pointer}/{key}"));
    306             }
    307         }
    308         Value::Array(values) => {
    309             for (index, child) in values.iter().enumerate() {
    310                 assert_integer_bounds(child, &format!("{pointer}/{index}"));
    311             }
    312         }
    313         _ => {}
    314     }
    315 }
    316 
    317 fn assert_object_schemas_are_closed(value: &Value, pointer: &str) {
    318     match value {
    319         Value::Object(object) => {
    320             if object.get("type") == Some(&Value::String("object".to_owned())) {
    321                 assert_eq!(
    322                     object.get("additionalProperties"),
    323                     Some(&Value::Bool(false)),
    324                     "open object schema at {pointer}"
    325                 );
    326             }
    327             for (key, child) in object {
    328                 assert_object_schemas_are_closed(child, &format!("{pointer}/{key}"));
    329             }
    330         }
    331         Value::Array(values) => {
    332             for (index, child) in values.iter().enumerate() {
    333                 assert_object_schemas_are_closed(child, &format!("{pointer}/{index}"));
    334             }
    335         }
    336         _ => {}
    337     }
    338 }
    339 
    340 #[test]
    341 fn schema_identity_structure_and_machine_policy_are_exact() {
    342     let schema = schema();
    343     assert_eq!(
    344         schema["$schema"],
    345         "https://json-schema.org/draft/2020-12/schema"
    346     );
    347     assert_eq!(schema["type"], "object");
    348     assert_eq!(schema["additionalProperties"], false);
    349     assert_eq!(
    350         schema["required"],
    351         json!([
    352             "schema",
    353             "schema_version",
    354             "service",
    355             "logging",
    356             "operations",
    357             "database",
    358             "identity",
    359             "relays",
    360             "network",
    361             "evidence",
    362             "reconciliation",
    363             "attestation",
    364             "publication",
    365             "presence",
    366             "resource_limits",
    367             "retention"
    368         ])
    369     );
    370     assert_eq!(
    371         schema["properties"]["schema"]["const"],
    372         "radroots.rhi.config"
    373     );
    374     assert_eq!(schema["properties"]["schema_version"]["const"], 1);
    375     assert_eq!(
    376         schema["x-radroots-contract"]["bootstrap_only"],
    377         json!(["profile", "instance", "repo_local_root", "config_path"])
    378     );
    379     assert_eq!(
    380         schema["x-radroots-contract"]["document_max_utf8_bytes"],
    381         1_048_576
    382     );
    383     assert_eq!(
    384         schema["x-radroots-contract"]["duplicate_keys"],
    385         "reject_on_original_wire"
    386     );
    387     assert_eq!(
    388         schema["x-radroots-contract"]["null_values"],
    389         "reject_on_original_wire"
    390     );
    391     assert_eq!(
    392         schema["x-radroots-contract"]["environment_overlay"],
    393         "forbidden"
    394     );
    395     assert_eq!(
    396         schema["x-radroots-contract"]["protected_material"],
    397         "forbidden"
    398     );
    399     assert_eq!(
    400         schema["x-radroots-contract"]["effective_output"],
    401         "deterministic_redacted_with_exact_provenance"
    402     );
    403     assert_eq!(
    404         schema["x-radroots-contract"]["evidence_policy_sha256"],
    405         "b4da4aa2863905975c577668bc75cbb03857106a67899278130915e085319dc8"
    406     );
    407     assert_integer_bounds(&schema, "");
    408     assert_object_schemas_are_closed(&schema, "");
    409 
    410     let mut found_defaults = BTreeMap::new();
    411     defaults(&schema, "", &mut found_defaults);
    412     assert!(!found_defaults.is_empty());
    413     for pointer in found_defaults.keys() {
    414         let source = schema.pointer(pointer).expect("default pointer")["x-radroots-default-source"]
    415             .as_str()
    416             .expect("default source");
    417         assert!(
    418             schema["x-radroots-contract"]["safe_default_sources"]
    419                 .as_array()
    420                 .expect("safe default sources")
    421                 .iter()
    422                 .any(|allowed| allowed == source)
    423         );
    424     }
    425 }
    426 
    427 #[test]
    428 fn evidence_policy_is_consumed_without_reinterpretation() {
    429     let schema = schema();
    430     let policy = evidence_policy();
    431     assert_eq!(
    432         policy["configuration"]["contract"],
    433         schema["$defs"]["evidence"]["properties"]["contract"]["const"]
    434     );
    435     assert_eq!(
    436         policy["configuration"]["contract_version"],
    437         schema["$defs"]["evidence"]["properties"]["contract_version"]["const"]
    438     );
    439     assert_eq!(
    440         policy["configuration"]["sources"]["minimum_items"],
    441         schema["$defs"]["evidence"]["properties"]["sources"]["minItems"]
    442     );
    443     assert_eq!(
    444         policy["configuration"]["sources"]["maximum_items"],
    445         schema["$defs"]["evidence"]["properties"]["sources"]["maxItems"]
    446     );
    447     for field in ["deadline_ms", "lookback_seconds", "overlap_seconds"] {
    448         assert_eq!(
    449             policy["configuration"]["source"][field]["minimum"],
    450             schema["$defs"]["evidence_source"]["properties"][field]["minimum"]
    451         );
    452         assert_eq!(
    453             policy["configuration"]["source"][field]["maximum"],
    454             schema["$defs"]["evidence_source"]["properties"][field]["maximum"]
    455         );
    456     }
    457     assert_eq!(
    458         policy["source_kinds"]["qualified_v1"],
    459         json!(["nostr_relay"])
    460     );
    461     assert_eq!(policy["selector"]["id"], "trade_mutation_lineage_v1");
    462     assert_eq!(
    463         policy["completion"]["success_evidence"],
    464         "nostr_eose_received_before_source_deadline"
    465     );
    466     assert_eq!(
    467         policy["selector"]["maximum_events_per_result"],
    468         schema["$defs"]["source_result_limits"]["properties"]["events"]["maximum"]
    469     );
    470     assert_eq!(
    471         policy["selector"]["maximum_result_bytes"],
    472         schema["$defs"]["source_result_limits"]["properties"]["bytes"]["maximum"]
    473     );
    474 }
    475 
    476 #[test]
    477 fn canonical_example_and_required_positive_variants_pass() {
    478     let value = example();
    479     let validator = jsonschema::validator_for(&schema()).expect("schema");
    480     let schema_errors = validator
    481         .iter_errors(&value)
    482         .map(|error| error.to_string())
    483         .collect::<Vec<_>>();
    484     assert!(schema_errors.is_empty(), "{schema_errors:?}");
    485     assert!(semantic_valid(&value, Profile::Production));
    486     assert_eq!(value["identity"]["service"]["provider"], "encrypted_file");
    487     assert_eq!(
    488         value["attestation"],
    489         json!({
    490             "contract": "radroots.rhi.evidence_attestation.v1",
    491             "contract_version": 1,
    492             "method": "signed_evidence_snapshot",
    493             "reducer_contract": "radroots.trade.reducer.v1",
    494             "reducer_contract_version": 1
    495         })
    496     );
    497     assert_eq!(
    498         value["evidence"],
    499         evidence_policy()["configuration_fixture"]
    500     );
    501 
    502     let mut publication_disabled = value.clone();
    503     publication_disabled["publication"] = json!({"mode": "disabled"});
    504     assert!(semantic_valid(&publication_disabled, Profile::Production));
    505 
    506     let mut presence_disabled = value.clone();
    507     presence_disabled["presence"] = json!({
    508         "enabled": false,
    509         "profile": false,
    510         "application_handler": false
    511     });
    512     assert!(semantic_valid(&presence_disabled, Profile::Production));
    513 
    514     let mut repo_local = value.clone();
    515     repo_local["relays"][0]["url"] = json!("ws://127.0.0.1:8080/");
    516     repo_local["relays"][1]["url"] = json!("ws://localhost:8081/");
    517     assert!(semantic_valid(&repo_local, Profile::RepoLocal));
    518     assert_rejected(&repo_local, Profile::Production);
    519 
    520     let mut enabled_operations = value;
    521     enabled_operations["operations"] = json!({
    522         "enabled": true,
    523         "listen": "127.0.0.1:9460",
    524         "bind_policy": "loopback_only",
    525         "limits": {}
    526     });
    527     assert!(semantic_valid(&enabled_operations, Profile::Production));
    528 
    529     let mut sparse_retry = example();
    530     sparse_retry["publication"]["retry"] = json!({"initial_backoff_ms": 250});
    531     assert!(semantic_valid(&sparse_retry, Profile::Production));
    532 }
    533 
    534 #[test]
    535 fn structural_unknown_legacy_and_original_wire_vectors_fail() {
    536     let value = example();
    537     for (pointer, key) in [
    538         ("", "unknown"),
    539         ("/service", "shutdowm_grace_ms"),
    540         ("/identity/service", "wrapping_key_path"),
    541         ("/evidence/sources/0", "completion"),
    542         ("/resource_limits/admin", "body_bytes"),
    543     ] {
    544         let mut invalid = value.clone();
    545         insert(&mut invalid, pointer, key, json!(true));
    546         assert_rejected(&invalid, Profile::Production);
    547     }
    548     for field in schema()["x-radroots-contract"]["forbidden_prototype_fields"]
    549         .as_array()
    550         .expect("forbidden fields")
    551     {
    552         let mut invalid = value.clone();
    553         invalid.as_object_mut().expect("config object").insert(
    554             field.as_str().expect("field").to_owned(),
    555             Value::Object(Map::new()),
    556         );
    557         assert_rejected(&invalid, Profile::Production);
    558     }
    559     assert!(toml::from_str::<toml::Value>("schema='a'\nschema='b'\n").is_err());
    560     assert!(
    561         toml::from_str::<toml::Value>(
    562             "[service]\nshutdown_grace_ms=30000\nshutdown_grace_ms=30001\n"
    563         )
    564         .is_err()
    565     );
    566     assert!(toml::from_str::<toml::Value>("schema = null\n").is_err());
    567 }
    568 
    569 #[test]
    570 fn identity_relay_and_network_authority_fail_closed() {
    571     let value = example();
    572     let mut relative = value.clone();
    573     relative["identity"]["service"]["envelope_path"] = json!("relative/path");
    574     assert_rejected(&relative, Profile::Production);
    575     for path in ["/", "/var/lib/radroots/../escape.ncrypt"] {
    576         let mut invalid = value.clone();
    577         invalid["identity"]["service"]["envelope_path"] = json!(path);
    578         assert_rejected(&invalid, Profile::Production);
    579     }
    580     for provider in [
    581         "plaintext_file",
    582         "external_command",
    583         "keyring",
    584         "managed_account",
    585     ] {
    586         let mut invalid = value.clone();
    587         invalid["identity"]["service"]["provider"] = json!(provider);
    588         assert_rejected(&invalid, Profile::Production);
    589     }
    590     let mut invalid = value.clone();
    591     invalid["identity"]["service"]["expected_public_key"] = json!("f".repeat(64));
    592     assert_rejected(&invalid, Profile::Production);
    593     let mut duplicate_id = value.clone();
    594     duplicate_id["relays"][1]["id"] = duplicate_id["relays"][0]["id"].clone();
    595     assert_rejected(&duplicate_id, Profile::Production);
    596     let mut duplicate_url = value.clone();
    597     duplicate_url["relays"][1]["url"] = duplicate_url["relays"][0]["url"].clone();
    598     assert_rejected(&duplicate_url, Profile::Production);
    599     let mut inactive = value.clone();
    600     inactive["relays"][0]["read"] = json!(false);
    601     inactive["relays"][0]["write"] = json!(false);
    602     assert_rejected(&inactive, Profile::Production);
    603     let mut insecure = value.clone();
    604     insecure["relays"][0]["url"] = json!("ws://127.0.0.1:8080/");
    605     assert_rejected(&insecure, Profile::Production);
    606     let mut noncanonical = value;
    607     noncanonical["relays"][0]["url"] = json!("WSS://relay.example.com");
    608     assert_rejected(&noncanonical, Profile::Production);
    609 }
    610 
    611 #[test]
    612 fn evidence_scope_bounds_and_relationships_fail_closed() {
    613     let value = example();
    614     for (pointer, minimum, below, maximum, above) in [
    615         ("/evidence/sources/0/deadline_ms", 100, 99, 30_000, 30_001),
    616         (
    617             "/evidence/sources/0/lookback_seconds",
    618             60,
    619             59,
    620             2_678_400,
    621             2_678_401,
    622         ),
    623         ("/evidence/sources/0/overlap_seconds", 1, 0, 86_400, 86_401),
    624     ] {
    625         for valid in [minimum, maximum] {
    626             let mut candidate = value.clone();
    627             *candidate.pointer_mut(pointer).expect("bounded field") = json!(valid);
    628             if pointer.ends_with("lookback_seconds") && valid == 60 {
    629                 candidate["evidence"]["sources"][0]["overlap_seconds"] = json!(60);
    630             }
    631             assert!(semantic_valid(&candidate, Profile::Production), "{pointer}");
    632         }
    633         for invalid in [below, above] {
    634             let mut candidate = value.clone();
    635             *candidate.pointer_mut(pointer).expect("bounded field") = json!(invalid);
    636             assert_rejected(&candidate, Profile::Production);
    637         }
    638     }
    639     let mut no_required = value.clone();
    640     no_required["evidence"]["sources"][0]["required"] = json!(false);
    641     assert_rejected(&no_required, Profile::Production);
    642     let mut overlap = value.clone();
    643     overlap["evidence"]["sources"][0]["lookback_seconds"] = json!(60);
    644     overlap["evidence"]["sources"][0]["overlap_seconds"] = json!(61);
    645     assert_rejected(&overlap, Profile::Production);
    646     let mut missing_relay = value.clone();
    647     missing_relay["evidence"]["sources"][0]["relay_id"] = json!("missing");
    648     assert_rejected(&missing_relay, Profile::Production);
    649     let mut write_only = value.clone();
    650     write_only["relays"][0]["read"] = json!(false);
    651     assert_rejected(&write_only, Profile::Production);
    652     for replacement in [json!("filesystem"), json!("publication_target")] {
    653         let mut invalid = value.clone();
    654         invalid["evidence"]["sources"][0]["kind"] = replacement;
    655         assert_rejected(&invalid, Profile::Production);
    656     }
    657     let mut selector = value.clone();
    658     selector["evidence"]["sources"][0]["selector"] = json!("all_events_v1");
    659     assert_rejected(&selector, Profile::Production);
    660     let mut seventeen = value;
    661     let source = seventeen["evidence"]["sources"][0].clone();
    662     seventeen["evidence"]["sources"] = Value::Array(vec![source; 17]);
    663     assert_rejected(&seventeen, Profile::Production);
    664 }
    665 
    666 #[test]
    667 fn reconciliation_publication_presence_and_operations_fail_closed() {
    668     let value = example();
    669     let mut renewal = value.clone();
    670     renewal["reconciliation"]["lease_renewal_ms"] = renewal["reconciliation"]["lease_ms"].clone();
    671     assert_rejected(&renewal, Profile::Production);
    672     let mut backoff = value.clone();
    673     backoff["reconciliation"]["initial_backoff_ms"] = json!(30_001);
    674     assert_rejected(&backoff, Profile::Production);
    675     let mut attempt = value.clone();
    676     attempt["reconciliation"]["attempt_deadline_ms"] = json!(9_999);
    677     assert_rejected(&attempt, Profile::Production);
    678     let mut publication_relay = value.clone();
    679     publication_relay["publication"]["target_relay_ids"] = json!(["missing"]);
    680     assert_rejected(&publication_relay, Profile::Production);
    681     let mut disabled_leak = value.clone();
    682     disabled_leak["publication"]["mode"] = json!("disabled");
    683     assert_rejected(&disabled_leak, Profile::Production);
    684     let mut publication_backoff = value.clone();
    685     publication_backoff["publication"]["retry"]["initial_backoff_ms"] = json!(30_001);
    686     assert_rejected(&publication_backoff, Profile::Production);
    687     let mut presence_relay = value.clone();
    688     presence_relay["presence"]["target_relay_ids"] = json!(["missing"]);
    689     assert_rejected(&presence_relay, Profile::Production);
    690     let mut no_desired_presence = value.clone();
    691     no_desired_presence["presence"]["profile"] = json!(false);
    692     no_desired_presence["presence"]["application_handler"] = json!(false);
    693     assert_rejected(&no_desired_presence, Profile::Production);
    694     let mut disabled_presence_leak = value.clone();
    695     disabled_presence_leak["presence"]["enabled"] = json!(false);
    696     assert_rejected(&disabled_presence_leak, Profile::Production);
    697     let mut public_loopback_policy = value;
    698     public_loopback_policy["operations"] = json!({
    699         "enabled": true,
    700         "listen": "0.0.0.0:9460",
    701         "bind_policy": "loopback_only",
    702         "limits": {}
    703     });
    704     assert_rejected(&public_loopback_policy, Profile::Production);
    705 }
    706 
    707 #[test]
    708 fn exact_resource_boundaries_and_safe_defaults_are_frozen() {
    709     let value = example();
    710     for (pointer, maximum, over) in [
    711         ("/database/busy_timeout_ms", 60_000_u64, 60_001_u64),
    712         ("/database/max_connections", 8, 9),
    713         ("/resource_limits/admin/header_count", 64, 65),
    714         ("/resource_limits/admin/query_items", 200, 201),
    715         ("/resource_limits/events/wire_bytes", 524_288, 524_289),
    716         ("/resource_limits/metrics/samples", 512, 513),
    717         ("/resource_limits/runtime/worker_threads", 32, 33),
    718     ] {
    719         let mut minimum = value.clone();
    720         *minimum.pointer_mut(pointer).expect("bounded field") = json!(1);
    721         if pointer.ends_with("worker_threads") {
    722             *minimum.pointer_mut(pointer).expect("bounded field") = json!(2);
    723         }
    724         assert!(
    725             semantic_valid(&minimum, Profile::Production),
    726             "minimum {pointer}"
    727         );
    728         let mut exact = value.clone();
    729         *exact.pointer_mut(pointer).expect("bounded field") = json!(maximum);
    730         assert!(
    731             semantic_valid(&exact, Profile::Production),
    732             "maximum {pointer}"
    733         );
    734         let mut zero = value.clone();
    735         *zero.pointer_mut(pointer).expect("bounded field") = json!(0);
    736         assert_rejected(&zero, Profile::Production);
    737         let mut excessive = value.clone();
    738         *excessive.pointer_mut(pointer).expect("bounded field") = json!(over);
    739         assert_rejected(&excessive, Profile::Production);
    740     }
    741     for (pointer, exact, lower, over) in [
    742         (
    743             "/resource_limits/source_results/events",
    744             4_096,
    745             4_095,
    746             4_097,
    747         ),
    748         (
    749             "/resource_limits/source_results/bytes",
    750             8_388_608,
    751             8_388_607,
    752             8_388_609,
    753         ),
    754     ] {
    755         let mut exact_candidate = value.clone();
    756         *exact_candidate.pointer_mut(pointer).expect("bounded field") = json!(exact);
    757         assert!(semantic_valid(&exact_candidate, Profile::Production));
    758         for invalid in [0, lower, over] {
    759             let mut candidate = value.clone();
    760             *candidate.pointer_mut(pointer).expect("bounded field") = json!(invalid);
    761             assert_rejected(&candidate, Profile::Production);
    762         }
    763     }
    764     let mut exact_admin_response = value.clone();
    765     exact_admin_response["resource_limits"]["admin"]["response_body_utf8_bytes"] = json!(512);
    766     assert!(semantic_valid(&exact_admin_response, Profile::Production));
    767     let mut undersized = value.clone();
    768     undersized["resource_limits"]["admin"]["response_body_utf8_bytes"] = json!(511);
    769     assert_rejected(&undersized, Profile::Production);
    770 
    771     let mut omitted = value;
    772     omitted["service"] = json!({});
    773     omitted["logging"] = json!({});
    774     omitted["network"] = json!({});
    775     omitted["reconciliation"] = json!({});
    776     omitted["resource_limits"] = json!({});
    777     omitted["database"]
    778         .as_object_mut()
    779         .expect("database")
    780         .remove("busy_timeout_ms");
    781     omitted["database"]
    782         .as_object_mut()
    783         .expect("database")
    784         .remove("max_connections");
    785     assert!(semantic_valid(&omitted, Profile::Production));
    786 }
    787 
    788 #[test]
    789 fn schema_version_authority_and_protected_material_are_closed() {
    790     let value = example();
    791     for replacement in [json!("radroots.rhi.config.v2"), json!("rhi")] {
    792         let mut invalid = value.clone();
    793         invalid["schema"] = replacement;
    794         assert_rejected(&invalid, Profile::Production);
    795     }
    796     for replacement in [json!(0), json!(2), json!("1")] {
    797         let mut invalid = value.clone();
    798         invalid["schema_version"] = replacement;
    799         assert_rejected(&invalid, Profile::Production);
    800     }
    801     for required in [
    802         "minimum_free_bytes",
    803         "identity",
    804         "relays",
    805         "evidence",
    806         "sources",
    807         "deadline_ms",
    808         "lookback_seconds",
    809         "overlap_seconds",
    810         "reconciliation",
    811         "attestation",
    812         "publication",
    813         "presence",
    814         "resource_limits",
    815         "retention",
    816     ] {
    817         assert!(
    818             CONFIG_SCHEMA.contains(required),
    819             "missing authority {required}"
    820         );
    821     }
    822     let lowercase = CONFIG_EXAMPLE.to_ascii_lowercase();
    823     for forbidden in [
    824         "private_key",
    825         "secret_key",
    826         "mnemonic",
    827         "nsec1",
    828         "wrapping_key =",
    829         "wrapping_key_path",
    830         "rhi_",
    831         "env_file",
    832         "plaintext_file",
    833         "keyring",
    834         "managed_account",
    835         "worker_root",
    836         "state_file",
    837         "json_state",
    838     ] {
    839         assert!(
    840             !lowercase.contains(forbidden),
    841             "forbidden material {forbidden}"
    842         );
    843     }
    844 }