commit fda4c3c98f0b3ac6818e0e4924a11554c48d8ee1
parent 63a9735dd29b16c2d996cd3a01fac6b60cd626b7
Author: triesap <tyson@radroots.org>
Date: Sun, 23 Aug 2026 18:40:42 +0000
contracts: freeze RHI v1 configuration
Diffstat:
7 files changed, 1755 insertions(+), 18 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -24,6 +24,20 @@ dependencies = [
]
[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "getrandom 0.3.4",
+ "once_cell",
+ "serde",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -33,6 +47,12 @@ dependencies = [
]
[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -173,6 +193,21 @@ dependencies = [
]
[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
name = "bitcoin-io"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -214,12 +249,24 @@ dependencies = [
]
[[package]]
+name = "borrow-or-share"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
+
+[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
+name = "bytecount"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
+
+[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -316,7 +363,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -428,7 +475,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -455,6 +502,15 @@ dependencies = [
]
[[package]]
+name = "email_address"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -471,6 +527,17 @@ dependencies = [
]
[[package]]
+name = "fancy-regex"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
+dependencies = [
+ "bit-set",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -493,12 +560,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
+name = "fluent-uri"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e"
+dependencies = [
+ "borrow-or-share",
+ "ref-cast",
+ "serde",
+]
+
+[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -508,6 +592,16 @@ dependencies = [
]
[[package]]
+name = "fraction"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872"
+dependencies = [
+ "lazy_static",
+ "num",
+]
+
+[[package]]
name = "futures"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -613,9 +707,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
"r-efi 5.3.0",
"wasip2",
+ "wasm-bindgen",
]
[[package]]
@@ -660,7 +756,7 @@ version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
- "foldhash",
+ "foldhash 0.1.5",
]
[[package]]
@@ -670,6 +766,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
+
+[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -896,6 +1003,58 @@ dependencies = [
]
[[package]]
+name = "jsonschema"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f05a6cff806294a7699d48761d4ae2bdebab7fa2eef05e4a0b83320f0ff46901"
+dependencies = [
+ "ahash",
+ "bytecount",
+ "data-encoding",
+ "email_address",
+ "fancy-regex",
+ "fraction",
+ "getrandom 0.3.4",
+ "idna 1.1.0",
+ "itoa",
+ "jsonschema-regex",
+ "jsonschema-value",
+ "num-cmp",
+ "num-traits",
+ "percent-encoding",
+ "referencing",
+ "regex",
+ "serde",
+ "serde_json",
+ "strum",
+ "unicode-general-category",
+ "uuid-simd",
+]
+
+[[package]]
+name = "jsonschema-regex"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84708dd3f6c5327478a96f37378adb27deed6a626cf84d19de780d9e8ae84b17"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "jsonschema-value"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd8c04ab4ce60e42c4e302d3bd9d184652bb56f212495d2eea23573bcbf72556"
+dependencies = [
+ "ahash",
+ "bytecount",
+ "fraction",
+ "num-cmp",
+ "num-traits",
+ "serde_json",
+]
+
+[[package]]
name = "k256"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -978,6 +1137,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
+name = "micromap"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
+
+[[package]]
name = "mio"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1083,12 +1248,81 @@ dependencies = [
]
[[package]]
+name = "num"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-cmp"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa"
+
+[[package]]
+name = "num-complex"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
name = "num-conv"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
[[package]]
+name = "num-integer"
+version = "0.1.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1116,6 +1350,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
+name = "outref"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
+
+[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1213,7 +1453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -1429,6 +1669,55 @@ dependencies = [
]
[[package]]
+name = "ref-cast"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "referencing"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "980e77f5cfffc592c0e7dfb6d20770a0b96a6ab94d403de7454ed9799505e4ba"
+dependencies = [
+ "ahash",
+ "fluent-uri",
+ "getrandom 0.3.4",
+ "hashbrown 0.17.1",
+ "itoa",
+ "micromap",
+ "parking_lot",
+ "percent-encoding",
+ "serde_json",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1453,6 +1742,7 @@ dependencies = [
"chacha20poly1305",
"clap",
"futures-executor",
+ "jsonschema",
"nostr",
"nostr-sdk",
"radroots_event",
@@ -1473,6 +1763,7 @@ dependencies = [
"tracing",
"tracing-appender",
"tracing-subscriber",
+ "url",
"zeroize",
]
@@ -1647,7 +1938,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -1754,6 +2045,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
+name = "strum"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1771,6 +2083,17 @@ dependencies = [
]
[[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1778,7 +2101,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -1820,7 +2143,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -1831,7 +2154,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -1924,7 +2247,7 @@ checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -2037,7 +2360,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -2197,6 +2520,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
+name = "uuid-simd"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8"
+dependencies = [
+ "outref",
+ "vsimd",
+]
+
+[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2209,6 +2542,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
+name = "vsimd"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
+
+[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2278,7 +2617,7 @@ dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
"wasm-bindgen-shared",
]
@@ -2480,7 +2819,7 @@ dependencies = [
"heck",
"indexmap",
"prettyplease",
- "syn",
+ "syn 2.0.117",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
@@ -2496,7 +2835,7 @@ dependencies = [
"prettyplease",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
"wit-bindgen-core",
"wit-bindgen-rust",
]
@@ -2563,7 +2902,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
"synstructure",
]
@@ -2584,7 +2923,7 @@ checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -2604,7 +2943,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
"synstructure",
]
@@ -2644,7 +2983,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
@@ -54,3 +54,5 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
zeroize = { version = "1" }
[dev-dependencies]
+jsonschema = { version = "0.48.1", default-features = false }
+url = "2"
diff --git a/README b/README
@@ -8,6 +8,23 @@ RHI publishes its service kind-0 Profile only through the sealed
`RadrootsAuthoredProfile` replacement snapshot. It does not retain a generic
kind-0 event-authoring path.
+## Hardened v1 configuration contract
+
+The target service configuration is frozen by
+`contracts/services_hardening/config.v1.schema.json` and the canonical
+non-secret `config.v1.example.toml`. It is one strict immutable TOML document
+with explicit identity, relay, evidence-source, reconciliation, attestation,
+publication, presence, resource, and retention authority. The evidence fields
+and maxima consume `evidence_policy.v1.json` without reinterpretation. Only
+reviewed bounded operational leaves have defaults. Bootstrap profile,
+instance, repo-local root, and config-path selection are CLI concerns and are
+not document fields.
+
+The current runtime loader and root `config.toml` remain transitional until
+their ordered replacement steps are complete. Existing environment and worker
+selectors are prototype evidence, not authority to change or weaken the v1
+contract.
+
Use the repository-owned Nix lanes for validation:
```text
diff --git a/contracts/services_hardening/config.v1.example.toml b/contracts/services_hardening/config.v1.example.toml
@@ -0,0 +1,137 @@
+schema = "radroots.rhi.config"
+schema_version = 1
+
+[service]
+shutdown_grace_ms = 30000
+
+[logging]
+level = "info"
+format = "json"
+
+[operations]
+enabled = false
+
+[database]
+busy_timeout_ms = 5000
+max_connections = 8
+minimum_free_bytes = 268435456
+
+[identity.service]
+provider = "encrypted_file"
+envelope_path = "/var/lib/radroots/services/rhi/default/secrets/service.identity.ncrypt"
+credential_reference = "service_wrapping_key"
+expected_public_key = "2222222222222222222222222222222222222222222222222222222222222222"
+
+[[relays]]
+id = "relay-primary"
+url = "wss://relay.example.com/"
+read = true
+write = true
+required = true
+authentication = "required"
+
+[[relays]]
+id = "relay-secondary"
+url = "wss://relay-secondary.example.com/"
+read = false
+write = true
+required = false
+authentication = "none"
+
+[network]
+connect_deadline_ms = 10000
+dns_answer_limit = 16
+
+[evidence]
+contract = "radroots.rhi.evidence-policy"
+contract_version = 1
+policy_id = "production-primary"
+
+[[evidence.sources]]
+source_id = "trade-primary"
+kind = "nostr_relay"
+relay_id = "relay-primary"
+required = true
+selector = "trade_mutation_lineage_v1"
+deadline_ms = 10000
+lookback_seconds = 86400
+overlap_seconds = 300
+
+[reconciliation]
+concurrency = 8
+queue_capacity = 4096
+lease_ms = 30000
+lease_renewal_ms = 10000
+max_attempts = 10
+initial_backoff_ms = 250
+maximum_backoff_ms = 30000
+attempt_deadline_ms = 30000
+
+[attestation]
+contract = "radroots.rhi.evidence_attestation.v1"
+contract_version = 1
+method = "signed_evidence_snapshot"
+reducer_contract = "radroots.trade.reducer.v1"
+reducer_contract_version = 1
+
+[publication]
+mode = "required"
+target_relay_ids = ["relay-primary", "relay-secondary"]
+
+[publication.retry]
+max_attempts = 10
+initial_backoff_ms = 250
+maximum_backoff_ms = 30000
+attempt_deadline_ms = 15000
+
+[presence]
+enabled = true
+profile = true
+application_handler = true
+target_relay_ids = ["relay-primary", "relay-secondary"]
+
+[resource_limits.admin]
+header_count = 32
+header_bytes = 16384
+request_body_utf8_bytes = 65536
+response_body_utf8_bytes = 1048576
+concurrent_connections = 32
+request_deadline_ms = 15000
+idle_timeout_ms = 30000
+query_items = 100
+
+[resource_limits.events]
+wire_bytes = 262144
+content_bytes = 131072
+tag_count = 1024
+tag_total_elements = 4096
+tag_element_bytes = 4096
+tag_total_bytes = 131072
+
+[resource_limits.source_results]
+events = 4096
+bytes = 8388608
+
+[resource_limits.queues]
+ingress = 1024
+reconciliation = 4096
+publication = 4096
+presence = 64
+
+[resource_limits.metrics]
+descriptors = 64
+samples = 512
+labels_per_sample = 8
+render_utf8_bytes = 1048576
+
+[resource_limits.runtime]
+worker_threads = 4
+blocking_threads = 8
+
+[retention]
+duplicate_observations_ms = 604800000
+completed_jobs_ms = 2592000000
+terminal_publication_attempts_ms = 2592000000
+terminal_presence_attempts_ms = 2592000000
+operation_dedup_ms = 604800000
+audit_ms = 31536000000
diff --git a/contracts/services_hardening/config.v1.schema.json b/contracts/services_hardening/config.v1.schema.json
@@ -0,0 +1,426 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://radroots.org/contracts/rhi/config.v1.schema.json",
+ "title": "Radroots RHI configuration v1",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "schema",
+ "schema_version",
+ "service",
+ "logging",
+ "operations",
+ "database",
+ "identity",
+ "relays",
+ "network",
+ "evidence",
+ "reconciliation",
+ "attestation",
+ "publication",
+ "presence",
+ "resource_limits",
+ "retention"
+ ],
+ "properties": {
+ "schema": { "const": "radroots.rhi.config" },
+ "schema_version": { "const": 1 },
+ "service": { "$ref": "#/$defs/service" },
+ "logging": { "$ref": "#/$defs/logging" },
+ "operations": { "$ref": "#/$defs/operations" },
+ "database": { "$ref": "#/$defs/database" },
+ "identity": { "$ref": "#/$defs/identity" },
+ "relays": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 32,
+ "items": { "$ref": "#/$defs/relay" }
+ },
+ "network": { "$ref": "#/$defs/network" },
+ "evidence": { "$ref": "#/$defs/evidence" },
+ "reconciliation": { "$ref": "#/$defs/reconciliation" },
+ "attestation": { "$ref": "#/$defs/attestation" },
+ "publication": { "$ref": "#/$defs/publication" },
+ "presence": { "$ref": "#/$defs/presence" },
+ "resource_limits": { "$ref": "#/$defs/resource_limits" },
+ "retention": { "$ref": "#/$defs/retention" }
+ },
+ "$defs": {
+ "stable_id": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64,
+ "pattern": "^[a-z][a-z0-9_-]*$"
+ },
+ "contract_id": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "pattern": "^[a-z][a-z0-9._-]*$"
+ },
+ "absolute_path": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 4096,
+ "pattern": "^/"
+ },
+ "credential_reference": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "pattern": "^[a-z][a-z0-9_-]*$"
+ },
+ "public_key": {
+ "type": "string",
+ "minLength": 64,
+ "maxLength": 64,
+ "pattern": "^[0-9a-f]{64}$"
+ },
+ "relay_id": { "$ref": "#/$defs/stable_id" },
+ "relay_url": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 2048,
+ "pattern": "^(wss|ws)://"
+ },
+ "service": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "shutdown_grace_ms": {
+ "type": "integer",
+ "minimum": 1000,
+ "maximum": 300000,
+ "default": 30000,
+ "x-radroots-default-source": "accepted_service_authority"
+ }
+ }
+ },
+ "logging": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "level": {
+ "enum": ["error", "warn", "info", "debug", "trace"],
+ "default": "info",
+ "x-radroots-default-source": "engineering_safety"
+ },
+ "format": {
+ "enum": ["json", "text"],
+ "default": "json",
+ "x-radroots-default-source": "engineering_safety"
+ }
+ }
+ },
+ "operations_limits": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "header_count": { "type": "integer", "minimum": 1, "maximum": 64, "default": 32, "x-radroots-default-source": "radroots_service_host" },
+ "header_bytes": { "type": "integer", "minimum": 8192, "maximum": 32768, "default": 16384, "x-radroots-default-source": "radroots_service_host" },
+ "response_body_utf8_bytes": { "type": "integer", "minimum": 1, "maximum": 1048576, "default": 1048576, "x-radroots-default-source": "radroots_service_host" },
+ "concurrent_connections": { "type": "integer", "minimum": 1, "maximum": 64, "default": 32, "x-radroots-default-source": "radroots_service_host" },
+ "request_deadline_ms": { "type": "integer", "minimum": 1, "maximum": 30000, "default": 15000, "x-radroots-default-source": "radroots_service_host" },
+ "idle_timeout_ms": { "type": "integer", "minimum": 1, "maximum": 60000, "default": 30000, "x-radroots-default-source": "radroots_service_host" }
+ }
+ },
+ "operations": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["enabled"],
+ "properties": {
+ "enabled": { "type": "boolean" },
+ "listen": { "type": "string", "minLength": 1, "maxLength": 256 },
+ "bind_policy": { "enum": ["loopback_only", "explicit_public"] },
+ "limits": { "$ref": "#/$defs/operations_limits" }
+ },
+ "allOf": [
+ {
+ "if": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] },
+ "then": { "required": ["listen", "bind_policy", "limits"] },
+ "else": { "maxProperties": 1 }
+ }
+ ]
+ },
+ "database": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["minimum_free_bytes"],
+ "properties": {
+ "busy_timeout_ms": { "type": "integer", "minimum": 1, "maximum": 60000, "default": 5000, "x-radroots-default-source": "radroots_service_sqlite" },
+ "max_connections": { "type": "integer", "minimum": 1, "maximum": 8, "default": 8, "x-radroots-default-source": "radroots_service_sqlite" },
+ "minimum_free_bytes": { "type": "integer", "minimum": 1, "maximum": 9223372036854775807 }
+ }
+ },
+ "encrypted_file_identity": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["provider", "envelope_path", "credential_reference", "expected_public_key"],
+ "properties": {
+ "provider": { "const": "encrypted_file" },
+ "envelope_path": { "$ref": "#/$defs/absolute_path" },
+ "credential_reference": { "$ref": "#/$defs/credential_reference" },
+ "expected_public_key": { "$ref": "#/$defs/public_key" }
+ }
+ },
+ "identity": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["service"],
+ "properties": { "service": { "$ref": "#/$defs/encrypted_file_identity" } }
+ },
+ "relay": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["id", "url", "read", "write", "required", "authentication"],
+ "properties": {
+ "id": { "$ref": "#/$defs/relay_id" },
+ "url": { "$ref": "#/$defs/relay_url" },
+ "read": { "type": "boolean" },
+ "write": { "type": "boolean" },
+ "required": { "type": "boolean" },
+ "authentication": { "enum": ["none", "required"] }
+ }
+ },
+ "network": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "connect_deadline_ms": { "type": "integer", "minimum": 100, "maximum": 30000, "default": 10000, "x-radroots-default-source": "engineering_safety" },
+ "dns_answer_limit": { "type": "integer", "minimum": 1, "maximum": 64, "default": 16, "x-radroots-default-source": "engineering_safety" }
+ }
+ },
+ "evidence_source": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["source_id", "kind", "relay_id", "required", "selector", "deadline_ms", "lookback_seconds", "overlap_seconds"],
+ "properties": {
+ "source_id": { "$ref": "#/$defs/stable_id" },
+ "kind": { "const": "nostr_relay" },
+ "relay_id": { "$ref": "#/$defs/relay_id" },
+ "required": { "type": "boolean" },
+ "selector": { "const": "trade_mutation_lineage_v1" },
+ "deadline_ms": { "type": "integer", "minimum": 100, "maximum": 30000 },
+ "lookback_seconds": { "type": "integer", "minimum": 60, "maximum": 2678400 },
+ "overlap_seconds": { "type": "integer", "minimum": 1, "maximum": 86400 }
+ }
+ },
+ "evidence": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["contract", "contract_version", "policy_id", "sources"],
+ "properties": {
+ "contract": { "const": "radroots.rhi.evidence-policy" },
+ "contract_version": { "const": 1 },
+ "policy_id": { "$ref": "#/$defs/stable_id" },
+ "sources": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 16,
+ "items": { "$ref": "#/$defs/evidence_source" }
+ }
+ }
+ },
+ "reconciliation": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "concurrency": { "type": "integer", "minimum": 1, "maximum": 64, "default": 8, "x-radroots-default-source": "engineering_safety" },
+ "queue_capacity": { "type": "integer", "minimum": 1, "maximum": 65536, "default": 4096, "x-radroots-default-source": "engineering_safety" },
+ "lease_ms": { "type": "integer", "minimum": 1000, "maximum": 300000, "default": 30000, "x-radroots-default-source": "engineering_safety" },
+ "lease_renewal_ms": { "type": "integer", "minimum": 100, "maximum": 150000, "default": 10000, "x-radroots-default-source": "engineering_safety" },
+ "max_attempts": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10, "x-radroots-default-source": "engineering_safety" },
+ "initial_backoff_ms": { "type": "integer", "minimum": 1, "maximum": 60000, "default": 250, "x-radroots-default-source": "engineering_safety" },
+ "maximum_backoff_ms": { "type": "integer", "minimum": 1, "maximum": 3600000, "default": 30000, "x-radroots-default-source": "engineering_safety" },
+ "attempt_deadline_ms": { "type": "integer", "minimum": 100, "maximum": 300000, "default": 30000, "x-radroots-default-source": "engineering_safety" }
+ }
+ },
+ "attestation": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["contract", "contract_version", "method", "reducer_contract", "reducer_contract_version"],
+ "properties": {
+ "contract": { "const": "radroots.rhi.evidence_attestation.v1" },
+ "contract_version": { "const": 1 },
+ "method": { "const": "signed_evidence_snapshot" },
+ "reducer_contract": { "const": "radroots.trade.reducer.v1" },
+ "reducer_contract_version": { "const": 1 }
+ }
+ },
+ "retry": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "max_attempts": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10, "x-radroots-default-source": "engineering_safety" },
+ "initial_backoff_ms": { "type": "integer", "minimum": 1, "maximum": 60000, "default": 250, "x-radroots-default-source": "engineering_safety" },
+ "maximum_backoff_ms": { "type": "integer", "minimum": 1, "maximum": 3600000, "default": 30000, "x-radroots-default-source": "engineering_safety" },
+ "attempt_deadline_ms": { "type": "integer", "minimum": 100, "maximum": 30000, "default": 15000, "x-radroots-default-source": "engineering_safety" }
+ }
+ },
+ "publication": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["mode"],
+ "properties": {
+ "mode": { "enum": ["required", "disabled"] },
+ "target_relay_ids": { "type": "array", "minItems": 1, "maxItems": 32, "uniqueItems": true, "items": { "$ref": "#/$defs/relay_id" } },
+ "retry": { "$ref": "#/$defs/retry" }
+ },
+ "allOf": [
+ {
+ "if": { "properties": { "mode": { "const": "required" } }, "required": ["mode"] },
+ "then": { "required": ["target_relay_ids", "retry"] },
+ "else": { "maxProperties": 1 }
+ }
+ ]
+ },
+ "presence": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["enabled", "profile", "application_handler"],
+ "properties": {
+ "enabled": { "type": "boolean" },
+ "profile": { "type": "boolean" },
+ "application_handler": { "type": "boolean" },
+ "target_relay_ids": { "type": "array", "minItems": 1, "maxItems": 32, "uniqueItems": true, "items": { "$ref": "#/$defs/relay_id" } }
+ },
+ "allOf": [
+ {
+ "if": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] },
+ "then": { "required": ["target_relay_ids"] },
+ "else": { "maxProperties": 3 }
+ }
+ ]
+ },
+ "admin_limits": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "header_count": { "type": "integer", "minimum": 1, "maximum": 64, "default": 32, "x-radroots-default-source": "radroots_service_host" },
+ "header_bytes": { "type": "integer", "minimum": 8192, "maximum": 32768, "default": 16384, "x-radroots-default-source": "radroots_service_host" },
+ "request_body_utf8_bytes": { "type": "integer", "minimum": 1, "maximum": 65536, "default": 65536, "x-radroots-default-source": "radroots_service_host" },
+ "response_body_utf8_bytes": { "type": "integer", "minimum": 512, "maximum": 1048576, "default": 1048576, "x-radroots-default-source": "radroots_service_host" },
+ "concurrent_connections": { "type": "integer", "minimum": 1, "maximum": 64, "default": 32, "x-radroots-default-source": "radroots_service_host" },
+ "request_deadline_ms": { "type": "integer", "minimum": 1, "maximum": 30000, "default": 15000, "x-radroots-default-source": "radroots_service_host" },
+ "idle_timeout_ms": { "type": "integer", "minimum": 1, "maximum": 60000, "default": 30000, "x-radroots-default-source": "radroots_service_host" },
+ "query_items": { "type": "integer", "minimum": 1, "maximum": 200, "default": 100, "x-radroots-default-source": "accepted_service_authority" }
+ }
+ },
+ "event_limits": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "wire_bytes": { "type": "integer", "minimum": 1, "maximum": 524288, "default": 262144, "x-radroots-default-source": "radroots_event" },
+ "content_bytes": { "type": "integer", "minimum": 1, "maximum": 131072, "default": 131072, "x-radroots-default-source": "radroots_event" },
+ "tag_count": { "type": "integer", "minimum": 1, "maximum": 1024, "default": 1024, "x-radroots-default-source": "radroots_event" },
+ "tag_total_elements": { "type": "integer", "minimum": 1, "maximum": 4096, "default": 4096, "x-radroots-default-source": "radroots_event" },
+ "tag_element_bytes": { "type": "integer", "minimum": 1, "maximum": 4096, "default": 4096, "x-radroots-default-source": "radroots_event" },
+ "tag_total_bytes": { "type": "integer", "minimum": 1, "maximum": 131072, "default": 131072, "x-radroots-default-source": "radroots_event" }
+ }
+ },
+ "source_result_limits": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "events": { "type": "integer", "minimum": 1, "maximum": 4096, "default": 4096, "x-radroots-default-source": "rhi_evidence_policy" },
+ "bytes": { "type": "integer", "minimum": 1, "maximum": 8388608, "default": 8388608, "x-radroots-default-source": "rhi_evidence_policy" }
+ }
+ },
+ "queue_limits": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "ingress": { "type": "integer", "minimum": 1, "maximum": 65536, "default": 1024, "x-radroots-default-source": "engineering_safety" },
+ "reconciliation": { "type": "integer", "minimum": 1, "maximum": 65536, "default": 4096, "x-radroots-default-source": "engineering_safety" },
+ "publication": { "type": "integer", "minimum": 1, "maximum": 65536, "default": 4096, "x-radroots-default-source": "engineering_safety" },
+ "presence": { "type": "integer", "minimum": 1, "maximum": 4096, "default": 64, "x-radroots-default-source": "engineering_safety" }
+ }
+ },
+ "metrics_limits": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "descriptors": { "type": "integer", "minimum": 1, "maximum": 64, "default": 64, "x-radroots-default-source": "radroots_service_host" },
+ "samples": { "type": "integer", "minimum": 1, "maximum": 512, "default": 512, "x-radroots-default-source": "radroots_service_host" },
+ "labels_per_sample": { "type": "integer", "minimum": 1, "maximum": 8, "default": 8, "x-radroots-default-source": "radroots_service_host" },
+ "render_utf8_bytes": { "type": "integer", "minimum": 1, "maximum": 1048576, "default": 1048576, "x-radroots-default-source": "radroots_service_host" }
+ }
+ },
+ "runtime_limits": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "worker_threads": { "type": "integer", "minimum": 2, "maximum": 32, "default": 4, "x-radroots-default-source": "engineering_safety" },
+ "blocking_threads": { "type": "integer", "minimum": 1, "maximum": 32, "default": 8, "x-radroots-default-source": "engineering_safety" }
+ }
+ },
+ "resource_limits": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "admin": { "$ref": "#/$defs/admin_limits" },
+ "events": { "$ref": "#/$defs/event_limits" },
+ "source_results": { "$ref": "#/$defs/source_result_limits" },
+ "queues": { "$ref": "#/$defs/queue_limits" },
+ "metrics": { "$ref": "#/$defs/metrics_limits" },
+ "runtime": { "$ref": "#/$defs/runtime_limits" }
+ }
+ },
+ "retention": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["duplicate_observations_ms", "completed_jobs_ms", "terminal_publication_attempts_ms", "terminal_presence_attempts_ms", "operation_dedup_ms", "audit_ms"],
+ "properties": {
+ "duplicate_observations_ms": { "type": "integer", "minimum": 1000, "maximum": 31536000000 },
+ "completed_jobs_ms": { "type": "integer", "minimum": 1000, "maximum": 31536000000 },
+ "terminal_publication_attempts_ms": { "type": "integer", "minimum": 1000, "maximum": 31536000000 },
+ "terminal_presence_attempts_ms": { "type": "integer", "minimum": 1000, "maximum": 31536000000 },
+ "operation_dedup_ms": { "type": "integer", "minimum": 1000, "maximum": 31536000000 },
+ "audit_ms": { "type": "integer", "minimum": 1000, "maximum": 31536000000 }
+ }
+ }
+ },
+ "x-radroots-contract": {
+ "schema": "radroots.rhi.configuration-contract.v1",
+ "contract_version": 1,
+ "document_format": "toml_utf8",
+ "document_max_utf8_bytes": 1048576,
+ "unknown_fields": "reject_at_every_object_boundary",
+ "duplicate_keys": "reject_on_original_wire",
+ "null_values": "reject_on_original_wire",
+ "environment_overlay": "forbidden",
+ "protected_material": "forbidden",
+ "effective_output": "deterministic_redacted_with_exact_provenance",
+ "bootstrap_only": ["profile", "instance", "repo_local_root", "config_path"],
+ "evidence_policy": "contracts/services_hardening/evidence_policy.v1.json",
+ "evidence_policy_sha256": "b4da4aa2863905975c577668bc75cbb03857106a67899278130915e085319dc8",
+ "safe_default_sources": ["radroots_service_host", "radroots_service_sqlite", "radroots_event", "rhi_evidence_policy", "accepted_service_authority", "engineering_safety"],
+ "semantic_relationships": [
+ "relay_ids_unique",
+ "relay_urls_canonical_unique",
+ "relay_read_or_write",
+ "production_relays_wss_only",
+ "repo_local_plaintext_relays_loopback_only",
+ "identity_envelope_path_is_canonical_absolute_non_root",
+ "configured_service_public_key_is_valid_x_only_secp256k1",
+ "evidence_source_ids_unique",
+ "evidence_source_bindings_unique",
+ "evidence_source_relays_reference_read_relays",
+ "at_least_one_required_evidence_source",
+ "evidence_overlap_not_greater_than_lookback",
+ "evidence_completion_is_nostr_eose_before_deadline",
+ "source_result_limits_match_evidence_policy_maxima",
+ "reconciliation_renewal_less_than_lease",
+ "reconciliation_initial_backoff_not_greater_than_maximum_backoff",
+ "reconciliation_attempt_deadline_not_less_than_source_deadlines",
+ "publication_targets_reference_write_relays",
+ "publication_disabled_has_no_targets_or_retry",
+ "publication_initial_backoff_not_greater_than_maximum_backoff",
+ "presence_targets_reference_write_relays",
+ "presence_disabled_has_no_targets_and_no_desired_documents",
+ "operations_public_bind_requires_explicit_public_policy",
+ "retention_preserves_current_and_reproducibility_authority"
+ ],
+ "forbidden_prototype_fields": ["paths", "storage", "workers", "worker", "output_dir", "state_file", "state_path", "relay_url", "env_file", "environment", "identity_path", "private_key", "secret_key", "wrapping_key_path", "sqlite_path", "json_state", "hot_reload", "include"]
+ }
+}
diff --git a/radroots.lib.source-lock.v1.toml b/radroots.lib.source-lock.v1.toml
@@ -6,4 +6,4 @@ workspace_catalog_sha256 = "8c27cebf6825f9ed74e122513c661f6dd31837dddb39d0988014
version = "0.1.0-alpha"
source_archive_sha256 = "68badd1fb02d9396682d368e62dfe79969d8da48529760789c823fb4ab54aea3"
lockfile = "Cargo.lock"
-lockfile_sha256 = "9805ffe5e184cc377b41571c5d447a0b575b50a650e8b9b8b5df7059a567172f"
+lockfile_sha256 = "3dda0cfcb08b133439343585c17429474cac1657dd97a7d46bfca3b04b3a8a87"
diff --git a/tests/services_hardening_config_contract.rs b/tests/services_hardening_config_contract.rs
@@ -0,0 +1,816 @@
+#![forbid(unsafe_code)]
+
+use std::collections::{BTreeMap, BTreeSet};
+use std::net::SocketAddr;
+use std::path::{Component, Path};
+
+use nostr::PublicKey;
+use serde_json::{Map, Value, json};
+
+const CONFIG_SCHEMA: &str = include_str!("../contracts/services_hardening/config.v1.schema.json");
+const CONFIG_EXAMPLE: &str = include_str!("../contracts/services_hardening/config.v1.example.toml");
+const EVIDENCE_POLICY: &str =
+ include_str!("../contracts/services_hardening/evidence_policy.v1.json");
+
+#[derive(Clone, Copy)]
+enum Profile {
+ Production,
+ RepoLocal,
+}
+
+fn schema() -> Value {
+ serde_json::from_str(CONFIG_SCHEMA).expect("configuration schema must be valid JSON")
+}
+
+fn evidence_policy() -> Value {
+ serde_json::from_str(EVIDENCE_POLICY).expect("evidence policy must be valid JSON")
+}
+
+fn example() -> Value {
+ let value = toml::from_str::<toml::Value>(CONFIG_EXAMPLE)
+ .expect("configuration example must be valid TOML");
+ serde_json::to_value(value).expect("TOML value must convert to JSON")
+}
+
+fn schema_valid(value: &Value) -> bool {
+ jsonschema::validator_for(&schema())
+ .expect("configuration schema must compile")
+ .is_valid(value)
+}
+
+fn string_array<'a>(value: &'a Value, pointer: &str) -> Vec<&'a str> {
+ value
+ .pointer(pointer)
+ .and_then(Value::as_array)
+ .expect("string array")
+ .iter()
+ .map(|value| value.as_str().expect("string item"))
+ .collect()
+}
+
+fn semantic_valid(value: &Value, profile: Profile) -> bool {
+ if !schema_valid(value) {
+ return false;
+ }
+
+ let relays = value["relays"].as_array().expect("relays");
+ let relay_ids = relays
+ .iter()
+ .map(|relay| relay["id"].as_str().expect("relay id"))
+ .collect::<Vec<_>>();
+ let relay_urls = relays
+ .iter()
+ .map(|relay| relay["url"].as_str().expect("relay URL"))
+ .collect::<Vec<_>>();
+ let parsed_relay_urls = relay_urls
+ .iter()
+ .map(|raw| {
+ url::Url::parse(raw)
+ .ok()
+ .filter(|parsed| parsed.as_str() == *raw)
+ })
+ .collect::<Vec<_>>();
+ if relay_ids.iter().collect::<BTreeSet<_>>().len() != relay_ids.len()
+ || relay_urls.iter().collect::<BTreeSet<_>>().len() != relay_urls.len()
+ || parsed_relay_urls.iter().any(Option::is_none)
+ || parsed_relay_urls.iter().flatten().any(|url| {
+ !url.username().is_empty() || url.password().is_some() || url.fragment().is_some()
+ })
+ || relays.iter().any(|relay| {
+ !relay["read"].as_bool().expect("read") && !relay["write"].as_bool().expect("write")
+ })
+ {
+ return false;
+ }
+ if parsed_relay_urls.iter().flatten().any(|url| match profile {
+ Profile::Production => url.scheme() != "wss",
+ Profile::RepoLocal => match url.scheme() {
+ "wss" => false,
+ "ws" => !matches!(
+ url.host_str(),
+ Some("127.0.0.1" | "localhost" | "[::1]" | "::1")
+ ),
+ _ => true,
+ },
+ }) {
+ return false;
+ }
+ let relays_by_id = relays
+ .iter()
+ .map(|relay| (relay["id"].as_str().expect("relay id"), relay))
+ .collect::<BTreeMap<_, _>>();
+
+ let envelope_path = value["identity"]["service"]["envelope_path"]
+ .as_str()
+ .expect("envelope path");
+ let path = Path::new(envelope_path);
+ if envelope_path.len() > 4096
+ || path == Path::new("/")
+ || !path.is_absolute()
+ || path
+ .components()
+ .any(|component| matches!(component, Component::CurDir | Component::ParentDir))
+ {
+ return false;
+ }
+
+ let Ok(public_key) = PublicKey::from_hex(
+ value["identity"]["service"]["expected_public_key"]
+ .as_str()
+ .expect("public key"),
+ ) else {
+ return false;
+ };
+ if public_key.xonly().is_err() {
+ return false;
+ }
+
+ let sources = value["evidence"]["sources"]
+ .as_array()
+ .expect("evidence sources");
+ let source_ids = sources
+ .iter()
+ .map(|source| source["source_id"].as_str().expect("source id"))
+ .collect::<Vec<_>>();
+ let source_bindings = sources
+ .iter()
+ .map(|source| {
+ (
+ source["kind"].as_str().expect("kind"),
+ source["relay_id"].as_str().expect("relay id"),
+ source["selector"].as_str().expect("selector"),
+ )
+ })
+ .collect::<Vec<_>>();
+ if source_ids.iter().collect::<BTreeSet<_>>().len() != source_ids.len()
+ || source_bindings.iter().collect::<BTreeSet<_>>().len() != source_bindings.len()
+ || !sources
+ .iter()
+ .any(|source| source["required"].as_bool() == Some(true))
+ || sources.iter().any(|source| {
+ source["overlap_seconds"].as_u64() > source["lookback_seconds"].as_u64()
+ || relays_by_id
+ .get(source["relay_id"].as_str().expect("relay id"))
+ .and_then(|relay| relay["read"].as_bool())
+ != Some(true)
+ })
+ {
+ return false;
+ }
+
+ let reconciliation = &value["reconciliation"];
+ let lease_ms = reconciliation["lease_ms"].as_u64().unwrap_or(30_000);
+ let lease_renewal_ms = reconciliation["lease_renewal_ms"]
+ .as_u64()
+ .unwrap_or(10_000);
+ let initial_backoff_ms = reconciliation["initial_backoff_ms"].as_u64().unwrap_or(250);
+ let maximum_backoff_ms = reconciliation["maximum_backoff_ms"]
+ .as_u64()
+ .unwrap_or(30_000);
+ let attempt_deadline_ms = reconciliation["attempt_deadline_ms"]
+ .as_u64()
+ .unwrap_or(30_000);
+ if lease_renewal_ms >= lease_ms
+ || initial_backoff_ms > maximum_backoff_ms
+ || sources
+ .iter()
+ .any(|source| source["deadline_ms"].as_u64() > Some(attempt_deadline_ms))
+ {
+ return false;
+ }
+
+ let publication = &value["publication"];
+ if publication["mode"] == "required"
+ && (string_array(value, "/publication/target_relay_ids")
+ .iter()
+ .any(|id| {
+ relays_by_id
+ .get(id)
+ .and_then(|relay| relay["write"].as_bool())
+ != Some(true)
+ })
+ || publication["retry"]["initial_backoff_ms"]
+ .as_u64()
+ .unwrap_or(250)
+ > publication["retry"]["maximum_backoff_ms"]
+ .as_u64()
+ .unwrap_or(30_000))
+ {
+ return false;
+ }
+
+ let presence = &value["presence"];
+ if presence["enabled"] == true {
+ if !presence["profile"].as_bool().unwrap_or(false)
+ && !presence["application_handler"].as_bool().unwrap_or(false)
+ {
+ return false;
+ }
+ if string_array(value, "/presence/target_relay_ids")
+ .iter()
+ .any(|id| {
+ relays_by_id
+ .get(id)
+ .and_then(|relay| relay["write"].as_bool())
+ != Some(true)
+ })
+ {
+ return false;
+ }
+ } else if presence["profile"] != false || presence["application_handler"] != false {
+ return false;
+ }
+
+ let operations = &value["operations"];
+ if operations["enabled"] == true {
+ let Ok(listen) = operations["listen"]
+ .as_str()
+ .unwrap_or_default()
+ .parse::<SocketAddr>()
+ else {
+ return false;
+ };
+ if listen.port() == 0
+ || (operations["bind_policy"] == "loopback_only" && !listen.ip().is_loopback())
+ {
+ return false;
+ }
+ }
+ true
+}
+
+fn assert_rejected(value: &Value, profile: Profile) {
+ assert!(
+ !semantic_valid(value, profile),
+ "negative configuration vector unexpectedly passed"
+ );
+}
+
+fn insert(value: &mut Value, pointer: &str, key: &str, replacement: Value) {
+ value
+ .pointer_mut(pointer)
+ .and_then(Value::as_object_mut)
+ .expect("object pointer")
+ .insert(key.to_owned(), replacement);
+}
+
+fn defaults(value: &Value, pointer: &str, output: &mut BTreeMap<String, Value>) {
+ match value {
+ Value::Object(object) => {
+ if let Some(default) = object.get("default") {
+ assert!(
+ object.contains_key("x-radroots-default-source"),
+ "default without provenance at {pointer}"
+ );
+ output.insert(pointer.to_owned(), default.clone());
+ }
+ for (key, child) in object {
+ defaults(child, &format!("{pointer}/{key}"), output);
+ }
+ }
+ Value::Array(values) => {
+ for (index, child) in values.iter().enumerate() {
+ defaults(child, &format!("{pointer}/{index}"), output);
+ }
+ }
+ _ => {}
+ }
+}
+
+fn assert_integer_bounds(value: &Value, pointer: &str) {
+ match value {
+ Value::Object(object) => {
+ if object.get("type") == Some(&Value::String("integer".to_owned())) {
+ assert!(
+ object.contains_key("minimum"),
+ "missing minimum at {pointer}"
+ );
+ assert!(
+ object.contains_key("maximum"),
+ "missing maximum at {pointer}"
+ );
+ }
+ for (key, child) in object {
+ assert_integer_bounds(child, &format!("{pointer}/{key}"));
+ }
+ }
+ Value::Array(values) => {
+ for (index, child) in values.iter().enumerate() {
+ assert_integer_bounds(child, &format!("{pointer}/{index}"));
+ }
+ }
+ _ => {}
+ }
+}
+
+fn assert_object_schemas_are_closed(value: &Value, pointer: &str) {
+ match value {
+ Value::Object(object) => {
+ if object.get("type") == Some(&Value::String("object".to_owned())) {
+ assert_eq!(
+ object.get("additionalProperties"),
+ Some(&Value::Bool(false)),
+ "open object schema at {pointer}"
+ );
+ }
+ for (key, child) in object {
+ assert_object_schemas_are_closed(child, &format!("{pointer}/{key}"));
+ }
+ }
+ Value::Array(values) => {
+ for (index, child) in values.iter().enumerate() {
+ assert_object_schemas_are_closed(child, &format!("{pointer}/{index}"));
+ }
+ }
+ _ => {}
+ }
+}
+
+#[test]
+fn schema_identity_structure_and_machine_policy_are_exact() {
+ let schema = schema();
+ assert_eq!(
+ schema["$schema"],
+ "https://json-schema.org/draft/2020-12/schema"
+ );
+ assert_eq!(schema["type"], "object");
+ assert_eq!(schema["additionalProperties"], false);
+ assert_eq!(
+ schema["required"],
+ json!([
+ "schema",
+ "schema_version",
+ "service",
+ "logging",
+ "operations",
+ "database",
+ "identity",
+ "relays",
+ "network",
+ "evidence",
+ "reconciliation",
+ "attestation",
+ "publication",
+ "presence",
+ "resource_limits",
+ "retention"
+ ])
+ );
+ assert_eq!(
+ schema["properties"]["schema"]["const"],
+ "radroots.rhi.config"
+ );
+ assert_eq!(schema["properties"]["schema_version"]["const"], 1);
+ assert_eq!(
+ schema["x-radroots-contract"]["bootstrap_only"],
+ json!(["profile", "instance", "repo_local_root", "config_path"])
+ );
+ assert_eq!(
+ schema["x-radroots-contract"]["document_max_utf8_bytes"],
+ 1_048_576
+ );
+ assert_eq!(
+ schema["x-radroots-contract"]["duplicate_keys"],
+ "reject_on_original_wire"
+ );
+ assert_eq!(
+ schema["x-radroots-contract"]["null_values"],
+ "reject_on_original_wire"
+ );
+ assert_eq!(
+ schema["x-radroots-contract"]["environment_overlay"],
+ "forbidden"
+ );
+ assert_eq!(
+ schema["x-radroots-contract"]["protected_material"],
+ "forbidden"
+ );
+ assert_eq!(
+ schema["x-radroots-contract"]["effective_output"],
+ "deterministic_redacted_with_exact_provenance"
+ );
+ assert_eq!(
+ schema["x-radroots-contract"]["evidence_policy_sha256"],
+ "b4da4aa2863905975c577668bc75cbb03857106a67899278130915e085319dc8"
+ );
+ assert_integer_bounds(&schema, "");
+ assert_object_schemas_are_closed(&schema, "");
+
+ let mut found_defaults = BTreeMap::new();
+ defaults(&schema, "", &mut found_defaults);
+ assert!(!found_defaults.is_empty());
+ for pointer in found_defaults.keys() {
+ let source = schema.pointer(pointer).expect("default pointer")["x-radroots-default-source"]
+ .as_str()
+ .expect("default source");
+ assert!(
+ schema["x-radroots-contract"]["safe_default_sources"]
+ .as_array()
+ .expect("safe default sources")
+ .iter()
+ .any(|allowed| allowed == source)
+ );
+ }
+}
+
+#[test]
+fn evidence_policy_is_consumed_without_reinterpretation() {
+ let schema = schema();
+ let policy = evidence_policy();
+ assert_eq!(
+ policy["configuration"]["contract"],
+ schema["$defs"]["evidence"]["properties"]["contract"]["const"]
+ );
+ assert_eq!(
+ policy["configuration"]["contract_version"],
+ schema["$defs"]["evidence"]["properties"]["contract_version"]["const"]
+ );
+ assert_eq!(
+ policy["configuration"]["sources"]["minimum_items"],
+ schema["$defs"]["evidence"]["properties"]["sources"]["minItems"]
+ );
+ assert_eq!(
+ policy["configuration"]["sources"]["maximum_items"],
+ schema["$defs"]["evidence"]["properties"]["sources"]["maxItems"]
+ );
+ for field in ["deadline_ms", "lookback_seconds", "overlap_seconds"] {
+ assert_eq!(
+ policy["configuration"]["source"][field]["minimum"],
+ schema["$defs"]["evidence_source"]["properties"][field]["minimum"]
+ );
+ assert_eq!(
+ policy["configuration"]["source"][field]["maximum"],
+ schema["$defs"]["evidence_source"]["properties"][field]["maximum"]
+ );
+ }
+ assert_eq!(
+ policy["source_kinds"]["qualified_v1"],
+ json!(["nostr_relay"])
+ );
+ assert_eq!(policy["selector"]["id"], "trade_mutation_lineage_v1");
+ assert_eq!(
+ policy["completion"]["success_evidence"],
+ "nostr_eose_received_before_source_deadline"
+ );
+ assert_eq!(
+ policy["selector"]["maximum_events_per_result"],
+ schema["$defs"]["source_result_limits"]["properties"]["events"]["maximum"]
+ );
+ assert_eq!(
+ policy["selector"]["maximum_result_bytes"],
+ schema["$defs"]["source_result_limits"]["properties"]["bytes"]["maximum"]
+ );
+}
+
+#[test]
+fn canonical_example_and_required_positive_variants_pass() {
+ let value = example();
+ let validator = jsonschema::validator_for(&schema()).expect("schema");
+ let schema_errors = validator
+ .iter_errors(&value)
+ .map(|error| error.to_string())
+ .collect::<Vec<_>>();
+ assert!(schema_errors.is_empty(), "{schema_errors:?}");
+ assert!(semantic_valid(&value, Profile::Production));
+ assert_eq!(value["identity"]["service"]["provider"], "encrypted_file");
+ assert_eq!(
+ value["attestation"],
+ json!({
+ "contract": "radroots.rhi.evidence_attestation.v1",
+ "contract_version": 1,
+ "method": "signed_evidence_snapshot",
+ "reducer_contract": "radroots.trade.reducer.v1",
+ "reducer_contract_version": 1
+ })
+ );
+ assert_eq!(
+ value["evidence"],
+ evidence_policy()["configuration_fixture"]
+ );
+
+ let mut publication_disabled = value.clone();
+ publication_disabled["publication"] = json!({"mode": "disabled"});
+ assert!(semantic_valid(&publication_disabled, Profile::Production));
+
+ let mut presence_disabled = value.clone();
+ presence_disabled["presence"] = json!({
+ "enabled": false,
+ "profile": false,
+ "application_handler": false
+ });
+ assert!(semantic_valid(&presence_disabled, Profile::Production));
+
+ let mut repo_local = value.clone();
+ repo_local["relays"][0]["url"] = json!("ws://127.0.0.1:8080/");
+ repo_local["relays"][1]["url"] = json!("ws://localhost:8081/");
+ assert!(semantic_valid(&repo_local, Profile::RepoLocal));
+ assert_rejected(&repo_local, Profile::Production);
+
+ let mut enabled_operations = value;
+ enabled_operations["operations"] = json!({
+ "enabled": true,
+ "listen": "127.0.0.1:9460",
+ "bind_policy": "loopback_only",
+ "limits": {}
+ });
+ assert!(semantic_valid(&enabled_operations, Profile::Production));
+
+ let mut sparse_retry = example();
+ sparse_retry["publication"]["retry"] = json!({"initial_backoff_ms": 250});
+ assert!(semantic_valid(&sparse_retry, Profile::Production));
+}
+
+#[test]
+fn structural_unknown_legacy_and_original_wire_vectors_fail() {
+ let value = example();
+ for (pointer, key) in [
+ ("", "unknown"),
+ ("/service", "shutdowm_grace_ms"),
+ ("/identity/service", "wrapping_key_path"),
+ ("/evidence/sources/0", "completion"),
+ ("/resource_limits/admin", "body_bytes"),
+ ] {
+ let mut invalid = value.clone();
+ insert(&mut invalid, pointer, key, json!(true));
+ assert_rejected(&invalid, Profile::Production);
+ }
+ for field in schema()["x-radroots-contract"]["forbidden_prototype_fields"]
+ .as_array()
+ .expect("forbidden fields")
+ {
+ let mut invalid = value.clone();
+ invalid.as_object_mut().expect("config object").insert(
+ field.as_str().expect("field").to_owned(),
+ Value::Object(Map::new()),
+ );
+ assert_rejected(&invalid, Profile::Production);
+ }
+ assert!(toml::from_str::<toml::Value>("schema='a'\nschema='b'\n").is_err());
+ assert!(
+ toml::from_str::<toml::Value>(
+ "[service]\nshutdown_grace_ms=30000\nshutdown_grace_ms=30001\n"
+ )
+ .is_err()
+ );
+ assert!(toml::from_str::<toml::Value>("schema = null\n").is_err());
+}
+
+#[test]
+fn identity_relay_and_network_authority_fail_closed() {
+ let value = example();
+ let mut relative = value.clone();
+ relative["identity"]["service"]["envelope_path"] = json!("relative/path");
+ assert_rejected(&relative, Profile::Production);
+ for path in ["/", "/var/lib/radroots/../escape.ncrypt"] {
+ let mut invalid = value.clone();
+ invalid["identity"]["service"]["envelope_path"] = json!(path);
+ assert_rejected(&invalid, Profile::Production);
+ }
+ for provider in [
+ "plaintext_file",
+ "external_command",
+ "keyring",
+ "managed_account",
+ ] {
+ let mut invalid = value.clone();
+ invalid["identity"]["service"]["provider"] = json!(provider);
+ assert_rejected(&invalid, Profile::Production);
+ }
+ let mut invalid = value.clone();
+ invalid["identity"]["service"]["expected_public_key"] = json!("f".repeat(64));
+ assert_rejected(&invalid, Profile::Production);
+ let mut duplicate_id = value.clone();
+ duplicate_id["relays"][1]["id"] = duplicate_id["relays"][0]["id"].clone();
+ assert_rejected(&duplicate_id, Profile::Production);
+ let mut duplicate_url = value.clone();
+ duplicate_url["relays"][1]["url"] = duplicate_url["relays"][0]["url"].clone();
+ assert_rejected(&duplicate_url, Profile::Production);
+ let mut inactive = value.clone();
+ inactive["relays"][0]["read"] = json!(false);
+ inactive["relays"][0]["write"] = json!(false);
+ assert_rejected(&inactive, Profile::Production);
+ let mut insecure = value.clone();
+ insecure["relays"][0]["url"] = json!("ws://127.0.0.1:8080/");
+ assert_rejected(&insecure, Profile::Production);
+ let mut noncanonical = value;
+ noncanonical["relays"][0]["url"] = json!("WSS://relay.example.com");
+ assert_rejected(&noncanonical, Profile::Production);
+}
+
+#[test]
+fn evidence_scope_bounds_and_relationships_fail_closed() {
+ let value = example();
+ for (pointer, minimum, below, maximum, above) in [
+ ("/evidence/sources/0/deadline_ms", 100, 99, 30_000, 30_001),
+ (
+ "/evidence/sources/0/lookback_seconds",
+ 60,
+ 59,
+ 2_678_400,
+ 2_678_401,
+ ),
+ ("/evidence/sources/0/overlap_seconds", 1, 0, 86_400, 86_401),
+ ] {
+ for valid in [minimum, maximum] {
+ let mut candidate = value.clone();
+ *candidate.pointer_mut(pointer).expect("bounded field") = json!(valid);
+ if pointer.ends_with("lookback_seconds") && valid == 60 {
+ candidate["evidence"]["sources"][0]["overlap_seconds"] = json!(60);
+ }
+ assert!(semantic_valid(&candidate, Profile::Production), "{pointer}");
+ }
+ for invalid in [below, above] {
+ let mut candidate = value.clone();
+ *candidate.pointer_mut(pointer).expect("bounded field") = json!(invalid);
+ assert_rejected(&candidate, Profile::Production);
+ }
+ }
+ let mut no_required = value.clone();
+ no_required["evidence"]["sources"][0]["required"] = json!(false);
+ assert_rejected(&no_required, Profile::Production);
+ let mut overlap = value.clone();
+ overlap["evidence"]["sources"][0]["lookback_seconds"] = json!(60);
+ overlap["evidence"]["sources"][0]["overlap_seconds"] = json!(61);
+ assert_rejected(&overlap, Profile::Production);
+ let mut missing_relay = value.clone();
+ missing_relay["evidence"]["sources"][0]["relay_id"] = json!("missing");
+ assert_rejected(&missing_relay, Profile::Production);
+ let mut write_only = value.clone();
+ write_only["relays"][0]["read"] = json!(false);
+ assert_rejected(&write_only, Profile::Production);
+ for replacement in [json!("filesystem"), json!("publication_target")] {
+ let mut invalid = value.clone();
+ invalid["evidence"]["sources"][0]["kind"] = replacement;
+ assert_rejected(&invalid, Profile::Production);
+ }
+ let mut selector = value.clone();
+ selector["evidence"]["sources"][0]["selector"] = json!("all_events_v1");
+ assert_rejected(&selector, Profile::Production);
+ let mut seventeen = value;
+ let source = seventeen["evidence"]["sources"][0].clone();
+ seventeen["evidence"]["sources"] = Value::Array(vec![source; 17]);
+ assert_rejected(&seventeen, Profile::Production);
+}
+
+#[test]
+fn reconciliation_publication_presence_and_operations_fail_closed() {
+ let value = example();
+ let mut renewal = value.clone();
+ renewal["reconciliation"]["lease_renewal_ms"] = renewal["reconciliation"]["lease_ms"].clone();
+ assert_rejected(&renewal, Profile::Production);
+ let mut backoff = value.clone();
+ backoff["reconciliation"]["initial_backoff_ms"] = json!(30_001);
+ assert_rejected(&backoff, Profile::Production);
+ let mut attempt = value.clone();
+ attempt["reconciliation"]["attempt_deadline_ms"] = json!(9_999);
+ assert_rejected(&attempt, Profile::Production);
+ let mut publication_relay = value.clone();
+ publication_relay["publication"]["target_relay_ids"] = json!(["missing"]);
+ assert_rejected(&publication_relay, Profile::Production);
+ let mut disabled_leak = value.clone();
+ disabled_leak["publication"]["mode"] = json!("disabled");
+ assert_rejected(&disabled_leak, Profile::Production);
+ let mut publication_backoff = value.clone();
+ publication_backoff["publication"]["retry"]["initial_backoff_ms"] = json!(30_001);
+ assert_rejected(&publication_backoff, Profile::Production);
+ let mut presence_relay = value.clone();
+ presence_relay["presence"]["target_relay_ids"] = json!(["missing"]);
+ assert_rejected(&presence_relay, Profile::Production);
+ let mut no_desired_presence = value.clone();
+ no_desired_presence["presence"]["profile"] = json!(false);
+ no_desired_presence["presence"]["application_handler"] = json!(false);
+ assert_rejected(&no_desired_presence, Profile::Production);
+ let mut disabled_presence_leak = value.clone();
+ disabled_presence_leak["presence"]["enabled"] = json!(false);
+ assert_rejected(&disabled_presence_leak, Profile::Production);
+ let mut public_loopback_policy = value;
+ public_loopback_policy["operations"] = json!({
+ "enabled": true,
+ "listen": "0.0.0.0:9460",
+ "bind_policy": "loopback_only",
+ "limits": {}
+ });
+ assert_rejected(&public_loopback_policy, Profile::Production);
+}
+
+#[test]
+fn exact_resource_boundaries_and_safe_defaults_are_frozen() {
+ let value = example();
+ for (pointer, maximum, over) in [
+ ("/database/busy_timeout_ms", 60_000_u64, 60_001_u64),
+ ("/database/max_connections", 8, 9),
+ ("/resource_limits/admin/header_count", 64, 65),
+ ("/resource_limits/admin/query_items", 200, 201),
+ ("/resource_limits/events/wire_bytes", 524_288, 524_289),
+ ("/resource_limits/source_results/events", 4_096, 4_097),
+ (
+ "/resource_limits/source_results/bytes",
+ 8_388_608,
+ 8_388_609,
+ ),
+ ("/resource_limits/metrics/samples", 512, 513),
+ ("/resource_limits/runtime/worker_threads", 32, 33),
+ ] {
+ let mut minimum = value.clone();
+ *minimum.pointer_mut(pointer).expect("bounded field") = json!(1);
+ if pointer.ends_with("worker_threads") {
+ *minimum.pointer_mut(pointer).expect("bounded field") = json!(2);
+ }
+ assert!(
+ semantic_valid(&minimum, Profile::Production),
+ "minimum {pointer}"
+ );
+ let mut exact = value.clone();
+ *exact.pointer_mut(pointer).expect("bounded field") = json!(maximum);
+ assert!(
+ semantic_valid(&exact, Profile::Production),
+ "maximum {pointer}"
+ );
+ let mut zero = value.clone();
+ *zero.pointer_mut(pointer).expect("bounded field") = json!(0);
+ assert_rejected(&zero, Profile::Production);
+ let mut excessive = value.clone();
+ *excessive.pointer_mut(pointer).expect("bounded field") = json!(over);
+ assert_rejected(&excessive, Profile::Production);
+ }
+ let mut exact_admin_response = value.clone();
+ exact_admin_response["resource_limits"]["admin"]["response_body_utf8_bytes"] = json!(512);
+ assert!(semantic_valid(&exact_admin_response, Profile::Production));
+ let mut undersized = value.clone();
+ undersized["resource_limits"]["admin"]["response_body_utf8_bytes"] = json!(511);
+ assert_rejected(&undersized, Profile::Production);
+
+ let mut omitted = value;
+ omitted["service"] = json!({});
+ omitted["logging"] = json!({});
+ omitted["network"] = json!({});
+ omitted["reconciliation"] = json!({});
+ omitted["resource_limits"] = json!({});
+ omitted["database"]
+ .as_object_mut()
+ .expect("database")
+ .remove("busy_timeout_ms");
+ omitted["database"]
+ .as_object_mut()
+ .expect("database")
+ .remove("max_connections");
+ assert!(semantic_valid(&omitted, Profile::Production));
+}
+
+#[test]
+fn schema_version_authority_and_protected_material_are_closed() {
+ let value = example();
+ for replacement in [json!("radroots.rhi.config.v2"), json!("rhi")] {
+ let mut invalid = value.clone();
+ invalid["schema"] = replacement;
+ assert_rejected(&invalid, Profile::Production);
+ }
+ for replacement in [json!(0), json!(2), json!("1")] {
+ let mut invalid = value.clone();
+ invalid["schema_version"] = replacement;
+ assert_rejected(&invalid, Profile::Production);
+ }
+ for required in [
+ "minimum_free_bytes",
+ "identity",
+ "relays",
+ "evidence",
+ "sources",
+ "deadline_ms",
+ "lookback_seconds",
+ "overlap_seconds",
+ "reconciliation",
+ "attestation",
+ "publication",
+ "presence",
+ "resource_limits",
+ "retention",
+ ] {
+ assert!(
+ CONFIG_SCHEMA.contains(required),
+ "missing authority {required}"
+ );
+ }
+ let lowercase = CONFIG_EXAMPLE.to_ascii_lowercase();
+ for forbidden in [
+ "private_key",
+ "secret_key",
+ "mnemonic",
+ "nsec1",
+ "wrapping_key =",
+ "wrapping_key_path",
+ "rhi_",
+ "env_file",
+ "plaintext_file",
+ "keyring",
+ "managed_account",
+ "worker_root",
+ "state_file",
+ "json_state",
+ ] {
+ assert!(
+ !lowercase.contains(forbidden),
+ "forbidden material {forbidden}"
+ );
+ }
+}