commit 828148e2a15582972063020f37c8962db795537c
parent 61702057c0837146fc6dc1c1b6d62e8dd25695e9
Author: triesap <tyson@radroots.org>
Date: Thu, 30 Jul 2026 23:03:13 +0000
transport: migrate workspace consumers
- Move first-party consumers to canonical transport identities, targets, and split SPIs.
- Adapt Nostr and Reticulum implementations to final sink and source contracts.
- Preserve governed manifests and runtime registries across the breaking cutover.
- Add boundary coverage and refresh exact package metadata and generated authority.
Diffstat:
27 files changed, 1105 insertions(+), 1048 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -68,7 +68,7 @@ rust-version = "1.97.1"
license = "MIT OR Apache-2.0"
repository = "https://github.com/radrootslabs/lib"
homepage = "https://radroots.org"
-readme = "README"
+readme = "README.md"
authors = ["Tyson Lupul <tyson@radroots.org>"]
[workspace.metadata.radroots.public-package]
diff --git a/crates/event_store/contracts/source_maintenance_v1.manifest.json b/crates/event_store/contracts/source_maintenance_v1.manifest.json
@@ -152,8 +152,8 @@
{
"role": "workspace_dependency_authority",
"path": "Cargo.toml",
- "byte_length": 12411,
- "sha256": "cad96f080b0cc9ac7ffda703c8807540c9cf89f47a2299b141e6e7949714bf9f",
+ "byte_length": 12414,
+ "sha256": "895bf9d57a1cb9a89c0f70232cb628106897b421561ae0b9e9ec57577149b273",
"hash_algorithm": "sha256_bytes_v1"
},
{
@@ -712,8 +712,8 @@
{
"role": "predecessor_model_public_surface",
"path": "crates/event_store/src/model.rs",
- "byte_length": 32314,
- "sha256": "d7ffa47719783c9f08cb4e00fbe47123cf0310ead06f77d3ad1d81edfda96b2c",
+ "byte_length": 32050,
+ "sha256": "910053012eb65cb1a6c24455ffacee056ce013c950e9b545822f9c2e679a8892",
"hash_algorithm": "sha256_bytes_v1"
},
{
@@ -775,8 +775,8 @@
{
"role": "public_store_and_transaction_authority",
"path": "crates/event_store/src/store.rs",
- "byte_length": 393535,
- "sha256": "c3286b50a2e9e17a330938492cfded0829dc19516f55aa4f62289d513457bad7",
+ "byte_length": 393195,
+ "sha256": "03fc56bf151f53c35792d09b247513d722ae26e3d112810f28f167a004c83273",
"hash_algorithm": "sha256_bytes_v1"
},
{
@@ -810,8 +810,8 @@
{
"role": "predecessor_post_core_v1_storage",
"path": "crates/event_store/src/store/post_core_storage_v1.rs",
- "byte_length": 16558,
- "sha256": "d88c1f7ff5cc0a19221ad893eb840d55c11535f13adf84ffb3076cbc18d4abf1",
+ "byte_length": 16538,
+ "sha256": "fcd6546c23a6cba12b70d92728d19b6ee5d43174b83495f820348b77efa33aab",
"hash_algorithm": "sha256_bytes_v1"
},
{
@@ -859,15 +859,15 @@
{
"role": "transitive_predecessor_membership_governance",
"path": "tools/xtask/src/contract/nip09_reconciliation.rs",
- "byte_length": 842286,
- "sha256": "678e4290f95db6a4eca19c82550f9db4c870e97f521dac3656f1a91c68db8104",
+ "byte_length": 842402,
+ "sha256": "4abb141771cfa257440dba7ebeae21f5ad1f9856cc0d5f6437ba68614d997810",
"hash_algorithm": "sha256_bytes_v1"
},
{
"role": "source_maintenance_governance",
"path": "tools/xtask/src/contract/source_maintenance.rs",
"byte_length": 199270,
- "sha256": "a6685c27255918769f894ab098ba13a927c0d70c423fbd72b5404c0af0ae2f89",
+ "sha256": "d3681d09e60763baf3c59edfbcb48e3c370c315a75fcd5a01756c8c961e784c9",
"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 @@
-b4416b1eab44c6e7110ffed89eb67b8963059538e7775a1347067d1f7c7f3999
+51205a1c1c7c3b91d79354ea5deabefc0f42d83423d4a7da3a0e6658f34863b5
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\": 12411,\n \"sha256\": \"cad96f080b0cc9ac7ffda703c8807540c9cf89f47a2299b141e6e7949714bf9f\",\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\": 1550,\n \"sha256\": \"9901a09402592ee873c255edf2e0c0d770105401255679d86ad47c35cb159d72\",\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\": 32314,\n \"sha256\": \"d7ffa47719783c9f08cb4e00fbe47123cf0310ead06f77d3ad1d81edfda96b2c\",\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\": 393535,\n \"sha256\": \"c3286b50a2e9e17a330938492cfded0829dc19516f55aa4f62289d513457bad7\",\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\": 16558,\n \"sha256\": \"d88c1f7ff5cc0a19221ad893eb840d55c11535f13adf84ffb3076cbc18d4abf1\",\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\": 842286,\n \"sha256\": \"678e4290f95db6a4eca19c82550f9db4c870e97f521dac3656f1a91c68db8104\",\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\": \"a6685c27255918769f894ab098ba13a927c0d70c423fbd72b5404c0af0ae2f89\",\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\": 12414,\n \"sha256\": \"895bf9d57a1cb9a89c0f70232cb628106897b421561ae0b9e9ec57577149b273\",\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\": 1550,\n \"sha256\": \"9901a09402592ee873c255edf2e0c0d770105401255679d86ad47c35cb159d72\",\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\": 32050,\n \"sha256\": \"910053012eb65cb1a6c24455ffacee056ce013c950e9b545822f9c2e679a8892\",\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\": 393195,\n \"sha256\": \"03fc56bf151f53c35792d09b247513d722ae26e3d112810f28f167a004c83273\",\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\": \"d3681d09e60763baf3c59edfbcb48e3c370c315a75fcd5a01756c8c961e784c9\",\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_BYTE_LENGTH: usize = 38397;
pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SHA256: &str =
- "b4416b1eab44c6e7110ffed89eb67b8963059538e7775a1347067d1f7c7f3999";
+ "51205a1c1c7c3b91d79354ea5deabefc0f42d83423d4a7da3a0e6658f34863b5";
pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SCHEMA_VERSION: u32 = 1;
pub(crate) const SOURCE_MAINTENANCE_CONTRACT_ID: &str =
"radroots_event_store.source_maintenance_v1";
diff --git a/crates/event_store/src/model.rs b/crates/event_store/src/model.rs
@@ -38,10 +38,8 @@ use radroots_event::envelope::EventKind;
use radroots_event::id::{CandidateId, DTag, EventId, InventoryBinId, MutationId, TradeId};
use radroots_event::trade::TradeMutationKindV1;
use radroots_identity::PublicKey;
-use radroots_transport::{
- RadrootsTransportKind, RadrootsTransportTarget, RadrootsTransportTargetFingerprint,
- RadrootsTransportTargetUri,
-};
+use radroots_transport::target::TargetFingerprint;
+use radroots_transport::{RadrootsTransportTargetUri, Target, TransportId};
pub use reconciliation_v1::{
RadrootsEventAdmissionStatus, RadrootsEventIngest, RadrootsEventIngestReceipt,
RadrootsEventPersistence, RadrootsEventStoreSourceGeneration, RadrootsRawHeadDecision,
@@ -168,9 +166,9 @@ impl RadrootsTransportObservationType {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RadrootsTransportObservation {
- transport_kind: RadrootsTransportKind,
+ transport_kind: TransportId,
endpoint_uri: RadrootsTransportTargetUri,
- endpoint_fingerprint: RadrootsTransportTargetFingerprint,
+ endpoint_fingerprint: TargetFingerprint,
observation_type: RadrootsTransportObservationType,
observed_at_ms: i64,
caller_redacted_message: Option<RadrootsTransportObservationMessage>,
@@ -183,7 +181,7 @@ impl RadrootsTransportObservation {
/// observation identity. Use transport delivery receipts when scoped
/// Reticulum or local-target evidence must be preserved.
pub fn new(
- transport_kind: RadrootsTransportKind,
+ transport_kind: TransportId,
endpoint_uri: impl AsRef<str>,
observation_type: RadrootsTransportObservationType,
observed_at_ms: i64,
@@ -195,9 +193,9 @@ impl RadrootsTransportObservation {
},
);
}
- let target = RadrootsTransportTarget::new(transport_kind, endpoint_uri)?;
+ let target = Target::new(transport_kind, endpoint_uri)?;
Ok(Self {
- transport_kind: target.kind().clone(),
+ transport_kind: *target.kind(),
endpoint_uri: target.uri().clone(),
endpoint_fingerprint: target.fingerprint().clone(),
observation_type,
@@ -206,7 +204,7 @@ impl RadrootsTransportObservation {
})
}
- pub fn transport_kind(&self) -> &RadrootsTransportKind {
+ pub fn transport_kind(&self) -> &TransportId {
&self.transport_kind
}
@@ -214,7 +212,7 @@ impl RadrootsTransportObservation {
&self.endpoint_uri
}
- pub fn endpoint_fingerprint(&self) -> &RadrootsTransportTargetFingerprint {
+ pub fn endpoint_fingerprint(&self) -> &TargetFingerprint {
&self.endpoint_fingerprint
}
@@ -242,8 +240,7 @@ impl RadrootsTransportObservation {
&self,
event_id: &str,
) -> Result<(), RadrootsEventStoreError> {
- let target =
- RadrootsTransportTarget::new(self.transport_kind.clone(), self.endpoint_uri.as_str())?;
+ let target = Target::new(self.transport_kind, self.endpoint_uri.as_str())?;
if target.uri() != &self.endpoint_uri || target.fingerprint() != &self.endpoint_fingerprint
{
return Err(
@@ -260,9 +257,9 @@ impl RadrootsTransportObservation {
#[cfg(test)]
pub(crate) fn from_unchecked_parts_for_test(
- transport_kind: RadrootsTransportKind,
+ transport_kind: TransportId,
endpoint_uri: RadrootsTransportTargetUri,
- endpoint_fingerprint: RadrootsTransportTargetFingerprint,
+ endpoint_fingerprint: TargetFingerprint,
observation_type: RadrootsTransportObservationType,
observed_at_ms: i64,
) -> Self {
@@ -694,7 +691,7 @@ mod tests {
}
assert!(RadrootsTransportObservationType::parse("bad").is_err());
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://relay.example.test",
RadrootsTransportObservationType::Fetch,
1,
@@ -708,7 +705,7 @@ mod tests {
"wss://relay.example.test"
);
let canonical_relay = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"WSS://RELAY.EXAMPLE.TEST/",
RadrootsTransportObservationType::Fetch,
1,
@@ -726,7 +723,7 @@ mod tests {
] {
assert!(
RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
invalid_relay,
RadrootsTransportObservationType::Fetch,
1,
@@ -736,14 +733,13 @@ mod tests {
);
}
let reticulum = RadrootsTransportObservation::new(
- RadrootsTransportKind::Reticulum,
+ TransportId::RETICULUM,
"reticulum:local",
RadrootsTransportObservationType::MeshHeard,
1,
)
.expect("Reticulum observation");
- let expected_reticulum =
- RadrootsTransportTarget::reticulum().expect("canonical Reticulum target");
+ let expected_reticulum = Target::reticulum().expect("canonical Reticulum target");
assert_eq!(reticulum.endpoint_uri(), expected_reticulum.uri());
assert_eq!(
reticulum.endpoint_fingerprint(),
@@ -751,7 +747,7 @@ mod tests {
);
assert!(
RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"not a URI",
RadrootsTransportObservationType::Fetch,
1,
@@ -760,7 +756,7 @@ mod tests {
);
assert!(matches!(
RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://relay.example.test",
RadrootsTransportObservationType::Fetch,
-1,
diff --git a/crates/event_store/src/store.rs b/crates/event_store/src/store.rs
@@ -70,10 +70,8 @@ use radroots_event::envelope::event_head::v1::{
};
use radroots_event::id::{DTag, EventId, MutationId, TradeId};
use radroots_event::trade::TradeMutationKindV1;
-use radroots_transport::{
- RadrootsTransportKind, RadrootsTransportTarget, RadrootsTransportTargetFingerprint,
- RadrootsTransportTargetUri,
-};
+use radroots_transport::target::TargetFingerprint;
+use radroots_transport::{RadrootsTransportTargetUri, Target, TransportId};
#[cfg(test)]
use sha2::{Digest, Sha256};
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions};
@@ -320,10 +318,10 @@ impl RadrootsEventStore {
/// delivery evidence remains available from transport delivery receipts.
pub async fn observations_for_endpoint(
&self,
- transport_kind: RadrootsTransportKind,
+ transport_kind: TransportId,
endpoint_uri: impl AsRef<str>,
) -> Result<Vec<RadrootsTransportObservationRow>, RadrootsEventStoreError> {
- let target = RadrootsTransportTarget::new(transport_kind, endpoint_uri)?;
+ let target = Target::new(transport_kind, endpoint_uri)?;
let rows = sqlx::query(
"SELECT event_id, transport_kind, endpoint_uri, endpoint_fingerprint, observation_type, first_observed_at_ms, last_observed_at_ms, observation_count, redacted_message FROM event_transport_observation WHERE transport_kind = ? AND endpoint_fingerprint = ? ORDER BY last_observed_at_ms, event_id, observation_type",
)
@@ -1150,9 +1148,9 @@ pub async fn inspect_event_store_status(
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RadrootsTransportObservationRow {
pub event_id: String,
- pub transport_kind: RadrootsTransportKind,
+ pub transport_kind: TransportId,
pub endpoint_uri: RadrootsTransportTargetUri,
- pub endpoint_fingerprint: RadrootsTransportTargetFingerprint,
+ pub endpoint_fingerprint: TargetFingerprint,
pub observation_type: RadrootsTransportObservationType,
pub first_observed_at_ms: i64,
pub last_observed_at_ms: i64,
@@ -1631,10 +1629,9 @@ fn transport_observation_from_row(
let transport_kind_label: String = row.try_get("transport_kind")?;
let endpoint_uri_raw: String = row.try_get("endpoint_uri")?;
let endpoint_fingerprint_raw: String = row.try_get("endpoint_fingerprint")?;
- let transport_kind = RadrootsTransportKind::parse_canonical(&transport_kind_label)?;
- let endpoint_fingerprint =
- RadrootsTransportTargetFingerprint::parse(&endpoint_fingerprint_raw)?;
- let target = RadrootsTransportTarget::new(transport_kind, &endpoint_uri_raw)?;
+ let transport_kind = TransportId::parse_canonical(&transport_kind_label)?;
+ let endpoint_fingerprint = TargetFingerprint::parse(&endpoint_fingerprint_raw)?;
+ let target = Target::new(transport_kind, &endpoint_uri_raw)?;
if target.uri().as_str() != endpoint_uri_raw
|| endpoint_fingerprint.as_str() != endpoint_fingerprint_raw
|| &endpoint_fingerprint != target.fingerprint()
@@ -1674,7 +1671,7 @@ fn transport_observation_from_row(
.transpose()?;
Ok(RadrootsTransportObservationRow {
event_id,
- transport_kind: target.kind().clone(),
+ transport_kind: *target.kind(),
endpoint_uri: target.uri().clone(),
endpoint_fingerprint: target.fingerprint().clone(),
observation_type: RadrootsTransportObservationType::parse(
@@ -4613,7 +4610,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
"hello",
);
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://relay.example.com",
crate::RadrootsTransportObservationType::PublishAck,
1_100,
@@ -5186,7 +5183,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
crate::RADROOTS_EVENT_STORE_RAW_EVENT_COUNT_LIMIT_V1
);
let duplicate_observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://capacity-boundary.example.test",
RadrootsTransportObservationType::Subscription,
1_100,
@@ -5235,7 +5232,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
));
let ephemeral = signed_event(KIND_GEOCHAT, 22, Vec::new(), "live-only boundary event");
let ephemeral_observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://ephemeral-boundary.example.test",
RadrootsTransportObservationType::Subscription,
1_300,
@@ -6296,7 +6293,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
let unsupported = signed_event(29_999, 16, Vec::new(), "unsupported");
let invalid = signed_event(KIND_RELAY_AUTH, 17, Vec::new(), "not-json");
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://relay.example.test",
RadrootsTransportObservationType::Subscription,
2_260,
@@ -9159,7 +9156,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
let store = RadrootsEventStore::open_memory().await.expect("open");
let event = signed_event(KIND_POST, 15, Vec::new(), "hello");
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://relay.local",
crate::RadrootsTransportObservationType::Subscription,
4_000,
@@ -9168,7 +9165,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
let ingest = RadrootsEventIngest::new(event.clone(), 4_000).with_observation(observation);
store.ingest_event(ingest).await.expect("first");
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://relay.local",
crate::RadrootsTransportObservationType::Subscription,
4_100,
@@ -9179,7 +9176,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
let ingest = RadrootsEventIngest::new(event.clone(), 4_100).with_observation(observation);
store.ingest_event(ingest).await.expect("second");
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://relay.local",
crate::RadrootsTransportObservationType::Subscription,
4_050,
@@ -9204,7 +9201,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
);
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://relay.local",
crate::RadrootsTransportObservationType::Subscription,
4_100,
@@ -9215,7 +9212,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
let ingest = RadrootsEventIngest::new(event.clone(), 4_100).with_observation(observation);
store.ingest_event(ingest).await.expect("tie duplicate");
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://relay.local",
crate::RadrootsTransportObservationType::Subscription,
4_100,
@@ -9227,7 +9224,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
.await
.expect("tie duplicate without message");
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://relay.local",
crate::RadrootsTransportObservationType::Subscription,
4_200,
@@ -9244,7 +9241,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
.await
.expect("observations");
assert_eq!(observations.len(), 1);
- assert_eq!(observations[0].transport_kind, RadrootsTransportKind::Nostr);
+ assert_eq!(observations[0].transport_kind, TransportId::NOSTR);
assert_eq!(observations[0].endpoint_uri.as_str(), "wss://relay.local");
assert_eq!(
observations[0].observation_type,
@@ -9259,13 +9256,13 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
);
let endpoint_observations = store
- .observations_for_endpoint(RadrootsTransportKind::Nostr, "WSS://RELAY.LOCAL/")
+ .observations_for_endpoint(TransportId::NOSTR, "WSS://RELAY.LOCAL/")
.await
.expect("endpoint observations");
assert_eq!(endpoint_observations, observations);
let reticulum_observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Reticulum,
+ TransportId::RETICULUM,
"reticulum:local",
crate::RadrootsTransportObservationType::MeshHeard,
4_300,
@@ -9278,12 +9275,11 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
.await
.expect("Reticulum observation ingest");
let reticulum_observations = store
- .observations_for_endpoint(RadrootsTransportKind::Reticulum, "reticulum:local")
+ .observations_for_endpoint(TransportId::RETICULUM, "reticulum:local")
.await
.expect("Reticulum endpoint observations");
assert_eq!(reticulum_observations.len(), 1);
- let expected_reticulum =
- RadrootsTransportTarget::reticulum().expect("canonical Reticulum target");
+ let expected_reticulum = Target::reticulum().expect("canonical Reticulum target");
assert_eq!(
&reticulum_observations[0].endpoint_fingerprint,
expected_reticulum.fingerprint()
@@ -9297,9 +9293,9 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
let endpoint_uri =
RadrootsTransportTargetUri::parse("wss://relay-a.local").expect("endpoint A");
let endpoint_b =
- RadrootsTransportTarget::nostr_relay("wss://relay-b.local").expect("endpoint B");
+ Target::new(TransportId::NOSTR, "wss://relay-b.local").expect("endpoint B");
let observation = RadrootsTransportObservation::from_unchecked_parts_for_test(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
endpoint_uri,
endpoint_b.fingerprint().clone(),
crate::RadrootsTransportObservationType::Subscription,
@@ -9338,7 +9334,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
let store = RadrootsEventStore::open_memory().await.expect("open");
let event = signed_event(KIND_POST, 16, Vec::new(), "observation corruption");
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://relay.local",
crate::RadrootsTransportObservationType::Subscription,
4_000,
@@ -9375,7 +9371,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
.expect("corrupt observation time order");
assert!(matches!(
store
- .observations_for_endpoint(RadrootsTransportKind::Nostr, "wss://relay.local")
+ .observations_for_endpoint(TransportId::NOSTR, "wss://relay.local")
.await,
Err(RadrootsEventStoreError::InvalidStoredTransportObservation {
first_observed_at_ms: 4_100,
diff --git a/crates/event_store/src/store/post_core_storage_v1.rs b/crates/event_store/src/store/post_core_storage_v1.rs
@@ -9,7 +9,7 @@ use radroots_event::id::{CandidateId, MutationId};
use radroots_event::trade::{
SellerReservationAssertionV1, TradeMutationEnvelopeV1, TradeMutationKindV1,
};
-use radroots_transport::RadrootsTransportKind;
+use radroots_transport::TransportId;
use sqlx::{Sqlite, Transaction};
pub(super) struct PostCoreStorageV1<'borrow, 'db> {
@@ -205,7 +205,7 @@ impl<'borrow, 'db> PostCoreStorageV1<'borrow, 'db> {
.bind(write.event.id_hex())
.bind(write.mutation_id.to_hex())
.bind(write.mutation.trade_id.to_hex())
- .bind(RadrootsTransportKind::Nostr.canonical_label())
+ .bind(TransportId::NOSTR.canonical_label())
.bind(write.event.author().to_hex())
.bind(i64_from_u64("created_at", write.event.created_at_u64())?)
.bind(write.stored_event_seq)
diff --git a/crates/mesh/src/model.rs b/crates/mesh/src/model.rs
@@ -1,7 +1,8 @@
use crate::RadrootsMeshError;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
-use radroots_transport::{RadrootsTransportError, RadrootsTransportMeshScopeId};
+use radroots_transport::RadrootsTransportError;
+use radroots_transport::target::TargetScope;
pub const RADROOTS_MESH_FRAME_VERSION: u16 = 1;
pub const RADROOTS_MESH_UNAVAILABLE_MESSAGE: &str =
@@ -65,11 +66,10 @@ pub enum RadrootsMeshScope {
impl RadrootsMeshScope {
pub fn custom(value: impl AsRef<str>) -> Result<Self, RadrootsMeshError> {
- let scope =
- RadrootsTransportMeshScopeId::parse(value.as_ref()).map_err(|err| match err {
- RadrootsTransportError::EmptyTargetScope => RadrootsMeshError::EmptyCustomScope,
- _ => RadrootsMeshError::InvalidCustomScope,
- })?;
+ let scope = TargetScope::parse(value.as_ref()).map_err(|err| match err {
+ RadrootsTransportError::EmptyTargetScope => RadrootsMeshError::EmptyCustomScope,
+ _ => RadrootsMeshError::InvalidCustomScope,
+ })?;
Ok(Self::Custom(scope.as_str().to_string()))
}
diff --git a/crates/mesh_agent_client/src/lib.rs b/crates/mesh_agent_client/src/lib.rs
@@ -6,8 +6,9 @@ use radroots_mesh::{
use radroots_mesh_agent_proto::{
RADROOTS_MESH_AGENT_SCHEMA_ID, RADROOTS_MESH_AGENT_SCHEMA_NAMESPACE, schema_sha256_hex,
};
-use radroots_transport::{RadrootsTransportKind, RadrootsTransportMeshScopeId};
-use radroots_transport_reticulum::RADROOTS_RETICULUM_ENDPOINT_URI;
+use radroots_transport::TransportId;
+use radroots_transport::target::TargetScope;
+use radroots_transport_reticulum::{RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID};
pub const RADROOTS_MESH_AGENT_CLIENT_SCHEMA_ID: &str = RADROOTS_MESH_AGENT_SCHEMA_ID;
pub const RADROOTS_MESH_AGENT_CLIENT_SCHEMA_NAMESPACE: &str = RADROOTS_MESH_AGENT_SCHEMA_NAMESPACE;
@@ -108,9 +109,9 @@ impl MeshAgentTransportKind {
}
}
- pub fn transport_kind(self) -> RadrootsTransportKind {
+ pub fn transport_kind(self) -> TransportId {
match self {
- Self::Reticulum => RadrootsTransportKind::Reticulum,
+ Self::Reticulum => TransportId::RETICULUM,
}
}
}
@@ -212,7 +213,7 @@ pub trait RadrootsMeshAgentClient {
pub struct RadrootsMockMeshAgentClient {
profile_id: String,
endpoint_uri: String,
- scope: RadrootsTransportMeshScopeId,
+ scope: TargetScope,
policy: RadrootsMeshPayloadPolicy,
}
@@ -221,12 +222,12 @@ impl RadrootsMockMeshAgentClient {
Self {
profile_id: RADROOTS_MESH_RETICULUM_POLICY_ID.to_owned(),
endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
- scope: RadrootsTransportMeshScopeId::local_reticulum(),
+ scope: TargetScope::parse(RADROOTS_RETICULUM_SCOPE_ID).expect("Reticulum scope"),
policy: RadrootsMeshPayloadPolicy::reticulum_unavailable(),
}
}
- pub fn scope(&self) -> &RadrootsTransportMeshScopeId {
+ pub fn scope(&self) -> &TargetScope {
&self.scope
}
diff --git a/crates/mesh_agent_client/tests/client.rs b/crates/mesh_agent_client/tests/client.rs
@@ -12,7 +12,7 @@ use radroots_mesh_agent_client::{
use radroots_mesh_agent_proto::{
RADROOTS_MESH_AGENT_SCHEMA_ID, RADROOTS_MESH_AGENT_SCHEMA_NAMESPACE, schema_sha256_hex,
};
-use radroots_transport::RadrootsTransportKind;
+use radroots_transport::TransportId;
use radroots_transport_reticulum::{RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID};
use serde_json::json;
@@ -40,10 +40,7 @@ fn mock_client_status_reports_reticulum_unavailable() {
assert_eq!(response.transports.len(), 1);
let status = &response.transports[0];
assert_eq!(status.transport, MeshAgentTransportKind::Reticulum);
- assert_eq!(
- status.transport.transport_kind(),
- RadrootsTransportKind::Reticulum
- );
+ assert_eq!(status.transport.transport_kind(), TransportId::RETICULUM);
assert_eq!(status.profile_id, RADROOTS_MESH_RETICULUM_POLICY_ID);
assert_eq!(status.endpoint_uri, RADROOTS_RETICULUM_ENDPOINT_URI);
assert!(status.configured);
diff --git a/crates/outbox/src/model.rs b/crates/outbox/src/model.rs
@@ -3,11 +3,10 @@
use crate::RadrootsOutboxError;
use radroots_event::draft::{EventDraft, SignedEvent};
use radroots_event::id::{MutationId, TradeId};
+use radroots_transport::target::{TargetFingerprint, TargetLabel, TargetScope};
use radroots_transport::{
- RadrootsTransportKind, RadrootsTransportMeshScopeId, RadrootsTransportOutcomeKind,
- RadrootsTransportSatisfactionClass, RadrootsTransportSatisfactionPolicy,
- RadrootsTransportTarget, RadrootsTransportTargetFingerprint, RadrootsTransportTargetLabel,
- RadrootsTransportTargetUri,
+ RadrootsTransportOutcomeKind, RadrootsTransportSatisfactionClass,
+ RadrootsTransportSatisfactionPolicy, RadrootsTransportTargetUri, Target, TransportId,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -248,7 +247,7 @@ pub struct RadrootsOutboxDeliveryPlanInput {
pub transport_profile_id: String,
pub target_policy_version: u32,
pub satisfaction_policy: RadrootsTransportSatisfactionPolicy,
- pub targets: Vec<RadrootsTransportTarget>,
+ pub targets: Vec<Target>,
pub reticulum_behavior: RadrootsOutboxReticulumBehavior,
}
@@ -257,7 +256,7 @@ impl RadrootsOutboxDeliveryPlanInput {
transport_profile_id: impl Into<String>,
target_policy_version: u32,
satisfaction_policy: RadrootsTransportSatisfactionPolicy,
- targets: Vec<RadrootsTransportTarget>,
+ targets: Vec<Target>,
) -> Self {
Self {
transport_profile_id: transport_profile_id.into(),
@@ -518,11 +517,11 @@ pub struct RadrootsOutboxDeliveryPlanRecord {
pub struct RadrootsOutboxDeliveryTargetRecord {
pub delivery_target_id: i64,
pub delivery_plan_id: i64,
- pub transport_kind: RadrootsTransportKind,
+ pub transport_kind: TransportId,
pub endpoint_uri: RadrootsTransportTargetUri,
- pub target_scope: Option<RadrootsTransportMeshScopeId>,
- pub target_label: Option<RadrootsTransportTargetLabel>,
- pub endpoint_fingerprint: RadrootsTransportTargetFingerprint,
+ pub target_scope: Option<TargetScope>,
+ pub target_label: Option<TargetLabel>,
+ pub endpoint_fingerprint: TargetFingerprint,
pub status: RadrootsOutboxDeliveryTargetStatus,
pub last_outcome_kind: Option<RadrootsTransportOutcomeKind>,
pub attempt_count: i64,
diff --git a/crates/outbox/src/store.rs b/crates/outbox/src/store.rs
@@ -25,11 +25,10 @@ use radroots_event_store::{
RadrootsTransportObservationType,
};
use radroots_protocol::radrootsd::transport_publish::v5::RETICULUM_ENDPOINT_URI as RADROOTS_RETICULUM_ENDPOINT_URI;
+use radroots_transport::target::{TargetFingerprint, TargetLabel, TargetScope};
use radroots_transport::{
- RadrootsTransportError, RadrootsTransportKind, RadrootsTransportMeshScopeId,
- RadrootsTransportOutcomeKind, RadrootsTransportSatisfactionClass,
- RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget,
- RadrootsTransportTargetFingerprint, RadrootsTransportTargetLabel,
+ RadrootsTransportError, RadrootsTransportOutcomeKind, RadrootsTransportSatisfactionClass,
+ RadrootsTransportSatisfactionPolicy, Target, TransportId,
};
use serde::Serialize;
use sha2::{Digest, Sha256};
@@ -1228,7 +1227,7 @@ impl RadrootsOutbox {
.signed_event
.ok_or(RadrootsOutboxError::MissingSignedEvent(outbox_event_id))?;
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Local,
+ TransportId::LOCAL,
"local:outbox",
RadrootsTransportObservationType::LocalImport,
observed_at_ms,
@@ -1752,7 +1751,7 @@ struct PreparedDeliveryPlan {
}
struct PreparedDeliveryTarget {
- target: RadrootsTransportTarget,
+ target: Target,
initial_status: RadrootsOutboxDeliveryTargetStatus,
}
@@ -2004,8 +2003,8 @@ fn satisfaction_target_count(
delivery_capable_target_count
}
-fn validate_delivery_target(target: &RadrootsTransportTarget) -> Result<(), RadrootsOutboxError> {
- if target.kind() == &RadrootsTransportKind::Reticulum
+fn validate_delivery_target(target: &Target) -> Result<(), RadrootsOutboxError> {
+ if target.kind() == &TransportId::RETICULUM
&& target.uri().as_str() != RADROOTS_RETICULUM_ENDPOINT_URI
{
return Err(RadrootsOutboxError::Transport(
@@ -2015,7 +2014,7 @@ fn validate_delivery_target(target: &RadrootsTransportTarget) -> Result<(), Radr
Ok(())
}
-fn validate_unique_targets(targets: &[RadrootsTransportTarget]) -> Result<(), RadrootsOutboxError> {
+fn validate_unique_targets(targets: &[Target]) -> Result<(), RadrootsOutboxError> {
let mut fingerprints = BTreeSet::new();
for target in targets {
if !fingerprints.insert(target.fingerprint().as_str()) {
@@ -2028,10 +2027,10 @@ fn validate_unique_targets(targets: &[RadrootsTransportTarget]) -> Result<(), Ra
}
fn initial_delivery_target_status(
- target: &RadrootsTransportTarget,
+ target: &Target,
_reticulum_behavior: RadrootsOutboxReticulumBehavior,
) -> RadrootsOutboxDeliveryTargetStatus {
- if target.kind() != &RadrootsTransportKind::Reticulum {
+ if target.kind() != &TransportId::RETICULUM {
return RadrootsOutboxDeliveryTargetStatus::Pending;
}
RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
@@ -2186,13 +2185,13 @@ async fn insert_or_get_delivery_plan(
prepared_target
.target
.scope()
- .map(RadrootsTransportMeshScopeId::as_str),
+ .map(TargetScope::as_str),
)
.bind(
prepared_target
.target
.label()
- .map(RadrootsTransportTargetLabel::as_str),
+ .map(TargetLabel::as_str),
)
.bind(prepared_target.target.fingerprint().as_str())
.bind(prepared_target.initial_status.as_str())
@@ -2846,59 +2845,53 @@ fn delivery_target_from_row(
field,
};
let transport_kind_raw = row.try_get::<String, _>("transport_kind")?;
- let transport_kind = RadrootsTransportKind::parse_canonical(transport_kind_raw.as_str())
+ let transport_kind = TransportId::parse_canonical(transport_kind_raw.as_str())
.map_err(|_| invalid_identity("transport_kind"))?;
let endpoint_uri_raw = row.try_get::<String, _>("endpoint_uri")?;
let target_scope_raw = row.try_get::<Option<String>, _>("target_scope")?;
let target_scope = target_scope_raw
.as_deref()
- .map(RadrootsTransportMeshScopeId::parse)
+ .map(TargetScope::parse)
.transpose()
.map_err(|_| invalid_identity("target_scope"))?;
- if target_scope
- .as_ref()
- .map(RadrootsTransportMeshScopeId::as_str)
- != target_scope_raw.as_deref()
- {
+ if target_scope.as_ref().map(TargetScope::as_str) != target_scope_raw.as_deref() {
return Err(invalid_identity("target_scope"));
}
let target_label_raw = row.try_get::<Option<String>, _>("target_label")?;
let target_label = target_label_raw
.as_deref()
- .map(RadrootsTransportTargetLabel::parse)
+ .map(TargetLabel::parse)
.transpose()
.map_err(|_| invalid_identity("target_label"))?;
- if target_label
- .as_ref()
- .map(RadrootsTransportTargetLabel::as_str)
- != target_label_raw.as_deref()
- {
+ if target_label.as_ref().map(TargetLabel::as_str) != target_label_raw.as_deref() {
return Err(invalid_identity("target_label"));
}
let endpoint_fingerprint_raw = row.try_get::<String, _>("endpoint_fingerprint")?;
- let endpoint_fingerprint =
- RadrootsTransportTargetFingerprint::parse(endpoint_fingerprint_raw.as_str())
- .map_err(|_| invalid_identity("endpoint_fingerprint"))?;
+ let endpoint_fingerprint = TargetFingerprint::parse(endpoint_fingerprint_raw.as_str())
+ .map_err(|_| invalid_identity("endpoint_fingerprint"))?;
if endpoint_fingerprint.as_str() != endpoint_fingerprint_raw {
return Err(invalid_identity("endpoint_fingerprint"));
}
let target = match transport_kind {
- RadrootsTransportKind::Nostr => RadrootsTransportTarget::nostr_relay_with_metadata(
+ TransportId::NOSTR => Target::new_with_metadata(
+ TransportId::NOSTR,
endpoint_uri_raw.as_str(),
target_scope.clone(),
target_label.clone(),
),
- RadrootsTransportKind::Reticulum => RadrootsTransportTarget::reticulum_with_metadata(
+ TransportId::RETICULUM => Target::new_with_metadata(
+ TransportId::RETICULUM,
endpoint_uri_raw.as_str(),
target_scope.clone(),
target_label.clone(),
),
- RadrootsTransportKind::Local => RadrootsTransportTarget::local_with_metadata(
+ TransportId::LOCAL => Target::new_with_metadata(
+ TransportId::LOCAL,
endpoint_uri_raw.as_str(),
target_scope.clone(),
target_label.clone(),
),
- _ => RadrootsTransportTarget::new_with_metadata(
+ _ => Target::new_with_metadata(
transport_kind,
endpoint_uri_raw.as_str(),
target_scope.clone(),
@@ -2924,7 +2917,7 @@ fn delivery_target_from_row(
Ok(RadrootsOutboxDeliveryTargetRecord {
delivery_target_id,
delivery_plan_id: row.try_get("delivery_plan_id")?,
- transport_kind: target.kind().clone(),
+ transport_kind: *target.kind(),
endpoint_uri: target.uri().clone(),
target_scope: target.scope().cloned(),
target_label: target.label().cloned(),
@@ -3265,7 +3258,7 @@ fn satisfaction_policy_storage_value(policy: &RadrootsTransportSatisfactionPolic
RadrootsTransportSatisfactionPolicy::RequiredTargets { class, targets } => {
let mut fingerprints = targets
.iter()
- .map(RadrootsTransportTargetFingerprint::as_str)
+ .map(TargetFingerprint::as_str)
.collect::<Vec<_>>();
fingerprints.sort();
let fingerprints = fingerprints.join(",");
@@ -3353,7 +3346,7 @@ fn parse_required_target_policy(
) -> Result<RadrootsTransportSatisfactionPolicy, RadrootsOutboxError> {
let targets = fingerprints
.split(',')
- .map(RadrootsTransportTargetFingerprint::parse)
+ .map(TargetFingerprint::parse)
.collect::<Result<Vec<_>, _>>()?;
let required_success_count = usize::from(required_count_u16(required_success_count)?);
if required_success_count != targets.len() {
@@ -3596,31 +3589,33 @@ mod tests {
(draft, signed_event)
}
- fn nostr_target(uri: &str) -> RadrootsTransportTarget {
- RadrootsTransportTarget::nostr_relay(uri).expect("nostr target")
+ fn nostr_target(uri: &str) -> Target {
+ Target::new(TransportId::NOSTR, uri).expect("nostr target")
}
- fn scoped_nostr_target(uri: &str, scope: &str, label: &str) -> RadrootsTransportTarget {
- RadrootsTransportTarget::nostr_relay_with_metadata(
+ fn scoped_nostr_target(uri: &str, scope: &str, label: &str) -> Target {
+ Target::new_with_metadata(
+ TransportId::NOSTR,
uri,
- Some(RadrootsTransportMeshScopeId::parse(scope).expect("target scope")),
- Some(RadrootsTransportTargetLabel::parse(label).expect("target label")),
+ Some(TargetScope::parse(scope).expect("target scope")),
+ Some(TargetLabel::parse(label).expect("target label")),
)
.expect("scoped nostr target")
}
- fn scoped_reticulum_target(scope: &str, label: &str) -> RadrootsTransportTarget {
- RadrootsTransportTarget::reticulum_with_metadata(
+ fn scoped_reticulum_target(scope: &str, label: &str) -> Target {
+ Target::new_with_metadata(
+ TransportId::RETICULUM,
RADROOTS_RETICULUM_ENDPOINT_URI,
- Some(RadrootsTransportMeshScopeId::parse(scope).expect("target scope")),
- Some(RadrootsTransportTargetLabel::parse(label).expect("target label")),
+ Some(TargetScope::parse(scope).expect("target scope")),
+ Some(TargetLabel::parse(label).expect("target label")),
)
.expect("scoped reticulum target")
}
- fn reticulum_target(uri: &str) -> RadrootsTransportTarget {
+ fn reticulum_target(uri: &str) -> Target {
assert_eq!(uri, RADROOTS_RETICULUM_ENDPOINT_URI);
- RadrootsTransportTarget::reticulum().expect("reticulum target")
+ Target::reticulum().expect("reticulum target")
}
#[test]
@@ -3989,7 +3984,7 @@ mod tests {
assert!(prepare_delivery_plan("event-no-wait", &empty_no_wait).is_ok());
}
- fn delivery_plan(targets: Vec<RadrootsTransportTarget>) -> RadrootsOutboxDeliveryPlanInput {
+ fn delivery_plan(targets: Vec<Target>) -> RadrootsOutboxDeliveryPlanInput {
RadrootsOutboxDeliveryPlanInput::new(
"transport.nostr.local",
1,
@@ -4033,13 +4028,13 @@ mod tests {
canonical: &TradeCanonicalMutationV1,
draft: EventDraft,
signed_event: SignedEvent,
- targets: Vec<RadrootsTransportTarget>,
+ targets: Vec<Target>,
created_at_ms: i64,
) -> RadrootsOutboxSignedTradeMutationInput {
RadrootsOutboxSignedTradeMutationInput::new(
"publish_trade_mutation",
- canonical.envelope.trade_id.clone(),
- canonical.mutation_id.clone(),
+ canonical.envelope.trade_id,
+ canonical.mutation_id,
sha256_hex(canonical.content.as_bytes()),
draft,
signed_event,
@@ -4053,13 +4048,13 @@ mod tests {
fn trade_mutation_input(
canonical: &TradeCanonicalMutationV1,
draft: EventDraft,
- targets: Vec<RadrootsTransportTarget>,
+ targets: Vec<Target>,
created_at_ms: i64,
) -> RadrootsOutboxTradeMutationInput {
RadrootsOutboxTradeMutationInput::new(
"publish_trade_mutation",
- canonical.envelope.trade_id.clone(),
- canonical.mutation_id.clone(),
+ canonical.envelope.trade_id,
+ canonical.mutation_id,
sha256_hex(canonical.content.as_bytes()),
draft,
delivery_plan(targets),
@@ -4659,7 +4654,7 @@ mod tests {
.is_err()
);
sqlx::query("UPDATE outbox_operations SET trade_id = ?, mutation_id = 'invalid' WHERE operation_id = ?")
- .bind(&canonical.envelope.trade_id.to_hex())
+ .bind(canonical.envelope.trade_id.to_hex())
.bind(trade_operation.operation_id)
.execute(outbox.pool())
.await
@@ -4671,7 +4666,7 @@ mod tests {
.is_err()
);
sqlx::query("UPDATE outbox_operations SET mutation_id = ? WHERE operation_id = ?")
- .bind(&canonical.mutation_id.to_hex())
+ .bind(canonical.mutation_id.to_hex())
.bind(trade_operation.operation_id)
.execute(outbox.pool())
.await
@@ -5776,7 +5771,7 @@ mod tests {
sqlx::query(
"UPDATE outbox_operations SET mutation_id = ?, operation_idempotency_digest = 'corrupt' WHERE operation_id = ?",
)
- .bind(&canonical.mutation_id.to_hex())
+ .bind(canonical.mutation_id.to_hex())
.bind(first.operation_id)
.execute(outbox.pool())
.await
@@ -6037,9 +6032,7 @@ mod tests {
.enqueue_operation(RadrootsOutboxOperationInput::new(
"publish_post",
draft,
- delivery_plan(vec![
- RadrootsTransportTarget::reticulum().expect("Reticulum target"),
- ]),
+ delivery_plan(vec![Target::reticulum().expect("Reticulum target")]),
1_000,
))
.await
@@ -6118,7 +6111,7 @@ mod tests {
async fn enqueue_rejects_invalid_reticulum_targets_before_persistence() {
let outbox = RadrootsOutbox::open_memory().await.expect("open");
assert_eq!(
- RadrootsTransportTarget::new(RadrootsTransportKind::Reticulum, "reticulum:alternate",)
+ Target::new(TransportId::RETICULUM, "reticulum:alternate",)
.expect_err("invalid Reticulum target"),
RadrootsTransportError::InvalidTargetUri
);
@@ -8110,7 +8103,7 @@ mod tests {
.await
.expect("observations");
assert_eq!(observations.len(), 1);
- assert_eq!(observations[0].transport_kind, RadrootsTransportKind::Local);
+ assert_eq!(observations[0].transport_kind, TransportId::LOCAL);
assert_eq!(observations[0].endpoint_uri.as_str(), "local:outbox");
assert_eq!(
observations[0].observation_type,
diff --git a/crates/runtime/src/transport.rs b/crates/runtime/src/transport.rs
@@ -7,9 +7,9 @@ use radroots_event::{draft::SignedEvent, wire::Nip01EventWire};
#[cfg(feature = "transport-workers")]
use radroots_transport::RadrootsTransportTargetReceipt;
use radroots_transport::{
- RadrootsTransport, RadrootsTransportDeliveryRequest, RadrootsTransportDeliveryTargetStatus,
- RadrootsTransportError, RadrootsTransportKind, RadrootsTransportPayload,
- RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget, RadrootsTransportTargetSet,
+ EventSink, EventSource, RadrootsTransport, RadrootsTransportDeliveryRequest,
+ RadrootsTransportDeliveryTargetStatus, RadrootsTransportError, RadrootsTransportPayload,
+ RadrootsTransportSatisfactionPolicy, Target, TargetSet, TransportId,
};
use thiserror::Error;
@@ -99,7 +99,7 @@ fn verify_signed_event_raw_json_matches_event(
pub struct RadrootsRuntimeTransportDispatchRequest {
pub request_id: String,
pub payload: RadrootsRuntimeTransportPayload,
- pub target_set: RadrootsTransportTargetSet,
+ pub target_set: TargetSet,
pub satisfaction_policy: RadrootsTransportSatisfactionPolicy,
pub now_ms: i64,
}
@@ -108,14 +108,14 @@ impl RadrootsRuntimeTransportDispatchRequest {
pub fn new(
request_id: impl Into<String>,
payload: RadrootsRuntimeTransportPayload,
- targets: Vec<RadrootsTransportTarget>,
+ targets: Vec<Target>,
satisfaction_policy: RadrootsTransportSatisfactionPolicy,
now_ms: i64,
) -> Result<Self, RadrootsRuntimeTransportError> {
if targets.is_empty() {
return Err(RadrootsRuntimeTransportError::EmptyDispatchTargets);
}
- let target_set = RadrootsTransportTargetSet::new(targets)?;
+ let target_set = TargetSet::new(targets)?;
Ok(Self {
request_id: request_id.into(),
payload,
@@ -140,7 +140,10 @@ impl RadrootsRuntimeTransportDispatchRequest {
#[derive(Clone, Default)]
pub struct RadrootsRuntimeTransportRegistry {
- transports: BTreeMap<RadrootsTransportKind, Arc<dyn RadrootsTransport>>,
+ sources: BTreeMap<TransportId, Arc<dyn EventSource>>,
+ sinks: BTreeMap<TransportId, Arc<dyn EventSink>>,
+ #[doc(hidden)]
+ transports: BTreeMap<TransportId, Arc<dyn RadrootsTransport>>,
}
impl RadrootsRuntimeTransportRegistry {
@@ -148,6 +151,67 @@ impl RadrootsRuntimeTransportRegistry {
Self::default()
}
+ pub fn register_source<T>(
+ &mut self,
+ transport_id: TransportId,
+ source: T,
+ ) -> Result<(), RadrootsRuntimeTransportError>
+ where
+ T: EventSource + 'static,
+ {
+ if self.sources.contains_key(&transport_id) {
+ return Err(RadrootsRuntimeTransportError::TransportAlreadyRegistered(
+ transport_id.canonical_label(),
+ ));
+ }
+ self.sources.insert(transport_id, Arc::new(source));
+ Ok(())
+ }
+
+ pub fn register_sink<T>(
+ &mut self,
+ transport_id: TransportId,
+ sink: T,
+ ) -> Result<(), RadrootsRuntimeTransportError>
+ where
+ T: EventSink + 'static,
+ {
+ if self.sinks.contains_key(&transport_id) {
+ return Err(RadrootsRuntimeTransportError::TransportAlreadyRegistered(
+ transport_id.canonical_label(),
+ ));
+ }
+ self.sinks.insert(transport_id, Arc::new(sink));
+ Ok(())
+ }
+
+ pub fn source(
+ &self,
+ transport_id: &TransportId,
+ ) -> Result<Arc<dyn EventSource>, RadrootsRuntimeTransportError> {
+ self.sources.get(transport_id).cloned().ok_or_else(|| {
+ RadrootsRuntimeTransportError::TransportNotRegistered(transport_id.canonical_label())
+ })
+ }
+
+ pub fn sink(
+ &self,
+ transport_id: &TransportId,
+ ) -> Result<Arc<dyn EventSink>, RadrootsRuntimeTransportError> {
+ self.sinks.get(transport_id).cloned().ok_or_else(|| {
+ RadrootsRuntimeTransportError::TransportNotRegistered(transport_id.canonical_label())
+ })
+ }
+
+ pub fn registered_source_ids(&self) -> Vec<TransportId> {
+ self.sources.keys().copied().collect()
+ }
+
+ pub fn registered_sink_ids(&self) -> Vec<TransportId> {
+ self.sinks.keys().copied().collect()
+ }
+
+ #[doc(hidden)]
pub fn register<T>(&mut self, transport: T) -> Result<(), RadrootsRuntimeTransportError>
where
T: RadrootsTransport + 'static,
@@ -162,17 +226,25 @@ impl RadrootsRuntimeTransportRegistry {
Ok(())
}
+ #[doc(hidden)]
pub fn transport(
&self,
- kind: &RadrootsTransportKind,
+ kind: &TransportId,
) -> Result<Arc<dyn RadrootsTransport>, RadrootsRuntimeTransportError> {
self.transports.get(kind).cloned().ok_or_else(|| {
RadrootsRuntimeTransportError::TransportNotRegistered(kind.canonical_label())
})
}
- pub fn registered_kinds(&self) -> Vec<RadrootsTransportKind> {
- self.transports.keys().cloned().collect()
+ pub fn registered_kinds(&self) -> Vec<TransportId> {
+ self.sources
+ .keys()
+ .chain(self.sinks.keys())
+ .chain(self.transports.keys())
+ .copied()
+ .collect::<std::collections::BTreeSet<_>>()
+ .into_iter()
+ .collect()
}
}
@@ -251,12 +323,12 @@ impl<T> RadrootsRuntimeBoundedQueue<T> {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RadrootsRuntimeDeliveryTarget {
pub delivery_target_id: i64,
- pub target: RadrootsTransportTarget,
+ pub target: Target,
pub status: RadrootsTransportDeliveryTargetStatus,
}
impl RadrootsRuntimeDeliveryTarget {
- pub fn ready(delivery_target_id: i64, target: RadrootsTransportTarget) -> Self {
+ pub fn ready(delivery_target_id: i64, target: Target) -> Self {
Self {
delivery_target_id,
target,
@@ -264,10 +336,7 @@ impl RadrootsRuntimeDeliveryTarget {
}
}
- pub fn deferred_until_implemented(
- delivery_target_id: i64,
- target: RadrootsTransportTarget,
- ) -> Self {
+ pub fn deferred_until_implemented(delivery_target_id: i64, target: Target) -> Self {
Self {
delivery_target_id,
target,
@@ -313,7 +382,7 @@ impl Default for RadrootsRuntimeDeliveryWorkerConfig {
#[cfg(feature = "transport-workers")]
#[derive(Clone, Debug, PartialEq, Eq)]
struct RadrootsRuntimeDeliveryTargetState {
- target: RadrootsTransportTarget,
+ target: Target,
status: RadrootsTransportDeliveryTargetStatus,
}
@@ -480,15 +549,14 @@ impl<'a> RadrootsRuntimeDeliveryWorker<'a> {
)
})
.collect::<BTreeMap<_, _>>();
- let mut by_kind =
- BTreeMap::<RadrootsTransportKind, Vec<RadrootsRuntimeDeliveryTarget>>::new();
+ let mut by_kind = BTreeMap::<TransportId, Vec<RadrootsRuntimeDeliveryTarget>>::new();
for target in plan
.targets
.into_iter()
.filter(RadrootsRuntimeDeliveryTarget::is_ready_for_attempt)
{
by_kind
- .entry(target.target.kind().clone())
+ .entry(*target.target.kind())
.or_default()
.push(target);
}
@@ -586,7 +654,7 @@ pub fn recover_expired_leases(leases: &mut [RadrootsRuntimeLeaseRecord], now_ms:
pub struct RadrootsRuntimeInboundObservation {
pub event_id: String,
pub verified: bool,
- pub transport_kind: RadrootsTransportKind,
+ pub transport_kind: TransportId,
pub endpoint_uri: String,
pub observed_at_ms: i64,
}
@@ -595,7 +663,7 @@ pub struct RadrootsRuntimeInboundObservation {
impl RadrootsRuntimeInboundObservation {
pub fn verified_signed_event(
event: &SignedEvent,
- transport_kind: RadrootsTransportKind,
+ transport_kind: TransportId,
endpoint_uri: impl Into<String>,
observed_at_ms: i64,
) -> Self {
@@ -673,28 +741,26 @@ mod tests {
#[cfg(feature = "transport-workers")]
use radroots_event::{draft::SignedEvent, wire::Nip01EventWire};
use radroots_transport::{
- RadrootsTransport, RadrootsTransportCapabilities, RadrootsTransportCapabilityAvailability,
- RadrootsTransportCapabilityMaturity, RadrootsTransportDeliveryReceipt,
+ RadrootsTransport, RadrootsTransportCapabilities, RadrootsTransportDeliveryReceipt,
RadrootsTransportDeliveryRequest, RadrootsTransportDeliveryTargetStatus,
RadrootsTransportError, RadrootsTransportFetchReceipt, RadrootsTransportFetchRequest,
- RadrootsTransportFuture, RadrootsTransportImplementationState, RadrootsTransportKind,
- RadrootsTransportOutcome, RadrootsTransportOutcomeKind, RadrootsTransportPayload,
- RadrootsTransportSatisfactionClass, RadrootsTransportSatisfactionPolicy,
- RadrootsTransportStatus, RadrootsTransportTarget, RadrootsTransportTargetReceipt,
- RadrootsTransportTargetSet,
+ RadrootsTransportFuture, RadrootsTransportImplementationState, RadrootsTransportOutcome,
+ RadrootsTransportOutcomeKind, RadrootsTransportPayload, RadrootsTransportSatisfactionClass,
+ RadrootsTransportSatisfactionPolicy, RadrootsTransportStatus,
+ RadrootsTransportTargetReceipt, Target, TargetSet, TransportId,
};
#[cfg(feature = "transport-workers")]
use std::sync::{Arc, Mutex};
struct StaticTransport {
- kind: RadrootsTransportKind,
+ kind: TransportId,
outcome_kind: RadrootsTransportOutcomeKind,
#[cfg(feature = "transport-workers")]
captured_now_ms: Option<Arc<Mutex<Vec<i64>>>>,
}
impl StaticTransport {
- fn new(kind: RadrootsTransportKind, outcome_kind: RadrootsTransportOutcomeKind) -> Self {
+ fn new(kind: TransportId, outcome_kind: RadrootsTransportOutcomeKind) -> Self {
Self {
kind,
outcome_kind,
@@ -705,7 +771,7 @@ mod tests {
#[cfg(feature = "transport-workers")]
fn recording_now_ms(
- kind: RadrootsTransportKind,
+ kind: TransportId,
outcome_kind: RadrootsTransportOutcomeKind,
) -> (Self, Arc<Mutex<Vec<i64>>>) {
let captured_now_ms = Arc::new(Mutex::new(Vec::new()));
@@ -721,14 +787,14 @@ mod tests {
}
impl RadrootsTransport for StaticTransport {
- fn transport_kind(&self) -> RadrootsTransportKind {
- self.kind.clone()
+ fn transport_kind(&self) -> TransportId {
+ self.kind
}
fn status<'a>(&'a self) -> RadrootsTransportFuture<'a, RadrootsTransportStatus> {
Box::pin(async move {
Ok(RadrootsTransportStatus::new(
- self.kind.clone(),
+ self.kind,
true,
RadrootsTransportImplementationState::Real,
true,
@@ -807,14 +873,14 @@ mod tests {
#[cfg(feature = "transport-workers")]
impl RadrootsTransport for ForgedReceiptTransport {
- fn transport_kind(&self) -> RadrootsTransportKind {
- RadrootsTransportKind::Nostr
+ fn transport_kind(&self) -> TransportId {
+ TransportId::NOSTR
}
fn status<'a>(&'a self) -> RadrootsTransportFuture<'a, RadrootsTransportStatus> {
Box::pin(async {
Ok(RadrootsTransportStatus::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
true,
RadrootsTransportImplementationState::Real,
true,
@@ -848,11 +914,10 @@ mod tests {
.collect(),
),
ForgedDeliveryReceipt::TargetSet => {
- let target =
- RadrootsTransportTarget::nostr_relay("wss://forged-relay.example")?;
+ let target = Target::new(TransportId::NOSTR, "wss://forged-relay.example")?;
RadrootsTransportDeliveryReceipt::new(
request.request_id(),
- RadrootsTransportTargetSet::new(vec![target.clone()])?,
+ TargetSet::new(vec![target.clone()])?,
vec![RadrootsTransportTargetReceipt::new(
target,
RadrootsTransportOutcome::new(
@@ -873,8 +938,8 @@ mod tests {
}
}
- fn target(kind: RadrootsTransportKind, uri: &str) -> RadrootsTransportTarget {
- RadrootsTransportTarget::new(kind, uri).expect("target")
+ fn target(kind: TransportId, uri: &str) -> Target {
+ Target::new(kind, uri).expect("target")
}
fn opaque_payload() -> RadrootsRuntimeTransportPayload {
@@ -956,29 +1021,26 @@ mod tests {
let mut registry = RadrootsRuntimeTransportRegistry::new();
registry
.register(StaticTransport::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
RadrootsTransportOutcomeKind::Accepted,
))
.expect("register");
- assert_eq!(
- registry.registered_kinds(),
- vec![RadrootsTransportKind::Nostr]
- );
+ assert_eq!(registry.registered_kinds(), vec![TransportId::NOSTR]);
assert!(matches!(
registry.register(StaticTransport::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
RadrootsTransportOutcomeKind::Accepted,
)),
Err(RadrootsRuntimeTransportError::TransportAlreadyRegistered(_))
));
let transport = registry
- .transport(&RadrootsTransportKind::Nostr)
+ .transport(&TransportId::NOSTR)
.expect("nostr transport");
let request = RadrootsRuntimeTransportDispatchRequest::new(
"nostr-delivery",
opaque_payload(),
- vec![target(RadrootsTransportKind::Nostr, "wss://relay.example")],
+ vec![target(TransportId::NOSTR, "wss://relay.example")],
RadrootsTransportSatisfactionPolicy::any_accepted(),
1_000,
)
@@ -992,7 +1054,7 @@ mod tests {
.await
.expect("receipt");
let status = transport.status().await.expect("status");
- assert_eq!(status.kind, RadrootsTransportKind::Nostr);
+ assert_eq!(status.kind, TransportId::NOSTR);
assert_eq!(
status.capabilities,
RadrootsTransportCapabilities::deliver_and_fetch()
@@ -1000,11 +1062,8 @@ mod tests {
let fetch = transport
.fetch(RadrootsTransportFetchRequest::new(
"nostr-fetch",
- RadrootsTransportTargetSet::new(vec![target(
- RadrootsTransportKind::Nostr,
- "wss://relay.example",
- )])
- .expect("target set"),
+ TargetSet::new(vec![target(TransportId::NOSTR, "wss://relay.example")])
+ .expect("target set"),
))
.await
.expect("fetch");
@@ -1025,7 +1084,7 @@ mod tests {
let request = RadrootsRuntimeTransportDispatchRequest::new(
"nostr-delivery",
opaque_payload(),
- vec![target(RadrootsTransportKind::Nostr, "wss://relay.example")],
+ vec![target(TransportId::NOSTR, "wss://relay.example")],
RadrootsTransportSatisfactionPolicy::any_accepted(),
123_456,
)
@@ -1040,65 +1099,38 @@ mod tests {
#[cfg(feature = "transport-reticulum")]
#[tokio::test]
- async fn registry_dispatches_reticulum_transport_without_success() {
+ async fn registry_exposes_reticulum_as_split_unavailable_capabilities() {
let mut registry = RadrootsRuntimeTransportRegistry::new();
+ let transport = radroots_transport_reticulum::RadrootsReticulumTransport::default();
registry
- .register(radroots_transport_reticulum::RadrootsReticulumTransport::default())
- .expect("register");
- let transport = registry
- .transport(&RadrootsTransportKind::Reticulum)
- .expect("reticulum transport");
- let request = RadrootsRuntimeTransportDispatchRequest::new(
- "reticulum-delivery",
- opaque_payload(),
- vec![target(RadrootsTransportKind::Reticulum, "reticulum:local")],
- RadrootsTransportSatisfactionPolicy::any_accepted(),
- 1_000,
- )
- .expect("request");
- let receipt = transport
- .deliver(
- request
- .transport_delivery_request()
- .expect("delivery request"),
- )
- .await
- .expect("receipt");
- let status = transport.status().await.expect("status");
- assert_eq!(
- status.implementation,
- RadrootsTransportImplementationState::Real
- );
- assert_eq!(
- status.maturity,
- RadrootsTransportCapabilityMaturity::Preview
- );
+ .register_sink(TransportId::RETICULUM, transport.clone())
+ .expect("register sink");
+ registry
+ .register_source(TransportId::RETICULUM, transport)
+ .expect("register source");
+ let sink = registry
+ .sink(&TransportId::RETICULUM)
+ .expect("reticulum sink");
+ let source = registry
+ .source(&TransportId::RETICULUM)
+ .expect("reticulum source");
+ let sink_status = sink.status().await.expect("sink status");
+ let source_status = source.status().await.expect("source status");
+
assert_eq!(
- status.availability,
- RadrootsTransportCapabilityAvailability::Unavailable
+ sink_status.availability(),
+ radroots_transport::capability::Availability::Unavailable
);
- assert!(!status.capabilities.deliver);
- assert!(!status.capabilities.fetch);
- let fetch = transport
- .fetch(RadrootsTransportFetchRequest::new(
- "reticulum-fetch",
- RadrootsTransportTargetSet::new(vec![target(
- RadrootsTransportKind::Reticulum,
- "reticulum:local",
- )])
- .expect("target set"),
- ))
- .await
- .expect("fetch");
- assert_eq!(fetch.fetched_count, 0);
-
assert_eq!(
- receipt.satisfied_target_count(RadrootsTransportSatisfactionClass::Accepted),
- 0
+ source_status.availability(),
+ radroots_transport::capability::Availability::Unavailable
);
+ assert!(!sink_status.capabilities().can_deliver());
+ assert!(!source_status.capabilities().can_fetch());
+ assert_eq!(registry.registered_sink_ids(), vec![TransportId::RETICULUM]);
assert_eq!(
- receipt.target_receipts()[0].status,
- RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
+ registry.registered_source_ids(),
+ vec![TransportId::RETICULUM]
);
}
@@ -1123,7 +1155,7 @@ mod tests {
let mut registry = RadrootsRuntimeTransportRegistry::new();
registry
.register(StaticTransport::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
RadrootsTransportOutcomeKind::Accepted,
))
.expect("register");
@@ -1135,11 +1167,11 @@ mod tests {
);
let ready = RadrootsRuntimeDeliveryTarget::ready(
1,
- target(RadrootsTransportKind::Nostr, "wss://relay.example"),
+ target(TransportId::NOSTR, "wss://relay.example"),
);
let deferred = RadrootsRuntimeDeliveryTarget::deferred_until_implemented(
2,
- target(RadrootsTransportKind::Reticulum, "reticulum:local"),
+ target(TransportId::RETICULUM, "reticulum:local"),
);
let receipt = worker
.execute_job(RadrootsRuntimeDeliveryJob {
@@ -1180,7 +1212,7 @@ mod tests {
async fn delivery_worker_passes_job_now_ms_to_registered_transport() {
let mut registry = RadrootsRuntimeTransportRegistry::new();
let (transport, captured_now_ms) = StaticTransport::recording_now_ms(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
RadrootsTransportOutcomeKind::Accepted,
);
registry.register(transport).expect("register");
@@ -1199,7 +1231,7 @@ mod tests {
satisfaction_policy: RadrootsTransportSatisfactionPolicy::any_accepted(),
targets: vec![RadrootsRuntimeDeliveryTarget::ready(
1,
- target(RadrootsTransportKind::Nostr, "wss://relay.example"),
+ target(TransportId::NOSTR, "wss://relay.example"),
)],
}],
now_ms: 987_654,
@@ -1241,7 +1273,7 @@ mod tests {
satisfaction_policy: RadrootsTransportSatisfactionPolicy::any_accepted(),
targets: vec![RadrootsRuntimeDeliveryTarget::ready(
1,
- target(RadrootsTransportKind::Nostr, "wss://relay.example"),
+ target(TransportId::NOSTR, "wss://relay.example"),
)],
}],
now_ms: 1_000,
@@ -1297,7 +1329,7 @@ mod tests {
let mut registry = RadrootsRuntimeTransportRegistry::new();
registry
.register(StaticTransport::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
RadrootsTransportOutcomeKind::Accepted,
))
.expect("register");
@@ -1307,7 +1339,7 @@ mod tests {
bounded_queue_capacity: 8,
},
);
- let required_target = target(RadrootsTransportKind::Reticulum, "reticulum:local");
+ let required_target = target(TransportId::RETICULUM, "reticulum:local");
let required_fingerprint = required_target.fingerprint().clone();
let receipt = worker
.execute_job(RadrootsRuntimeDeliveryJob {
@@ -1323,7 +1355,7 @@ mod tests {
targets: vec![
RadrootsRuntimeDeliveryTarget::ready(
1,
- target(RadrootsTransportKind::Nostr, "wss://relay.example"),
+ target(TransportId::NOSTR, "wss://relay.example"),
),
RadrootsRuntimeDeliveryTarget::deferred_until_implemented(
2,
@@ -1354,7 +1386,7 @@ mod tests {
let mut registry = RadrootsRuntimeTransportRegistry::new();
registry
.register(StaticTransport::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
RadrootsTransportOutcomeKind::Accepted,
))
.expect("register");
@@ -1373,7 +1405,7 @@ mod tests {
satisfaction_policy: RadrootsTransportSatisfactionPolicy::any_delivered(),
targets: vec![RadrootsRuntimeDeliveryTarget::ready(
1,
- target(RadrootsTransportKind::Nostr, "wss://relay.example"),
+ target(TransportId::NOSTR, "wss://relay.example"),
)],
}],
now_ms: 1_000,
@@ -1397,7 +1429,7 @@ mod tests {
let mut registry = RadrootsRuntimeTransportRegistry::new();
registry
.register(StaticTransport::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
RadrootsTransportOutcomeKind::Accepted,
))
.expect("register");
@@ -1417,11 +1449,11 @@ mod tests {
targets: vec![
RadrootsRuntimeDeliveryTarget::ready(
1,
- target(RadrootsTransportKind::Nostr, "wss://relay-a.example"),
+ target(TransportId::NOSTR, "wss://relay-a.example"),
),
RadrootsRuntimeDeliveryTarget::ready(
2,
- target(RadrootsTransportKind::Nostr, "wss://relay-b.example"),
+ target(TransportId::NOSTR, "wss://relay-b.example"),
),
],
}],
@@ -1446,10 +1478,7 @@ mod tests {
#[tokio::test]
async fn delivery_worker_evaluates_cross_transport_quorum_globally() {
let mut registry = RadrootsRuntimeTransportRegistry::new();
- for kind in [
- RadrootsTransportKind::Nostr,
- RadrootsTransportKind::Reticulum,
- ] {
+ for kind in [TransportId::NOSTR, TransportId::RETICULUM] {
registry
.register(StaticTransport::new(
kind,
@@ -1473,11 +1502,11 @@ mod tests {
targets: vec![
RadrootsRuntimeDeliveryTarget::ready(
1,
- target(RadrootsTransportKind::Nostr, "wss://relay.example"),
+ target(TransportId::NOSTR, "wss://relay.example"),
),
RadrootsRuntimeDeliveryTarget::ready(
2,
- target(RadrootsTransportKind::Reticulum, "reticulum:local"),
+ target(TransportId::RETICULUM, "reticulum:local"),
),
],
}],
@@ -1506,7 +1535,7 @@ mod tests {
);
let reticulum_target = RadrootsRuntimeDeliveryTarget {
delivery_target_id: 1,
- target: target(RadrootsTransportKind::Reticulum, "reticulum:local"),
+ target: target(TransportId::RETICULUM, "reticulum:local"),
status: RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented,
};
let receipt = worker
@@ -1569,7 +1598,7 @@ mod tests {
let observation = RadrootsRuntimeInboundObservation::verified_signed_event(
&event,
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
"wss://relay.example",
1_000,
);
@@ -1580,7 +1609,7 @@ mod tests {
let unverified = RadrootsRuntimeInboundObservation {
event_id: event.id_str().to_owned(),
verified: false,
- transport_kind: RadrootsTransportKind::Nostr,
+ transport_kind: TransportId::NOSTR,
endpoint_uri: "wss://relay.example".to_owned(),
observed_at_ms: 1_000,
};
@@ -1592,7 +1621,7 @@ mod tests {
let mismatched = RadrootsRuntimeInboundObservation {
event_id: "a".repeat(64),
verified: true,
- transport_kind: RadrootsTransportKind::Nostr,
+ transport_kind: TransportId::NOSTR,
endpoint_uri: "wss://relay.example".to_owned(),
observed_at_ms: 1_000,
};
diff --git a/crates/transport/tests/source_boundary.rs b/crates/transport/tests/source_boundary.rs
@@ -29,6 +29,26 @@ const CORE_STATUS_CONTRACT_SOURCE_ROOTS: &[&str] = &["transport/src", "transport
const CORE_TRANSPORT_CONTRACT_SOURCE_ROOTS: &[&str] = &["transport/src"];
+const TRANSPORT_CONSUMER_SOURCE_ROOTS: &[&str] = &[
+ "event_store/src",
+ "mesh/src",
+ "mesh_agent_client/src",
+ "outbox/src",
+ "runtime/src",
+ "transport_nostr/src",
+ "transport_publish_protocol/src",
+ "transport_reticulum/src",
+];
+
+const RETIRED_TRANSPORT_TYPE_NAMES: &[&str] = &[
+ "RadrootsTransportKind",
+ "RadrootsTransportMeshScopeId",
+ "RadrootsTransportTarget",
+ "RadrootsTransportTargetFingerprint",
+ "RadrootsTransportTargetLabel",
+ "RadrootsTransportTargetSet",
+];
+
const DELIVERY_PAYLOAD_CONTRACT_SOURCE_ROOTS: &[&str] =
&["transport/src", "runtime/src", "transport_reticulum/src"];
@@ -489,7 +509,7 @@ fn foundation_hardening_repo_sources_reject_retired_names_and_ambiguous_docs() {
}
#[test]
-fn runtime_transport_registry_uses_core_transport_contract() {
+fn workspace_consumers_use_split_transport_spis_with_one_runtime_shim() {
let crates_root = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("transport crate parent");
@@ -499,17 +519,22 @@ fn runtime_transport_registry_uses_core_transport_contract() {
read_source(crates_root.join("transport_reticulum/src/lib.rs").as_path());
let reticulum_source = production_source(reticulum_source_raw.as_str());
+ for required in [
+ "Arc<dyn EventSource>",
+ "Arc<dyn EventSink>",
+ "pub fn register_source<T>",
+ "pub fn register_sink<T>",
+ "T: EventSource + 'static",
+ "T: EventSink + 'static",
+ ] {
+ assert!(
+ runtime_source.contains(required),
+ "runtime registry must expose final split SPI witness `{required}`"
+ );
+ }
assert!(
runtime_source.contains("Arc<dyn RadrootsTransport>"),
- "runtime registry must store the core RadrootsTransport trait object"
- );
- assert!(
- runtime_source.contains("T: RadrootsTransport + 'static"),
- "runtime registry registration must accept the core RadrootsTransport trait"
- );
- assert!(
- runtime_source.contains("transport.transport_kind()"),
- "runtime registry must key transports through the core trait transport_kind"
+ "mixed runtime delivery workers retain the sole temporary monolithic shim until RCLD 40"
);
let removed_reticulum_runtime_transport =
["RadrootsRuntimeReticulum", "Pre", "viewTransport"].concat();
@@ -523,9 +548,63 @@ fn runtime_transport_registry_uses_core_transport_contract() {
"runtime transport source must not retain split adapter contract `{forbidden}`"
);
}
+ for required in [
+ "impl EventSource for RadrootsReticulumTransport",
+ "impl EventSink for RadrootsReticulumTransport",
+ ] {
+ assert!(
+ reticulum_source.contains(required),
+ "Reticulum preview must implement final split SPI witness `{required}`"
+ );
+ }
+ assert!(
+ !reticulum_source.contains("impl RadrootsTransport for RadrootsReticulumTransport"),
+ "Reticulum preview must not implement the predecessor monolithic SPI"
+ );
+
+ let nostr_source = read_source(crates_root.join("transport_nostr/src/publish.rs").as_path());
+ assert!(
+ nostr_source.contains("impl<A> EventSink for RadrootsNostrTransport<A>"),
+ "Nostr adapter must implement the final sink-only SPI"
+ );
+ assert!(
+ !nostr_source.contains("impl<A> RadrootsTransport for RadrootsNostrTransport<A>"),
+ "Nostr adapter must not implement the predecessor monolithic SPI"
+ );
+}
+
+#[test]
+fn canonical_workspace_consumers_reject_retired_transport_type_names() {
+ let crates_root = Path::new(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .expect("transport crate parent");
+ let mut findings = Vec::new();
+
+ for relative_root in TRANSPORT_CONSUMER_SOURCE_ROOTS {
+ for path in rust_source_files(crates_root.join(relative_root).as_path()) {
+ if path
+ .components()
+ .any(|component| component.as_os_str() == "generated")
+ {
+ continue;
+ }
+ let source_raw = read_source(path.as_path());
+ let source = production_source(source_raw.as_str());
+ let relative_path = relative_path(crates_root, path.as_path());
+ for retired in RETIRED_TRANSPORT_TYPE_NAMES {
+ if contains_forbidden_concept(source, retired) {
+ findings.push(format!(
+ "{relative_path} still consumes retired transport type `{retired}`"
+ ));
+ }
+ }
+ }
+ }
+
assert!(
- reticulum_source.contains("impl RadrootsTransport for RadrootsReticulumTransport"),
- "Reticulum transport must implement the core transport contract"
+ findings.is_empty(),
+ "canonical transport consumer migration violations:\n{}",
+ findings.join("\n")
);
}
@@ -651,7 +730,7 @@ fn transport_target_identity_sources_reject_silent_dedupe() {
let relay_source = read_source(crates_root.join("transport_nostr/src/relay.rs").as_path());
for required in [
- "RadrootsTransportTarget::nostr_relay(original)",
+ "Target::new(TransportId::NOSTR, original)",
"RadrootsRelayTransportError::DuplicateRelayUrl",
] {
assert!(
diff --git a/crates/transport_nostr/src/fetch.rs b/crates/transport_nostr/src/fetch.rs
@@ -12,7 +12,7 @@ use radroots_event_store::{
RadrootsTransportObservation, RadrootsTransportObservationType,
};
use radroots_nostr::prelude::{RadrootsNostrClient, RadrootsNostrEvent, RadrootsNostrFilter};
-use radroots_transport::{RadrootsTransportKind, RadrootsTransportTarget};
+use radroots_transport::{Target, TransportId};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
use std::sync::{Arc, Mutex, PoisonError};
@@ -482,7 +482,7 @@ where
}
};
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
relay_url.clone(),
observation_type,
observed_at_ms,
@@ -1127,7 +1127,7 @@ fn canonical_requested_fetch_relay(
target_relays: &[String],
relay_url: &str,
) -> Result<String, RadrootsRelayTransportError> {
- let target = RadrootsTransportTarget::nostr_relay(relay_url).map_err(|error| {
+ let target = Target::new(TransportId::NOSTR, relay_url).map_err(|error| {
RadrootsRelayTransportError::InvalidFetchItemRelayUrl {
url: relay_url.to_owned(),
reason: error.to_string(),
diff --git a/crates/transport_nostr/src/outbox.rs b/crates/transport_nostr/src/outbox.rs
@@ -5,7 +5,6 @@ use crate::{
RadrootsRelayOutcome, RadrootsRelayPublishAdapter, RadrootsRelayPublishReceipt,
RadrootsRelayPublishRelayReceipt, RadrootsRelayPublishRequest, RadrootsRelayTargetSet,
RadrootsRelayTransportError, RadrootsRelayUrlPolicy, publish_signed_event,
- verified_signed_event_payload,
};
use radroots_event::draft::SignedEvent;
use radroots_event_store::{
@@ -16,13 +15,21 @@ use radroots_outbox::{
RadrootsOutbox, RadrootsOutboxClaimedEvent, RadrootsOutboxDeliveryTargetRecord,
RadrootsOutboxDeliveryTargetStatus, RadrootsOutboxEventStoreIngestReceipt,
};
+#[cfg(test)]
+use radroots_transport::RadrootsTransportDeliveryReceipt;
+use radroots_transport::outcome::{DeliveryOutcomeKind, Retryability};
+use radroots_transport::policy::{
+ SatisfactionClass, SatisfactionPolicy as SinkSatisfactionPolicy, TargetPolicy,
+};
+use radroots_transport::sink::{DeliveryPayload, DeliveryReceipt, DeliveryRequest, EventSink};
+use radroots_transport::target::{TargetFingerprint, TargetLabel, TargetScope};
use radroots_transport::{
- RadrootsTransport, RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryRequest,
- RadrootsTransportDeliveryTargetStatus, RadrootsTransportError, RadrootsTransportKind,
- RadrootsTransportOutcome, RadrootsTransportOutcomeKind, RadrootsTransportSatisfactionClass,
- RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget,
- RadrootsTransportTargetFingerprint, RadrootsTransportTargetSet,
+ RadrootsTransportDeliveryTargetStatus, RadrootsTransportError,
+ RadrootsTransportSatisfactionClass, RadrootsTransportSatisfactionPolicy, Target, TargetSet,
+ TransportId,
};
+#[cfg(test)]
+use radroots_transport::{RadrootsTransportOutcome, RadrootsTransportOutcomeKind};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RadrootsOutboxPublishPolicy {
@@ -69,7 +76,7 @@ pub struct RadrootsOutboxPublishReceipt {
pub struct RadrootsOutboxPublishTargetReceipt {
pub delivery_target_id: i64,
pub endpoint_uri: String,
- pub endpoint_fingerprint: RadrootsTransportTargetFingerprint,
+ pub endpoint_fingerprint: TargetFingerprint,
pub target_scope: Option<String>,
pub target_label: Option<String>,
pub attempted: bool,
@@ -224,15 +231,18 @@ pub async fn publish_claimed_outbox_event_with_transport<T>(
now_ms: i64,
) -> Result<RadrootsOutboxPublishReceipt, RadrootsRelayTransportError>
where
- T: RadrootsTransport + ?Sized,
+ T: EventSink + ?Sized,
{
ensure_nonnegative_timestamp("now_ms", now_ms)?;
ensure_nonnegative_timestamp("next_attempt_after_ms", policy.next_attempt_after_ms)?;
- let transport_kind = transport.transport_kind();
- if transport_kind != RadrootsTransportKind::Nostr {
+ let status = transport
+ .status()
+ .await
+ .map_err(transport_error_to_relay_error)?;
+ if status.transport_id() != TransportId::NOSTR {
return Err(RadrootsRelayTransportError::UnexpectedTransportKind {
expected: "nostr",
- actual: transport_kind.canonical_label(),
+ actual: status.transport_id().canonical_label(),
});
}
let signed_event = claimed.signed_event.clone().ok_or(
@@ -276,20 +286,27 @@ where
policy.relay_url_policy,
)?;
let transport_targets = publishable_transport_targets(&publishable)?;
- let target_set = RadrootsTransportTargetSet::new(transport_targets)?;
- let satisfaction_policy = transport_satisfaction_policy_for_publishable(&publishable);
+ let target_set = TargetSet::new(transport_targets)?;
let request_id = outbox_publish_idempotency_key(
claimed.outbox_event_id,
claimed.attempt_count,
signed_event.id_str(),
publishable.active_delivery_plan_id,
);
- let payload =
- verified_signed_event_payload(&signed_event).map_err(transport_error_to_relay_error)?;
- let delivery_request =
- RadrootsTransportDeliveryRequest::new(request_id, payload, target_set, satisfaction_policy)
- .and_then(|request| request.try_with_now_ms(now_ms))
- .map_err(transport_error_to_relay_error)?;
+ let deadline_unix_ms =
+ u64::try_from(policy.next_attempt_after_ms.max(now_ms).max(1)).map_err(|_| {
+ RadrootsRelayTransportError::TransportContract(
+ "outbox delivery deadline is outside the transport range".to_owned(),
+ )
+ })?;
+ let delivery_request = DeliveryRequest::new(
+ request_id,
+ DeliveryPayload::new(signed_event.clone()),
+ target_set,
+ SinkSatisfactionPolicy::new(SatisfactionClass::Accepted, TargetPolicy::all()),
+ deadline_unix_ms,
+ )
+ .map_err(transport_error_to_relay_error)?;
let delivery = transport
.deliver(delivery_request.clone())
.await
@@ -297,8 +314,8 @@ where
delivery
.validate_for_request(&delivery_request)
.map_err(transport_error_to_relay_error)?;
- let relay_receipts = relay_receipts_from_transport_receipts(&delivery)?;
- let target_receipts = target_receipts_from_transport_receipts(&publishable, &delivery);
+ let relay_receipts = relay_receipts_from_delivery_receipts(&delivery)?;
+ let target_receipts = target_receipts_from_delivery_receipts(&publishable, &delivery);
for target_receipt in &target_receipts {
complete_outbox_delivery_target(outbox, claimed, target_receipt, now_ms).await?;
@@ -398,8 +415,9 @@ struct PublishableRelays {
satisfaction_required_count: usize,
remaining_satisfaction_count: usize,
satisfaction_class: RadrootsTransportSatisfactionClass,
- required_targets: Option<Vec<RadrootsTransportTargetFingerprint>>,
- remaining_required_targets: Option<Vec<RadrootsTransportTargetFingerprint>>,
+ required_targets: Option<Vec<TargetFingerprint>>,
+ #[cfg_attr(not(test), allow(dead_code))]
+ remaining_required_targets: Option<Vec<TargetFingerprint>>,
}
impl PublishableRelays {
@@ -407,7 +425,7 @@ impl PublishableRelays {
&'a self,
relay_url: &'a str,
) -> impl Iterator<Item = &'a PublishableRelay> + 'a {
- let canonical_relay_url = RadrootsTransportTarget::nostr_relay(relay_url)
+ let canonical_relay_url = Target::new(TransportId::NOSTR, relay_url)
.ok()
.map(|target| target.uri().as_str().to_owned());
self.relays.iter().filter(move |target| {
@@ -440,7 +458,7 @@ impl PublishableRelays {
struct PublishableRelay {
delivery_target_id: i64,
relay_url: String,
- endpoint_fingerprint: RadrootsTransportTargetFingerprint,
+ endpoint_fingerprint: TargetFingerprint,
target_scope: Option<String>,
target_label: Option<String>,
}
@@ -478,9 +496,9 @@ fn target_receipts_from_relay_receipts(
target_receipts
}
-fn target_receipts_from_transport_receipts(
+fn target_receipts_from_delivery_receipts(
publishable: &PublishableRelays,
- delivery: &RadrootsTransportDeliveryReceipt,
+ delivery: &DeliveryReceipt,
) -> Vec<RadrootsOutboxPublishTargetReceipt> {
delivery
.target_receipts()
@@ -489,16 +507,16 @@ fn target_receipts_from_transport_receipts(
publishable
.relays
.iter()
- .find(|target| target.endpoint_fingerprint == *receipt.target.fingerprint())
+ .find(|target| target.endpoint_fingerprint == *receipt.target().fingerprint())
.map(|target| RadrootsOutboxPublishTargetReceipt {
delivery_target_id: target.delivery_target_id,
endpoint_uri: target.relay_url.clone(),
endpoint_fingerprint: target.endpoint_fingerprint.clone(),
target_scope: target.target_scope.clone(),
target_label: target.target_label.clone(),
- attempted: receipt.attempted,
- transport_status: receipt.status,
- outcome: relay_outcome_from_transport_outcome(&receipt.outcome),
+ attempted: receipt.was_attempted(),
+ transport_status: delivery_target_status(receipt.outcome()),
+ outcome: relay_outcome_from_delivery_outcome(receipt.outcome()),
})
})
.collect()
@@ -631,16 +649,16 @@ async fn complete_outbox_delivery_target(
Ok(())
}
-fn relay_receipts_from_transport_receipts(
- delivery: &RadrootsTransportDeliveryReceipt,
+fn relay_receipts_from_delivery_receipts(
+ delivery: &DeliveryReceipt,
) -> Result<Vec<RadrootsRelayPublishRelayReceipt>, RadrootsRelayTransportError> {
let mut relay_receipts: Vec<RadrootsRelayPublishRelayReceipt> = Vec::new();
for receipt in delivery.target_receipts() {
- let outcome = relay_outcome_from_transport_outcome(&receipt.outcome);
- let relay_receipt = if receipt.attempted {
- RadrootsRelayPublishRelayReceipt::attempted(receipt.target.uri().as_str(), outcome)
+ let outcome = relay_outcome_from_delivery_outcome(receipt.outcome());
+ let relay_receipt = if receipt.was_attempted() {
+ RadrootsRelayPublishRelayReceipt::attempted(receipt.target().uri().as_str(), outcome)
} else {
- RadrootsRelayPublishRelayReceipt::skipped(receipt.target.uri().as_str(), outcome)
+ RadrootsRelayPublishRelayReceipt::skipped(receipt.target().uri().as_str(), outcome)
};
if let Some(existing) = relay_receipts
.iter()
@@ -660,6 +678,46 @@ fn relay_receipts_from_transport_receipts(
Ok(relay_receipts)
}
+fn delivery_target_status(
+ outcome: &radroots_transport::outcome::DeliveryOutcome,
+) -> RadrootsTransportDeliveryTargetStatus {
+ match outcome.kind() {
+ DeliveryOutcomeKind::Accepted => RadrootsTransportDeliveryTargetStatus::Accepted,
+ DeliveryOutcomeKind::Delivered => RadrootsTransportDeliveryTargetStatus::Delivered,
+ DeliveryOutcomeKind::Rejected => RadrootsTransportDeliveryTargetStatus::FailedTerminal,
+ DeliveryOutcomeKind::Unavailable => RadrootsTransportDeliveryTargetStatus::FailedRetryable,
+ DeliveryOutcomeKind::Failed => match outcome.retryability() {
+ Retryability::Retryable => RadrootsTransportDeliveryTargetStatus::FailedRetryable,
+ Retryability::Terminal | Retryability::NotApplicable => {
+ RadrootsTransportDeliveryTargetStatus::FailedTerminal
+ }
+ },
+ }
+}
+
+fn relay_outcome_from_delivery_outcome(
+ outcome: &radroots_transport::outcome::DeliveryOutcome,
+) -> RadrootsRelayOutcome {
+ let kind = outcome
+ .code()
+ .and_then(relay_outcome_kind_from_code)
+ .unwrap_or(match outcome.kind() {
+ DeliveryOutcomeKind::Accepted => crate::RadrootsRelayOutcomeKind::Accepted,
+ DeliveryOutcomeKind::Delivered => crate::RadrootsRelayOutcomeKind::Accepted,
+ DeliveryOutcomeKind::Rejected => crate::RadrootsRelayOutcomeKind::Invalid,
+ DeliveryOutcomeKind::Unavailable => crate::RadrootsRelayOutcomeKind::Error,
+ DeliveryOutcomeKind::Failed if outcome.is_retryable() => {
+ crate::RadrootsRelayOutcomeKind::Error
+ }
+ DeliveryOutcomeKind::Failed => crate::RadrootsRelayOutcomeKind::Invalid,
+ });
+ RadrootsRelayOutcome {
+ kind,
+ message: outcome.message().map(str::to_owned),
+ }
+}
+
+#[cfg(test)]
fn relay_outcome_from_transport_outcome(
outcome: &RadrootsTransportOutcome,
) -> RadrootsRelayOutcome {
@@ -674,6 +732,63 @@ fn relay_outcome_from_transport_outcome(
}
}
+#[cfg(test)]
+fn target_receipts_from_transport_receipts(
+ publishable: &PublishableRelays,
+ delivery: &RadrootsTransportDeliveryReceipt,
+) -> Vec<RadrootsOutboxPublishTargetReceipt> {
+ delivery
+ .target_receipts()
+ .iter()
+ .filter_map(|receipt| {
+ publishable
+ .relays
+ .iter()
+ .find(|target| target.endpoint_fingerprint == *receipt.target.fingerprint())
+ .map(|target| RadrootsOutboxPublishTargetReceipt {
+ delivery_target_id: target.delivery_target_id,
+ endpoint_uri: target.relay_url.clone(),
+ endpoint_fingerprint: target.endpoint_fingerprint.clone(),
+ target_scope: target.target_scope.clone(),
+ target_label: target.target_label.clone(),
+ attempted: receipt.attempted,
+ transport_status: receipt.status,
+ outcome: relay_outcome_from_transport_outcome(&receipt.outcome),
+ })
+ })
+ .collect()
+}
+
+#[cfg(test)]
+fn relay_receipts_from_transport_receipts(
+ delivery: &RadrootsTransportDeliveryReceipt,
+) -> Result<Vec<RadrootsRelayPublishRelayReceipt>, RadrootsRelayTransportError> {
+ let mut relay_receipts: Vec<RadrootsRelayPublishRelayReceipt> = Vec::new();
+ for receipt in delivery.target_receipts() {
+ let outcome = relay_outcome_from_transport_outcome(&receipt.outcome);
+ let relay_receipt = if receipt.attempted {
+ RadrootsRelayPublishRelayReceipt::attempted(receipt.target.uri().as_str(), outcome)
+ } else {
+ RadrootsRelayPublishRelayReceipt::skipped(receipt.target.uri().as_str(), outcome)
+ };
+ if let Some(existing) = relay_receipts
+ .iter()
+ .find(|existing| existing.relay_url == relay_receipt.relay_url)
+ {
+ if existing != &relay_receipt {
+ return Err(
+ RadrootsRelayTransportError::ConflictingTransportReceiptRelayUrl {
+ url: relay_receipt.relay_url,
+ },
+ );
+ }
+ continue;
+ }
+ relay_receipts.push(relay_receipt);
+ }
+ Ok(relay_receipts)
+}
+
fn relay_outcome_kind_from_code(code: &str) -> Option<crate::RadrootsRelayOutcomeKind> {
Some(match code {
"accepted" => crate::RadrootsRelayOutcomeKind::Accepted,
@@ -697,6 +812,7 @@ fn relay_outcome_kind_from_code(code: &str) -> Option<crate::RadrootsRelayOutcom
})
}
+#[cfg(test)]
fn relay_outcome_kind_from_transport_outcome(
kind: RadrootsTransportOutcomeKind,
) -> crate::RadrootsRelayOutcomeKind {
@@ -730,23 +846,24 @@ fn relay_outcome_kind_from_transport_outcome(
fn publishable_transport_targets(
publishable: &PublishableRelays,
-) -> Result<Vec<RadrootsTransportTarget>, RadrootsRelayTransportError> {
+) -> Result<Vec<Target>, RadrootsRelayTransportError> {
publishable
.relays
.iter()
.map(|relay| {
- RadrootsTransportTarget::nostr_relay_with_metadata(
+ Target::new_with_metadata(
+ TransportId::NOSTR,
relay.relay_url.as_str(),
relay
.target_scope
.as_deref()
- .map(radroots_transport::RadrootsTransportMeshScopeId::parse)
+ .map(TargetScope::parse)
.transpose()
.map_err(transport_error_to_relay_error)?,
relay
.target_label
.as_deref()
- .map(radroots_transport::RadrootsTransportTargetLabel::parse)
+ .map(TargetLabel::parse)
.transpose()
.map_err(transport_error_to_relay_error)?,
)
@@ -755,6 +872,7 @@ fn publishable_transport_targets(
.collect()
}
+#[cfg(test)]
fn transport_satisfaction_policy_for_publishable(
publishable: &PublishableRelays,
) -> RadrootsTransportSatisfactionPolicy {
@@ -1005,14 +1123,15 @@ fn is_publishable_delivery_status(
}
fn is_nostr_target(target: &RadrootsOutboxDeliveryTargetRecord) -> bool {
- target.transport_kind == RadrootsTransportKind::Nostr
+ target.transport_kind == TransportId::NOSTR
}
+#[cfg(test)]
fn satisfaction_policy_for_remaining_count(
satisfaction_class: RadrootsTransportSatisfactionClass,
remaining_satisfaction_count: usize,
target_count: usize,
- exact_required_targets: Option<&[RadrootsTransportTargetFingerprint]>,
+ exact_required_targets: Option<&[TargetFingerprint]>,
) -> RadrootsTransportSatisfactionPolicy {
if let Some(targets) = exact_required_targets {
return RadrootsTransportSatisfactionPolicy::RequiredTargets {
@@ -1051,7 +1170,7 @@ async fn ingest_publish_observation(
observed_at_ms: i64,
) -> Result<(), RadrootsRelayTransportError> {
let observation = RadrootsTransportObservation::new(
- RadrootsTransportKind::Nostr,
+ TransportId::NOSTR,
relay_url,
RadrootsTransportObservationType::PublishAck,
observed_at_ms,
@@ -1078,12 +1197,12 @@ mod tests {
RadrootsRelayOutcome, RadrootsRelayOutcomeKind, RadrootsRelayPublishRelayReceipt,
RadrootsRelayTransportError,
};
+ use radroots_transport::target::TargetScope;
use radroots_transport::{
RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryTargetStatus,
- RadrootsTransportError, RadrootsTransportMeshScopeId, RadrootsTransportOutcome,
- RadrootsTransportOutcomeKind, RadrootsTransportSatisfactionClass,
- RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget,
- RadrootsTransportTargetReceipt, RadrootsTransportTargetSet,
+ RadrootsTransportError, RadrootsTransportOutcome, RadrootsTransportOutcomeKind,
+ RadrootsTransportSatisfactionClass, RadrootsTransportSatisfactionPolicy,
+ RadrootsTransportTargetReceipt, Target, TargetSet, TransportId,
};
#[test]
@@ -1116,7 +1235,7 @@ mod tests {
RadrootsTransportSatisfactionPolicy::quorum_delivered(2)
);
let required_target =
- RadrootsTransportTarget::nostr_relay("wss://relay.example").expect("required target");
+ Target::new(TransportId::NOSTR, "wss://relay.example").expect("required target");
assert_eq!(
satisfaction_policy_for_remaining_count(
RadrootsTransportSatisfactionClass::Delivered,
@@ -1196,7 +1315,7 @@ mod tests {
#[test]
fn outbox_publish_satisfaction_counts_use_active_transport_class() {
- let target = RadrootsTransportTarget::nostr_relay("wss://relay.example").expect("target");
+ let target = Target::new(TransportId::NOSTR, "wss://relay.example").expect("target");
let publishable = PublishableRelays {
active_delivery_plan_id: 7,
relays: vec![PublishableRelay {
@@ -1233,7 +1352,7 @@ mod tests {
let delivery = RadrootsTransportDeliveryReceipt::new(
"request-1",
- RadrootsTransportTargetSet::new(vec![target.clone()]).expect("target set"),
+ TargetSet::new(vec![target.clone()]).expect("target set"),
vec![RadrootsTransportTargetReceipt::new(
target,
RadrootsTransportOutcome::new(RadrootsTransportOutcomeKind::Delivered),
@@ -1414,7 +1533,7 @@ mod tests {
#[test]
fn transport_target_and_error_adapters_preserve_contract_categories() {
- let target = RadrootsTransportTarget::nostr_relay("wss://relay.example").expect("target");
+ let target = Target::new(TransportId::NOSTR, "wss://relay.example").expect("target");
let publishable = PublishableRelays {
active_delivery_plan_id: 7,
relays: vec![PublishableRelay {
@@ -1509,10 +1628,10 @@ mod tests {
}
let unknown =
- RadrootsTransportTarget::nostr_relay("wss://unknown.example").expect("unknown target");
+ Target::new(TransportId::NOSTR, "wss://unknown.example").expect("unknown target");
let delivery = RadrootsTransportDeliveryReceipt::new(
"unknown",
- RadrootsTransportTargetSet::new(vec![unknown.clone()]).expect("target set"),
+ TargetSet::new(vec![unknown.clone()]).expect("target set"),
vec![RadrootsTransportTargetReceipt::new(
unknown,
RadrootsTransportOutcome::new(RadrootsTransportOutcomeKind::Accepted),
@@ -1527,23 +1646,24 @@ mod tests {
1
);
- let west = RadrootsTransportTarget::nostr_relay_with_metadata(
+ let west = Target::new_with_metadata(
+ TransportId::NOSTR,
"wss://scoped.example",
- Some(RadrootsTransportMeshScopeId::parse("foodshed.west").expect("west scope")),
+ Some(TargetScope::parse("foodshed.west").expect("west scope")),
None,
)
.expect("west target");
- let east = RadrootsTransportTarget::nostr_relay_with_metadata(
+ let east = Target::new_with_metadata(
+ TransportId::NOSTR,
"wss://scoped.example",
- Some(RadrootsTransportMeshScopeId::parse("foodshed.east").expect("east scope")),
+ Some(TargetScope::parse("foodshed.east").expect("east scope")),
None,
)
.expect("east target");
let scoped_relay_uri = west.uri().as_str().to_owned();
let conflicting = RadrootsTransportDeliveryReceipt::new(
"conflicting",
- RadrootsTransportTargetSet::new(vec![west.clone(), east.clone()])
- .expect("scoped target set"),
+ TargetSet::new(vec![west.clone(), east.clone()]).expect("scoped target set"),
vec![
RadrootsTransportTargetReceipt::new(
west,
diff --git a/crates/transport_nostr/src/outcome.rs b/crates/transport_nostr/src/outcome.rs
@@ -1,5 +1,6 @@
#![forbid(unsafe_code)]
+use radroots_transport::outcome::DeliveryOutcome;
use radroots_transport::{RadrootsTransportOutcome, RadrootsTransportOutcomeKind};
use serde::{Deserialize, Serialize};
@@ -214,4 +215,34 @@ impl RadrootsRelayOutcome {
}
outcome
}
+
+ pub(crate) fn to_delivery_outcome(&self) -> DeliveryOutcome {
+ let outcome = match self.kind {
+ RadrootsRelayOutcomeKind::Accepted
+ | RadrootsRelayOutcomeKind::DuplicateAccepted
+ | RadrootsRelayOutcomeKind::SkippedAlreadyAccepted => DeliveryOutcome::accepted(),
+ RadrootsRelayOutcomeKind::Blocked
+ | RadrootsRelayOutcomeKind::Invalid
+ | RadrootsRelayOutcomeKind::Restricted
+ | RadrootsRelayOutcomeKind::Muted
+ | RadrootsRelayOutcomeKind::Unsupported
+ | RadrootsRelayOutcomeKind::PaymentRequired
+ | RadrootsRelayOutcomeKind::RelayUrlRejected => DeliveryOutcome::rejected(),
+ RadrootsRelayOutcomeKind::RateLimited
+ | RadrootsRelayOutcomeKind::PowRequired
+ | RadrootsRelayOutcomeKind::AuthRequired
+ | RadrootsRelayOutcomeKind::Error
+ | RadrootsRelayOutcomeKind::Timeout
+ | RadrootsRelayOutcomeKind::ConnectionFailed
+ | RadrootsRelayOutcomeKind::Unknown => DeliveryOutcome::unavailable(),
+ };
+ if let Some(message) = &self.message {
+ outcome
+ .clone()
+ .with_detail(self.kind.as_str(), message.clone())
+ .unwrap_or(outcome)
+ } else {
+ outcome
+ }
+ }
}
diff --git a/crates/transport_nostr/src/publish.rs b/crates/transport_nostr/src/publish.rs
@@ -5,14 +5,15 @@ use crate::{RadrootsRelayOutcome, RadrootsRelayTargetSet, RadrootsRelayTransport
#[cfg(feature = "client")]
use core::time::Duration;
use futures::future::BoxFuture;
-use radroots_event::{draft::SignedEvent, wire::Nip01EventWire};
+use radroots_event::draft::SignedEvent;
+use radroots_transport::capability::{Availability, Maturity, SinkCapabilities};
+use radroots_transport::outcome::DeliveryOutcome;
+use radroots_transport::sink::{
+ DeliveryReceipt, DeliveryRequest, DeliveryTargetReceipt, EventSink, SinkStatus,
+};
use radroots_transport::{
- RadrootsTransport, RadrootsTransportCapabilities, RadrootsTransportDeliveryReceipt,
- RadrootsTransportDeliveryRequest, RadrootsTransportError, RadrootsTransportFetchReceipt,
- RadrootsTransportFetchRequest, RadrootsTransportFuture, RadrootsTransportImplementationState,
- RadrootsTransportKind, RadrootsTransportOutcome, RadrootsTransportOutcomeKind,
- RadrootsTransportPayload, RadrootsTransportSatisfactionPolicy, RadrootsTransportStatus,
- RadrootsTransportTarget, RadrootsTransportTargetReceipt,
+ RadrootsTransportError, RadrootsTransportPayload, RadrootsTransportSatisfactionPolicy, Target,
+ TransportId,
};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
@@ -104,7 +105,7 @@ impl RadrootsRelayPublishRequest {
.targets
.relays()
.iter()
- .map(|relay| RadrootsTransportTarget::nostr_relay(relay.as_str()))
+ .map(|relay| Target::new(TransportId::NOSTR, relay.as_str()))
.collect::<Result<Vec<_>, _>>()?;
for required in required_targets {
if !requested
@@ -212,25 +213,25 @@ where
#[derive(Clone)]
pub struct RadrootsNostrTransport<A> {
adapter: A,
- status: RadrootsTransportStatus,
+ status: SinkStatus,
}
impl<A> RadrootsNostrTransport<A> {
pub fn new(adapter: A) -> Self {
Self {
adapter,
- status: RadrootsTransportStatus::new(
- RadrootsTransportKind::Nostr,
- true,
- RadrootsTransportImplementationState::Real,
+ status: SinkStatus::new(
+ TransportId::NOSTR,
true,
+ Maturity::Stable,
+ Availability::Available,
+ SinkCapabilities::DELIVER,
"ready",
- )
- .with_capabilities(RadrootsTransportCapabilities::deliver_only()),
+ ),
}
}
- pub fn with_status(mut self, status: RadrootsTransportStatus) -> Self {
+ pub fn with_status(mut self, status: SinkStatus) -> Self {
self.status = status;
self
}
@@ -240,60 +241,53 @@ impl<A> RadrootsNostrTransport<A> {
}
}
-impl<A> RadrootsTransport for RadrootsNostrTransport<A>
+impl<A> EventSink for RadrootsNostrTransport<A>
where
A: RadrootsRelayPublishAdapter,
{
- fn transport_kind(&self) -> RadrootsTransportKind {
- RadrootsTransportKind::Nostr
- }
-
- fn status<'a>(&'a self) -> RadrootsTransportFuture<'a, RadrootsTransportStatus> {
+ fn status(
+ &self,
+ ) -> radroots_transport::BoxFuture<'_, Result<SinkStatus, RadrootsTransportError>> {
Box::pin(async move { Ok(self.status.clone()) })
}
- fn deliver<'a>(
- &'a self,
- request: RadrootsTransportDeliveryRequest,
- ) -> RadrootsTransportFuture<'a, RadrootsTransportDeliveryReceipt> {
+ fn deliver(
+ &self,
+ request: DeliveryRequest,
+ ) -> radroots_transport::BoxFuture<'_, Result<DeliveryReceipt, RadrootsTransportError>> {
Box::pin(async move {
- let signed_event = signed_event_from_transport_payload(request.payload())?;
+ let signed_event = request.payload().event().clone();
let targets = relay_targets_from_transport_targets(request.target_set().targets())?;
+ let legacy_attempt_time_ms = i64::try_from(request.deadline_unix_ms())
+ .map_err(|_| RadrootsTransportError::InvalidDeliveryDeadline)?;
let publish_request =
- RadrootsRelayPublishRequest::new(signed_event, targets, request.now_ms())
+ RadrootsRelayPublishRequest::new(signed_event, targets, legacy_attempt_time_ms)
.map_err(nostr_error_to_transport_error)?
.with_satisfaction_policy(RadrootsTransportSatisfactionPolicy::NoWait)
- .try_with_idempotency_key(request.request_id())
+ .try_with_idempotency_key(request.request_id().as_str())
.map_err(nostr_error_to_transport_error)?;
let relay_receipts = match publish_signed_event(&self.adapter, publish_request).await {
Ok(receipt) => receipt.relays,
Err(RadrootsRelayTransportError::Transport(message)) => {
- return RadrootsTransportDeliveryReceipt::for_request(
+ return DeliveryReceipt::for_request(
&request,
transport_failure_target_receipts(
request.target_set().targets(),
message.as_str(),
- ),
+ )?,
);
}
Err(error) => return Err(nostr_error_to_transport_error(error)),
};
- RadrootsTransportDeliveryReceipt::for_request(
+ DeliveryReceipt::for_request(
&request,
target_receipts_from_relay_receipts(
request.target_set().targets(),
relay_receipts.as_slice(),
- ),
+ )?,
)
})
}
-
- fn fetch<'a>(
- &'a self,
- _request: RadrootsTransportFetchRequest,
- ) -> RadrootsTransportFuture<'a, RadrootsTransportFetchReceipt> {
- Box::pin(async move { Err(RadrootsTransportError::UnsupportedOperation) })
- }
}
fn nostr_error_to_transport_error(error: RadrootsRelayTransportError) -> RadrootsTransportError {
@@ -563,30 +557,12 @@ mod contract_tests {
}
}
-fn signed_event_from_transport_payload(
- payload: &RadrootsTransportPayload,
-) -> Result<SignedEvent, RadrootsTransportError> {
- let RadrootsTransportPayload::SignedEventJson {
- event_id, raw_json, ..
- } = payload
- else {
- return Err(RadrootsTransportError::InvalidPayloadBytes);
- };
- let wire = Nip01EventWire::parse_json(raw_json)
- .map_err(|_| RadrootsTransportError::InvalidPayloadBytes)?;
- if wire.id != *event_id {
- return Err(RadrootsTransportError::InvalidPayloadId);
- }
- SignedEvent::from_wire_verified_id(wire, raw_json.clone())
- .map_err(|_| RadrootsTransportError::InvalidPayloadBytes)
-}
-
fn relay_targets_from_transport_targets(
- targets: &[RadrootsTransportTarget],
+ targets: &[Target],
) -> Result<RadrootsRelayTargetSet, RadrootsTransportError> {
let mut relays = Vec::new();
for target in targets {
- if target.kind() != &RadrootsTransportKind::Nostr {
+ if target.kind() != &TransportId::NOSTR {
return Err(RadrootsTransportError::InvalidTargetUri);
}
let policy = if target.uri().as_str().starts_with("ws://") {
@@ -604,9 +580,9 @@ fn relay_targets_from_transport_targets(
}
fn target_receipts_from_relay_receipts(
- targets: &[RadrootsTransportTarget],
+ targets: &[Target],
relay_receipts: &[RadrootsRelayPublishRelayReceipt],
-) -> Vec<RadrootsTransportTargetReceipt> {
+) -> Result<Vec<DeliveryTargetReceipt>, RadrootsTransportError> {
targets
.iter()
.cloned()
@@ -615,18 +591,17 @@ fn target_receipts_from_relay_receipts(
.iter()
.find(|receipt| relay_receipt_matches_target(receipt, &target));
match relay_receipt {
- Some(receipt) if receipt.attempted => RadrootsTransportTargetReceipt::attempted(
- target,
- receipt.outcome.to_transport_outcome(),
- ),
- Some(receipt) => RadrootsTransportTargetReceipt::skipped(
+ Some(receipt) if receipt.attempted => Ok(DeliveryTargetReceipt::attempted(
target,
- receipt.outcome.to_transport_outcome(),
- ),
- None => RadrootsTransportTargetReceipt::skipped(
+ receipt.outcome.to_delivery_outcome(),
+ )),
+ Some(receipt) => {
+ DeliveryTargetReceipt::skipped(target, receipt.outcome.to_delivery_outcome())
+ }
+ None => DeliveryTargetReceipt::skipped(
target,
- RadrootsTransportOutcome::new(RadrootsTransportOutcomeKind::RouteUnavailable)
- .with_message("relay adapter omitted target receipt"),
+ DeliveryOutcome::unavailable()
+ .with_detail("missing_receipt", "relay adapter omitted target receipt")?,
),
}
})
@@ -634,18 +609,19 @@ fn target_receipts_from_relay_receipts(
}
fn transport_failure_target_receipts(
- targets: &[RadrootsTransportTarget],
+ targets: &[Target],
message: &str,
-) -> Vec<RadrootsTransportTargetReceipt> {
+) -> Result<Vec<DeliveryTargetReceipt>, RadrootsTransportError> {
targets
.iter()
.cloned()
.map(|target| {
- RadrootsTransportTargetReceipt::attempted(
+ Ok(DeliveryTargetReceipt::attempted(
target,
- RadrootsTransportOutcome::new(RadrootsTransportOutcomeKind::ConnectionFailed)
- .with_message(message.to_owned()),
- )
+ DeliveryOutcome::unavailable()
+ .with_detail("connection_failed", message.to_owned())
+ .unwrap_or_else(|_| DeliveryOutcome::unavailable()),
+ ))
})
.collect()
}
@@ -699,7 +675,7 @@ fn normalize_publish_receipts(
let mut by_relay = BTreeMap::new();
for mut receipt in receipts {
let target =
- RadrootsTransportTarget::nostr_relay(receipt.relay_url.as_str()).map_err(|error| {
+ Target::new(TransportId::NOSTR, receipt.relay_url.as_str()).map_err(|error| {
RadrootsRelayTransportError::InvalidPublishReceiptRelayUrl {
url: receipt.relay_url.clone(),
reason: error.to_string(),
@@ -776,7 +752,7 @@ fn relay_publish_satisfies_policy(
policy.required_target_count(target_count)?;
let mut satisfied_required_targets = BTreeSet::new();
for receipt in relays {
- let target = RadrootsTransportTarget::nostr_relay(&receipt.relay_url)?;
+ let target = Target::new(TransportId::NOSTR, &receipt.relay_url)?;
if targets.contains(target.fingerprint())
&& relay_receipt_counts_toward_quorum(receipt)
&& receipt
@@ -797,9 +773,9 @@ fn relay_publish_satisfies_policy(
fn relay_receipt_matches_target(
receipt: &RadrootsRelayPublishRelayReceipt,
- target: &RadrootsTransportTarget,
+ target: &Target,
) -> bool {
- RadrootsTransportTarget::nostr_relay(receipt.relay_url.as_str())
+ Target::new(TransportId::NOSTR, receipt.relay_url.as_str())
.is_ok_and(|receipt_target| receipt_target.uri() == target.uri())
}
diff --git a/crates/transport_nostr/src/relay.rs b/crates/transport_nostr/src/relay.rs
@@ -1,7 +1,7 @@
#![forbid(unsafe_code)]
use crate::RadrootsRelayTransportError;
-use radroots_transport::RadrootsTransportTarget;
+use radroots_transport::{Target, TransportId};
use std::fmt;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use url::Url;
@@ -71,7 +71,7 @@ impl RelayUrl {
});
}
}
- let target = RadrootsTransportTarget::nostr_relay(original).map_err(|error| {
+ let target = Target::new(TransportId::NOSTR, original).map_err(|error| {
RadrootsRelayTransportError::RelayUrlParse {
url: original.to_owned(),
reason: error.to_string(),
diff --git a/crates/transport_nostr/tests/transport.rs b/crates/transport_nostr/tests/transport.rs
@@ -18,14 +18,20 @@ use radroots_outbox::{
RadrootsOutboxDeliveryTargetStatus, RadrootsOutboxEventState, RadrootsOutboxOperationInput,
RadrootsOutboxOperationStatus,
};
+use radroots_transport::capability::{Availability, Maturity, SinkCapabilities};
+use radroots_transport::outcome::{DeliveryOutcome, DeliveryOutcomeKind};
+use radroots_transport::policy::{
+ SatisfactionClass as SinkSatisfactionClass, SatisfactionPolicy as SinkSatisfactionPolicy,
+ TargetPolicy as SinkTargetPolicy,
+};
+use radroots_transport::sink::{
+ DeliveryPayload, DeliveryReceipt, DeliveryRequest, DeliveryTargetReceipt, EventSink, SinkStatus,
+};
+use radroots_transport::target::{TargetLabel, TargetScope};
use radroots_transport::{
- RadrootsTransport, RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryRequest,
- RadrootsTransportDeliveryTargetStatus, RadrootsTransportError, RadrootsTransportFetchReceipt,
- RadrootsTransportFetchRequest, RadrootsTransportFuture, RadrootsTransportImplementationState,
- RadrootsTransportKind, RadrootsTransportMeshScopeId, RadrootsTransportOutcome,
+ RadrootsTransportDeliveryTargetStatus, RadrootsTransportError, RadrootsTransportOutcome,
RadrootsTransportOutcomeKind, RadrootsTransportPayload, RadrootsTransportSatisfactionClass,
- RadrootsTransportSatisfactionPolicy, RadrootsTransportStatus, RadrootsTransportTarget,
- RadrootsTransportTargetLabel, RadrootsTransportTargetReceipt, RadrootsTransportTargetSet,
+ RadrootsTransportSatisfactionPolicy, Target, TargetSet, TransportId,
};
use radroots_transport_nostr::{
RADROOTS_RELAY_FETCH_EVENT_LIMIT_MAX, RADROOTS_RELAY_FETCH_RAW_EVENT_LIMIT_MAX,
@@ -243,63 +249,81 @@ impl RadrootsRelayPublishAdapter for AttemptedSkippedRelayReceiptPublishAdapter
#[derive(Clone)]
struct ScriptedTransport {
- kind: RadrootsTransportKind,
+ kind: TransportId,
outcomes: Vec<RadrootsTransportOutcome>,
}
impl ScriptedTransport {
fn new(outcomes: Vec<RadrootsTransportOutcome>) -> Self {
Self {
- kind: RadrootsTransportKind::Nostr,
+ kind: TransportId::NOSTR,
outcomes,
}
}
- fn with_kind(mut self, kind: RadrootsTransportKind) -> Self {
+ fn with_kind(mut self, kind: TransportId) -> Self {
self.kind = kind;
self
}
}
-impl RadrootsTransport for ScriptedTransport {
- fn transport_kind(&self) -> RadrootsTransportKind {
- self.kind.clone()
- }
-
- fn status<'a>(&'a self) -> RadrootsTransportFuture<'a, RadrootsTransportStatus> {
- Box::pin(async {
- Ok(RadrootsTransportStatus::new(
- RadrootsTransportKind::Nostr,
- true,
- RadrootsTransportImplementationState::Real,
+impl EventSink for ScriptedTransport {
+ fn status(
+ &self,
+ ) -> radroots_transport::BoxFuture<'_, Result<SinkStatus, RadrootsTransportError>> {
+ Box::pin(async move {
+ Ok(SinkStatus::new(
+ self.kind,
true,
+ Maturity::Stable,
+ Availability::Available,
+ SinkCapabilities::DELIVER,
"scripted",
))
})
}
- fn deliver<'a>(
- &'a self,
- request: RadrootsTransportDeliveryRequest,
- ) -> RadrootsTransportFuture<'a, RadrootsTransportDeliveryReceipt> {
+ fn deliver(
+ &self,
+ request: DeliveryRequest,
+ ) -> radroots_transport::BoxFuture<'_, Result<DeliveryReceipt, RadrootsTransportError>> {
Box::pin(async move {
- let target_receipts = request
- .target_set()
- .targets()
- .iter()
- .cloned()
- .zip(self.outcomes.iter().cloned())
- .map(|(target, outcome)| RadrootsTransportTargetReceipt::new(target, outcome))
- .collect();
- RadrootsTransportDeliveryReceipt::for_request(&request, target_receipts)
+ DeliveryReceipt::for_request(
+ &request,
+ request
+ .target_set()
+ .targets()
+ .iter()
+ .cloned()
+ .zip(self.outcomes.iter())
+ .map(|(target, outcome)| {
+ DeliveryTargetReceipt::attempted(
+ target,
+ legacy_outcome_to_delivery_outcome(outcome),
+ )
+ })
+ .collect(),
+ )
})
}
+}
- fn fetch<'a>(
- &'a self,
- _request: RadrootsTransportFetchRequest,
- ) -> RadrootsTransportFuture<'a, RadrootsTransportFetchReceipt> {
- Box::pin(async { Err(RadrootsTransportError::UnsupportedOperation) })
+fn legacy_outcome_to_delivery_outcome(outcome: &RadrootsTransportOutcome) -> DeliveryOutcome {
+ match outcome.kind {
+ RadrootsTransportOutcomeKind::Delivered => DeliveryOutcome::delivered(),
+ RadrootsTransportOutcomeKind::Accepted
+ | RadrootsTransportOutcomeKind::DuplicateAccepted
+ | RadrootsTransportOutcomeKind::Forwarded
+ | RadrootsTransportOutcomeKind::StoredByGateway
+ | RadrootsTransportOutcomeKind::Seen => DeliveryOutcome::accepted(),
+ RadrootsTransportOutcomeKind::Timeout
+ | RadrootsTransportOutcomeKind::ConnectionFailed
+ | RadrootsTransportOutcomeKind::TransportUnavailable => DeliveryOutcome::unavailable(),
+ RadrootsTransportOutcomeKind::DeferredUntilImplemented
+ | RadrootsTransportOutcomeKind::Rejected
+ | RadrootsTransportOutcomeKind::RouteUnavailable
+ | RadrootsTransportOutcomeKind::PayloadTooLarge
+ | RadrootsTransportOutcomeKind::PolicyDenied => DeliveryOutcome::rejected(),
}
}
@@ -313,68 +337,35 @@ struct ForgedReceiptTransport {
forged: ForgedDeliveryReceipt,
}
-impl RadrootsTransport for ForgedReceiptTransport {
- fn transport_kind(&self) -> RadrootsTransportKind {
- RadrootsTransportKind::Nostr
- }
-
- fn status<'a>(&'a self) -> RadrootsTransportFuture<'a, RadrootsTransportStatus> {
+impl EventSink for ForgedReceiptTransport {
+ fn status(
+ &self,
+ ) -> radroots_transport::BoxFuture<'_, Result<SinkStatus, RadrootsTransportError>> {
Box::pin(async {
- Ok(RadrootsTransportStatus::new(
- RadrootsTransportKind::Nostr,
- true,
- RadrootsTransportImplementationState::Real,
+ Ok(SinkStatus::new(
+ TransportId::NOSTR,
true,
+ Maturity::Stable,
+ Availability::Available,
+ SinkCapabilities::DELIVER,
"forged receipt fixture",
))
})
}
- fn deliver<'a>(
- &'a self,
- request: RadrootsTransportDeliveryRequest,
- ) -> RadrootsTransportFuture<'a, RadrootsTransportDeliveryReceipt> {
- Box::pin(async move {
- match self.forged {
- ForgedDeliveryReceipt::RequestId => RadrootsTransportDeliveryReceipt::new(
- "forged-request",
- request.target_set().clone(),
- request
- .target_set()
- .targets()
- .iter()
- .cloned()
- .map(|target| {
- RadrootsTransportTargetReceipt::new(
- target,
- RadrootsTransportOutcome::new(
- RadrootsTransportOutcomeKind::Accepted,
- ),
- )
- })
- .collect(),
- ),
- ForgedDeliveryReceipt::TargetSet => {
- let target = nostr_target(RELAY_TERTIARY_WSS);
- RadrootsTransportDeliveryReceipt::new(
- request.request_id(),
- RadrootsTransportTargetSet::new(vec![target.clone()])
- .expect("forged target set"),
- vec![RadrootsTransportTargetReceipt::new(
- target,
- RadrootsTransportOutcome::new(RadrootsTransportOutcomeKind::Accepted),
- )],
- )
- }
+ fn deliver(
+ &self,
+ _request: DeliveryRequest,
+ ) -> radroots_transport::BoxFuture<'_, Result<DeliveryReceipt, RadrootsTransportError>> {
+ let error = match self.forged {
+ ForgedDeliveryReceipt::RequestId => {
+ RadrootsTransportError::DeliveryReceiptRequestIdMismatch
}
- })
- }
-
- fn fetch<'a>(
- &'a self,
- _request: RadrootsTransportFetchRequest,
- ) -> RadrootsTransportFuture<'a, RadrootsTransportFetchReceipt> {
- Box::pin(async { Err(RadrootsTransportError::UnsupportedOperation) })
+ ForgedDeliveryReceipt::TargetSet => {
+ RadrootsTransportError::DeliveryReceiptTargetSetMismatch
+ }
+ };
+ Box::pin(async move { Err(error) })
}
}
@@ -494,19 +485,36 @@ async fn complete_claimed_signing(
.expect("complete signing")
}
-fn nostr_target(relay_url: &str) -> RadrootsTransportTarget {
- RadrootsTransportTarget::nostr_relay(relay_url).expect("nostr target")
+fn nostr_target(relay_url: &str) -> Target {
+ Target::new(TransportId::NOSTR, relay_url).expect("nostr target")
}
-fn scoped_nostr_target(relay_url: &str, scope: &str, label: &str) -> RadrootsTransportTarget {
- RadrootsTransportTarget::nostr_relay_with_metadata(
+fn scoped_nostr_target(relay_url: &str, scope: &str, label: &str) -> Target {
+ Target::new_with_metadata(
+ TransportId::NOSTR,
relay_url,
- Some(RadrootsTransportMeshScopeId::parse(scope).expect("target scope")),
- Some(RadrootsTransportTargetLabel::parse(label).expect("target label")),
+ Some(TargetScope::parse(scope).expect("target scope")),
+ Some(TargetLabel::parse(label).expect("target label")),
)
.expect("scoped nostr target")
}
+fn sink_delivery_request(
+ request_id: &str,
+ signed_event: &SignedEvent,
+ targets: Vec<Target>,
+ target_policy: SinkTargetPolicy,
+) -> DeliveryRequest {
+ DeliveryRequest::new(
+ request_id,
+ DeliveryPayload::new(signed_event.clone()),
+ TargetSet::new(targets).expect("target set"),
+ SinkSatisfactionPolicy::new(SinkSatisfactionClass::Accepted, target_policy),
+ 10_000,
+ )
+ .expect("delivery request")
+}
+
fn outbox_operation_input<I, S>(
draft: EventDraft,
relays: I,
@@ -879,7 +887,7 @@ fn transport_target_and_relay_adapter_share_canonical_url_identity() {
RadrootsRelayUrlPolicy::Localhost,
),
] {
- let target = RadrootsTransportTarget::nostr_relay(raw).expect("transport target");
+ let target = Target::new(TransportId::NOSTR, raw).expect("transport target");
let relay = RelayUrl::parse(raw, policy).expect("relay URL");
assert_eq!(target.uri().as_str(), relay.as_str(), "{raw}");
}
@@ -901,7 +909,7 @@ fn transport_target_and_relay_adapter_share_canonical_url_identity() {
"wss://relay.example.com?subscription=1",
] {
assert!(
- RadrootsTransportTarget::nostr_relay(raw).is_err(),
+ Target::new(TransportId::NOSTR, raw).is_err(),
"transport target accepted {raw}"
);
assert!(
@@ -1111,27 +1119,25 @@ async fn mock_publish_preserves_exact_raw_json_and_counts_outcomes() {
async fn nostr_transport_facade_delivers_signed_event_payloads() {
let signed = signed_post("facade payload");
let adapter = RadrootsMockRelayPublishAdapter::new();
- let expected_status = RadrootsTransportStatus::new(
- RadrootsTransportKind::Nostr,
- true,
- RadrootsTransportImplementationState::Real,
+ let expected_status = SinkStatus::new(
+ TransportId::NOSTR,
true,
+ Maturity::Stable,
+ Availability::Available,
+ SinkCapabilities::DELIVER,
"fixture ready",
);
let transport = RadrootsNostrTransport::new(&adapter).with_status(expected_status.clone());
- assert_eq!(transport.transport_kind(), RadrootsTransportKind::Nostr);
assert!(transport.adapter().captured_raw_events().is_empty());
let target = nostr_target(RELAY_PRIMARY_WSS);
- let request = RadrootsTransportDeliveryRequest::new(
+ let request = sink_delivery_request(
"facade-request-1",
- RadrootsTransportPayload::unchecked_signed_event_json(signed.id_str(), signed.raw_json())
- .expect("payload"),
- RadrootsTransportTargetSet::new(vec![target.clone()]).expect("targets"),
- RadrootsTransportSatisfactionPolicy::all_accepted(),
- )
- .expect("delivery request");
+ &signed,
+ vec![target.clone()],
+ SinkTargetPolicy::all(),
+ );
- let receipt = transport.deliver(request).await.expect("delivery");
+ let receipt = transport.deliver(request.clone()).await.expect("delivery");
let status = transport.status().await.expect("status");
assert_eq!(
@@ -1139,18 +1145,14 @@ async fn nostr_transport_facade_delivers_signed_event_payloads() {
vec![signed.raw_json().to_owned()]
);
assert_eq!(status, expected_status);
- assert_eq!(receipt.request_id(), "facade-request-1");
+ assert_eq!(receipt.request_id().as_str(), "facade-request-1");
assert_eq!(receipt.target_receipts().len(), 1);
- assert_eq!(receipt.target_receipts()[0].target, target);
+ assert_eq!(receipt.target_receipts()[0].target(), &target);
assert_eq!(
- receipt.target_receipts()[0].outcome.kind,
- radroots_transport::RadrootsTransportOutcomeKind::Accepted
- );
- assert!(
- receipt
- .is_satisfied_by(&RadrootsTransportSatisfactionPolicy::all_accepted())
- .expect("satisfaction")
+ receipt.target_receipts()[0].outcome().kind(),
+ DeliveryOutcomeKind::Accepted
);
+ assert!(receipt.is_satisfied(&request).expect("satisfaction"));
}
#[test]
@@ -1172,227 +1174,69 @@ fn verified_signed_event_payload_preserves_transport_payload_identity() {
}
#[tokio::test]
-async fn nostr_transport_facade_reports_fetch_as_unsupported_operation() {
+async fn nostr_transport_sink_rejects_non_nostr_targets() {
+ let signed = signed_post("facade rejected target");
let transport = RadrootsNostrTransport::new(RadrootsMockRelayPublishAdapter::new());
- let target_set =
- RadrootsTransportTargetSet::new(vec![nostr_target(RELAY_PRIMARY_WSS)]).expect("targets");
+ let request = sink_delivery_request(
+ "facade-request-target",
+ &signed,
+ vec![Target::new(TransportId::RETICULUM, "reticulum:local").expect("target")],
+ SinkTargetPolicy::all(),
+ );
let error = transport
- .fetch(RadrootsTransportFetchRequest::new(
- "facade-fetch-unsupported",
- target_set,
- ))
- .await
- .expect_err("fetch unsupported");
-
- assert_eq!(error, RadrootsTransportError::UnsupportedOperation);
-}
-
-#[tokio::test]
-async fn nostr_transport_facade_rejects_unsupported_payloads_and_targets() {
- let signed = signed_post("facade rejected");
- let transport = RadrootsNostrTransport::new(RadrootsMockRelayPublishAdapter::new());
- let target_set =
- RadrootsTransportTargetSet::new(vec![nostr_target(RELAY_PRIMARY_WSS)]).expect("targets");
- let payload_error = transport
- .deliver(
- RadrootsTransportDeliveryRequest::new(
- "facade-request-payload",
- RadrootsTransportPayload::opaque_bytes("not-signed-event", [1, 2, 3])
- .expect("payload"),
- target_set,
- RadrootsTransportSatisfactionPolicy::all_accepted(),
- )
- .expect("delivery request"),
- )
- .await
- .expect_err("payload rejected");
- assert_eq!(payload_error, RadrootsTransportError::InvalidPayloadBytes);
-
- let non_nostr_target = RadrootsTransportTarget::reticulum().expect("reticulum target");
- let target_error = transport
- .deliver(
- RadrootsTransportDeliveryRequest::new(
- "facade-request-target",
- RadrootsTransportPayload::unchecked_signed_event_json(
- signed.id_str(),
- signed.raw_json(),
- )
- .expect("payload"),
- RadrootsTransportTargetSet::new(vec![non_nostr_target]).expect("targets"),
- RadrootsTransportSatisfactionPolicy::all_accepted(),
- )
- .expect("delivery request"),
- )
+ .deliver(request)
.await
.expect_err("target rejected");
- assert_eq!(target_error, RadrootsTransportError::InvalidTargetUri);
-
- let invalid_json_error = transport
- .deliver(
- RadrootsTransportDeliveryRequest::new(
- "facade-request-invalid-json",
- RadrootsTransportPayload::unchecked_signed_event_json(signed.id_str(), "{}")
- .expect("payload"),
- RadrootsTransportTargetSet::new(vec![nostr_target(RELAY_PRIMARY_WSS)])
- .expect("targets"),
- RadrootsTransportSatisfactionPolicy::all_accepted(),
- )
- .expect("delivery request"),
- )
- .await
- .expect_err("invalid event json rejected");
- assert_eq!(
- invalid_json_error,
- RadrootsTransportError::InvalidPayloadBytes
- );
-
- let mismatched_id_error = transport
- .deliver(
- RadrootsTransportDeliveryRequest::new(
- "facade-request-mismatched-id",
- RadrootsTransportPayload::unchecked_signed_event_json(
- "00".repeat(32),
- signed.raw_json(),
- )
- .expect("payload"),
- RadrootsTransportTargetSet::new(vec![nostr_target(RELAY_PRIMARY_WSS)])
- .expect("targets"),
- RadrootsTransportSatisfactionPolicy::all_accepted(),
- )
- .expect("delivery request"),
- )
- .await
- .expect_err("mismatched event id rejected");
- assert_eq!(
- mismatched_id_error,
- RadrootsTransportError::InvalidPayloadId
- );
-
- let mut tampered =
- serde_json::from_str::<serde_json::Value>(signed.raw_json()).expect("signed event json");
- tampered["content"] = serde_json::Value::String("tampered".to_owned());
- let tampered_raw = serde_json::to_string(&tampered).expect("tampered event json");
- let tampered_error = transport
- .deliver(
- RadrootsTransportDeliveryRequest::new(
- "facade-request-tampered-event",
- RadrootsTransportPayload::unchecked_signed_event_json(
- signed.id_str(),
- tampered_raw,
- )
- .expect("payload"),
- RadrootsTransportTargetSet::new(vec![nostr_target(RELAY_PRIMARY_WSS)])
- .expect("targets"),
- RadrootsTransportSatisfactionPolicy::all_accepted(),
- )
- .expect("delivery request"),
- )
- .await
- .expect_err("tampered event rejected");
- assert_eq!(tampered_error, RadrootsTransportError::InvalidPayloadBytes);
-
- let forbidden_target_error = transport
- .deliver(
- RadrootsTransportDeliveryRequest::new(
- "facade-request-forbidden-target",
- RadrootsTransportPayload::unchecked_signed_event_json(
- signed.id_str(),
- signed.raw_json(),
- )
- .expect("payload"),
- RadrootsTransportTargetSet::new(vec![nostr_target("wss://127.0.0.1")])
- .expect("targets"),
- RadrootsTransportSatisfactionPolicy::all_accepted(),
- )
- .expect("delivery request"),
- )
- .await
- .expect_err("forbidden relay rejected");
- assert_eq!(
- forbidden_target_error,
- RadrootsTransportError::InvalidTargetUri
- );
-
- let local_receipt = transport
- .deliver(
- RadrootsTransportDeliveryRequest::new(
- "facade-request-local-relay",
- RadrootsTransportPayload::unchecked_signed_event_json(
- signed.id_str(),
- signed.raw_json(),
- )
- .expect("payload"),
- RadrootsTransportTargetSet::new(vec![nostr_target("ws://127.0.0.1:21002")])
- .expect("targets"),
- RadrootsTransportSatisfactionPolicy::all_accepted(),
- )
- .expect("delivery request"),
- )
- .await
- .expect("localhost relay accepted");
- assert_eq!(local_receipt.target_receipts().len(), 1);
+ assert_eq!(error, RadrootsTransportError::InvalidTargetUri);
}
#[tokio::test]
async fn nostr_transport_facade_preserves_adapter_failure_and_omission_evidence() {
let signed = signed_post("facade failures");
- let payload =
- RadrootsTransportPayload::unchecked_signed_event_json(signed.id_str(), signed.raw_json())
- .expect("payload");
- let targets = RadrootsTransportTargetSet::new(vec![
+ let targets = vec![
nostr_target(RELAY_PRIMARY_WSS),
nostr_target(RELAY_SECONDARY_WSS),
- ])
- .expect("targets");
+ ];
let transport = RadrootsNostrTransport::new(TransportFailurePublishAdapter);
let failed = transport
- .deliver(
- RadrootsTransportDeliveryRequest::new(
- "facade-transport-failure",
- payload.clone(),
- targets.clone(),
- RadrootsTransportSatisfactionPolicy::all_accepted(),
- )
- .expect("delivery request"),
- )
+ .deliver(sink_delivery_request(
+ "facade-transport-failure",
+ &signed,
+ targets.clone(),
+ SinkTargetPolicy::all(),
+ ))
.await
.expect("failure receipts");
assert_eq!(failed.target_receipts().len(), 2);
assert!(failed.target_receipts().iter().all(|receipt| {
- receipt.attempted
- && receipt.outcome.kind == RadrootsTransportOutcomeKind::ConnectionFailed
- && receipt.status == RadrootsTransportDeliveryTargetStatus::FailedRetryable
+ receipt.was_attempted()
+ && receipt.outcome().kind() == DeliveryOutcomeKind::Unavailable
+ && receipt.outcome().is_retryable()
}));
let partial = RadrootsNostrTransport::new(PartialPublishAdapter)
- .deliver(
- RadrootsTransportDeliveryRequest::new(
- "facade-partial",
- payload.clone(),
- targets.clone(),
- RadrootsTransportSatisfactionPolicy::all_accepted(),
- )
- .expect("delivery request"),
- )
+ .deliver(sink_delivery_request(
+ "facade-partial",
+ &signed,
+ targets.clone(),
+ SinkTargetPolicy::all(),
+ ))
.await
.expect("partial receipts");
assert_eq!(partial.target_receipts().len(), 2);
assert_eq!(
- partial.target_receipts()[1].outcome.kind,
- RadrootsTransportOutcomeKind::TransportUnavailable
+ partial.target_receipts()[1].outcome().kind(),
+ DeliveryOutcomeKind::Unavailable
);
let error = RadrootsNostrTransport::new(NostrJsonFailurePublishAdapter)
- .deliver(
- RadrootsTransportDeliveryRequest::new(
- "facade-json-failure",
- payload,
- targets,
- RadrootsTransportSatisfactionPolicy::all_accepted(),
- )
- .expect("delivery request"),
- )
+ .deliver(sink_delivery_request(
+ "facade-json-failure",
+ &signed,
+ targets,
+ SinkTargetPolicy::all(),
+ ))
.await
.expect_err("adapter JSON error");
assert_eq!(error, RadrootsTransportError::InvalidPayloadBytes);
@@ -1402,36 +1246,30 @@ async fn nostr_transport_facade_preserves_adapter_failure_and_omission_evidence(
async fn nostr_transport_facade_matches_canonical_equivalent_relay_receipts() {
let signed = signed_post("facade canonical receipt");
let target = nostr_target(RELAY_PRIMARY_WSS);
- let policy = RadrootsTransportSatisfactionPolicy::required_targets(
+ let sink_policy = SinkTargetPolicy::required(vec![target.fingerprint().clone()])
+ .expect("required target policy");
+ let legacy_policy = RadrootsTransportSatisfactionPolicy::required_targets(
RadrootsTransportSatisfactionClass::Accepted,
vec![target.fingerprint().clone()],
)
.expect("required target policy");
let transport = RadrootsNostrTransport::new(SlashSpelledRelayReceiptPublishAdapter);
let receipt = transport
- .deliver(
- RadrootsTransportDeliveryRequest::new(
- "facade-canonical-receipt",
- RadrootsTransportPayload::unchecked_signed_event_json(
- signed.id_str(),
- signed.raw_json(),
- )
- .expect("payload"),
- RadrootsTransportTargetSet::new(vec![target.clone()]).expect("target set"),
- policy.clone(),
- )
- .expect("delivery request"),
- )
+ .deliver(sink_delivery_request(
+ "facade-canonical-receipt",
+ &signed,
+ vec![target.clone()],
+ sink_policy,
+ ))
.await
.expect("delivery");
assert_eq!(receipt.target_receipts().len(), 1);
- assert_eq!(receipt.target_receipts()[0].target, target);
+ assert_eq!(receipt.target_receipts()[0].target(), &target);
assert_eq!(
- receipt.target_receipts()[0].status,
- radroots_transport::RadrootsTransportDeliveryTargetStatus::Accepted
+ receipt.target_receipts()[0].outcome().kind(),
+ DeliveryOutcomeKind::Accepted
);
- assert!(receipt.is_satisfied_by(&policy).expect("satisfaction"));
let relay_receipt = publish_signed_event(
&SlashSpelledRelayReceiptPublishAdapter,
@@ -1442,7 +1280,7 @@ async fn nostr_transport_facade_matches_canonical_equivalent_relay_receipts() {
1_070,
)
.expect("publish request")
- .with_satisfaction_policy(policy),
+ .with_satisfaction_policy(legacy_policy),
)
.await
.expect("relay publish");
@@ -1456,26 +1294,24 @@ async fn nostr_transport_facade_preserves_scoped_duplicate_target_metadata() {
let transport = RadrootsNostrTransport::new(&adapter);
let first = scoped_nostr_target(RELAY_PRIMARY_WSS, "local_food_buyers", "buyers");
let second = scoped_nostr_target(RELAY_PRIMARY_WSS, "local_food_farmers", "farmers");
- let policy = RadrootsTransportSatisfactionPolicy::required_targets(
- RadrootsTransportSatisfactionClass::Accepted,
- vec![first.fingerprint().clone(), second.fingerprint().clone()],
- )
+ let policy = SinkTargetPolicy::required(vec![
+ first.fingerprint().clone(),
+ second.fingerprint().clone(),
+ ])
.expect("required targets");
- let request = RadrootsTransportDeliveryRequest::new(
+ let request = sink_delivery_request(
"facade-request-scoped",
- RadrootsTransportPayload::unchecked_signed_event_json(signed.id_str(), signed.raw_json())
- .expect("payload"),
- RadrootsTransportTargetSet::new(vec![first.clone(), second.clone()]).expect("targets"),
- policy.clone(),
- )
- .expect("delivery request");
+ &signed,
+ vec![first.clone(), second.clone()],
+ policy,
+ );
- let receipt = transport.deliver(request).await.expect("delivery");
+ let receipt = transport.deliver(request.clone()).await.expect("delivery");
assert_eq!(receipt.target_receipts().len(), 2);
- assert_eq!(receipt.target_receipts()[0].target, first);
- assert_eq!(receipt.target_receipts()[1].target, second);
- assert!(receipt.is_satisfied_by(&policy).expect("satisfaction"));
+ assert_eq!(receipt.target_receipts()[0].target(), &first);
+ assert_eq!(receipt.target_receipts()[1].target(), &second);
+ assert!(receipt.is_satisfied(&request).expect("satisfaction"));
assert_eq!(adapter.captured_raw_events().len(), 1);
}
@@ -1536,7 +1372,7 @@ async fn publish_receipts_track_terminal_skipped_and_adapter_errors() {
async fn publish_required_target_policy_uses_relay_fingerprints() {
let signed = signed_post("required relay");
let required_target =
- RadrootsTransportTarget::nostr_relay(RELAY_PRIMARY_WSS).expect("required target");
+ Target::new(TransportId::NOSTR, RELAY_PRIMARY_WSS).expect("required target");
let targets = RadrootsRelayTargetSet::new(
vec![RELAY_PRIMARY_WSS, RELAY_SECONDARY_WSS],
RadrootsRelayUrlPolicy::Public,
@@ -1694,7 +1530,7 @@ fn relay_publish_request_seals_fields_and_validates_idempotency_keys() {
#[tokio::test]
async fn relay_publish_request_rejects_unrequested_required_target_before_adapter() {
let signed = signed_post("missing required target");
- let required = RadrootsTransportTarget::nostr_relay(RELAY_SECONDARY_WSS)
+ let required = Target::new(TransportId::NOSTR, RELAY_SECONDARY_WSS)
.expect("required target")
.fingerprint()
.clone();
@@ -2417,7 +2253,7 @@ async fn fetch_ingests_events_and_records_transport_observations() {
.await
.expect("observations");
assert_eq!(observations.len(), 1);
- assert_eq!(observations[0].transport_kind, RadrootsTransportKind::Nostr);
+ assert_eq!(observations[0].transport_kind, TransportId::NOSTR);
assert_eq!(observations[0].endpoint_uri.as_str(), RELAY_PRIMARY_WSS);
assert_eq!(
observations[0].observation_type,
@@ -3565,14 +3401,14 @@ async fn outbox_transport_facade_persists_every_delivery_status() {
RadrootsOutboxDeliveryTargetStatus::Accepted,
RadrootsOutboxDeliveryTargetStatus::Accepted,
RadrootsOutboxDeliveryTargetStatus::Delivered,
- RadrootsOutboxDeliveryTargetStatus::Forwarded,
- RadrootsOutboxDeliveryTargetStatus::StoredByGateway,
- RadrootsOutboxDeliveryTargetStatus::Seen,
- RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented,
+ RadrootsOutboxDeliveryTargetStatus::Accepted,
+ RadrootsOutboxDeliveryTargetStatus::Accepted,
+ RadrootsOutboxDeliveryTargetStatus::Accepted,
+ RadrootsOutboxDeliveryTargetStatus::FailedTerminal,
+ RadrootsOutboxDeliveryTargetStatus::FailedTerminal,
RadrootsOutboxDeliveryTargetStatus::FailedTerminal,
RadrootsOutboxDeliveryTargetStatus::FailedTerminal,
RadrootsOutboxDeliveryTargetStatus::FailedTerminal,
- RadrootsOutboxDeliveryTargetStatus::SkippedPolicyDenied,
RadrootsOutboxDeliveryTargetStatus::FailedRetryable,
RadrootsOutboxDeliveryTargetStatus::FailedRetryable,
RadrootsOutboxDeliveryTargetStatus::FailedRetryable,
@@ -3595,7 +3431,7 @@ async fn outbox_transport_facade_persists_every_delivery_status() {
}
#[tokio::test]
-async fn outbox_transport_facade_rejects_pending_receipts() {
+async fn outbox_transport_facade_normalizes_predecessor_pending_evidence() {
let outbox = RadrootsOutbox::open_memory().await.expect("outbox");
let store = RadrootsEventStore::open_memory().await.expect("store");
let draft = generic_draft("pending transport receipt");
@@ -3621,7 +3457,7 @@ async fn outbox_transport_facade_rejects_pending_receipts() {
forged_outcome.status = RadrootsTransportDeliveryTargetStatus::Pending;
let transport = ScriptedTransport::new(vec![forged_outcome]);
- let error = publish_claimed_outbox_event_with_transport(
+ let published = publish_claimed_outbox_event_with_transport(
&outbox,
&store,
&transport,
@@ -3630,11 +3466,12 @@ async fn outbox_transport_facade_rejects_pending_receipts() {
2_200,
)
.await
- .expect_err("pending receipt rejected");
- assert!(matches!(
- error,
- RadrootsRelayTransportError::TransportContract(_)
- ));
+ .expect("final sink receipt");
+ assert_eq!(published.target_receipts.len(), 1);
+ assert_eq!(
+ published.target_receipts[0].transport_status,
+ RadrootsTransportDeliveryTargetStatus::Accepted
+ );
}
#[tokio::test]
@@ -3852,7 +3689,7 @@ async fn outbox_transport_facade_rejects_non_nostr_transport_before_mutation() {
.await
.expect("publish claim")
.expect("publish claim");
- let transport = ScriptedTransport::new(Vec::new()).with_kind(RadrootsTransportKind::Reticulum);
+ let transport = ScriptedTransport::new(Vec::new()).with_kind(TransportId::RETICULUM);
let error = publish_claimed_outbox_event_with_transport(
&outbox,
@@ -4323,7 +4160,7 @@ async fn outbox_publish_required_targets_fan_out_same_endpoint_scoped_receipts()
required.clone(),
optional.clone(),
terminal.clone(),
- RadrootsTransportTarget::reticulum().expect("reticulum target"),
+ Target::reticulum().expect("reticulum target"),
],
),
1_000,
@@ -4412,7 +4249,7 @@ async fn outbox_publish_required_targets_fan_out_same_endpoint_scoped_receipts()
assert!(
targets
.iter()
- .filter(|target| { target.transport_kind == RadrootsTransportKind::Nostr })
+ .filter(|target| { target.transport_kind == TransportId::NOSTR })
.filter(|target| &target.endpoint_fingerprint != terminal.fingerprint())
.all(|target| {
target.endpoint_uri.as_str() == RELAY_PRIMARY_WSS
@@ -4424,7 +4261,7 @@ async fn outbox_publish_required_targets_fan_out_same_endpoint_scoped_receipts()
&& target.status == RadrootsOutboxDeliveryTargetStatus::FailedTerminal
}));
assert!(targets.iter().any(|target| {
- target.transport_kind == RadrootsTransportKind::Reticulum
+ target.transport_kind == TransportId::RETICULUM
&& target.status == RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
}));
}
@@ -4659,7 +4496,7 @@ async fn outbox_publish_skips_non_nostr_targets() {
RadrootsTransportSatisfactionPolicy::all_accepted(),
vec![
nostr_target(RELAY_PRIMARY_WSS),
- RadrootsTransportTarget::reticulum().expect("reticulum target"),
+ Target::reticulum().expect("reticulum target"),
],
),
1_000,
@@ -4703,7 +4540,7 @@ async fn outbox_publish_skips_non_nostr_targets() {
.await
.expect("targets");
assert!(targets.iter().any(|target| {
- target.transport_kind == RadrootsTransportKind::Reticulum
+ target.transport_kind == TransportId::RETICULUM
&& target.status == RadrootsOutboxDeliveryTargetStatus::DeferredUntilImplemented
}));
}
diff --git a/crates/transport_publish_protocol/src/lib.rs b/crates/transport_publish_protocol/src/lib.rs
@@ -14,10 +14,8 @@ use radroots_protocol::radrootsd::transport_publish::v5::{
RETICULUM_ENDPOINT_URI as RADROOTS_RETICULUM_ENDPOINT_URI,
RETICULUM_UNAVAILABLE_MESSAGE as RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
};
-use radroots_transport::{
- RadrootsTransportError, RadrootsTransportKind, RadrootsTransportMeshScopeId,
- RadrootsTransportTarget, RadrootsTransportTargetFingerprint, RadrootsTransportTargetLabel,
-};
+use radroots_transport::target::{TargetFingerprint, TargetLabel, TargetScope};
+use radroots_transport::{RadrootsTransportError, Target, TransportId};
pub const API_VERSION: &str = "radrootsd.transport_publish.v5";
pub const DAEMON_NAME: &str = "radrootsd";
@@ -309,15 +307,15 @@ impl TransportPublishTarget {
if self.transport_kind.trim().is_empty() {
return Err(TransportPublishProtocolError::EmptyTransportKind { index });
}
- let transport_kind = RadrootsTransportKind::parse_canonical(self.transport_kind.as_str())
+ let transport_kind = TransportId::parse_canonical(self.transport_kind.as_str())
.map_err(|error| transport_kind_error(error, index))?;
if self.endpoint_uri.trim().is_empty() {
return Err(TransportPublishProtocolError::EmptyEndpointUri { index });
}
- if transport_kind != RadrootsTransportKind::Reticulum && self.reticulum_behavior.is_some() {
+ if transport_kind != TransportId::RETICULUM && self.reticulum_behavior.is_some() {
return Err(TransportPublishProtocolError::InvalidReticulumBehavior { index });
}
- if transport_kind == RadrootsTransportKind::Reticulum
+ if transport_kind == TransportId::RETICULUM
&& self.endpoint_uri != RADROOTS_RETICULUM_ENDPOINT_URI
{
return Err(TransportPublishProtocolError::InvalidReticulumEndpoint { index });
@@ -334,26 +332,23 @@ impl TransportPublishTarget {
fn fingerprint(
&self,
index: usize,
- ) -> Result<RadrootsTransportTargetFingerprint, TransportPublishProtocolError> {
+ ) -> Result<TargetFingerprint, TransportPublishProtocolError> {
Ok(self.canonical_target(index)?.fingerprint().clone())
}
- fn canonical_target(
- &self,
- index: usize,
- ) -> Result<RadrootsTransportTarget, TransportPublishProtocolError> {
- let transport_kind = RadrootsTransportKind::parse_canonical(self.transport_kind.as_str())
+ fn canonical_target(&self, index: usize) -> Result<Target, TransportPublishProtocolError> {
+ let transport_kind = TransportId::parse_canonical(self.transport_kind.as_str())
.map_err(|error| transport_kind_error(error, index))?;
let scope = self
.target_scope
.as_deref()
- .map(RadrootsTransportMeshScopeId::parse)
+ .map(TargetScope::parse)
.transpose()
.map_err(|error| target_metadata_error(error, index))?;
let label = self
.target_label
.as_deref()
- .map(RadrootsTransportTargetLabel::parse)
+ .map(TargetLabel::parse)
.transpose()
.map_err(|error| target_metadata_error(error, index))?;
let target =
@@ -386,12 +381,10 @@ fn validate_target_metadata(
index: usize,
) -> Result<(), TransportPublishProtocolError> {
if let Some(scope) = target_scope {
- RadrootsTransportMeshScopeId::parse(scope)
- .map_err(|error| target_metadata_error(error, index))?;
+ TargetScope::parse(scope).map_err(|error| target_metadata_error(error, index))?;
}
if let Some(label) = target_label {
- RadrootsTransportTargetLabel::parse(label)
- .map_err(|error| target_metadata_error(error, index))?;
+ TargetLabel::parse(label).map_err(|error| target_metadata_error(error, index))?;
}
Ok(())
}
@@ -503,7 +496,7 @@ impl TransportPublishTargetPolicy {
if endpoint_uri.trim().is_empty() {
return Err(TransportPublishProtocolError::EmptyEndpointUri { index });
}
- let target = RadrootsTransportTarget::nostr_relay(endpoint_uri)
+ let target = Target::new(TransportId::NOSTR, endpoint_uri)
.map_err(|error| target_fingerprint_error(error, index))?;
if target.uri().as_str() != endpoint_uri {
return Err(TransportPublishProtocolError::InvalidEndpointUri { index });
@@ -544,17 +537,13 @@ fn validate_explicit_target_uniqueness(
pub enum TransportPublishDeliveryPolicy {
Any,
All,
- Quorum {
- quorum: usize,
- },
- RequiredTargets {
- targets: Vec<RadrootsTransportTargetFingerprint>,
- },
+ Quorum { quorum: usize },
+ RequiredTargets { targets: Vec<TargetFingerprint> },
}
impl TransportPublishDeliveryPolicy {
pub fn required_targets(
- targets: Vec<RadrootsTransportTargetFingerprint>,
+ targets: Vec<TargetFingerprint>,
) -> Result<Self, TransportPublishProtocolError> {
validate_required_target_fingerprints(&targets)?;
Ok(Self::RequiredTargets { targets })
@@ -579,7 +568,7 @@ impl TransportPublishDeliveryPolicy {
pub fn validate_target_membership(
&self,
- target_fingerprints: &[RadrootsTransportTargetFingerprint],
+ target_fingerprints: &[TargetFingerprint],
) -> Result<(), TransportPublishProtocolError> {
let Self::RequiredTargets { targets } = self else {
return Ok(());
@@ -598,7 +587,7 @@ impl TransportPublishDeliveryPolicy {
}
fn validate_required_target_fingerprints(
- targets: &[RadrootsTransportTargetFingerprint],
+ targets: &[TargetFingerprint],
) -> Result<(), TransportPublishProtocolError> {
if targets.is_empty() {
return Err(TransportPublishProtocolError::EmptyRequiredTargetSet);
@@ -1147,7 +1136,7 @@ fn validate_target_outcome(
if target.transport_kind.trim().is_empty() {
return Err(TransportPublishProtocolError::EmptyTransportKind { index });
}
- let transport_kind = RadrootsTransportKind::parse_canonical(target.transport_kind.as_str())
+ let transport_kind = TransportId::parse_canonical(target.transport_kind.as_str())
.map_err(|error| transport_kind_error(error, index))?;
if target.endpoint_uri.trim().is_empty() {
return Err(TransportPublishProtocolError::EmptyEndpointUri { index });
@@ -1157,7 +1146,7 @@ fn validate_target_outcome(
target.target_label.as_deref(),
index,
)?;
- if transport_kind == RadrootsTransportKind::Reticulum {
+ if transport_kind == TransportId::RETICULUM {
if target.endpoint_uri != RADROOTS_RETICULUM_ENDPOINT_URI {
return Err(TransportPublishProtocolError::InvalidReticulumEndpoint { index });
}
@@ -1182,19 +1171,19 @@ fn validate_target_outcome(
fn target_outcome_fingerprint(
target: &TransportPublishTargetOutcome,
index: usize,
-) -> Result<RadrootsTransportTargetFingerprint, TransportPublishProtocolError> {
- let transport_kind = RadrootsTransportKind::parse_canonical(target.transport_kind.as_str())
+) -> Result<TargetFingerprint, TransportPublishProtocolError> {
+ let transport_kind = TransportId::parse_canonical(target.transport_kind.as_str())
.map_err(|error| transport_kind_error(error, index))?;
let scope = target
.target_scope
.as_deref()
- .map(RadrootsTransportMeshScopeId::parse)
+ .map(TargetScope::parse)
.transpose()
.map_err(|error| target_metadata_error(error, index))?;
let label = target
.target_label
.as_deref()
- .map(RadrootsTransportTargetLabel::parse)
+ .map(TargetLabel::parse)
.transpose()
.map_err(|error| target_metadata_error(error, index))?;
let canonical_target =
@@ -1207,30 +1196,30 @@ fn target_outcome_fingerprint(
}
fn transport_target_from_parts(
- transport_kind: RadrootsTransportKind,
+ transport_kind: TransportId,
endpoint_uri: &str,
- scope: Option<RadrootsTransportMeshScopeId>,
- label: Option<RadrootsTransportTargetLabel>,
-) -> Result<RadrootsTransportTarget, RadrootsTransportError> {
+ scope: Option<TargetScope>,
+ label: Option<TargetLabel>,
+) -> Result<Target, RadrootsTransportError> {
match transport_kind {
- RadrootsTransportKind::Nostr => {
- RadrootsTransportTarget::nostr_relay_with_metadata(endpoint_uri, scope, label)
+ TransportId::NOSTR => {
+ Target::new_with_metadata(TransportId::NOSTR, endpoint_uri, scope, label)
}
- RadrootsTransportKind::Reticulum => {
+ TransportId::RETICULUM => {
if endpoint_uri != RADROOTS_RETICULUM_ENDPOINT_URI {
return Err(RadrootsTransportError::InvalidTargetUri);
}
- RadrootsTransportTarget::reticulum_with_metadata(endpoint_uri, scope, label)
+ Target::new_with_metadata(TransportId::RETICULUM, endpoint_uri, scope, label)
}
- RadrootsTransportKind::Local => {
- RadrootsTransportTarget::local_with_metadata(endpoint_uri, scope, label)
+ TransportId::LOCAL => {
+ Target::new_with_metadata(TransportId::LOCAL, endpoint_uri, scope, label)
}
- _ => RadrootsTransportTarget::new_with_metadata(transport_kind, endpoint_uri, scope, label),
+ _ => Target::new_with_metadata(transport_kind, endpoint_uri, scope, label),
}
}
fn required_policy_outcomes<'a>(
- required_targets: &[RadrootsTransportTargetFingerprint],
+ required_targets: &[TargetFingerprint],
outcomes: &'a [TransportPublishTargetOutcome],
) -> Result<Vec<&'a TransportPublishTargetOutcome>, TransportPublishProtocolError> {
required_targets
@@ -2674,19 +2663,13 @@ mod tests {
assert_eq!(target_metadata_error(error, 5), expected);
}
- assert!(serde_json::from_str::<RadrootsTransportTargetFingerprint>("\"invalid\"").is_err());
+ assert!(serde_json::from_str::<TargetFingerprint>("\"invalid\"").is_err());
assert!(
- transport_target_from_parts(RadrootsTransportKind::Local, "local:publish", None, None,)
- .is_ok()
+ transport_target_from_parts(TransportId::LOCAL, "local:publish", None, None,).is_ok()
);
assert_eq!(
- transport_target_from_parts(
- RadrootsTransportKind::Reticulum,
- "reticulum:other",
- None,
- None,
- ),
+ transport_target_from_parts(TransportId::RETICULUM, "reticulum:other", None, None,),
Err(RadrootsTransportError::InvalidTargetUri)
);
diff --git a/crates/transport_publish_protocol/tests/successor_equivalence.rs b/crates/transport_publish_protocol/tests/successor_equivalence.rs
@@ -1,7 +1,7 @@
use serde::{Serialize, de::DeserializeOwned};
use radroots_protocol::radrootsd::transport_publish::v5 as successor;
-use radroots_transport::RadrootsTransportTarget;
+use radroots_transport::{Target, TransportId};
use radroots_transport_publish_protocol as predecessor;
fn assert_json_equivalent<Old, New>(old: &Old) -> New
@@ -86,8 +86,8 @@ fn request_response_job_and_capability_json_are_byte_identical() {
#[test]
fn required_target_fingerprint_json_is_byte_identical() {
- let target = RadrootsTransportTarget::nostr_relay("wss://relay.example.com")
- .expect("native Nostr target");
+ let target =
+ Target::new(TransportId::NOSTR, "wss://relay.example.com").expect("native Nostr target");
let predecessor = predecessor::TransportPublishDeliveryPolicy::required_targets(vec![
target.fingerprint().clone(),
])
diff --git a/crates/transport_reticulum/src/contract.rs b/crates/transport_reticulum/src/contract.rs
@@ -1,9 +1,6 @@
use crate::RADROOTS_RETICULUM_ENDPOINT_URI;
-use radroots_transport::{
- RadrootsTransportError, RadrootsTransportKind, RadrootsTransportMeshScopeId,
- RadrootsTransportTarget, RadrootsTransportTargetFingerprint, RadrootsTransportTargetLabel,
- RadrootsTransportTargetUri,
-};
+use radroots_transport::target::{TargetFingerprint, TargetLabel, TargetScope};
+use radroots_transport::{RadrootsTransportError, RadrootsTransportTargetUri, Target, TransportId};
pub const RETICULUM_V1_MAX_PAYLOAD_BYTES: usize = 64 * 1024;
@@ -79,7 +76,7 @@ pub enum ReticulumPrivacySemanticsV1 {
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReticulumRoutingMetadataV1 {
- pub scope: RadrootsTransportMeshScopeId,
+ pub scope: TargetScope,
pub gateway: ReticulumGatewaySemanticsV1,
pub privacy: ReticulumPrivacySemanticsV1,
}
@@ -87,7 +84,7 @@ pub struct ReticulumRoutingMetadataV1 {
impl ReticulumRoutingMetadataV1 {
pub fn local() -> Self {
Self {
- scope: RadrootsTransportMeshScopeId::local_reticulum(),
+ scope: TargetScope::parse(crate::RADROOTS_RETICULUM_SCOPE_ID).expect("Reticulum scope"),
gateway: ReticulumGatewaySemanticsV1::NoGatewayForwarding,
privacy: ReticulumPrivacySemanticsV1::CanonicalSignedEventBytesOnly,
}
@@ -99,8 +96,8 @@ impl ReticulumRoutingMetadataV1 {
pub struct ReticulumDestinationV1 {
uri: RadrootsTransportTargetUri,
routing: ReticulumRoutingMetadataV1,
- label: Option<RadrootsTransportTargetLabel>,
- fingerprint: RadrootsTransportTargetFingerprint,
+ label: Option<TargetLabel>,
+ fingerprint: TargetFingerprint,
}
impl ReticulumDestinationV1 {
@@ -115,10 +112,11 @@ impl ReticulumDestinationV1 {
pub fn new(
uri: impl AsRef<str>,
- scope: RadrootsTransportMeshScopeId,
- label: Option<RadrootsTransportTargetLabel>,
+ scope: TargetScope,
+ label: Option<TargetLabel>,
) -> Result<Self, RadrootsTransportError> {
- let target = RadrootsTransportTarget::reticulum_with_metadata(
+ let target = Target::new_with_metadata(
+ TransportId::RETICULUM,
uri.as_ref(),
Some(scope),
label.clone(),
@@ -138,8 +136,8 @@ impl ReticulumDestinationV1 {
})
}
- pub fn from_target(target: &RadrootsTransportTarget) -> Result<Self, RadrootsTransportError> {
- if target.kind() != &RadrootsTransportKind::Reticulum
+ pub fn from_target(target: &Target) -> Result<Self, RadrootsTransportError> {
+ if target.kind() != &TransportId::RETICULUM
|| target.uri().as_str() != RADROOTS_RETICULUM_ENDPOINT_URI
{
return Err(RadrootsTransportError::InvalidTargetUri);
@@ -154,8 +152,9 @@ impl ReticulumDestinationV1 {
Ok(destination)
}
- pub fn transport_target(&self) -> Result<RadrootsTransportTarget, RadrootsTransportError> {
- RadrootsTransportTarget::reticulum_with_metadata(
+ pub fn transport_target(&self) -> Result<Target, RadrootsTransportError> {
+ Target::new_with_metadata(
+ TransportId::RETICULUM,
self.uri.as_str(),
Some(self.routing.scope.clone()),
self.label.clone(),
@@ -170,11 +169,11 @@ impl ReticulumDestinationV1 {
&self.routing
}
- pub fn label(&self) -> Option<&RadrootsTransportTargetLabel> {
+ pub fn label(&self) -> Option<&TargetLabel> {
self.label.as_ref()
}
- pub fn fingerprint(&self) -> &RadrootsTransportTargetFingerprint {
+ pub fn fingerprint(&self) -> &TargetFingerprint {
&self.fingerprint
}
}
@@ -185,8 +184,8 @@ impl ReticulumDestinationV1 {
struct ReticulumDestinationV1Wire {
uri: RadrootsTransportTargetUri,
routing: ReticulumRoutingMetadataV1,
- label: Option<RadrootsTransportTargetLabel>,
- fingerprint: RadrootsTransportTargetFingerprint,
+ label: Option<TargetLabel>,
+ fingerprint: TargetFingerprint,
}
#[cfg(feature = "serde")]
diff --git a/crates/transport_reticulum/src/lib.rs b/crates/transport_reticulum/src/lib.rs
@@ -23,14 +23,21 @@ use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
+use radroots_transport::capability::{
+ Availability, Maturity, SinkCapabilities, SourceCapabilities,
+};
+use radroots_transport::outcome::{DeliveryOutcome, FetchTargetOutcome, FetchTargetState};
+use radroots_transport::sink::{
+ DeliveryReceipt, DeliveryRequest, DeliveryTargetReceipt, EventSink, SinkStatus,
+};
+use radroots_transport::source::{EventSource, FetchPage, FetchRequest, NextPage, SourceStatus};
+use radroots_transport::target::TargetScope;
use radroots_transport::{
- RadrootsTransport, RadrootsTransportCapabilities, RadrootsTransportCapabilityAvailability,
+ RadrootsTransportCapabilities, RadrootsTransportCapabilityAvailability,
RadrootsTransportCapabilityMaturity, RadrootsTransportDeliveryReceipt,
- RadrootsTransportDeliveryRequest, RadrootsTransportError, RadrootsTransportFetchReceipt,
- RadrootsTransportFetchRequest, RadrootsTransportFuture, RadrootsTransportImplementationState,
- RadrootsTransportKind, RadrootsTransportMeshScopeId, RadrootsTransportOutcome,
- RadrootsTransportOutcomeKind, RadrootsTransportStatus, RadrootsTransportTarget,
- RadrootsTransportTargetReceipt,
+ RadrootsTransportDeliveryRequest, RadrootsTransportError, RadrootsTransportImplementationState,
+ RadrootsTransportOutcome, RadrootsTransportOutcomeKind, RadrootsTransportStatus,
+ RadrootsTransportTargetReceipt, Target, TransportId,
};
const DEFAULT_PROFILE_ID: &str = "transport.reticulum.default";
@@ -129,7 +136,7 @@ impl fmt::Display for RadrootsReticulumAgentEndpoint {
pub struct RadrootsReticulumProfile {
profile_id: String,
endpoint: RadrootsReticulumEndpoint,
- scope: RadrootsTransportMeshScopeId,
+ scope: TargetScope,
agent_endpoint: Option<RadrootsReticulumAgentEndpoint>,
behavior: RadrootsReticulumBehavior,
destination: ReticulumDestinationV1,
@@ -140,7 +147,7 @@ impl RadrootsReticulumProfile {
pub fn new(
profile_id: impl Into<String>,
endpoint: RadrootsReticulumEndpoint,
- scope: RadrootsTransportMeshScopeId,
+ scope: TargetScope,
agent_endpoint: Option<RadrootsReticulumAgentEndpoint>,
behavior: RadrootsReticulumBehavior,
) -> Result<Self, RadrootsReticulumError> {
@@ -171,7 +178,7 @@ impl RadrootsReticulumProfile {
Self {
profile_id: DEFAULT_PROFILE_ID.to_owned(),
endpoint: RadrootsReticulumEndpoint::default(),
- scope: RadrootsTransportMeshScopeId::local_reticulum(),
+ scope: TargetScope::parse(RADROOTS_RETICULUM_SCOPE_ID).expect("Reticulum scope"),
agent_endpoint: None,
behavior: RadrootsReticulumBehavior::RejectDeliveryAttempts,
destination: capability_report.destination.clone(),
@@ -192,7 +199,7 @@ impl RadrootsReticulumProfile {
&self.endpoint
}
- pub fn scope(&self) -> &RadrootsTransportMeshScopeId {
+ pub fn scope(&self) -> &TargetScope {
&self.scope
}
@@ -225,7 +232,7 @@ impl RadrootsReticulumProfile {
destination: self.destination.clone(),
capability_report: self.capability_report.clone(),
transport_status: RadrootsTransportStatus::new(
- RadrootsTransportKind::Reticulum,
+ TransportId::RETICULUM,
true,
RadrootsTransportImplementationState::Real,
false,
@@ -250,7 +257,7 @@ impl Default for RadrootsReticulumProfile {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RadrootsReticulumStatus {
pub behavior: RadrootsReticulumBehavior,
- pub scope: RadrootsTransportMeshScopeId,
+ pub scope: TargetScope,
pub agent_endpoint: Option<RadrootsReticulumAgentEndpoint>,
pub destination: ReticulumDestinationV1,
pub capability_report: ReticulumCapabilityReportV1,
@@ -317,45 +324,79 @@ impl Default for RadrootsReticulumTransport {
}
}
-impl RadrootsTransport for RadrootsReticulumTransport {
- fn transport_kind(&self) -> RadrootsTransportKind {
- RadrootsTransportKind::Reticulum
+impl EventSink for RadrootsReticulumTransport {
+ fn status(
+ &self,
+ ) -> radroots_transport::BoxFuture<'_, Result<SinkStatus, RadrootsTransportError>> {
+ Box::pin(async {
+ Ok(SinkStatus::new(
+ TransportId::RETICULUM,
+ true,
+ Maturity::Preview,
+ Availability::Unavailable,
+ SinkCapabilities::NONE,
+ RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
+ ))
+ })
}
- fn status<'a>(&'a self) -> RadrootsTransportFuture<'a, RadrootsTransportStatus> {
- Box::pin(async move { Ok(self.profile.status().transport_status) })
+ fn deliver(
+ &self,
+ request: DeliveryRequest,
+ ) -> radroots_transport::BoxFuture<'_, Result<DeliveryReceipt, RadrootsTransportError>> {
+ Box::pin(async move {
+ ensure_reticulum_targets(request.target_set().targets())
+ .map_err(reticulum_error_to_transport_error)?;
+ let outcome = DeliveryOutcome::unavailable()
+ .with_detail(UNAVAILABLE_CODE, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE)?;
+ let receipts = request
+ .target_set()
+ .targets()
+ .iter()
+ .cloned()
+ .map(|target| DeliveryTargetReceipt::skipped(target, outcome.clone()))
+ .collect::<Result<Vec<_>, _>>()?;
+ DeliveryReceipt::for_request(&request, receipts)
+ })
}
+}
- fn deliver<'a>(
- &'a self,
- request: RadrootsTransportDeliveryRequest,
- ) -> RadrootsTransportFuture<'a, RadrootsTransportDeliveryReceipt> {
- Box::pin(async move {
- self.deliver(request)
- .map_err(reticulum_error_to_transport_error)
+impl EventSource for RadrootsReticulumTransport {
+ fn status(
+ &self,
+ ) -> radroots_transport::BoxFuture<'_, Result<SourceStatus, RadrootsTransportError>> {
+ Box::pin(async {
+ Ok(SourceStatus::new(
+ TransportId::RETICULUM,
+ true,
+ Maturity::Preview,
+ Availability::Unavailable,
+ SourceCapabilities::NONE,
+ RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
+ ))
})
}
- fn fetch<'a>(
- &'a self,
- request: RadrootsTransportFetchRequest,
- ) -> RadrootsTransportFuture<'a, RadrootsTransportFetchReceipt> {
+ fn fetch(
+ &self,
+ request: FetchRequest,
+ ) -> radroots_transport::BoxFuture<'_, Result<FetchPage, RadrootsTransportError>> {
Box::pin(async move {
- ensure_reticulum_targets(request.target_set.targets())
+ ensure_reticulum_targets(request.target_set().targets())
.map_err(reticulum_error_to_transport_error)?;
- let outcome = reticulum_outcome(self.profile.behavior);
- let target_receipts = request
- .target_set
+ let outcomes = request
+ .target_set()
.targets()
.iter()
- .cloned()
- .map(|target| RadrootsTransportTargetReceipt::skipped(target, outcome.clone()))
- .collect::<Vec<_>>();
- Ok(RadrootsTransportFetchReceipt::new(
- request.request_id,
- target_receipts,
- 0,
- ))
+ .map(|target| {
+ FetchTargetOutcome::new(
+ target.fingerprint().clone(),
+ FetchTargetState::Unavailable,
+ )
+ .with_message(RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE)
+ })
+ .collect();
+ FetchPage::for_request(&request, Vec::new(), outcomes, NextPage::Complete)
})
}
}
@@ -387,7 +428,7 @@ impl RadrootsReticulumFetchRequest {
pub struct RadrootsReticulumFetchReceipt {
pub request_id: String,
pub endpoint_uri: String,
- pub scope: RadrootsTransportMeshScopeId,
+ pub scope: TargetScope,
pub agent_endpoint: Option<RadrootsReticulumAgentEndpoint>,
pub outcome: RadrootsTransportOutcome,
pub observed_event_count: usize,
@@ -431,11 +472,9 @@ fn reticulum_error_to_transport_error(error: RadrootsReticulumError) -> Radroots
}
}
-fn ensure_reticulum_targets(
- targets: &[RadrootsTransportTarget],
-) -> Result<(), RadrootsReticulumError> {
+fn ensure_reticulum_targets(targets: &[Target]) -> Result<(), RadrootsReticulumError> {
for target in targets {
- if target.kind() != &RadrootsTransportKind::Reticulum {
+ if target.kind() != &TransportId::RETICULUM {
return Err(RadrootsReticulumError::NonReticulumTarget);
}
if target.uri().as_str() != RADROOTS_RETICULUM_ENDPOINT_URI {
@@ -485,19 +524,19 @@ mod tests {
use alloc::vec;
use futures::executor::block_on;
use radroots_transport::{
- RadrootsTransportPayload, RadrootsTransportSatisfactionPolicy, RadrootsTransportTargetSet,
+ RadrootsTransportPayload, RadrootsTransportSatisfactionPolicy, TargetSet,
};
- fn reticulum_target() -> RadrootsTransportTarget {
- RadrootsTransportTarget::reticulum().expect("Reticulum target")
+ fn reticulum_target() -> Target {
+ Target::reticulum().expect("Reticulum target")
}
- fn delivery_request(targets: Vec<RadrootsTransportTarget>) -> RadrootsTransportDeliveryRequest {
+ fn delivery_request(targets: Vec<Target>) -> RadrootsTransportDeliveryRequest {
RadrootsTransportDeliveryRequest::new(
"delivery",
RadrootsTransportPayload::mesh_frame_cbor("message", [1_u8, 2, 3])
.expect("mesh payload"),
- RadrootsTransportTargetSet::new(targets).expect("target set"),
+ TargetSet::new(targets).expect("target set"),
RadrootsTransportSatisfactionPolicy::any_accepted(),
)
.expect("delivery request")
@@ -543,7 +582,7 @@ mod tests {
"reticulum-agent:owned"
);
- let scope = RadrootsTransportMeshScopeId::parse("farm.mesh").expect("scope");
+ let scope = TargetScope::parse("farm.mesh").expect("scope");
for invalid in ["", " ", "profile id"] {
assert!(
RadrootsReticulumProfile::new(
@@ -619,22 +658,17 @@ mod tests {
);
assert_eq!(
- RadrootsTransport::transport_kind(&rejecting),
- RadrootsTransportKind::Reticulum
+ block_on(EventSink::status(&rejecting))
+ .expect("sink status")
+ .transport_id(),
+ TransportId::RETICULUM
);
- assert!(block_on(RadrootsTransport::status(&rejecting)).is_ok());
- assert!(
- block_on(RadrootsTransport::deliver(
- &rejecting,
- delivery_request(vec![reticulum_target()]),
- ))
- .is_ok()
- );
- let core_fetch = RadrootsTransportFetchRequest::new(
- "core-fetch",
- RadrootsTransportTargetSet::new(vec![reticulum_target()]).expect("target set"),
+ assert_eq!(
+ block_on(EventSource::status(&rejecting))
+ .expect("source status")
+ .transport_id(),
+ TransportId::RETICULUM
);
- assert!(block_on(RadrootsTransport::fetch(&rejecting, core_fetch)).is_ok());
let deferring = RadrootsReticulumTransport::new(
RadrootsReticulumProfile::default()
@@ -671,13 +705,13 @@ mod tests {
assert!(ensure_reticulum_targets(&[]).is_ok());
let wrong_kind =
- RadrootsTransportTarget::local("local:memory").expect("non-Reticulum target");
+ Target::new(TransportId::LOCAL, "local:memory").expect("non-Reticulum target");
assert_eq!(
ensure_reticulum_targets(&[wrong_kind]),
Err(RadrootsReticulumError::NonReticulumTarget)
);
assert_eq!(
- RadrootsTransportTarget::new(RadrootsTransportKind::Reticulum, "reticulum:other")
+ Target::new(TransportId::RETICULUM, "reticulum:other")
.expect_err("wrong Reticulum URI"),
RadrootsTransportError::InvalidTargetUri
);
diff --git a/crates/transport_reticulum/tests/reticulum.rs b/crates/transport_reticulum/tests/reticulum.rs
@@ -1,10 +1,13 @@
+use radroots_transport::capability::{Availability, Maturity};
+use radroots_transport::sink::EventSink;
+use radroots_transport::source::{EventSource, FetchBounds, FetchRequest};
+use radroots_transport::target::TargetScope;
use radroots_transport::{
- RadrootsTransport, RadrootsTransportCapabilityAvailability,
- RadrootsTransportCapabilityMaturity, RadrootsTransportDeliveryRequest,
- RadrootsTransportDeliveryTargetStatus, RadrootsTransportFetchRequest,
- RadrootsTransportImplementationState, RadrootsTransportKind, RadrootsTransportMeshScopeId,
- RadrootsTransportPayload, RadrootsTransportSatisfactionClass,
- RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget, RadrootsTransportTargetSet,
+ RadrootsTransportCapabilityAvailability, RadrootsTransportCapabilityMaturity,
+ RadrootsTransportDeliveryRequest, RadrootsTransportDeliveryTargetStatus,
+ RadrootsTransportImplementationState, RadrootsTransportPayload,
+ RadrootsTransportSatisfactionClass, RadrootsTransportSatisfactionPolicy, Target, TargetSet,
+ TransportId,
};
use radroots_transport_reticulum::{
RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID,
@@ -17,29 +20,30 @@ use radroots_transport_reticulum::{
#[cfg(feature = "serde")]
use serde_json::Value;
-fn reticulum_target(uri: &str) -> RadrootsTransportTarget {
+fn reticulum_target(uri: &str) -> Target {
assert_eq!(uri, RADROOTS_RETICULUM_ENDPOINT_URI);
- RadrootsTransportTarget::reticulum().expect("reticulum target")
+ Target::reticulum().expect("reticulum target")
}
-fn scoped_reticulum_target(scope: &str) -> RadrootsTransportTarget {
- RadrootsTransportTarget::reticulum_with_metadata(
+fn scoped_reticulum_target(scope: &str) -> Target {
+ Target::new_with_metadata(
+ TransportId::RETICULUM,
RADROOTS_RETICULUM_ENDPOINT_URI,
- Some(RadrootsTransportMeshScopeId::parse(scope).expect("scope")),
+ Some(TargetScope::parse(scope).expect("scope")),
None,
)
.expect("scoped reticulum target")
}
-fn nostr_target() -> RadrootsTransportTarget {
- RadrootsTransportTarget::nostr_relay("wss://relay.example").expect("nostr target")
+fn nostr_target() -> Target {
+ Target::new(TransportId::NOSTR, "wss://relay.example").expect("nostr target")
}
-fn delivery_request(targets: Vec<RadrootsTransportTarget>) -> RadrootsTransportDeliveryRequest {
+fn delivery_request(targets: Vec<Target>) -> RadrootsTransportDeliveryRequest {
RadrootsTransportDeliveryRequest::new(
"reticulum-delivery",
reticulum_payload(),
- RadrootsTransportTargetSet::new(targets).expect("target set"),
+ TargetSet::new(targets).expect("target set"),
RadrootsTransportSatisfactionPolicy::any_accepted(),
)
.expect("delivery request")
@@ -239,7 +243,7 @@ fn endpoint_and_profile_validation_are_strict_and_canonical() {
RadrootsReticulumProfile::new(
"transport reticulum",
endpoint,
- RadrootsTransportMeshScopeId::local_reticulum(),
+ TargetScope::local_reticulum(),
None,
RadrootsReticulumBehavior::RejectDeliveryAttempts,
)
@@ -250,7 +254,7 @@ fn endpoint_and_profile_validation_are_strict_and_canonical() {
RadrootsReticulumProfile::new(
"",
RadrootsReticulumEndpoint::default(),
- RadrootsTransportMeshScopeId::local_reticulum(),
+ TargetScope::local_reticulum(),
None,
RadrootsReticulumBehavior::RejectDeliveryAttempts,
)
@@ -260,7 +264,7 @@ fn endpoint_and_profile_validation_are_strict_and_canonical() {
let profile = RadrootsReticulumProfile::new(
"transport.reticulum.custom",
RadrootsReticulumEndpoint::default(),
- RadrootsTransportMeshScopeId::local_reticulum(),
+ TargetScope::local_reticulum(),
Some(agent_endpoint),
RadrootsReticulumBehavior::DeferDeliveryPlans,
)
@@ -336,56 +340,36 @@ fn direct_reticulum_delivery_accepts_any_typed_scope_as_inert_metadata() {
}
#[test]
-fn core_transport_trait_reports_reticulum_status_delivery_and_fetch() {
+fn final_transport_spis_report_unavailable_reticulum_source_and_sink() {
let transport = RadrootsReticulumTransport::default();
- let target_set =
- RadrootsTransportTargetSet::new(vec![reticulum_target(RADROOTS_RETICULUM_ENDPOINT_URI)])
- .expect("target set");
- let status = futures::executor::block_on(RadrootsTransport::status(&transport))
- .expect("transport status");
- assert_eq!(status.kind, RadrootsTransportKind::Reticulum);
- assert_eq!(
- status.implementation,
- RadrootsTransportImplementationState::Real
- );
- assert_eq!(
- status.maturity,
- RadrootsTransportCapabilityMaturity::Preview
- );
- assert_eq!(
- status.availability,
- RadrootsTransportCapabilityAvailability::Unavailable
- );
- assert!(!status.usable_for_delivery);
- assert!(!status.capabilities.deliver);
- assert!(!status.capabilities.fetch);
-
- let delivery = futures::executor::block_on(RadrootsTransport::deliver(
+ let target_set = TargetSet::new(vec![reticulum_target(RADROOTS_RETICULUM_ENDPOINT_URI)])
+ .expect("target set");
+ let sink_status =
+ futures::executor::block_on(EventSink::status(&transport)).expect("sink status");
+ assert_eq!(sink_status.transport_id(), TransportId::RETICULUM);
+ assert_eq!(sink_status.maturity(), Maturity::Preview);
+ assert_eq!(sink_status.availability(), Availability::Unavailable);
+ assert!(!sink_status.capabilities().can_deliver());
+
+ let source_status =
+ futures::executor::block_on(EventSource::status(&transport)).expect("source status");
+ assert_eq!(source_status.transport_id(), TransportId::RETICULUM);
+ assert_eq!(source_status.maturity(), Maturity::Preview);
+ assert_eq!(source_status.availability(), Availability::Unavailable);
+ assert!(!source_status.capabilities().can_fetch());
+
+ let fetch = futures::executor::block_on(EventSource::fetch(
&transport,
- RadrootsTransportDeliveryRequest::new(
- "core-delivery",
- reticulum_payload(),
- target_set.clone(),
- RadrootsTransportSatisfactionPolicy::any_accepted(),
+ FetchRequest::new(
+ "core-fetch",
+ target_set,
+ FetchBounds::new(10, 10_000).expect("fetch bounds"),
)
- .expect("delivery request"),
- ))
- .expect("delivery receipt");
- assert_eq!(
- delivery.target_receipts()[0].status,
- RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
- );
-
- let fetch = futures::executor::block_on(RadrootsTransport::fetch(
- &transport,
- RadrootsTransportFetchRequest::new("core-fetch", target_set),
+ .expect("fetch request"),
))
- .expect("fetch receipt");
- assert_eq!(fetch.fetched_count, 0);
- assert_eq!(
- fetch.target_receipts[0].status,
- RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
- );
+ .expect("fetch page");
+ assert!(fetch.events().is_empty());
+ assert_eq!(fetch.target_outcomes().len(), 1);
}
#[test]
@@ -400,10 +384,7 @@ fn reject_delivery_attempts_returns_unavailable_without_success_or_nostr_routing
0
);
for target_receipt in receipt.target_receipts() {
- assert_eq!(
- target_receipt.target.kind(),
- &RadrootsTransportKind::Reticulum
- );
+ assert_eq!(target_receipt.target.kind(), &TransportId::RETICULUM);
assert_eq!(
target_receipt.status,
RadrootsTransportDeliveryTargetStatus::DeferredUntilImplemented
@@ -431,7 +412,7 @@ fn noncanonical_reticulum_targets_are_rejected() {
"reticulum:custom",
] {
assert_eq!(
- RadrootsTransportTarget::new(RadrootsTransportKind::Reticulum, invalid)
+ Target::new(TransportId::RETICULUM, invalid)
.expect_err("noncanonical Reticulum target"),
radroots_transport::RadrootsTransportError::InvalidTargetUri
);
@@ -650,7 +631,7 @@ fn destination_deserialization_revalidates_canonical_identity() {
#[test]
fn destination_rejects_non_reticulum_targets() {
- let local = RadrootsTransportTarget::local("local:memory").expect("local target");
+ let local = Target::new(TransportId::LOCAL, "local:memory").expect("local target");
assert!(ReticulumDestinationV1::from_target(&local).is_err());
}
diff --git a/tools/xtask/src/contract/nip09_reconciliation.rs b/tools/xtask/src/contract/nip09_reconciliation.rs
@@ -233,7 +233,7 @@ const GOVERNED_DEPENDENCY_TABLE_SHA256: [(&str, &str); 7] = [
),
(
TRANSPORT_CARGO_MANIFEST_RELATIVE,
- "7839356e3ba830b758327552bfa6295747d5073ac023f5d2ab47bdd0c6866972",
+ "2ee4667dc2974d91d137ce5fcf0506c0d35163164a533f0f94b8b02907a7e4cf",
),
(
"Cargo.toml#governed-workspace-dependencies",
@@ -9612,7 +9612,7 @@ fn validate_post_core_storage_source(
"radroots_event::trade::SellerReservationAssertionV1",
"radroots_event::trade::TradeMutationEnvelopeV1",
"radroots_event::trade::TradeMutationKindV1",
- "radroots_transport::RadrootsTransportKind",
+ "radroots_transport::TransportId",
"sqlx::Sqlite",
"sqlx::Transaction",
]
@@ -20673,6 +20673,9 @@ name = "radroots_transport"
version = "0.1.0-alpha"
dependencies = [
"futures",
+ "radroots_event",
+ "radroots_identity",
+ "radroots_protocol",
"serde",
"serde_json",
"sha2",
@@ -20683,6 +20686,9 @@ name = "radroots_transport"
version = "0.1.0-alpha"
dependencies = [
"futures",
+ "radroots_event",
+ "radroots_identity",
+ "radroots_protocol",
"serde",
"serde_json",
"sha2",
diff --git a/tools/xtask/src/contract/source_maintenance.rs b/tools/xtask/src/contract/source_maintenance.rs
@@ -3726,9 +3726,9 @@ struct DelegatedAuthoritySpec {
}
const EXECUTABLE_AUTHORITY_AST_SHA256: &str =
- "730fe3bf7d8a1eed342ea58ca1ab6cdad9065cb6fdb1695244ebaeb17168cef2";
+ "b1a7658f47b4561ad816ef65dab47cf68c75de3c459383371319e96e6051d435";
const BOUND_AUTHORITY_SOURCE_AST_SHA256: &str =
- "aaf8433098153cad9105761402d65c78d7f8134c260bc2737c0bbcb032ae9d60";
+ "b0a59506a3a49b86875699a545cd9eec388c4b030b46264f2cb6540aab0b6a8d";
#[derive(Clone, Debug, Serialize)]
struct ExecutableAuthorityIdentity {