commit 71f087a17a66a0198c219c1b56a9e26f63b95bd3
parent e748a00296afafc0ea93c3e760b66ac2068a227b
Author: triesap <tyson@radroots.org>
Date: Tue, 4 Aug 2026 01:02:29 +0000
release: qualify complete workspace
- remove obsolete net and nostr runtime predecessors after satisfied cutover gates
- repair stale conformance package and proof fixtures exposed by the full matrix
- refresh source-maintenance authority for deterministic signing updates
- verify format check clippy test docs contracts and generated outputs
Diffstat:
58 files changed, 119 insertions(+), 3095 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -1455,21 +1455,12 @@ dependencies = [
]
[[package]]
-name = "directories"
-version = "6.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
-dependencies = [
- "dirs-sys 0.5.0",
-]
-
-[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
- "dirs-sys 0.4.1",
+ "dirs-sys",
]
[[package]]
@@ -1480,23 +1471,11 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
- "redox_users 0.4.6",
+ "redox_users",
"windows-sys 0.48.0",
]
[[package]]
-name = "dirs-sys"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
-dependencies = [
- "libc",
- "option-ext",
- "redox_users 0.5.2",
- "windows-sys 0.61.2",
-]
-
-[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4695,32 +4674,6 @@ dependencies = [
]
[[package]]
-name = "radroots_net"
-version = "0.1.0-alpha"
-dependencies = [
- "directories",
- "futures",
- "hex",
- "nostr",
- "radroots_event",
- "radroots_event_codec",
- "radroots_identity",
- "radroots_log",
- "radroots_nostr",
- "radroots_nostr_accounts",
- "radroots_nostr_signer",
- "radroots_runtime_paths",
- "radroots_transport_nostr",
- "secrecy",
- "serde",
- "serde_json",
- "tempfile",
- "thiserror 1.0.69",
- "tokio",
- "tracing",
-]
-
-[[package]]
name = "radroots_nostr"
version = "0.1.0-alpha"
dependencies = [
@@ -4774,18 +4727,6 @@ dependencies = [
]
[[package]]
-name = "radroots_nostr_runtime"
-version = "0.1.0-alpha"
-dependencies = [
- "futures",
- "nostr",
- "radroots_nostr",
- "radroots_transport_nostr",
- "thiserror 1.0.69",
- "tokio",
-]
-
-[[package]]
name = "radroots_nostr_signer"
version = "0.1.0-alpha"
dependencies = [
@@ -4814,7 +4755,6 @@ dependencies = [
"nostr",
"nostrdb",
"radroots_nostr",
- "radroots_nostr_runtime",
"serde_json",
"tempfile",
"thiserror 1.0.69",
@@ -5471,17 +5411,6 @@ dependencies = [
]
[[package]]
-name = "redox_users"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
-dependencies = [
- "getrandom 0.2.17",
- "libredox",
- "thiserror 2.0.18",
-]
-
-[[package]]
name = "ref-cast"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5957,15 +5886,6 @@ dependencies = [
]
[[package]]
-name = "secrecy"
-version = "0.10.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
-dependencies = [
- "zeroize",
-]
-
-[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -11,7 +11,6 @@ members = [
"crates/identity",
"crates/runtime_store",
"crates/log",
- "crates/net",
"crates/nostr",
"crates/nostr_accounts",
"crates/nostr_connect",
@@ -24,7 +23,6 @@ members = [
"crates/geonames",
"crates/nostr_signer",
"crates/nostrdb",
- "crates/nostr_runtime",
"crates/outbox",
"crates/protocol_contract_v1",
"crates/transport_publish_protocol",
@@ -112,8 +110,6 @@ radroots_runtime_paths = { path = "crates/runtime_paths", version = "=0.1.0-alph
radroots_runtime_distribution = { path = "crates/runtime_distribution", version = "=0.1.0-alpha", default-features = false }
radroots_runtime_manager = { path = "crates/runtime_manager", version = "=0.1.0-alpha", default-features = false }
radroots_log = { path = "crates/log", version = "=0.1.0-alpha", default-features = false }
-radroots_net = { path = "crates/net", version = "=0.1.0-alpha", default-features = false }
-radroots_nostr_runtime = { path = "crates/nostr_runtime", version = "=0.1.0-alpha", default-features = false }
radroots_outbox = { path = "crates/outbox", version = "=0.1.0-alpha", default-features = false }
radroots_protocol_contract_v1 = { path = "crates/protocol_contract_v1", version = "=0.1.0-alpha", default-features = false }
radroots_transport_publish_protocol = { path = "crates/transport_publish_protocol", version = "=0.1.0-alpha", default-features = false }
diff --git a/contracts/conformance/vectors/comment/verified_profile.v1.json b/contracts/conformance/vectors/comment/verified_profile.v1.json
@@ -12986,7 +12986,7 @@
"event_json": "{\"id\":\"f8b01f88fffb9281dbc060e9f18cc8329072c5e36740ef189db07e1b0147967a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800000200,\"kind\":1111,\"tags\":[[\"E\",\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"wss://victoria.example\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"],[\"K\",\"30023\"],[\"P\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"wss://victoria.example\"],[\"e\",\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"wss://victoria.example\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"],[\"k\",\"30402\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"wss://victoria.example\"]],\"content\":\"Are these carrots available Saturday?\",\"sig\":\"da287048f846127b4c10ca8a8bed43f5900ca8eda24532eed3b78d4c35ce88e23109731cc66bb20495728e94509af0c0165c09b140ec98e825ba85082fb61a58\"}"
},
"expected": {
- "error": "comment_root_kind_unsupported"
+ "error": "comment_parent_kind_invalid"
}
},
{
diff --git a/contracts/coverage-profiles.toml b/contracts/coverage-profiles.toml
@@ -13,11 +13,6 @@ no_default_features = false
features = ["json"]
test_threads = 1
-[profiles.crates."radroots_nostr_runtime"]
-no_default_features = true
-features = ["std"]
-test_threads = 1
-
[profiles.crates."radroots_nostr"]
no_default_features = false
features = ["blossom"]
diff --git a/contracts/coverage.toml b/contracts/coverage.toml
@@ -15,11 +15,6 @@ require_branches = false
temporary = true
reason = "branch coverage is not applicable while the crate has no measured branch records"
-[overrides.radroots_net]
-require_branches = false
-temporary = true
-reason = "branch coverage is not applicable while the crate has no measured branch records"
-
[overrides.radroots_replica_schema]
require_branches = false
temporary = true
@@ -47,12 +42,10 @@ crates = [
"radroots_mesh",
"radroots_mesh_agent_client",
"radroots_mesh_agent_proto",
- "radroots_net",
"radroots_nostr",
"radroots_nostr_accounts",
"radroots_nostr_connect",
"radroots_nostrdb",
- "radroots_nostr_runtime",
"radroots_nostr_signer",
"radroots_outbox",
"radroots_protocol_contract_v1",
diff --git a/contracts/manifest.toml b/contracts/manifest.toml
@@ -58,8 +58,6 @@ deferred_publication = [
"radroots_mesh",
"radroots_mesh_agent_client",
"radroots_mesh_agent_proto",
- "radroots_net",
- "radroots_nostr_runtime",
"radroots_nostrdb",
"radroots_simplex_chat_proto",
"radroots_simplex_smp_proto",
diff --git a/contracts/releases/publish_policy.toml b/contracts/releases/publish_policy.toml
@@ -55,9 +55,7 @@ private = [
"radroots_event_store",
"radroots_geocoder",
"radroots_log",
- "radroots_net",
"radroots_nostr_accounts",
- "radroots_nostr_runtime",
"radroots_nostr_signer",
"radroots_outbox",
"radroots_protected_store",
diff --git a/crates/event/tests/source_boundary.rs b/crates/event/tests/source_boundary.rs
@@ -191,10 +191,8 @@ fn forbidden_event_name_allowed(relative_path: &str, pattern: &str) -> bool {
fn is_nostr_protocol_context(relative_path: &str) -> bool {
relative_path.starts_with("crates/nostr/")
|| relative_path.starts_with("crates/nostrdb/")
- || relative_path.starts_with("crates/nostr_runtime/")
|| relative_path.starts_with("crates/nostr_signer/")
|| relative_path.starts_with("crates/transport_nostr/")
- || relative_path.starts_with("crates/net/src/nostr_client/")
}
fn contains_forbidden_event_name(source: &str, pattern: &str) -> bool {
diff --git a/crates/event_codec/src/admission.rs b/crates/event_codec/src/admission.rs
@@ -498,7 +498,10 @@ mod tests {
.expect("canonical event id");
let nostr_id = nostr::EventId::from_hex(&id.to_hex()).expect("Nostr event id");
let message = Message::from_digest(nostr_id.to_bytes());
- let signature = SECP256K1.sign_schnorr_no_aux_rand(&message, keys.key_pair(SECP256K1));
+ let signature = SECP256K1.sign_schnorr_no_aux_rand(
+ &message,
+ &nostr::secp256k1::Keypair::from_secret_key(SECP256K1, keys.secret_key()),
+ );
EventEnvelope::new(EventEnvelopeParts {
id: id.into_string(),
diff --git a/crates/event_codec/src/deletion/reconciliation_v1/admission/tests.rs b/crates/event_codec/src/deletion/reconciliation_v1/admission/tests.rs
@@ -26,7 +26,10 @@ fn admitted_contract_resolves_registry_entry() {
.expect("canonical deletion request id");
let nostr_id = nostr::EventId::from_hex(&id.to_hex()).expect("Nostr event id");
let message = Message::from_digest(nostr_id.to_bytes());
- let signature = SECP256K1.sign_schnorr_no_aux_rand(&message, keys.key_pair(SECP256K1));
+ let signature = SECP256K1.sign_schnorr_no_aux_rand(
+ &message,
+ &nostr::secp256k1::Keypair::from_secret_key(SECP256K1, keys.secret_key()),
+ );
let event = EventEnvelope::new(EventEnvelopeParts {
id: id.into_string(),
author,
diff --git a/crates/event_codec/src/deletion/reconciliation_v1/evaluator/tests.rs b/crates/event_codec/src/deletion/reconciliation_v1/evaluator/tests.rs
@@ -543,7 +543,10 @@ fn signed_event(
.expect("canonical event id");
let nostr_id = nostr::EventId::from_hex(&id.to_hex()).expect("Nostr event id");
let message = Message::from_digest(nostr_id.to_bytes());
- let signature = SECP256K1.sign_schnorr_no_aux_rand(&message, keys.key_pair(SECP256K1));
+ let signature = SECP256K1.sign_schnorr_no_aux_rand(
+ &message,
+ &nostr::secp256k1::Keypair::from_secret_key(SECP256K1, keys.secret_key()),
+ );
EventEnvelope::new(EventEnvelopeParts {
id: id.into_string(),
diff --git a/crates/event_codec/tests/fixtures/comment_verified_profile.v1.json b/crates/event_codec/tests/fixtures/comment_verified_profile.v1.json
@@ -12986,7 +12986,7 @@
"event_json": "{\"id\":\"f8b01f88fffb9281dbc060e9f18cc8329072c5e36740ef189db07e1b0147967a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800000200,\"kind\":1111,\"tags\":[[\"E\",\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"wss://victoria.example\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"],[\"K\",\"30023\"],[\"P\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"wss://victoria.example\"],[\"e\",\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"wss://victoria.example\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"],[\"k\",\"30402\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"wss://victoria.example\"]],\"content\":\"Are these carrots available Saturday?\",\"sig\":\"da287048f846127b4c10ca8a8bed43f5900ca8eda24532eed3b78d4c35ce88e23109731cc66bb20495728e94509af0c0165c09b140ec98e825ba85082fb61a58\"}"
},
"expected": {
- "error": "comment_root_kind_unsupported"
+ "error": "comment_parent_kind_invalid"
}
},
{
diff --git a/crates/event_codec/tests/food_availability_conformance.rs b/crates/event_codec/tests/food_availability_conformance.rs
@@ -676,7 +676,10 @@ fn verified_event(value: &Value, vector_id: &str) -> RadrootsSignatureVerifiedEv
let keys = keys_for_pubkey(&raw.pubkey, vector_id);
let message = Message::from_digest(event.id.to_bytes());
- let deterministic = SECP256K1.sign_schnorr_no_aux_rand(&message, keys.key_pair(SECP256K1));
+ let deterministic = SECP256K1.sign_schnorr_no_aux_rand(
+ &message,
+ &nostr::secp256k1::Keypair::from_secret_key(SECP256K1, keys.secret_key()),
+ );
assert_eq!(
event.sig, deterministic,
"{vector_id} deterministic signature drift"
@@ -717,7 +720,10 @@ fn directly_signed_verified_event(
.unwrap_or_else(|error| panic!("{vector_id} direct event id conversion failed: {error}"));
let keys = keys_for_pubkey(&raw.pubkey, vector_id);
let message = Message::from_digest(nostr_id.to_bytes());
- let signature = SECP256K1.sign_schnorr_no_aux_rand(&message, keys.key_pair(SECP256K1));
+ let signature = SECP256K1.sign_schnorr_no_aux_rand(
+ &message,
+ &nostr::secp256k1::Keypair::from_secret_key(SECP256K1, keys.secret_key()),
+ );
raw.id = id.into_string();
raw.sig = signature.to_string();
diff --git a/crates/event_codec/tests/package_boundary.rs b/crates/event_codec/tests/package_boundary.rs
@@ -33,10 +33,9 @@ fn manifest_has_final_identity_and_required_radroots_dependencies() {
}
#[test]
-fn standalone_fuzz_package_uses_the_repository_version_contract() {
- let package =
- "name = \"radroots_event_codec_fuzz\"\npublish = false\nversion = \"0.1.0-alpha\"";
- let locked_package = "name = \"radroots_event_codec_fuzz\"\nversion = \"0.1.0-alpha\"";
+fn consolidated_parser_fuzz_package_uses_the_repository_version_contract() {
+ let package = "name = \"radroots_parser_fuzz\"\npublish = false\nversion = \"0.1.0-alpha\"";
+ let locked_package = "name = \"radroots_parser_fuzz\"\nversion = \"0.1.0-alpha\"";
assert!(FUZZ_MANIFEST.contains(package));
assert!(FUZZ_LOCK.contains(locked_package));
diff --git a/crates/event_store/contracts/source_maintenance_v1.manifest.json b/crates/event_store/contracts/source_maintenance_v1.manifest.json
@@ -152,8 +152,8 @@
{
"role": "workspace_dependency_authority",
"path": "Cargo.toml",
- "byte_length": 12664,
- "sha256": "0070b6f53f8f2b8033869bf8418c92543bfc8a7e9ac27dc8db960d89f815e625",
+ "byte_length": 12420,
+ "sha256": "a85d7cf805301bfb35f3e7643b3b48537f93d66bf02b9aea1d991934c856a1b1",
"hash_algorithm": "sha256_bytes_v1"
},
{
@@ -509,8 +509,8 @@
{
"role": "event_codec_admission_facade",
"path": "crates/event_codec/src/admission.rs",
- "byte_length": 21251,
- "sha256": "25928e734e7b3688f92af806a12a13458c48fb47e94249f60f0226300d5da2b4",
+ "byte_length": 21347,
+ "sha256": "190478478f4c90a74b2a60d1634fb2f352a7af0cf7b9510ed42fefe95987dc18",
"hash_algorithm": "sha256_bytes_v1"
},
{
@@ -754,8 +754,8 @@
{
"role": "source_generation_rebuild_authority",
"path": "crates/event_store/src/nip09/reconciliation_v1.rs",
- "byte_length": 184833,
- "sha256": "3ca802cb7a8444c8512b2be1390a019281704316a430fb9b75a8ad2cad358a07",
+ "byte_length": 184917,
+ "sha256": "1c02fbf00881839c29de36c286e649061582eef09829fc1ccb526a62478101fd",
"hash_algorithm": "sha256_bytes_v1"
},
{
@@ -867,7 +867,7 @@
"role": "source_maintenance_governance",
"path": "tools/xtask/src/contract/source_maintenance.rs",
"byte_length": 199270,
- "sha256": "a75fdd37990073a3f16e0ac771980a638641a2dc8ee317580aa100b41efc99de",
+ "sha256": "4b83f736e7ff8ac01fd7b2b66757af9275cfe997303ede3c1f026872f864bbd0",
"hash_algorithm": "sha256_bytes_v1"
},
{
diff --git a/crates/event_store/contracts/source_maintenance_v1.manifest.sha256 b/crates/event_store/contracts/source_maintenance_v1.manifest.sha256
@@ -1 +1 @@
-ef03a4f87259e2d2d11b9b581d24f3109fbaac0789f02c7ded92bf6adbc5ec60
+64d17dedb83e853208a136fe3c9265eeca92c3698e17d02f20f47bf1f58b2ae9
diff --git a/crates/event_store/src/generated/source_maintenance_manifest.rs b/crates/event_store/src/generated/source_maintenance_manifest.rs
@@ -1,8 +1,8 @@
// @generated by `cargo xtask contract source-maintenance-manifest --write`; do not edit.
-pub(crate) const SOURCE_MAINTENANCE_MANIFEST_JSON: &str = "{\n \"schema_version\": 1,\n \"contract_id\": \"radroots_event_store.source_maintenance_v1\",\n \"hook_id\": \"source_maintenance_v1\",\n \"manifest_schema\": {\n \"path\": \"crates/event_store/contracts/source_maintenance_v1.manifest.schema.json\",\n \"byte_length\": 12315,\n \"sha256\": \"96ea98a5abf3348b61565954cd34f2c622411d8dd34205ef04134bbe0e3d3f2b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"predecessor\": {\n \"hook_id\": \"food_availability_projection_v1\",\n \"manifest\": {\n \"path\": \"crates/event_store/contracts/food_availability_projection_v1.manifest.json\",\n \"byte_length\": 17455,\n \"sha256\": \"02dfe1b450fbdac16e718888215b4dd5c85d8975440fa21e8f439fb24c2b2990\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n },\n \"migration\": {\n \"version\": 4,\n \"name\": \"source_maintenance\",\n \"up\": {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.up.sql\",\n \"byte_length\": 19841,\n \"sha256\": \"ab2724188f8d08c897eebea2533a635e7c74282a25e84e4c0c37e78b08837a43\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"down\": {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.down.sql\",\n \"byte_length\": 5172,\n \"sha256\": \"fe44fd53c51545c08ea479b385e6781079dab70fc63da2a3c205d727a00ce860\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"schema_sha256\": \"074f85b663444ac150239ecd8441ea4a96ad83a798a55e22d2e5e2f7ee943a8c\",\n \"catalog\": {\n \"objects\": [\n \"radroots_event_store_source_capacity_delete_guard\",\n \"radroots_event_store_source_capacity_insert_guard\",\n \"radroots_event_store_source_capacity_marker_close_guard\",\n \"radroots_event_store_source_capacity_update_guard\",\n \"radroots_event_store_source_capacity_v1\",\n \"radroots_event_store_source_generation_capacity_advance\",\n \"radroots_event_store_source_generation_capacity_guard\"\n ],\n \"replaced_objects\": [\n \"radroots_event_store_food_availability_image_delete_guard\",\n \"radroots_event_store_food_availability_projection_delete_guard\",\n \"radroots_event_store_source_rebuild_marker_insert_guard\"\n ],\n \"tables\": [\n \"radroots_event_store_source_capacity_v1\"\n ],\n \"fts5_tables\": []\n }\n },\n \"source_maintenance\": {\n \"version\": 1,\n \"event_contract_registry_version\": 7,\n \"capacity_authority_id\": \"radroots_event_store_source_capacity_v1\",\n \"accounting\": {\n \"algorithm\": \"sqlite_cast_blob_octet_sum_v1\",\n \"raw_event_columns\": [\n \"event_id\",\n \"pubkey\",\n \"tags_json\",\n \"content\",\n \"sig\",\n \"raw_json\"\n ],\n \"raw_tag_columns\": [\n \"event_id\",\n \"tag_name\",\n \"tag_value\",\n \"tag_json\"\n ],\n \"nullable_raw_tag_columns\": [\n \"tag_value\"\n ]\n },\n \"limits\": {\n \"raw_events\": 25000,\n \"raw_tags\": 250000,\n \"raw_event_text_bytes\": 67108864,\n \"raw_tag_text_bytes\": 33554432,\n \"retained_source_generations\": 8\n },\n \"reopen_validation\": {\n \"mode\": \"bounded_full_raw_recount_v1\",\n \"raw_event_rejection_scan_bound\": 25001,\n \"raw_tag_rejection_scan_bound\": 250001,\n \"generation_history_validation\": \"bounded_count_plus_active_ordinal_v1\",\n \"retained_generation_rejection_scan_bound\": 9\n },\n \"rebuild_seal\": {\n \"nip09_hook_id\": \"nip09_reconciliation_v1\",\n \"nip09_manifest_sha256\": \"74af832420ffbaa9805e89df3c0b34f126a443e1598f757e3372f407f9003b77\",\n \"food_hook_id\": \"food_availability_projection_v1\",\n \"food_manifest_sha256\": \"02dfe1b450fbdac16e718888215b4dd5c85d8975440fa21e8f439fb24c2b2990\",\n \"food_scope_fingerprint_sha256\": \"8b63c5ddc48a2cc7db69295238b96d5f814dba50427c80b4d0079f061e6d3de0\",\n \"active_generation_authority\": \"radroots_event_store_source_state\",\n \"marker_close_authority\": \"radroots_event_store_source_capacity_marker_close_guard\"\n }\n },\n \"entry_points\": [\n {\n \"role\": \"migration_registry\",\n \"rust_path\": \"radroots_event_store::migrations::EVENT_STORE_MIGRATIONS[3]\"\n },\n {\n \"role\": \"migration_apply_hook\",\n \"rust_path\": \"radroots_event_store::schema::apply_migration_hook\"\n },\n {\n \"role\": \"migration_validation_hook\",\n \"rust_path\": \"radroots_event_store::schema::validate_migration_hook_state\"\n },\n {\n \"role\": \"capacity_query\",\n \"rust_path\": \"radroots_event_store::RadrootsEventStore::source_capacity_v1\"\n },\n {\n \"role\": \"raw_append_preflight\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::preflight_unique_raw_source_append_v1\"\n },\n {\n \"role\": \"raw_append_advance\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::advance_source_capacity_after_insert_v1\"\n },\n {\n \"role\": \"generation_append_preflight\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::preflight_source_generation_append_v1\"\n },\n {\n \"role\": \"generation_rebuild_bind\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::bind_source_capacity_to_generation_v1\"\n },\n {\n \"role\": \"sqlite_encoding_preflight\",\n \"rust_path\": \"radroots_event_store::store::validate_main_database_encoding\"\n },\n {\n \"role\": \"source_generation_history_rollback_guard\",\n \"rust_path\": \"radroots_event_store::schema::validate_rollback_preserves_source_generation_history\"\n },\n {\n \"role\": \"result_vector_executor\",\n \"rust_path\": \"source_maintenance_v1_result_vector\"\n }\n ],\n \"source_files\": [\n {\n \"role\": \"workspace_dependency_authority\",\n \"path\": \"Cargo.toml\",\n \"byte_length\": 12664,\n \"sha256\": \"0070b6f53f8f2b8033869bf8418c92543bfc8a7e9ac27dc8db960d89f815e625\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_currency_value_authority\",\n \"path\": \"crates/core/src/currency.rs\",\n \"byte_length\": 4142,\n \"sha256\": \"42e6da2d8d2fdd6955dc2d83e98dd00266e02bc910b69923b117662c05089d14\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_decimal_value_authority\",\n \"path\": \"crates/core/src/decimal.rs\",\n \"byte_length\": 7839,\n \"sha256\": \"4b1d681a92d7a9e074bee7e1f20eb1499bb05bfc6528a6c9ba69fa7d00416550\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_money_value_authority\",\n \"path\": \"crates/core/src/money.rs\",\n \"byte_length\": 8025,\n \"sha256\": \"3f30bc21e21951a62fdc5d4033736ed42a883df590e8403940775bd2edfeebc5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_percent_value_authority\",\n \"path\": \"crates/core/src/percent.rs\",\n \"byte_length\": 2652,\n \"sha256\": \"42ceab109881329f3539d06a0ee0381ab4359985fe185a18d58a9c9558c5eb7f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_quantity_value_authority\",\n \"path\": \"crates/core/src/quantity.rs\",\n \"byte_length\": 6785,\n \"sha256\": \"d14e619da3829cdaf3ca7385fb892d330c286c1e21455c135c37518cbb588b5f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_quantity_price_value_authority\",\n \"path\": \"crates/core/src/quantity_price.rs\",\n \"byte_length\": 7061,\n \"sha256\": \"d60f70377099470ed283f45868a5119d7bb46143cb250b5082a4c038ba7f6cdc\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_unit_value_authority\",\n \"path\": \"crates/core/src/unit.rs\",\n \"byte_length\": 10023,\n \"sha256\": \"550e9582b6a5290aac3f637b83853b21fefcbab92c432d067c83448f4b2fe3e9\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_public_surface\",\n \"path\": \"crates/blossom/src/lib.rs\",\n \"byte_length\": 510,\n \"sha256\": \"a4dfcbd193457c50c1b0fbfc91bb547981ecd3a6e23cfc6f1030edfb511dc58c\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_authorization_authority\",\n \"path\": \"crates/blossom/src/authorization.rs\",\n \"byte_length\": 39360,\n \"sha256\": \"461947b30516315a342b3b5697599f701d27050888f29f67f5d7a7ad4afa4c28\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_descriptor_authority\",\n \"path\": \"crates/blossom/src/descriptor.rs\",\n \"byte_length\": 13702,\n \"sha256\": \"b2a4ffa760256e1316f70e012d200e2b5f4afd8ede771b85a5147f96b247f599\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_error_authority\",\n \"path\": \"crates/blossom/src/error.rs\",\n \"byte_length\": 17229,\n \"sha256\": \"02af55beacf437040be17d1fc3271d6e2a62915f656ec2dbe06247deef83f95a\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_hash_authority\",\n \"path\": \"crates/blossom/src/hash.rs\",\n \"byte_length\": 11006,\n \"sha256\": \"45eca0d81ac0f46c305a32f81aafcdd90b14679d0614f72862289a6e5d4cb08f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_media_type_authority\",\n \"path\": \"crates/blossom/src/media_type.rs\",\n \"byte_length\": 2368,\n \"sha256\": \"69db5c0f9fcdd7dbe1f22daa499f65603ae4ce198083dab2c73a36813fa18a46\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_url_authority\",\n \"path\": \"crates/blossom/src/url.rs\",\n \"byte_length\": 14580,\n \"sha256\": \"342c995fcf620e5fb32461a7d8276f5668ba2662890bd32d12780cc9121c6451\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_public_surface\",\n \"path\": \"crates/event/src/lib.rs\",\n \"byte_length\": 1892,\n \"sha256\": \"5d609b963a9b8db18ef96a10f394617413734d2631774d3296e6d8fc029cb02c\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_contract_facade\",\n \"path\": \"crates/event/src/contract.rs\",\n \"byte_length\": 208,\n \"sha256\": \"27466bf36461071931f391c60646a13f7781d19584583166cc54c0eece76d010\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_contract_registry_v7_authority\",\n \"path\": \"crates/event/src/contract/registry_v7.rs\",\n \"byte_length\": 145805,\n \"sha256\": \"0a62603f6fc05dc9f758561cf7da258c676ada810ceaeca7fb151364a5c83d62\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_envelope_authority\",\n \"path\": \"crates/event/src/envelope.rs\",\n \"byte_length\": 29310,\n \"sha256\": \"29f8d8c4b17c01cb5a8c7f59e4ce52e134180e0e6f37d265063d278b8f2ff26e\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_verification_typestate_authority\",\n \"path\": \"crates/event/src/verification.rs\",\n \"byte_length\": 9400,\n \"sha256\": \"417acb2ce670d266b1fa4fcafd6b48a910218deab38f57fb8b942056db76f94d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_admission_typestate_authority\",\n \"path\": \"crates/event/src/admission.rs\",\n \"byte_length\": 7052,\n \"sha256\": \"62bd2ceb0f434946fdfc2b81f4efb13738676a29a6936c23a427e43aadf2d9cb\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_head_facade\",\n \"path\": \"crates/event/src/event_head.rs\",\n \"byte_length\": 97,\n \"sha256\": \"f761ff3e74c4f5e1e28381db00ce698c633ff048669b22984d14a587482e8e83\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_head_v1_authority\",\n \"path\": \"crates/event/src/event_head/v1.rs\",\n \"byte_length\": 6849,\n \"sha256\": \"9f4144d8d240023cf493ce0b538ade4c16dc44204d6b5f2797152caf6f9e7dba\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_ids_authority\",\n \"path\": \"crates/event/src/id.rs\",\n \"byte_length\": 48381,\n \"sha256\": \"a08fe3873815453a03318a706e2fa98a8a6728ec69d24368a87ec60025566fe0\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_trade_authority\",\n \"path\": \"crates/event/src/trade.rs\",\n \"byte_length\": 65549,\n \"sha256\": \"f818a7287ed82de3fb9ef1d973f0fb18d84b30eb4827ddc576ee502dd980145f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_kinds_authority\",\n \"path\": \"crates/event/src/kinds.rs\",\n \"byte_length\": 34961,\n \"sha256\": \"8b3ce6193cab1f7e1587d0c1b2880a81b0aa43c77d89671b602772db37edcebf\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_tags_authority\",\n \"path\": \"crates/event/src/tags.rs\",\n \"byte_length\": 4530,\n \"sha256\": \"cb8f12a639fc72d9238e32495d9e67e928e6627fb8c94ec0a58c5d95e7373cd6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_draft_authority\",\n \"path\": \"crates/event/src/draft.rs\",\n \"byte_length\": 51150,\n \"sha256\": \"ecc619aae28d688d13f35467398f17766b60f26c2bf3616d772ce65b7fa4fb53\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_calendar_authority\",\n \"path\": \"crates/event/src/calendar.rs\",\n \"byte_length\": 97992,\n \"sha256\": \"73e79d61ee175cb46e6123e1d28239252ddf0ad094dc1113bdb1286b63b88d57\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_classified_listing_authority\",\n \"path\": \"crates/event/src/classified_listing.rs\",\n \"byte_length\": 7956,\n \"sha256\": \"ac58484b87c7712c81f50f884d1c0dd60938571aa5f6f05d555191f529a4c2b0\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_profile_authority\",\n \"path\": \"crates/event/src/profile.rs\",\n \"byte_length\": 17011,\n \"sha256\": \"572bfe3b6f1711d109db445077f0a8453cad3f021cc0638f79201b618e16b978\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_post_authority\",\n \"path\": \"crates/event/src/post.rs\",\n \"byte_length\": 19968,\n \"sha256\": \"a822cd51eff9cbaa39db998cc5c90d117f7a983f0fe2d42d42118cb00e95982c\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_comment_authority\",\n \"path\": \"crates/event/src/comment.rs\",\n \"byte_length\": 34896,\n \"sha256\": \"a35b17418884c4c620e19b96d36a418db9d0065a5582f6b869a6bc6111f65bf6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_food_availability_authority\",\n \"path\": \"crates/event/src/food_availability.rs\",\n \"byte_length\": 42829,\n \"sha256\": \"52472c07eb0855d884c60755dc3a3e1a963793451d707e51f769f2d78384f318\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_deletion_authority\",\n \"path\": \"crates/event/src/deletion.rs\",\n \"byte_length\": 30022,\n \"sha256\": \"8c3f2e32407520a8e8242c1293af32613c648fb76218b8003a18929ac0b3811f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_dto_authority\",\n \"path\": \"crates/event/src/dto.rs\",\n \"byte_length\": 5038,\n \"sha256\": \"9e228c95ff6ff33f99441d8682781fd0a1c4d3f1db24284a43985dcbac3ba136\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_farm_crdt_authority\",\n \"path\": \"crates/event/src/farm_crdt.rs\",\n \"byte_length\": 21118,\n \"sha256\": \"cec265ac9c42b59a14be4eb96924d26a83477c27291f667f23e6e47a64b6b2c7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_knowledge_authority\",\n \"path\": \"crates/event/src/knowledge.rs\",\n \"byte_length\": 54624,\n \"sha256\": \"3d55ff88a4e30d5d605f9ae4b2e96b9df6978d6c14e52f9aeb0b8039cfc200d7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_operational_listing_authority\",\n \"path\": \"crates/event/src/operational_listing.rs\",\n \"byte_length\": 10307,\n \"sha256\": \"52e710db816c89c8d4b87c86541987f7447f2f7091c394c68d9cb98a27f90159\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_order_authority\",\n \"path\": \"crates/event/src/order.rs\",\n \"byte_length\": 60218,\n \"sha256\": \"4203fcc469612cac5c705884564e140b9fb076cd57079e22ed85275a8b936e50\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_reply_authority\",\n \"path\": \"crates/event/src/reply.rs\",\n \"byte_length\": 15581,\n \"sha256\": \"b0b83a1e96125c0892d932e9314519242a715fa8f46334aff765ffd248c91d9a\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_trade_validation_authority\",\n \"path\": \"crates/event/src/trade_validation.rs\",\n \"byte_length\": 4669,\n \"sha256\": \"1486f79bd1d0eadea6cc1b7646ce1f7f57ccade18334a3ea37286426676b9b98\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_relay_hint_authority\",\n \"path\": \"crates/event/src/relay_hint.rs\",\n \"byte_length\": 13598,\n \"sha256\": \"fb8b026eeccf51d78f20771c44223e7b48825aa0e3c760d6b3cb90148021a674\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_media_authority\",\n \"path\": \"crates/event/src/media.rs\",\n \"byte_length\": 3455,\n \"sha256\": \"10678af3a202d5367dbcf3bcdc0bf9fdf8d8fb89b045d7d3fb96b3cc195ab6f9\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_social_authority\",\n \"path\": \"crates/event/src/social.rs\",\n \"byte_length\": 6076,\n \"sha256\": \"a2dab19caad46eab14fac86e7c878a801a450d8ac374b0e728b71a5a224eef19\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_wire_facade\",\n \"path\": \"crates/event/src/wire.rs\",\n \"byte_length\": 68,\n \"sha256\": \"cb52f6006f7ecd862707d6b048f9fc407e0cd5ebcd3091b3c54e195ffa5cba64\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_wire_v1_authority\",\n \"path\": \"crates/event/src/wire/v1.rs\",\n \"byte_length\": 22198,\n \"sha256\": \"c818bc1f67e215948ba253c8c62b09ab788db9aeec9b6df8096014b55565cb8c\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_public_surface\",\n \"path\": \"crates/event_codec/src/lib.rs\",\n \"byte_length\": 2448,\n \"sha256\": \"7d4b0040ad3971f34395b17cb1aa40617e9c2c9e1b4222676c388c3adaa47e07\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_verification_facade\",\n \"path\": \"crates/event_codec/src/verification.rs\",\n \"byte_length\": 253,\n \"sha256\": \"b49a32df605035c87f295c0a151140d43d2e588c5e11b05183e5fc92993dae0e\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_verification_v1_authority\",\n \"path\": \"crates/event_codec/src/verification/v1.rs\",\n \"byte_length\": 6584,\n \"sha256\": \"72f64615bd6b6dc3b051579e3069b2213f3781dcc28e12f0b533c7d81b81b9b1\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_registry_v7_admission_authority\",\n \"path\": \"crates/event_codec/src/admission/registry_v7.rs\",\n \"byte_length\": 5228,\n \"sha256\": \"62da30cb6ef7d0ed2d43715a73bd529283e8ae8c12571bc5c68722cb400f17b8\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_admission_facade\",\n \"path\": \"crates/event_codec/src/admission.rs\",\n \"byte_length\": 21251,\n \"sha256\": \"25928e734e7b3688f92af806a12a13458c48fb47e94249f60f0226300d5da2b4\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_profile_inbound_facade\",\n \"path\": \"crates/event_codec/src/profile/inbound.rs\",\n \"byte_length\": 86,\n \"sha256\": \"9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_profile_registry_v7_authority\",\n \"path\": \"crates/event_codec/src/profile/inbound/registry_v7.rs\",\n \"byte_length\": 10347,\n \"sha256\": \"32111c0e0592229c11a93a3a8054e7b124b039451aa696a9abcb56df2a6608d3\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_post_inbound_facade\",\n \"path\": \"crates/event_codec/src/post/inbound.rs\",\n \"byte_length\": 86,\n \"sha256\": \"9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_post_registry_v7_authority\",\n \"path\": \"crates/event_codec/src/post/inbound/registry_v7.rs\",\n \"byte_length\": 15985,\n \"sha256\": \"e17083b0596e3a55994c399bc6272c0ebf04cabaa513260b3d45dd546f7f50ef\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_reply_inbound_facade\",\n \"path\": \"crates/event_codec/src/reply/inbound.rs\",\n \"byte_length\": 86,\n \"sha256\": \"9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_reply_registry_v7_authority\",\n \"path\": \"crates/event_codec/src/reply/inbound/registry_v7.rs\",\n \"byte_length\": 26662,\n \"sha256\": \"d1d11116ca27801b7e2f17da60900e6a9ca0b6f27ea9ee0bf19e97dbf24c0c32\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_comment_inbound_facade\",\n \"path\": \"crates/event_codec/src/comment/inbound.rs\",\n \"byte_length\": 86,\n \"sha256\": \"9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_comment_registry_v7_authority\",\n \"path\": \"crates/event_codec/src/comment/inbound/registry_v7.rs\",\n \"byte_length\": 53311,\n \"sha256\": \"c63fe853536b17bded6073f32b53434b60fc5123f911d01c642a6b3982f09c70\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_deletion_facade\",\n \"path\": \"crates/event_codec/src/deletion/mod.rs\",\n \"byte_length\": 115,\n \"sha256\": \"17d56f82ddb0a86bc97abbcf3e037fe460ea31fff2548f527be4bf004dac3a95\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_deletion_reconciliation_v1_authority\",\n \"path\": \"crates/event_codec/src/deletion/reconciliation_v1.rs\",\n \"byte_length\": 38028,\n \"sha256\": \"c6901f559e83700610595720c47a4d062078ae8411e1fc2fb952dd215b19dd90\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_error_authority\",\n \"path\": \"crates/event_codec/src/error.rs\",\n \"byte_length\": 3666,\n \"sha256\": \"174ce982bca37fac28d016c3ab3236441c9b31b2169ec94db6c9288b36c72849\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_food_admission_authority\",\n \"path\": \"crates/event_codec/src/food_availability/admission.rs\",\n \"byte_length\": 5681,\n \"sha256\": \"99809859090f5295572688d06bbb30dbe50524ba2c996de7ec5b26f6dd390643\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_food_authored_authority\",\n \"path\": \"crates/event_codec/src/food_availability/authored.rs\",\n \"byte_length\": 9724,\n \"sha256\": \"8673ec62ed3fc47b691efd9ca828643719803971e72b756c5b4603d409d0fba5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_food_inbound_facade\",\n \"path\": \"crates/event_codec/src/food_availability/inbound.rs\",\n \"byte_length\": 86,\n \"sha256\": \"9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_food_registry_v7_authority\",\n \"path\": \"crates/event_codec/src/food_availability/inbound/registry_v7.rs\",\n \"byte_length\": 26037,\n \"sha256\": \"92a7e75da3293967cec17a514479144c3c948a64542577dd58c7f07992968e4f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_job_traits_authority\",\n \"path\": \"crates/event_codec/src/job/traits.rs\",\n \"byte_length\": 4946,\n \"sha256\": \"1b3558a3196744005978dceddd33d3a72ad1ab8f50fb1cbfd997dcfe98bb9e39\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_job_encode_authority\",\n \"path\": \"crates/event_codec/src/job/encode.rs\",\n \"byte_length\": 1670,\n \"sha256\": \"5d6cae6309fcdd02deb7c751b5c451d257cf1900986391cb39cbb2bfd1ddb577\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_knowledge_verification_authority\",\n \"path\": \"crates/event_codec/src/knowledge/verification.rs\",\n \"byte_length\": 6986,\n \"sha256\": \"6f8a10ed262ce37f05acd9c89a1e37ca2fa99ce7da18407891e8abe228ee2349\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_operational_listing_tags_authority\",\n \"path\": \"crates/event_codec/src/operational_listing/tags.rs\",\n \"byte_length\": 50222,\n \"sha256\": \"55b6860bc6f4699dd47a49f8336764e9db78aafb7c32849102dedeb96804b373\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_order_decode_authority\",\n \"path\": \"crates/event_codec/src/order/decode.rs\",\n \"byte_length\": 42667,\n \"sha256\": \"033d9465bc041d205eab3f820572b719a35651340dcb50bdce7c60fabee5f6e1\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_profile_facade\",\n \"path\": \"crates/event_codec/src/profile/mod.rs\",\n \"byte_length\": 1456,\n \"sha256\": \"06b890da54580e2ec68f7e729281b7c3b650ac0a95a4fcb0db3c6cdaa591a7ef\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_tag_builders_authority\",\n \"path\": \"crates/event_codec/src/tag_builders.rs\",\n \"byte_length\": 9232,\n \"sha256\": \"bd81ccf04fd72358331b48eddcde087a95e115c5bd90c05fd9332644979ad5e6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_trade_facade\",\n \"path\": \"crates/event_codec/src/trade/mod.rs\",\n \"byte_length\": 21472,\n \"sha256\": \"10cee4b6cd6429ac9eb00327a521bcd0982fae4de98bda77e4728fda002965d3\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_dependency_authority\",\n \"path\": \"crates/event_store/Cargo.toml\",\n \"byte_length\": 1797,\n \"sha256\": \"e79f4b69500553835e2cde28e7d9139788bebe6e2cd4e0b4ae72cc150491daf4\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_error_and_limits\",\n \"path\": \"crates/event_store/src/error.rs\",\n \"byte_length\": 19458,\n \"sha256\": \"3d87df984af6ae5decf7ca8c3a8d5422be6c647e6a05490225b6b2037bf3701b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"generated_descriptor_registration\",\n \"path\": \"crates/event_store/src/generated.rs\",\n \"byte_length\": 144,\n \"sha256\": \"6b0a8d6f249bd4fc3f878d37cb5e418680f0f1be2d9eec2518dedf03efc47121\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_surface\",\n \"path\": \"crates/event_store/src/lib.rs\",\n \"byte_length\": 3860,\n \"sha256\": \"de1c5cc1ab36e1166d23e2b76aeae1d5f0f401cf07ce243590741c0126d02132\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"migration_registry\",\n \"path\": \"crates/event_store/src/migrations.rs\",\n \"byte_length\": 73585,\n \"sha256\": \"a47477d04759ec6e71d14d3d05459864eae78fce0e3c2481a1b03147f04637f7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_model_public_surface\",\n \"path\": \"crates/event_store/src/model.rs\",\n \"byte_length\": 32070,\n \"sha256\": \"2cdbf6d1a4e0fa6680f45f02758513509339a7cf3a2387e824fb73b0edf749a4\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_reconciliation_v1_model\",\n \"path\": \"crates/event_store/src/model/reconciliation_v1.rs\",\n \"byte_length\": 10446,\n \"sha256\": \"475b7b840bffdae7e3f7a31f5b940b9dfa579eaa1cac640729aaaeadabf83ba6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_reconciliation_v1_ingest_model\",\n \"path\": \"crates/event_store/src/model/ingest_reconciliation_v1.rs\",\n \"byte_length\": 1588,\n \"sha256\": \"6117e797674c35bb1ccebffbc6a8c0108bfc38c7c9f82607066dfd802b8f0734\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"addressable_transition_feed_model\",\n \"path\": \"crates/event_store/src/model/addressable_transition_feed_v1.rs\",\n \"byte_length\": 22229,\n \"sha256\": \"e651952d73ed0a29d2b137076f56f699beada205ac84ed8a96834e86e343fa78\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"current_visibility_model\",\n \"path\": \"crates/event_store/src/model/current_visibility_v1.rs\",\n \"byte_length\": 5634,\n \"sha256\": \"d64429bc7985231e923c1c3e5295d94270a817dd5dc96693f35dfdf2dc398e4f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"food_projection_model\",\n \"path\": \"crates/event_store/src/model/food_availability_projection_v1.rs\",\n \"byte_length\": 17108,\n \"sha256\": \"67861a966d674efbc40f0b0433db2ec006048ff923f6261df4161e2ddba717b5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_generation_rebuild_authority\",\n \"path\": \"crates/event_store/src/nip09/reconciliation_v1.rs\",\n \"byte_length\": 184833,\n \"sha256\": \"3ca802cb7a8444c8512b2be1390a019281704316a430fb9b75a8ad2cad358a07\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"nip09_successor_result_vector_executor\",\n \"path\": \"crates/event_store/tests/support/nip09_reconciliation_v1_result_vector_v2.rs\",\n \"byte_length\": 18205,\n \"sha256\": \"c632beee70dc777b8dd2a4f88cadb55296fee7aa5742e6e2c9f6d49f26ae2c78\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"schema_migration_and_reopen_authority\",\n \"path\": \"crates/event_store/src/schema.rs\",\n \"byte_length\": 146146,\n \"sha256\": \"93b060e80d3edd73f86208e4bf698fa9d53eaf1eeb04526c9261fb8b5726fb0d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_store_and_transaction_authority\",\n \"path\": \"crates/event_store/src/store.rs\",\n \"byte_length\": 393218,\n \"sha256\": \"64565c3bc043779e1c2f26d9761901b109b4acab0f48c2021102c0e4e1f1f726\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"addressable_transition_feed_store\",\n \"path\": \"crates/event_store/src/store/addressable_transition_feed_v1.rs\",\n \"byte_length\": 40209,\n \"sha256\": \"2cebcca9602633652a79e7041d18d3a7d3feb1388a98f4f68137b9814efcedfa\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"current_visibility_store\",\n \"path\": \"crates/event_store/src/store/current_visibility_v1.rs\",\n \"byte_length\": 15737,\n \"sha256\": \"aa5d1bff7a5368cbac2b37906bb40f54bfc8aa6906bab34369569fdd105bf899\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"food_projection_store\",\n \"path\": \"crates/event_store/src/store/food_availability_projection_v1.rs\",\n \"byte_length\": 48919,\n \"sha256\": \"3a30da308ef863b806ed63d1cb65ac80f6a946a71821af84a0c595330491757e\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_post_core_v1_extension\",\n \"path\": \"crates/event_store/src/store/post_core_extensions_v1.rs\",\n \"byte_length\": 6569,\n \"sha256\": \"f5bd8ddb45e1b2144895bd8da737fc92db051be1844ce7e8047a32b47376ce37\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_post_core_v1_storage\",\n \"path\": \"crates/event_store/src/store/post_core_storage_v1.rs\",\n \"byte_length\": 16538,\n \"sha256\": \"fcd6546c23a6cba12b70d92728d19b6ee5d43174b83495f820348b77efa33aab\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"raw_ingest_capacity_authority\",\n \"path\": \"crates/event_store/src/store/protocol_reconciliation_v1.rs\",\n \"byte_length\": 29950,\n \"sha256\": \"c72aaff06e4f35d0b4523c6625e83345879f9e15b9fe9563dccf589e5d01e277\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_protocol_storage\",\n \"path\": \"crates/event_store/src/store/protocol_storage_v1.rs\",\n \"byte_length\": 10835,\n \"sha256\": \"c82179b9f57968191f3e71dd008cc701787c62715ada74090eeecaa3b213076d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_runtime\",\n \"path\": \"crates/event_store/src/source_maintenance_v1.rs\",\n \"byte_length\": 51849,\n \"sha256\": \"db43df5849c811d767a87bfa7884ce499400d0b169734e18242542a4415b3a51\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_food_result_vector_executor\",\n \"path\": \"crates/event_store/tests/food_availability_projection_v1_result_vector.rs\",\n \"byte_length\": 34046,\n \"sha256\": \"776903c6431ff07f26c6ad6b713db5628efce1a72f34d86de6e47d4404fa3a6d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"artifact_transaction_authority\",\n \"path\": \"tools/xtask/src/contract/artifact_bundle.rs\",\n \"byte_length\": 39149,\n \"sha256\": \"3d1bba3980b80698d2a21e26caf7af6fe4d9dc4a7fbabc6e406b5d33928b457f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_successor_governance\",\n \"path\": \"tools/xtask/src/contract/food_availability_projection.rs\",\n \"byte_length\": 198991,\n \"sha256\": \"36fd3d8b5dda3b0855a06a47dae14cae7f4cc84aaca9902c31477c218859fccc\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"transitive_predecessor_membership_governance\",\n \"path\": \"tools/xtask/src/contract/nip09_reconciliation.rs\",\n \"byte_length\": 842433,\n \"sha256\": \"6a8474d580d1165da4f89adca039811095f69a0601b4e74e652c22a42b9fc125\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_governance\",\n \"path\": \"tools/xtask/src/contract/source_maintenance.rs\",\n \"byte_length\": 199270,\n \"sha256\": \"a75fdd37990073a3f16e0ac771980a638641a2dc8ee317580aa100b41efc99de\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 515326,\n \"sha256\": \"d6c0a6630ac30b88e571162c84e89509ebc72eda3f614b5cc3f456bedfe19f4b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"dto_root_generation_authority\",\n \"path\": \"tools/xtask/src/dto_roots.rs\",\n \"byte_length\": 35940,\n \"sha256\": \"a899a5695d7533c151d03477c4bd697a6e55c47a4980549724ac42046af694f5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"xtask_dispatch_and_release_preflight\",\n \"path\": \"tools/xtask/src/main.rs\",\n \"byte_length\": 17427,\n \"sha256\": \"c59c0ff0b3683756c309decd9e9a99ed3e037d186dd3590de56988be74d37289\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n ],\n \"public_api\": {\n \"inherited_predecessor_symbols\": [\n \"RADROOTS_ADDRESSABLE_TRANSITION_CURSOR_JSON_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_D_TAG_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_FEED_VERSION_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_LIMIT_MAX_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_RAW_JSON_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_SCAN_MAX_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_SCOPE_KIND_MAX_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_PROJECTION_APPLY_PAGE_LIMIT_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_PROJECTION_VERSION_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_SEARCH_QUERY_MAX_BYTES_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_SEARCH_QUERY_MAX_TERMS_V1\",\n \"RadrootsAddressableTransitionCauseV1\",\n \"RadrootsAddressableTransitionCoordinateV1\",\n \"RadrootsAddressableTransitionCursorV1\",\n \"RadrootsAddressableTransitionEventReferenceV1\",\n \"RadrootsAddressableTransitionOriginV1\",\n \"RadrootsAddressableTransitionPageV1\",\n \"RadrootsAddressableTransitionRawHeadDecisionV1\",\n \"RadrootsAddressableTransitionScopeFingerprintV1\",\n \"RadrootsAddressableTransitionScopeV1\",\n \"RadrootsAddressableTransitionV1\",\n \"RadrootsAddressableTransitionVisibilityV1\",\n \"RadrootsCurrentEventVisibilityV1\",\n \"RadrootsCurrentVisibilityDecisionV1\",\n \"RadrootsFoodAvailabilitySearchQueryV1\",\n \"RadrootsFoodAvailabilityStatusFilterV1\",\n \"RadrootsNip09SuppressionEvidenceV1\",\n \"RadrootsNip09SuppressionOutcome\",\n \"RadrootsNip09SuppressionReason\",\n \"RadrootsStoreProducedCanonicalEventV1\",\n \"RadrootsStoredFoodAvailabilityImageV1\",\n \"RadrootsStoredFoodAvailabilityV1\"\n ],\n \"added_symbols\": [\n \"RADROOTS_EVENT_STORE_RAW_EVENT_COUNT_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_EVENT_TEXT_BYTES_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_TAG_COUNT_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_TAG_TEXT_BYTES_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1\",\n \"RadrootsEventStoreSourceCapacityResourceV1\",\n \"RadrootsEventStoreSourceCapacityV1\"\n ],\n \"methods\": [\n \"RadrootsEventStore::source_capacity_v1\",\n \"RadrootsEventStoreSourceCapacityResourceV1::as_str\",\n \"RadrootsEventStoreSourceCapacityV1::source_generation\",\n \"RadrootsEventStoreSourceCapacityV1::raw_event_count\",\n \"RadrootsEventStoreSourceCapacityV1::raw_tag_count\",\n \"RadrootsEventStoreSourceCapacityV1::raw_event_text_bytes\",\n \"RadrootsEventStoreSourceCapacityV1::raw_tag_text_bytes\",\n \"RadrootsEventStoreSourceCapacityV1::raw_high_water_seq\",\n \"RadrootsEventStoreSourceCapacityV1::retained_generation_count\",\n \"RadrootsEventStoreSourceCapacityV1::retained_generation_limit\"\n ],\n \"error_variants\": [\n \"SourceCapacityExceeded\",\n \"SourceGenerationHistoryLimitReached\",\n \"PersistedEphemeralRawEvent\",\n \"SourceCapacityStateDrift\",\n \"SqliteMainDatabaseEncodingNotUtf8\",\n \"RollbackWouldDiscardSourceGenerationHistory\"\n ],\n \"removed_symbols\": [\n \"RadrootsEventStoreReconciliationResource\",\n \"RadrootsEventStoreError::ReconciliationCapacityExceeded\"\n ],\n \"breaking_replacements\": [\n {\n \"removed\": \"RadrootsEventStoreReconciliationResource\",\n \"replacement\": \"RadrootsEventStoreSourceCapacityResourceV1\"\n },\n {\n \"removed\": \"RadrootsEventStoreError::ReconciliationCapacityExceeded\",\n \"replacement\": \"RadrootsEventStoreError::SourceCapacityExceeded\"\n }\n ]\n },\n \"result_vector\": {\n \"canonical_path\": \"contracts/conformance/vectors/event_store/source_maintenance.v1.json\",\n \"mirror_path\": \"crates/event_store/tests/fixtures/source_maintenance.v1.json\",\n \"byte_length\": 16253,\n \"sha256\": \"997aba2604a2b9d199fb87dc9d07942ca50d91863aeadcf3eeacf16d191dd71f\",\n \"hash_algorithm\": \"sha256_bytes_v1\",\n \"executor_id\": \"radroots_event_store.source_maintenance_v1.result_vector_executor.v1\",\n \"executor_path\": \"crates/event_store/tests/source_maintenance_v1_result_vector.rs\",\n \"executor_test\": \"source_maintenance_v1_result_vector\",\n \"executor_byte_length\": 23510,\n \"executor_sha256\": \"a7487afdfe19fc5fc794811d0f0e6035203e1aabcf0a33a1d398f6b3555d38f3\",\n \"executor_hash_algorithm\": \"sha256_bytes_v1\"\n }\n}\n";
+pub(crate) const SOURCE_MAINTENANCE_MANIFEST_JSON: &str = "{\n \"schema_version\": 1,\n \"contract_id\": \"radroots_event_store.source_maintenance_v1\",\n \"hook_id\": \"source_maintenance_v1\",\n \"manifest_schema\": {\n \"path\": \"crates/event_store/contracts/source_maintenance_v1.manifest.schema.json\",\n \"byte_length\": 12315,\n \"sha256\": \"96ea98a5abf3348b61565954cd34f2c622411d8dd34205ef04134bbe0e3d3f2b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"predecessor\": {\n \"hook_id\": \"food_availability_projection_v1\",\n \"manifest\": {\n \"path\": \"crates/event_store/contracts/food_availability_projection_v1.manifest.json\",\n \"byte_length\": 17455,\n \"sha256\": \"02dfe1b450fbdac16e718888215b4dd5c85d8975440fa21e8f439fb24c2b2990\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n },\n \"migration\": {\n \"version\": 4,\n \"name\": \"source_maintenance\",\n \"up\": {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.up.sql\",\n \"byte_length\": 19841,\n \"sha256\": \"ab2724188f8d08c897eebea2533a635e7c74282a25e84e4c0c37e78b08837a43\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"down\": {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.down.sql\",\n \"byte_length\": 5172,\n \"sha256\": \"fe44fd53c51545c08ea479b385e6781079dab70fc63da2a3c205d727a00ce860\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"schema_sha256\": \"074f85b663444ac150239ecd8441ea4a96ad83a798a55e22d2e5e2f7ee943a8c\",\n \"catalog\": {\n \"objects\": [\n \"radroots_event_store_source_capacity_delete_guard\",\n \"radroots_event_store_source_capacity_insert_guard\",\n \"radroots_event_store_source_capacity_marker_close_guard\",\n \"radroots_event_store_source_capacity_update_guard\",\n \"radroots_event_store_source_capacity_v1\",\n \"radroots_event_store_source_generation_capacity_advance\",\n \"radroots_event_store_source_generation_capacity_guard\"\n ],\n \"replaced_objects\": [\n \"radroots_event_store_food_availability_image_delete_guard\",\n \"radroots_event_store_food_availability_projection_delete_guard\",\n \"radroots_event_store_source_rebuild_marker_insert_guard\"\n ],\n \"tables\": [\n \"radroots_event_store_source_capacity_v1\"\n ],\n \"fts5_tables\": []\n }\n },\n \"source_maintenance\": {\n \"version\": 1,\n \"event_contract_registry_version\": 7,\n \"capacity_authority_id\": \"radroots_event_store_source_capacity_v1\",\n \"accounting\": {\n \"algorithm\": \"sqlite_cast_blob_octet_sum_v1\",\n \"raw_event_columns\": [\n \"event_id\",\n \"pubkey\",\n \"tags_json\",\n \"content\",\n \"sig\",\n \"raw_json\"\n ],\n \"raw_tag_columns\": [\n \"event_id\",\n \"tag_name\",\n \"tag_value\",\n \"tag_json\"\n ],\n \"nullable_raw_tag_columns\": [\n \"tag_value\"\n ]\n },\n \"limits\": {\n \"raw_events\": 25000,\n \"raw_tags\": 250000,\n \"raw_event_text_bytes\": 67108864,\n \"raw_tag_text_bytes\": 33554432,\n \"retained_source_generations\": 8\n },\n \"reopen_validation\": {\n \"mode\": \"bounded_full_raw_recount_v1\",\n \"raw_event_rejection_scan_bound\": 25001,\n \"raw_tag_rejection_scan_bound\": 250001,\n \"generation_history_validation\": \"bounded_count_plus_active_ordinal_v1\",\n \"retained_generation_rejection_scan_bound\": 9\n },\n \"rebuild_seal\": {\n \"nip09_hook_id\": \"nip09_reconciliation_v1\",\n \"nip09_manifest_sha256\": \"74af832420ffbaa9805e89df3c0b34f126a443e1598f757e3372f407f9003b77\",\n \"food_hook_id\": \"food_availability_projection_v1\",\n \"food_manifest_sha256\": \"02dfe1b450fbdac16e718888215b4dd5c85d8975440fa21e8f439fb24c2b2990\",\n \"food_scope_fingerprint_sha256\": \"8b63c5ddc48a2cc7db69295238b96d5f814dba50427c80b4d0079f061e6d3de0\",\n \"active_generation_authority\": \"radroots_event_store_source_state\",\n \"marker_close_authority\": \"radroots_event_store_source_capacity_marker_close_guard\"\n }\n },\n \"entry_points\": [\n {\n \"role\": \"migration_registry\",\n \"rust_path\": \"radroots_event_store::migrations::EVENT_STORE_MIGRATIONS[3]\"\n },\n {\n \"role\": \"migration_apply_hook\",\n \"rust_path\": \"radroots_event_store::schema::apply_migration_hook\"\n },\n {\n \"role\": \"migration_validation_hook\",\n \"rust_path\": \"radroots_event_store::schema::validate_migration_hook_state\"\n },\n {\n \"role\": \"capacity_query\",\n \"rust_path\": \"radroots_event_store::RadrootsEventStore::source_capacity_v1\"\n },\n {\n \"role\": \"raw_append_preflight\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::preflight_unique_raw_source_append_v1\"\n },\n {\n \"role\": \"raw_append_advance\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::advance_source_capacity_after_insert_v1\"\n },\n {\n \"role\": \"generation_append_preflight\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::preflight_source_generation_append_v1\"\n },\n {\n \"role\": \"generation_rebuild_bind\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::bind_source_capacity_to_generation_v1\"\n },\n {\n \"role\": \"sqlite_encoding_preflight\",\n \"rust_path\": \"radroots_event_store::store::validate_main_database_encoding\"\n },\n {\n \"role\": \"source_generation_history_rollback_guard\",\n \"rust_path\": \"radroots_event_store::schema::validate_rollback_preserves_source_generation_history\"\n },\n {\n \"role\": \"result_vector_executor\",\n \"rust_path\": \"source_maintenance_v1_result_vector\"\n }\n ],\n \"source_files\": [\n {\n \"role\": \"workspace_dependency_authority\",\n \"path\": \"Cargo.toml\",\n \"byte_length\": 12420,\n \"sha256\": \"a85d7cf805301bfb35f3e7643b3b48537f93d66bf02b9aea1d991934c856a1b1\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_currency_value_authority\",\n \"path\": \"crates/core/src/currency.rs\",\n \"byte_length\": 4142,\n \"sha256\": \"42e6da2d8d2fdd6955dc2d83e98dd00266e02bc910b69923b117662c05089d14\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_decimal_value_authority\",\n \"path\": \"crates/core/src/decimal.rs\",\n \"byte_length\": 7839,\n \"sha256\": \"4b1d681a92d7a9e074bee7e1f20eb1499bb05bfc6528a6c9ba69fa7d00416550\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_money_value_authority\",\n \"path\": \"crates/core/src/money.rs\",\n \"byte_length\": 8025,\n \"sha256\": \"3f30bc21e21951a62fdc5d4033736ed42a883df590e8403940775bd2edfeebc5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_percent_value_authority\",\n \"path\": \"crates/core/src/percent.rs\",\n \"byte_length\": 2652,\n \"sha256\": \"42ceab109881329f3539d06a0ee0381ab4359985fe185a18d58a9c9558c5eb7f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_quantity_value_authority\",\n \"path\": \"crates/core/src/quantity.rs\",\n \"byte_length\": 6785,\n \"sha256\": \"d14e619da3829cdaf3ca7385fb892d330c286c1e21455c135c37518cbb588b5f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_quantity_price_value_authority\",\n \"path\": \"crates/core/src/quantity_price.rs\",\n \"byte_length\": 7061,\n \"sha256\": \"d60f70377099470ed283f45868a5119d7bb46143cb250b5082a4c038ba7f6cdc\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_unit_value_authority\",\n \"path\": \"crates/core/src/unit.rs\",\n \"byte_length\": 10023,\n \"sha256\": \"550e9582b6a5290aac3f637b83853b21fefcbab92c432d067c83448f4b2fe3e9\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_public_surface\",\n \"path\": \"crates/blossom/src/lib.rs\",\n \"byte_length\": 510,\n \"sha256\": \"a4dfcbd193457c50c1b0fbfc91bb547981ecd3a6e23cfc6f1030edfb511dc58c\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_authorization_authority\",\n \"path\": \"crates/blossom/src/authorization.rs\",\n \"byte_length\": 39360,\n \"sha256\": \"461947b30516315a342b3b5697599f701d27050888f29f67f5d7a7ad4afa4c28\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_descriptor_authority\",\n \"path\": \"crates/blossom/src/descriptor.rs\",\n \"byte_length\": 13702,\n \"sha256\": \"b2a4ffa760256e1316f70e012d200e2b5f4afd8ede771b85a5147f96b247f599\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_error_authority\",\n \"path\": \"crates/blossom/src/error.rs\",\n \"byte_length\": 17229,\n \"sha256\": \"02af55beacf437040be17d1fc3271d6e2a62915f656ec2dbe06247deef83f95a\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_hash_authority\",\n \"path\": \"crates/blossom/src/hash.rs\",\n \"byte_length\": 11006,\n \"sha256\": \"45eca0d81ac0f46c305a32f81aafcdd90b14679d0614f72862289a6e5d4cb08f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_media_type_authority\",\n \"path\": \"crates/blossom/src/media_type.rs\",\n \"byte_length\": 2368,\n \"sha256\": \"69db5c0f9fcdd7dbe1f22daa499f65603ae4ce198083dab2c73a36813fa18a46\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"blossom_url_authority\",\n \"path\": \"crates/blossom/src/url.rs\",\n \"byte_length\": 14580,\n \"sha256\": \"342c995fcf620e5fb32461a7d8276f5668ba2662890bd32d12780cc9121c6451\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_public_surface\",\n \"path\": \"crates/event/src/lib.rs\",\n \"byte_length\": 1892,\n \"sha256\": \"5d609b963a9b8db18ef96a10f394617413734d2631774d3296e6d8fc029cb02c\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_contract_facade\",\n \"path\": \"crates/event/src/contract.rs\",\n \"byte_length\": 208,\n \"sha256\": \"27466bf36461071931f391c60646a13f7781d19584583166cc54c0eece76d010\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_contract_registry_v7_authority\",\n \"path\": \"crates/event/src/contract/registry_v7.rs\",\n \"byte_length\": 145805,\n \"sha256\": \"0a62603f6fc05dc9f758561cf7da258c676ada810ceaeca7fb151364a5c83d62\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_envelope_authority\",\n \"path\": \"crates/event/src/envelope.rs\",\n \"byte_length\": 29310,\n \"sha256\": \"29f8d8c4b17c01cb5a8c7f59e4ce52e134180e0e6f37d265063d278b8f2ff26e\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_verification_typestate_authority\",\n \"path\": \"crates/event/src/verification.rs\",\n \"byte_length\": 9400,\n \"sha256\": \"417acb2ce670d266b1fa4fcafd6b48a910218deab38f57fb8b942056db76f94d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_admission_typestate_authority\",\n \"path\": \"crates/event/src/admission.rs\",\n \"byte_length\": 7052,\n \"sha256\": \"62bd2ceb0f434946fdfc2b81f4efb13738676a29a6936c23a427e43aadf2d9cb\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_head_facade\",\n \"path\": \"crates/event/src/event_head.rs\",\n \"byte_length\": 97,\n \"sha256\": \"f761ff3e74c4f5e1e28381db00ce698c633ff048669b22984d14a587482e8e83\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_head_v1_authority\",\n \"path\": \"crates/event/src/event_head/v1.rs\",\n \"byte_length\": 6849,\n \"sha256\": \"9f4144d8d240023cf493ce0b538ade4c16dc44204d6b5f2797152caf6f9e7dba\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_ids_authority\",\n \"path\": \"crates/event/src/id.rs\",\n \"byte_length\": 48381,\n \"sha256\": \"a08fe3873815453a03318a706e2fa98a8a6728ec69d24368a87ec60025566fe0\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_trade_authority\",\n \"path\": \"crates/event/src/trade.rs\",\n \"byte_length\": 65549,\n \"sha256\": \"f818a7287ed82de3fb9ef1d973f0fb18d84b30eb4827ddc576ee502dd980145f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_kinds_authority\",\n \"path\": \"crates/event/src/kinds.rs\",\n \"byte_length\": 34961,\n \"sha256\": \"8b3ce6193cab1f7e1587d0c1b2880a81b0aa43c77d89671b602772db37edcebf\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_tags_authority\",\n \"path\": \"crates/event/src/tags.rs\",\n \"byte_length\": 4530,\n \"sha256\": \"cb8f12a639fc72d9238e32495d9e67e928e6627fb8c94ec0a58c5d95e7373cd6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_draft_authority\",\n \"path\": \"crates/event/src/draft.rs\",\n \"byte_length\": 51150,\n \"sha256\": \"ecc619aae28d688d13f35467398f17766b60f26c2bf3616d772ce65b7fa4fb53\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_calendar_authority\",\n \"path\": \"crates/event/src/calendar.rs\",\n \"byte_length\": 97992,\n \"sha256\": \"73e79d61ee175cb46e6123e1d28239252ddf0ad094dc1113bdb1286b63b88d57\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_classified_listing_authority\",\n \"path\": \"crates/event/src/classified_listing.rs\",\n \"byte_length\": 7956,\n \"sha256\": \"ac58484b87c7712c81f50f884d1c0dd60938571aa5f6f05d555191f529a4c2b0\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_profile_authority\",\n \"path\": \"crates/event/src/profile.rs\",\n \"byte_length\": 17011,\n \"sha256\": \"572bfe3b6f1711d109db445077f0a8453cad3f021cc0638f79201b618e16b978\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_post_authority\",\n \"path\": \"crates/event/src/post.rs\",\n \"byte_length\": 19968,\n \"sha256\": \"a822cd51eff9cbaa39db998cc5c90d117f7a983f0fe2d42d42118cb00e95982c\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_comment_authority\",\n \"path\": \"crates/event/src/comment.rs\",\n \"byte_length\": 34896,\n \"sha256\": \"a35b17418884c4c620e19b96d36a418db9d0065a5582f6b869a6bc6111f65bf6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_food_availability_authority\",\n \"path\": \"crates/event/src/food_availability.rs\",\n \"byte_length\": 42829,\n \"sha256\": \"52472c07eb0855d884c60755dc3a3e1a963793451d707e51f769f2d78384f318\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_deletion_authority\",\n \"path\": \"crates/event/src/deletion.rs\",\n \"byte_length\": 30022,\n \"sha256\": \"8c3f2e32407520a8e8242c1293af32613c648fb76218b8003a18929ac0b3811f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_dto_authority\",\n \"path\": \"crates/event/src/dto.rs\",\n \"byte_length\": 5038,\n \"sha256\": \"9e228c95ff6ff33f99441d8682781fd0a1c4d3f1db24284a43985dcbac3ba136\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_farm_crdt_authority\",\n \"path\": \"crates/event/src/farm_crdt.rs\",\n \"byte_length\": 21118,\n \"sha256\": \"cec265ac9c42b59a14be4eb96924d26a83477c27291f667f23e6e47a64b6b2c7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_knowledge_authority\",\n \"path\": \"crates/event/src/knowledge.rs\",\n \"byte_length\": 54624,\n \"sha256\": \"3d55ff88a4e30d5d605f9ae4b2e96b9df6978d6c14e52f9aeb0b8039cfc200d7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_operational_listing_authority\",\n \"path\": \"crates/event/src/operational_listing.rs\",\n \"byte_length\": 10307,\n \"sha256\": \"52e710db816c89c8d4b87c86541987f7447f2f7091c394c68d9cb98a27f90159\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_order_authority\",\n \"path\": \"crates/event/src/order.rs\",\n \"byte_length\": 60218,\n \"sha256\": \"4203fcc469612cac5c705884564e140b9fb076cd57079e22ed85275a8b936e50\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_reply_authority\",\n \"path\": \"crates/event/src/reply.rs\",\n \"byte_length\": 15581,\n \"sha256\": \"b0b83a1e96125c0892d932e9314519242a715fa8f46334aff765ffd248c91d9a\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_trade_validation_authority\",\n \"path\": \"crates/event/src/trade_validation.rs\",\n \"byte_length\": 4669,\n \"sha256\": \"1486f79bd1d0eadea6cc1b7646ce1f7f57ccade18334a3ea37286426676b9b98\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_relay_hint_authority\",\n \"path\": \"crates/event/src/relay_hint.rs\",\n \"byte_length\": 13598,\n \"sha256\": \"fb8b026eeccf51d78f20771c44223e7b48825aa0e3c760d6b3cb90148021a674\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_media_authority\",\n \"path\": \"crates/event/src/media.rs\",\n \"byte_length\": 3455,\n \"sha256\": \"10678af3a202d5367dbcf3bcdc0bf9fdf8d8fb89b045d7d3fb96b3cc195ab6f9\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_social_authority\",\n \"path\": \"crates/event/src/social.rs\",\n \"byte_length\": 6076,\n \"sha256\": \"a2dab19caad46eab14fac86e7c878a801a450d8ac374b0e728b71a5a224eef19\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_wire_facade\",\n \"path\": \"crates/event/src/wire.rs\",\n \"byte_length\": 68,\n \"sha256\": \"cb52f6006f7ecd862707d6b048f9fc407e0cd5ebcd3091b3c54e195ffa5cba64\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_wire_v1_authority\",\n \"path\": \"crates/event/src/wire/v1.rs\",\n \"byte_length\": 22198,\n \"sha256\": \"c818bc1f67e215948ba253c8c62b09ab788db9aeec9b6df8096014b55565cb8c\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_public_surface\",\n \"path\": \"crates/event_codec/src/lib.rs\",\n \"byte_length\": 2448,\n \"sha256\": \"7d4b0040ad3971f34395b17cb1aa40617e9c2c9e1b4222676c388c3adaa47e07\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_verification_facade\",\n \"path\": \"crates/event_codec/src/verification.rs\",\n \"byte_length\": 253,\n \"sha256\": \"b49a32df605035c87f295c0a151140d43d2e588c5e11b05183e5fc92993dae0e\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_verification_v1_authority\",\n \"path\": \"crates/event_codec/src/verification/v1.rs\",\n \"byte_length\": 6584,\n \"sha256\": \"72f64615bd6b6dc3b051579e3069b2213f3781dcc28e12f0b533c7d81b81b9b1\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_registry_v7_admission_authority\",\n \"path\": \"crates/event_codec/src/admission/registry_v7.rs\",\n \"byte_length\": 5228,\n \"sha256\": \"62da30cb6ef7d0ed2d43715a73bd529283e8ae8c12571bc5c68722cb400f17b8\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_admission_facade\",\n \"path\": \"crates/event_codec/src/admission.rs\",\n \"byte_length\": 21347,\n \"sha256\": \"190478478f4c90a74b2a60d1634fb2f352a7af0cf7b9510ed42fefe95987dc18\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_profile_inbound_facade\",\n \"path\": \"crates/event_codec/src/profile/inbound.rs\",\n \"byte_length\": 86,\n \"sha256\": \"9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_profile_registry_v7_authority\",\n \"path\": \"crates/event_codec/src/profile/inbound/registry_v7.rs\",\n \"byte_length\": 10347,\n \"sha256\": \"32111c0e0592229c11a93a3a8054e7b124b039451aa696a9abcb56df2a6608d3\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_post_inbound_facade\",\n \"path\": \"crates/event_codec/src/post/inbound.rs\",\n \"byte_length\": 86,\n \"sha256\": \"9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_post_registry_v7_authority\",\n \"path\": \"crates/event_codec/src/post/inbound/registry_v7.rs\",\n \"byte_length\": 15985,\n \"sha256\": \"e17083b0596e3a55994c399bc6272c0ebf04cabaa513260b3d45dd546f7f50ef\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_reply_inbound_facade\",\n \"path\": \"crates/event_codec/src/reply/inbound.rs\",\n \"byte_length\": 86,\n \"sha256\": \"9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_reply_registry_v7_authority\",\n \"path\": \"crates/event_codec/src/reply/inbound/registry_v7.rs\",\n \"byte_length\": 26662,\n \"sha256\": \"d1d11116ca27801b7e2f17da60900e6a9ca0b6f27ea9ee0bf19e97dbf24c0c32\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_comment_inbound_facade\",\n \"path\": \"crates/event_codec/src/comment/inbound.rs\",\n \"byte_length\": 86,\n \"sha256\": \"9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_comment_registry_v7_authority\",\n \"path\": \"crates/event_codec/src/comment/inbound/registry_v7.rs\",\n \"byte_length\": 53311,\n \"sha256\": \"c63fe853536b17bded6073f32b53434b60fc5123f911d01c642a6b3982f09c70\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_deletion_facade\",\n \"path\": \"crates/event_codec/src/deletion/mod.rs\",\n \"byte_length\": 115,\n \"sha256\": \"17d56f82ddb0a86bc97abbcf3e037fe460ea31fff2548f527be4bf004dac3a95\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_deletion_reconciliation_v1_authority\",\n \"path\": \"crates/event_codec/src/deletion/reconciliation_v1.rs\",\n \"byte_length\": 38028,\n \"sha256\": \"c6901f559e83700610595720c47a4d062078ae8411e1fc2fb952dd215b19dd90\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_error_authority\",\n \"path\": \"crates/event_codec/src/error.rs\",\n \"byte_length\": 3666,\n \"sha256\": \"174ce982bca37fac28d016c3ab3236441c9b31b2169ec94db6c9288b36c72849\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_food_admission_authority\",\n \"path\": \"crates/event_codec/src/food_availability/admission.rs\",\n \"byte_length\": 5681,\n \"sha256\": \"99809859090f5295572688d06bbb30dbe50524ba2c996de7ec5b26f6dd390643\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_food_authored_authority\",\n \"path\": \"crates/event_codec/src/food_availability/authored.rs\",\n \"byte_length\": 9724,\n \"sha256\": \"8673ec62ed3fc47b691efd9ca828643719803971e72b756c5b4603d409d0fba5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_food_inbound_facade\",\n \"path\": \"crates/event_codec/src/food_availability/inbound.rs\",\n \"byte_length\": 86,\n \"sha256\": \"9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_food_registry_v7_authority\",\n \"path\": \"crates/event_codec/src/food_availability/inbound/registry_v7.rs\",\n \"byte_length\": 26037,\n \"sha256\": \"92a7e75da3293967cec17a514479144c3c948a64542577dd58c7f07992968e4f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_job_traits_authority\",\n \"path\": \"crates/event_codec/src/job/traits.rs\",\n \"byte_length\": 4946,\n \"sha256\": \"1b3558a3196744005978dceddd33d3a72ad1ab8f50fb1cbfd997dcfe98bb9e39\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_job_encode_authority\",\n \"path\": \"crates/event_codec/src/job/encode.rs\",\n \"byte_length\": 1670,\n \"sha256\": \"5d6cae6309fcdd02deb7c751b5c451d257cf1900986391cb39cbb2bfd1ddb577\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_knowledge_verification_authority\",\n \"path\": \"crates/event_codec/src/knowledge/verification.rs\",\n \"byte_length\": 6986,\n \"sha256\": \"6f8a10ed262ce37f05acd9c89a1e37ca2fa99ce7da18407891e8abe228ee2349\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_operational_listing_tags_authority\",\n \"path\": \"crates/event_codec/src/operational_listing/tags.rs\",\n \"byte_length\": 50222,\n \"sha256\": \"55b6860bc6f4699dd47a49f8336764e9db78aafb7c32849102dedeb96804b373\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_order_decode_authority\",\n \"path\": \"crates/event_codec/src/order/decode.rs\",\n \"byte_length\": 42667,\n \"sha256\": \"033d9465bc041d205eab3f820572b719a35651340dcb50bdce7c60fabee5f6e1\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_profile_facade\",\n \"path\": \"crates/event_codec/src/profile/mod.rs\",\n \"byte_length\": 1456,\n \"sha256\": \"06b890da54580e2ec68f7e729281b7c3b650ac0a95a4fcb0db3c6cdaa591a7ef\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_tag_builders_authority\",\n \"path\": \"crates/event_codec/src/tag_builders.rs\",\n \"byte_length\": 9232,\n \"sha256\": \"bd81ccf04fd72358331b48eddcde087a95e115c5bd90c05fd9332644979ad5e6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_codec_trade_facade\",\n \"path\": \"crates/event_codec/src/trade/mod.rs\",\n \"byte_length\": 21472,\n \"sha256\": \"10cee4b6cd6429ac9eb00327a521bcd0982fae4de98bda77e4728fda002965d3\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_dependency_authority\",\n \"path\": \"crates/event_store/Cargo.toml\",\n \"byte_length\": 1797,\n \"sha256\": \"e79f4b69500553835e2cde28e7d9139788bebe6e2cd4e0b4ae72cc150491daf4\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_error_and_limits\",\n \"path\": \"crates/event_store/src/error.rs\",\n \"byte_length\": 19458,\n \"sha256\": \"3d87df984af6ae5decf7ca8c3a8d5422be6c647e6a05490225b6b2037bf3701b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"generated_descriptor_registration\",\n \"path\": \"crates/event_store/src/generated.rs\",\n \"byte_length\": 144,\n \"sha256\": \"6b0a8d6f249bd4fc3f878d37cb5e418680f0f1be2d9eec2518dedf03efc47121\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_surface\",\n \"path\": \"crates/event_store/src/lib.rs\",\n \"byte_length\": 3860,\n \"sha256\": \"de1c5cc1ab36e1166d23e2b76aeae1d5f0f401cf07ce243590741c0126d02132\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"migration_registry\",\n \"path\": \"crates/event_store/src/migrations.rs\",\n \"byte_length\": 73585,\n \"sha256\": \"a47477d04759ec6e71d14d3d05459864eae78fce0e3c2481a1b03147f04637f7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_model_public_surface\",\n \"path\": \"crates/event_store/src/model.rs\",\n \"byte_length\": 32070,\n \"sha256\": \"2cdbf6d1a4e0fa6680f45f02758513509339a7cf3a2387e824fb73b0edf749a4\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_reconciliation_v1_model\",\n \"path\": \"crates/event_store/src/model/reconciliation_v1.rs\",\n \"byte_length\": 10446,\n \"sha256\": \"475b7b840bffdae7e3f7a31f5b940b9dfa579eaa1cac640729aaaeadabf83ba6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_reconciliation_v1_ingest_model\",\n \"path\": \"crates/event_store/src/model/ingest_reconciliation_v1.rs\",\n \"byte_length\": 1588,\n \"sha256\": \"6117e797674c35bb1ccebffbc6a8c0108bfc38c7c9f82607066dfd802b8f0734\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"addressable_transition_feed_model\",\n \"path\": \"crates/event_store/src/model/addressable_transition_feed_v1.rs\",\n \"byte_length\": 22229,\n \"sha256\": \"e651952d73ed0a29d2b137076f56f699beada205ac84ed8a96834e86e343fa78\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"current_visibility_model\",\n \"path\": \"crates/event_store/src/model/current_visibility_v1.rs\",\n \"byte_length\": 5634,\n \"sha256\": \"d64429bc7985231e923c1c3e5295d94270a817dd5dc96693f35dfdf2dc398e4f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"food_projection_model\",\n \"path\": \"crates/event_store/src/model/food_availability_projection_v1.rs\",\n \"byte_length\": 17108,\n \"sha256\": \"67861a966d674efbc40f0b0433db2ec006048ff923f6261df4161e2ddba717b5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_generation_rebuild_authority\",\n \"path\": \"crates/event_store/src/nip09/reconciliation_v1.rs\",\n \"byte_length\": 184917,\n \"sha256\": \"1c02fbf00881839c29de36c286e649061582eef09829fc1ccb526a62478101fd\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"nip09_successor_result_vector_executor\",\n \"path\": \"crates/event_store/tests/support/nip09_reconciliation_v1_result_vector_v2.rs\",\n \"byte_length\": 18205,\n \"sha256\": \"c632beee70dc777b8dd2a4f88cadb55296fee7aa5742e6e2c9f6d49f26ae2c78\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"schema_migration_and_reopen_authority\",\n \"path\": \"crates/event_store/src/schema.rs\",\n \"byte_length\": 146146,\n \"sha256\": \"93b060e80d3edd73f86208e4bf698fa9d53eaf1eeb04526c9261fb8b5726fb0d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_store_and_transaction_authority\",\n \"path\": \"crates/event_store/src/store.rs\",\n \"byte_length\": 393218,\n \"sha256\": \"64565c3bc043779e1c2f26d9761901b109b4acab0f48c2021102c0e4e1f1f726\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"addressable_transition_feed_store\",\n \"path\": \"crates/event_store/src/store/addressable_transition_feed_v1.rs\",\n \"byte_length\": 40209,\n \"sha256\": \"2cebcca9602633652a79e7041d18d3a7d3feb1388a98f4f68137b9814efcedfa\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"current_visibility_store\",\n \"path\": \"crates/event_store/src/store/current_visibility_v1.rs\",\n \"byte_length\": 15737,\n \"sha256\": \"aa5d1bff7a5368cbac2b37906bb40f54bfc8aa6906bab34369569fdd105bf899\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"food_projection_store\",\n \"path\": \"crates/event_store/src/store/food_availability_projection_v1.rs\",\n \"byte_length\": 48919,\n \"sha256\": \"3a30da308ef863b806ed63d1cb65ac80f6a946a71821af84a0c595330491757e\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_post_core_v1_extension\",\n \"path\": \"crates/event_store/src/store/post_core_extensions_v1.rs\",\n \"byte_length\": 6569,\n \"sha256\": \"f5bd8ddb45e1b2144895bd8da737fc92db051be1844ce7e8047a32b47376ce37\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_post_core_v1_storage\",\n \"path\": \"crates/event_store/src/store/post_core_storage_v1.rs\",\n \"byte_length\": 16538,\n \"sha256\": \"fcd6546c23a6cba12b70d92728d19b6ee5d43174b83495f820348b77efa33aab\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"raw_ingest_capacity_authority\",\n \"path\": \"crates/event_store/src/store/protocol_reconciliation_v1.rs\",\n \"byte_length\": 29950,\n \"sha256\": \"c72aaff06e4f35d0b4523c6625e83345879f9e15b9fe9563dccf589e5d01e277\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_protocol_storage\",\n \"path\": \"crates/event_store/src/store/protocol_storage_v1.rs\",\n \"byte_length\": 10835,\n \"sha256\": \"c82179b9f57968191f3e71dd008cc701787c62715ada74090eeecaa3b213076d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_runtime\",\n \"path\": \"crates/event_store/src/source_maintenance_v1.rs\",\n \"byte_length\": 51849,\n \"sha256\": \"db43df5849c811d767a87bfa7884ce499400d0b169734e18242542a4415b3a51\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_food_result_vector_executor\",\n \"path\": \"crates/event_store/tests/food_availability_projection_v1_result_vector.rs\",\n \"byte_length\": 34046,\n \"sha256\": \"776903c6431ff07f26c6ad6b713db5628efce1a72f34d86de6e47d4404fa3a6d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"artifact_transaction_authority\",\n \"path\": \"tools/xtask/src/contract/artifact_bundle.rs\",\n \"byte_length\": 39149,\n \"sha256\": \"3d1bba3980b80698d2a21e26caf7af6fe4d9dc4a7fbabc6e406b5d33928b457f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_successor_governance\",\n \"path\": \"tools/xtask/src/contract/food_availability_projection.rs\",\n \"byte_length\": 198991,\n \"sha256\": \"36fd3d8b5dda3b0855a06a47dae14cae7f4cc84aaca9902c31477c218859fccc\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"transitive_predecessor_membership_governance\",\n \"path\": \"tools/xtask/src/contract/nip09_reconciliation.rs\",\n \"byte_length\": 842433,\n \"sha256\": \"6a8474d580d1165da4f89adca039811095f69a0601b4e74e652c22a42b9fc125\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_governance\",\n \"path\": \"tools/xtask/src/contract/source_maintenance.rs\",\n \"byte_length\": 199270,\n \"sha256\": \"4b83f736e7ff8ac01fd7b2b66757af9275cfe997303ede3c1f026872f864bbd0\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 515326,\n \"sha256\": \"d6c0a6630ac30b88e571162c84e89509ebc72eda3f614b5cc3f456bedfe19f4b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"dto_root_generation_authority\",\n \"path\": \"tools/xtask/src/dto_roots.rs\",\n \"byte_length\": 35940,\n \"sha256\": \"a899a5695d7533c151d03477c4bd697a6e55c47a4980549724ac42046af694f5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"xtask_dispatch_and_release_preflight\",\n \"path\": \"tools/xtask/src/main.rs\",\n \"byte_length\": 17427,\n \"sha256\": \"c59c0ff0b3683756c309decd9e9a99ed3e037d186dd3590de56988be74d37289\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n ],\n \"public_api\": {\n \"inherited_predecessor_symbols\": [\n \"RADROOTS_ADDRESSABLE_TRANSITION_CURSOR_JSON_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_D_TAG_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_FEED_VERSION_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_LIMIT_MAX_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_RAW_JSON_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_SCAN_MAX_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_SCOPE_KIND_MAX_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_PROJECTION_APPLY_PAGE_LIMIT_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_PROJECTION_VERSION_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_SEARCH_QUERY_MAX_BYTES_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_SEARCH_QUERY_MAX_TERMS_V1\",\n \"RadrootsAddressableTransitionCauseV1\",\n \"RadrootsAddressableTransitionCoordinateV1\",\n \"RadrootsAddressableTransitionCursorV1\",\n \"RadrootsAddressableTransitionEventReferenceV1\",\n \"RadrootsAddressableTransitionOriginV1\",\n \"RadrootsAddressableTransitionPageV1\",\n \"RadrootsAddressableTransitionRawHeadDecisionV1\",\n \"RadrootsAddressableTransitionScopeFingerprintV1\",\n \"RadrootsAddressableTransitionScopeV1\",\n \"RadrootsAddressableTransitionV1\",\n \"RadrootsAddressableTransitionVisibilityV1\",\n \"RadrootsCurrentEventVisibilityV1\",\n \"RadrootsCurrentVisibilityDecisionV1\",\n \"RadrootsFoodAvailabilitySearchQueryV1\",\n \"RadrootsFoodAvailabilityStatusFilterV1\",\n \"RadrootsNip09SuppressionEvidenceV1\",\n \"RadrootsNip09SuppressionOutcome\",\n \"RadrootsNip09SuppressionReason\",\n \"RadrootsStoreProducedCanonicalEventV1\",\n \"RadrootsStoredFoodAvailabilityImageV1\",\n \"RadrootsStoredFoodAvailabilityV1\"\n ],\n \"added_symbols\": [\n \"RADROOTS_EVENT_STORE_RAW_EVENT_COUNT_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_EVENT_TEXT_BYTES_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_TAG_COUNT_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_TAG_TEXT_BYTES_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1\",\n \"RadrootsEventStoreSourceCapacityResourceV1\",\n \"RadrootsEventStoreSourceCapacityV1\"\n ],\n \"methods\": [\n \"RadrootsEventStore::source_capacity_v1\",\n \"RadrootsEventStoreSourceCapacityResourceV1::as_str\",\n \"RadrootsEventStoreSourceCapacityV1::source_generation\",\n \"RadrootsEventStoreSourceCapacityV1::raw_event_count\",\n \"RadrootsEventStoreSourceCapacityV1::raw_tag_count\",\n \"RadrootsEventStoreSourceCapacityV1::raw_event_text_bytes\",\n \"RadrootsEventStoreSourceCapacityV1::raw_tag_text_bytes\",\n \"RadrootsEventStoreSourceCapacityV1::raw_high_water_seq\",\n \"RadrootsEventStoreSourceCapacityV1::retained_generation_count\",\n \"RadrootsEventStoreSourceCapacityV1::retained_generation_limit\"\n ],\n \"error_variants\": [\n \"SourceCapacityExceeded\",\n \"SourceGenerationHistoryLimitReached\",\n \"PersistedEphemeralRawEvent\",\n \"SourceCapacityStateDrift\",\n \"SqliteMainDatabaseEncodingNotUtf8\",\n \"RollbackWouldDiscardSourceGenerationHistory\"\n ],\n \"removed_symbols\": [\n \"RadrootsEventStoreReconciliationResource\",\n \"RadrootsEventStoreError::ReconciliationCapacityExceeded\"\n ],\n \"breaking_replacements\": [\n {\n \"removed\": \"RadrootsEventStoreReconciliationResource\",\n \"replacement\": \"RadrootsEventStoreSourceCapacityResourceV1\"\n },\n {\n \"removed\": \"RadrootsEventStoreError::ReconciliationCapacityExceeded\",\n \"replacement\": \"RadrootsEventStoreError::SourceCapacityExceeded\"\n }\n ]\n },\n \"result_vector\": {\n \"canonical_path\": \"contracts/conformance/vectors/event_store/source_maintenance.v1.json\",\n \"mirror_path\": \"crates/event_store/tests/fixtures/source_maintenance.v1.json\",\n \"byte_length\": 16253,\n \"sha256\": \"997aba2604a2b9d199fb87dc9d07942ca50d91863aeadcf3eeacf16d191dd71f\",\n \"hash_algorithm\": \"sha256_bytes_v1\",\n \"executor_id\": \"radroots_event_store.source_maintenance_v1.result_vector_executor.v1\",\n \"executor_path\": \"crates/event_store/tests/source_maintenance_v1_result_vector.rs\",\n \"executor_test\": \"source_maintenance_v1_result_vector\",\n \"executor_byte_length\": 23510,\n \"executor_sha256\": \"a7487afdfe19fc5fc794811d0f0e6035203e1aabcf0a33a1d398f6b3555d38f3\",\n \"executor_hash_algorithm\": \"sha256_bytes_v1\"\n }\n}\n";
pub(crate) const SOURCE_MAINTENANCE_MANIFEST_BYTE_LENGTH: usize = 38397;
pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SHA256: &str =
- "ef03a4f87259e2d2d11b9b581d24f3109fbaac0789f02c7ded92bf6adbc5ec60";
+ "64d17dedb83e853208a136fe3c9265eeca92c3698e17d02f20f47bf1f58b2ae9";
pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SCHEMA_VERSION: u32 = 1;
pub(crate) const SOURCE_MAINTENANCE_CONTRACT_ID: &str =
"radroots_event_store.source_maintenance_v1";
diff --git a/crates/event_store/src/nip09/reconciliation_v1.rs b/crates/event_store/src/nip09/reconciliation_v1.rs
@@ -4560,7 +4560,10 @@ INSERT INTO caller_child(id, parent_id) VALUES (1, 999);",
.expect("event id");
let nostr_id = nostr::EventId::from_hex(&id.to_hex()).expect("Nostr event id");
let message = Message::from_digest(nostr_id.to_bytes());
- let signature = SECP256K1.sign_schnorr_no_aux_rand(&message, keys.key_pair(SECP256K1));
+ let signature = SECP256K1.sign_schnorr_no_aux_rand(
+ &message,
+ &nostr::secp256k1::Keypair::from_secret_key(SECP256K1, keys.secret_key()),
+ );
EventEnvelope::new(EventEnvelopeParts {
id: id.into_string(),
author,
diff --git a/crates/identity/tests/package_boundary.rs b/crates/identity/tests/package_boundary.rs
@@ -30,22 +30,6 @@ fn manifest_has_exact_features_and_dependencies() {
));
assert!(MANIFEST.contains("serde = { workspace = true, optional = true }"));
assert!(MANIFEST.contains("thiserror = { version = \"2\", default-features = false }"));
-
- for forbidden in [
- "json-file",
- "nostr",
- "radroots_protected_store",
- "radroots_runtime",
- "radroots_runtime_paths",
- "radroots_secret_vault",
- "tracing",
- "tempfile",
- ] {
- assert!(
- !MANIFEST.contains(forbidden),
- "identity manifest must not contain host persistence edge {forbidden}"
- );
- }
}
#[test]
diff --git a/crates/net/Cargo.toml b/crates/net/Cargo.toml
@@ -1,72 +0,0 @@
-[package]
-name = "radroots_net"
-publish = false
-version = "0.1.0-alpha"
-edition.workspace = true
-authors = ["Tyson Lupul <tyson@radroots.org>"]
-rust-version.workspace = true
-license.workspace = true
-description = "Network layer for Radroots"
-repository.workspace = true
-homepage.workspace = true
-readme = "README"
-
-[package.metadata.radroots.compatibility]
-status = "publish_frozen"
-replacement = "radroots_transport + radroots_sync + radroots_sdk"
-deviation = "RCRV1-DEV-010"
-removal_step = 313
-new_consumers_forbidden = true
-
-[features]
-default = ["std"]
-std = ["serde/std"]
-rt = ["std", "dep:tokio"]
-nostr-client = [
- "rt",
- "dep:radroots_event",
- "dep:radroots_event_codec",
- "radroots_event/serde",
- "dep:radroots_nostr_accounts",
- "dep:radroots_nostr_signer",
- "dep:secrecy",
- "dep:hex",
- "dep:tempfile",
- "dep:serde_json",
- "dep:radroots_nostr",
- "dep:nostr",
- "dep:radroots_transport_nostr",
-]
-directories = ["std", "dep:directories"]
-fs-persistence = ["std", "dep:radroots_runtime_paths"]
-
-[dependencies]
-radroots_event = { workspace = true, optional = true, default-features = true, features = [
- "std",
- "serde",
-] }
-radroots_log = { workspace = true, features = ["std"] }
-radroots_nostr_accounts = { workspace = true, optional = true, default-features = true }
-radroots_nostr_signer = { workspace = true, optional = true }
-radroots_event_codec = { workspace = true, optional = true, default-features = true, features = [
- "std",
-] }
-radroots_nostr = { workspace = true, optional = true, default-features = true, features = [
- "events",
-] }
-nostr = { workspace = true, optional = true, features = ["std"] }
-radroots_transport_nostr = { workspace = true, optional = true, default-features = false }
-directories = { workspace = true, optional = true }
-hex = { workspace = true, optional = true }
-radroots_runtime_paths = { workspace = true, optional = true }
-secrecy = { workspace = true, optional = true }
-serde = { workspace = true, features = ["derive"] }
-serde_json = { workspace = true, optional = true }
-tempfile = { workspace = true, optional = true }
-thiserror = { workspace = true }
-tokio = { workspace = true, optional = true, features = ["rt-multi-thread"] }
-tracing = { workspace = true }
-futures = { workspace = true }
-
-[dev-dependencies]
-radroots_identity = { workspace = true, default-features = true }
diff --git a/crates/net/README b/crates/net/README
@@ -1,26 +0,0 @@
-# radroots_net
-
-`radroots_net` is a private, publication-frozen compatibility package for the
-superseded broad network/runtime layer. New consumers and new behavior are
-forbidden. Final transport contracts belong to `radroots_transport`, shared
-orchestration belongs to `radroots_sync`, and application composition belongs
-to `radroots_sdk` or the host.
-
-## Compatibility quarantine
-
-The existing `app_rt` consumer still resolves this package as
-`radroots_net_core` while its downstream cutover is pending. Removing the
-package now would break that standalone consumer, so `RCRV1-DEV-010` retains
-only this documentation-hidden, `publish = false` bridge. Step 313 removes the
-package after the first-party consumer matrix proves the cutover complete.
-
-Do not add consumers, features, public contracts, transport implementations,
-or runtime behavior here. In particular, the optional legacy Nostr-client
-surface is not the public `radroots_transport_nostr` API and cannot enter a
-release feature closure.
-
-## Copyright
-
-Except as otherwise noted, all files in the `radroots_net` distribution are
-copyright (c) 2025 Tyson Lupul. See `LICENSE` for usage, redistribution, and
-warranty terms.
diff --git a/crates/net/build.rs b/crates/net/build.rs
@@ -1,77 +0,0 @@
-use std::env;
-use std::path::PathBuf;
-use std::process::Command;
-use std::time::{SystemTime, UNIX_EPOCH};
-
-fn main() {
- println!("cargo:rerun-if-changed=build.rs");
- println!("cargo:rerun-if-env-changed=CARGO_MANIFEST_DIR");
- println!("cargo:rerun-if-env-changed=RUSTC");
- println!("cargo:rerun-if-env-changed=PROFILE");
-
- let manifest_dir =
- env::var("CARGO_MANIFEST_DIR").expect("missing required env var CARGO_MANIFEST_DIR");
-
- let mut dir = PathBuf::from(&manifest_dir);
- let git_dir = loop {
- if dir.join(".git").exists() {
- break dir.join(".git");
- }
- if !dir.pop() {
- break PathBuf::from(".git");
- }
- };
-
- if git_dir.exists() {
- println!("cargo:rerun-if-changed={}", git_dir.join("HEAD").display());
- println!(
- "cargo:rerun-if-changed={}",
- git_dir.join("refs/heads").display()
- );
- println!("cargo:rerun-if-changed={}", git_dir.join("index").display());
- }
-
- let build_time_unix = SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .map(|d| d.as_secs())
- .expect("system time before unix epoch");
- println!("cargo:rustc-env=BUILD_TIME_UNIX={}", build_time_unix);
-
- let rustc_bin = env::var("RUSTC").expect("missing required env var RUSTC");
- if let Ok(out) = Command::new(rustc_bin).arg("--version").output()
- && out.status.success()
- && let Ok(ver) = String::from_utf8(out.stdout)
- {
- println!("cargo:rustc-env=RUSTC_VERSION={}", ver.trim());
- }
-
- let git_hash = Command::new("git")
- .args(["rev-parse", "--short=12", "HEAD"])
- .output()
- .ok()
- .and_then(|o| {
- if o.status.success() {
- String::from_utf8(o.stdout).ok()
- } else {
- None
- }
- })
- .map(|s| s.trim().to_string());
-
- let dirty = Command::new("git")
- .args(["status", "--porcelain"])
- .output()
- .ok()
- .map(|o| o.status.success() && !o.stdout.is_empty())
- .unwrap_or(false);
-
- if let Some(mut h) = git_hash {
- if dirty {
- h.push_str("-dirty");
- }
- println!("cargo:rustc-env=GIT_HASH={}", h);
- }
-
- let profile = env::var("PROFILE").expect("missing required env var PROFILE");
- println!("cargo:rustc-env=PROFILE={}", profile);
-}
diff --git a/crates/net/src/builder.rs b/crates/net/src/builder.rs
@@ -1,53 +0,0 @@
-use crate::config::NetConfig;
-use crate::error::Result;
-use crate::{Net, NetHandle};
-
-#[derive(Debug, Clone, Default)]
-pub struct NetBuilder {
- config: NetConfig,
- manage_runtime: bool,
-}
-
-impl NetBuilder {
- pub fn new() -> Self {
- Self::default()
- }
-
- pub fn config(mut self, cfg: NetConfig) -> Self {
- self.config = cfg;
- self
- }
-
- pub fn manage_runtime(mut self, yes: bool) -> Self {
- self.manage_runtime = yes;
- self
- }
-
- pub fn build(self) -> Result<NetHandle> {
- let mut _net = Net::new(self.config.clone());
-
- #[cfg(feature = "rt")]
- if self.manage_runtime {
- _net.init_managed_runtime(None)?;
- }
-
- Ok(NetHandle::from_inner(_net))
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::NetBuilder;
-
- #[test]
- fn manage_runtime_path_is_callable() {
- let cfg = crate::config::NetConfig::default();
- let handle = NetBuilder::new()
- .config(cfg)
- .manage_runtime(true)
- .build()
- .expect("build net handle");
- let guard = handle.lock();
- assert!(guard.is_ok());
- }
-}
diff --git a/crates/net/src/config.rs b/crates/net/src/config.rs
@@ -1,20 +0,0 @@
-use serde::{Deserialize, Serialize};
-use std::path::PathBuf;
-
-#[derive(Debug, Clone, Default, Serialize, Deserialize)]
-pub struct NetConfig {}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub enum KeyFormat {
- Json,
- Nsec,
- Hex,
- Bin,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct KeyPersistenceConfig {
- pub path: Option<PathBuf>,
- pub format: KeyFormat,
- pub no_overwrite: bool,
-}
diff --git a/crates/net/src/error.rs b/crates/net/src/error.rs
@@ -1,72 +0,0 @@
-use alloc::string::String;
-use thiserror::Error;
-
-#[derive(Debug, Error)]
-pub enum NetError {
- #[error("{0}")]
- Msg(String),
-
- #[error("mutex lock poisoned")]
- Poisoned,
-
- #[cfg(feature = "std")]
- #[error("I/O error: {0}")]
- Io(#[from] std::io::Error),
-
- #[error("missing key")]
- MissingKey,
-
- #[error("invalid hex32")]
- InvalidHex32,
-
- #[error("invalid bech32")]
- InvalidBech32,
-
- #[error("invalid key file")]
- InvalidKeyFile,
-
- #[error("key I/O")]
- KeyIo,
-
- #[error("overwrite denied")]
- OverwriteDenied,
-
- #[error("persistence path required")]
- PersistencePathRequired,
-
- #[error("persistence unsupported")]
- PersistenceUnsupported,
-
- #[error("logging init failed: {0}")]
- LoggingInit(&'static str),
-}
-
-impl NetError {
- pub fn msg<M: Into<String>>(msg: M) -> Self {
- NetError::Msg(msg.into())
- }
-}
-
-impl Clone for NetError {
- fn clone(&self) -> Self {
- match self {
- NetError::Msg(m) => NetError::Msg(m.clone()),
- NetError::Poisoned => NetError::Poisoned,
- #[cfg(feature = "std")]
- NetError::Io(_) => {
- panic!("cannot clone std::io::Error");
- }
- NetError::MissingKey => NetError::MissingKey,
- NetError::InvalidHex32 => NetError::InvalidHex32,
- NetError::InvalidBech32 => NetError::InvalidBech32,
- NetError::InvalidKeyFile => NetError::InvalidKeyFile,
- NetError::KeyIo => NetError::KeyIo,
- NetError::OverwriteDenied => NetError::OverwriteDenied,
- NetError::PersistencePathRequired => NetError::PersistencePathRequired,
- NetError::PersistenceUnsupported => NetError::PersistenceUnsupported,
- NetError::LoggingInit(s) => NetError::LoggingInit(s),
- }
- }
-}
-
-pub type Result<T> = core::result::Result<T, NetError>;
diff --git a/crates/net/src/keys.rs b/crates/net/src/keys.rs
@@ -1,350 +0,0 @@
-#[cfg(feature = "nostr-client")]
-use crate::config::{KeyFormat, KeyPersistenceConfig};
-#[cfg(feature = "nostr-client")]
-use crate::error::{NetError, Result};
-#[cfg(feature = "nostr-client")]
-use nostr::nips::nip19::ToBech32 as RadrootsNostrToBech32;
-use nostr::secp256k1::SecretKey as RadrootsNostrSecp256k1SecretKey;
-use nostr::{Keys as RadrootsNostrKeys, SecretKey as RadrootsNostrSecretKey};
-#[cfg(feature = "nostr-client")]
-use serde::Deserialize;
-#[cfg(feature = "nostr-client")]
-use std::path::{Path, PathBuf};
-#[cfg(feature = "nostr-client")]
-use std::str::FromStr;
-
-#[cfg(feature = "nostr-client")]
-#[derive(Debug, Clone, Deserialize, serde::Serialize)]
-#[serde(deny_unknown_fields)]
-struct KeysFile {
- pub key: String,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub npub: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub created_at: Option<u64>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub note: Option<String>,
-}
-
-#[cfg(feature = "nostr-client")]
-#[derive(Debug, Clone, Default)]
-pub struct KeysState {
- pub loaded: bool,
- pub source: Option<PathBuf>,
- pub npub: Option<String>,
- pub last_error: Option<NetError>,
-}
-
-#[cfg(feature = "nostr-client")]
-#[derive(Debug, Clone, Default)]
-pub struct KeysManager {
- pub keys: Option<RadrootsNostrKeys>,
- pub state: KeysState,
-}
-
-#[cfg(feature = "nostr-client")]
-#[derive(Debug, Clone)]
-pub enum LoadOutcome {
- FromFile(PathBuf),
- GeneratedEphemeral,
-}
-
-#[cfg(feature = "nostr-client")]
-impl KeysManager {
- pub fn new() -> Self {
- Self::default()
- }
-
- pub fn is_valid_hex32(s: &str) -> bool {
- s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit())
- }
- pub fn is_valid_nsec(s: &str) -> bool {
- s.starts_with("nsec1")
- }
-
- pub fn load_from_secret_bytes(&mut self, sk: &[u8; 32]) -> Result<()> {
- let secp = RadrootsNostrSecp256k1SecretKey::from_slice(&sk[..])
- .map_err(|_| NetError::InvalidHex32)?;
- let nostr_sk = RadrootsNostrSecretKey::from(secp);
- let keys = RadrootsNostrKeys::new(nostr_sk);
- self.set_keys(keys);
- Ok(())
- }
-
- pub fn load_from_hex32(&mut self, hex: &str) -> Result<()> {
- use secrecy::{ExposeSecret, SecretString};
- let secret = SecretString::new(hex.to_owned().into());
- let k = RadrootsNostrSecretKey::from_str(secret.expose_secret())
- .map_err(|_| NetError::InvalidHex32)?;
- let keys = RadrootsNostrKeys::new(k);
- self.set_keys(keys);
- Ok(())
- }
-
- pub fn load_from_nsec(&mut self, nsec: &str) -> Result<()> {
- use secrecy::{ExposeSecret, SecretString};
- let secret = SecretString::new(nsec.to_owned().into());
- let keys = RadrootsNostrKeys::parse(secret.expose_secret())
- .map_err(|_| NetError::InvalidBech32)?;
- self.set_keys(keys);
- Ok(())
- }
-
- pub fn set_keys(&mut self, keys: RadrootsNostrKeys) {
- let npub = keys.public_key().to_bech32().ok();
- self.keys = Some(keys);
- self.state.loaded = true;
- self.state.source = None;
- self.state.npub = npub;
- self.state.last_error = None;
- }
-
- pub fn clear(&mut self) {
- *self = Self::default();
- }
-
- pub fn require(&self) -> Result<&RadrootsNostrKeys> {
- self.keys.as_ref().ok_or(NetError::MissingKey)
- }
-
- pub fn load_from_path_auto(&mut self, path: impl AsRef<Path>) -> Result<()> {
- let p = path.as_ref();
- if let Ok(s) = std::fs::read_to_string(p) {
- if let Ok(jf) = serde_json::from_str::<KeysFile>(&s) {
- return self.load_from_hex32(&jf.key).map(|_| {
- self.state.source = Some(p.to_path_buf());
- });
- }
- let trimmed = s.trim();
- if Self::is_valid_nsec(trimmed) {
- return self.load_from_nsec(trimmed).map(|_| {
- self.state.source = Some(p.to_path_buf());
- });
- }
- if Self::is_valid_hex32(trimmed) {
- return self.load_from_hex32(trimmed).map(|_| {
- self.state.source = Some(p.to_path_buf());
- });
- }
- }
- match std::fs::read(p) {
- Ok(bytes) if bytes.len() == 32 => {
- let mut arr = [0u8; 32];
- arr.copy_from_slice(&bytes);
- self.load_from_secret_bytes(&arr)?;
- self.state.source = Some(p.to_path_buf());
- Ok(())
- }
- _ => Err(NetError::InvalidKeyFile),
- }
- }
-
- pub fn load_from_file(&mut self, path: impl AsRef<Path>) -> Result<()> {
- self.load_from_path_auto(path)
- }
-
- pub fn save_to_path_with_format(
- &self,
- path: impl AsRef<Path>,
- format: KeyFormat,
- no_overwrite: bool,
- ) -> Result<()> {
- if no_overwrite && path.as_ref().exists() {
- return Err(NetError::OverwriteDenied);
- }
- match format {
- KeyFormat::Json => self.save_json(path),
- KeyFormat::Nsec => self.save_nsec_text(path, no_overwrite),
- KeyFormat::Hex => self.save_hex_text(path, no_overwrite),
- KeyFormat::Bin => self.save_raw_bin(path, no_overwrite),
- }
- }
-
- fn require_secret_hex(&self) -> Result<String> {
- let keys = self.require()?;
- Ok(keys.secret_key().to_secret_hex())
- }
-
- pub fn export_secret_hex(&self) -> Result<String> {
- self.require_secret_hex()
- }
-
- fn save_json(&self, path: impl AsRef<Path>) -> Result<()> {
- use std::time::{SystemTime, UNIX_EPOCH};
- let keys = self.require()?;
- let secret_hex = keys.secret_key().to_secret_hex();
- let payload = KeysFile {
- key: secret_hex,
- npub: self.npub(),
- created_at: SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .ok()
- .map(|d| d.as_secs()),
- note: None,
- };
- let json = serde_json::to_string_pretty(&payload).map_err(|_| NetError::KeyIo)?;
- write_secret_atomically_noclobber(path.as_ref(), json.as_bytes())
- .map_err(|_| NetError::KeyIo)?;
- Ok(())
- }
-
- fn save_nsec_text(&self, path: impl AsRef<Path>, no_overwrite: bool) -> Result<()> {
- if no_overwrite && path.as_ref().exists() {
- return Err(NetError::OverwriteDenied);
- }
- let keys = self.require()?;
- let nsec = keys.secret_key().to_bech32().map_err(|_| NetError::KeyIo)?;
- write_secret_atomically_noclobber(path.as_ref(), nsec.as_bytes())
- .map_err(|_| NetError::KeyIo)?;
- Ok(())
- }
-
- fn save_hex_text(&self, path: impl AsRef<Path>, no_overwrite: bool) -> Result<()> {
- if no_overwrite && path.as_ref().exists() {
- return Err(NetError::OverwriteDenied);
- }
- let hex = self.require_secret_hex()?;
- write_secret_atomically_noclobber(path.as_ref(), hex.as_bytes())
- .map_err(|_| NetError::KeyIo)?;
- Ok(())
- }
-
- fn save_raw_bin(&self, path: impl AsRef<Path>, no_overwrite: bool) -> Result<()> {
- if no_overwrite && path.as_ref().exists() {
- return Err(NetError::OverwriteDenied);
- }
- let hex = self.require_secret_hex()?;
- let mut out = [0u8; 32];
- hex::decode_to_slice(hex, &mut out).map_err(|_| NetError::KeyIo)?;
- write_secret_atomically_noclobber(path.as_ref(), &out).map_err(|_| NetError::KeyIo)?;
- Ok(())
- }
-
- pub fn generate_in_memory(&mut self) -> &RadrootsNostrKeys {
- let keys = RadrootsNostrKeys::generate();
- self.set_keys(keys);
- self.keys.as_ref().unwrap()
- }
-
- pub fn ensure_loaded_from_file_outcome(
- &mut self,
- path: impl AsRef<Path>,
- allow_generate: bool,
- ) -> Result<LoadOutcome> {
- let p = path.as_ref();
- if p.exists() {
- self.load_from_path_auto(p)?;
- return Ok(LoadOutcome::FromFile(p.to_path_buf()));
- }
- if !allow_generate {
- self.state.last_error = Some(NetError::MissingKey);
- return Err(NetError::MissingKey);
- }
- let _ = self.generate_in_memory();
- Ok(LoadOutcome::GeneratedEphemeral)
- }
-
- pub fn ensure_loaded_from_file(
- &mut self,
- path: impl AsRef<Path>,
- allow_generate: bool,
- ) -> Result<()> {
- let _ = self.ensure_loaded_from_file_outcome(path, allow_generate)?;
- Ok(())
- }
-
- pub fn npub(&self) -> Option<String> {
- self.state.npub.clone()
- }
-
- #[cfg(feature = "fs-persistence")]
- pub fn persist_with_config(&self, cfg: &KeyPersistenceConfig) -> Result<PathBuf> {
- let path = cfg.path.clone().ok_or(NetError::PersistencePathRequired)?;
- self.save_to_path_with_format(&path, cfg.format, cfg.no_overwrite)?;
- Ok(path)
- }
-
- #[cfg(not(feature = "fs-persistence"))]
- pub fn persist_with_config(&self, _cfg: &KeyPersistenceConfig) -> Result<PathBuf> {
- Err(NetError::PersistenceUnsupported)
- }
-}
-
-#[cfg(feature = "nostr-client")]
-fn write_secret_atomically_noclobber(path: &Path, data: &[u8]) -> crate::error::Result<()> {
- use std::io::Write;
- let dir = path.parent().unwrap_or_else(|| Path::new("."));
- std::fs::create_dir_all(dir)?;
-
- let mut tmp = tempfile::NamedTempFile::new_in(dir)?;
- tmp.write_all(data)?;
- tmp.flush()?;
-
- let persist_result = tmp.persist_noclobber(path);
-
- if let Err(e) = persist_result {
- if e.error.kind() == std::io::ErrorKind::AlreadyExists {
- return Err(crate::error::NetError::OverwriteDenied);
- } else {
- return Err(crate::error::NetError::KeyIo);
- }
- }
-
- #[cfg(unix)]
- {
- use std::fs::Permissions;
- use std::os::unix::fs::PermissionsExt;
- let _ = std::fs::set_permissions(path, Permissions::from_mode(0o600));
- }
-
- Ok(())
-}
-
-#[cfg(all(test, feature = "nostr-client", feature = "fs-persistence"))]
-mod tests {
- use tempfile::tempdir;
-
- use crate::{
- config::{KeyFormat, KeyPersistenceConfig},
- error::NetError,
- };
-
- use super::KeysManager;
-
- #[test]
- fn persist_with_config_requires_explicit_path() {
- let mut manager = KeysManager::new();
- let _ = manager.generate_in_memory();
- let cfg = KeyPersistenceConfig {
- path: None,
- format: KeyFormat::Json,
- no_overwrite: true,
- };
-
- let err = manager
- .persist_with_config(&cfg)
- .expect_err("implicit default persistence path should be rejected");
-
- assert!(matches!(err, NetError::PersistencePathRequired));
- }
-
- #[test]
- fn persist_with_config_writes_only_to_explicit_path() {
- let dir = tempdir().expect("tempdir");
- let path = dir.path().join("keys.json");
- let mut manager = KeysManager::new();
- let _ = manager.generate_in_memory();
- let cfg = KeyPersistenceConfig {
- path: Some(path.clone()),
- format: KeyFormat::Json,
- no_overwrite: true,
- };
-
- let written = manager
- .persist_with_config(&cfg)
- .expect("explicit persistence path should succeed");
-
- assert_eq!(written, path);
- assert!(written.exists());
- }
-}
diff --git a/crates/net/src/lib.rs b/crates/net/src/lib.rs
@@ -1,21 +0,0 @@
-#![doc(hidden)]
-#![cfg_attr(not(feature = "std"), no_std)]
-
-extern crate alloc;
-
-pub mod error;
-pub mod net;
-
-#[cfg(feature = "std")]
-pub mod logging;
-
-pub mod builder;
-pub mod config;
-
-#[cfg(feature = "nostr-client")]
-pub mod keys;
-
-#[cfg(feature = "nostr-client")]
-pub mod nostr_client;
-
-pub use net::{Net, NetHandle, NetInfo};
diff --git a/crates/net/src/logging.rs b/crates/net/src/logging.rs
@@ -1,84 +0,0 @@
-use crate::error::{NetError, Result};
-use std::path::PathBuf;
-use tracing::info;
-
-#[derive(Debug, Clone)]
-pub struct LoggingOptions {
- pub dir: Option<PathBuf>,
- pub file_name: String,
- pub also_stdout: bool,
-}
-
-impl Default for LoggingOptions {
- fn default() -> Self {
- Self {
- dir: None,
- file_name: "radroots_net.log".into(),
- also_stdout: true,
- }
- }
-}
-
-pub fn init_logging(opts: LoggingOptions) -> Result<()> {
- let file_path = opts
- .dir
- .as_ref()
- .map(|d| d.join(&opts.file_name).display().to_string())
- .unwrap_or_else(|| "<disabled>".into());
- let log_opts = radroots_log::LoggingOptions {
- dir: opts.dir.clone(),
- file_name: opts.file_name.clone(),
- stdout: opts.also_stdout,
- default_level: None,
- file_layout: radroots_log::LogFileLayout::StableFileName,
- ..radroots_log::LoggingOptions::default()
- };
- match radroots_log::init_logging(log_opts) {
- Ok(()) => {}
- Err(_) => return Err(NetError::LoggingInit("init")),
- }
- info!(
- "logging initialized (file: {}, stdout: {})",
- file_path, opts.also_stdout
- );
- Ok(())
-}
-
-#[cfg(test)]
-mod tests {
- use super::{LoggingOptions, init_logging};
- use crate::error::NetError;
- use std::path::PathBuf;
-
- #[test]
- fn logging_options_default_values_are_stable() {
- let defaults = LoggingOptions::default();
- assert_eq!(defaults.dir, None);
- assert_eq!(defaults.file_name, "radroots_net.log");
- assert!(defaults.also_stdout);
- }
-
- #[test]
- fn init_logging_covers_error_and_success_paths() {
- let invalid = init_logging(LoggingOptions {
- dir: Some(PathBuf::from("/dev/null/file")),
- file_name: "x.log".to_string(),
- also_stdout: false,
- });
- assert!(matches!(invalid, Err(NetError::LoggingInit("init"))));
-
- let valid_with_dir = init_logging(LoggingOptions {
- dir: Some(std::env::temp_dir().join("radroots_net-log-tests")),
- file_name: "ok.log".to_string(),
- also_stdout: false,
- });
- assert!(valid_with_dir.is_ok());
-
- let conflicting = init_logging(LoggingOptions {
- dir: None,
- file_name: "ok2.log".to_string(),
- also_stdout: true,
- });
- assert!(matches!(conflicting, Err(NetError::LoggingInit("init"))));
- }
-}
diff --git a/crates/net/src/net.rs b/crates/net/src/net.rs
@@ -1,248 +0,0 @@
-use serde::Serialize;
-use std::sync::{Arc, Mutex, MutexGuard};
-
-use crate::error::{NetError, Result};
-#[cfg(feature = "nostr-client")]
-use crate::nostr_client::{NostrClientManager, NostrConnectionSnapshot};
-#[cfg(feature = "nostr-client")]
-use radroots_nostr_accounts::prelude::RadrootsNostrAccountsManager;
-#[cfg(feature = "nostr-client")]
-use radroots_nostr_signer::prelude::RadrootsNostrSignerCapability;
-
-#[derive(Debug, Clone, Serialize)]
-pub struct BuildInfo {
- pub crate_name: &'static str,
- pub crate_version: &'static str,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub rustc: Option<&'static str>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub profile: Option<&'static str>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub git_sha: Option<&'static str>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub build_time_unix: Option<u64>,
-}
-
-#[derive(Debug, Clone, Serialize)]
-pub struct NetInfo {
- pub build: BuildInfo,
-}
-
-pub struct Net {
- pub info: NetInfo,
- pub config: crate::config::NetConfig,
-
- #[cfg(feature = "nostr-client")]
- pub accounts: RadrootsNostrAccountsManager,
-
- #[cfg(feature = "nostr-client")]
- pub signer: Option<RadrootsNostrSignerCapability>,
-
- #[cfg(feature = "nostr-client")]
- pub nostr: Option<NostrClientManager>,
-
- #[cfg(feature = "rt")]
- pub rt: Option<tokio::runtime::Runtime>,
-}
-
-impl Net {
- pub fn new(cfg: crate::config::NetConfig) -> Self {
- Self {
- info: NetInfo {
- build: BuildInfo {
- crate_name: env!("CARGO_PKG_NAME"),
- crate_version: env!("CARGO_PKG_VERSION"),
- rustc: option_env!("RUSTC_VERSION"),
- profile: option_env!("PROFILE"),
- git_sha: option_env!("GIT_HASH"),
- build_time_unix: option_env!("BUILD_TIME_UNIX").and_then(|s| s.parse().ok()),
- },
- },
- config: cfg,
- #[cfg(feature = "nostr-client")]
- accounts: RadrootsNostrAccountsManager::new_in_memory(),
- #[cfg(feature = "nostr-client")]
- signer: None,
- #[cfg(feature = "nostr-client")]
- nostr: None,
- #[cfg(feature = "rt")]
- rt: None,
- }
- }
-
- #[cfg(feature = "rt")]
- pub fn init_managed_runtime(&mut self, worker_threads: Option<usize>) -> Result<()> {
- if self.rt.is_some() {
- return Ok(());
- }
-
- let threads = worker_threads.unwrap_or_else(|| {
- std::thread::available_parallelism()
- .map(|n| n.get())
- .unwrap_or(1)
- .max(1)
- });
-
- let rt = tokio::runtime::Builder::new_multi_thread()
- .worker_threads(threads)
- .enable_all()
- .build()
- .map_err(|e| NetError::msg(format!("failed to build tokio runtime: {e}")))?;
-
- self.rt = Some(rt);
- Ok(())
- }
-
- #[cfg(feature = "nostr-client")]
- pub fn nostr_set_default_relays(&mut self, urls: &[String]) -> Result<()> {
- if self.nostr.is_none() {
- let keys = self.selected_nostr_keys().ok_or(NetError::MissingKey)?;
- let rt = self
- .rt
- .as_ref()
- .ok_or_else(|| NetError::msg("tokio runtime missing"))?;
- self.nostr = Some(NostrClientManager::new(keys, rt.handle().clone()));
- }
- if let Some(n) = &self.nostr {
- n.set_relays(urls);
- }
- Ok(())
- }
-
- #[cfg(feature = "nostr-client")]
- pub fn nostr_connect_if_key_present(&mut self) -> Result<()> {
- let Some(keys) = self.selected_nostr_keys() else {
- return Ok(());
- };
- let rt = self
- .rt
- .as_ref()
- .ok_or_else(|| NetError::msg("tokio runtime missing"))?;
- if self.nostr.is_none() {
- self.nostr = Some(NostrClientManager::new(keys, rt.handle().clone()));
- }
- if let Some(n) = &self.nostr {
- n.connect()?;
- }
- Ok(())
- }
-
- #[cfg(feature = "nostr-client")]
- pub fn nostr_connection_snapshot(&self) -> Option<NostrConnectionSnapshot> {
- self.nostr.as_ref().map(|n| n.snapshot())
- }
-
- #[cfg(feature = "nostr-client")]
- pub fn set_nostr_signer(&mut self, signer: Option<RadrootsNostrSignerCapability>) {
- self.signer = signer;
- }
-
- #[cfg(feature = "nostr-client")]
- pub fn selected_nostr_signer(&self) -> Option<RadrootsNostrSignerCapability> {
- self.signer
- .clone()
- .or_else(|| self.accounts.default_signer_capability().ok().flatten())
- }
-
- #[cfg(feature = "nostr-client")]
- pub fn selected_nostr_keys(&self) -> Option<nostr::Keys> {
- let signer = self.selected_nostr_signer()?;
- self.accounts
- .resolve_signing_keys_for_signer(&signer)
- .ok()
- .flatten()
- }
-}
-
-#[derive(Clone)]
-pub struct NetHandle(Arc<Mutex<Net>>);
-
-impl NetHandle {
- pub fn from_inner(inner: Net) -> Self {
- Self(Arc::new(Mutex::new(inner)))
- }
-
- pub fn lock(&self) -> Result<MutexGuard<'_, Net>> {
- self.0.lock().map_err(|_| NetError::Poisoned)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::builder::NetBuilder;
- #[cfg(feature = "nostr-client")]
- use nostr::Keys as RadrootsNostrKeys;
- #[cfg(feature = "nostr-client")]
- use radroots_identity::{PublicIdentity, PublicKey};
- #[cfg(feature = "nostr-client")]
- use radroots_nostr_signer::prelude::{
- RadrootsNostrRemoteSessionSignerCapability, RadrootsNostrSignerCapability,
- RadrootsNostrSignerConnectionId,
- };
-
- #[cfg(feature = "nostr-client")]
- fn public_identity() -> PublicIdentity {
- let keys = RadrootsNostrKeys::generate();
- PublicIdentity::new(
- PublicKey::from_hex(&keys.public_key().to_hex()).expect("public identity"),
- )
- }
-
- #[test]
- fn builds_minimal() {
- let cfg = crate::config::NetConfig::default();
- let handle = NetBuilder::new().config(cfg).build();
- assert!(handle.is_ok());
- }
-
- #[test]
- fn lock_is_ok() {
- let cfg = crate::config::NetConfig::default();
- let handle = NetBuilder::new().config(cfg).build().unwrap();
- let guard = handle.lock();
- assert!(guard.is_ok());
- }
-
- #[cfg(feature = "rt")]
- #[test]
- fn builds_with_managed_rt() {
- let cfg = crate::config::NetConfig::default();
- let handle = crate::builder::NetBuilder::new()
- .config(cfg)
- .manage_runtime(true)
- .build()
- .expect("build with runtime");
-
- let rt_present = handle.lock().unwrap().rt.is_some();
- assert!(rt_present);
- }
-
- #[cfg(feature = "nostr-client")]
- #[test]
- fn selected_nostr_keys_reflects_selected_signing_account() {
- let cfg = crate::config::NetConfig::default();
- let mut net = crate::Net::new(cfg);
- assert!(net.selected_nostr_keys().is_none());
- assert!(net.selected_nostr_signer().is_none());
-
- net.accounts
- .generate_keys(Some("primary".into()), true)
- .expect("generate account");
- assert!(net.selected_nostr_keys().is_some());
- assert!(net.selected_nostr_signer().is_some());
-
- let remote = RadrootsNostrSignerCapability::RemoteSession(Box::new(
- RadrootsNostrRemoteSessionSignerCapability::new(
- RadrootsNostrSignerConnectionId::new_v7(),
- public_identity(),
- public_identity(),
- ),
- ));
- net.set_nostr_signer(Some(remote.clone()));
- assert_eq!(net.selected_nostr_signer(), Some(remote));
- assert!(net.selected_nostr_keys().is_none());
-
- net.set_nostr_signer(None);
- assert!(net.selected_nostr_keys().is_some());
- }
-}
diff --git a/crates/net/src/nostr_client/connection.rs b/crates/net/src/nostr_client/connection.rs
@@ -1,46 +0,0 @@
-use crate::error::{NetError, Result};
-use tracing::error;
-
-use super::manager::NostrClientManager;
-
-impl NostrClientManager {
- pub fn set_relays(&self, urls: &[String]) {
- if let Ok(mut guard) = self.inner.relays.lock() {
- *guard = urls.to_vec();
- }
- }
-
- pub fn connect(&self) -> Result<()> {
- let inner = self.inner.clone();
- let urls = {
- let g = inner.relays.lock().ok();
- g.map(|v| v.clone()).unwrap_or_default()
- };
-
- if urls.is_empty() {
- if let Ok(mut e) = inner.last_error.lock() {
- *e = Some("no relays configured".to_string());
- }
- return Err(NetError::Msg("no relays configured".into()));
- }
-
- let inner_for_task = inner.clone();
- let rt = inner.rt.clone();
- rt.spawn(async move {
- for u in &urls {
- match inner_for_task.client.add_relay(u.as_str()).await {
- Ok(_) => {}
- Err(e) => {
- if let Ok(mut last) = inner_for_task.last_error.lock() {
- *last = Some(format!("add_relay {}: {}", u, e));
- }
- error!("add_relay failed for {}: {}", u, e);
- }
- }
- }
- inner_for_task.client.connect().await;
- });
-
- Ok(())
- }
-}
diff --git a/crates/net/src/nostr_client/events/fetch.rs b/crates/net/src/nostr_client/events/fetch.rs
@@ -1,31 +0,0 @@
-#![forbid(unsafe_code)]
-
-use crate::error::{NetError, Result};
-use radroots_nostr::event::Event as RadrootsNostrEvent;
-use radroots_nostr::filter::Filter as RadrootsNostrFilter;
-
-use crate::nostr_client::manager::NostrClientManager;
-
-impl NostrClientManager {
- pub async fn fetch_events(
- &self,
- filter: RadrootsNostrFilter,
- timeout: core::time::Duration,
- ) -> Result<Vec<RadrootsNostrEvent>> {
- self.inner
- .client
- .fetch_events(filter, timeout)
- .await
- .map_err(|error| NetError::Msg(error.to_string()))
- }
-
- pub fn fetch_events_blocking(
- &self,
- filter: RadrootsNostrFilter,
- timeout: core::time::Duration,
- ) -> Result<Vec<RadrootsNostrEvent>> {
- let rt = self.inner.rt.clone();
- let this = self.clone();
- rt.block_on(async move { this.fetch_events(filter, timeout).await })
- }
-}
diff --git a/crates/net/src/nostr_client/events/post.rs b/crates/net/src/nostr_client/events/post.rs
@@ -1,81 +0,0 @@
-use crate::error::{NetError, Result};
-use radroots_event::{post::AuthoredUpdate, post::reply::AuthoredNip10Reply};
-use radroots_event_codec::{parsed::RadrootsParsedData, post::decode::LegacyPost};
-use radroots_nostr::event::build_nip10_reply;
-use radroots_nostr::event::build_update;
-use radroots_nostr::event::post_filter;
-
-use crate::nostr_client::manager::NostrClientManager;
-
-impl NostrClientManager {
- pub async fn publish_update_event(&self, update: &AuthoredUpdate) -> Result<String> {
- let builder = build_update(update).map_err(|e| NetError::Msg(e.to_string()))?;
- let event = builder
- .sign_with_keys(&self.inner.keys)
- .map_err(|error| NetError::Msg(error.to_string()))?;
- let out = self
- .inner
- .client
- .send_event(&event)
- .await
- .map_err(|error| NetError::Msg(error.to_string()))?;
- Ok(out.val.to_string())
- }
-
- pub fn publish_update_event_blocking(&self, update: AuthoredUpdate) -> Result<String> {
- let rt = self.inner.rt.clone();
- let this = self.clone();
- rt.block_on(async move { this.publish_update_event(&update).await })
- }
-
- pub async fn publish_nip10_reply_event(&self, reply: &AuthoredNip10Reply) -> Result<String> {
- let builder = build_nip10_reply(reply).map_err(|e| NetError::Msg(e.to_string()))?;
- let event = builder
- .sign_with_keys(&self.inner.keys)
- .map_err(|error| NetError::Msg(error.to_string()))?;
- let out = self
- .inner
- .client
- .send_event(&event)
- .await
- .map_err(|error| NetError::Msg(error.to_string()))?;
-
- Ok(out.val.to_string())
- }
-
- pub fn publish_nip10_reply_event_blocking(&self, reply: AuthoredNip10Reply) -> Result<String> {
- let rt = self.inner.rt.clone();
- let this = self.clone();
- rt.block_on(async move { this.publish_nip10_reply_event(&reply).await })
- }
-
- /// Fetches generic kind-1 compatibility projections without claiming
- /// Radroots product-profile admission.
- pub async fn fetch_post_events(
- &self,
- limit: u16,
- since_unix: Option<u64>,
- ) -> Result<Vec<RadrootsParsedData<LegacyPost>>> {
- let filter = post_filter(Some(limit), since_unix);
- let events = self
- .inner
- .client
- .fetch_events(filter, core::time::Duration::from_secs(10))
- .await
- .map_err(|error| NetError::Msg(error.to_string()))?;
- Ok(events
- .into_iter()
- .map(|event| radroots_nostr::event::to_post_event_metadata(&event))
- .collect())
- }
-
- pub fn fetch_post_events_blocking(
- &self,
- limit: u16,
- since_unix: Option<u64>,
- ) -> Result<Vec<RadrootsParsedData<LegacyPost>>> {
- let rt = self.inner.rt.clone();
- let this = self.clone();
- rt.block_on(async move { this.fetch_post_events(limit, since_unix).await })
- }
-}
diff --git a/crates/net/src/nostr_client/events/profile.rs b/crates/net/src/nostr_client/events/profile.rs
@@ -1,54 +0,0 @@
-use crate::error::{NetError, Result};
-use nostr::PublicKey as RadrootsNostrPublicKey;
-use radroots_event_codec::parsed::RadrootsParsedData;
-use radroots_event_codec::profile::RadrootsProfileData;
-use radroots_nostr::event::Kind as RadrootsNostrKind;
-use radroots_nostr::filter::Filter as RadrootsNostrFilter;
-
-use crate::nostr_client::manager::NostrClientManager;
-
-impl NostrClientManager {
- pub async fn fetch_profile_event(
- &self,
- author: RadrootsNostrPublicKey,
- ) -> Result<Option<RadrootsParsedData<RadrootsProfileData>>> {
- let filter = RadrootsNostrFilter::new()
- .authors(vec![author])
- .kind(RadrootsNostrKind::Metadata);
- let stored = self
- .inner
- .client
- .query_database(filter.clone())
- .await
- .map_err(|error| NetError::Msg(error.to_string()))?;
- let fetched = self
- .inner
- .client
- .fetch_events(filter, core::time::Duration::from_secs(5))
- .await
- .map_err(|error| NetError::Msg(error.to_string()))?;
- let ev = stored
- .into_iter()
- .chain(fetched)
- .filter(|event| event.kind == RadrootsNostrKind::Metadata)
- .max_by_key(|event| event.created_at);
- if let Some(e) = ev {
- if let Some(meta) = radroots_nostr::event::to_profile_event_metadata(&e) {
- return Ok(Some(meta));
- }
- return Err(NetError::Msg(
- "failed to parse kind:0 metadata content".to_string(),
- ));
- }
- Ok(None)
- }
-
- pub fn fetch_profile_event_blocking(
- &self,
- author: RadrootsNostrPublicKey,
- ) -> Result<Option<RadrootsParsedData<RadrootsProfileData>>> {
- let rt = self.inner.rt.clone();
- let this = self.clone();
- rt.block_on(async move { this.fetch_profile_event(author).await })
- }
-}
diff --git a/crates/net/src/nostr_client/inner.rs b/crates/net/src/nostr_client/inner.rs
@@ -1,45 +0,0 @@
-use std::collections::HashMap;
-use std::sync::{Arc, Mutex};
-
-use nostr::Keys as RadrootsNostrKeys;
-use radroots_event_codec::{parsed::RadrootsParsedData, post::decode::LegacyPost};
-use radroots_transport_nostr::{
- RadrootsNostrClient, RadrootsNostrClientKey, RadrootsNostrMonitor, RadrootsNostrRelayStatus,
- RelayUrl,
-};
-use tokio::runtime::Handle;
-use tokio::sync::broadcast;
-use tokio::task::JoinHandle;
-
-pub(super) struct Inner {
- pub client: RadrootsNostrClient,
- pub keys: RadrootsNostrKeys,
- pub relays: Arc<Mutex<Vec<String>>>,
- pub statuses: Arc<Mutex<HashMap<RelayUrl, RadrootsNostrRelayStatus>>>,
- pub last_error: Arc<Mutex<Option<String>>>,
- pub rt: Handle,
- pub post_events_tx: broadcast::Sender<RadrootsParsedData<LegacyPost>>,
- pub post_events_stream: Arc<Mutex<Option<JoinHandle<()>>>>,
-}
-
-impl Inner {
- pub fn new(keys: RadrootsNostrKeys, rt: Handle) -> Arc<Self> {
- let monitor = RadrootsNostrMonitor::new(2048);
- let client_key =
- RadrootsNostrClientKey::from_secret_key_bytes(keys.secret_key().to_secret_bytes())
- .expect("an existing Nostr key remains valid at the transport boundary");
- let client = RadrootsNostrClient::new_with_monitor(client_key, monitor);
- let (tx, _) = broadcast::channel(2048);
-
- Arc::new(Self {
- client,
- keys,
- relays: Arc::new(Mutex::new(Vec::new())),
- statuses: Arc::new(Mutex::new(HashMap::new())),
- last_error: Arc::new(Mutex::new(None)),
- rt,
- post_events_tx: tx,
- post_events_stream: Arc::new(Mutex::new(None)),
- })
- }
-}
diff --git a/crates/net/src/nostr_client/manager.rs b/crates/net/src/nostr_client/manager.rs
@@ -1,93 +0,0 @@
-use std::sync::Arc;
-use tokio::runtime::Handle;
-
-use super::inner::Inner;
-use nostr::Keys as RadrootsNostrKeys;
-use radroots_nostr::event::Timestamp as RadrootsNostrTimestamp;
-use radroots_nostr::event::post_filter;
-
-#[derive(Clone)]
-pub struct NostrClientManager {
- pub(super) inner: Arc<Inner>,
-}
-
-impl NostrClientManager {
- pub fn new(keys: RadrootsNostrKeys, rt: Handle) -> Self {
- let inner = Inner::new(keys, rt);
- let this = Self {
- inner: inner.clone(),
- };
- this.spawn_status_watcher();
- this
- }
-}
-
-impl NostrClientManager {
- pub fn start_post_event_stream(&self, since_unix: Option<u64>) {
- if self
- .inner
- .post_events_stream
- .lock()
- .ok()
- .is_some_and(|h| h.is_some())
- {
- return;
- }
-
- let inner = self.inner.clone();
- let rt = inner.rt.clone();
- let handle = rt.spawn({
- let inner = inner.clone();
- async move {
- use futures::StreamExt;
-
- let mut since =
- since_unix.unwrap_or_else(|| RadrootsNostrTimestamp::now().as_secs());
- loop {
- let filter = post_filter(None, Some(since));
-
- let mut stream = match inner
- .client
- .stream_events(filter, core::time::Duration::from_secs(30))
- .await
- {
- Ok(s) => s,
- Err(_) => {
- tokio::time::sleep(core::time::Duration::from_secs(2)).await;
- continue;
- }
- };
-
- while let Some(event) = stream.next().await {
- let meta = radroots_nostr::event::to_post_event_metadata(&event);
- let ts = event.created_at.as_secs();
- since = ts.saturating_add(1);
- let _ = inner.post_events_tx.send(meta);
- }
- }
- }
- });
-
- if let Ok(mut g) = self.inner.post_events_stream.lock() {
- *g = Some(handle);
- }
- }
-
- pub fn stop_post_event_stream(&self) {
- if let Ok(mut g) = self.inner.post_events_stream.lock()
- && let Some(h) = g.take()
- {
- h.abort();
- }
- }
-
- pub fn subscribe_post_events(
- &self,
- ) -> tokio::sync::broadcast::Receiver<
- radroots_event_codec::parsed::RadrootsParsedData<
- radroots_event_codec::post::decode::LegacyPost,
- >,
- > {
- self.inner.post_events_tx.subscribe()
- }
-}
diff --git a/crates/net/src/nostr_client/mod.rs b/crates/net/src/nostr_client/mod.rs
@@ -1,13 +0,0 @@
-mod connection;
-mod inner;
-mod manager;
-mod events {
- mod fetch;
- mod post;
- mod profile;
-}
-mod status;
-pub mod types;
-
-pub use manager::NostrClientManager;
-pub use types::{Light, NostrConnectionSnapshot};
diff --git a/crates/net/src/nostr_client/status.rs b/crates/net/src/nostr_client/status.rs
@@ -1,75 +0,0 @@
-use radroots_transport_nostr::{RadrootsNostrMonitorNotification, RadrootsNostrRelayStatus};
-use tracing::{info, warn};
-
-use super::manager::NostrClientManager;
-use super::types::{Light, NostrConnectionSnapshot};
-
-impl NostrClientManager {
- pub(super) fn spawn_status_watcher(&self) {
- let inner = self.inner.clone();
- let rt = inner.rt.clone();
- let inner_for_task = inner.clone();
-
- rt.spawn(async move {
- if let Some(monitor) = inner_for_task.client.monitor() {
- let mut rx = monitor.subscribe();
- while let Ok(notification) = rx.recv().await {
- match notification {
- RadrootsNostrMonitorNotification::StatusChanged { relay_url, status } => {
- if let Ok(mut map) = inner_for_task.statuses.lock() {
- map.insert(relay_url.clone(), status);
- } else if let Ok(mut last) = inner_for_task.last_error.lock() {
- *last = Some("status watcher: statuses mutex poisoned".to_string());
- warn!(
- "status watcher: statuses mutex poisoned; dropping update for {}",
- relay_url
- );
- continue;
- }
-
- info!("relay status changed {} -> {:?}", relay_url, status);
- }
- }
- }
- }
- });
- }
-
- pub fn snapshot(&self) -> NostrConnectionSnapshot {
- let map = self
- .inner
- .statuses
- .lock()
- .ok()
- .map(|g| g.clone())
- .unwrap_or_default();
-
- let mut connected = 0usize;
- let mut connecting = 0usize;
-
- for st in map.values() {
- match st {
- RadrootsNostrRelayStatus::Connected => connected += 1,
- RadrootsNostrRelayStatus::Connecting => connecting += 1,
- _ => {}
- }
- }
-
- let light = if connected > 0 {
- Light::Green
- } else if connecting > 0 {
- Light::Yellow
- } else {
- Light::Red
- };
-
- let last_error = self.inner.last_error.lock().ok().and_then(|e| e.clone());
-
- NostrConnectionSnapshot {
- light,
- connected,
- connecting,
- last_error,
- }
- }
-}
diff --git a/crates/net/src/nostr_client/types.rs b/crates/net/src/nostr_client/types.rs
@@ -1,14 +0,0 @@
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Light {
- Red,
- Yellow,
- Green,
-}
-
-#[derive(Debug, Clone)]
-pub struct NostrConnectionSnapshot {
- pub light: Light,
- pub connected: usize,
- pub connecting: usize,
- pub last_error: Option<String>,
-}
diff --git a/crates/net/tests/error.rs b/crates/net/tests/error.rs
@@ -1,38 +0,0 @@
-use radroots_net::error::NetError;
-use std::panic::{AssertUnwindSafe, catch_unwind};
-
-#[test]
-fn msg_constructor_creates_msg_variant() {
- let err = NetError::msg("hello");
- assert!(matches!(err, NetError::Msg(ref message) if message == "hello"));
-}
-
-#[test]
-fn clone_covers_non_io_variants() {
- let variants = [
- NetError::msg("hello"),
- NetError::Poisoned,
- NetError::MissingKey,
- NetError::InvalidHex32,
- NetError::InvalidBech32,
- NetError::InvalidKeyFile,
- NetError::KeyIo,
- NetError::OverwriteDenied,
- NetError::PersistencePathRequired,
- NetError::PersistenceUnsupported,
- NetError::LoggingInit("init"),
- ];
- for variant in variants {
- let cloned = variant.clone();
- assert_eq!(format!("{variant}"), format!("{cloned}"));
- }
-}
-
-#[test]
-fn clone_panics_for_io_variant() {
- let io_err = NetError::Io(std::io::Error::other("io"));
- let result = catch_unwind(AssertUnwindSafe(|| {
- let _ = io_err.clone();
- }));
- assert!(result.is_err());
-}
diff --git a/crates/nostr_runtime/Cargo.toml b/crates/nostr_runtime/Cargo.toml
@@ -1,37 +0,0 @@
-[package]
-name = "radroots_nostr_runtime"
-publish = false
-version = "0.1.0-alpha"
-edition.workspace = true
-authors = ["Tyson Lupul <tyson@radroots.org>"]
-rust-version.workspace = true
-license.workspace = true
-description = "Nostr runtime layer for Radroots"
-repository.workspace = true
-homepage.workspace = true
-readme = "README"
-
-[package.metadata.radroots.compatibility]
-status = "publish_frozen"
-replacement = "radroots_transport_nostr + radroots_sync"
-deviation = "RCRV1-DEV-010"
-removal_step = 215
-new_consumers_forbidden = true
-
-[features]
-default = ["std"]
-std = []
-rt = ["std", "dep:futures", "dep:tokio"]
-nostr-client = ["std", "dep:nostr", "dep:radroots_nostr", "dep:radroots_transport_nostr"]
-nostrdb = ["nostr-client"]
-
-[dependencies]
-radroots_nostr = { workspace = true, optional = true, default-features = true }
-radroots_transport_nostr = { workspace = true, optional = true, default-features = false }
-nostr = { workspace = true, optional = true, features = ["std"] }
-futures = { workspace = true, optional = true }
-thiserror = { workspace = true }
-tokio = { workspace = true, optional = true, features = ["rt", "sync", "time"] }
-
-[dev-dependencies]
-tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] }
diff --git a/crates/nostr_runtime/README b/crates/nostr_runtime/README
@@ -1,39 +0,0 @@
-# radroots_nostr_runtime
-
-`radroots_nostr_runtime` is a private, publication-frozen predecessor package.
-Its former client/runtime integration is no longer active by default and must
-not receive new consumers. Final relay I/O belongs to
-`radroots_transport_nostr`; final source/sink orchestration belongs to
-`radroots_sync`.
-
-## Compatibility quarantine
-
-The private `radroots_nostrdb` runtime adapter still resolves the predecessor
-sink types behind an opt-in, non-default feature. `RCRV1-DEV-010` therefore
-retains this documentation-hidden, `publish = false` package only until the
-sync-orchestration cutover. Step 215 removes the package and the adapter after
-their behavior is owned by `radroots_sync`.
-
-Do not add consumers, features, public contracts, or runtime behavior here.
-The remaining opt-in source is also governed by `RCRV1-DEV-007` and cannot
-enter a release feature closure.
-
-## Overview
-
- * runtime builder, runtime events, and long-lived connection state types;
- * subscription specifications, policies, handles, and traffic-light status
- models;
- * sink abstractions and snapshot types used by higher-level network
- orchestration;
- * quarantined feature-gated predecessor integration retained only for the
- governed removal gate.
-
-## Copyright
-
-Except as otherwise noted, all files in the `radroots_nostr_runtime`
-distribution are
-
- Copyright (c) 2025 Tyson Lupul
-
-For information on usage and redistribution, and for a DISCLAIMER OF ALL
-WARRANTIES, see LICENSE included in the `radroots_nostr_runtime` distribution.
diff --git a/crates/nostr_runtime/src/error.rs b/crates/nostr_runtime/src/error.rs
@@ -1,69 +0,0 @@
-use thiserror::Error;
-
-#[derive(Debug, Error)]
-pub enum RadrootsNostrRuntimeError {
- #[error("runtime not started")]
- RuntimeNotStarted,
-
- #[error("runtime already started")]
- RuntimeAlreadyStarted,
-
- #[error("runtime shutdown")]
- RuntimeShutdown,
-
- #[error("missing required runtime configuration: {0}")]
- MissingConfig(&'static str),
-
- #[error("invalid runtime configuration: {0}")]
- InvalidConfig(&'static str),
-
- #[error("nostr client error: {0}")]
- Client(String),
-
- #[error("subscription not found: {0}")]
- SubscriptionNotFound(String),
-
- #[error("runtime error: {0}")]
- Runtime(String),
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn error_variants_render_messages() {
- assert_eq!(
- RadrootsNostrRuntimeError::RuntimeNotStarted.to_string(),
- "runtime not started"
- );
- assert_eq!(
- RadrootsNostrRuntimeError::RuntimeAlreadyStarted.to_string(),
- "runtime already started"
- );
- assert_eq!(
- RadrootsNostrRuntimeError::RuntimeShutdown.to_string(),
- "runtime shutdown"
- );
- assert_eq!(
- RadrootsNostrRuntimeError::MissingConfig("keys").to_string(),
- "missing required runtime configuration: keys"
- );
- assert_eq!(
- RadrootsNostrRuntimeError::InvalidConfig("queue_capacity").to_string(),
- "invalid runtime configuration: queue_capacity"
- );
- assert_eq!(
- RadrootsNostrRuntimeError::Client("client failure".into()).to_string(),
- "nostr client error: client failure"
- );
- assert_eq!(
- RadrootsNostrRuntimeError::SubscriptionNotFound("sub-1".into()).to_string(),
- "subscription not found: sub-1"
- );
- assert_eq!(
- RadrootsNostrRuntimeError::Runtime("runtime failure".into()).to_string(),
- "runtime error: runtime failure"
- );
- }
-}
diff --git a/crates/nostr_runtime/src/lib.rs b/crates/nostr_runtime/src/lib.rs
@@ -1,26 +0,0 @@
-#![doc(hidden)]
-#![cfg_attr(not(feature = "std"), no_std)]
-#![forbid(unsafe_code)]
-
-extern crate alloc;
-
-pub mod error;
-pub mod types;
-
-#[cfg(all(feature = "nostr-client", feature = "rt"))]
-pub mod runtime;
-#[cfg(all(feature = "nostr-client", feature = "rt"))]
-pub mod sink;
-
-pub mod prelude {
- pub use crate::error::RadrootsNostrRuntimeError;
- #[cfg(all(feature = "nostr-client", feature = "rt"))]
- pub use crate::runtime::{RadrootsNostrRuntime, RadrootsNostrRuntimeBuilder};
- #[cfg(all(feature = "nostr-client", feature = "rt"))]
- pub use crate::sink::{RadrootsNostrEventSink, RadrootsNostrInMemoryEventSink};
- pub use crate::types::{
- RadrootsNostrConnectionSnapshot, RadrootsNostrRuntimeEvent,
- RadrootsNostrSubscriptionHandle, RadrootsNostrSubscriptionPolicy,
- RadrootsNostrSubscriptionSpec, RadrootsNostrTrafficLight,
- };
-}
diff --git a/crates/nostr_runtime/src/runtime.rs b/crates/nostr_runtime/src/runtime.rs
@@ -1,572 +0,0 @@
-use crate::error::RadrootsNostrRuntimeError;
-use crate::sink::RadrootsNostrEventSink;
-use crate::types::{
- RadrootsNostrConnectionSnapshot, RadrootsNostrRuntimeEvent, RadrootsNostrSubscriptionHandle,
- RadrootsNostrSubscriptionPolicy, RadrootsNostrSubscriptionSpec, RadrootsNostrTrafficLight,
-};
-use alloc::string::{String, ToString};
-use alloc::sync::Arc;
-use alloc::vec::Vec;
-use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
-use core::time::Duration;
-use futures::StreamExt;
-use radroots_nostr::event::Timestamp as RadrootsNostrTimestamp;
-use radroots_transport_nostr::{
- RadrootsNostrClient, RadrootsNostrClientKey, RadrootsNostrMonitor,
- RadrootsNostrMonitorNotification, RadrootsNostrRelayStatus, RelayUrl,
-};
-use std::collections::HashMap;
-use std::sync::Mutex;
-use tokio::sync::mpsc;
-use tokio::task::JoinHandle;
-
-pub struct RadrootsNostrRuntimeBuilder {
- keys: Option<RadrootsNostrClientKey>,
- relays: Vec<String>,
- queue_capacity: usize,
- monitor_capacity: usize,
- event_sink: Option<Arc<dyn RadrootsNostrEventSink>>,
-}
-
-impl RadrootsNostrRuntimeBuilder {
- pub const DEFAULT_QUEUE_CAPACITY: usize = 2_048;
- pub const DEFAULT_MONITOR_CAPACITY: usize = 2_048;
-
- pub fn new() -> Self {
- Self {
- keys: None,
- relays: Vec::new(),
- queue_capacity: Self::DEFAULT_QUEUE_CAPACITY,
- monitor_capacity: Self::DEFAULT_MONITOR_CAPACITY,
- event_sink: None,
- }
- }
-
- pub fn keys(mut self, keys: RadrootsNostrClientKey) -> Self {
- self.keys = Some(keys);
- self
- }
-
- pub fn relays(mut self, relays: Vec<String>) -> Self {
- self.relays = relays;
- self
- }
-
- pub fn add_relay(mut self, relay: impl Into<String>) -> Self {
- self.relays.push(relay.into());
- self
- }
-
- pub fn queue_capacity(mut self, capacity: usize) -> Self {
- self.queue_capacity = capacity;
- self
- }
-
- pub fn monitor_capacity(mut self, capacity: usize) -> Self {
- self.monitor_capacity = capacity;
- self
- }
-
- pub fn event_sink(mut self, sink: Arc<dyn RadrootsNostrEventSink>) -> Self {
- self.event_sink = Some(sink);
- self
- }
-
- pub fn build(self) -> Result<RadrootsNostrRuntime, RadrootsNostrRuntimeError> {
- let keys = self
- .keys
- .ok_or(RadrootsNostrRuntimeError::MissingConfig("keys"))?;
- if self.relays.is_empty() {
- return Err(RadrootsNostrRuntimeError::MissingConfig("relays"));
- }
- if self.queue_capacity == 0 {
- return Err(RadrootsNostrRuntimeError::InvalidConfig("queue_capacity"));
- }
- if self.monitor_capacity == 0 {
- return Err(RadrootsNostrRuntimeError::InvalidConfig("monitor_capacity"));
- }
-
- let monitor = RadrootsNostrMonitor::new(self.monitor_capacity);
- let client = RadrootsNostrClient::new_with_monitor(keys, monitor);
- let (queue_tx, queue_rx) = mpsc::channel(self.queue_capacity);
-
- let inner = Arc::new(RadrootsNostrRuntimeInner {
- client,
- relays: Mutex::new(self.relays),
- queue_tx,
- queue_rx: Mutex::new(queue_rx),
- statuses: Mutex::new(HashMap::new()),
- last_error: Mutex::new(None),
- monitor_task: Mutex::new(None),
- subscription_tasks: Mutex::new(HashMap::new()),
- started: AtomicBool::new(false),
- shutting_down: AtomicBool::new(false),
- next_subscription_id: AtomicU64::new(1),
- event_sink: self.event_sink,
- });
-
- Ok(RadrootsNostrRuntime { inner })
- }
-}
-
-impl Default for RadrootsNostrRuntimeBuilder {
- fn default() -> Self {
- Self::new()
- }
-}
-
-#[derive(Clone)]
-pub struct RadrootsNostrRuntime {
- inner: Arc<RadrootsNostrRuntimeInner>,
-}
-
-struct RadrootsNostrRuntimeInner {
- client: RadrootsNostrClient,
- relays: Mutex<Vec<String>>,
- queue_tx: mpsc::Sender<RadrootsNostrRuntimeEvent>,
- queue_rx: Mutex<mpsc::Receiver<RadrootsNostrRuntimeEvent>>,
- statuses: Mutex<HashMap<RelayUrl, RadrootsNostrRelayStatus>>,
- last_error: Mutex<Option<String>>,
- monitor_task: Mutex<Option<JoinHandle<()>>>,
- subscription_tasks: Mutex<HashMap<String, JoinHandle<()>>>,
- started: AtomicBool,
- shutting_down: AtomicBool,
- next_subscription_id: AtomicU64,
- event_sink: Option<Arc<dyn RadrootsNostrEventSink>>,
-}
-
-impl RadrootsNostrRuntime {
- pub async fn start(&self) -> Result<(), RadrootsNostrRuntimeError> {
- if self.inner.started.swap(true, Ordering::SeqCst) {
- return Err(RadrootsNostrRuntimeError::RuntimeAlreadyStarted);
- }
- self.inner.shutting_down.store(false, Ordering::SeqCst);
-
- let relays = self.relays();
- for relay in relays {
- if let Err(source) = self.inner.client.add_relay(relay.as_str()).await {
- let message = source.to_string();
- self.record_error(message.clone());
- let _ = self
- .inner
- .queue_tx
- .send(RadrootsNostrRuntimeEvent::Error { message })
- .await;
- }
- }
-
- self.spawn_monitor_watcher();
- self.inner.client.connect().await;
- let _ = self
- .inner
- .queue_tx
- .send(RadrootsNostrRuntimeEvent::RuntimeStarted)
- .await;
-
- Ok(())
- }
-
- pub async fn shutdown(&self) -> Result<(), RadrootsNostrRuntimeError> {
- if !self.inner.started.swap(false, Ordering::SeqCst) {
- return Err(RadrootsNostrRuntimeError::RuntimeNotStarted);
- }
- self.inner.shutting_down.store(true, Ordering::SeqCst);
-
- if let Ok(mut guard) = self.inner.subscription_tasks.lock() {
- for (_, handle) in guard.drain() {
- handle.abort();
- }
- }
-
- if let Ok(mut guard) = self.inner.monitor_task.lock()
- && let Some(handle) = guard.take()
- {
- handle.abort();
- }
-
- let _ = self
- .inner
- .queue_tx
- .send(RadrootsNostrRuntimeEvent::RuntimeStopped)
- .await;
-
- Ok(())
- }
-
- pub async fn subscribe(
- &self,
- spec: RadrootsNostrSubscriptionSpec,
- ) -> Result<RadrootsNostrSubscriptionHandle, RadrootsNostrRuntimeError> {
- if !self.inner.started.load(Ordering::SeqCst) {
- return Err(RadrootsNostrRuntimeError::RuntimeNotStarted);
- }
-
- let sequence = self
- .inner
- .next_subscription_id
- .fetch_add(1, Ordering::SeqCst);
- let id = alloc::format!("sub-{sequence}");
- let handle = RadrootsNostrSubscriptionHandle {
- id: id.clone(),
- name: spec.name.clone(),
- };
-
- let worker = spawn_subscription_worker(self.inner.clone(), id.clone(), spec);
- self.inner
- .subscription_tasks
- .lock()
- .map_err(|_| RadrootsNostrRuntimeError::Runtime("subscription lock poisoned".into()))?
- .insert(id, worker);
-
- Ok(handle)
- }
-
- pub async fn unsubscribe(
- &self,
- handle: &RadrootsNostrSubscriptionHandle,
- ) -> Result<(), RadrootsNostrRuntimeError> {
- let removed = self
- .inner
- .subscription_tasks
- .lock()
- .map_err(|_| RadrootsNostrRuntimeError::Runtime("subscription lock poisoned".into()))?
- .remove(handle.id.as_str());
-
- let task = removed.ok_or_else(|| {
- RadrootsNostrRuntimeError::SubscriptionNotFound(handle.id.to_string())
- })?;
- task.abort();
- let _ = self
- .inner
- .queue_tx
- .send(RadrootsNostrRuntimeEvent::SubscriptionClosed {
- id: handle.id.clone(),
- })
- .await;
-
- Ok(())
- }
-
- pub fn set_relays(&self, relays: Vec<String>) -> Result<(), RadrootsNostrRuntimeError> {
- if relays.is_empty() {
- return Err(RadrootsNostrRuntimeError::InvalidConfig("relays"));
- }
- self.inner
- .relays
- .lock()
- .map_err(|_| RadrootsNostrRuntimeError::Runtime("relays lock poisoned".into()))
- .map(|mut guard| {
- *guard = relays;
- })
- }
-
- pub fn relays(&self) -> Vec<String> {
- self.inner
- .relays
- .lock()
- .map(|guard| guard.clone())
- .unwrap_or_default()
- }
-
- pub fn drain_events(&self, max: usize) -> Vec<RadrootsNostrRuntimeEvent> {
- if max == 0 {
- return Vec::new();
- }
-
- let mut out = Vec::with_capacity(max);
- let mut guard = match self.inner.queue_rx.lock() {
- Ok(guard) => guard,
- Err(_) => return out,
- };
-
- for _ in 0..max {
- match guard.try_recv() {
- Ok(event) => out.push(event),
- Err(tokio::sync::mpsc::error::TryRecvError::Empty) => break,
- Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => break,
- }
- }
-
- out
- }
-
- pub fn snapshot(&self) -> RadrootsNostrConnectionSnapshot {
- let statuses = self
- .inner
- .statuses
- .lock()
- .map(|guard| guard.clone())
- .unwrap_or_default();
- let last_error = self
- .inner
- .last_error
- .lock()
- .ok()
- .and_then(|guard| guard.clone());
-
- let mut connected = 0usize;
- let mut connecting = 0usize;
- for status in statuses.values() {
- match status {
- RadrootsNostrRelayStatus::Connected => connected += 1,
- RadrootsNostrRelayStatus::Connecting => connecting += 1,
- _ => {}
- }
- }
-
- let light = if connected > 0 {
- RadrootsNostrTrafficLight::Green
- } else if connecting > 0 {
- RadrootsNostrTrafficLight::Yellow
- } else {
- RadrootsNostrTrafficLight::Red
- };
-
- RadrootsNostrConnectionSnapshot {
- light,
- connected,
- connecting,
- last_error,
- }
- }
-
- fn spawn_monitor_watcher(&self) {
- let inner = self.inner.clone();
- let handle = tokio::spawn(async move {
- if let Some(monitor) = inner.client.monitor() {
- let mut rx = monitor.subscribe();
- while let Ok(notification) = rx.recv().await {
- match notification {
- RadrootsNostrMonitorNotification::StatusChanged { relay_url, status } => {
- if let Ok(mut map) = inner.statuses.lock() {
- map.insert(relay_url, status);
- }
- }
- }
- }
- }
- });
-
- if let Ok(mut guard) = self.inner.monitor_task.lock()
- && let Some(existing) = guard.replace(handle)
- {
- existing.abort();
- }
- }
-
- fn record_error(&self, message: String) {
- if let Ok(mut guard) = self.inner.last_error.lock() {
- *guard = Some(message);
- }
- }
-}
-
-fn spawn_subscription_worker(
- inner: Arc<RadrootsNostrRuntimeInner>,
- id: String,
- spec: RadrootsNostrSubscriptionSpec,
-) -> JoinHandle<()> {
- tokio::spawn(async move {
- let _ = inner
- .queue_tx
- .send(RadrootsNostrRuntimeEvent::SubscriptionOpened { id: id.clone() })
- .await;
-
- let timeout = Duration::from_secs(spec.stream_timeout_secs.max(1));
- let reconnect_delay = Duration::from_millis(spec.reconnect_delay_millis.max(1));
- let mut since_unix: Option<u64> = None;
-
- loop {
- if inner.shutting_down.load(Ordering::SeqCst) {
- break;
- }
-
- let mut filter = spec.filter.clone();
- if let Some(since) = since_unix {
- filter = filter.since(RadrootsNostrTimestamp::from(since));
- }
-
- let mut stream = match inner.client.stream_events(filter, timeout).await {
- Ok(stream) => stream,
- Err(source) => {
- let message = source.to_string();
- if let Ok(mut guard) = inner.last_error.lock() {
- *guard = Some(message.clone());
- }
- let _ = inner
- .queue_tx
- .send(RadrootsNostrRuntimeEvent::Error { message })
- .await;
-
- if matches!(spec.policy, RadrootsNostrSubscriptionPolicy::OneShotOnEose) {
- break;
- }
-
- tokio::time::sleep(reconnect_delay).await;
- continue;
- }
- };
-
- while let Some(event) = stream.next().await {
- let event_id = event.id.to_hex();
- let author = event.pubkey.to_hex();
- let kind = event.kind.as_u16();
- since_unix = Some(event.created_at.as_secs().saturating_add(1));
-
- if let Some(sink) = inner.event_sink.as_ref()
- && let Err(message) = sink.ingest_event(&event)
- {
- if let Ok(mut guard) = inner.last_error.lock() {
- *guard = Some(message.clone());
- }
- let _ = inner
- .queue_tx
- .send(RadrootsNostrRuntimeEvent::Error { message })
- .await;
- }
-
- let _ = inner
- .queue_tx
- .send(RadrootsNostrRuntimeEvent::Note {
- subscription_id: id.clone(),
- id: event_id,
- author,
- kind,
- relay: None,
- })
- .await;
-
- if inner.shutting_down.load(Ordering::SeqCst) {
- break;
- }
- }
-
- if matches!(spec.policy, RadrootsNostrSubscriptionPolicy::OneShotOnEose) {
- break;
- }
-
- tokio::time::sleep(reconnect_delay).await;
- }
-
- let _ = inner
- .queue_tx
- .send(RadrootsNostrRuntimeEvent::SubscriptionClosed { id })
- .await;
- })
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::sink::RadrootsNostrInMemoryEventSink;
- use alloc::sync::Arc;
- use radroots_nostr::filter::Filter as RadrootsNostrFilter;
-
- fn sample_runtime() -> RadrootsNostrRuntime {
- RadrootsNostrRuntimeBuilder::new()
- .keys(RadrootsNostrClientKey::generate())
- .add_relay("wss://relay.example.com")
- .build()
- .expect("runtime should build")
- }
-
- #[test]
- fn build_requires_keys() {
- let result = RadrootsNostrRuntimeBuilder::new()
- .add_relay("wss://relay.example.com")
- .build();
- assert!(matches!(
- result,
- Err(RadrootsNostrRuntimeError::MissingConfig("keys"))
- ));
- }
-
- #[test]
- fn build_requires_relays() {
- let result = RadrootsNostrRuntimeBuilder::new()
- .keys(RadrootsNostrClientKey::generate())
- .build();
- assert!(matches!(
- result,
- Err(RadrootsNostrRuntimeError::MissingConfig("relays"))
- ));
- }
-
- #[test]
- fn queue_capacity_must_be_positive() {
- let result = RadrootsNostrRuntimeBuilder::new()
- .keys(RadrootsNostrClientKey::generate())
- .add_relay("wss://relay.example.com")
- .queue_capacity(0)
- .build();
- assert!(matches!(
- result,
- Err(RadrootsNostrRuntimeError::InvalidConfig("queue_capacity"))
- ));
- }
-
- #[test]
- fn monitor_capacity_must_be_positive() {
- let result = RadrootsNostrRuntimeBuilder::new()
- .keys(RadrootsNostrClientKey::generate())
- .add_relay("wss://relay.example.com")
- .monitor_capacity(0)
- .build();
- assert!(matches!(
- result,
- Err(RadrootsNostrRuntimeError::InvalidConfig("monitor_capacity"))
- ));
- }
-
- #[test]
- fn build_accepts_event_sink() {
- let sink = Arc::new(RadrootsNostrInMemoryEventSink::new());
- let result = RadrootsNostrRuntimeBuilder::new()
- .keys(RadrootsNostrClientKey::generate())
- .add_relay("wss://relay.example.com")
- .event_sink(sink)
- .build();
- assert!(result.is_ok());
- }
-
- #[test]
- fn set_relays_rejects_empty_input() {
- let runtime = sample_runtime();
- let result = runtime.set_relays(Vec::new());
- assert!(matches!(
- result,
- Err(RadrootsNostrRuntimeError::InvalidConfig("relays"))
- ));
- }
-
- #[test]
- fn drain_events_zero_returns_empty() {
- let runtime = sample_runtime();
- assert!(runtime.drain_events(0).is_empty());
- }
-
- #[tokio::test]
- async fn subscribe_requires_started_runtime() {
- let runtime = sample_runtime();
- let spec = RadrootsNostrSubscriptionSpec::streaming(RadrootsNostrFilter::new());
- let result = runtime.subscribe(spec).await;
- assert!(matches!(
- result,
- Err(RadrootsNostrRuntimeError::RuntimeNotStarted)
- ));
- }
-
- #[tokio::test]
- async fn unsubscribe_requires_existing_subscription() {
- let runtime = sample_runtime();
- let handle = RadrootsNostrSubscriptionHandle {
- id: "sub-999".into(),
- name: None,
- };
- let result = runtime.unsubscribe(&handle).await;
- assert!(matches!(
- result,
- Err(RadrootsNostrRuntimeError::SubscriptionNotFound(_))
- ));
- }
-}
diff --git a/crates/nostr_runtime/src/sink.rs b/crates/nostr_runtime/src/sink.rs
@@ -1,65 +0,0 @@
-use alloc::string::ToString;
-use alloc::vec::Vec;
-use radroots_nostr::event::Event as RadrootsNostrEvent;
-use std::sync::Mutex;
-
-pub trait RadrootsNostrEventSink: Send + Sync {
- fn ingest_event(&self, event: &RadrootsNostrEvent) -> Result<(), String>;
-}
-
-#[derive(Default)]
-pub struct RadrootsNostrInMemoryEventSink {
- events: Mutex<Vec<RadrootsNostrEvent>>,
-}
-
-impl RadrootsNostrInMemoryEventSink {
- pub fn new() -> Self {
- Self::default()
- }
-
- pub fn events(&self) -> Vec<RadrootsNostrEvent> {
- self.events
- .lock()
- .map(|guard| guard.clone())
- .unwrap_or_default()
- }
-
- pub fn len(&self) -> usize {
- self.events.lock().map(|guard| guard.len()).unwrap_or(0)
- }
-
- pub fn is_empty(&self) -> bool {
- self.len() == 0
- }
-}
-
-impl RadrootsNostrEventSink for RadrootsNostrInMemoryEventSink {
- fn ingest_event(&self, event: &RadrootsNostrEvent) -> Result<(), String> {
- self.events
- .lock()
- .map_err(|_| "in-memory sink lock poisoned".to_string())
- .map(|mut guard| {
- guard.push(event.clone());
- })
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use nostr::EventBuilder;
- use nostr::Keys as RadrootsNostrKeys;
-
- #[test]
- fn in_memory_sink_tracks_events() {
- let sink = RadrootsNostrInMemoryEventSink::new();
- let keys = RadrootsNostrKeys::generate();
- let event = EventBuilder::text_note("hello")
- .sign_with_keys(&keys)
- .expect("event should sign");
-
- sink.ingest_event(&event).expect("event should be accepted");
- assert_eq!(sink.len(), 1);
- assert!(!sink.is_empty());
- }
-}
diff --git a/crates/nostr_runtime/src/types.rs b/crates/nostr_runtime/src/types.rs
@@ -1,277 +0,0 @@
-use alloc::string::String;
-#[cfg(feature = "nostr-client")]
-use nostr::PublicKey as RadrootsNostrPublicKey;
-#[cfg(feature = "nostr-client")]
-use radroots_nostr::event::Timestamp as RadrootsNostrTimestamp;
-#[cfg(feature = "nostr-client")]
-use radroots_nostr::event::post_filter;
-#[cfg(feature = "nostr-client")]
-use radroots_nostr::filter::Filter as RadrootsNostrFilter;
-#[cfg(feature = "nostr-client")]
-use radroots_nostr::filter::kind as nostr_kind;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum RadrootsNostrSubscriptionPolicy {
- Streaming,
- OneShotOnEose,
-}
-
-#[derive(Debug, Clone)]
-pub struct RadrootsNostrSubscriptionSpec {
- pub name: Option<String>,
- #[cfg(feature = "nostr-client")]
- pub filter: RadrootsNostrFilter,
- pub policy: RadrootsNostrSubscriptionPolicy,
- pub stream_timeout_secs: u64,
- pub reconnect_delay_millis: u64,
-}
-
-impl RadrootsNostrSubscriptionSpec {
- pub const DEFAULT_STREAM_TIMEOUT_SECS: u64 = 30;
- pub const DEFAULT_RECONNECT_DELAY_MILLIS: u64 = 2_000;
-
- #[cfg(feature = "nostr-client")]
- pub fn streaming(filter: RadrootsNostrFilter) -> Self {
- Self {
- name: None,
- filter,
- policy: RadrootsNostrSubscriptionPolicy::Streaming,
- stream_timeout_secs: Self::DEFAULT_STREAM_TIMEOUT_SECS,
- reconnect_delay_millis: Self::DEFAULT_RECONNECT_DELAY_MILLIS,
- }
- }
-
- #[cfg(feature = "nostr-client")]
- pub fn one_shot(filter: RadrootsNostrFilter) -> Self {
- Self {
- name: None,
- filter,
- policy: RadrootsNostrSubscriptionPolicy::OneShotOnEose,
- stream_timeout_secs: Self::DEFAULT_STREAM_TIMEOUT_SECS,
- reconnect_delay_millis: Self::DEFAULT_RECONNECT_DELAY_MILLIS,
- }
- }
-
- pub fn with_policy(mut self, policy: RadrootsNostrSubscriptionPolicy) -> Self {
- self.policy = policy;
- self
- }
-
- #[cfg(feature = "nostr-client")]
- pub fn text_notes(
- limit: Option<u16>,
- since_unix: Option<u64>,
- policy: RadrootsNostrSubscriptionPolicy,
- ) -> Self {
- Self::streaming(post_filter(limit, since_unix)).with_policy(policy)
- }
-
- #[cfg(feature = "nostr-client")]
- pub fn by_kind(
- kind: u16,
- limit: Option<u16>,
- since_unix: Option<u64>,
- policy: RadrootsNostrSubscriptionPolicy,
- ) -> Self {
- let mut filter = RadrootsNostrFilter::new().kind(nostr_kind(kind));
- if let Some(limit) = limit {
- filter = filter.limit(limit.into());
- }
- if let Some(since) = since_unix {
- filter = filter.since(RadrootsNostrTimestamp::from(since));
- }
- Self::streaming(filter).with_policy(policy)
- }
-
- #[cfg(feature = "nostr-client")]
- pub fn by_author(mut self, author: RadrootsNostrPublicKey) -> Self {
- self.filter = self.filter.author(author);
- self
- }
-
- pub fn named(mut self, name: impl Into<String>) -> Self {
- self.name = Some(name.into());
- self
- }
-
- pub fn stream_timeout_secs(mut self, value: u64) -> Self {
- self.stream_timeout_secs = value;
- self
- }
-
- pub fn reconnect_delay_millis(mut self, value: u64) -> Self {
- self.reconnect_delay_millis = value;
- self
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- #[cfg(feature = "nostr-client")]
- use nostr::Keys as RadrootsNostrKeys;
-
- fn base_spec() -> RadrootsNostrSubscriptionSpec {
- RadrootsNostrSubscriptionSpec {
- name: None,
- #[cfg(feature = "nostr-client")]
- filter: radroots_nostr::filter::Filter::new(),
- policy: RadrootsNostrSubscriptionPolicy::Streaming,
- stream_timeout_secs: RadrootsNostrSubscriptionSpec::DEFAULT_STREAM_TIMEOUT_SECS,
- reconnect_delay_millis: RadrootsNostrSubscriptionSpec::DEFAULT_RECONNECT_DELAY_MILLIS,
- }
- }
-
- #[cfg(feature = "nostr-client")]
- #[test]
- fn text_notes_constructor_sets_defaults() {
- let spec = RadrootsNostrSubscriptionSpec::text_notes(
- Some(5),
- Some(10),
- RadrootsNostrSubscriptionPolicy::Streaming,
- );
- assert!(matches!(
- spec.policy,
- RadrootsNostrSubscriptionPolicy::Streaming
- ));
- assert_eq!(
- spec.stream_timeout_secs,
- RadrootsNostrSubscriptionSpec::DEFAULT_STREAM_TIMEOUT_SECS
- );
- assert_eq!(
- spec.reconnect_delay_millis,
- RadrootsNostrSubscriptionSpec::DEFAULT_RECONNECT_DELAY_MILLIS
- );
- }
-
- #[cfg(feature = "nostr-client")]
- #[test]
- fn by_kind_constructor_respects_policy() {
- let spec = RadrootsNostrSubscriptionSpec::by_kind(
- 30023,
- None,
- None,
- RadrootsNostrSubscriptionPolicy::OneShotOnEose,
- );
- assert!(matches!(
- spec.policy,
- RadrootsNostrSubscriptionPolicy::OneShotOnEose
- ));
- }
-
- #[cfg(feature = "nostr-client")]
- #[test]
- fn builder_methods_update_spec_fields() {
- let keys = RadrootsNostrKeys::generate();
- let author = keys.public_key();
- let spec = RadrootsNostrSubscriptionSpec::text_notes(
- None,
- None,
- RadrootsNostrSubscriptionPolicy::Streaming,
- )
- .by_author(author)
- .named("posts")
- .stream_timeout_secs(12)
- .reconnect_delay_millis(99)
- .with_policy(RadrootsNostrSubscriptionPolicy::OneShotOnEose);
-
- assert_eq!(spec.name.as_deref(), Some("posts"));
- assert_eq!(spec.stream_timeout_secs, 12);
- assert_eq!(spec.reconnect_delay_millis, 99);
- assert_eq!(spec.policy, RadrootsNostrSubscriptionPolicy::OneShotOnEose);
- }
-
- #[test]
- fn builder_methods_update_common_fields_without_client_feature() {
- let spec = base_spec()
- .named("posts")
- .stream_timeout_secs(12)
- .reconnect_delay_millis(99)
- .with_policy(RadrootsNostrSubscriptionPolicy::OneShotOnEose);
-
- assert_eq!(spec.name.as_deref(), Some("posts"));
- assert_eq!(spec.stream_timeout_secs, 12);
- assert_eq!(spec.reconnect_delay_millis, 99);
- assert_eq!(spec.policy, RadrootsNostrSubscriptionPolicy::OneShotOnEose);
- }
-
- #[test]
- fn connection_snapshot_default_is_red() {
- let snapshot = RadrootsNostrConnectionSnapshot::default();
- assert_eq!(snapshot.light, RadrootsNostrTrafficLight::Red);
- assert_eq!(snapshot.connected, 0);
- assert_eq!(snapshot.connecting, 0);
- assert!(snapshot.last_error.is_none());
- }
-
- #[test]
- fn branch_probe_covers_true_and_false_paths() {
- let mut total = 0;
- for flag in [true, false] {
- if flag {
- total += 1;
- } else {
- total += 2;
- }
- }
- assert_eq!(total, 3);
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct RadrootsNostrSubscriptionHandle {
- pub id: String,
- pub name: Option<String>,
-}
-
-#[derive(Debug, Clone)]
-pub enum RadrootsNostrRuntimeEvent {
- RuntimeStarted,
- RuntimeStopped,
- SubscriptionOpened {
- id: String,
- },
- SubscriptionClosed {
- id: String,
- },
- Note {
- subscription_id: String,
- id: String,
- author: String,
- kind: u16,
- relay: Option<String>,
- },
- Notice {
- relay: String,
- message: String,
- },
- Error {
- message: String,
- },
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum RadrootsNostrTrafficLight {
- Red,
- Yellow,
- Green,
-}
-
-#[derive(Debug, Clone)]
-pub struct RadrootsNostrConnectionSnapshot {
- pub light: RadrootsNostrTrafficLight,
- pub connected: usize,
- pub connecting: usize,
- pub last_error: Option<String>,
-}
-
-impl Default for RadrootsNostrConnectionSnapshot {
- fn default() -> Self {
- Self {
- light: RadrootsNostrTrafficLight::Red,
- connected: 0,
- connecting: 0,
- last_error: None,
- }
- }
-}
diff --git a/crates/nostr_signer/README b/crates/nostr_signer/README
@@ -5,12 +5,11 @@ public integrations must use `radroots_signing` for signer injection and
`radroots_nostr_connect` for NIP-46 protocol types. Myc-private persisted state
will remain application-owned.
-The package remains temporarily because `radroots_net`,
-`radroots_nostr_accounts`, `oss/sdk`, `oss/myc`, and `oss/cli` still consume
-parts of its source API. Their scheduled crate, SDK, and downstream cutovers
-run through Steps 109-294. The package must be removed at Step 313 after the
-Step 294 compatibility matrix proves that every first-party consumer has
-migrated.
+The package remains temporarily because `radroots_nostr_accounts`, `oss/sdk`,
+`oss/myc`, and `oss/cli` still consume parts of its source API. Their scheduled
+crate, SDK, and downstream cutovers run through Steps 109-294. The package must
+be removed at Step 313 after the Step 294 compatibility matrix proves that
+every first-party consumer has migrated.
This package is not an approved public crate, must keep `publish = false`, and
must not acquire new consumers, features, or behavior while it is retained.
diff --git a/crates/nostrdb/Cargo.toml b/crates/nostrdb/Cargo.toml
@@ -16,18 +16,12 @@ default = ["std", "rt", "nostrdb"]
std = []
rt = ["std", "dep:futures", "dep:tokio"]
nostrdb = ["std", "dep:nostrdb"]
-runtime-adapter = ["std", "dep:radroots_nostr_runtime"]
giftwrap = ["std", "nostrdb"]
[dependencies]
radroots_nostr = { workspace = true, default-features = false, features = [
"std",
] }
-radroots_nostr_runtime = { workspace = true, optional = true, default-features = false, features = [
- "std",
- "rt",
- "nostr-client",
-] }
futures = { workspace = true, optional = true }
hex = { workspace = true }
nostrdb = { version = "0.9.0", optional = true }
diff --git a/crates/nostrdb/README b/crates/nostrdb/README
@@ -7,9 +7,7 @@ primitives for the `radroots` core libraries.
* configuration, filter, ingest, query, and subscription types for
nostrdb-backed workflows;
- * runtime-adapter hooks that let higher layers drive nostrdb as an event sink;
- * feature-gated support for the `nostrdb` crate, runtime glue, and gift-wrap
- handling;
+ * feature-gated support for the `nostrdb` crate and gift-wrap handling;
* a `std`-based integration crate with optional async runtime support.
## Copyright
diff --git a/crates/nostrdb/src/lib.rs b/crates/nostrdb/src/lib.rs
@@ -20,8 +20,6 @@ pub mod ingest;
pub mod nostrdb;
#[cfg(feature = "nostrdb")]
pub mod query;
-#[cfg(all(feature = "nostrdb", feature = "runtime-adapter"))]
-pub mod runtime_adapter;
#[cfg(feature = "nostrdb")]
pub mod subscription;
@@ -37,8 +35,6 @@ pub mod prelude {
pub use crate::nostrdb::RadrootsNostrdb;
#[cfg(feature = "nostrdb")]
pub use crate::query::{RadrootsNostrdbNote, RadrootsNostrdbProfile, RadrootsNostrdbQuerySpec};
- #[cfg(all(feature = "nostrdb", feature = "runtime-adapter"))]
- pub use crate::runtime_adapter::RadrootsNostrdbEventSinkAdapter;
#[cfg(all(feature = "nostrdb", feature = "rt"))]
pub use crate::subscription::RadrootsNostrdbSubscriptionStream;
#[cfg(feature = "nostrdb")]
diff --git a/crates/nostrdb/src/runtime_adapter.rs b/crates/nostrdb/src/runtime_adapter.rs
@@ -1,119 +0,0 @@
-use crate::ingest::RadrootsNostrdbIngestSource;
-use crate::nostrdb::RadrootsNostrdb;
-use radroots_nostr::event::Event as RadrootsNostrEvent;
-use radroots_nostr_runtime::prelude::RadrootsNostrEventSink;
-use std::sync::Arc;
-
-#[derive(Clone)]
-pub struct RadrootsNostrdbEventSinkAdapter {
- nostrdb: RadrootsNostrdb,
- source: RadrootsNostrdbIngestSource,
-}
-
-fn nostrdb_error_to_string(source: crate::error::RadrootsNostrdbError) -> String {
- source.to_string()
-}
-
-impl RadrootsNostrdbEventSinkAdapter {
- pub fn new(nostrdb: RadrootsNostrdb) -> Self {
- Self {
- nostrdb,
- source: RadrootsNostrdbIngestSource::client(),
- }
- }
-
- pub fn with_source(mut self, source: RadrootsNostrdbIngestSource) -> Self {
- self.source = source;
- self
- }
-
- pub fn into_event_sink(self) -> Arc<dyn RadrootsNostrEventSink> {
- Arc::new(self)
- }
-}
-
-impl RadrootsNostrEventSink for RadrootsNostrdb {
- fn ingest_event(&self, event: &RadrootsNostrEvent) -> Result<(), String> {
- RadrootsNostrdb::ingest_event(self, event, RadrootsNostrdbIngestSource::client())
- .map_err(nostrdb_error_to_string)
- }
-}
-
-impl RadrootsNostrEventSink for RadrootsNostrdbEventSinkAdapter {
- fn ingest_event(&self, event: &RadrootsNostrEvent) -> Result<(), String> {
- self.nostrdb
- .ingest_event(event, self.source.clone())
- .map_err(nostrdb_error_to_string)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::config::RadrootsNostrdbConfig;
- use nostr::EventBuilder;
- use nostr::Keys as RadrootsNostrKeys;
- use tempfile::TempDir;
-
- #[test]
- fn runtime_adapter_accepts_signed_events() {
- let tmp_dir = TempDir::new().expect("tempdir should open");
- let db_dir = tmp_dir.path().join("nostrdb");
- let config = RadrootsNostrdbConfig::new(&db_dir);
- let nostrdb = RadrootsNostrdb::open(config).expect("database should open");
- let adapter = RadrootsNostrdbEventSinkAdapter::new(nostrdb);
-
- let keys = RadrootsNostrKeys::generate();
- let event = EventBuilder::text_note("hello from runtime adapter")
- .sign_with_keys(&keys)
- .expect("event should sign");
-
- adapter
- .ingest_event(&event)
- .expect("adapter should ingest event");
- }
-
- #[test]
- fn runtime_adapter_can_be_boxed_as_sink_trait() {
- let tmp_dir = TempDir::new().expect("tempdir should open");
- let db_dir = tmp_dir.path().join("nostrdb");
- let config = RadrootsNostrdbConfig::new(&db_dir);
- let nostrdb = RadrootsNostrdb::open(config).expect("database should open");
- let sink = RadrootsNostrdbEventSinkAdapter::new(nostrdb)
- .with_source(RadrootsNostrdbIngestSource::relay("wss://radroots.org"))
- .into_event_sink();
-
- let keys = RadrootsNostrKeys::generate();
- let event = EventBuilder::text_note("hello trait object")
- .sign_with_keys(&keys)
- .expect("event should sign");
-
- sink.ingest_event(&event)
- .expect("boxed sink should ingest event");
- }
-
- #[test]
- fn nostrdb_can_be_boxed_as_sink_trait() {
- let tmp_dir = TempDir::new().expect("tempdir should open");
- let db_dir = tmp_dir.path().join("nostrdb");
- let config = RadrootsNostrdbConfig::new(&db_dir);
- let nostrdb = RadrootsNostrdb::open(config).expect("database should open");
- let sink: Arc<dyn RadrootsNostrEventSink> = Arc::new(nostrdb.clone());
-
- let keys = RadrootsNostrKeys::generate();
- let event = EventBuilder::text_note("hello nostrdb trait object")
- .sign_with_keys(&keys)
- .expect("event should sign");
-
- sink.ingest_event(&event)
- .expect("nostrdb trait object should ingest event");
- }
-
- #[test]
- fn runtime_adapter_error_to_string_converts() {
- let rendered = nostrdb_error_to_string(crate::error::RadrootsNostrdbError::Nostrdb(
- "nostrdb error".to_string(),
- ));
- assert_eq!(rendered, "nostrdb error: nostrdb error");
- }
-}
diff --git a/crates/trade/tests/operational_listing_conformance.rs b/crates/trade/tests/operational_listing_conformance.rs
@@ -238,8 +238,10 @@ fn verified_event(vector: &Vector, keys: &Keys) -> RadrootsSignatureVerifiedEven
vector.id
);
let message = Message::from_digest(event.id.to_bytes());
- let deterministic_signature =
- SECP256K1.sign_schnorr_no_aux_rand(&message, keys.key_pair(SECP256K1));
+ let deterministic_signature = SECP256K1.sign_schnorr_no_aux_rand(
+ &message,
+ &nostr::secp256k1::Keypair::from_secret_key(SECP256K1, keys.secret_key()),
+ );
assert_eq!(
event.sig, deterministic_signature,
"{} deterministic signature drift",
diff --git a/crates/trade_sp1_host/src/lib.rs b/crates/trade_sp1_host/src/lib.rs
@@ -1794,9 +1794,6 @@ mod tests {
RadrootsSp1TradeOrderRequestWitness, RadrootsSp1TradeProofResult,
RadrootsSp1TradePublicValuesExecution,
};
- #[cfg(feature = "sp1_verify")]
- use serde::Deserialize;
-
type ProofEnvelopeMutation = (&'static str, fn(&mut super::RadrootsSp1TradeProofEnvelope));
type PublicValuesExecutionMutation =
(&'static str, fn(&mut RadrootsSp1TradePublicValuesExecution));
@@ -2840,131 +2837,19 @@ mod tests {
}
#[cfg(feature = "sp1_verify")]
- #[derive(Deserialize)]
- #[serde(deny_unknown_fields)]
- struct RemoteReturnedProofFixture {
- schema_version: u32,
- fixture_id: String,
- proof_target: String,
- proof_mode: RadrootsSp1TradeProofMode,
- proof_system: RadrootsValidationReceiptProofSystem,
- sp1_version_line: String,
- remote_prover_request: super::RadrootsSp1TradeRemoteProverRequest,
- remote_prover_response: super::RadrootsSp1TradeRemoteProverResponse,
- }
-
- #[cfg(feature = "sp1_verify")]
- fn remote_returned_proof_fixture() -> RemoteReturnedProofFixture {
- serde_json::from_str(include_str!(
+ #[test]
+ fn legacy_remote_proof_fixture_is_rejected_without_validator_set_binding() {
+ let fixture: serde_json::Value = serde_json::from_str(include_str!(
"../tests/fixtures/remote_returned_proof_order_acceptance_core_v1.json"
))
- .expect("remote returned proof fixture")
- }
-
- #[cfg(feature = "sp1_verify")]
- fn verified_remote_returned_proof_fixture() -> (
- RadrootsSp1TradePublicValuesExecution,
- super::RadrootsSp1TradeProofArtifact,
- ) {
- let fixture = remote_returned_proof_fixture();
- assert_eq!(fixture.schema_version, 1);
- assert_eq!(
- fixture.fixture_id,
- "remote_returned_proof_order_acceptance_core_v1"
- );
- assert_eq!(
- fixture.proof_target,
- RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET
- );
- assert_eq!(fixture.proof_mode, RadrootsSp1TradeProofMode::Core);
- assert_eq!(
- fixture.proof_system,
- RadrootsValidationReceiptProofSystem::Sp1Core
- );
- assert_eq!(
- fixture.sp1_version_line,
- super::RADROOTS_SP1_TRADE_SP1_VERSION_LINE
- );
-
- let request = fixture.remote_prover_request;
- let response = fixture.remote_prover_response;
- let execution = super::execute_order_acceptance_public_values(&request.witness)
- .expect("deterministic execution");
- assert_eq!(
- execution.public_values_hash,
- request.expected_public_values_hash
- );
- assert_eq!(
- execution.public_values.sp1_program_hash.as_deref(),
- Some(request.expected_sp1_program_hash.as_str())
- );
- assert_eq!(
- execution.public_values.sp1_verifying_key_hash.as_deref(),
- Some(request.expected_sp1_verifying_key_hash.as_str())
- );
- assert_eq!(
- response.status,
- super::RadrootsSp1TradeRemoteProverStatus::Completed
- );
- assert_eq!(response.request_id, request.request_id);
- assert_eq!(
- response.public_values_hash.as_deref(),
- Some(request.expected_public_values_hash.as_str())
- );
- assert_eq!(
- response.sp1_program_hash.as_deref(),
- Some(request.expected_sp1_program_hash.as_str())
- );
- assert_eq!(
- response.sp1_verifying_key_hash.as_deref(),
- Some(request.expected_sp1_verifying_key_hash.as_str())
- );
-
- let artifact = response.proof_artifact.expect("proof artifact");
- verify_order_acceptance_proof_artifact_structure(&execution, &artifact)
- .expect("artifact structure");
- (execution, artifact)
- }
-
- #[cfg(feature = "sp1_verify")]
- #[test]
- fn remote_returned_proof_artifact_fixture_is_structurally_valid() {
- verified_remote_returned_proof_fixture();
- }
-
- #[cfg(all(feature = "sp1_verify", radroots_sp1_real_proof_tests))]
- #[tokio::test]
- async fn remote_returned_proof_artifact_real_sp1_verifies() {
- let (execution, artifact) = verified_remote_returned_proof_fixture();
- super::verify_order_acceptance_resolved_sp1_proof_artifact(
- &execution,
- &super::RadrootsSp1TradeResolvedProofArtifact::inline(artifact.clone()),
- )
- .await
- .expect("remote proof verifies");
-
- let mut digest_mismatch = artifact.clone();
- digest_mismatch.proof_digest =
- "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
- let err = verify_order_acceptance_proof_artifact_structure(&execution, &digest_mismatch)
- .expect_err("digest mismatch");
- assert_eq!(err, RadrootsSp1TradeHostError::ProofDigestMismatch);
-
- let mut identity_mismatch = execution.clone();
- identity_mismatch.public_values.sp1_program_hash =
- Some("0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd".to_string());
- let err = verify_order_acceptance_proof_artifact_structure(&identity_mismatch, &artifact)
- .expect_err("identity mismatch");
- assert_eq!(err, RadrootsSp1TradeHostError::Sp1ProgramHashMismatch);
-
- let mut public_values_mismatch = request.witness;
- public_values_mismatch.inventory_sequence += 1;
- let mismatch_execution =
- super::execute_order_acceptance_public_values(&public_values_mismatch)
- .expect("mismatch execution");
- let err = verify_order_acceptance_proof_artifact_structure(&mismatch_execution, &artifact)
- .expect_err("public values mismatch");
- assert_eq!(err, RadrootsSp1TradeHostError::PublicValuesHashMismatch);
+ .expect("legacy remote returned proof fixture JSON");
+ let witness = fixture
+ .pointer("/remote_prover_request/witness")
+ .cloned()
+ .expect("legacy witness");
+ let error = serde_json::from_value::<RadrootsSp1TradeOrderAcceptanceWitness>(witness)
+ .expect_err("legacy proof must fail closed without validator-set binding");
+ assert!(error.to_string().contains("validator_set_addr"));
}
#[cfg(feature = "sp1_verify")]
diff --git a/crates/transport_nostr/tests/legacy_quarantine.rs b/crates/transport_nostr/tests/legacy_quarantine.rs
@@ -1,28 +1,15 @@
+use std::fs;
+use std::path::Path;
+
const DEVIATIONS: &str = include_str!("../../../docs/implementation/deviations.toml");
const SHIMS: &str = include_str!("../../../docs/implementation/COMPATIBILITY_SHIMS.md");
const PUBLISH_POLICY: &str = include_str!("../../../contracts/releases/publish_policy.toml");
-const PREDECESSORS: &[(&str, &str, &str, &str, &str, &str)] = &[
- (
- "radroots_nostr_runtime",
- "radroots_transport_nostr + radroots_sync",
- "215",
- include_str!("../../nostr_runtime/Cargo.toml"),
- include_str!("../../nostr_runtime/src/lib.rs"),
- include_str!("../../nostr_runtime/README"),
- ),
- (
- "radroots_net",
- "radroots_transport + radroots_sync + radroots_sdk",
- "313",
- include_str!("../../net/Cargo.toml"),
- include_str!("../../net/src/lib.rs"),
- include_str!("../../net/README"),
- ),
-];
-
#[test]
-fn superseded_transport_packages_are_fail_closed_compatibility_quarantines() {
+fn superseded_transport_packages_are_removed_from_release_authority() {
+ let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
+ let workspace_manifest =
+ fs::read_to_string(workspace.join("Cargo.toml")).expect("workspace manifest");
let approved = PUBLISH_POLICY
.split_once("[workspace_classification]")
.map(|(publication, _)| publication)
@@ -33,47 +20,32 @@ fn superseded_transport_packages_are_fail_closed_compatibility_quarantines() {
.map(|(entries, _)| entries)
.expect("private package classification");
- for (package, replacement, removal_step, manifest, root, readme) in PREDECESSORS {
- for required in [
- "publish = false",
- "[package.metadata.radroots.compatibility]",
- "status = \"publish_frozen\"",
- "deviation = \"RCRV1-DEV-010\"",
- "new_consumers_forbidden = true",
- ] {
- assert!(
- manifest.contains(required),
- "{package} manifest is missing `{required}`"
- );
- }
- assert!(manifest.contains(&format!("replacement = \"{replacement}\"")));
- assert!(manifest.contains(&format!("removal_step = {removal_step}")));
- assert!(!manifest.contains("documentation = \"https://docs.rs/"));
- assert!(root.starts_with("#![doc(hidden)]"));
- assert!(readme.contains("## Compatibility quarantine"));
- assert!(readme.contains(&format!("Step {removal_step} removes")));
- assert!(readme.contains("package"));
+ for package in ["radroots_net", "radroots_nostr_runtime"] {
+ assert!(
+ !workspace
+ .join(format!("crates/{package}/Cargo.toml"))
+ .exists()
+ );
+ assert!(!workspace_manifest.contains(package));
assert!(!approved.contains(package));
- assert!(private.contains(&format!("\"{package}\"")));
- assert!(SHIMS.contains(&format!("| `{package}` |")));
+ assert!(!private.contains(&format!("\"{package}\"")));
+ assert!(!SHIMS.contains(&format!("| `{package}` |")));
}
}
#[test]
-fn quarantine_records_consumers_and_exact_removal_gates() {
+fn closure_record_captures_the_pulled_forward_removal_gates() {
for required in [
"id = \"RCRV1-DEV-010\"",
- "app_rt resolving radroots_net as radroots_net_core",
- "radroots_nostrdb runtime-adapter feature",
- "Step 215 sync retirement gate",
- "delete it at Step 313",
- "opt-in predecessor feature closures do not compile",
+ "status = \"closed\"",
+ "radroots_nostr_runtime Step 215 deletion and radroots_net Step 313 deletion had not been applied",
+ "Delete radroots_nostr_runtime, its NostrDB runtime adapter, and radroots_net during Step 301 qualification",
+ "historical names remain only in governed specifications, migration evidence, and fail-closed regression assertions",
+ "closure_evidence = [",
] {
assert!(
DEVIATIONS.contains(required),
- "transport quarantine is missing `{required}`"
+ "transport closure record is missing `{required}`"
);
}
- assert!(SHIMS.contains("| `radroots_nostr_runtime` |"));
- assert!(SHIMS.contains("| `radroots_net` |"));
}
diff --git a/crates/transport_nostr/tests/workspace_consumers.rs b/crates/transport_nostr/tests/workspace_consumers.rs
@@ -12,15 +12,7 @@ const REMOVED_CLIENT_NAMES: [&str; 5] = [
#[test]
fn ready_workspace_consumers_do_not_import_the_removed_client_surface() {
let workspace = workspace_root();
- let allowed = [
- workspace.join("crates/net/src"),
- workspace.join("crates/nostr_runtime/src"),
- ];
-
for source in rust_sources(workspace.join("crates")) {
- if allowed.iter().any(|root| source.starts_with(root)) {
- continue;
- }
let contents = fs::read_to_string(&source).expect("read workspace source");
for removed in REMOVED_CLIENT_NAMES {
assert!(
@@ -33,27 +25,23 @@ fn ready_workspace_consumers_do_not_import_the_removed_client_surface() {
}
#[test]
-fn predecessor_runtime_is_publish_frozen_and_not_default_reachable() {
+fn superseded_transport_packages_and_nostrdb_adapter_are_removed() {
let workspace = workspace_root();
- let manifest = fs::read_to_string(workspace.join("crates/nostr_runtime/Cargo.toml"))
- .expect("runtime manifest");
+ assert!(!workspace.join("crates/nostr_runtime/Cargo.toml").exists());
+ assert!(!workspace.join("crates/net/Cargo.toml").exists());
+ let workspace_manifest =
+ fs::read_to_string(workspace.join("Cargo.toml")).expect("workspace manifest");
let nostrdb_manifest =
fs::read_to_string(workspace.join("crates/nostrdb/Cargo.toml")).expect("nostrdb manifest");
- let readme =
- fs::read_to_string(workspace.join("crates/nostr_runtime/README")).expect("runtime readme");
let deviations = fs::read_to_string(workspace.join("docs/implementation/deviations.toml"))
.expect("deviation authority");
- assert!(manifest.contains("publish = false"));
- assert!(manifest.contains("default = [\"std\"]"));
- assert!(!manifest.contains("default = [\"std\", \"nostr-client\""));
- assert!(
- !nostrdb_manifest.contains("default = [\"std\", \"rt\", \"nostrdb\", \"runtime-adapter\"]")
- );
- assert!(readme.contains("RCRV1-DEV-007"));
- assert!(readme.contains("radroots_sync"));
- assert!(deviations.contains("id = \"RCRV1-DEV-007\""));
- assert!(deviations.contains("affected_steps = [\"122\", \"170\", \"215\", \"235\", \"305\"]"));
+ assert!(!workspace_manifest.contains("radroots_nostr_runtime"));
+ assert!(!workspace_manifest.contains("radroots_net"));
+ assert!(!nostrdb_manifest.contains("runtime-adapter"));
+ assert!(deviations.contains(
+ "Delete radroots_nostr_runtime, its NostrDB runtime adapter, and radroots_net during Step 301 qualification"
+ ));
}
fn workspace_root() -> PathBuf {
diff --git a/docs/implementation/COMPATIBILITY_SHIMS.md b/docs/implementation/COMPATIBILITY_SHIMS.md
@@ -8,13 +8,11 @@ identity or a second contract authority.
| Bridge | Final owner | Remaining first-party consumers | Assigned cutover | Exact final removal |
| --- | --- | --- | --- | --- |
| `radroots_authority` | `radroots_signing` | `oss/cli`, `oss/studio_app` | downstream Steps 269-293; matrix Step 294 | Step 313 |
-| `radroots_nostr_signer` | `radroots_signing`, `radroots_nostr_connect`, Myc-private state | `radroots_net`, `radroots_nostr_accounts`, `oss/sdk`, `oss/myc`, `oss/cli` | crate Steps 109-143; SDK Step 248; downstream Steps 269-293; matrix Step 294 | Step 313 |
+| `radroots_nostr_signer` | `radroots_signing`, `radroots_nostr_connect`, Myc-private state | `radroots_nostr_accounts`, `oss/sdk`, `oss/myc`, `oss/cli` | crate Steps 109-143; SDK Step 248; downstream Steps 269-293; matrix Step 294 | Step 313 |
| hidden `radroots_nostr_connect::prelude` and prefixed client bridge | final `radroots_nostr_connect` modules and client state machine | `oss/cli`, `oss/myc`, enterprise NIP-46 adapters, integration harnesses | CLI Step 271; Myc Step 288; residual consumers Step 293; matrix Step 294 | Step 313 |
-| `radroots_nostr_runtime` | `radroots_transport_nostr`, `radroots_sync` | private `radroots_nostrdb` runtime adapter | sync Steps 202-214 | Step 215 |
-| `radroots_net` | `radroots_transport`, `radroots_sync`, `radroots_sdk` | `app_rt` as `radroots_net_core` | downstream Steps 269-293; matrix Step 294 | Step 313 |
| `radroots_geocoder` | `radroots_geonames` | standalone `radroots_sdk` GeoNames feature and error adapter | SDK manifest cutover Step 226 | SDK quarantine removal Step 248 |
-Both compatibility package manifests and `radroots_nostr_connect` keep
+The remaining compatibility package manifests and `radroots_nostr_connect` keep
`publish = false`. Release policy classifies the shims as private and excludes
them from the exact 19-package public API inventory. The
Compatibility package manifests intentionally have no docs.rs URL. No new
@@ -23,3 +21,9 @@ consumer, feature, public contract, or behavior may be added before removal.
The Step 294 matrix must prove all listed consumers have migrated. Step 313
then removes the packages, their workspace/dependency entries, their source
names, and every remaining source pin.
+
+`radroots_nostr_runtime`, the private NostrDB runtime adapter, and
+`radroots_net` were deleted during Step 301 qualification. The full workspace
+matrix proved their assigned removal edits had been missed even though their
+consumer gates were satisfied, and neither obsolete all-feature closure could
+compile against the final transport boundary.
diff --git a/docs/implementation/deviations.toml b/docs/implementation/deviations.toml
@@ -39,27 +39,31 @@ adr_required = false
[[deviation]]
id = "RCRV1-DEV-010"
date = "2026-08-03"
-status = "active"
+status = "closed"
approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user."
-affected_steps = ["201", "215", "269", "294", "313"]
+affected_steps = ["201", "215", "269", "294", "301", "313"]
spec_anchors = [
"docs/specs/radroots_crates_release_v1.md#15-radroots_transport_nostr",
"docs/specs/radroots_crates_release_v1.md#20-current-to-target-migration-map",
]
source_evidence = [
- "The first-party Step 201 census found app_rt resolving radroots_net as radroots_net_core through a sibling path while its downstream cutover remains scheduled for Steps 269-293.",
- "The private radroots_nostrdb runtime-adapter feature still resolves radroots_nostr_runtime predecessor sink types until canonical sync orchestration is complete.",
- "Both predecessor packages are excluded from the exact release inventory and neither is reachable from a default public package feature closure.",
+ "The Step 294 downstream matrix proved the former app_rt radroots_net_core consumer had migrated and no current first-party manifest retained an edge to radroots_net.",
+ "Step 301 full workspace qualification proved that the assigned radroots_nostr_runtime Step 215 deletion and radroots_net Step 313 deletion had not been applied even though both removal gates were satisfied.",
+ "Both obsolete packages failed against the final transport boundary when the complete all-feature workspace closure was checked.",
]
-replacement_action = "Keep radroots_nostr_runtime as a documentation-hidden publish-frozen bridge only until the Step 215 sync retirement gate; keep radroots_net as a documentation-hidden publish-frozen bridge only until the downstream matrix is green, then delete it at Step 313. Forbid all new consumers, features, contracts, and behavior in both packages."
+replacement_action = "Delete radroots_nostr_runtime, its NostrDB runtime adapter, and radroots_net during Step 301 qualification; forbid their package, dependency, alias, feature, and source identities from being reintroduced."
verification = [
- "Both manifests carry machine-readable publish-frozen metadata naming their final owners, this deviation, exact removal step, and new-consumer prohibition.",
- "Transport-owned quarantine tests require both roots to remain documentation-hidden, private, and absent from the approved publication inventory.",
- "Step 215 removes radroots_nostr_runtime and its NostrDB adapter; Step 313 rejects residual radroots_net package, dependency, alias, and source identities after the downstream matrix.",
+ "Transport-owned tests reject reintroduction of radroots_nostr_runtime, radroots_net, or the NostrDB runtime-adapter feature.",
+ "Workspace-wide source tests reject every removed transport-client identifier without a compatibility exception.",
+ "The full all-target and all-feature workspace qualification compiles without either predecessor package.",
]
-unresolved_risk = "The opt-in predecessor feature closures do not compile against the final adapter API; package-realistic publication remains blocked until the exact removal gates delete them."
+unresolved_risk = "None for the two removed packages; historical names remain only in governed specifications, migration evidence, and fail-closed regression assertions."
normative_architecture_change = false
adr_required = false
+closure_evidence = [
+ "crates/transport_nostr/tests/workspace_consumers.rs proves both package directories and workspace dependency identities are absent.",
+ "crates/transport_nostr/tests/legacy_quarantine.rs proves release policy and the compatibility ledger no longer classify either package as a retained shim.",
+]
[[deviation]]
id = "RCRV1-DEV-008"
diff --git a/tools/xtask/src/contract/source_maintenance.rs b/tools/xtask/src/contract/source_maintenance.rs
@@ -3728,7 +3728,7 @@ struct DelegatedAuthoritySpec {
const EXECUTABLE_AUTHORITY_AST_SHA256: &str =
"b1a7658f47b4561ad816ef65dab47cf68c75de3c459383371319e96e6051d435";
const BOUND_AUTHORITY_SOURCE_AST_SHA256: &str =
- "f3e62e27e48c661b81aaabc37de75264caf433896b1db881b6cb984ddd23554b";
+ "3b8fcb08ea4e05be5ceef64029462c8f5c15d0675576114320341e2514a9b646";
#[derive(Clone, Debug, Serialize)]
struct ExecutableAuthorityIdentity {