commit 7f5b979d04b65bd1847b0cc2c317a2daa9273127 parent fde1b7728f0b9fac1ecbf137d2e80d68b450dea9 Author: triesap <tyson@radroots.org> Date: Fri, 31 Jul 2026 09:03:21 +0000 nostr: remove live relay-client dependencies - Move relay clients and NIP-11 HTTP ownership into transport. - Normalize live transport outputs and errors behind Radroots-owned types. - Update net/runtime consumers and remove portable client feature paths. - Refresh API, dependency, and source-maintenance boundary contracts. Diffstat:
41 files changed, 798 insertions(+), 1163 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock @@ -4690,6 +4690,7 @@ dependencies = [ "radroots_nostr_accounts", "radroots_nostr_signer", "radroots_runtime_paths", + "radroots_transport_nostr", "secrecy", "serde", "serde_json", @@ -4705,7 +4706,6 @@ version = "0.1.0-alpha" dependencies = [ "base64 0.22.1", "nostr", - "nostr-sdk", "radroots_blossom", "radroots_event", "radroots_event_codec", @@ -4713,7 +4713,6 @@ dependencies = [ "radroots_protocol", "radroots_signing", "radroots_test_fixtures", - "reqwest", "serde", "serde_json", "thiserror 1.0.69", @@ -4757,6 +4756,7 @@ dependencies = [ "futures", "nostr", "radroots_nostr", + "radroots_transport_nostr", "thiserror 1.0.69", "tokio", ] @@ -5191,11 +5191,13 @@ version = "0.1.0-alpha" dependencies = [ "futures", "nostr", + "nostr-sdk", "radroots_event", "radroots_event_store", "radroots_nostr", "radroots_outbox", "radroots_transport", + "reqwest", "serde", "serde_json", "thiserror 1.0.69", diff --git a/contracts/releases/api_boundaries.toml b/contracts/releases/api_boundaries.toml @@ -98,63 +98,6 @@ allowed_public_paths = [] name = "radroots" allowed_public_paths = [] [[exception]] -id = "RCRV1-API-003" -package = "radroots_nostr" -source = "src/client.rs" -forbidden_path = "nostr_sdk" -items = [ - "client::RadrootsNostrClient::from_inner", - "client::RadrootsNostrClient::into_inner", -] -observed_paths = ["nostr_sdk::Client"] -adr = "docs/decisions/0001-public-api-leakage-migration-baseline.md" -removal_step = 124 -rationale = "Legacy client ownership remains visible only until the Nostr package removes live relay-client dependencies." - -[[exception]] -id = "RCRV1-API-004" -package = "radroots_nostr" -source = "src/error.rs" -forbidden_path = "nostr_sdk" -items = ["error::RadrootsNostrError"] -observed_paths = [ - "nostr_sdk::client::Error", - "nostr_sdk::prelude::DatabaseError", -] -adr = "docs/decisions/0001-public-api-leakage-migration-baseline.md" -removal_step = 124 -rationale = "Legacy SDK error variants remain exact-scoped until normalized adapter errors replace them." - -[[exception]] -id = "RCRV1-API-005" -package = "radroots_nostr" -source = "src/types.rs" -forbidden_path = "nostr_sdk" -items = [ - "types::RadrootsNostrEventStream", - "types::RadrootsNostrMonitor", - "types::RadrootsNostrMonitorNotification", - "types::RadrootsNostrOutput", - "types::RadrootsNostrRelay", - "types::RadrootsNostrRelayPoolNotification", - "types::RadrootsNostrRelayStatus", - "types::RadrootsNostrSubscribeAutoCloseOptions", -] -observed_paths = [ - "nostr_sdk::Relay", - "nostr_sdk::RelayPoolNotification", - "nostr_sdk::RelayStatus", - "nostr_sdk::SubscribeAutoCloseOptions", - "nostr_sdk::pool::stream::BoxedStream", - "nostr_sdk::prelude::Monitor", - "nostr_sdk::prelude::MonitorNotification", - "nostr_sdk::prelude::Output", -] -adr = "docs/decisions/0001-public-api-leakage-migration-baseline.md" -removal_step = 124 -rationale = "Broad live-client aliases are frozen and must be removed with the Nostr client dependency at Step 124." - -[[exception]] id = "RCRV1-API-006" package = "radroots_nostr_connect" source = "src/client.rs" diff --git a/crates/event_store/contracts/source_maintenance_v1.manifest.json b/crates/event_store/contracts/source_maintenance_v1.manifest.json @@ -873,8 +873,8 @@ { "role": "contract_command_authority", "path": "tools/xtask/src/contract.rs", - "byte_length": 508630, - "sha256": "203cc8578100894be51122d69cfb431650c3b5331e4071564f4b1095b0b46b94", + "byte_length": 508285, + "sha256": "885595ff4e29501d0ffc30378ccfbf7e3f8922d5a4526ec0942c56cea643cc5d", "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 @@ -ae9be4735520b5756a03e19b31d86cb7d8566f31727793d9c60a60992db9f8eb +c60465365397f88f6efb661fd8db2ae8bd5f69718ea979e4a231f36bfdf0f526 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\": 12440,\n \"sha256\": \"8981f78d3ea85454a8cf7be156b3eef5659b399f1631d7c1068e7c336988070d\",\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\": 34745,\n \"sha256\": \"24b7fda13229f21cee5cef2a9941e3aaafdbc486b19ed42731b0b6ee37c28703\",\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\": 1570,\n \"sha256\": \"e2116eb02eb2b18b21db7351f29a1efeffe11ce4ab9bb7c85a3f3e7f2e56aca4\",\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\": 3844,\n \"sha256\": \"3cd9653bcb752fb3c4442d4904b98a0a6208011a9a238125b7b7073d7f4e312b\",\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\": 842402,\n \"sha256\": \"4abb141771cfa257440dba7ebeae21f5ad1f9856cc0d5f6437ba68614d997810\",\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\": \"65b83a3ee1a700527d15c7403ca042cf2aaa4cd8935a2f8d2ab190c491a815bd\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 508630,\n \"sha256\": \"203cc8578100894be51122d69cfb431650c3b5331e4071564f4b1095b0b46b94\",\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\": 16426,\n \"sha256\": \"abc24896c6c740c10811a3e003ec4be8ebdb376a7b6b01445bfa7a5889f094a7\",\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\": 12440,\n \"sha256\": \"8981f78d3ea85454a8cf7be156b3eef5659b399f1631d7c1068e7c336988070d\",\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\": 34745,\n \"sha256\": \"24b7fda13229f21cee5cef2a9941e3aaafdbc486b19ed42731b0b6ee37c28703\",\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\": 1570,\n \"sha256\": \"e2116eb02eb2b18b21db7351f29a1efeffe11ce4ab9bb7c85a3f3e7f2e56aca4\",\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\": 3844,\n \"sha256\": \"3cd9653bcb752fb3c4442d4904b98a0a6208011a9a238125b7b7073d7f4e312b\",\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\": 842402,\n \"sha256\": \"4abb141771cfa257440dba7ebeae21f5ad1f9856cc0d5f6437ba68614d997810\",\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\": \"65b83a3ee1a700527d15c7403ca042cf2aaa4cd8935a2f8d2ab190c491a815bd\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 508285,\n \"sha256\": \"885595ff4e29501d0ffc30378ccfbf7e3f8922d5a4526ec0942c56cea643cc5d\",\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\": 16426,\n \"sha256\": \"abc24896c6c740c10811a3e003ec4be8ebdb376a7b6b01445bfa7a5889f094a7\",\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 = - "ae9be4735520b5756a03e19b31d86cb7d8566f31727793d9c60a60992db9f8eb"; + "c60465365397f88f6efb661fd8db2ae8bd5f69718ea979e4a231f36bfdf0f526"; 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/net/Cargo.toml b/crates/net/Cargo.toml @@ -27,6 +27,7 @@ nostr-client = [ "dep:tempfile", "dep:serde_json", "dep:radroots_nostr", + "dep:radroots_transport_nostr", ] directories = ["std", "dep:directories"] fs-persistence = ["std", "dep:radroots_runtime_paths"] @@ -43,10 +44,12 @@ radroots_event_codec = { workspace = true, optional = true, default-features = t "std", ] } radroots_nostr = { workspace = true, optional = true, default-features = true, features = [ - "client", "events", "codec", ] } +radroots_transport_nostr = { workspace = true, optional = true, default-features = false, features = [ + "client", +] } directories = { workspace = true, optional = true } hex = { workspace = true, optional = true } radroots_runtime_paths = { workspace = true, optional = true } diff --git a/crates/net/src/nostr_client/events/post.rs b/crates/net/src/nostr_client/events/post.rs @@ -3,8 +3,7 @@ use radroots_event::{post::AuthoredUpdate, post::reply::AuthoredNip10Reply}; use radroots_event_codec::{parsed::RadrootsParsedData, post::decode::LegacyPost}; use radroots_nostr::prelude::{ radroots_nostr_build_nip10_reply_event, radroots_nostr_build_update_event, - radroots_nostr_fetch_post_events, radroots_nostr_send_nip10_reply_event, - radroots_nostr_send_post_event, + radroots_nostr_post_events_filter, }; use crate::nostr_client::manager::NostrClientManager; @@ -13,9 +12,15 @@ impl NostrClientManager { pub async fn publish_update_event(&self, update: &AuthoredUpdate) -> Result<String> { let builder = radroots_nostr_build_update_event(update).map_err(|e| NetError::Msg(e.to_string()))?; - let out = radroots_nostr_send_post_event(&self.inner.client, builder) + 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(|e| NetError::Msg(e.to_string()))?; + .map_err(|error| NetError::Msg(error.to_string()))?; Ok(out.val.to_string()) } @@ -28,9 +33,15 @@ impl NostrClientManager { pub async fn publish_nip10_reply_event(&self, reply: &AuthoredNip10Reply) -> Result<String> { let builder = radroots_nostr_build_nip10_reply_event(reply) .map_err(|e| NetError::Msg(e.to_string()))?; - let out = radroots_nostr_send_nip10_reply_event(&self.inner.client, builder) + 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(|e| NetError::Msg(e.to_string()))?; + .map_err(|error| NetError::Msg(error.to_string()))?; Ok(out.val.to_string()) } @@ -48,10 +59,17 @@ impl NostrClientManager { limit: u16, since_unix: Option<u64>, ) -> Result<Vec<RadrootsParsedData<LegacyPost>>> { - let items = radroots_nostr_fetch_post_events(&self.inner.client, limit, since_unix) + let filter = radroots_nostr_post_events_filter(Some(limit), since_unix); + let events = self + .inner + .client + .fetch_events(filter, core::time::Duration::from_secs(10)) .await - .map_err(|e| NetError::Msg(e.to_string()))?; - Ok(items) + .map_err(|error| NetError::Msg(error.to_string()))?; + Ok(events + .into_iter() + .map(|event| radroots_nostr::event_adapters::to_post_event_metadata(&event)) + .collect()) } pub fn fetch_post_events_blocking( diff --git a/crates/net/src/nostr_client/events/profile.rs b/crates/net/src/nostr_client/events/profile.rs @@ -1,7 +1,7 @@ use crate::error::{NetError, Result}; use radroots_event_codec::parsed::RadrootsParsedData; use radroots_event_codec::profile::RadrootsProfileData; -use radroots_nostr::prelude::{RadrootsNostrPublicKey, radroots_nostr_fetch_metadata_for_author}; +use radroots_nostr::prelude::{RadrootsNostrFilter, RadrootsNostrKind, RadrootsNostrPublicKey}; use crate::nostr_client::manager::NostrClientManager; @@ -10,13 +10,26 @@ impl NostrClientManager { &self, author: RadrootsNostrPublicKey, ) -> Result<Option<RadrootsParsedData<RadrootsProfileData>>> { - let ev = radroots_nostr_fetch_metadata_for_author( - &self.inner.client, - author, - core::time::Duration::from_secs(5), - ) - .await - .map_err(|e| NetError::Msg(e.to_string()))?; + 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_adapters::to_profile_event_metadata(&e) { return Ok(Some(meta)); diff --git a/crates/net/src/nostr_client/inner.rs b/crates/net/src/nostr_client/inner.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; use radroots_event_codec::{parsed::RadrootsParsedData, post::decode::LegacyPost}; -use radroots_nostr::prelude::{ - RadrootsNostrClient, RadrootsNostrKeys, RadrootsNostrMonitor, RadrootsNostrRelayStatus, - RadrootsNostrRelayUrl, +use radroots_nostr::prelude::{RadrootsNostrKeys, RadrootsNostrRelayUrl}; +use radroots_transport_nostr::{ + RadrootsNostrClient, RadrootsNostrMonitor, RadrootsNostrRelayStatus, }; use tokio::runtime::Handle; use tokio::sync::broadcast; @@ -12,6 +12,7 @@ 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<RadrootsNostrRelayUrl, RadrootsNostrRelayStatus>>>, pub last_error: Arc<Mutex<Option<String>>>, @@ -23,11 +24,12 @@ pub(super) struct Inner { impl Inner { pub fn new(keys: RadrootsNostrKeys, rt: Handle) -> Arc<Self> { let monitor = RadrootsNostrMonitor::new(2048); - let client = RadrootsNostrClient::new_with_monitor(keys, monitor); + let client = RadrootsNostrClient::new_with_monitor(keys.clone(), 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)), diff --git a/crates/net/src/nostr_client/status.rs b/crates/net/src/nostr_client/status.rs @@ -1,4 +1,4 @@ -use radroots_nostr::prelude::{RadrootsNostrMonitorNotification, RadrootsNostrRelayStatus}; +use radroots_transport_nostr::{RadrootsNostrMonitorNotification, RadrootsNostrRelayStatus}; use tracing::{info, warn}; use super::manager::NostrClientManager; diff --git a/crates/nostr/Cargo.toml b/crates/nostr/Cargo.toml @@ -27,14 +27,12 @@ std = [ "radroots_identity/std", ] blossom = ["std", "dep:base64", "dep:radroots_blossom"] -client = ["std", "dep:nostr-sdk"] codec = [] events = [ "std", "radroots_event/serde", "radroots_event_codec/json", ] -http = ["dep:reqwest"] nip17 = ["std", "codec", "nostr/nip44", "nostr/nip59"] signing = ["events", "dep:radroots_signing", "radroots_signing/std"] @@ -49,11 +47,6 @@ nostr = { workspace = true, default-features = false, features = [ "alloc", "nip04", ] } -nostr-sdk = { workspace = true, optional = true } -reqwest = { workspace = true, optional = true, default-features = false, features = [ - "json", - "rustls-tls", -] } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/nostr/src/client.rs b/crates/nostr/src/client.rs @@ -1,615 +0,0 @@ -#![forbid(unsafe_code)] - -use core::time::Duration; -use std::collections::HashMap; -#[cfg(not(target_arch = "wasm32"))] -use std::net::SocketAddr; - -use nostr_sdk::{Client, ClientBuilder, ClientOptions}; - -use crate::error::RadrootsNostrError; -#[cfg(feature = "events")] -use crate::events::comment::RadrootsNostrNip22CommentEventBuilder; -#[cfg(feature = "events")] -use crate::events::deletion::RadrootsNostrNip09DeletionRequestEventBuilder; -#[cfg(feature = "events")] -use crate::events::food_availability::RadrootsNostrFoodAvailabilityEventBuilder; -#[cfg(feature = "events")] -use crate::events::metadata::RadrootsNostrProfileEventBuilder; -#[cfg(feature = "events")] -use crate::events::post::RadrootsNostrPostEventBuilder; -#[cfg(feature = "events")] -use crate::events::reply::RadrootsNostrNip10ReplyEventBuilder; -use crate::types::{ - RadrootsNostrEvent, RadrootsNostrEventId, RadrootsNostrEventStream, RadrootsNostrFilter, - RadrootsNostrGenericEventBuilder, RadrootsNostrKeys, RadrootsNostrMonitor, RadrootsNostrOutput, - RadrootsNostrPublicKey, RadrootsNostrRelay, RadrootsNostrRelayUrl, - RadrootsNostrSubscribeAutoCloseOptions, RadrootsNostrSubscriptionId, -}; - -#[derive(Clone)] -pub struct RadrootsNostrClient { - inner: Client, -} - -#[derive(Debug, Clone, Default)] -pub struct RadrootsNostrClientOptions { - automatic_authentication: Option<bool>, - max_avg_latency_ms: Option<u64>, - verify_subscriptions: Option<bool>, - ban_relay_on_mismatch: Option<bool>, - #[cfg(not(target_arch = "wasm32"))] - proxy: Option<SocketAddr>, -} - -impl RadrootsNostrClientOptions { - pub fn new() -> Self { - Self::default() - } - - pub fn automatic_authentication(mut self, enabled: bool) -> Self { - self.automatic_authentication = Some(enabled); - self - } - - pub fn max_avg_latency_ms(mut self, max_ms: u64) -> Self { - self.max_avg_latency_ms = Some(max_ms); - self - } - - pub fn verify_subscriptions(mut self, enabled: bool) -> Self { - self.verify_subscriptions = Some(enabled); - self - } - - pub fn ban_relay_on_mismatch(mut self, enabled: bool) -> Self { - self.ban_relay_on_mismatch = Some(enabled); - self - } - - #[cfg(not(target_arch = "wasm32"))] - pub fn proxy_addr(mut self, addr: SocketAddr) -> Self { - self.proxy = Some(addr); - self - } - - #[cfg(not(target_arch = "wasm32"))] - pub fn proxy_str(mut self, addr: &str) -> Result<Self, RadrootsNostrError> { - let parsed: SocketAddr = addr.parse().map_err(|err: std::net::AddrParseError| { - RadrootsNostrError::ClientConfigError(err.to_string()) - })?; - self.proxy = Some(parsed); - Ok(self) - } - - fn to_client_options(&self) -> Result<ClientOptions, RadrootsNostrError> { - let mut opts = ClientOptions::new(); - if let Some(enabled) = self.automatic_authentication { - opts = opts.automatic_authentication(enabled); - } - if let Some(max_ms) = self.max_avg_latency_ms { - opts = opts.max_avg_latency(Duration::from_millis(max_ms)); - } - if let Some(enabled) = self.verify_subscriptions { - opts = opts.verify_subscriptions(enabled); - } - if let Some(enabled) = self.ban_relay_on_mismatch { - opts = opts.ban_relay_on_mismatch(enabled); - } - #[cfg(not(target_arch = "wasm32"))] - if let Some(proxy) = self.proxy { - let connection = nostr_sdk::client::options::Connection::new().proxy(proxy); - opts = opts.connection(connection); - } - Ok(opts) - } -} - -impl RadrootsNostrClient { - pub fn new_signerless() -> Self { - Self { - inner: Client::default(), - } - } - - pub fn new_signerless_with_options( - options: RadrootsNostrClientOptions, - ) -> Result<Self, RadrootsNostrError> { - let opts = options.to_client_options()?; - let inner = ClientBuilder::new().opts(opts).build(); - Ok(Self { inner }) - } - - pub fn new(keys: RadrootsNostrKeys) -> Self { - Self { - inner: Client::new(keys), - } - } - - pub fn from_keys_with_options( - keys: RadrootsNostrKeys, - options: RadrootsNostrClientOptions, - ) -> Result<Self, RadrootsNostrError> { - let opts = options.to_client_options()?; - let inner = ClientBuilder::new().signer(keys).opts(opts).build(); - Ok(Self { inner }) - } - - pub fn new_with_monitor(keys: RadrootsNostrKeys, monitor: RadrootsNostrMonitor) -> Self { - let inner = Client::builder().signer(keys).monitor(monitor).build(); - Self { inner } - } - - pub fn from_inner(inner: Client) -> Self { - Self { inner } - } - - /// Returns the underlying SDK client for explicit low-level interoperability. - pub fn into_inner(self) -> Client { - self.inner - } - - pub async fn has_signer(&self) -> bool { - self.inner.has_signer().await - } - - pub async fn public_key(&self) -> Result<RadrootsNostrPublicKey, RadrootsNostrError> { - Ok(self.inner.public_key().await?) - } - - pub fn monitor(&self) -> Option<&RadrootsNostrMonitor> { - self.inner.monitor() - } - - pub async fn connect(&self) { - self.inner.connect().await; - } - - pub async fn wait_for_connection(&self, timeout: Duration) { - self.inner.wait_for_connection(timeout).await; - } - - pub async fn try_connect(&self, timeout: Duration) -> RadrootsNostrOutput<()> { - self.inner.try_connect(timeout).await - } - - pub async fn add_relay(&self, url: &str) -> Result<bool, RadrootsNostrError> { - Ok(self.inner.add_relay(url).await?) - } - - pub async fn add_write_relay(&self, url: &str) -> Result<bool, RadrootsNostrError> { - Ok(self.inner.add_write_relay(url).await?) - } - - pub async fn add_read_relay(&self, url: &str) -> Result<bool, RadrootsNostrError> { - Ok(self.inner.add_read_relay(url).await?) - } - - pub async fn remove_relay(&self, url: &str) -> Result<(), RadrootsNostrError> { - self.inner.force_remove_relay(url).await?; - Ok(()) - } - - pub async fn relays(&self) -> HashMap<RadrootsNostrRelayUrl, RadrootsNostrRelay> { - self.inner.relays().await - } - - pub async fn fetch_events( - &self, - filter: RadrootsNostrFilter, - timeout: Duration, - ) -> Result<Vec<RadrootsNostrEvent>, RadrootsNostrError> { - let events = self.inner.fetch_events(filter, timeout).await?; - Ok(events.to_vec()) - } - - pub async fn query_database( - &self, - filter: RadrootsNostrFilter, - ) -> Result<Vec<RadrootsNostrEvent>, RadrootsNostrError> { - Ok(self.inner.database().query(filter).await?.to_vec()) - } - - pub async fn stream_events( - &self, - filter: RadrootsNostrFilter, - timeout: Duration, - ) -> Result<RadrootsNostrEventStream, RadrootsNostrError> { - Ok(self.inner.stream_events(filter, timeout).await?) - } - - pub async fn subscribe( - &self, - filter: RadrootsNostrFilter, - opts: Option<RadrootsNostrSubscribeAutoCloseOptions>, - ) -> Result<RadrootsNostrOutput<RadrootsNostrSubscriptionId>, RadrootsNostrError> { - Ok(self.inner.subscribe(filter, opts).await?) - } - - pub async fn subscribe_to_relays( - &self, - relays: &[RadrootsNostrRelayUrl], - filter: RadrootsNostrFilter, - opts: Option<RadrootsNostrSubscribeAutoCloseOptions>, - ) -> Result<RadrootsNostrOutput<RadrootsNostrSubscriptionId>, RadrootsNostrError> { - Ok(self - .inner - .subscribe_to(relays.iter().cloned(), filter, opts) - .await?) - } - - pub async fn unsubscribe(&self, subscription_id: &RadrootsNostrSubscriptionId) { - self.inner.unsubscribe(subscription_id).await; - } - - /// Publishes a generic event builder. - /// - /// Kind 0 profiles, all kind 1 events, all kind 5 deletion requests, kind - /// 1111 Comments, and focused or mixed kind 30402 FoodAvailability marker - /// partitions are rejected because their product shape must come from - /// typed authoring. Marker-free NIP-99 and operational-only kind 30402 - /// builders remain available for compatibility. - pub async fn send_event_builder( - &self, - event: RadrootsNostrGenericEventBuilder, - ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - let event = event.into_checked_event_builder()?; - Ok(self.inner.send_event_builder(event).await?) - } - - /// Publishes a validated kind-0 Profile replacement snapshot. - /// - /// Media-bearing callers must prove successful BUD-02 upload first. - #[cfg(feature = "events")] - pub async fn send_profile_event_builder( - &self, - event: RadrootsNostrProfileEventBuilder, - ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - Ok(self - .inner - .send_event_builder(event.into_event_builder()) - .await?) - } - - /// Publishes a validated root post through the sealed typed boundary. - #[cfg(feature = "events")] - pub async fn send_post_event_builder( - &self, - event: RadrootsNostrPostEventBuilder, - ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - Ok(self - .inner - .send_event_builder(event.into_event_builder()) - .await?) - } - - /// Publishes a validated strict marked NIP-10 Reply. - #[cfg(feature = "events")] - pub async fn send_nip10_reply_event_builder( - &self, - event: RadrootsNostrNip10ReplyEventBuilder, - ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - Ok(self - .inner - .send_event_builder(event.into_event_builder()) - .await?) - } - - /// Publishes a validated NIP-09 deletion request. - /// - /// Publication establishes no target-deletion authorization or effect. - #[cfg(feature = "events")] - pub async fn send_nip09_deletion_request_event_builder( - &self, - event: RadrootsNostrNip09DeletionRequestEventBuilder, - ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - Ok(self - .inner - .send_event_builder(event.into_event_builder()) - .await?) - } - - /// Publishes a validated strict NIP-22 Comment. - #[cfg(feature = "events")] - pub async fn send_nip22_comment_event_builder( - &self, - event: RadrootsNostrNip22CommentEventBuilder, - ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - Ok(self - .inner - .send_event_builder(event.into_event_builder()) - .await?) - } - - /// Publishes a validated focused FoodAvailability event. - /// - /// Media-bearing callers must prove successful BUD-02 upload first. - #[cfg(feature = "events")] - pub async fn send_food_availability_event_builder( - &self, - event: RadrootsNostrFoodAvailabilityEventBuilder, - ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - Ok(self - .inner - .send_event_builder(event.into_event_builder()) - .await?) - } - - /// Relays a caller-supplied signed event. - /// - /// This is a transport boundary, not an authored-builder boundary. The - /// caller is responsible for the event's authoring policy and signature. - pub async fn send_event( - &self, - event: &RadrootsNostrEvent, - ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - Ok(self.inner.send_event(event).await?) - } - - pub async fn send_event_to_relays( - &self, - relays: &[RadrootsNostrRelayUrl], - event: &RadrootsNostrEvent, - ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - Ok(self - .inner - .send_event_to(relays.iter().cloned(), event) - .await?) - } - - pub async fn send_event_to( - &self, - relays: Vec<String>, - event: &RadrootsNostrEvent, - ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - Ok(self.inner.send_event_to(relays, event).await?) - } -} - -/// Publishes a generic builder subject to typed-authoring reservations. -pub async fn radroots_nostr_send_event( - client: &RadrootsNostrClient, - event: RadrootsNostrGenericEventBuilder, -) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - client.send_event_builder(event).await -} - -/// Publishes a validated kind-0 Profile replacement snapshot. -/// -/// Media-bearing callers must prove successful BUD-02 upload first. -#[cfg(feature = "events")] -pub async fn radroots_nostr_send_profile_event( - client: &RadrootsNostrClient, - event: RadrootsNostrProfileEventBuilder, -) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - client.send_profile_event_builder(event).await -} - -/// Publishes a validated root post through the sealed typed boundary. -#[cfg(feature = "events")] -pub async fn radroots_nostr_send_post_event( - client: &RadrootsNostrClient, - event: RadrootsNostrPostEventBuilder, -) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - client.send_post_event_builder(event).await -} - -/// Publishes a validated strict marked NIP-10 Reply. -#[cfg(feature = "events")] -pub async fn radroots_nostr_send_nip10_reply_event( - client: &RadrootsNostrClient, - event: RadrootsNostrNip10ReplyEventBuilder, -) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - client.send_nip10_reply_event_builder(event).await -} - -/// Publishes a validated NIP-09 deletion request. -/// -/// Publication establishes no target-deletion authorization or effect. -#[cfg(feature = "events")] -pub async fn radroots_nostr_send_nip09_deletion_request_event( - client: &RadrootsNostrClient, - event: RadrootsNostrNip09DeletionRequestEventBuilder, -) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - client - .send_nip09_deletion_request_event_builder(event) - .await -} - -/// Publishes a validated strict NIP-22 Comment. -#[cfg(feature = "events")] -pub async fn radroots_nostr_send_nip22_comment_event( - client: &RadrootsNostrClient, - event: RadrootsNostrNip22CommentEventBuilder, -) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - client.send_nip22_comment_event_builder(event).await -} - -/// Publishes a validated focused FoodAvailability event. -/// -/// Media-bearing callers must prove successful BUD-02 upload first. -#[cfg(feature = "events")] -pub async fn radroots_nostr_send_food_availability_event( - client: &RadrootsNostrClient, - event: RadrootsNostrFoodAvailabilityEventBuilder, -) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - client.send_food_availability_event_builder(event).await -} - -pub async fn radroots_nostr_fetch_event_by_id( - client: &RadrootsNostrClient, - id: &str, -) -> Result<RadrootsNostrEvent, RadrootsNostrError> { - let event_id = RadrootsNostrEventId::parse(id)?; - let filter = RadrootsNostrFilter::new().id(event_id); - let events = client.fetch_events(filter, Duration::from_secs(10)).await?; - let event = events - .first() - .ok_or_else(|| RadrootsNostrError::EventNotFound(event_id.to_hex()))?; - Ok(event.clone()) -} - -#[cfg(test)] -mod tests { - use super::{RadrootsNostrClient, RadrootsNostrClientOptions}; - use crate::error::RadrootsNostrError; - use crate::test_fixtures::FIXTURE_BOB_PUBLIC_KEY_HEX; - use crate::types::{ - RadrootsNostrFilter, RadrootsNostrGenericEventBuilder, RadrootsNostrKeys, - RadrootsNostrKind, RadrootsNostrSecretKey, RadrootsNostrSubscriptionId, RadrootsNostrTag, - }; - - #[tokio::test] - async fn signerless_client_has_no_signer() { - let client = RadrootsNostrClient::new_signerless(); - - assert!(!client.has_signer().await); - } - - #[tokio::test] - async fn signerless_client_with_options_has_no_signer() { - let client = RadrootsNostrClient::new_signerless_with_options( - RadrootsNostrClientOptions::new() - .automatic_authentication(true) - .verify_subscriptions(true), - ) - .expect("signerless client"); - - assert!(!client.has_signer().await); - } - - #[tokio::test] - async fn targeted_operations_require_relays_and_cleanup_is_explicit() { - let keys = RadrootsNostrKeys::new( - RadrootsNostrSecretKey::from_slice(&[1_u8; 32]).expect("test secret key"), - ); - let client = RadrootsNostrClient::new(keys.clone()); - let subscription = client - .subscribe_to_relays(&[], RadrootsNostrFilter::new(), None) - .await; - assert!(subscription.is_err()); - - let event = nostr::EventBuilder::new(RadrootsNostrKind::TextNote, "test") - .sign_with_keys(&keys) - .expect("test event"); - let published = client.send_event_to_relays(&[], &event).await; - assert!(published.is_err()); - - client - .unsubscribe(&RadrootsNostrSubscriptionId::new("missing")) - .await; - } - - #[tokio::test] - async fn generic_builder_rejects_all_typed_authoring_reservations_before_signer_access() { - let client = RadrootsNostrClient::new_signerless(); - let raw_kind_one = RadrootsNostrKind::Custom(RadrootsNostrKind::TextNote.as_u16()); - let deletion_request = - RadrootsNostrKind::Custom(radroots_event::envelope::kind::KIND_DELETION_REQUEST as u16); - let comment = - RadrootsNostrKind::Custom(radroots_event::envelope::kind::KIND_COMMENT as u16); - let classified_listing = RadrootsNostrKind::Custom(30_402); - let builders = vec![ - RadrootsNostrGenericEventBuilder::new(RadrootsNostrKind::Metadata, "{}"), - RadrootsNostrGenericEventBuilder::new(raw_kind_one, "Unmarked root"), - RadrootsNostrGenericEventBuilder::new(raw_kind_one, "Is it ripe?").tags([ - RadrootsNostrTag::parse(["t", "radroots-ask"]).expect("ask marker"), - RadrootsNostrTag::parse([ - "imeta", - "url https://media.example/ask.webp", - "m image/webp", - ]) - .expect("image metadata"), - ]), - RadrootsNostrGenericEventBuilder::new(deletion_request, "Raw deletion request"), - RadrootsNostrGenericEventBuilder::new(comment, "Raw Comment"), - RadrootsNostrGenericEventBuilder::new(classified_listing, "Focused").tag( - RadrootsNostrTag::parse(["radroots:price_unit", "lb"]).expect("focused marker"), - ), - RadrootsNostrGenericEventBuilder::new(classified_listing, "Ambiguous").tags([ - RadrootsNostrTag::parse(["radroots:price_unit", "lb"]).expect("focused marker"), - RadrootsNostrTag::parse(["radroots:primary_bin", "bin-1"]) - .expect("operational marker"), - ]), - ]; - - for builder in builders { - let error = client - .send_event_builder(builder) - .await - .expect_err("raw root kind-1 builder must be rejected before signer access"); - - assert!(matches!( - error, - RadrootsNostrError::TypedAuthoringRequired { .. } - )); - } - } - - #[tokio::test] - async fn generic_builder_rejects_e_tagged_thread_before_signer_access() { - let keys = RadrootsNostrKeys::new( - RadrootsNostrSecretKey::from_slice(&[3_u8; 32]).expect("test secret key"), - ); - let client = RadrootsNostrClient::new(keys); - let builder = RadrootsNostrGenericEventBuilder::text_note("Reply").tag( - crate::types::RadrootsNostrTag::event(crate::types::RadrootsNostrEventId::all_zeros()), - ); - - let error = client - .send_event_builder(builder) - .await - .expect_err("generic reply must fail before relay access"); - - assert!(matches!( - error, - RadrootsNostrError::TypedAuthoringRequired { .. } - )); - } - - #[cfg(feature = "events")] - #[tokio::test] - async fn sealed_post_builder_reaches_typed_client_publication() { - let keys = RadrootsNostrKeys::new( - RadrootsNostrSecretKey::from_slice(&[4_u8; 32]).expect("test secret key"), - ); - let client = RadrootsNostrClient::new(keys); - let update = - radroots_event::post::AuthoredUpdate::new("Farm update").expect("authored update"); - let builder = crate::events::post::radroots_nostr_build_update_event(&update) - .expect("sealed post builder"); - - let error = client - .send_post_event_builder(builder) - .await - .expect_err("no relay is configured"); - - assert!(matches!(error, RadrootsNostrError::ClientError(_))); - } - - #[cfg(feature = "events")] - #[tokio::test] - async fn sealed_nip10_reply_builder_reaches_typed_client_publication() { - let keys = RadrootsNostrKeys::new( - RadrootsNostrSecretKey::from_slice(&[5_u8; 32]).expect("test secret key"), - ); - let client = RadrootsNostrClient::new(keys); - let reference = radroots_event::post::reply::Nip10ReplyReference::parse( - "a".repeat(64), - FIXTURE_BOB_PUBLIC_KEY_HEX, - None, - ) - .expect("reference"); - let reply = radroots_event::post::reply::AuthoredNip10Reply::direct("Reply", reference) - .expect("reply"); - let builder = - crate::events::reply::radroots_nostr_build_nip10_reply_event(&reply).expect("builder"); - - let error = client - .send_nip10_reply_event_builder(builder) - .await - .expect_err("no relay is configured"); - - assert!(matches!(error, RadrootsNostrError::ClientError(_))); - } -} diff --git a/crates/nostr/src/error.rs b/crates/nostr/src/error.rs @@ -9,18 +9,6 @@ pub enum RadrootsNostrError { #[error("Radroots event field {field} cannot be represented by Nostr")] EventConversion { field: &'static str }, - #[cfg(feature = "client")] - #[error("Client error: {0}")] - ClientError(#[from] nostr_sdk::client::Error), - - #[cfg(feature = "client")] - #[error("Database error: {0}")] - DatabaseError(#[from] nostr_sdk::prelude::DatabaseError), - - #[cfg(feature = "client")] - #[error("Client configuration error: {0}")] - ClientConfigError(String), - #[error("Nostr event kind {kind} requires typed authoring")] TypedAuthoringRequired { kind: u16 }, @@ -42,9 +30,6 @@ pub enum RadrootsNostrError { #[error("Event error: {0}")] EventError(#[cfg_attr(feature = "std", source)] nostr::event::Error), - #[error("Event not found: {0}")] - EventNotFound(String), - #[error("Event builder failure: {0}")] EventBuildError(#[cfg_attr(feature = "std", source)] nostr::event::builder::Error), diff --git a/crates/nostr/src/events/application_handler.rs b/crates/nostr/src/events/application_handler.rs @@ -6,15 +6,7 @@ use alloc::{string::String, vec::Vec}; use crate::error::RadrootsNostrError; use crate::events::radroots_nostr_build_event_unchecked; -#[cfg(feature = "client")] -use crate::filter::radroots_nostr_filter_tag; -#[cfg(feature = "client")] -use crate::tags::radroots_nostr_tag_first_value; -#[cfg(feature = "client")] -use crate::types::{RadrootsNostrEvent, RadrootsNostrFilter, RadrootsNostrKind}; use crate::types::{RadrootsNostrGenericEventBuilder, RadrootsNostrMetadata}; -#[cfg(feature = "client")] -use core::time::Duration; use radroots_event::envelope::kind::KIND_APPLICATION_HANDLER; #[derive(Debug, Clone)] @@ -103,56 +95,6 @@ pub fn radroots_nostr_metadata_has_fields(md: &RadrootsNostrMetadata) -> bool { || !md.custom.is_empty() } -#[cfg(feature = "client")] -pub async fn radroots_nostr_publish_application_handler( - client: &crate::client::RadrootsNostrClient, - spec: &RadrootsNostrApplicationHandlerSpec, -) -> Result<crate::types::RadrootsNostrOutput<crate::types::RadrootsNostrEventId>, RadrootsNostrError> -{ - let mut spec = spec.clone(); - if spec.identifier.is_none() - && let Some(existing) = fetch_existing_identifier(client, &spec).await? - { - spec.identifier = Some(existing); - } - let builder = radroots_nostr_build_application_handler_event(&spec)?; - crate::client::radroots_nostr_send_event(client, builder).await -} - -#[cfg(feature = "client")] -async fn fetch_existing_identifier( - client: &crate::client::RadrootsNostrClient, - spec: &RadrootsNostrApplicationHandlerSpec, -) -> Result<Option<String>, RadrootsNostrError> { - let first_kind = spec - .kinds - .first() - .ok_or_else(|| RadrootsNostrError::FilterTagError("kinds are empty".to_string()))?; - let application_handler_kind = u16::try_from(KIND_APPLICATION_HANDLER).map_err(|_| { - RadrootsNostrError::KindOutOfRange { - kind: KIND_APPLICATION_HANDLER, - max: u16::MAX, - } - })?; - let author = client.public_key().await?; - let filter = RadrootsNostrFilter::new() - .author(author) - .kind(RadrootsNostrKind::Custom(application_handler_kind)); - let filter = radroots_nostr_filter_tag(filter, "k", vec![first_kind.to_string()])?; - let mut events = client.fetch_events(filter, Duration::from_secs(5)).await?; - events.sort_by_key(|event| event.created_at.as_secs()); - let event = events.pop(); - Ok(event.and_then(|event| tag_value(&event, "d"))) -} - -#[cfg(feature = "client")] -fn tag_value(event: &RadrootsNostrEvent, key: &str) -> Option<String> { - event - .tags - .iter() - .find_map(|tag| radroots_nostr_tag_first_value(tag, key)) -} - #[cfg(test)] mod tests { use super::radroots_nostr_metadata_has_fields; diff --git a/crates/nostr/src/events/comment.rs b/crates/nostr/src/events/comment.rs @@ -36,11 +36,6 @@ impl RadrootsNostrNip22CommentEventBuilder { ) -> Result<RadrootsNostrEvent, RadrootsNostrError> { Ok(self.inner.sign_with_keys(keys)?) } - - #[cfg(feature = "client")] - pub(crate) fn into_event_builder(self) -> RadrootsNostrEventBuilderUnchecked { - self.inner - } } pub fn radroots_nostr_build_nip22_comment_event( diff --git a/crates/nostr/src/events/deletion.rs b/crates/nostr/src/events/deletion.rs @@ -51,11 +51,6 @@ impl RadrootsNostrNip09DeletionRequestEventBuilder { ) -> Result<RadrootsNostrEvent, RadrootsNostrError> { Ok(self.inner.sign_with_keys(keys)?) } - - #[cfg(feature = "client")] - pub(crate) fn into_event_builder(self) -> RadrootsNostrEventBuilderUnchecked { - self.inner - } } pub fn radroots_nostr_build_nip09_deletion_request_event( diff --git a/crates/nostr/src/events/food_availability.rs b/crates/nostr/src/events/food_availability.rs @@ -47,11 +47,6 @@ impl RadrootsNostrFoodAvailabilityEventBuilder { ) -> Result<RadrootsNostrEvent, RadrootsNostrError> { Ok(self.inner.sign_with_keys(keys)?) } - - #[cfg(feature = "client")] - pub(crate) fn into_event_builder(self) -> RadrootsNostrEventBuilderUnchecked { - self.inner - } } /// Builds a sealed Nostr builder from strict FoodAvailability details. diff --git a/crates/nostr/src/events/metadata.rs b/crates/nostr/src/events/metadata.rs @@ -1,15 +1,9 @@ -#[cfg(feature = "client")] -use crate::client::RadrootsNostrClient; -#[cfg(any(feature = "client", feature = "events"))] +#[cfg(feature = "events")] use crate::error::RadrootsNostrError; -#[cfg(any(feature = "client", feature = "events"))] +#[cfg(feature = "events")] use crate::types::RadrootsNostrEvent; #[cfg(feature = "events")] use crate::types::{RadrootsNostrEventBuilderUnchecked, RadrootsNostrKeys, RadrootsNostrTimestamp}; -#[cfg(feature = "client")] -use crate::types::{RadrootsNostrFilter, RadrootsNostrKind, RadrootsNostrPublicKey}; -#[cfg(feature = "client")] -use core::time::Duration; #[cfg(feature = "events")] use radroots_event::profile::AuthoredProfile; #[cfg(feature = "events")] @@ -41,11 +35,6 @@ impl RadrootsNostrProfileEventBuilder { ) -> Result<RadrootsNostrEvent, RadrootsNostrError> { Ok(self.inner.sign_with_keys(keys)?) } - - #[cfg(feature = "client")] - pub(crate) fn into_event_builder(self) -> RadrootsNostrEventBuilderUnchecked { - self.inner - } } /// Builds a sealed kind-0 event from the strict authored Profile contract. @@ -58,32 +47,3 @@ pub fn radroots_nostr_build_profile_event( crate::events::radroots_nostr_build_event_unchecked(parts.kind, parts.content, parts.tags)?; Ok(RadrootsNostrProfileEventBuilder { inner }) } - -#[cfg(feature = "client")] -/// Fetches metadata through the legacy compatibility path. -/// -/// This helper is outside `profile.parse_inbound_metadata`; callers must not -/// treat its result as strict Profile admission. -pub async fn radroots_nostr_fetch_metadata_for_author( - client: &RadrootsNostrClient, - author: RadrootsNostrPublicKey, - timeout: Duration, -) -> Result<Option<RadrootsNostrEvent>, RadrootsNostrError> { - let filter = RadrootsNostrFilter::new() - .authors(vec![author]) - .kind(RadrootsNostrKind::Metadata); - let stored = client.query_database(filter.clone()).await?; - let fetched = client.fetch_events(filter, timeout).await?; - - let mut latest: Option<RadrootsNostrEvent> = None; - for ev in stored.into_iter().chain(fetched) { - if ev.kind != RadrootsNostrKind::Metadata { - continue; - } - match &latest { - Some(cur) if ev.created_at <= cur.created_at => {} - _ => latest = Some(ev), - } - } - Ok(latest) -} diff --git a/crates/nostr/src/events/post.rs b/crates/nostr/src/events/post.rs @@ -15,11 +15,6 @@ use radroots_event_codec::encode::post::{ authored_ask_to_wire_parts, authored_photo_update_to_wire_parts, authored_update_to_wire_parts, }; -#[cfg(all(feature = "client", feature = "events"))] -use crate::client::RadrootsNostrClient; -#[cfg(all(feature = "client", feature = "events"))] -use core::time::Duration; - /// A sealed builder for a validated Radroots root post profile. /// /// The wrapper intentionally exposes no raw builder conversion or tag/content @@ -45,11 +40,6 @@ impl RadrootsNostrPostEventBuilder { ) -> Result<RadrootsNostrEvent, RadrootsNostrError> { Ok(self.inner.sign_with_keys(keys)?) } - - #[cfg(feature = "client")] - pub(crate) fn into_event_builder(self) -> RadrootsNostrEventBuilderUnchecked { - self.inner - } } #[cfg(feature = "events")] @@ -95,32 +85,3 @@ fn builder_from_wire_parts( crate::events::radroots_nostr_build_event_unchecked(parts.kind, parts.content, parts.tags)?; Ok(RadrootsNostrPostEventBuilder { inner }) } - -#[cfg(all(feature = "client", feature = "events"))] -/// Fetches generic kind-1 events through the compatibility post projection. -/// -/// The unmarked filter intentionally retains ordinary Nostr notes and replies. -/// This compatibility read discards tags and does not establish Radroots -/// product admission; product consumers must use the verified admission API. -pub async fn radroots_nostr_fetch_post_events( - client: &RadrootsNostrClient, - limit: u16, - since_unix: Option<u64>, -) -> Result< - Vec< - radroots_event_codec::decode::RadrootsParsedData< - radroots_event_codec::decode::post::LegacyPost, - >, - >, - RadrootsNostrError, -> { - let filter = radroots_nostr_post_events_filter(Some(limit), since_unix); - - let events = client.fetch_events(filter, Duration::from_secs(10)).await?; - let out = events - .into_iter() - .map(|ev| crate::event_adapters::to_post_event_metadata(&ev)) - .collect(); - - Ok(out) -} diff --git a/crates/nostr/src/events/reply.rs b/crates/nostr/src/events/reply.rs @@ -36,11 +36,6 @@ impl RadrootsNostrNip10ReplyEventBuilder { ) -> Result<RadrootsNostrEvent, RadrootsNostrError> { Ok(self.inner.sign_with_keys(keys)?) } - - #[cfg(feature = "client")] - pub(crate) fn into_event_builder(self) -> RadrootsNostrEventBuilderUnchecked { - self.inner - } } pub fn radroots_nostr_build_nip10_reply_event( diff --git a/crates/nostr/src/lib.rs b/crates/nostr/src/lib.rs @@ -4,9 +4,6 @@ extern crate alloc; #[cfg(not(feature = "std"))] extern crate core as std; -#[cfg(feature = "client")] -pub mod client; - #[cfg(feature = "blossom")] pub mod blossom; @@ -16,8 +13,6 @@ pub mod events; pub mod filter; pub mod key; pub mod parse; -#[cfg(feature = "client")] -pub mod relays; pub mod tag; pub mod tags; pub mod types; @@ -40,9 +35,6 @@ pub mod nip17; #[cfg(feature = "signing")] pub mod signing; -#[cfg(feature = "http")] -pub mod nip11; - #[cfg(feature = "events")] pub mod event_adapters; @@ -64,19 +56,6 @@ pub mod prelude { radroots_nostr_sign_blossom_authorization, }; - #[cfg(feature = "client")] - pub use crate::client::{ - RadrootsNostrClient, RadrootsNostrClientOptions, radroots_nostr_fetch_event_by_id, - radroots_nostr_send_event, - }; - - #[cfg(all(feature = "client", feature = "events"))] - pub use crate::client::{ - radroots_nostr_send_food_availability_event, - radroots_nostr_send_nip09_deletion_request_event, radroots_nostr_send_nip10_reply_event, - radroots_nostr_send_nip22_comment_event, radroots_nostr_send_post_event, - }; - pub use crate::error::{RadrootsNostrError, RadrootsNostrTagsResolveError}; #[cfg(feature = "std")] pub use crate::filter::radroots_nostr_filter_new_events; @@ -127,23 +106,7 @@ pub mod prelude { radroots_nostr_metadata_has_fields, }; - #[cfg(all(feature = "client", feature = "events"))] - pub use crate::events::application_handler::radroots_nostr_publish_application_handler; - - #[cfg(feature = "client")] - pub use crate::events::metadata::radroots_nostr_fetch_metadata_for_author; - - #[cfg(all(feature = "client", feature = "events"))] - pub use crate::client::radroots_nostr_send_profile_event; - - #[cfg(all(feature = "client", feature = "events"))] - pub use crate::events::post::radroots_nostr_fetch_post_events; - pub use crate::parse::{radroots_nostr_parse_pubkey, radroots_nostr_parse_pubkeys}; - #[cfg(feature = "client")] - pub use crate::relays::{ - radroots_nostr_add_relay, radroots_nostr_connect, radroots_nostr_remove_relay, - }; pub use crate::tags::*; pub use crate::types::{ RadrootsNostrCoordinate, RadrootsNostrEvent, RadrootsNostrEventId, @@ -154,12 +117,6 @@ pub mod prelude { RadrootsNostrTag, RadrootsNostrTagKind, RadrootsNostrTagStandard, RadrootsNostrTimestamp, RadrootsNostrToBech32, RadrootsNostrUrl, }; - #[cfg(feature = "client")] - pub use crate::types::{ - RadrootsNostrEventStream, RadrootsNostrMonitor, RadrootsNostrMonitorNotification, - RadrootsNostrOutput, RadrootsNostrRelay, RadrootsNostrRelayPoolNotification, - RadrootsNostrRelayStatus, RadrootsNostrSubscribeAutoCloseOptions, - }; pub use crate::util::radroots_nostr_npub_string; #[cfg(feature = "nip17")] @@ -169,9 +126,6 @@ pub mod prelude { radroots_nostr_wrap_message_file, }; - #[cfg(feature = "http")] - pub use crate::nip11::fetch_nip11; - #[cfg(feature = "events")] pub use crate::event_adapters::{to_post_event_metadata, to_profile_event_metadata}; diff --git a/crates/nostr/src/nip11.rs b/crates/nostr/src/nip11.rs @@ -1,20 +0,0 @@ -#[cfg(all(feature = "http", feature = "codec"))] -use radroots_event::social::relay_document::RelayDocument; - -#[cfg(all(feature = "http", feature = "codec"))] -use crate::util::ws_to_http; - -#[cfg(all(feature = "http", feature = "codec"))] -pub async fn fetch_nip11(ws_url: &str) -> Option<RelayDocument> { - let http_url = ws_to_http(ws_url)?; - let client = reqwest::Client::new(); - client - .get(&http_url) - .header("Accept", "application/nostr+json") - .send() - .await - .ok()? - .json::<RelayDocument>() - .await - .ok() -} diff --git a/crates/nostr/src/relays.rs b/crates/nostr/src/relays.rs @@ -1,22 +0,0 @@ -use crate::client::RadrootsNostrClient; -use crate::error::RadrootsNostrError; - -pub async fn radroots_nostr_add_relay( - client: &RadrootsNostrClient, - url: &str, -) -> Result<(), RadrootsNostrError> { - client.add_relay(url).await?; - Ok(()) -} - -pub async fn radroots_nostr_remove_relay( - client: &RadrootsNostrClient, - url: &str, -) -> Result<(), RadrootsNostrError> { - client.remove_relay(url).await?; - Ok(()) -} - -pub async fn radroots_nostr_connect(client: &RadrootsNostrClient) { - client.connect().await; -} diff --git a/crates/nostr/src/types.rs b/crates/nostr/src/types.rs @@ -224,14 +224,6 @@ impl RadrootsNostrGenericEventBuilder { Self { inner } } - #[cfg(feature = "client")] - pub(crate) fn into_checked_event_builder( - self, - ) -> Result<RadrootsNostrEventBuilderUnchecked, RadrootsNostrError> { - self.validate_generic_authoring_policy()?; - Ok(self.inner) - } - #[cfg(feature = "std")] fn validate_generic_authoring_policy(&self) -> Result<(), RadrootsNostrError> { // Inspect an unsigned clone so rejection never consults a signer. PoW @@ -284,30 +276,6 @@ pub use nostr::nips::nip19::{ }; pub use nostr::secp256k1::SecretKey as RadrootsNostrSecp256k1SecretKey; -#[cfg(feature = "client")] -pub type RadrootsNostrMonitor = nostr_sdk::prelude::Monitor; - -#[cfg(feature = "client")] -pub type RadrootsNostrMonitorNotification = nostr_sdk::prelude::MonitorNotification; - -#[cfg(feature = "client")] -pub type RadrootsNostrOutput<T> = nostr_sdk::prelude::Output<T>; - -#[cfg(feature = "client")] -pub type RadrootsNostrEventStream = nostr_sdk::pool::stream::BoxedStream<RadrootsNostrEvent>; - -#[cfg(feature = "client")] -pub type RadrootsNostrRelay = nostr_sdk::Relay; - -#[cfg(feature = "client")] -pub type RadrootsNostrRelayPoolNotification = nostr_sdk::RelayPoolNotification; - -#[cfg(feature = "client")] -pub type RadrootsNostrRelayStatus = nostr_sdk::RelayStatus; - -#[cfg(feature = "client")] -pub type RadrootsNostrSubscribeAutoCloseOptions = nostr_sdk::SubscribeAutoCloseOptions; - #[cfg(test)] mod tests { use super::*; diff --git a/crates/nostr/src/util.rs b/crates/nostr/src/util.rs @@ -14,18 +14,3 @@ pub fn created_at_u32_saturating(ts: RadrootsNostrTimestamp) -> u32 { pub fn event_created_at_u32_saturating(event: &RadrootsNostrEvent) -> u32 { created_at_u32_saturating(event.created_at) } - -#[cfg(feature = "http")] -pub fn ws_to_http(ws: &str) -> Option<String> { - let mut u = reqwest::Url::parse(ws).ok()?; - let scheme = u.scheme().to_owned(); - - let new_scheme = match scheme.as_str() { - "wss" => "https", - "ws" => "http", - other => other, - }; - - u.set_scheme(new_scheme).ok()?; - Some(u.into()) -} diff --git a/crates/nostr/tests/food_availability_profile.rs b/crates/nostr/tests/food_availability_profile.rs @@ -15,9 +15,6 @@ use radroots_nostr::prelude::{ }; use radroots_test_fixtures::FIXTURE_ALICE_SECRET_KEY_HEX; -#[cfg(feature = "client")] -use radroots_nostr::prelude::RadrootsNostrClient; - const CREATED_AT: u64 = 1_784_347_200; #[test] @@ -156,24 +153,6 @@ fn generic_builder_retains_marker_free_and_operational_nip99_compatibility() { } } -#[cfg(feature = "client")] -#[tokio::test] -async fn typed_food_builder_reaches_client_publication() { - let client = RadrootsNostrClient::new(fixture_keys()); - let builder = radroots_nostr_build_food_availability_event( - &details(), - RadrootsNostrTimestamp::from_secs(CREATED_AT), - ) - .expect("typed FoodAvailability builder"); - - let error = client - .send_food_availability_event_builder(builder) - .await - .expect_err("no relay is configured"); - - assert!(matches!(error, RadrootsNostrError::ClientError(_))); -} - fn fixture_keys() -> RadrootsNostrKeys { RadrootsNostrKeys::new( RadrootsNostrSecretKey::from_hex(FIXTURE_ALICE_SECRET_KEY_HEX).expect("fixture secret key"), diff --git a/crates/nostr/tests/nip09_deletion_request_profile.rs b/crates/nostr/tests/nip09_deletion_request_profile.rs @@ -12,11 +12,6 @@ use radroots_nostr::prelude::{ }; use radroots_test_fixtures::{FIXTURE_ALICE_SECRET_KEY_HEX, FIXTURE_BOB_PUBLIC_KEY_HEX}; -#[cfg(feature = "client")] -use radroots_nostr::prelude::{ - RadrootsNostrClient, radroots_nostr_send_nip09_deletion_request_event, -}; - const CREATED_AT: u64 = 1_784_347_200; const TARGET_EVENT_ID: &str = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; @@ -86,48 +81,6 @@ fn generic_kind_five_builder_cannot_bypass_typed_deletion_authoring() { )); } -#[cfg(feature = "client")] -#[tokio::test] -async fn generic_kind_five_client_rejection_precedes_signer_access() { - let client = RadrootsNostrClient::new_signerless(); - let builder = RadrootsNostrGenericEventBuilder::new( - RadrootsNostrKind::Custom(KIND_DELETION_REQUEST as u16), - "Raw deletion request", - ); - - let error = client - .send_event_builder(builder) - .await - .expect_err("generic kind 5 must fail before signer access"); - - assert!(matches!( - error, - RadrootsNostrError::TypedAuthoringRequired { kind } - if kind == KIND_DELETION_REQUEST as u16 - )); -} - -#[cfg(feature = "client")] -#[tokio::test] -async fn typed_nip09_deletion_request_reaches_client_publication() { - let client = RadrootsNostrClient::new(fixture_keys()); - let method_builder = radroots_nostr_build_nip09_deletion_request_event(&request()) - .expect("typed NIP-09 builder"); - let helper_builder = radroots_nostr_build_nip09_deletion_request_event(&request()) - .expect("typed NIP-09 builder"); - - let method_error = client - .send_nip09_deletion_request_event_builder(method_builder) - .await - .expect_err("no relay is configured"); - let helper_error = radroots_nostr_send_nip09_deletion_request_event(&client, helper_builder) - .await - .expect_err("no relay is configured"); - - assert!(matches!(method_error, RadrootsNostrError::ClientError(_))); - assert!(matches!(helper_error, RadrootsNostrError::ClientError(_))); -} - fn request() -> AuthoredNip09DeletionRequest { AuthoredNip09DeletionRequest::new( "superseded", diff --git a/crates/nostr/tests/nip10_reply_profile.rs b/crates/nostr/tests/nip10_reply_profile.rs @@ -16,9 +16,6 @@ use radroots_test_fixtures::{ RELAY_PRIMARY_WSS, RELAY_SECONDARY_WSS, }; -#[cfg(feature = "client")] -use radroots_nostr::prelude::RadrootsNostrClient; - const CREATED_AT: u64 = 1_784_347_200; const ROOT_EVENT_ID: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const PARENT_EVENT_ID: &str = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"; @@ -178,30 +175,6 @@ fn generic_kind_one_builder_cannot_bypass_typed_nip10_reply_authoring() { } } -#[cfg(feature = "client")] -#[tokio::test] -async fn typed_nip10_reply_builder_reaches_client_publication() { - let client = RadrootsNostrClient::new(fixture_keys()); - let reply = AuthoredNip10Reply::direct( - "Publish Reply", - reference( - ROOT_EVENT_ID, - FIXTURE_BOB_PUBLIC_KEY_HEX, - Some(RELAY_PRIMARY_WSS), - ), - ) - .expect("authored direct reply"); - let builder = - radroots_nostr_build_nip10_reply_event(&reply).expect("typed NIP-10 Reply builder"); - - let error = client - .send_nip10_reply_event_builder(builder) - .await - .expect_err("no relay is configured"); - - assert!(matches!(error, RadrootsNostrError::ClientError(_))); -} - fn fixture_keys() -> RadrootsNostrKeys { RadrootsNostrKeys::new( RadrootsNostrSecretKey::from_hex(FIXTURE_ALICE_SECRET_KEY_HEX).expect("fixture secret key"), diff --git a/crates/nostr/tests/nip22_comment_profile.rs b/crates/nostr/tests/nip22_comment_profile.rs @@ -21,9 +21,6 @@ use radroots_test_fixtures::{ RELAY_PRIMARY_WSS, RELAY_SECONDARY_WSS, }; -#[cfg(feature = "client")] -use radroots_nostr::prelude::RadrootsNostrClient; - const CREATED_AT: u64 = 1_784_347_200; const ROOT_EVENT_ID: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const ADDRESS_REVISION_ID: &str = @@ -171,32 +168,6 @@ fn generic_kind_1111_builder_cannot_bypass_typed_comment_authoring() { )); } -#[cfg(feature = "client")] -#[tokio::test] -async fn typed_nip22_comment_builder_reaches_client_publication() { - let client = RadrootsNostrClient::new(fixture_keys()); - let comment = AuthoredNip22Comment::top_level_event( - "Publish Comment", - Nip22EventRootReference::parse( - ROOT_EVENT_ID, - FIXTURE_BOB_PUBLIC_KEY_HEX, - KIND_CLASSIFIED_LISTING, - None, - ) - .expect("event root"), - ) - .expect("Comment"); - let builder = - radroots_nostr_build_nip22_comment_event(&comment).expect("typed Comment builder"); - - let error = client - .send_nip22_comment_event_builder(builder) - .await - .expect_err("no relay is configured"); - - assert!(matches!(error, RadrootsNostrError::ClientError(_))); -} - fn fixture_keys() -> RadrootsNostrKeys { RadrootsNostrKeys::new( RadrootsNostrSecretKey::from_hex(FIXTURE_ALICE_SECRET_KEY_HEX).expect("fixture secret key"), diff --git a/crates/nostr/tests/package_boundary.rs b/crates/nostr/tests/package_boundary.rs @@ -1,10 +1,14 @@ use std::collections::BTreeSet; +use std::fs; +use std::path::{Path, PathBuf}; #[allow(unused_imports)] use radroots_nostr::{Error as _, event as _, filter as _, key as _, tag as _}; const MANIFEST: &str = include_str!("../Cargo.toml"); const ROOT: &str = include_str!("../src/lib.rs"); +const TRANSPORT_MANIFEST: &str = include_str!("../../transport_nostr/Cargo.toml"); +const TRANSPORT_ROOT: &str = include_str!("../../transport_nostr/src/lib.rs"); #[test] fn manifest_has_final_identity_features_and_radroots_dependencies() { @@ -76,6 +80,107 @@ fn crate_root_establishes_the_final_public_module_skeleton() { assert!(ROOT.contains("pub use error::RadrootsNostrError as Error;")); } +#[test] +fn live_client_and_http_ownership_belongs_to_transport_nostr() { + for forbidden in [ + "nostr-sdk", + "reqwest", + "client =", + "http =", + "radroots_nostr/client", + ] { + assert!( + !MANIFEST.contains(forbidden), + "portable manifest still owns forbidden live-client surface `{forbidden}`" + ); + } + + for forbidden in ["pub mod client;", "pub mod relays;", "pub mod nip11;"] { + assert!( + !ROOT.contains(forbidden), + "portable crate root still exposes live-client module `{forbidden}`" + ); + } + + for required in ["nostr-sdk", "reqwest", "nip11 = [\"client\""] { + assert!( + TRANSPORT_MANIFEST.contains(required), + "transport manifest is missing live-client ownership marker `{required}`" + ); + } + for required in [ + "mod client;", + "mod relays;", + "mod nip11;", + "pub use client::{", + "pub use nip11::fetch_nip11;", + ] { + assert!( + TRANSPORT_ROOT.contains(required), + "transport crate root is missing live-client owner `{required}`" + ); + } + + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + assert!(!manifest_dir.join("src/client.rs").exists()); + assert!(!manifest_dir.join("src/relays.rs").exists()); + assert!(!manifest_dir.join("src/nip11.rs").exists()); + assert!( + manifest_dir + .join("../transport_nostr/src/client.rs") + .exists() + ); + assert!( + manifest_dir + .join("../transport_nostr/src/relays.rs") + .exists() + ); + assert!( + manifest_dir + .join("../transport_nostr/src/nip11.rs") + .exists() + ); + + let forbidden_source = [ + "nostr_sdk", + "reqwest::", + "feature = \"client\"", + "feature = \"http\"", + "crate::client", + ]; + for source_path in rust_sources(&manifest_dir.join("src")) { + let source = fs::read_to_string(&source_path) + .unwrap_or_else(|error| panic!("failed to read {}: {error}", source_path.display())); + for forbidden in forbidden_source { + assert!( + !source.contains(forbidden), + "{} still contains live-client ownership marker `{forbidden}`", + source_path.display() + ); + } + } +} + +fn rust_sources(root: &Path) -> Vec<PathBuf> { + let mut pending = vec![root.to_path_buf()]; + let mut sources = Vec::new(); + while let Some(directory) = pending.pop() { + for entry in fs::read_dir(&directory) + .unwrap_or_else(|error| panic!("failed to read {}: {error}", directory.display())) + { + let path = entry + .expect("source directory entry must be readable") + .path(); + if path.is_dir() { + pending.push(path); + } else if path.extension().is_some_and(|extension| extension == "rs") { + sources.push(path); + } + } + } + sources +} + fn table_keys<'a>(source: &'a str, header: &str) -> BTreeSet<&'a str> { table_lines(source, header) .filter_map(|line| line.split_once('=').map(|(key, _)| key.trim())) diff --git a/crates/nostr_runtime/Cargo.toml b/crates/nostr_runtime/Cargo.toml @@ -15,11 +15,12 @@ readme = "README" default = ["std", "nostr-client", "rt"] std = [] rt = ["std", "dep:futures", "dep:tokio"] -nostr-client = ["std", "dep:radroots_nostr"] +nostr-client = ["std", "dep:radroots_nostr", "dep:radroots_transport_nostr"] nostrdb = ["nostr-client"] [dependencies] -radroots_nostr = { workspace = true, optional = true, default-features = true, features = [ +radroots_nostr = { workspace = true, optional = true, default-features = true } +radroots_transport_nostr = { workspace = true, optional = true, default-features = false, features = [ "client", ] } futures = { workspace = true, optional = true } diff --git a/crates/nostr_runtime/src/runtime.rs b/crates/nostr_runtime/src/runtime.rs @@ -10,9 +10,10 @@ use alloc::vec::Vec; use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use core::time::Duration; use futures::StreamExt; -use radroots_nostr::prelude::{ - RadrootsNostrClient, RadrootsNostrKeys, RadrootsNostrMonitor, RadrootsNostrMonitorNotification, - RadrootsNostrRelayStatus, RadrootsNostrRelayUrl, RadrootsNostrTimestamp, +use radroots_nostr::prelude::{RadrootsNostrKeys, RadrootsNostrRelayUrl, RadrootsNostrTimestamp}; +use radroots_transport_nostr::{ + RadrootsNostrClient, RadrootsNostrMonitor, RadrootsNostrMonitorNotification, + RadrootsNostrRelayStatus, }; use std::collections::HashMap; use std::sync::Mutex; diff --git a/crates/transport_nostr/Cargo.toml b/crates/transport_nostr/Cargo.toml @@ -19,10 +19,12 @@ default = ["std", "client", "storage", "runtime-tokio"] std = [] client = [ "dep:radroots_nostr", + "dep:nostr-sdk", + "dep:tokio", "radroots_nostr/std", - "radroots_nostr/client", "radroots_nostr/events", ] +nip11 = ["client", "dep:reqwest"] storage = ["dep:radroots_event_store", "dep:radroots_outbox", "client"] runtime-tokio = [ "dep:tokio", @@ -42,7 +44,6 @@ radroots_event_store = { workspace = true, optional = true, default-features = f ] } radroots_nostr = { workspace = true, optional = true, default-features = false, features = [ "std", - "client", "events", ] } radroots_outbox = { workspace = true, optional = true, default-features = false, features = [ @@ -52,10 +53,15 @@ radroots_outbox = { workspace = true, optional = true, default-features = false, radroots_transport = { workspace = true, default-features = false } futures = { workspace = true } nostr = { workspace = true, features = ["std"] } +nostr-sdk = { workspace = true, optional = true } +reqwest = { workspace = true, optional = true, default-features = false, features = [ + "json", + "rustls-tls", +] } serde = { workspace = true, features = ["derive", "std"] } serde_json = { workspace = true, features = ["std"] } thiserror = { workspace = true } -tokio = { workspace = true, optional = true, features = ["rt"] } +tokio = { workspace = true, optional = true, features = ["rt", "sync"] } url = { workspace = true } [dev-dependencies] diff --git a/crates/transport_nostr/src/client.rs b/crates/transport_nostr/src/client.rs @@ -0,0 +1,502 @@ +#![forbid(unsafe_code)] + +use core::time::Duration; +use core::{ + fmt::Debug, + pin::Pin, + task::{Context, Poll}, +}; +use std::collections::{HashMap, HashSet}; +#[cfg(not(target_arch = "wasm32"))] +use std::net::SocketAddr; + +use futures::Stream; +use nostr_sdk::{Client, ClientBuilder, ClientOptions}; + +use crate::RadrootsRelayTransportError; +use radroots_nostr::prelude::{ + RadrootsNostrEvent, RadrootsNostrEventId, RadrootsNostrFilter, RadrootsNostrKeys, + RadrootsNostrPublicKey, RadrootsNostrRelayUrl, RadrootsNostrSubscriptionId, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum RadrootsNostrRelayStatus { + Initialized, + Pending, + Connecting, + Connected, + Disconnected, + Terminated, + Banned, + Sleeping, +} + +fn normalize_relay_status(value: nostr_sdk::RelayStatus) -> RadrootsNostrRelayStatus { + match value { + nostr_sdk::RelayStatus::Initialized => RadrootsNostrRelayStatus::Initialized, + nostr_sdk::RelayStatus::Pending => RadrootsNostrRelayStatus::Pending, + nostr_sdk::RelayStatus::Connecting => RadrootsNostrRelayStatus::Connecting, + nostr_sdk::RelayStatus::Connected => RadrootsNostrRelayStatus::Connected, + nostr_sdk::RelayStatus::Disconnected => RadrootsNostrRelayStatus::Disconnected, + nostr_sdk::RelayStatus::Terminated => RadrootsNostrRelayStatus::Terminated, + nostr_sdk::RelayStatus::Banned => RadrootsNostrRelayStatus::Banned, + nostr_sdk::RelayStatus::Sleeping => RadrootsNostrRelayStatus::Sleeping, + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RadrootsNostrMonitorNotification { + StatusChanged { + relay_url: RadrootsNostrRelayUrl, + status: RadrootsNostrRelayStatus, + }, +} + +#[derive(Debug, Clone)] +pub struct RadrootsNostrMonitor { + inner: nostr_sdk::prelude::Monitor, +} + +impl RadrootsNostrMonitor { + pub fn new(channel_size: usize) -> Self { + Self { + inner: nostr_sdk::prelude::Monitor::new(channel_size), + } + } + + pub fn subscribe(&self) -> RadrootsNostrMonitorReceiver { + RadrootsNostrMonitorReceiver { + inner: self.inner.subscribe(), + } + } +} + +pub struct RadrootsNostrMonitorReceiver { + inner: tokio::sync::broadcast::Receiver<nostr_sdk::prelude::MonitorNotification>, +} + +impl RadrootsNostrMonitorReceiver { + pub async fn recv( + &mut self, + ) -> Result<RadrootsNostrMonitorNotification, RadrootsNostrMonitorReceiveError> { + self.inner + .recv() + .await + .map(normalize_monitor_notification) + .map_err(|error| match error { + tokio::sync::broadcast::error::RecvError::Closed => { + RadrootsNostrMonitorReceiveError::Closed + } + tokio::sync::broadcast::error::RecvError::Lagged(skipped) => { + RadrootsNostrMonitorReceiveError::Lagged { skipped } + } + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RadrootsNostrMonitorReceiveError { + Closed, + Lagged { skipped: u64 }, +} + +fn normalize_monitor_notification( + notification: nostr_sdk::prelude::MonitorNotification, +) -> RadrootsNostrMonitorNotification { + match notification { + nostr_sdk::prelude::MonitorNotification::StatusChanged { relay_url, status } => { + RadrootsNostrMonitorNotification::StatusChanged { + relay_url, + status: normalize_relay_status(status), + } + } + } +} + +#[derive(Debug, Clone)] +pub struct RadrootsNostrOutput<T> { + pub val: T, + pub success: HashSet<RadrootsNostrRelayUrl>, + pub failed: HashMap<RadrootsNostrRelayUrl, String>, +} + +fn normalize_output<T>(output: nostr_sdk::prelude::Output<T>) -> RadrootsNostrOutput<T> +where + T: Debug, +{ + RadrootsNostrOutput { + val: output.val, + success: output.success, + failed: output.failed, + } +} + +pub struct RadrootsNostrEventStream { + inner: nostr_sdk::pool::stream::BoxedStream<RadrootsNostrEvent>, +} + +impl Stream for RadrootsNostrEventStream { + type Item = RadrootsNostrEvent; + + fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> { + self.get_mut().inner.as_mut().poll_next(context) + } +} + +#[derive(Clone)] +pub struct RadrootsNostrRelay { + inner: nostr_sdk::Relay, +} + +impl RadrootsNostrRelay { + pub fn is_connected(&self) -> bool { + self.inner.is_connected() + } + + pub fn url(&self) -> &RadrootsNostrRelayUrl { + self.inner.url() + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct RadrootsNostrSubscribeAutoCloseOptions { + timeout: Option<Duration>, + idle_timeout: Option<Duration>, +} + +impl RadrootsNostrSubscribeAutoCloseOptions { + pub fn timeout(mut self, timeout: Option<Duration>) -> Self { + self.timeout = timeout; + self + } + + pub fn idle_timeout(mut self, timeout: Option<Duration>) -> Self { + self.idle_timeout = timeout; + self + } + + fn into_sdk(self) -> nostr_sdk::SubscribeAutoCloseOptions { + nostr_sdk::SubscribeAutoCloseOptions::default() + .timeout(self.timeout) + .idle_timeout(self.idle_timeout) + } +} + +#[derive(Clone)] +pub struct RadrootsNostrClient { + inner: Client, + monitor: Option<RadrootsNostrMonitor>, +} + +#[derive(Debug, Clone, Default)] +pub struct RadrootsNostrClientOptions { + automatic_authentication: Option<bool>, + max_avg_latency_ms: Option<u64>, + verify_subscriptions: Option<bool>, + ban_relay_on_mismatch: Option<bool>, + #[cfg(not(target_arch = "wasm32"))] + proxy: Option<SocketAddr>, +} + +impl RadrootsNostrClientOptions { + pub fn new() -> Self { + Self::default() + } + + pub fn automatic_authentication(mut self, enabled: bool) -> Self { + self.automatic_authentication = Some(enabled); + self + } + + pub fn max_avg_latency_ms(mut self, max_ms: u64) -> Self { + self.max_avg_latency_ms = Some(max_ms); + self + } + + pub fn verify_subscriptions(mut self, enabled: bool) -> Self { + self.verify_subscriptions = Some(enabled); + self + } + + pub fn ban_relay_on_mismatch(mut self, enabled: bool) -> Self { + self.ban_relay_on_mismatch = Some(enabled); + self + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn proxy_addr(mut self, addr: SocketAddr) -> Self { + self.proxy = Some(addr); + self + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn proxy_str(mut self, addr: &str) -> Result<Self, RadrootsRelayTransportError> { + let parsed: SocketAddr = addr.parse().map_err(|err: std::net::AddrParseError| { + RadrootsRelayTransportError::ClientConfig(err.to_string()) + })?; + self.proxy = Some(parsed); + Ok(self) + } + + fn to_client_options(&self) -> ClientOptions { + let mut options = ClientOptions::new(); + if let Some(enabled) = self.automatic_authentication { + options = options.automatic_authentication(enabled); + } + if let Some(max_ms) = self.max_avg_latency_ms { + options = options.max_avg_latency(Duration::from_millis(max_ms)); + } + if let Some(enabled) = self.verify_subscriptions { + options = options.verify_subscriptions(enabled); + } + if let Some(enabled) = self.ban_relay_on_mismatch { + options = options.ban_relay_on_mismatch(enabled); + } + #[cfg(not(target_arch = "wasm32"))] + if let Some(proxy) = self.proxy { + let connection = nostr_sdk::client::options::Connection::new().proxy(proxy); + options = options.connection(connection); + } + options + } +} + +impl RadrootsNostrClient { + pub fn new_signerless() -> Self { + Self { + inner: Client::default(), + monitor: None, + } + } + + pub fn new_signerless_with_options(options: RadrootsNostrClientOptions) -> Self { + let inner = ClientBuilder::new() + .opts(options.to_client_options()) + .build(); + Self { + inner, + monitor: None, + } + } + + pub fn new(keys: RadrootsNostrKeys) -> Self { + Self { + inner: Client::new(keys), + monitor: None, + } + } + + pub fn from_keys_with_options( + keys: RadrootsNostrKeys, + options: RadrootsNostrClientOptions, + ) -> Self { + let inner = ClientBuilder::new() + .signer(keys) + .opts(options.to_client_options()) + .build(); + Self { + inner, + monitor: None, + } + } + + pub fn new_with_monitor(keys: RadrootsNostrKeys, monitor: RadrootsNostrMonitor) -> Self { + let inner = Client::builder() + .signer(keys) + .monitor(monitor.inner.clone()) + .build(); + Self { + inner, + monitor: Some(monitor), + } + } + + pub async fn has_signer(&self) -> bool { + self.inner.has_signer().await + } + + pub async fn public_key(&self) -> Result<RadrootsNostrPublicKey, RadrootsRelayTransportError> { + self.inner + .public_key() + .await + .map_err(|error| RadrootsRelayTransportError::Client(error.to_string())) + } + + pub fn monitor(&self) -> Option<&RadrootsNostrMonitor> { + self.monitor.as_ref() + } + + pub async fn connect(&self) { + self.inner.connect().await; + } + + pub async fn wait_for_connection(&self, timeout: Duration) { + self.inner.wait_for_connection(timeout).await; + } + + pub async fn try_connect(&self, timeout: Duration) -> RadrootsNostrOutput<()> { + normalize_output(self.inner.try_connect(timeout).await) + } + + pub async fn add_relay(&self, url: &str) -> Result<bool, RadrootsRelayTransportError> { + self.inner + .add_relay(url) + .await + .map_err(|error| RadrootsRelayTransportError::Client(error.to_string())) + } + + pub async fn add_write_relay(&self, url: &str) -> Result<bool, RadrootsRelayTransportError> { + self.inner + .add_write_relay(url) + .await + .map_err(|error| RadrootsRelayTransportError::Client(error.to_string())) + } + + pub async fn add_read_relay(&self, url: &str) -> Result<bool, RadrootsRelayTransportError> { + self.inner + .add_read_relay(url) + .await + .map_err(|error| RadrootsRelayTransportError::Client(error.to_string())) + } + + pub async fn remove_relay(&self, url: &str) -> Result<(), RadrootsRelayTransportError> { + self.inner + .force_remove_relay(url) + .await + .map_err(|error| RadrootsRelayTransportError::Client(error.to_string())) + } + + pub async fn relays(&self) -> HashMap<RadrootsNostrRelayUrl, RadrootsNostrRelay> { + self.inner + .relays() + .await + .into_iter() + .map(|(url, inner)| (url, RadrootsNostrRelay { inner })) + .collect() + } + + pub async fn fetch_events( + &self, + filter: RadrootsNostrFilter, + timeout: Duration, + ) -> Result<Vec<RadrootsNostrEvent>, RadrootsRelayTransportError> { + self.inner + .fetch_events(filter, timeout) + .await + .map(|events| events.to_vec()) + .map_err(|error| RadrootsRelayTransportError::Client(error.to_string())) + } + + pub async fn query_database( + &self, + filter: RadrootsNostrFilter, + ) -> Result<Vec<RadrootsNostrEvent>, RadrootsRelayTransportError> { + self.inner + .database() + .query(filter) + .await + .map(|events| events.to_vec()) + .map_err(|error| RadrootsRelayTransportError::ClientDatabase(error.to_string())) + } + + pub async fn stream_events( + &self, + filter: RadrootsNostrFilter, + timeout: Duration, + ) -> Result<RadrootsNostrEventStream, RadrootsRelayTransportError> { + self.inner + .stream_events(filter, timeout) + .await + .map(|inner| RadrootsNostrEventStream { inner }) + .map_err(|error| RadrootsRelayTransportError::Client(error.to_string())) + } + + pub async fn subscribe( + &self, + filter: RadrootsNostrFilter, + options: Option<RadrootsNostrSubscribeAutoCloseOptions>, + ) -> Result<RadrootsNostrOutput<RadrootsNostrSubscriptionId>, RadrootsRelayTransportError> { + self.inner + .subscribe( + filter, + options.map(RadrootsNostrSubscribeAutoCloseOptions::into_sdk), + ) + .await + .map(normalize_output) + .map_err(|error| RadrootsRelayTransportError::Client(error.to_string())) + } + + pub async fn subscribe_to_relays( + &self, + relays: &[RadrootsNostrRelayUrl], + filter: RadrootsNostrFilter, + options: Option<RadrootsNostrSubscribeAutoCloseOptions>, + ) -> Result<RadrootsNostrOutput<RadrootsNostrSubscriptionId>, RadrootsRelayTransportError> { + self.inner + .subscribe_to( + relays.iter().cloned(), + filter, + options.map(RadrootsNostrSubscribeAutoCloseOptions::into_sdk), + ) + .await + .map(normalize_output) + .map_err(|error| RadrootsRelayTransportError::Client(error.to_string())) + } + + pub async fn unsubscribe(&self, subscription_id: &RadrootsNostrSubscriptionId) { + self.inner.unsubscribe(subscription_id).await; + } + + /// Relays a caller-supplied signed event. + /// + /// This is a transport boundary, not an authored-builder boundary. The + /// caller is responsible for the event's authoring policy and signature. + pub async fn send_event( + &self, + event: &RadrootsNostrEvent, + ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsRelayTransportError> { + self.inner + .send_event(event) + .await + .map(normalize_output) + .map_err(|error| RadrootsRelayTransportError::Client(error.to_string())) + } + + pub async fn send_event_to_relays( + &self, + relays: &[RadrootsNostrRelayUrl], + event: &RadrootsNostrEvent, + ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsRelayTransportError> { + self.inner + .send_event_to(relays.iter().cloned(), event) + .await + .map(normalize_output) + .map_err(|error| RadrootsRelayTransportError::Client(error.to_string())) + } + + pub async fn send_event_to( + &self, + relays: Vec<String>, + event: &RadrootsNostrEvent, + ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsRelayTransportError> { + self.inner + .send_event_to(relays, event) + .await + .map(normalize_output) + .map_err(|error| RadrootsRelayTransportError::Client(error.to_string())) + } +} + +pub async fn radroots_nostr_fetch_event_by_id( + client: &RadrootsNostrClient, + id: &str, +) -> Result<RadrootsNostrEvent, RadrootsRelayTransportError> { + let event_id = RadrootsNostrEventId::parse(id) + .map_err(|error| RadrootsRelayTransportError::NostrEvent(error.to_string()))?; + let filter = RadrootsNostrFilter::new().id(event_id); + let events = client.fetch_events(filter, Duration::from_secs(10)).await?; + let event = events + .first() + .ok_or_else(|| RadrootsRelayTransportError::EventNotFound(event_id.to_hex()))?; + Ok(event.clone()) +} diff --git a/crates/transport_nostr/src/error.rs b/crates/transport_nostr/src/error.rs @@ -4,6 +4,26 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum RadrootsRelayTransportError { + #[cfg(feature = "client")] + #[error("Nostr client error: {0}")] + Client(String), + + #[cfg(feature = "client")] + #[error("Nostr client database error: {0}")] + ClientDatabase(String), + + #[cfg(feature = "client")] + #[error("Nostr protocol event error: {0}")] + NostrEvent(String), + + #[cfg(feature = "client")] + #[error("Nostr client configuration error: {0}")] + ClientConfig(String), + + #[cfg(feature = "client")] + #[error("Nostr event not found: {0}")] + EventNotFound(String), + #[error("Relay URL parse failed for `{url}`: {reason}")] RelayUrlParse { url: String, reason: String }, diff --git a/crates/transport_nostr/src/fetch.rs b/crates/transport_nostr/src/fetch.rs @@ -1,5 +1,6 @@ #![forbid(unsafe_code)] +use crate::RadrootsNostrClient; use crate::error::ensure_nonnegative_timestamp; use crate::{RadrootsRelayOutcome, RadrootsRelayTargetSet, RadrootsRelayTransportError}; use core::time::Duration; @@ -11,7 +12,7 @@ use radroots_event_store::{ RadrootsEventPersistence, RadrootsEventStore, RadrootsEventVisibility, RadrootsTransportObservation, RadrootsTransportObservationType, }; -use radroots_nostr::prelude::{RadrootsNostrClient, RadrootsNostrEvent, RadrootsNostrFilter}; +use radroots_nostr::prelude::{RadrootsNostrEvent, RadrootsNostrFilter}; use radroots_transport::{Target, TransportId}; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; diff --git a/crates/transport_nostr/src/lib.rs b/crates/transport_nostr/src/lib.rs @@ -1,15 +1,29 @@ #![cfg_attr(coverage_nightly, feature(coverage_attribute))] #![forbid(unsafe_code)] +#[cfg(feature = "client")] +mod client; mod error; #[cfg(feature = "storage")] mod fetch; +#[cfg(feature = "nip11")] +mod nip11; #[cfg(feature = "storage")] mod outbox; mod outcome; mod publish; mod relay; +#[cfg(feature = "client")] +mod relays; +#[cfg(feature = "client")] +pub use client::{ + RadrootsNostrClient, RadrootsNostrClientOptions, RadrootsNostrEventStream, + RadrootsNostrMonitor, RadrootsNostrMonitorNotification, RadrootsNostrMonitorReceiveError, + RadrootsNostrMonitorReceiver, RadrootsNostrOutput, RadrootsNostrRelay, + RadrootsNostrRelayStatus, RadrootsNostrSubscribeAutoCloseOptions, + radroots_nostr_fetch_event_by_id, +}; pub use error::RadrootsRelayTransportError; #[cfg(all(feature = "storage", feature = "runtime-tokio"))] pub use fetch::fetch_relay_events_blocking; @@ -25,6 +39,8 @@ pub use fetch::{ RadrootsRelayFetchRelayOutcome, RadrootsRelayFetchRequest, RadrootsRelayFetchedEvent, RadrootsRelayFetchedEventsReceipt, fetch_and_ingest_relay_events, fetch_relay_events, }; +#[cfg(feature = "nip11")] +pub use nip11::fetch_nip11; #[cfg(feature = "storage")] pub use outbox::{ RadrootsOutboxPublishPolicy, RadrootsOutboxPublishReceipt, RadrootsOutboxPublishTargetReceipt, @@ -40,3 +56,5 @@ pub use publish::{ verified_signed_event_payload, }; pub use relay::{RadrootsRelayTargetSet, RadrootsRelayUrlPolicy, RelayUrl}; +#[cfg(feature = "client")] +pub use relays::{radroots_nostr_add_relay, radroots_nostr_connect, radroots_nostr_remove_relay}; diff --git a/crates/transport_nostr/src/nip11.rs b/crates/transport_nostr/src/nip11.rs @@ -0,0 +1,26 @@ +use radroots_event::social::relay_document::RelayDocument; +pub async fn fetch_nip11(ws_url: &str) -> Option<RelayDocument> { + let http_url = ws_to_http(ws_url)?; + let client = reqwest::Client::new(); + client + .get(&http_url) + .header("Accept", "application/nostr+json") + .send() + .await + .ok()? + .json::<RelayDocument>() + .await + .ok() +} + +fn ws_to_http(ws: &str) -> Option<String> { + let mut url = reqwest::Url::parse(ws).ok()?; + let scheme = url.scheme().to_owned(); + let replacement = match scheme.as_str() { + "wss" => "https", + "ws" => "http", + other => other, + }; + url.set_scheme(replacement).ok()?; + Some(url.into()) +} diff --git a/crates/transport_nostr/src/publish.rs b/crates/transport_nostr/src/publish.rs @@ -19,11 +19,13 @@ use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; use std::sync::{Arc, Mutex, PoisonError}; +#[cfg(feature = "client")] +use crate::RadrootsNostrClient; use crate::RadrootsRelayOutcomeKind; #[cfg(feature = "client")] use nostr::JsonUtil; #[cfg(feature = "client")] -use radroots_nostr::prelude::{RadrootsNostrClient, RadrootsNostrEvent}; +use radroots_nostr::prelude::RadrootsNostrEvent; #[cfg(feature = "client")] const RELAY_CONNECT_TIMEOUT: Duration = Duration::from_secs(10); @@ -322,9 +324,21 @@ fn nostr_error_to_transport_error(error: RadrootsRelayTransportError) -> Radroot | RadrootsRelayTransportError::InvalidPublishReceiptAttemptState { .. } => { RadrootsTransportError::InvalidTargetUri } + #[cfg(feature = "client")] + RadrootsRelayTransportError::NostrEvent(_) + | RadrootsRelayTransportError::NostrEventJson(_) + | RadrootsRelayTransportError::Json(_) => RadrootsTransportError::InvalidPayloadBytes, + #[cfg(not(feature = "client"))] RadrootsRelayTransportError::NostrEventJson(_) | RadrootsRelayTransportError::Json(_) => { RadrootsTransportError::InvalidPayloadBytes } + #[cfg(feature = "client")] + RadrootsRelayTransportError::Client(_) + | RadrootsRelayTransportError::ClientDatabase(_) + | RadrootsRelayTransportError::ClientConfig(_) + | RadrootsRelayTransportError::EventNotFound(_) => { + RadrootsTransportError::InvalidTransportKind + } RadrootsRelayTransportError::Transport(_) => RadrootsTransportError::InvalidTransportKind, RadrootsRelayTransportError::EmptyFetchFilters | RadrootsRelayTransportError::InvalidFetchLimit { .. } diff --git a/crates/transport_nostr/src/relays.rs b/crates/transport_nostr/src/relays.rs @@ -0,0 +1,21 @@ +use crate::{RadrootsNostrClient, RadrootsRelayTransportError}; + +pub async fn radroots_nostr_add_relay( + client: &RadrootsNostrClient, + url: &str, +) -> Result<(), RadrootsRelayTransportError> { + client.add_relay(url).await?; + Ok(()) +} + +pub async fn radroots_nostr_remove_relay( + client: &RadrootsNostrClient, + url: &str, +) -> Result<(), RadrootsRelayTransportError> { + client.remove_relay(url).await?; + Ok(()) +} + +pub async fn radroots_nostr_connect(client: &RadrootsNostrClient) { + client.connect().await; +} diff --git a/tools/xtask/src/contract.rs b/tools/xtask/src/contract.rs @@ -1905,7 +1905,7 @@ const REPLY_WITNESSES: [EventBoundarySourceWitness; 5] = [ }, ]; -const COMMENT_WITNESSES: [EventBoundarySourceWitness; 8] = [ +const COMMENT_WITNESSES: [EventBoundarySourceWitness; 7] = [ EventBoundarySourceWitness { relative_path: "crates/event/src/relay_hint.rs", required_fragments: &["pub struct NostrRelayHint"], @@ -1944,16 +1944,12 @@ const COMMENT_WITNESSES: [EventBoundarySourceWitness; 8] = [ ], }, EventBoundarySourceWitness { - relative_path: "crates/nostr/src/client.rs", - required_fragments: &["pub async fn send_nip22_comment_event_builder"], - }, - EventBoundarySourceWitness { relative_path: "crates/event/src/kinds.rs", required_fragments: &["pub const KIND_COMMENT: u32 = 1111;"], }, ]; -const DELETION_WITNESSES: [EventBoundarySourceWitness; 8] = [ +const DELETION_WITNESSES: [EventBoundarySourceWitness; 7] = [ EventBoundarySourceWitness { relative_path: "crates/event/src/deletion.rs", required_fragments: &["pub struct AuthoredNip09DeletionRequest"], @@ -1995,10 +1991,6 @@ const DELETION_WITNESSES: [EventBoundarySourceWitness; 8] = [ ], }, EventBoundarySourceWitness { - relative_path: "crates/nostr/src/client.rs", - required_fragments: &["pub async fn send_nip09_deletion_request_event_builder"], - }, - EventBoundarySourceWitness { relative_path: "crates/event/src/kinds.rs", required_fragments: &["pub const KIND_DELETION_REQUEST: u32 = 5;"], }, @@ -2613,7 +2605,7 @@ const RELAY_DOC_WITNESSES: [EventBoundarySourceWitness; 2] = [ required_fragments: &["pub struct RelayDocument"], }, EventBoundarySourceWitness { - relative_path: "crates/nostr/src/nip11.rs", + relative_path: "crates/transport_nostr/src/nip11.rs", required_fragments: &["pub async fn fetch_nip11(ws_url: &str) -> Option<RelayDocument>"], }, ];