lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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:
MCargo.lock | 84++-----------------------------------------------------------------------------
MCargo.toml | 4----
Mcontracts/conformance/vectors/comment/verified_profile.v1.json | 2+-
Mcontracts/coverage-profiles.toml | 5-----
Mcontracts/coverage.toml | 7-------
Mcontracts/manifest.toml | 2--
Mcontracts/releases/publish_policy.toml | 2--
Mcrates/event/tests/source_boundary.rs | 2--
Mcrates/event_codec/src/admission.rs | 5++++-
Mcrates/event_codec/src/deletion/reconciliation_v1/admission/tests.rs | 5++++-
Mcrates/event_codec/src/deletion/reconciliation_v1/evaluator/tests.rs | 5++++-
Mcrates/event_codec/tests/fixtures/comment_verified_profile.v1.json | 2+-
Mcrates/event_codec/tests/food_availability_conformance.rs | 10++++++++--
Mcrates/event_codec/tests/package_boundary.rs | 7+++----
Mcrates/event_store/contracts/source_maintenance_v1.manifest.json | 14+++++++-------
Mcrates/event_store/contracts/source_maintenance_v1.manifest.sha256 | 2+-
Mcrates/event_store/src/generated/source_maintenance_manifest.rs | 4++--
Mcrates/event_store/src/nip09/reconciliation_v1.rs | 5++++-
Mcrates/identity/tests/package_boundary.rs | 16----------------
Dcrates/net/Cargo.toml | 72------------------------------------------------------------------------
Dcrates/net/README | 26--------------------------
Dcrates/net/build.rs | 77-----------------------------------------------------------------------------
Dcrates/net/src/builder.rs | 53-----------------------------------------------------
Dcrates/net/src/config.rs | 20--------------------
Dcrates/net/src/error.rs | 72------------------------------------------------------------------------
Dcrates/net/src/keys.rs | 350-------------------------------------------------------------------------------
Dcrates/net/src/lib.rs | 21---------------------
Dcrates/net/src/logging.rs | 84-------------------------------------------------------------------------------
Dcrates/net/src/net.rs | 248-------------------------------------------------------------------------------
Dcrates/net/src/nostr_client/connection.rs | 46----------------------------------------------
Dcrates/net/src/nostr_client/events/fetch.rs | 31-------------------------------
Dcrates/net/src/nostr_client/events/post.rs | 81-------------------------------------------------------------------------------
Dcrates/net/src/nostr_client/events/profile.rs | 54------------------------------------------------------
Dcrates/net/src/nostr_client/inner.rs | 45---------------------------------------------
Dcrates/net/src/nostr_client/manager.rs | 93-------------------------------------------------------------------------------
Dcrates/net/src/nostr_client/mod.rs | 13-------------
Dcrates/net/src/nostr_client/status.rs | 75---------------------------------------------------------------------------
Dcrates/net/src/nostr_client/types.rs | 14--------------
Dcrates/net/tests/error.rs | 38--------------------------------------
Dcrates/nostr_runtime/Cargo.toml | 37-------------------------------------
Dcrates/nostr_runtime/README | 39---------------------------------------
Dcrates/nostr_runtime/src/error.rs | 69---------------------------------------------------------------------
Dcrates/nostr_runtime/src/lib.rs | 26--------------------------
Dcrates/nostr_runtime/src/runtime.rs | 572-------------------------------------------------------------------------------
Dcrates/nostr_runtime/src/sink.rs | 65-----------------------------------------------------------------
Dcrates/nostr_runtime/src/types.rs | 277-------------------------------------------------------------------------------
Mcrates/nostr_signer/README | 11+++++------
Mcrates/nostrdb/Cargo.toml | 6------
Mcrates/nostrdb/README | 4+---
Mcrates/nostrdb/src/lib.rs | 4----
Dcrates/nostrdb/src/runtime_adapter.rs | 119-------------------------------------------------------------------------------
Mcrates/trade/tests/operational_listing_conformance.rs | 6++++--
Mcrates/trade_sp1_host/src/lib.rs | 137+++++++------------------------------------------------------------------------
Mcrates/transport_nostr/tests/legacy_quarantine.rs | 74+++++++++++++++++++++++---------------------------------------------------
Mcrates/transport_nostr/tests/workspace_consumers.rs | 34+++++++++++-----------------------
Mdocs/implementation/COMPATIBILITY_SHIMS.md | 12++++++++----
Mdocs/implementation/deviations.toml | 24++++++++++++++----------
Mtools/xtask/src/contract/source_maintenance.rs | 2+-
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 {