lib

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

commit 6f0255c7adb493852bbe57d8dc918f8b7ba0fbe0
parent 377c866126b9bf5121ec4e1858771977490e14e6
Author: triesap <tyson@radroots.org>
Date:   Mon, 27 Jul 2026 10:20:13 +0000

workspace: align public package layout and names

- establish all seventeen governed public package identities at version 0.1.0
- preserve underscore Rust crate paths behind hyphenated Cargo package names
- align contract, coverage, Nix, and architecture checks with the final inventory
- refresh lock and generated governance artifacts without dependency upgrades

Diffstat:
MCargo.lock | 338+++++++++++++++++++++++++++++++++++++++++++------------------------------------
MCargo.toml | 28++++++++++++++++++----------
Mbuild/nix/common.nix | 16++++++++--------
Mcontracts/coverage-profiles.toml | 4++--
Mcontracts/coverage.toml | 27+++++++++++++++++----------
Mcontracts/knowledge/knowledge_event_contract_manifest.v2.json | 4++--
Mcontracts/knowledge/knowledge_event_contract_manifest.v2.sha256 | 2+-
Mcontracts/releases/publish_policy.toml | 10----------
Mcrates/blossom/Cargo.toml | 17++++++++++-------
Dcrates/blossom/README | 23-----------------------
Acrates/blossom/README.md | 23+++++++++++++++++++++++
Mcrates/core/Cargo.toml | 17++++++++++-------
Dcrates/core/README | 21---------------------
Acrates/core/README.md | 21+++++++++++++++++++++
Mcrates/event/Cargo.toml | 17++++++++++-------
Dcrates/event/README | 236-------------------------------------------------------------------------------
Acrates/event/README.md | 236+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/event_codec/Cargo.toml | 17++++++++++-------
Dcrates/event_codec/README | 342-------------------------------------------------------------------------------
Acrates/event_codec/README.md | 342+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/event_store/contracts/source_maintenance_v1.manifest.json | 16++++++++--------
Mcrates/event_store/contracts/source_maintenance_v1.manifest.sha256 | 2+-
Mcrates/event_store/src/generated/source_maintenance_manifest.rs | 4++--
Acrates/geonames/Cargo.toml | 18++++++++++++++++++
Acrates/geonames/README.md | 7+++++++
Acrates/geonames/src/lib.rs | 1+
Mcrates/identity/Cargo.toml | 17++++++++++-------
Dcrates/identity/README | 25-------------------------
Acrates/identity/README.md | 25+++++++++++++++++++++++++
Mcrates/identity/tests/identity.rs | 58++++++++++++++++++++++++----------------------------------
Mcrates/nostr/Cargo.toml | 16+++++++++++-----
Dcrates/nostr/README | 143-------------------------------------------------------------------------------
Acrates/nostr/README.md | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/nostr_connect/Cargo.toml | 17++++++++++-------
Dcrates/nostr_connect/README | 36------------------------------------
Acrates/nostr_connect/README.md | 36++++++++++++++++++++++++++++++++++++
Acrates/protocol/Cargo.toml | 18++++++++++++++++++
Acrates/protocol/README.md | 6++++++
Acrates/protocol/src/lib.rs | 1+
Acrates/secrets/Cargo.toml | 18++++++++++++++++++
Acrates/secrets/README.md | 7+++++++
Acrates/secrets/src/lib.rs | 1+
Acrates/signing/Cargo.toml | 18++++++++++++++++++
Acrates/signing/README.md | 6++++++
Acrates/signing/src/lib.rs | 1+
Acrates/storage/Cargo.toml | 18++++++++++++++++++
Acrates/storage/README.md | 7+++++++
Acrates/storage/src/lib.rs | 1+
Acrates/storage_sqlite/Cargo.toml | 18++++++++++++++++++
Acrates/storage_sqlite/README.md | 7+++++++
Acrates/storage_sqlite/src/lib.rs | 1+
Acrates/sync/Cargo.toml | 18++++++++++++++++++
Acrates/sync/README.md | 6++++++
Acrates/sync/src/lib.rs | 1+
Mcrates/trade/Cargo.toml | 17++++++++++-------
Dcrates/trade/README | 25-------------------------
Acrates/trade/README.md | 25+++++++++++++++++++++++++
Mcrates/transport/Cargo.toml | 15+++++++++------
Dcrates/transport/README | 21---------------------
Acrates/transport/README.md | 21+++++++++++++++++++++
Mcrates/transport_nostr/Cargo.toml | 15+++++++++------
Dcrates/transport_nostr/README | 43-------------------------------------------
Acrates/transport_nostr/README.md | 43+++++++++++++++++++++++++++++++++++++++++++
Mtools/xtask/src/architecture.rs | 32++++++++++++++++++++++++++++++--
Mtools/xtask/src/contract.rs | 51++++++++++++++++++++++++++++++++++++---------------
Mtools/xtask/src/contract/food_availability_projection.rs | 10+++++++---
Mtools/xtask/src/contract/nip09_reconciliation.rs | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mtools/xtask/src/coverage.rs | 14+++++++-------
68 files changed, 1609 insertions(+), 1268 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -4495,17 +4495,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] -name = "radroots_authority" -version = "1.0.0-alpha.1" -dependencies = [ - "radroots_event", - "radroots_nostr", - "serde_json", -] - -[[package]] -name = "radroots_blossom" -version = "1.0.0-alpha.1" +name = "radroots-blossom" +version = "0.1.0" dependencies = [ "hex", "mediatype", @@ -4517,8 +4508,8 @@ dependencies = [ ] [[package]] -name = "radroots_core" -version = "1.0.0-alpha.1" +name = "radroots-core" +version = "0.1.0" dependencies = [ "dto_bindgen", "rust_decimal", @@ -4528,14 +4519,14 @@ dependencies = [ ] [[package]] -name = "radroots_event" -version = "1.0.0-alpha.1" +name = "radroots-event" +version = "0.1.0" dependencies = [ "dto_bindgen", "hex", "jiff-tzdb", - "radroots_blossom", - "radroots_core", + "radroots-blossom", + "radroots-core", "secp256k1", "serde", "serde_json", @@ -4545,18 +4536,157 @@ dependencies = [ ] [[package]] -name = "radroots_event_codec" -version = "1.0.0-alpha.1" +name = "radroots-event-codec" +version = "0.1.0" +dependencies = [ + "hex", + "nostr", + "radroots-blossom", + "radroots-core", + "radroots-event", + "radroots_test_fixtures", + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "radroots-geonames" +version = "0.1.0" + +[[package]] +name = "radroots-identity" +version = "0.1.0" +dependencies = [ + "nostr", + "radroots_protected_store", + "radroots_runtime", + "radroots_runtime_paths", + "radroots_secret_vault", + "secrecy", + "serde", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "tracing", + "zeroize", +] + +[[package]] +name = "radroots-nostr" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "nostr", + "nostr-sdk", + "radroots-blossom", + "radroots-event", + "radroots-event-codec", + "radroots-identity", + "radroots_test_fixtures", + "reqwest", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "radroots-nostr-connect" +version = "0.1.0" dependencies = [ + "nostr", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "url", +] + +[[package]] +name = "radroots-protocol" +version = "0.1.0" + +[[package]] +name = "radroots-secrets" +version = "0.1.0" + +[[package]] +name = "radroots-signing" +version = "0.1.0" + +[[package]] +name = "radroots-storage" +version = "0.1.0" + +[[package]] +name = "radroots-storage-sqlite" +version = "0.1.0" + +[[package]] +name = "radroots-sync" +version = "0.1.0" + +[[package]] +name = "radroots-trade" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "dto_bindgen", + "dto_bindgen_core", "hex", "nostr", - "radroots_blossom", - "radroots_core", - "radroots_event", + "radroots-core", + "radroots-event", + "radroots-event-codec", + "radroots-nostr", + "radroots-transport", + "radroots_authority", + "radroots_event_store", "radroots_test_fixtures", "serde", "serde_json", "sha2", + "sqlx", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "radroots-transport" +version = "0.1.0" +dependencies = [ + "futures", + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "radroots-transport-nostr" +version = "0.1.0" +dependencies = [ + "futures", + "nostr", + "radroots-event", + "radroots-nostr", + "radroots-transport", + "radroots_event_store", + "radroots_outbox", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "url", +] + +[[package]] +name = "radroots_authority" +version = "1.0.0-alpha.1" +dependencies = [ + "radroots-event", + "radroots-nostr", + "serde_json", ] [[package]] @@ -4576,10 +4706,10 @@ dependencies = [ "getrandom 0.2.17", "hex", "nostr", - "radroots_blossom", - "radroots_event", - "radroots_event_codec", - "radroots_transport", + "radroots-blossom", + "radroots-event", + "radroots-event-codec", + "radroots-transport", "serde", "serde_json", "sha2", @@ -4607,24 +4737,6 @@ dependencies = [ ] [[package]] -name = "radroots_identity" -version = "1.0.0-alpha.1" -dependencies = [ - "nostr", - "radroots_protected_store", - "radroots_runtime", - "radroots_runtime_paths", - "radroots_secret_vault", - "secrecy", - "serde", - "serde_json", - "tempfile", - "thiserror 1.0.69", - "tracing", - "zeroize", -] - -[[package]] name = "radroots_log" version = "1.0.0-alpha.1" dependencies = [ @@ -4640,7 +4752,7 @@ dependencies = [ name = "radroots_mesh" version = "1.0.0-alpha.1" dependencies = [ - "radroots_transport", + "radroots-transport", "serde", "serde_json", ] @@ -4649,9 +4761,9 @@ dependencies = [ name = "radroots_mesh_agent_client" version = "1.0.0-alpha.1" dependencies = [ + "radroots-transport", "radroots_mesh", "radroots_mesh_agent_proto", - "radroots_transport", "serde", "serde_json", ] @@ -4670,11 +4782,11 @@ dependencies = [ "directories", "futures", "hex", - "radroots_event", - "radroots_event_codec", - "radroots_identity", + "radroots-event", + "radroots-event-codec", + "radroots-identity", + "radroots-nostr", "radroots_log", - "radroots_nostr", "radroots_nostr_accounts", "radroots_nostr_signer", "radroots_runtime_paths", @@ -4688,29 +4800,10 @@ dependencies = [ ] [[package]] -name = "radroots_nostr" -version = "1.0.0-alpha.1" -dependencies = [ - "base64 0.22.1", - "nostr", - "nostr-sdk", - "radroots_blossom", - "radroots_event", - "radroots_event_codec", - "radroots_identity", - "radroots_test_fixtures", - "reqwest", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", -] - -[[package]] name = "radroots_nostr_accounts" version = "1.0.0-alpha.1" dependencies = [ - "radroots_identity", + "radroots-identity", "radroots_nostr_signer", "radroots_nostrdb", "radroots_protected_store", @@ -4724,24 +4817,12 @@ dependencies = [ ] [[package]] -name = "radroots_nostr_connect" -version = "1.0.0-alpha.1" -dependencies = [ - "nostr", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "url", -] - -[[package]] name = "radroots_nostr_runtime" version = "1.0.0-alpha.1" dependencies = [ "futures", "nostr", - "radroots_nostr", + "radroots-nostr", "thiserror 1.0.69", "tokio", ] @@ -4752,9 +4833,9 @@ version = "1.0.0-alpha.1" dependencies = [ "hex", "nostr", - "radroots_identity", - "radroots_nostr", - "radroots_nostr_connect", + "radroots-identity", + "radroots-nostr", + "radroots-nostr-connect", "radroots_runtime", "radroots_sql_core", "serde", @@ -4774,7 +4855,7 @@ dependencies = [ "hex", "nostr", "nostrdb", - "radroots_nostr", + "radroots-nostr", "radroots_nostr_runtime", "serde_json", "tempfile", @@ -4787,10 +4868,10 @@ name = "radroots_outbox" version = "1.0.0-alpha.1" dependencies = [ "hex", - "radroots_event", + "radroots-event", + "radroots-nostr", + "radroots-transport", "radroots_event_store", - "radroots_nostr", - "radroots_transport", "serde", "serde_json", "sha2", @@ -4849,10 +4930,10 @@ dependencies = [ "base64 0.22.1", "hex", "nostr", - "radroots_core", - "radroots_event", - "radroots_event_codec", - "radroots_nostr", + "radroots-core", + "radroots-event", + "radroots-event-codec", + "radroots-nostr", "radroots_replica_schema", "radroots_replica_store", "radroots_sql_core", @@ -4872,12 +4953,12 @@ dependencies = [ "clap", "config", "getrandom 0.2.17", - "radroots_event", + "radroots-event", + "radroots-transport", "radroots_log", "radroots_protected_store", "radroots_runtime_paths", "radroots_secret_vault", - "radroots_transport", "radroots_transport_reticulum", "serde", "serde_json", @@ -5066,32 +5147,7 @@ dependencies = [ name = "radroots_test_fixtures" version = "1.0.0-alpha.1" dependencies = [ - "radroots_event", -] - -[[package]] -name = "radroots_trade" -version = "1.0.0-alpha.1" -dependencies = [ - "base64 0.22.1", - "dto_bindgen", - "dto_bindgen_core", - "hex", - "nostr", - "radroots_authority", - "radroots_core", - "radroots_event", - "radroots_event_codec", - "radroots_event_store", - "radroots_nostr", - "radroots_test_fixtures", - "radroots_transport", - "serde", - "serde_json", - "sha2", - "sqlx", - "thiserror 1.0.69", - "tokio", + "radroots-event", ] [[package]] @@ -5112,8 +5168,8 @@ dependencies = [ "base64 0.22.1", "bincode", "futures", - "radroots_event", - "radroots_trade", + "radroots-event", + "radroots-trade", "radroots_trade_sp1_guest", "serde", "serde_json", @@ -5125,38 +5181,10 @@ dependencies = [ ] [[package]] -name = "radroots_transport" -version = "1.0.0-alpha.1" -dependencies = [ - "futures", - "serde", - "serde_json", - "sha2", -] - -[[package]] -name = "radroots_transport_nostr" -version = "1.0.0-alpha.1" -dependencies = [ - "futures", - "nostr", - "radroots_event", - "radroots_event_store", - "radroots_nostr", - "radroots_outbox", - "radroots_transport", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "url", -] - -[[package]] name = "radroots_transport_publish_protocol" version = "1.0.0-alpha.1" dependencies = [ - "radroots_transport", + "radroots-transport", "serde", "serde_json", ] @@ -5166,7 +5194,7 @@ name = "radroots_transport_reticulum" version = "1.0.0-alpha.1" dependencies = [ "futures", - "radroots_transport", + "radroots-transport", "serde", "serde_json", ] @@ -9004,7 +9032,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "radroots_event_codec", + "radroots-event-codec", "radroots_protocol_contract_v1", "semver", "serde", diff --git a/Cargo.toml b/Cargo.toml @@ -15,6 +15,13 @@ members = [ "crates/nostr", "crates/nostr_accounts", "crates/nostr_connect", + "crates/protocol", + "crates/signing", + "crates/secrets", + "crates/storage", + "crates/storage_sqlite", + "crates/sync", + "crates/geonames", "crates/nostr_signer", "crates/nostrdb", "crates/nostr_runtime", @@ -71,6 +78,7 @@ readme = "README.md" [workspace.lints.rust] unsafe_code = "forbid" +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } [workspace.lints.rustdoc] broken_intra_doc_links = "deny" @@ -83,19 +91,19 @@ unimplemented = "deny" [workspace.dependencies] dto_bindgen = { version = "0.1.0" } dto_bindgen_core = { version = "0.1.0" } -radroots_core = { path = "crates/core", version = "=1.0.0-alpha.1", default-features = false } -radroots_event = { path = "crates/event", version = "=1.0.0-alpha.1", default-features = false } +radroots_core = { package = "radroots-core", path = "crates/core", version = "=0.1.0", default-features = false } +radroots_event = { package = "radroots-event", path = "crates/event", version = "=0.1.0", default-features = false } radroots_event_store = { path = "crates/event_store", version = "=1.0.0-alpha.1", default-features = false } -radroots_event_codec = { path = "crates/event_codec", version = "=1.0.0-alpha.1", default-features = false } +radroots_event_codec = { package = "radroots-event-codec", path = "crates/event_codec", version = "=0.1.0", default-features = false } radroots_event_index = { path = "crates/event_index", version = "=1.0.0-alpha.1", default-features = false } radroots_authority = { path = "crates/authority", version = "=1.0.0-alpha.1", default-features = false } -radroots_blossom = { path = "crates/blossom", version = "=1.0.0-alpha.1", default-features = false } +radroots_blossom = { package = "radroots-blossom", path = "crates/blossom", version = "=0.1.0", default-features = false } radroots_geocoder = { path = "crates/geocoder", version = "=1.0.0-alpha.1" } -radroots_identity = { path = "crates/identity", version = "=1.0.0-alpha.1", default-features = false } +radroots_identity = { package = "radroots-identity", path = "crates/identity", version = "=0.1.0", default-features = false } radroots_runtime_store = { path = "crates/runtime_store", version = "=1.0.0-alpha.1", default-features = false } -radroots_nostr = { path = "crates/nostr", version = "=1.0.0-alpha.1", default-features = false } +radroots_nostr = { package = "radroots-nostr", path = "crates/nostr", version = "=0.1.0", default-features = false } radroots_nostr_accounts = { path = "crates/nostr_accounts", version = "=1.0.0-alpha.1", default-features = false } -radroots_nostr_connect = { path = "crates/nostr_connect", version = "=1.0.0-alpha.1", default-features = false } +radroots_nostr_connect = { package = "radroots-nostr-connect", path = "crates/nostr_connect", version = "=0.1.0", default-features = false } radroots_nostr_signer = { path = "crates/nostr_signer", version = "=1.0.0-alpha.1", default-features = false } radroots_nostrdb = { path = "crates/nostrdb", version = "=1.0.0-alpha.1", default-features = false } radroots_runtime = { path = "crates/runtime", version = "=1.0.0-alpha.1", default-features = false } @@ -108,7 +116,7 @@ radroots_nostr_runtime = { path = "crates/nostr_runtime", version = "=1.0.0-alph radroots_outbox = { path = "crates/outbox", version = "=1.0.0-alpha.1", default-features = false } radroots_protocol_contract_v1 = { path = "crates/protocol_contract_v1", version = "=1.0.0-alpha.1", default-features = false } radroots_transport_publish_protocol = { path = "crates/transport_publish_protocol", version = "=1.0.0-alpha.1", default-features = false } -radroots_transport_nostr = { path = "crates/transport_nostr", version = "=1.0.0-alpha.1", default-features = false } +radroots_transport_nostr = { package = "radroots-transport-nostr", path = "crates/transport_nostr", version = "=0.1.0", default-features = false } radroots_transport_reticulum = { path = "crates/transport_reticulum", version = "=1.0.0-alpha.1", default-features = false } radroots_simplex_agent_proto = { path = "crates/simplex_agent_proto", version = "=1.0.0-alpha.1", default-features = false } radroots_simplex_agent_runtime = { path = "crates/simplex_agent_runtime", version = "=1.0.0-alpha.1", default-features = false } @@ -123,8 +131,8 @@ radroots_test_fixtures = { path = "crates/test_fixtures", version = "=1.0.0-alph radroots_replica_schema = { path = "crates/replica_schema", version = "=1.0.0-alpha.1", default-features = false } radroots_replica_sync = { path = "crates/replica_sync", version = "=1.0.0-alpha.1", default-features = false } radroots_replica_store = { path = "crates/replica_store", version = "=1.0.0-alpha.1", default-features = false } -radroots_trade = { path = "crates/trade", version = "=1.0.0-alpha.1", default-features = false } -radroots_transport = { path = "crates/transport", version = "=1.0.0-alpha.1", default-features = false } +radroots_trade = { package = "radroots-trade", path = "crates/trade", version = "=0.1.0", default-features = false } +radroots_transport = { package = "radroots-transport", path = "crates/transport", version = "=0.1.0", default-features = false } radroots_mesh = { path = "crates/mesh", version = "=1.0.0-alpha.1", default-features = false } radroots_mesh_agent_client = { path = "crates/mesh_agent_client", version = "=1.0.0-alpha.1", default-features = false } radroots_mesh_agent_proto = { path = "crates/mesh_agent_proto", version = "=1.0.0-alpha.1", default-features = false } diff --git a/build/nix/common.nix b/build/nix/common.nix @@ -94,16 +94,16 @@ let releaseRuntimeInputs = coverageRuntimeInputs; coreContractCrates = [ "xtask" - "radroots_blossom" - "radroots_core" - "radroots_event" - "radroots_trade" - "radroots_identity" + "radroots-blossom" + "radroots-core" + "radroots-event" + "radroots-trade" + "radroots-identity" "radroots_replica_schema" - "radroots_event_codec" + "radroots-event-codec" "radroots_event_store" - "radroots_nostr" - "radroots_nostr_connect" + "radroots-nostr" + "radroots-nostr-connect" "radroots_nostr_signer" ]; coreContractCargoArgs = diff --git a/contracts/coverage-profiles.toml b/contracts/coverage-profiles.toml @@ -8,7 +8,7 @@ no_default_features = true features = [] test_threads = 1 -[profiles.crates."radroots_event_codec"] +[profiles.crates."radroots-event-codec"] no_default_features = false features = ["serde_json", "nostr"] test_threads = 1 @@ -18,7 +18,7 @@ no_default_features = true features = ["std"] test_threads = 1 -[profiles.crates."radroots_nostr"] +[profiles.crates."radroots-nostr"] no_default_features = false features = ["blossom"] test_threads = 1 diff --git a/contracts/coverage.toml b/contracts/coverage.toml @@ -32,23 +32,24 @@ reason = "branch coverage is not applicable while the crate has no measured bran [required] crates = [ "radroots_authority", - "radroots_blossom", - "radroots_core", + "radroots-blossom", + "radroots-core", "radroots_event_store", - "radroots_event", - "radroots_event_codec", + "radroots-event", + "radroots-event-codec", "radroots_event_index", "radroots_geocoder", - "radroots_identity", + "radroots-geonames", + "radroots-identity", "radroots_runtime_store", "radroots_log", "radroots_mesh", "radroots_mesh_agent_client", "radroots_mesh_agent_proto", "radroots_net", - "radroots_nostr", + "radroots-nostr", "radroots_nostr_accounts", - "radroots_nostr_connect", + "radroots-nostr-connect", "radroots_nostrdb", "radroots_nostr_runtime", "radroots_nostr_signer", @@ -56,7 +57,13 @@ crates = [ "radroots_protocol_contract_v1", "radroots_protected_store", "radroots_transport_publish_protocol", - "radroots_transport_nostr", + "radroots-protocol", + "radroots-secrets", + "radroots-signing", + "radroots-storage", + "radroots-storage-sqlite", + "radroots-sync", + "radroots-transport-nostr", "radroots_transport_reticulum", "radroots_replica_store", "radroots_replica_schema", @@ -70,7 +77,7 @@ crates = [ "radroots_trade_sp1_host", "radroots_sql_core", "radroots_test_fixtures", - "radroots_trade", - "radroots_transport", + "radroots-trade", + "radroots-transport", "xtask", ] diff --git a/contracts/knowledge/knowledge_event_contract_manifest.v2.json b/contracts/knowledge/knowledge_event_contract_manifest.v2.json @@ -1,8 +1,8 @@ { "schema_version": 2, "registry_version": 7, - "radroots_event_version": "1.0.0-alpha.1", - "radroots_event_codec_version": "1.0.0-alpha.1", + "radroots_event_version": "0.1.0", + "radroots_event_codec_version": "0.1.0", "contract_count": 11, "contracts": [ { diff --git a/contracts/knowledge/knowledge_event_contract_manifest.v2.sha256 b/contracts/knowledge/knowledge_event_contract_manifest.v2.sha256 @@ -1 +1 @@ -d17a44375f52be93b0205be0f0541664b0310ee75106a160e7690b9b245a5bab +4c7143b04c4c2113bfc7c992daf727ca7eed860fda107a1afd394a0063207d59 diff --git a/contracts/releases/publish_policy.toml b/contracts/releases/publish_policy.toml @@ -51,19 +51,12 @@ external_packages = ["radroots-sdk", "radroots"] [workspace_classification] private = [ "radroots_authority", - "radroots_blossom", - "radroots_core", - "radroots_event", - "radroots_event_codec", "radroots_event_index", "radroots_event_store", "radroots_geocoder", - "radroots_identity", "radroots_log", "radroots_net", - "radroots_nostr", "radroots_nostr_accounts", - "radroots_nostr_connect", "radroots_nostr_runtime", "radroots_nostr_signer", "radroots_outbox", @@ -76,9 +69,6 @@ private = [ "radroots_runtime_store", "radroots_secret_vault", "radroots_sql_core", - "radroots_trade", - "radroots_transport", - "radroots_transport_nostr", "radroots_transport_publish_protocol", ] build_codegen = ["xtask"] diff --git a/crates/blossom/Cargo.toml b/crates/blossom/Cargo.toml @@ -1,16 +1,19 @@ [package] -name = "radroots_blossom" +name = "radroots-blossom" publish = false -version = "1.0.0-alpha.1" +version = "0.1.0" edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] +authors.workspace = true rust-version.workspace = true license.workspace = true description = "Portable Blossom protocol primitives for Radroots" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots_blossom" -readme = "README" +documentation = "https://docs.rs/radroots-blossom" +readme = "README.md" + +[lib] +name = "radroots_blossom" [features] default = ["serde"] @@ -28,5 +31,5 @@ url_nostd = { workspace = true } hex = { workspace = true } serde_json = { workspace = true, features = ["std"] } -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } +[lints] +workspace = true diff --git a/crates/blossom/README b/crates/blossom/README @@ -1,23 +0,0 @@ -# radroots_blossom - -`radroots_blossom` provides portable, runtime-independent primitives for -Blossom blob hashes, root hash paths, blob URLs, BUD-02 descriptors, -Radroots-approved byte verification, and pure BUD-11 authorization claims. - -The crate is `no_std + alloc`, performs no HTTP requests, and does not depend on -Nostr event types. Structural Blossom validity is kept separate from the -stricter Radroots reference policy: public HTTP descriptors remain parseable, -but only HTTPS and loopback HTTP references can advance to an approved state. -The byte-verified descriptor state proves local descriptor-to-byte agreement; -it is not an upload receipt, and HTTP-capable runtimes must gate successful -BUD-02 upload completion separately. BUD-11 support similarly stops at typed -claim construction and endpoint validation; signing and canonical -`Authorization: Nostr` encoding live behind the `radroots_nostr` `blossom` -feature, and kind `24242` is never a relay-publication event. - -Protocol behavior is pinned to Blossom commit -`b5bd2801d1763aa635fc8fea7a76597e0eb18990`: - -- BUD-01: <https://github.com/hzrd149/blossom/blob/b5bd2801d1763aa635fc8fea7a76597e0eb18990/buds/01.md> -- BUD-02: <https://github.com/hzrd149/blossom/blob/b5bd2801d1763aa635fc8fea7a76597e0eb18990/buds/02.md> -- BUD-11: <https://github.com/hzrd149/blossom/blob/b5bd2801d1763aa635fc8fea7a76597e0eb18990/buds/11.md> diff --git a/crates/blossom/README.md b/crates/blossom/README.md @@ -0,0 +1,23 @@ +# radroots-blossom + +`radroots_blossom` provides portable, runtime-independent primitives for +Blossom blob hashes, root hash paths, blob URLs, BUD-02 descriptors, +Radroots-approved byte verification, and pure BUD-11 authorization claims. + +The crate is `no_std + alloc`, performs no HTTP requests, and does not depend on +Nostr event types. Structural Blossom validity is kept separate from the +stricter Radroots reference policy: public HTTP descriptors remain parseable, +but only HTTPS and loopback HTTP references can advance to an approved state. +The byte-verified descriptor state proves local descriptor-to-byte agreement; +it is not an upload receipt, and HTTP-capable runtimes must gate successful +BUD-02 upload completion separately. BUD-11 support similarly stops at typed +claim construction and endpoint validation; signing and canonical +`Authorization: Nostr` encoding live behind the `radroots_nostr` `blossom` +feature, and kind `24242` is never a relay-publication event. + +Protocol behavior is pinned to Blossom commit +`b5bd2801d1763aa635fc8fea7a76597e0eb18990`: + +- BUD-01: <https://github.com/hzrd149/blossom/blob/b5bd2801d1763aa635fc8fea7a76597e0eb18990/buds/01.md> +- BUD-02: <https://github.com/hzrd149/blossom/blob/b5bd2801d1763aa635fc8fea7a76597e0eb18990/buds/02.md> +- BUD-11: <https://github.com/hzrd149/blossom/blob/b5bd2801d1763aa635fc8fea7a76597e0eb18990/buds/11.md> diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml @@ -1,16 +1,19 @@ [package] -name = "radroots_core" +name = "radroots-core" publish = false -version = "1.0.0-alpha.1" +version = "0.1.0" edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] +authors.workspace = true rust-version.workspace = true license.workspace = true description = "Core value model for Radroots" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots_core" -readme = "README" +documentation = "https://docs.rs/radroots-core" +readme = "README.md" + +[lib] +name = "radroots_core" [features] default = ["std", "serde"] @@ -31,5 +34,5 @@ serde = { workspace = true, default-features = false, features = [ serde_json = { workspace = true } rust_decimal = { workspace = true } -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } +[lints] +workspace = true diff --git a/crates/core/README b/crates/core/README @@ -1,21 +0,0 @@ -# radroots_core - -This is the README for `radroots_core`, which provides core value types and -unit semantics for the `radroots` core libraries. - -## Overview - - * stable domain value types for currency, money, quantity, quantity pricing, - percent, discounts, and decimals; - * unit kinds, dimensions, parsing, and deterministic conversion helpers; - * portable core semantics for both `std` and `no_std` builds; - * optional integration with `serde` for serialization. - -## Copyright - -Except as otherwise noted, all files in the `radroots_core` distribution are - - Copyright (c) 2025 Tyson Lupul - -For information on usage and redistribution, and for a DISCLAIMER OF ALL -WARRANTIES, see LICENSE included in the `radroots_core` distribution. diff --git a/crates/core/README.md b/crates/core/README.md @@ -0,0 +1,21 @@ +# radroots-core + +This is the README for `radroots_core`, which provides core value types and +unit semantics for the `radroots` core libraries. + +## Overview + + * stable domain value types for currency, money, quantity, quantity pricing, + percent, discounts, and decimals; + * unit kinds, dimensions, parsing, and deterministic conversion helpers; + * portable core semantics for both `std` and `no_std` builds; + * optional integration with `serde` for serialization. + +## Copyright + +Except as otherwise noted, all files in the `radroots_core` distribution are + + Copyright (c) 2025 Tyson Lupul + +For information on usage and redistribution, and for a DISCLAIMER OF ALL +WARRANTIES, see LICENSE included in the `radroots_core` distribution. diff --git a/crates/event/Cargo.toml b/crates/event/Cargo.toml @@ -1,16 +1,19 @@ [package] -name = "radroots_event" +name = "radroots-event" publish = false -version = "1.0.0-alpha.1" +version = "0.1.0" edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] +authors.workspace = true rust-version.workspace = true license.workspace = true description = "Domain event model for Radroots" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots_event" -readme = "README" +documentation = "https://docs.rs/radroots-event" +readme = "README.md" + +[lib] +name = "radroots_event" [features] default = ["std", "serde"] @@ -55,5 +58,5 @@ serde = { workspace = true, default-features = false, features = [ "derive", ] } -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } +[lints] +workspace = true diff --git a/crates/event/README b/crates/event/README @@ -1,236 +0,0 @@ -# radroots_event - -This is the README for `radroots_event`, which provides typed `radroots` event -models, kinds, and tag conventions for the `radroots` core libraries. - -## Overview - - * typed content modules for accounts, app data, comments, coops, documents, - farms, farm workspaces, farm CRDT changes, farm files, groups, auth events, - jobs, lists, messages, posts, profiles, reactions, trades, and related - domains; - * shared event references, pointers, and kind and tag definitions used across - event-processing code; - * portable event model semantics for both `std` and `no_std` builds; - * optional integration with `serde` for serialization. - -The Profile module exposes the exclusive strict authored model. It requires a -non-whitespace, control-free name; its media fields accept only image-typed, -byte-verified Blossom descriptors; and its NIP-05 identifier type validates -syntax without making a network identity claim. The legacy read projection is -not serializable or exported as a DTO. Tolerant reads use -`RadrootsInboundProfileMetadata` from `radroots_event_codec`. - -The post module keeps the legacy mutable `RadrootsPost` model as a compatibility -read projection only. New root kind-1 publication uses private-field -`RadrootsAuthoredUpdate`, `RadrootsAuthoredPhotoUpdate`, and -`RadrootsAuthoredAsk` types. Photo and optional Ask media require nonzero -dimensions, bounded alt text, approved same-digest fallbacks, and an -image-typed byte-verified Blossom descriptor. That descriptor state is not an -upload receipt; BUD-02 completion remains a runtime prerequisite before -signing. - -The shared relay-hint module exposes `RadrootsNostrRelayHint` for NIP-10 Reply -and NIP-22 Comment references. It is a byte-stable subset of WebSocket URLs: -exact lowercase `ws://` or `wss://`, visible ASCII, canonical lowercase DNS or -four-octet IPv4 or bracketed pure-hex RFC 5952 IPv6, canonical optional port -`1..65535`, and RFC 3986 path-abempty/query syntax with uppercase `%HH` -escapes. It rejects IDNA and percent-encoded hosts, legacy IPv4, userinfo, -fragments, controls, backslashes, and normalization-dependent spellings. - -The Reply module exposes opaque `RadrootsNip10ReplyReference` and -`RadrootsAuthoredNip10Reply` types for strict direct and nested kind-1 -authoring. References carry a validated event id, referenced author, and -optional shared relay hint; construction emits either one marked root or -distinct marked root and parent references. Relay-hint syntax is not a -wire-size claim: Reply construction separately enforces the 4,096-byte -tag-element ceiling. These values prove syntax and authored shape, not target -existence, target kind, signature, author, or relay availability. - -The Comment module implements the strict Radroots -[NIP-22](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/22.md) -kind-`1111` profile. `RadrootsAuthoredNip22Comment` and its opaque event-root, -address-root, parent, position, and root-kind values admit only kind-`30402`, -kind-`31922`, or kind-`31923` event or address roots. External `I`/`i` -references and kind-`1` roots are unsupported. The authored model has no Serde -construction path. - -Canonical authoring emits `E,K,P,e,k,p` for a top-level event root, -`A,K,P,a,e,k,p` for a top-level address root, or `E,K,P,e,k,p` and -`A,K,P,e,k,p` for nested event and address roots. Event references always -contain four elements, including an empty relay position when no hint exists -and a final author hint. Address and participant references contain two -elements plus an optional relay; an address root's current-revision `e` tag has -no author hint. A direct `k` repeats the root kind and a nested `k` is `1111`. - -The event-contract registry v7 classifies `radroots.social.comment.v1` as -`TypedOnly` and `AdmissionOnly`; serialized registry versions `1` through `6` -are stale. Generic kind-`1111` draft and signing paths cannot claim the typed -contract. The Comment resource profile limits content to 131072 UTF-8 bytes, -tags to 1024, total tag elements including names to 4096, each element to 4096 -bytes, aggregate tag bytes to 131072, and compact signed wire JSON to 262144 -bytes. `RadrootsInboundNip22CommentProjection` and -`RadrootsAdmittedNip22CommentEvent` provide verified inbound projection and -admission. The three governed Comment operations are -`social.comment.build_authored_draft`, -`social.comment.project_verified_event`, and -`social.comment.verify_and_admit_event`; they and the canonical self-contained -114-case corpus are owned by `radroots_event_codec` and -`contracts/conformance`. - -The Deletion module implements the effect-free request layer of -[NIP-09](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/09.md). -`RadrootsAuthoredNip09DeletionRequest` requires at least one validated event-id -or replaceable/addressable coordinate target. Event targets carry a -caller-asserted kind advisory in `0..=65535`; this is metadata rather than -proof of the target event. Address coordinates accept NIP-01 replaceable kinds -`0`, `3`, and `10000..=19999` only with an empty identifier, and addressable -kinds `30000..=39999` with an opaque identifier. - -Construction canonicalizes event targets by event id, address targets by -coordinate, and unique derived kind advisories in ascending order. Duplicate -normalized targets are rejected. The request enforces the shared content, tag, -element, aggregate-tag-byte, and compact signed-event budgets before it can -reach signing. It represents only a kind-`5` protocol request: it does not -retrieve a target, prove same-author authority, compute an address cutoff, -suppress content, mutate a store, or make a deletion request itself deletable. - -The immutable registry-v7 inventory and addressable-feed-v1 head functions are -historical protocol inputs to event-store reconciliation v1. The explicit -`event_contract_registry_v7`, `validate_event_contract_registry_v7`, -`event_head_candidate_for_nip01_event_v1`, and `select_event_head_v1` -entrypoints must retain their v7/v1 behavior when a later current registry or -head algorithm is introduced. - -Kind `30402` has a raw, allocation-free marker partition before profile-specific -tag-shape validation. Presence of `radroots:price_unit` or `radroots:quantity` selects -the focused FoodAvailability marker family; presence of -`radroots:primary_bin`, `radroots:bin`, or `radroots:price` selects the richer -Operational Listing marker family. Focused-only, operational-only, marker-free -generic NIP-99, and mixed-marker events produce -`RadrootsClassifiedListingPartition::{FocusedFoodAvailability, -OperationalListing, GenericNip99, Ambiguous}` respectively. A malformed -one-element tag still contributes its raw first name, and marker matching is -case-sensitive. `classify_classified_listing_tags` and the borrowed-slice -variant inspect neither kind, tag values, nor tag arity. - -The FoodAvailability module provides `RadrootsFoodAvailabilityDetails` and -checked identifier, text, publication timestamp, price, currency, unit, -quantity, status, image-dimension, and image values. Content contains at least -one scalar outside Unicode whitespace and U+001C through U+001F, and is bounded -to 131072 UTF-8 bytes. Identifiers reject whitespace plus Unicode control and -format characters; title, summary, and location use trimmed, nonempty, -control-free text bounded to 4096 UTF-8 bytes. Food units are closed to `g`, -`kg`, `lb`, `oz`, `each`, `dozen`, `bunch`, `punnet`, `bag`, and `basket`. -Price permits zero; quantity is strictly positive and uses the price unit. -Image dimensions use two nonzero canonical `u32` decimal values in -`WIDTHxHEIGHT` form. Details accept at most 64 images, require unique image URLs -and Blossom digests, and accept only `RadrootsAuthoredImage` values that already -prove local descriptor-to-byte agreement. Details retain nonzero `published_at` -and can validate that it is not later than a supplied `created_at`. - -These checked details are the exclusive typed input to the focused -`radroots.food.availability.v1` authoring contract. The event-contract registry -classifies that kind-`30402` profile as `TypedOnly` and `AdmissionOnly`, so a -generic unsigned classified listing cannot claim the focused contract. The -details themselves remain domain values rather than signed events; -`radroots_event_codec` owns deterministic wire construction, verified inbound -projection and admission, and strict revision comparison. - -An authored image proves local descriptor-to-byte agreement only. Successful -BUD-02 upload completion and any required raster, retrieval, or availability -checks remain runtime responsibilities before signing. Neither this domain -module nor the registry signs, publishes, replicates, or retrieves an event. - -The calendar module keeps three different states explicit for NIP-52 kinds -`31922`, `31923`, `31924`, and `31925`: the complete structural event envelope, -a tolerant baseline NIP-52 projection, and a strict Radroots-admitted -projection. The authored types are `RadrootsAuthoredCalendarDateEvent`, -`RadrootsAuthoredCalendarTimeEvent`, `RadrootsAuthoredCalendar`, and -`RadrootsAuthoredCalendarEventRsvp`; their inbound counterparts use matching -`RadrootsParsedNip52*` and `RadrootsAdmitted*` types. Envelope construction -validates structure, not a matching event id or Schnorr signature. Callers must -perform those cryptographic checks independently and keep any parsed or -admitted value bound to the verified envelope and expected kind. - -Baseline projections retain the pinned NIP-52 common fields: repeated -locations, participants, categories, absolute-URI references, kind-`31924` -calendar-inclusion requests, and deprecated `name` compatibility data in -addition to `d`, `title`, description, summary, image, and geohash. Date -events use semantic Gregorian dates and retain observed uppercase-`D` tags as -uninterpreted extensions. Time events validate unsigned timestamps, exact IANA -time-zone identifiers, and at least one in-range `D` day while tolerating the -NIP's non-mandatory start-day and complete-coverage forms. An absent `end_tzid` -falls back to `start_tzid` when one is present. - -Strict authoring and admission add canonical metadata and bounded resource -rules. Authored common fields include repeated locations, participants, -categories, references, and calendar-inclusion requests; deprecated `name` is -not authored. Strict date events reject uppercase `D`, while strict time -events derive or admit only the complete, ascending sequence of UTC-day -indices and cover at most 366 days. - -Kind `31924` has one calendar-specific contract. It is not decoded or authored -through either generic NIP-51 list codec. Its NIP-52 detailed description is -plain-text event content and remains distinct from the optional NIP-51 -`description` tag. It requires one `d` and one `title`, permits optional NIP-51 -`description` and `image` tags, and contains zero or more `a` references to -kind `31922` or `31923` events. Each reference may have its own relay hint, and -an empty calendar collection is valid. - -Kind `31925` has exactly one required `d`, one `a` event coordinate, and one -`status`; `e`, `fb`, and `p` are optional singletons. The `a`, `e`, and `p` -references preserve independent optional relay hints. The `p` tag is an event -author hint without participant-role semantics, and strict admission requires -it to match the author in the `a` coordinate. An inbound declined RSVP may -retain an observed `fb` for diagnostics but exposes no effective free/busy -state. Authored declined RSVPs cannot carry `fb`. - -Strict collection and RSVP identifiers use exactly 22 unpadded base64url -characters representing 128 bits. The type proves only syntax; the runtime is -responsible for generating a fresh value for each new identity. Parsing or -admission does not prove reference existence, revision correspondence, RSVP -authority, upload completion, or network availability. - -Inbound images begin as unverified absolute URIs. Strict admission, including -kind-`31924` collection images, requires a structural Blossom hash-path URL but -makes no byte or network claim. Authored calendar images require the shared -`image/*`, byte-verified Blossom descriptor. That state is not an upload -receipt: a runtime must require successful BUD-02 upload completion and a -bounded retrievability check before signing or publishing a media-bearing -event. - -## Field Event Boundary - -`radroots_event` includes the public event-layer models needed by Field-style -farming operations: - - * workspace manifests for discovering the farm group, relay set, media servers, - and supported event kinds; - * CRDT change envelopes for operation documents such as tasks, work sessions, - harvest records, and approvals; - * farm file metadata events for media attached to farm documents; - * NIP-42 relay auth and NIP-98 HTTP auth payload models; - * NIP-29 group metadata, member lists, roles, invites, joins, leaves, and user - operations for the supported `9000`, `9001`, `9002`, `9005`, `9007`, `9008`, - `9009`, `9021`, `9022`, `39000`, `39001`, `39002`, and `39003` subset. - -The NIP-29 group surface uses bare metadata marker tags such as `private`, -`restricted`, `hidden`, and `closed`, `supported_kinds` declarations, and -`code` tags for invite and join flows. User management and moderation events -preserve optional reason content. LiveKit room metadata and live participant -state are not part of this crate's current group event subset. - -Task records, work sessions, harvest records, approvals, and similar Field -business objects are CRDT document semantics carried by -`RadrootsFarmCrdtChange`. They are not separate `rr-rs` event families and this -crate does not enforce private Field workflow authorization. - -## Copyright - -Except as otherwise noted, all files in the `radroots_event` distribution are - - Copyright (c) 2025 Tyson Lupul - -For information on usage and redistribution, and for a DISCLAIMER OF ALL -WARRANTIES, see LICENSE included in the `radroots_event` distribution. diff --git a/crates/event/README.md b/crates/event/README.md @@ -0,0 +1,236 @@ +# radroots-event + +This is the README for `radroots_event`, which provides typed `radroots` event +models, kinds, and tag conventions for the `radroots` core libraries. + +## Overview + + * typed content modules for accounts, app data, comments, coops, documents, + farms, farm workspaces, farm CRDT changes, farm files, groups, auth events, + jobs, lists, messages, posts, profiles, reactions, trades, and related + domains; + * shared event references, pointers, and kind and tag definitions used across + event-processing code; + * portable event model semantics for both `std` and `no_std` builds; + * optional integration with `serde` for serialization. + +The Profile module exposes the exclusive strict authored model. It requires a +non-whitespace, control-free name; its media fields accept only image-typed, +byte-verified Blossom descriptors; and its NIP-05 identifier type validates +syntax without making a network identity claim. The legacy read projection is +not serializable or exported as a DTO. Tolerant reads use +`RadrootsInboundProfileMetadata` from `radroots_event_codec`. + +The post module keeps the legacy mutable `RadrootsPost` model as a compatibility +read projection only. New root kind-1 publication uses private-field +`RadrootsAuthoredUpdate`, `RadrootsAuthoredPhotoUpdate`, and +`RadrootsAuthoredAsk` types. Photo and optional Ask media require nonzero +dimensions, bounded alt text, approved same-digest fallbacks, and an +image-typed byte-verified Blossom descriptor. That descriptor state is not an +upload receipt; BUD-02 completion remains a runtime prerequisite before +signing. + +The shared relay-hint module exposes `RadrootsNostrRelayHint` for NIP-10 Reply +and NIP-22 Comment references. It is a byte-stable subset of WebSocket URLs: +exact lowercase `ws://` or `wss://`, visible ASCII, canonical lowercase DNS or +four-octet IPv4 or bracketed pure-hex RFC 5952 IPv6, canonical optional port +`1..65535`, and RFC 3986 path-abempty/query syntax with uppercase `%HH` +escapes. It rejects IDNA and percent-encoded hosts, legacy IPv4, userinfo, +fragments, controls, backslashes, and normalization-dependent spellings. + +The Reply module exposes opaque `RadrootsNip10ReplyReference` and +`RadrootsAuthoredNip10Reply` types for strict direct and nested kind-1 +authoring. References carry a validated event id, referenced author, and +optional shared relay hint; construction emits either one marked root or +distinct marked root and parent references. Relay-hint syntax is not a +wire-size claim: Reply construction separately enforces the 4,096-byte +tag-element ceiling. These values prove syntax and authored shape, not target +existence, target kind, signature, author, or relay availability. + +The Comment module implements the strict Radroots +[NIP-22](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/22.md) +kind-`1111` profile. `RadrootsAuthoredNip22Comment` and its opaque event-root, +address-root, parent, position, and root-kind values admit only kind-`30402`, +kind-`31922`, or kind-`31923` event or address roots. External `I`/`i` +references and kind-`1` roots are unsupported. The authored model has no Serde +construction path. + +Canonical authoring emits `E,K,P,e,k,p` for a top-level event root, +`A,K,P,a,e,k,p` for a top-level address root, or `E,K,P,e,k,p` and +`A,K,P,e,k,p` for nested event and address roots. Event references always +contain four elements, including an empty relay position when no hint exists +and a final author hint. Address and participant references contain two +elements plus an optional relay; an address root's current-revision `e` tag has +no author hint. A direct `k` repeats the root kind and a nested `k` is `1111`. + +The event-contract registry v7 classifies `radroots.social.comment.v1` as +`TypedOnly` and `AdmissionOnly`; serialized registry versions `1` through `6` +are stale. Generic kind-`1111` draft and signing paths cannot claim the typed +contract. The Comment resource profile limits content to 131072 UTF-8 bytes, +tags to 1024, total tag elements including names to 4096, each element to 4096 +bytes, aggregate tag bytes to 131072, and compact signed wire JSON to 262144 +bytes. `RadrootsInboundNip22CommentProjection` and +`RadrootsAdmittedNip22CommentEvent` provide verified inbound projection and +admission. The three governed Comment operations are +`social.comment.build_authored_draft`, +`social.comment.project_verified_event`, and +`social.comment.verify_and_admit_event`; they and the canonical self-contained +114-case corpus are owned by `radroots_event_codec` and +`contracts/conformance`. + +The Deletion module implements the effect-free request layer of +[NIP-09](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/09.md). +`RadrootsAuthoredNip09DeletionRequest` requires at least one validated event-id +or replaceable/addressable coordinate target. Event targets carry a +caller-asserted kind advisory in `0..=65535`; this is metadata rather than +proof of the target event. Address coordinates accept NIP-01 replaceable kinds +`0`, `3`, and `10000..=19999` only with an empty identifier, and addressable +kinds `30000..=39999` with an opaque identifier. + +Construction canonicalizes event targets by event id, address targets by +coordinate, and unique derived kind advisories in ascending order. Duplicate +normalized targets are rejected. The request enforces the shared content, tag, +element, aggregate-tag-byte, and compact signed-event budgets before it can +reach signing. It represents only a kind-`5` protocol request: it does not +retrieve a target, prove same-author authority, compute an address cutoff, +suppress content, mutate a store, or make a deletion request itself deletable. + +The immutable registry-v7 inventory and addressable-feed-v1 head functions are +historical protocol inputs to event-store reconciliation v1. The explicit +`event_contract_registry_v7`, `validate_event_contract_registry_v7`, +`event_head_candidate_for_nip01_event_v1`, and `select_event_head_v1` +entrypoints must retain their v7/v1 behavior when a later current registry or +head algorithm is introduced. + +Kind `30402` has a raw, allocation-free marker partition before profile-specific +tag-shape validation. Presence of `radroots:price_unit` or `radroots:quantity` selects +the focused FoodAvailability marker family; presence of +`radroots:primary_bin`, `radroots:bin`, or `radroots:price` selects the richer +Operational Listing marker family. Focused-only, operational-only, marker-free +generic NIP-99, and mixed-marker events produce +`RadrootsClassifiedListingPartition::{FocusedFoodAvailability, +OperationalListing, GenericNip99, Ambiguous}` respectively. A malformed +one-element tag still contributes its raw first name, and marker matching is +case-sensitive. `classify_classified_listing_tags` and the borrowed-slice +variant inspect neither kind, tag values, nor tag arity. + +The FoodAvailability module provides `RadrootsFoodAvailabilityDetails` and +checked identifier, text, publication timestamp, price, currency, unit, +quantity, status, image-dimension, and image values. Content contains at least +one scalar outside Unicode whitespace and U+001C through U+001F, and is bounded +to 131072 UTF-8 bytes. Identifiers reject whitespace plus Unicode control and +format characters; title, summary, and location use trimmed, nonempty, +control-free text bounded to 4096 UTF-8 bytes. Food units are closed to `g`, +`kg`, `lb`, `oz`, `each`, `dozen`, `bunch`, `punnet`, `bag`, and `basket`. +Price permits zero; quantity is strictly positive and uses the price unit. +Image dimensions use two nonzero canonical `u32` decimal values in +`WIDTHxHEIGHT` form. Details accept at most 64 images, require unique image URLs +and Blossom digests, and accept only `RadrootsAuthoredImage` values that already +prove local descriptor-to-byte agreement. Details retain nonzero `published_at` +and can validate that it is not later than a supplied `created_at`. + +These checked details are the exclusive typed input to the focused +`radroots.food.availability.v1` authoring contract. The event-contract registry +classifies that kind-`30402` profile as `TypedOnly` and `AdmissionOnly`, so a +generic unsigned classified listing cannot claim the focused contract. The +details themselves remain domain values rather than signed events; +`radroots_event_codec` owns deterministic wire construction, verified inbound +projection and admission, and strict revision comparison. + +An authored image proves local descriptor-to-byte agreement only. Successful +BUD-02 upload completion and any required raster, retrieval, or availability +checks remain runtime responsibilities before signing. Neither this domain +module nor the registry signs, publishes, replicates, or retrieves an event. + +The calendar module keeps three different states explicit for NIP-52 kinds +`31922`, `31923`, `31924`, and `31925`: the complete structural event envelope, +a tolerant baseline NIP-52 projection, and a strict Radroots-admitted +projection. The authored types are `RadrootsAuthoredCalendarDateEvent`, +`RadrootsAuthoredCalendarTimeEvent`, `RadrootsAuthoredCalendar`, and +`RadrootsAuthoredCalendarEventRsvp`; their inbound counterparts use matching +`RadrootsParsedNip52*` and `RadrootsAdmitted*` types. Envelope construction +validates structure, not a matching event id or Schnorr signature. Callers must +perform those cryptographic checks independently and keep any parsed or +admitted value bound to the verified envelope and expected kind. + +Baseline projections retain the pinned NIP-52 common fields: repeated +locations, participants, categories, absolute-URI references, kind-`31924` +calendar-inclusion requests, and deprecated `name` compatibility data in +addition to `d`, `title`, description, summary, image, and geohash. Date +events use semantic Gregorian dates and retain observed uppercase-`D` tags as +uninterpreted extensions. Time events validate unsigned timestamps, exact IANA +time-zone identifiers, and at least one in-range `D` day while tolerating the +NIP's non-mandatory start-day and complete-coverage forms. An absent `end_tzid` +falls back to `start_tzid` when one is present. + +Strict authoring and admission add canonical metadata and bounded resource +rules. Authored common fields include repeated locations, participants, +categories, references, and calendar-inclusion requests; deprecated `name` is +not authored. Strict date events reject uppercase `D`, while strict time +events derive or admit only the complete, ascending sequence of UTC-day +indices and cover at most 366 days. + +Kind `31924` has one calendar-specific contract. It is not decoded or authored +through either generic NIP-51 list codec. Its NIP-52 detailed description is +plain-text event content and remains distinct from the optional NIP-51 +`description` tag. It requires one `d` and one `title`, permits optional NIP-51 +`description` and `image` tags, and contains zero or more `a` references to +kind `31922` or `31923` events. Each reference may have its own relay hint, and +an empty calendar collection is valid. + +Kind `31925` has exactly one required `d`, one `a` event coordinate, and one +`status`; `e`, `fb`, and `p` are optional singletons. The `a`, `e`, and `p` +references preserve independent optional relay hints. The `p` tag is an event +author hint without participant-role semantics, and strict admission requires +it to match the author in the `a` coordinate. An inbound declined RSVP may +retain an observed `fb` for diagnostics but exposes no effective free/busy +state. Authored declined RSVPs cannot carry `fb`. + +Strict collection and RSVP identifiers use exactly 22 unpadded base64url +characters representing 128 bits. The type proves only syntax; the runtime is +responsible for generating a fresh value for each new identity. Parsing or +admission does not prove reference existence, revision correspondence, RSVP +authority, upload completion, or network availability. + +Inbound images begin as unverified absolute URIs. Strict admission, including +kind-`31924` collection images, requires a structural Blossom hash-path URL but +makes no byte or network claim. Authored calendar images require the shared +`image/*`, byte-verified Blossom descriptor. That state is not an upload +receipt: a runtime must require successful BUD-02 upload completion and a +bounded retrievability check before signing or publishing a media-bearing +event. + +## Field Event Boundary + +`radroots_event` includes the public event-layer models needed by Field-style +farming operations: + + * workspace manifests for discovering the farm group, relay set, media servers, + and supported event kinds; + * CRDT change envelopes for operation documents such as tasks, work sessions, + harvest records, and approvals; + * farm file metadata events for media attached to farm documents; + * NIP-42 relay auth and NIP-98 HTTP auth payload models; + * NIP-29 group metadata, member lists, roles, invites, joins, leaves, and user + operations for the supported `9000`, `9001`, `9002`, `9005`, `9007`, `9008`, + `9009`, `9021`, `9022`, `39000`, `39001`, `39002`, and `39003` subset. + +The NIP-29 group surface uses bare metadata marker tags such as `private`, +`restricted`, `hidden`, and `closed`, `supported_kinds` declarations, and +`code` tags for invite and join flows. User management and moderation events +preserve optional reason content. LiveKit room metadata and live participant +state are not part of this crate's current group event subset. + +Task records, work sessions, harvest records, approvals, and similar Field +business objects are CRDT document semantics carried by +`RadrootsFarmCrdtChange`. They are not separate `rr-rs` event families and this +crate does not enforce private Field workflow authorization. + +## Copyright + +Except as otherwise noted, all files in the `radroots_event` distribution are + + Copyright (c) 2025 Tyson Lupul + +For information on usage and redistribution, and for a DISCLAIMER OF ALL +WARRANTIES, see LICENSE included in the `radroots_event` distribution. diff --git a/crates/event_codec/Cargo.toml b/crates/event_codec/Cargo.toml @@ -1,16 +1,19 @@ [package] -name = "radroots_event_codec" +name = "radroots-event-codec" publish = false -version = "1.0.0-alpha.1" +version = "0.1.0" edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] +authors.workspace = true rust-version.workspace = true license.workspace = true description = "Event codec layer for Radroots" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots_event_codec" -readme = "README" +documentation = "https://docs.rs/radroots-event-codec" +readme = "README.md" + +[lib] +name = "radroots_event_codec" [features] default = ["std"] @@ -46,5 +49,5 @@ radroots_blossom = { workspace = true, default-features = false, features = [ radroots_test_fixtures = { workspace = true } serde_json = { workspace = true, features = ["std"] } -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } +[lints] +workspace = true diff --git a/crates/event_codec/README b/crates/event_codec/README @@ -1,342 +0,0 @@ -# radroots_event_codec - -This is the README for `radroots_event_codec`, which provides canonical event -codecs and tag builders for the `radroots` core libraries. - -## Overview - - * canonical decoders for event content, tags, job envelopes, profiles, Field - event envelopes, NIP-29 group events, and wire representations; - * tag builder helpers for the same event families exposed by - `radroots_event`; - * parsed view and error types for codec-driven validation and routing; - * optional `serde_json` and `radroots_nostr` integration for JSON and wire - interop. - -With the optional `serde_json` feature, the Profile codec exposes separate -strict authored and tolerant inbound operations. Strict authoring emits -bounded, deterministic kind-`0` metadata with empty tags and image-typed, -byte-verified Blossom media references. Tolerant inbound parsing retains raw -and residual fields and never upgrades observed media or NIP-05 syntax into -network verification. Its content parser accepts only bounded kind-`0` content -from an event whose identifier and signature the caller has already verified. -With `serde_json,nostr`, `profile::admission::verify_and_admit_profile_event` -provides that combined boundary and returns metadata bound to a non-forgeable -signature-verified envelope. Standard tagless kind-`0` events are accepted; no -Radroots marker is required. - -General NIP-01 identifier and Schnorr verification lives in `verification` and -is independent of the optional `knowledge` decoder. The `nostr` feature exposes -`RadrootsSignatureVerifiedEvent` and rejects event kinds above `u16::MAX` -instead of truncating them. - -With `serde_json`, `admission::admit_verified_event` is the current central -contract boundary over an already verified event. It preserves typed admitted -Profile, root Post, Reply, Comment, DeletionRequest, and FoodAvailability -values, while other registered events must pass complete registry shape -validation and return `ContractValidated`. Kind `1` is tested as a root Post -before the exact thread-excluded candidate may be promoted to Reply. Kind -`30402` is partitioned as focused Food, Operational Listing, generic NIP-99, -or ambiguous before any profile validation; a valid excluded Operational -Listing falls back to the registry, while generic and mixed-marker candidates -remain distinct failures. - -`admission::admit_verified_event_registry_v7` is the immutable historical -admission graph used by event-store reconciliation v1. It returns the closed -registry-v7 admitted, unsupported, invalid, or internal-defect decision needed -for persisted facts without depending on richer current admission enums. -Later registry revisions must add a new versioned entrypoint rather than -changing this behavior. Neither admission operation accepts an unverified -envelope, signs or publishes events, selects event heads, evaluates deletion -effects, or mutates storage. - -The post codec exposes deterministic authored wire builders and a separate -verified-event projection. Update emits no profile tags, PhotoUpdate emits -strict ordered NIP-92 `imeta`, and Ask emits one exact `t=radroots-ask` marker -before optional strict media. The former mutable post encoder and generic tag -builder are removed. Inbound projection preserves ordinary kind-1 reads, -excludes any `e`-tagged reply before product classification, and applies Ask, -PhotoUpdate, Update precedence. Unknown media fields and repeatable fallbacks -remain ordered; malformed media becomes diagnostic Update unless a valid Ask -marker takes precedence. Structural inbound URLs remain unverified and no -network retrieval occurs. - -The Reply codec deterministically emits strict marked direct and nested NIP-10 -wire parts from `RadrootsAuthoredNip10Reply`. Its inbound boundary projects -only signature-and-id verified kind-1 envelopes, accepts marked and deprecated -positional thread anchors, retains valid supplemental citations, and reports -malformed advisory relay, author, participant, and citation metadata through -ordered diagnostics. Root and parent ambiguity or malformed hard anchors -remain projection failures. - -Authored and inbound NIP-10 Reply and NIP-22 Comment relay metadata uses the -shared `RadrootsNostrRelayHint` profile: exact lowercase `ws://` or `wss://`, -visible ASCII, canonical lowercase DNS or four-octet IPv4 or bracketed -pure-hex RFC 5952 IPv6, canonical optional port `1..65535`, and RFC 3986 -path-abempty/query syntax with uppercase `%HH` escapes. IDNA or -percent-encoded hosts, legacy IPv4, userinfo, fragments, controls, -backslashes, and normalization-dependent forms are rejected. Inbound -projection ignores an invalid advisory hint while preserving its exact raw tag -in ordered diagnostics. Relay syntax remains independent from each event -profile's 4,096-byte tag-element budget. - -The Comment codec implements the strict Radroots -[NIP-22](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/22.md) -kind-`1111` profile for event or address roots of kind `30402`, `31922`, or -`31923`. It rejects external `I`/`i` authority and kind-`1` roots. -`authored_nip22_comment_to_wire_parts` emits only canonical order: -`E,K,P,e,k,p` for a top-level event, `A,K,P,a,e,k,p` for a top-level -address, and `E,K,P,e,k,p` or `A,K,P,e,k,p` for a nested event or address. -Event `E` and parent or direct `e` references always have four elements with a -relay slot and author hint. Address and participant references have two -elements plus an optional relay; a top-level address Comment's revision `e` -has no author hint. Direct `k` repeats the root kind and nested `k` is exactly -`1111`. - -`project_verified_nip22_comment_event` accepts only a -`RadrootsSignatureVerifiedEvent`. It resolves the `E`/`A`, `K`, `P`, `e`/`a`, -`k`, and selected `p` authority independently of tag order and treats invalid -cardinality, shapes, noncanonical kinds, unsupported roots, coordinate -conflicts, and valid-but-conflicting author hints as hard errors. It preserves -exact raw tags, supplemental unknown and `q` tags, and distinct unselected -lowercase `p` mentions. Inbound NIP-22 reference event IDs, public keys, and -coordinate-author hex accept either ASCII hex case; typed values normalize to -lowercase while raw tags retain the original spelling. Malformed advisory relay -and participant metadata produce ordered diagnostics without weakening the -required authority. -`verify_and_admit_nip22_comment_event` first verifies the NIP-01 id and Schnorr -signature, then binds that envelope to -`RadrootsInboundNip22CommentProjection` in -`RadrootsAdmittedNip22CommentEvent`; it does not verify any referenced event or -relay. - -The exact Comment operation namespace is -`social.comment.build_authored_draft`, -`social.comment.project_verified_event`, and -`social.comment.verify_and_admit_event`. Registry v7 makes -`radroots.social.comment.v1` `TypedOnly` for authoring and `AdmissionOnly` for -matching; registry versions `1` through `6` are stale. Content is limited to -131072 UTF-8 bytes, a Comment to 1024 tags, all tags to 4096 elements including -tag names, each element to 4096 bytes, aggregate tag bytes to 131072, and -compact signed wire JSON to 262144 bytes. - -The canonical 114-case self-contained Comment corpus is -`contracts/conformance/vectors/comment/verified_profile.v1.json`; the packaged -fixture is byte-identical. Projection and admission cases contain fixed signed -event JSON, while authored cases contain explicit checked inputs and complete -wire expectations. The runner consumes these records directly and covers -valid shapes, diagnostics, exact and over-limit budgets, stable error -precedence, and NIP-01 admission without secret keys, mutation recipes, or -ambient state. - -The Deletion codec implements the request and pure suppression-evaluation -boundaries of -[NIP-09](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/09.md). -`authored_nip09_deletion_request_to_wire_parts` emits canonical two-element -`e` targets, then `a` targets, then the complete unique ascending set of `k` -kind advisories. `project_verified_nip09_deletion_request_event` accepts only -an id-and-signature-verified kind-`5` envelope. It retains exact raw tags, -deduplicates normalized targets with first-seen provenance, sorts the typed -target views, and treats malformed `e` or `a` targets as hard errors. Unknown -tags and trailing target elements remain observable. Advisory `k` shape, -numeric spelling, duplicates, and address-only conflicts produce stable -ordered diagnostics; event-target kind correspondence remains unprovable at -this boundary. - -`verify_and_admit_nip09_deletion_request_event` binds the projection to the -verified request envelope. It does not authorize or apply a deletion, look up -a target, compare authors, compute an address cutoff, suppress an event, or -mutate storage. - -`evaluate_nip09_suppression` is a separate pure operation over one -`RadrootsSignatureVerifiedEvent` candidate and admitted deletion requests. It -returns an explicit `RadrootsNip09SuppressionDecision` with canonical evidence -without changing any input or touching a store. Kind `5` is immune. Other -candidates require the request author to equal the candidate author. An exact -event-id target applies independently of timestamps; a canonical address -target applies through the inclusive maximum qualifying request timestamp, so -a later replacement is not suppressed. Advisory `k` values do not affect the -result, and request order cannot affect the decision. - -The reconciliation-v1 entrypoints -`project_verified_nip09_deletion_request_event_v1`, -`admit_verified_nip09_deletion_request_event_v1`, and -`evaluate_nip09_suppression_v1` freeze the exact request and suppression -behavior consumed by event-store schema version 2. Current compatibility -entrypoints delegate to these functions; a future algorithm revision must add -new versioned functions. - -The exact operation namespace is -`social.deletion_request.build_authored_draft`, -`social.deletion_request.project_verified_event`, -`social.deletion_request.verify_and_admit_event`, and -`social.deletion_request.evaluate_suppression`. Registry v7 makes -`radroots.social.deletion_request.v1` `TypedOnly` for authoring and -`AdmissionOnly` for matching, with generic kind-`5` authoring and publication -reserved. - -The canonical self-contained 80-case corpus is -`contracts/conformance/vectors/deletion/verified_profile.v1.json`; the -packaged fixture is byte-identical. Signed inputs are complete compact event -JSON, expected projections contain no effect-authority output, and executable -cases cover canonical authoring, tolerant verified projection, diagnostics, -exact and over-limit resource budgets, precedence, and admission without -shipping keys, seeds, generators, or mutation recipes. - -The separate -`contracts/conformance/vectors/deletion/suppression.v1.json` corpus and its -byte-identical packaged fixture execute the evaluator's same-author, direct -event, inclusive address-cutoff, later-replacement, pre-target request, -kind-`5` immunity, and deterministic evidence behavior. These vectors govern a -decision value only and claim no persistent store effect. - -The FoodAvailability codec owns four boundaries for the focused -`radroots.food.availability.v1` kind-`30402` profile. Strict details plus -`created_at` produce bounded unsigned wire parts whose tag order is exactly -`d`, `title`, `summary`, `published_at`, `location`, `price`, -`radroots:price_unit`, optional `radroots:quantity`, `status`, and repeated -`image`. Content and decoded tags remain bounded, and compact signed-event size -is checked after exact JSON escaping. Authored images already prove local -Blossom descriptor-to-byte agreement, but not upload completion or network -availability. - -Verified inbound projection partitions raw tag names before validation. -Focused-only candidates are projected, Operational Listing and marker-free -generic NIP-99 candidates are explicit exclusions, and mixed-marker candidates -fail as ambiguous. Focused core fields are required; decimal and currency -spellings are normalized within their wire bounds; optional unowned NIP-99 -tags are ignored; and tags purporting to add commerce, fulfillment, -provenance, or operational capabilities fail closed. Image observations remain -ordered structural HTTP(S) data. At most 64 are retained, with stable ordered -diagnostics for overflow, shape, URL, dimensions, duplicate URL, and duplicate -Blossom digest. An image diagnostic does not invalidate an otherwise complete -focused core and never upgrades observed media to an authored typestate. - -`verify_and_admit_food_availability_event` first verifies the NIP-01 id and -Schnorr signature, then binds either the focused projection or an explicit -non-focused exclusion to that verified envelope. Revision validation accepts -two independently verified events, requires both to satisfy the exact authored -wire profile, preserves kind, author, `d`, and `published_at`, and applies the -NIP-01 newer-event rule: later `created_at`, then lower event id at equal time. -Successful combined verification and admission uses the optional `nostr` -feature; without it, signature verification returns the stable unavailable -error rather than admitting an event. The packaged conformance runner enables -`serde_json,nostr` for signed JSON event cases. -These codecs do not sign, publish, replicate, upload, retrieve, or provide -client behavior. - -The NIP-52 calendar codecs expose a deliberate three-stage boundary for kinds -`31922`, `31923`, `31924`, and `31925`: - -1. bounded structural wire or envelope parsing preserves the complete event; -2. the kind-specific date event, time event, calendar collection, or RSVP - parser produces a tolerant `RadrootsParsedNip52*` projection and rejects a - mismatched kind; -3. the corresponding `admit_radroots_*` operation applies the stricter - Radroots profile and returns a non-interchangeable admitted type. - -Structural envelope construction, the baseline parsers, and the admission -functions do not verify a Schnorr signature. Before treating inbound data as -accepted, callers must recompute and compare its NIP-01 id, verify its -signature against the author, use the parser for its expected kind, and keep -the typed result bound to that verified envelope. The calendar -`*_parsed_from_event` convenience functions remain structural wrappers; their -names do not imply cryptographic verification. - -The baseline parser projects repeated NIP-52 `location`, participant `p`, -category `t`, absolute-URI reference `r`, and kind-`31924` inclusion-request -`a` tags, plus deprecated `name` compatibility data. It accepts arbitrary -absolute image URIs. Kind `31922` requires semantic Gregorian dates and retains -uppercase-`D` tags as uninterpreted extensions. Kind `31923` requires at least -one in-range `D` day and exact IANA time-zone identifiers. NIP-52 makes -multi-day coverage a `SHOULD`, so the baseline requires neither the start-day -index nor complete coverage; duplicate, unordered, and leading-zero numeric -observations are additionally preserved as parser tolerance and are never -canonical authored output. An omitted -`end_tzid` uses `start_tzid` when present. - -Strict date admission rejects every uppercase-`D` extension. Strict time -admission requires canonical decimal timestamps and the exact ascending -uppercase-`D` sequence for the exclusive interval, bounded to 366 covered -days. Authored codecs derive that sequence and emit unsigned -`RadrootsNip01EventWireParts`; signing and transport stay at the owning runtime -boundary. - -Kind `31924` is handled only by the calendar-specific codec. Generic NIP-51 -taxonomy may recognize it as a calendar list, but the generic list-set decoder -and authoring path and the broader generic list codec reject it. Calendar collection content is the bounded -plain-text NIP-52 detailed description; it is not substituted from or collapsed -into the optional NIP-51 `description` tag. The parser requires exactly one -`d` and `title`, accepts optional singleton `description` and `image` tags, and -accepts zero or more exact `a` references to kind `31922` or `31923` events. -An empty collection is valid, and each event reference preserves its own -optional relay hint. Singleton collection tags have exactly two elements; an -`a` tag has exactly two elements plus an optional relay element. - -Kind `31925` has bounded optional note content and exact singleton -cardinality: required `d`, `a`, and `status`, with optional `e`, `fb`, and `p`. -The `a`, `e`, and `p` values preserve independent optional relay hints. The -`p` tag is parsed as an event-author hint without participant-role semantics; -strict admission requires it to match the author in the `a` coordinate. -`d`, `status`, and `fb` tags have exactly two elements, while `a`, `e`, and `p` -have exactly two elements plus an optional relay element. -Tolerant parsing preserves `fb` observed on a declined RSVP, but parsed and -admitted values expose no effective free/busy state for it. Authored declined -RSVPs never emit `fb`. - -Strict collection and RSVP authoring and admission require a canonical -22-character unpadded base64url 128-bit identifier and canonical reference -spellings. Identifier syntax does not establish runtime uniqueness. Parsing -valid identifiers, event ids, author keys, coordinates, and relay hints does -not establish reference existence, revision correspondence, RSVP authority, -or relay availability. - -Strict inbound admission, including kind-`31924` collection images, upgrades -an image only to a structural Blossom hash-path URL. It does not prove -reference approval, bytes, upload completion, or retrievability. Strict -authored image inputs are approved, byte-verified Blossom `image/*` -descriptors, but are still not upload receipts. A publishing runtime must -require successful BUD-02 completion and a bounded retrievability check before -signing or publishing media-bearing wire parts. - -## Field Event Codecs - -The Field codec surface validates the public Nostr event substrate exposed by -`radroots_event`: - - * workspace manifests use kind `30078`, JSON content, a schema marker, `d` and - `h` routing tags, owner tags, relay references, and Field-supported kind - declarations; - * CRDT changes use kind `78`, JSON content, `h`, `d`, `a`, optional author `p`, - and semantic `t` tags, with base64url change payload validation; - * farm files use kind `1063`, NIP-94-compatible metadata tags, a workspace - address, a farm group id, an owner document tag, lowercase SHA-256 hashes, - and optional caption text as content; - * NIP-42 relay auth and NIP-98 HTTP auth events require empty content and the - auth tags required by their protocols; NIP-98 `u`, `method`, and `payload` - tags are parsed as singleton security tags and duplicate occurrences fail - closed; - * NIP-29 group codecs preserve the protocol distinction between `h`-routed - group operations and `d`-routed addressable group state for the supported - `9000`, `9001`, `9002`, `9005`, `9007`, `9008`, `9009`, `9021`, `9022`, - `39000`, `39001`, `39002`, and `39003` subset. - -These codecs validate event shape, routing tags, hashes, and payload encoding. -They do not validate private Field task, work-session, harvest, approval, or -authorization semantics; those remain application and CRDT document concerns. -The group codecs use bare metadata marker tags such as `private`, `restricted`, -`hidden`, and `closed`, `supported_kinds` declarations, and `code` tags for -invite and join flows. They preserve optional reason content on user management -and moderation events. LiveKit room metadata and live participant state are -deferred. SDK-owned wasm bindings expose deterministic JSON tag builders for the -same Field and NIP-29 families. - -## Copyright - -Except as otherwise noted, all files in the `radroots_event_codec` -distribution are - - Copyright (c) 2025 Tyson Lupul - -For information on usage and redistribution, and for a DISCLAIMER OF ALL -WARRANTIES, see LICENSE included in the `radroots_event_codec` distribution. diff --git a/crates/event_codec/README.md b/crates/event_codec/README.md @@ -0,0 +1,342 @@ +# radroots-event-codec + +This is the README for `radroots_event_codec`, which provides canonical event +codecs and tag builders for the `radroots` core libraries. + +## Overview + + * canonical decoders for event content, tags, job envelopes, profiles, Field + event envelopes, NIP-29 group events, and wire representations; + * tag builder helpers for the same event families exposed by + `radroots_event`; + * parsed view and error types for codec-driven validation and routing; + * optional `serde_json` and `radroots_nostr` integration for JSON and wire + interop. + +With the optional `serde_json` feature, the Profile codec exposes separate +strict authored and tolerant inbound operations. Strict authoring emits +bounded, deterministic kind-`0` metadata with empty tags and image-typed, +byte-verified Blossom media references. Tolerant inbound parsing retains raw +and residual fields and never upgrades observed media or NIP-05 syntax into +network verification. Its content parser accepts only bounded kind-`0` content +from an event whose identifier and signature the caller has already verified. +With `serde_json,nostr`, `profile::admission::verify_and_admit_profile_event` +provides that combined boundary and returns metadata bound to a non-forgeable +signature-verified envelope. Standard tagless kind-`0` events are accepted; no +Radroots marker is required. + +General NIP-01 identifier and Schnorr verification lives in `verification` and +is independent of the optional `knowledge` decoder. The `nostr` feature exposes +`RadrootsSignatureVerifiedEvent` and rejects event kinds above `u16::MAX` +instead of truncating them. + +With `serde_json`, `admission::admit_verified_event` is the current central +contract boundary over an already verified event. It preserves typed admitted +Profile, root Post, Reply, Comment, DeletionRequest, and FoodAvailability +values, while other registered events must pass complete registry shape +validation and return `ContractValidated`. Kind `1` is tested as a root Post +before the exact thread-excluded candidate may be promoted to Reply. Kind +`30402` is partitioned as focused Food, Operational Listing, generic NIP-99, +or ambiguous before any profile validation; a valid excluded Operational +Listing falls back to the registry, while generic and mixed-marker candidates +remain distinct failures. + +`admission::admit_verified_event_registry_v7` is the immutable historical +admission graph used by event-store reconciliation v1. It returns the closed +registry-v7 admitted, unsupported, invalid, or internal-defect decision needed +for persisted facts without depending on richer current admission enums. +Later registry revisions must add a new versioned entrypoint rather than +changing this behavior. Neither admission operation accepts an unverified +envelope, signs or publishes events, selects event heads, evaluates deletion +effects, or mutates storage. + +The post codec exposes deterministic authored wire builders and a separate +verified-event projection. Update emits no profile tags, PhotoUpdate emits +strict ordered NIP-92 `imeta`, and Ask emits one exact `t=radroots-ask` marker +before optional strict media. The former mutable post encoder and generic tag +builder are removed. Inbound projection preserves ordinary kind-1 reads, +excludes any `e`-tagged reply before product classification, and applies Ask, +PhotoUpdate, Update precedence. Unknown media fields and repeatable fallbacks +remain ordered; malformed media becomes diagnostic Update unless a valid Ask +marker takes precedence. Structural inbound URLs remain unverified and no +network retrieval occurs. + +The Reply codec deterministically emits strict marked direct and nested NIP-10 +wire parts from `RadrootsAuthoredNip10Reply`. Its inbound boundary projects +only signature-and-id verified kind-1 envelopes, accepts marked and deprecated +positional thread anchors, retains valid supplemental citations, and reports +malformed advisory relay, author, participant, and citation metadata through +ordered diagnostics. Root and parent ambiguity or malformed hard anchors +remain projection failures. + +Authored and inbound NIP-10 Reply and NIP-22 Comment relay metadata uses the +shared `RadrootsNostrRelayHint` profile: exact lowercase `ws://` or `wss://`, +visible ASCII, canonical lowercase DNS or four-octet IPv4 or bracketed +pure-hex RFC 5952 IPv6, canonical optional port `1..65535`, and RFC 3986 +path-abempty/query syntax with uppercase `%HH` escapes. IDNA or +percent-encoded hosts, legacy IPv4, userinfo, fragments, controls, +backslashes, and normalization-dependent forms are rejected. Inbound +projection ignores an invalid advisory hint while preserving its exact raw tag +in ordered diagnostics. Relay syntax remains independent from each event +profile's 4,096-byte tag-element budget. + +The Comment codec implements the strict Radroots +[NIP-22](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/22.md) +kind-`1111` profile for event or address roots of kind `30402`, `31922`, or +`31923`. It rejects external `I`/`i` authority and kind-`1` roots. +`authored_nip22_comment_to_wire_parts` emits only canonical order: +`E,K,P,e,k,p` for a top-level event, `A,K,P,a,e,k,p` for a top-level +address, and `E,K,P,e,k,p` or `A,K,P,e,k,p` for a nested event or address. +Event `E` and parent or direct `e` references always have four elements with a +relay slot and author hint. Address and participant references have two +elements plus an optional relay; a top-level address Comment's revision `e` +has no author hint. Direct `k` repeats the root kind and nested `k` is exactly +`1111`. + +`project_verified_nip22_comment_event` accepts only a +`RadrootsSignatureVerifiedEvent`. It resolves the `E`/`A`, `K`, `P`, `e`/`a`, +`k`, and selected `p` authority independently of tag order and treats invalid +cardinality, shapes, noncanonical kinds, unsupported roots, coordinate +conflicts, and valid-but-conflicting author hints as hard errors. It preserves +exact raw tags, supplemental unknown and `q` tags, and distinct unselected +lowercase `p` mentions. Inbound NIP-22 reference event IDs, public keys, and +coordinate-author hex accept either ASCII hex case; typed values normalize to +lowercase while raw tags retain the original spelling. Malformed advisory relay +and participant metadata produce ordered diagnostics without weakening the +required authority. +`verify_and_admit_nip22_comment_event` first verifies the NIP-01 id and Schnorr +signature, then binds that envelope to +`RadrootsInboundNip22CommentProjection` in +`RadrootsAdmittedNip22CommentEvent`; it does not verify any referenced event or +relay. + +The exact Comment operation namespace is +`social.comment.build_authored_draft`, +`social.comment.project_verified_event`, and +`social.comment.verify_and_admit_event`. Registry v7 makes +`radroots.social.comment.v1` `TypedOnly` for authoring and `AdmissionOnly` for +matching; registry versions `1` through `6` are stale. Content is limited to +131072 UTF-8 bytes, a Comment to 1024 tags, all tags to 4096 elements including +tag names, each element to 4096 bytes, aggregate tag bytes to 131072, and +compact signed wire JSON to 262144 bytes. + +The canonical 114-case self-contained Comment corpus is +`contracts/conformance/vectors/comment/verified_profile.v1.json`; the packaged +fixture is byte-identical. Projection and admission cases contain fixed signed +event JSON, while authored cases contain explicit checked inputs and complete +wire expectations. The runner consumes these records directly and covers +valid shapes, diagnostics, exact and over-limit budgets, stable error +precedence, and NIP-01 admission without secret keys, mutation recipes, or +ambient state. + +The Deletion codec implements the request and pure suppression-evaluation +boundaries of +[NIP-09](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/09.md). +`authored_nip09_deletion_request_to_wire_parts` emits canonical two-element +`e` targets, then `a` targets, then the complete unique ascending set of `k` +kind advisories. `project_verified_nip09_deletion_request_event` accepts only +an id-and-signature-verified kind-`5` envelope. It retains exact raw tags, +deduplicates normalized targets with first-seen provenance, sorts the typed +target views, and treats malformed `e` or `a` targets as hard errors. Unknown +tags and trailing target elements remain observable. Advisory `k` shape, +numeric spelling, duplicates, and address-only conflicts produce stable +ordered diagnostics; event-target kind correspondence remains unprovable at +this boundary. + +`verify_and_admit_nip09_deletion_request_event` binds the projection to the +verified request envelope. It does not authorize or apply a deletion, look up +a target, compare authors, compute an address cutoff, suppress an event, or +mutate storage. + +`evaluate_nip09_suppression` is a separate pure operation over one +`RadrootsSignatureVerifiedEvent` candidate and admitted deletion requests. It +returns an explicit `RadrootsNip09SuppressionDecision` with canonical evidence +without changing any input or touching a store. Kind `5` is immune. Other +candidates require the request author to equal the candidate author. An exact +event-id target applies independently of timestamps; a canonical address +target applies through the inclusive maximum qualifying request timestamp, so +a later replacement is not suppressed. Advisory `k` values do not affect the +result, and request order cannot affect the decision. + +The reconciliation-v1 entrypoints +`project_verified_nip09_deletion_request_event_v1`, +`admit_verified_nip09_deletion_request_event_v1`, and +`evaluate_nip09_suppression_v1` freeze the exact request and suppression +behavior consumed by event-store schema version 2. Current compatibility +entrypoints delegate to these functions; a future algorithm revision must add +new versioned functions. + +The exact operation namespace is +`social.deletion_request.build_authored_draft`, +`social.deletion_request.project_verified_event`, +`social.deletion_request.verify_and_admit_event`, and +`social.deletion_request.evaluate_suppression`. Registry v7 makes +`radroots.social.deletion_request.v1` `TypedOnly` for authoring and +`AdmissionOnly` for matching, with generic kind-`5` authoring and publication +reserved. + +The canonical self-contained 80-case corpus is +`contracts/conformance/vectors/deletion/verified_profile.v1.json`; the +packaged fixture is byte-identical. Signed inputs are complete compact event +JSON, expected projections contain no effect-authority output, and executable +cases cover canonical authoring, tolerant verified projection, diagnostics, +exact and over-limit resource budgets, precedence, and admission without +shipping keys, seeds, generators, or mutation recipes. + +The separate +`contracts/conformance/vectors/deletion/suppression.v1.json` corpus and its +byte-identical packaged fixture execute the evaluator's same-author, direct +event, inclusive address-cutoff, later-replacement, pre-target request, +kind-`5` immunity, and deterministic evidence behavior. These vectors govern a +decision value only and claim no persistent store effect. + +The FoodAvailability codec owns four boundaries for the focused +`radroots.food.availability.v1` kind-`30402` profile. Strict details plus +`created_at` produce bounded unsigned wire parts whose tag order is exactly +`d`, `title`, `summary`, `published_at`, `location`, `price`, +`radroots:price_unit`, optional `radroots:quantity`, `status`, and repeated +`image`. Content and decoded tags remain bounded, and compact signed-event size +is checked after exact JSON escaping. Authored images already prove local +Blossom descriptor-to-byte agreement, but not upload completion or network +availability. + +Verified inbound projection partitions raw tag names before validation. +Focused-only candidates are projected, Operational Listing and marker-free +generic NIP-99 candidates are explicit exclusions, and mixed-marker candidates +fail as ambiguous. Focused core fields are required; decimal and currency +spellings are normalized within their wire bounds; optional unowned NIP-99 +tags are ignored; and tags purporting to add commerce, fulfillment, +provenance, or operational capabilities fail closed. Image observations remain +ordered structural HTTP(S) data. At most 64 are retained, with stable ordered +diagnostics for overflow, shape, URL, dimensions, duplicate URL, and duplicate +Blossom digest. An image diagnostic does not invalidate an otherwise complete +focused core and never upgrades observed media to an authored typestate. + +`verify_and_admit_food_availability_event` first verifies the NIP-01 id and +Schnorr signature, then binds either the focused projection or an explicit +non-focused exclusion to that verified envelope. Revision validation accepts +two independently verified events, requires both to satisfy the exact authored +wire profile, preserves kind, author, `d`, and `published_at`, and applies the +NIP-01 newer-event rule: later `created_at`, then lower event id at equal time. +Successful combined verification and admission uses the optional `nostr` +feature; without it, signature verification returns the stable unavailable +error rather than admitting an event. The packaged conformance runner enables +`serde_json,nostr` for signed JSON event cases. +These codecs do not sign, publish, replicate, upload, retrieve, or provide +client behavior. + +The NIP-52 calendar codecs expose a deliberate three-stage boundary for kinds +`31922`, `31923`, `31924`, and `31925`: + +1. bounded structural wire or envelope parsing preserves the complete event; +2. the kind-specific date event, time event, calendar collection, or RSVP + parser produces a tolerant `RadrootsParsedNip52*` projection and rejects a + mismatched kind; +3. the corresponding `admit_radroots_*` operation applies the stricter + Radroots profile and returns a non-interchangeable admitted type. + +Structural envelope construction, the baseline parsers, and the admission +functions do not verify a Schnorr signature. Before treating inbound data as +accepted, callers must recompute and compare its NIP-01 id, verify its +signature against the author, use the parser for its expected kind, and keep +the typed result bound to that verified envelope. The calendar +`*_parsed_from_event` convenience functions remain structural wrappers; their +names do not imply cryptographic verification. + +The baseline parser projects repeated NIP-52 `location`, participant `p`, +category `t`, absolute-URI reference `r`, and kind-`31924` inclusion-request +`a` tags, plus deprecated `name` compatibility data. It accepts arbitrary +absolute image URIs. Kind `31922` requires semantic Gregorian dates and retains +uppercase-`D` tags as uninterpreted extensions. Kind `31923` requires at least +one in-range `D` day and exact IANA time-zone identifiers. NIP-52 makes +multi-day coverage a `SHOULD`, so the baseline requires neither the start-day +index nor complete coverage; duplicate, unordered, and leading-zero numeric +observations are additionally preserved as parser tolerance and are never +canonical authored output. An omitted +`end_tzid` uses `start_tzid` when present. + +Strict date admission rejects every uppercase-`D` extension. Strict time +admission requires canonical decimal timestamps and the exact ascending +uppercase-`D` sequence for the exclusive interval, bounded to 366 covered +days. Authored codecs derive that sequence and emit unsigned +`RadrootsNip01EventWireParts`; signing and transport stay at the owning runtime +boundary. + +Kind `31924` is handled only by the calendar-specific codec. Generic NIP-51 +taxonomy may recognize it as a calendar list, but the generic list-set decoder +and authoring path and the broader generic list codec reject it. Calendar collection content is the bounded +plain-text NIP-52 detailed description; it is not substituted from or collapsed +into the optional NIP-51 `description` tag. The parser requires exactly one +`d` and `title`, accepts optional singleton `description` and `image` tags, and +accepts zero or more exact `a` references to kind `31922` or `31923` events. +An empty collection is valid, and each event reference preserves its own +optional relay hint. Singleton collection tags have exactly two elements; an +`a` tag has exactly two elements plus an optional relay element. + +Kind `31925` has bounded optional note content and exact singleton +cardinality: required `d`, `a`, and `status`, with optional `e`, `fb`, and `p`. +The `a`, `e`, and `p` values preserve independent optional relay hints. The +`p` tag is parsed as an event-author hint without participant-role semantics; +strict admission requires it to match the author in the `a` coordinate. +`d`, `status`, and `fb` tags have exactly two elements, while `a`, `e`, and `p` +have exactly two elements plus an optional relay element. +Tolerant parsing preserves `fb` observed on a declined RSVP, but parsed and +admitted values expose no effective free/busy state for it. Authored declined +RSVPs never emit `fb`. + +Strict collection and RSVP authoring and admission require a canonical +22-character unpadded base64url 128-bit identifier and canonical reference +spellings. Identifier syntax does not establish runtime uniqueness. Parsing +valid identifiers, event ids, author keys, coordinates, and relay hints does +not establish reference existence, revision correspondence, RSVP authority, +or relay availability. + +Strict inbound admission, including kind-`31924` collection images, upgrades +an image only to a structural Blossom hash-path URL. It does not prove +reference approval, bytes, upload completion, or retrievability. Strict +authored image inputs are approved, byte-verified Blossom `image/*` +descriptors, but are still not upload receipts. A publishing runtime must +require successful BUD-02 completion and a bounded retrievability check before +signing or publishing media-bearing wire parts. + +## Field Event Codecs + +The Field codec surface validates the public Nostr event substrate exposed by +`radroots_event`: + + * workspace manifests use kind `30078`, JSON content, a schema marker, `d` and + `h` routing tags, owner tags, relay references, and Field-supported kind + declarations; + * CRDT changes use kind `78`, JSON content, `h`, `d`, `a`, optional author `p`, + and semantic `t` tags, with base64url change payload validation; + * farm files use kind `1063`, NIP-94-compatible metadata tags, a workspace + address, a farm group id, an owner document tag, lowercase SHA-256 hashes, + and optional caption text as content; + * NIP-42 relay auth and NIP-98 HTTP auth events require empty content and the + auth tags required by their protocols; NIP-98 `u`, `method`, and `payload` + tags are parsed as singleton security tags and duplicate occurrences fail + closed; + * NIP-29 group codecs preserve the protocol distinction between `h`-routed + group operations and `d`-routed addressable group state for the supported + `9000`, `9001`, `9002`, `9005`, `9007`, `9008`, `9009`, `9021`, `9022`, + `39000`, `39001`, `39002`, and `39003` subset. + +These codecs validate event shape, routing tags, hashes, and payload encoding. +They do not validate private Field task, work-session, harvest, approval, or +authorization semantics; those remain application and CRDT document concerns. +The group codecs use bare metadata marker tags such as `private`, `restricted`, +`hidden`, and `closed`, `supported_kinds` declarations, and `code` tags for +invite and join flows. They preserve optional reason content on user management +and moderation events. LiveKit room metadata and live participant state are +deferred. SDK-owned wasm bindings expose deterministic JSON tag builders for the +same Field and NIP-29 families. + +## Copyright + +Except as otherwise noted, all files in the `radroots_event_codec` +distribution are + + Copyright (c) 2025 Tyson Lupul + +For information on usage and redistribution, and for a DISCLAIMER OF ALL +WARRANTIES, see LICENSE included in the `radroots_event_codec` distribution. 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": 11092, - "sha256": "812ce187ddab5e4b95e61b0af42333f41fd772d5dfdc76b72093ef33b3dcdf47", + "byte_length": 11546, + "sha256": "0378afe281a46e02e83e128324efefc3da950abf3e1d3d06c75b628f69810bd2", "hash_algorithm": "sha256_bytes_v1" }, { @@ -236,15 +236,15 @@ { "role": "predecessor_successor_governance", "path": "tools/xtask/src/contract/food_availability_projection.rs", - "byte_length": 194901, - "sha256": "01fe9546c95244b4197b3d2a6cec3d72e1f7c48f088bc93f168b5e2e4977b048", + "byte_length": 195036, + "sha256": "d3c567ea3b4ea709723af2d5b6a2dc47993c6ba42c1d62895bd26fa101ebf829", "hash_algorithm": "sha256_bytes_v1" }, { "role": "transitive_predecessor_membership_governance", "path": "tools/xtask/src/contract/nip09_reconciliation.rs", - "byte_length": 833937, - "sha256": "ee111a10b9b51f213d7c18558aaffb3bc4054449ca585be8f8ea1a9dbd464f60", + "byte_length": 835585, + "sha256": "51d524cb00d2d96dc0da35fc9133c54f2e5328a830d56b8d5e83a7f1e9654ddc", "hash_algorithm": "sha256_bytes_v1" }, { @@ -257,8 +257,8 @@ { "role": "contract_command_authority", "path": "tools/xtask/src/contract.rs", - "byte_length": 499596, - "sha256": "ece5990259eeacac8bb12a27e89f889f82f8641b958103a0ef97eaa6d2f66fb0", + "byte_length": 500480, + "sha256": "84739ad8b78bae49ea1f992672101bd83135415042cb22a85dbcbf5c878fc097", "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 @@ -20d3348c5d1c485c174b45b495331e919e0884cd0372e145e6a9f683acf92afd +ffb6ec82c763730a69d24ee8e8194e3e0911ee590df58e5fe78a23ae68122652 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\": \"ad4a6c8ae9488fc8033792bc6952af04687f312901c1847d8c668a62913bb642\",\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\": \"33b93a3c87ce428e8aa6f5e92643c77203d9aa006c53ce96f3562fe6d68ffd23\",\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\": \"425dc799f392b87f265a6fb81f89c4a1c7a5db8391ab0380970708cb9c66704d\",\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\": \"d526d96ea02be12b4b0aed99e97cfdde17c4474ace67111506a7b900ee78b186\",\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\": \"33b93a3c87ce428e8aa6f5e92643c77203d9aa006c53ce96f3562fe6d68ffd23\",\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\": 11092,\n \"sha256\": \"812ce187ddab5e4b95e61b0af42333f41fd772d5dfdc76b72093ef33b3dcdf47\",\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\": 19421,\n \"sha256\": \"4772e041cb20a4963afb2f3159804c777e2f2be61bfdb6ee267e7a7c04258972\",\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\": 33617,\n \"sha256\": \"79296b8f263aa06d17005795e4515f769f064ea6fd971eeb1296e1151debaf20\",\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\": 184407,\n \"sha256\": \"c455d40fc736e3db264f567c7809af7bd897d89be8a33dfb21667a6ef6b8d6c6\",\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\": 394574,\n \"sha256\": \"db57dc3e35e64c7194683142fe55edba853671a829269449dd2273056dfc3a0e\",\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\": 30140,\n \"sha256\": \"210112eeaa6975a3b4fbb97d5c52588f8c6d8d07975e531d39737fd11235de51\",\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\": 51756,\n \"sha256\": \"f8d5b62f0613104aa86658d5bf1baade92c7df83f00ef0cddadd734b9797afca\",\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\": 38279,\n \"sha256\": \"f326ea57b56d40135f95b6b1e15961f66eed363337180a6d12a5ea903e1a9a29\",\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\": 194901,\n \"sha256\": \"01fe9546c95244b4197b3d2a6cec3d72e1f7c48f088bc93f168b5e2e4977b048\",\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\": 833937,\n \"sha256\": \"ee111a10b9b51f213d7c18558aaffb3bc4054449ca585be8f8ea1a9dbd464f60\",\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\": 176811,\n \"sha256\": \"cc6125aaeba8d7dab83c2413ca0f1a80d6f9f26876f0117e4c8fceae156304e5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 499596,\n \"sha256\": \"ece5990259eeacac8bb12a27e89f889f82f8641b958103a0ef97eaa6d2f66fb0\",\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\": 14364,\n \"sha256\": \"27096ad2c226c6402313621712db6af23a4fdda5117c3e732cf3a3fdf3d9b434\",\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\": \"ad4a6c8ae9488fc8033792bc6952af04687f312901c1847d8c668a62913bb642\",\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\": \"33b93a3c87ce428e8aa6f5e92643c77203d9aa006c53ce96f3562fe6d68ffd23\",\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\": \"425dc799f392b87f265a6fb81f89c4a1c7a5db8391ab0380970708cb9c66704d\",\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\": \"d526d96ea02be12b4b0aed99e97cfdde17c4474ace67111506a7b900ee78b186\",\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\": \"33b93a3c87ce428e8aa6f5e92643c77203d9aa006c53ce96f3562fe6d68ffd23\",\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\": 11546,\n \"sha256\": \"0378afe281a46e02e83e128324efefc3da950abf3e1d3d06c75b628f69810bd2\",\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\": 19421,\n \"sha256\": \"4772e041cb20a4963afb2f3159804c777e2f2be61bfdb6ee267e7a7c04258972\",\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\": 33617,\n \"sha256\": \"79296b8f263aa06d17005795e4515f769f064ea6fd971eeb1296e1151debaf20\",\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\": 184407,\n \"sha256\": \"c455d40fc736e3db264f567c7809af7bd897d89be8a33dfb21667a6ef6b8d6c6\",\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\": 394574,\n \"sha256\": \"db57dc3e35e64c7194683142fe55edba853671a829269449dd2273056dfc3a0e\",\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\": 30140,\n \"sha256\": \"210112eeaa6975a3b4fbb97d5c52588f8c6d8d07975e531d39737fd11235de51\",\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\": 51756,\n \"sha256\": \"f8d5b62f0613104aa86658d5bf1baade92c7df83f00ef0cddadd734b9797afca\",\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\": 38279,\n \"sha256\": \"f326ea57b56d40135f95b6b1e15961f66eed363337180a6d12a5ea903e1a9a29\",\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\": 195036,\n \"sha256\": \"d3c567ea3b4ea709723af2d5b6a2dc47993c6ba42c1d62895bd26fa101ebf829\",\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\": 835585,\n \"sha256\": \"51d524cb00d2d96dc0da35fc9133c54f2e5328a830d56b8d5e83a7f1e9654ddc\",\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\": 176811,\n \"sha256\": \"cc6125aaeba8d7dab83c2413ca0f1a80d6f9f26876f0117e4c8fceae156304e5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 500480,\n \"sha256\": \"84739ad8b78bae49ea1f992672101bd83135415042cb22a85dbcbf5c878fc097\",\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\": 14364,\n \"sha256\": \"27096ad2c226c6402313621712db6af23a4fdda5117c3e732cf3a3fdf3d9b434\",\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 = 14459; pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SHA256: &str = - "20d3348c5d1c485c174b45b495331e919e0884cd0372e145e6a9f683acf92afd"; + "ffb6ec82c763730a69d24ee8e8194e3e0911ee590df58e5fe78a23ae68122652"; 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/geonames/Cargo.toml b/crates/geonames/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "radroots-geonames" +description = "GeoNames-backed geocoding for Radroots" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +readme = "README.md" +publish = false + +[lib] +name = "radroots_geonames" + +[lints] +workspace = true diff --git a/crates/geonames/README.md b/crates/geonames/README.md @@ -0,0 +1,7 @@ +# radroots-geonames + +GeoNames-backed geocoding for Radroots. + +This package root is established for the Release V1 refactor. Dataset models, +indexes, and deterministic lookup behavior are migrated in the subsequent +GeoNames checkpoints. diff --git a/crates/geonames/src/lib.rs b/crates/geonames/src/lib.rs @@ -0,0 +1 @@ +//! GeoNames-backed geocoding for Radroots. diff --git a/crates/identity/Cargo.toml b/crates/identity/Cargo.toml @@ -1,16 +1,19 @@ [package] -name = "radroots_identity" +name = "radroots-identity" publish = false -version = "1.0.0-alpha.1" +version = "0.1.0" edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] +authors.workspace = true rust-version.workspace = true license.workspace = true description = "Identity model for Radroots" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots_identity" -readme = "README" +documentation = "https://docs.rs/radroots-identity" +readme = "README.md" + +[lib] +name = "radroots_identity" [features] default = ["std", "json-file", "nip49"] @@ -44,5 +47,5 @@ zeroize = { workspace = true, optional = true } [dev-dependencies] tempfile = { workspace = true } -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } +[lints] +workspace = true diff --git a/crates/identity/README b/crates/identity/README @@ -1,25 +0,0 @@ -# radroots_identity - -This is the README for `radroots_identity`, which provides identity models, -encrypted file storage, and profile utilities for the `radroots` core -libraries. - -## Overview - - * public and private identity, profile, file, and identifier types; - * username validation, normalization, and parser helpers; - * default identity path constants and JSON file support behind feature flags; - * encrypted identity-file and public-profile storage helpers for local runtime - consumers; - * optional NIP-49, `secrecy`, and `zeroize` support for protected key - material. - -## Copyright - -Except as otherwise noted, all files in the `radroots_identity` distribution -are - - Copyright (c) 2025 Tyson Lupul - -For information on usage and redistribution, and for a DISCLAIMER OF ALL -WARRANTIES, see LICENSE included in the `radroots_identity` distribution. diff --git a/crates/identity/README.md b/crates/identity/README.md @@ -0,0 +1,25 @@ +# radroots-identity + +This is the README for `radroots_identity`, which provides identity models, +encrypted file storage, and profile utilities for the `radroots` core +libraries. + +## Overview + + * public and private identity, profile, file, and identifier types; + * username validation, normalization, and parser helpers; + * default identity path constants and JSON file support behind feature flags; + * encrypted identity-file and public-profile storage helpers for local runtime + consumers; + * optional NIP-49, `secrecy`, and `zeroize` support for protected key + material. + +## Copyright + +Except as otherwise noted, all files in the `radroots_identity` distribution +are + + Copyright (c) 2025 Tyson Lupul + +For information on usage and redistribution, and for a DISCLAIMER OF ALL +WARRANTIES, see LICENSE included in the `radroots_identity` distribution. diff --git a/crates/identity/tests/identity.rs b/crates/identity/tests/identity.rs @@ -21,40 +21,10 @@ use radroots_runtime_paths::{ RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, RadrootsPlatform, }; -use std::{ - ffi::OsString, - path::PathBuf, - sync::{Mutex, OnceLock}, -}; +use std::path::PathBuf; use test_fixtures::{ApprovedFixtureIdentity, FIXTURE_ALICE, FIXTURE_BOB}; -fn home_env_lock() -> &'static Mutex<()> { - static LOCK: OnceLock<Mutex<()>> = OnceLock::new(); - LOCK.get_or_init(|| Mutex::new(())) -} - -struct EnvVarGuard { - key: &'static str, - previous: Option<OsString>, -} - -impl EnvVarGuard { - fn remove(key: &'static str) -> Self { - let previous = std::env::var_os(key); - unsafe { std::env::remove_var(key) }; - Self { key, previous } - } -} - -impl Drop for EnvVarGuard { - fn drop(&mut self) { - if let Some(value) = self.previous.as_ref() { - unsafe { std::env::set_var(self.key, value) }; - } else { - unsafe { std::env::remove_var(self.key) }; - } - } -} +const MISSING_HOME_CHILD: &str = "RADROOTS_IDENTITY_MISSING_HOME_CHILD"; fn fixture_keys(fixture: ApprovedFixtureIdentity) -> nostr::Keys { let secret = nostr::SecretKey::from_hex(fixture.secret_key_hex).unwrap(); @@ -638,9 +608,29 @@ fn default_path_for_reports_missing_home_dir() { #[test] fn load_or_generate_without_explicit_path_propagates_default_path_errors() { - let _lock = home_env_lock().lock().unwrap(); - let _guard = EnvVarGuard::remove("HOME"); + let output = std::process::Command::new(std::env::current_exe().unwrap()) + .args([ + "--exact", + "load_or_generate_without_explicit_path_child", + "--nocapture", + ]) + .env_remove("HOME") + .env(MISSING_HOME_CHILD, "1") + .output() + .unwrap(); + assert!( + output.status.success(), + "child test failed:\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} +#[test] +fn load_or_generate_without_explicit_path_child() { + if std::env::var_os(MISSING_HOME_CHILD).is_none() { + return; + } let err = RadrootsIdentity::load_or_generate::<&std::path::Path>(None, false).unwrap_err(); assert!(matches!(err, IdentityError::Paths(_))); } diff --git a/crates/nostr/Cargo.toml b/crates/nostr/Cargo.toml @@ -1,16 +1,22 @@ [package] -name = "radroots_nostr" +name = "radroots-nostr" publish = false -version = "1.0.0-alpha.1" +version = "0.1.0" edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] +authors.workspace = true rust-version.workspace = true license.workspace = true description = "Nostr integration layer for Radroots" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots_nostr" -readme = "README" +documentation = "https://docs.rs/radroots-nostr" +readme = "README.md" + +[lib] +name = "radroots_nostr" + +[lints] +workspace = true [features] default = ["std"] diff --git a/crates/nostr/README b/crates/nostr/README @@ -1,143 +0,0 @@ -# radroots_nostr - -This is the README for `radroots_nostr`, which provides shared Nostr protocol -primitives for the `radroots` core libraries. - -## Overview - - * typed filters, tags, events, relay metadata, parsers, and utility helpers; - * feature-gated client operations and relay-management helpers for active - network use; - * adapters between `radroots_event`, `radroots_event_codec`, and Nostr wire - representations; - * strict BUD-11 signed HTTP authorization adapters behind the `blossom` - feature; - * optional NIP-11 and NIP-17 support across feature-gated builds. - -The `blossom` feature signs kind-24242 authorization events and encodes or -verifies their `Authorization: Nostr` HTTP values. It does not publish these -ephemeral authorization events to relays. Pure BUD-11 claim parsing and policy -validation remain in `radroots_blossom`. - -The `events` feature is std-backed. With it, kind-1 root publication is -available only through typed Update, PhotoUpdate, and Ask builders backed by -the strict `radroots_event_codec` wire operations. NIP-10 Reply publication is -separately available through the typed direct or nested Reply model and its -sealed builder. Authored Replies always emit marked `root` and optional -`reply` event references plus the required referenced-author `p` tags. -Verified inbound projection also accepts deprecated positional NIP-10 -references for interoperability and preserves valid supplemental unmarked `e` -references as citations. Empty marker slots remain absent even when an optional -fifth-element author hint is present. Missing or malformed advisory -participant, middle citation, relay, and referenced-author metadata is retained -as ordered diagnostics instead of erasing an unambiguous Reply. A Reply remains -thread content and can never enter root-card admission. Reply admission proves -the Reply event's NIP-01 id and signature; it does not prove that a referenced -target exists, is kind `1`, or was authored by the declared referenced author. - -Strict -[NIP-22](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/22.md) -Comment publication is separately exposed through -`RadrootsNostrNip22CommentEventBuilder`. Its only input is a checked -`RadrootsAuthoredNip22Comment`; callers may choose the timestamp and sign or -publish, but cannot mutate the kind, content, or canonical -root, parent, kind, and participant tags. The profile admits event or address -roots only for kinds `30402`, `31922`, and `31923`; it has no external -`I`/`i` or kind-`1` root surface. - -Reply and Comment authoring and verified projection share the portable -`RadrootsNostrRelayHint` profile rather than the generic relay URL type. It -accepts only exact lowercase `ws://` or `wss://` visible-ASCII URLs with a -canonical lowercase DNS, four-octet IPv4, or bracketed pure-hex RFC 5952 IPv6 -authority, an optional canonical port `1..65535`, and RFC 3986 -path-abempty/query syntax using uppercase `%HH` escapes. Rejected inbound hints -remain verbatim in ordered raw-tag diagnostics. The hint profile does not own -the event boundary's separate 4,096-byte tag-element budget. - -Inbound Comment projection is owned by `radroots_event_codec` and accepts only -an id-and-signature verified kind-`1111` envelope. -`RadrootsInboundNip22CommentProjection` retains the order-independent -authority projection, and `RadrootsAdmittedNip22CommentEvent` keeps it bound to -the verified envelope. Malformed optional relay or participant metadata -remains diagnostic, while cardinality, unsupported roots, coordinate -conflicts, and valid-but-conflicting author hints fail admission. Admission -does not prove any referenced target or relay. The registry-v7 contract is -`TypedOnly` for authoring and `AdmissionOnly` for matching, with registry -versions `1` through `6` stale. - -Strict -[NIP-09](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/09.md) -deletion-request publication is exposed through the sealed -`RadrootsNostrNip09DeletionRequestEventBuilder`. Its only input is a checked -`RadrootsAuthoredNip09DeletionRequest`; callers may choose the timestamp and -sign or publish, but cannot mutate the kind, content, or canonical `e`, `a`, -and derived `k` tags. Generic kind-5 builders are rejected before signer -access. Admission proves only the signed deletion-request event and its typed -projection. It does not prove target authorship, existence, deletion -authorization, applicability, relay handling, or any deletion effect. - -The former free-form text-note post builder is removed. Generic protocol -builders reject kind-0 Profile events, every kind-1 event, every kind-5 -deletion request, and kind-1111 Comments at both direct signing and -client-publication boundaries before signer access. Typed media builders can -sign or publish only after the owning runtime separately proves successful -BUD-02 upload completion; their byte-verified descriptors do not attest upload -completion. The generic net manager intentionally exposes no direct -PhotoUpdate or media Ask publisher. - -The complete governed Comment operation namespace remains -`social.comment.build_authored_draft`, -`social.comment.project_verified_event`, and -`social.comment.verify_and_admit_event`. The typed builder and client -publication surface consume that strict contract; they do not add a fourth -wire operation. Comment inputs retain the 131072-byte content, 1024-tag, -4096-total-element, 4096-byte element, 131072-byte aggregate tag, and -262144-byte compact signed-wire ceilings proven by the canonical -self-contained 114-case conformance corpus. - -Focused FoodAvailability kind-30402 authoring likewise uses a sealed builder. -Its `created_at` is fixed during strict construction and cannot be changed -after wire validation. Generic direct signing and client publication reject -focused or mixed FoodAvailability/Operational Listing markers before signer -access; marker-free NIP-99 and operational-only compatibility builders remain -available. Relaying an already signed event remains a transport operation and -does not establish typed FoodAvailability authoring. - -The opaque generic-builder policy governs Radroots builder signing and client -publication. It does not redefine the standard NIP-46 `sign_event` method or a -signer backend's externally supplied unsigned-event operation. Those are -explicit low-level Nostr interoperability boundaries; their outputs carry no -Radroots typed product-authoring claim and are not product authoring entry -points. - -Generic protocol events that require an external custody provider finalize -through `RadrootsNostrExternalSigningRequest`. The opaque request is available -without the relay-client feature and serializes as a standard unsigned Nostr -event only after generic typed-authoring reservations pass. It accepts a -returned event only when the author and canonical event id match the request -and the complete NIP-01 event verifies. It exposes no raw mutable builder, -unsigned-event conversion, or unchecked deserialization path. - -Strict kind-0 Profile publication uses -`RadrootsNostrProfileEventBuilder`, constructed only from -`RadrootsAuthoredProfile`. The sealed wrapper permits timestamp selection and -local signing or client publication, but no raw kind, content, or tag -mutation. A media-bearing Profile still requires runtime-owned proof of -successful BUD-02 upload before it reaches this authoring boundary. - -## Portable relay-client lifecycle - -With the `client` feature, callers can subscribe and publish to selected relay -sets and explicitly unsubscribe through Radroots types. The wrapper does not -create detached listeners or take ownership of subscription lifetime. The -`client,events` feature combination is qualified for native targets and -`wasm32-unknown-unknown`. - -## Copyright - -Except as otherwise noted, all files in the `radroots_nostr` distribution are - - Copyright (c) 2025 Tyson Lupul - -For information on usage and redistribution, and for a DISCLAIMER OF ALL -WARRANTIES, see LICENSE included in the `radroots_nostr` distribution. diff --git a/crates/nostr/README.md b/crates/nostr/README.md @@ -0,0 +1,143 @@ +# radroots-nostr + +This is the README for `radroots_nostr`, which provides shared Nostr protocol +primitives for the `radroots` core libraries. + +## Overview + + * typed filters, tags, events, relay metadata, parsers, and utility helpers; + * feature-gated client operations and relay-management helpers for active + network use; + * adapters between `radroots_event`, `radroots_event_codec`, and Nostr wire + representations; + * strict BUD-11 signed HTTP authorization adapters behind the `blossom` + feature; + * optional NIP-11 and NIP-17 support across feature-gated builds. + +The `blossom` feature signs kind-24242 authorization events and encodes or +verifies their `Authorization: Nostr` HTTP values. It does not publish these +ephemeral authorization events to relays. Pure BUD-11 claim parsing and policy +validation remain in `radroots_blossom`. + +The `events` feature is std-backed. With it, kind-1 root publication is +available only through typed Update, PhotoUpdate, and Ask builders backed by +the strict `radroots_event_codec` wire operations. NIP-10 Reply publication is +separately available through the typed direct or nested Reply model and its +sealed builder. Authored Replies always emit marked `root` and optional +`reply` event references plus the required referenced-author `p` tags. +Verified inbound projection also accepts deprecated positional NIP-10 +references for interoperability and preserves valid supplemental unmarked `e` +references as citations. Empty marker slots remain absent even when an optional +fifth-element author hint is present. Missing or malformed advisory +participant, middle citation, relay, and referenced-author metadata is retained +as ordered diagnostics instead of erasing an unambiguous Reply. A Reply remains +thread content and can never enter root-card admission. Reply admission proves +the Reply event's NIP-01 id and signature; it does not prove that a referenced +target exists, is kind `1`, or was authored by the declared referenced author. + +Strict +[NIP-22](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/22.md) +Comment publication is separately exposed through +`RadrootsNostrNip22CommentEventBuilder`. Its only input is a checked +`RadrootsAuthoredNip22Comment`; callers may choose the timestamp and sign or +publish, but cannot mutate the kind, content, or canonical +root, parent, kind, and participant tags. The profile admits event or address +roots only for kinds `30402`, `31922`, and `31923`; it has no external +`I`/`i` or kind-`1` root surface. + +Reply and Comment authoring and verified projection share the portable +`RadrootsNostrRelayHint` profile rather than the generic relay URL type. It +accepts only exact lowercase `ws://` or `wss://` visible-ASCII URLs with a +canonical lowercase DNS, four-octet IPv4, or bracketed pure-hex RFC 5952 IPv6 +authority, an optional canonical port `1..65535`, and RFC 3986 +path-abempty/query syntax using uppercase `%HH` escapes. Rejected inbound hints +remain verbatim in ordered raw-tag diagnostics. The hint profile does not own +the event boundary's separate 4,096-byte tag-element budget. + +Inbound Comment projection is owned by `radroots_event_codec` and accepts only +an id-and-signature verified kind-`1111` envelope. +`RadrootsInboundNip22CommentProjection` retains the order-independent +authority projection, and `RadrootsAdmittedNip22CommentEvent` keeps it bound to +the verified envelope. Malformed optional relay or participant metadata +remains diagnostic, while cardinality, unsupported roots, coordinate +conflicts, and valid-but-conflicting author hints fail admission. Admission +does not prove any referenced target or relay. The registry-v7 contract is +`TypedOnly` for authoring and `AdmissionOnly` for matching, with registry +versions `1` through `6` stale. + +Strict +[NIP-09](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/09.md) +deletion-request publication is exposed through the sealed +`RadrootsNostrNip09DeletionRequestEventBuilder`. Its only input is a checked +`RadrootsAuthoredNip09DeletionRequest`; callers may choose the timestamp and +sign or publish, but cannot mutate the kind, content, or canonical `e`, `a`, +and derived `k` tags. Generic kind-5 builders are rejected before signer +access. Admission proves only the signed deletion-request event and its typed +projection. It does not prove target authorship, existence, deletion +authorization, applicability, relay handling, or any deletion effect. + +The former free-form text-note post builder is removed. Generic protocol +builders reject kind-0 Profile events, every kind-1 event, every kind-5 +deletion request, and kind-1111 Comments at both direct signing and +client-publication boundaries before signer access. Typed media builders can +sign or publish only after the owning runtime separately proves successful +BUD-02 upload completion; their byte-verified descriptors do not attest upload +completion. The generic net manager intentionally exposes no direct +PhotoUpdate or media Ask publisher. + +The complete governed Comment operation namespace remains +`social.comment.build_authored_draft`, +`social.comment.project_verified_event`, and +`social.comment.verify_and_admit_event`. The typed builder and client +publication surface consume that strict contract; they do not add a fourth +wire operation. Comment inputs retain the 131072-byte content, 1024-tag, +4096-total-element, 4096-byte element, 131072-byte aggregate tag, and +262144-byte compact signed-wire ceilings proven by the canonical +self-contained 114-case conformance corpus. + +Focused FoodAvailability kind-30402 authoring likewise uses a sealed builder. +Its `created_at` is fixed during strict construction and cannot be changed +after wire validation. Generic direct signing and client publication reject +focused or mixed FoodAvailability/Operational Listing markers before signer +access; marker-free NIP-99 and operational-only compatibility builders remain +available. Relaying an already signed event remains a transport operation and +does not establish typed FoodAvailability authoring. + +The opaque generic-builder policy governs Radroots builder signing and client +publication. It does not redefine the standard NIP-46 `sign_event` method or a +signer backend's externally supplied unsigned-event operation. Those are +explicit low-level Nostr interoperability boundaries; their outputs carry no +Radroots typed product-authoring claim and are not product authoring entry +points. + +Generic protocol events that require an external custody provider finalize +through `RadrootsNostrExternalSigningRequest`. The opaque request is available +without the relay-client feature and serializes as a standard unsigned Nostr +event only after generic typed-authoring reservations pass. It accepts a +returned event only when the author and canonical event id match the request +and the complete NIP-01 event verifies. It exposes no raw mutable builder, +unsigned-event conversion, or unchecked deserialization path. + +Strict kind-0 Profile publication uses +`RadrootsNostrProfileEventBuilder`, constructed only from +`RadrootsAuthoredProfile`. The sealed wrapper permits timestamp selection and +local signing or client publication, but no raw kind, content, or tag +mutation. A media-bearing Profile still requires runtime-owned proof of +successful BUD-02 upload before it reaches this authoring boundary. + +## Portable relay-client lifecycle + +With the `client` feature, callers can subscribe and publish to selected relay +sets and explicitly unsubscribe through Radroots types. The wrapper does not +create detached listeners or take ownership of subscription lifetime. The +`client,events` feature combination is qualified for native targets and +`wasm32-unknown-unknown`. + +## Copyright + +Except as otherwise noted, all files in the `radroots_nostr` distribution are + + Copyright (c) 2025 Tyson Lupul + +For information on usage and redistribution, and for a DISCLAIMER OF ALL +WARRANTIES, see LICENSE included in the `radroots_nostr` distribution. diff --git a/crates/nostr_connect/Cargo.toml b/crates/nostr_connect/Cargo.toml @@ -1,16 +1,19 @@ [package] -name = "radroots_nostr_connect" +name = "radroots-nostr-connect" publish = false -version = "1.0.0-alpha.1" +version = "0.1.0" edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] +authors.workspace = true rust-version.workspace = true license.workspace = true description = "NIP-46 protocol model for Radroots" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots_nostr_connect" -readme = "README" +documentation = "https://docs.rs/radroots-nostr-connect" +readme = "README.md" + +[lib] +name = "radroots_nostr_connect" [dependencies] nostr = { workspace = true, features = ["nip44"] } @@ -22,5 +25,5 @@ url = { workspace = true } [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt"] } -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } +[lints] +workspace = true diff --git a/crates/nostr_connect/README b/crates/nostr_connect/README @@ -1,36 +0,0 @@ -# radroots_nostr_connect - -This is the README for `radroots_nostr_connect`, which provides NIP-46 -connection and URI models for the `radroots` core libraries. - -## Overview - - * request and response message types for Nostr Connect exchanges; - * method, permission, and pending-connection outcome models; - * bunker and client URI parsing and formatting helpers; - * portable shared models with optional serialization and TypeScript export - support. - -## Current NIP-46 behavior - -`connect` accepts the current one-to-four-parameter wire shape. The fourth -parameter carries bounded, normalized display metadata (`name`, `url`, and -`image`), while requested permissions remain the third parameter. Legacy -one-, two-, and three-parameter requests continue to decode. `nostrconnect://` -requires an ordered relay set and a non-empty secret; `bunker://` preserves -ordered relays and keeps its secret optional. - -The typed client models support secret-echo connection responses, -`auth_url` continuation, relay switching, and zero-parameter `logout` with an -`ack` response. Deterministic protocol cases live in -`contracts/conformance/vectors/nip46/current_session.v1.json`. - -## Copyright - -Except as otherwise noted, all files in the `radroots_nostr_connect` -distribution are - - Copyright (c) 2025 Tyson Lupul - -For information on usage and redistribution, and for a DISCLAIMER OF ALL -WARRANTIES, see LICENSE included in the `radroots_nostr_connect` distribution. diff --git a/crates/nostr_connect/README.md b/crates/nostr_connect/README.md @@ -0,0 +1,36 @@ +# radroots-nostr-connect + +This is the README for `radroots_nostr_connect`, which provides NIP-46 +connection and URI models for the `radroots` core libraries. + +## Overview + + * request and response message types for Nostr Connect exchanges; + * method, permission, and pending-connection outcome models; + * bunker and client URI parsing and formatting helpers; + * portable shared models with optional serialization and TypeScript export + support. + +## Current NIP-46 behavior + +`connect` accepts the current one-to-four-parameter wire shape. The fourth +parameter carries bounded, normalized display metadata (`name`, `url`, and +`image`), while requested permissions remain the third parameter. Legacy +one-, two-, and three-parameter requests continue to decode. `nostrconnect://` +requires an ordered relay set and a non-empty secret; `bunker://` preserves +ordered relays and keeps its secret optional. + +The typed client models support secret-echo connection responses, +`auth_url` continuation, relay switching, and zero-parameter `logout` with an +`ack` response. Deterministic protocol cases live in +`contracts/conformance/vectors/nip46/current_session.v1.json`. + +## Copyright + +Except as otherwise noted, all files in the `radroots_nostr_connect` +distribution are + + Copyright (c) 2025 Tyson Lupul + +For information on usage and redistribution, and for a DISCLAIMER OF ALL +WARRANTIES, see LICENSE included in the `radroots_nostr_connect` distribution. diff --git a/crates/protocol/Cargo.toml b/crates/protocol/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "radroots-protocol" +description = "Versioned wire contracts for Radroots" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +readme = "README.md" +publish = false + +[lib] +name = "radroots_protocol" + +[lints] +workspace = true diff --git a/crates/protocol/README.md b/crates/protocol/README.md @@ -0,0 +1,6 @@ +# radroots-protocol + +Versioned cross-process and cross-language schemas for Radroots. + +This package root is established for the Release V1 refactor. Its governed +modules and contracts are migrated in the subsequent protocol checkpoints. diff --git a/crates/protocol/src/lib.rs b/crates/protocol/src/lib.rs @@ -0,0 +1 @@ +//! Versioned wire contracts for Radroots. diff --git a/crates/secrets/Cargo.toml b/crates/secrets/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "radroots-secrets" +description = "Secret material and protected-storage abstractions for Radroots" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +readme = "README.md" +publish = false + +[lib] +name = "radroots_secrets" + +[lints] +workspace = true diff --git a/crates/secrets/README.md b/crates/secrets/README.md @@ -0,0 +1,7 @@ +# radroots-secrets + +Secret material and protected-storage abstractions for Radroots. + +This package root is established for the Release V1 refactor. Secret types, +providers, and wrapping policy are migrated in the subsequent secrets +checkpoints. diff --git a/crates/secrets/src/lib.rs b/crates/secrets/src/lib.rs @@ -0,0 +1 @@ +//! Secret material and protected-storage abstractions for Radroots. diff --git a/crates/signing/Cargo.toml b/crates/signing/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "radroots-signing" +description = "Signing abstractions and authorization policy for Radroots" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +readme = "README.md" +publish = false + +[lib] +name = "radroots_signing" + +[lints] +workspace = true diff --git a/crates/signing/README.md b/crates/signing/README.md @@ -0,0 +1,6 @@ +# radroots-signing + +Signing abstractions and authorization policy for Radroots. + +This package root is established for the Release V1 refactor. Its signer SPI +and policy surface are migrated in the subsequent signing checkpoints. diff --git a/crates/signing/src/lib.rs b/crates/signing/src/lib.rs @@ -0,0 +1 @@ +//! Signing abstractions and authorization policy for Radroots. diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "radroots-storage" +description = "Backend-neutral persistence abstractions for Radroots" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +readme = "README.md" +publish = false + +[lib] +name = "radroots_storage" + +[lints] +workspace = true diff --git a/crates/storage/README.md b/crates/storage/README.md @@ -0,0 +1,7 @@ +# radroots-storage + +Backend-neutral persistence abstractions for Radroots. + +This package root is established for the Release V1 refactor. Storage SPIs, +models, and atomic operation contracts are migrated in the subsequent storage +checkpoints. diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs @@ -0,0 +1 @@ +//! Backend-neutral persistence abstractions for Radroots. diff --git a/crates/storage_sqlite/Cargo.toml b/crates/storage_sqlite/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "radroots-storage-sqlite" +description = "SQLite storage backend for Radroots" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +readme = "README.md" +publish = false + +[lib] +name = "radroots_storage_sqlite" + +[lints] +workspace = true diff --git a/crates/storage_sqlite/README.md b/crates/storage_sqlite/README.md @@ -0,0 +1,7 @@ +# radroots-storage-sqlite + +SQLite storage backend for Radroots. + +This package root is established for the Release V1 refactor. Its backend +implementation and migration authority are introduced in the subsequent +SQLite storage checkpoints. diff --git a/crates/storage_sqlite/src/lib.rs b/crates/storage_sqlite/src/lib.rs @@ -0,0 +1 @@ +//! SQLite storage backend for Radroots. diff --git a/crates/sync/Cargo.toml b/crates/sync/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "radroots-sync" +description = "Synchronization orchestration for Radroots" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +readme = "README.md" +publish = false + +[lib] +name = "radroots_sync" + +[lints] +workspace = true diff --git a/crates/sync/README.md b/crates/sync/README.md @@ -0,0 +1,6 @@ +# radroots-sync + +Synchronization orchestration for Radroots. + +This package root is established for the Release V1 refactor. Sync planning, +execution, and reporting are migrated in the subsequent sync checkpoints. diff --git a/crates/sync/src/lib.rs b/crates/sync/src/lib.rs @@ -0,0 +1 @@ +//! Synchronization orchestration for Radroots. diff --git a/crates/trade/Cargo.toml b/crates/trade/Cargo.toml @@ -1,16 +1,19 @@ [package] -name = "radroots_trade" +name = "radroots-trade" publish = false -version = "1.0.0-alpha.1" +version = "0.1.0" edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] +authors.workspace = true rust-version.workspace = true license.workspace = true description = "Trade domain model for Radroots" repository.workspace = true homepage.workspace = true -documentation = "https://docs.rs/radroots_trade" -readme = "README" +documentation = "https://docs.rs/radroots-trade" +readme = "README.md" + +[lib] +name = "radroots_trade" [features] default = ["std", "serde", "serde_json"] @@ -90,5 +93,5 @@ sqlx = { workspace = true, default-features = false, features = [ ] } tokio = { workspace = true, features = ["macros", "rt"] } -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } +[lints] +workspace = true diff --git a/crates/trade/README b/crates/trade/README @@ -1,25 +0,0 @@ -# radroots_trade - -This is the README for `radroots_trade`, which provides shared trade workflow -helpers for the `radroots` core libraries. - -## Overview - - * listing model types such as subtotals and totals built on `radroots_core` - money, quantity, currency, and unit values; - * listing publish helpers for canonical address normalization, author checks, - and event-shaping workflows; - * one typed Operational Listing semantic reducer shared by unsigned model - validation and the signature-verified Nostr event boundary; - * order and public-trade helpers for shared request validation and projection; - * optional `serde` and `serde_json` support for shared model serialization; - * portable shared-model support for both `std` and `no_std` builds. - -## Copyright - -Except as otherwise noted, all files in the `radroots_trade` distribution are - - Copyright (c) 2025 Tyson Lupul - -For information on usage and redistribution, and for a DISCLAIMER OF ALL -WARRANTIES, see LICENSE included in the `radroots_trade` distribution. diff --git a/crates/trade/README.md b/crates/trade/README.md @@ -0,0 +1,25 @@ +# radroots-trade + +This is the README for `radroots_trade`, which provides shared trade workflow +helpers for the `radroots` core libraries. + +## Overview + + * listing model types such as subtotals and totals built on `radroots_core` + money, quantity, currency, and unit values; + * listing publish helpers for canonical address normalization, author checks, + and event-shaping workflows; + * one typed Operational Listing semantic reducer shared by unsigned model + validation and the signature-verified Nostr event boundary; + * order and public-trade helpers for shared request validation and projection; + * optional `serde` and `serde_json` support for shared model serialization; + * portable shared-model support for both `std` and `no_std` builds. + +## Copyright + +Except as otherwise noted, all files in the `radroots_trade` distribution are + + Copyright (c) 2025 Tyson Lupul + +For information on usage and redistribution, and for a DISCLAIMER OF ALL +WARRANTIES, see LICENSE included in the `radroots_trade` distribution. diff --git a/crates/transport/Cargo.toml b/crates/transport/Cargo.toml @@ -1,15 +1,18 @@ [package] -name = "radroots_transport" +name = "radroots-transport" publish = false -version = "1.0.0-alpha.1" +version = "0.1.0" edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] +authors.workspace = true rust-version.workspace = true license.workspace = true description = "Transport-neutral delivery model for Radroots" repository.workspace = true homepage.workspace = true -readme = "README" +readme = "README.md" + +[lib] +name = "radroots_transport" [features] default = ["serde"] @@ -23,5 +26,5 @@ sha2 = { workspace = true, default-features = false } futures = { workspace = true } serde_json = { workspace = true, features = ["std"] } -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } +[lints] +workspace = true diff --git a/crates/transport/README b/crates/transport/README @@ -1,21 +0,0 @@ -# radroots_transport - -This is the README for `radroots_transport`, which provides transport-neutral -delivery models for the `radroots` core libraries. - -## Overview - - * transport kind, implementation state, and status model types; - * target URI, target fingerprint, target set, request, and receipt contracts; - * deterministic target fingerprints based on transport kind and canonical - target URI; - * portable shared models with optional `serde` support. - -## Copyright - -Except as otherwise noted, all files in the `radroots_transport` distribution are - - Copyright (c) 2025 Tyson Lupul - -For information on usage and redistribution, and for a DISCLAIMER OF ALL -WARRANTIES, see LICENSE included in the `radroots_transport` distribution. diff --git a/crates/transport/README.md b/crates/transport/README.md @@ -0,0 +1,21 @@ +# radroots-transport + +This is the README for `radroots_transport`, which provides transport-neutral +delivery models for the `radroots` core libraries. + +## Overview + + * transport kind, implementation state, and status model types; + * target URI, target fingerprint, target set, request, and receipt contracts; + * deterministic target fingerprints based on transport kind and canonical + target URI; + * portable shared models with optional `serde` support. + +## Copyright + +Except as otherwise noted, all files in the `radroots_transport` distribution are + + Copyright (c) 2025 Tyson Lupul + +For information on usage and redistribution, and for a DISCLAIMER OF ALL +WARRANTIES, see LICENSE included in the `radroots_transport` distribution. diff --git a/crates/transport_nostr/Cargo.toml b/crates/transport_nostr/Cargo.toml @@ -1,15 +1,18 @@ [package] -name = "radroots_transport_nostr" +name = "radroots-transport-nostr" publish = false -version = "1.0.0-alpha.1" +version = "0.1.0" edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] +authors.workspace = true rust-version.workspace = true license.workspace = true description = "Nostr transport layer for Radroots" repository.workspace = true homepage.workspace = true -readme = "README" +readme = "README.md" + +[lib] +name = "radroots_transport_nostr" [features] default = ["std", "client", "storage", "runtime-tokio"] @@ -58,5 +61,5 @@ url = { workspace = true } [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt"] } -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } +[lints] +workspace = true diff --git a/crates/transport_nostr/README b/crates/transport_nostr/README @@ -1,43 +0,0 @@ -# radroots_transport_nostr - -Deterministic Nostr relay transport substrate for exact signed-event publish, -fetch ingest, and outbox delivery target coordination. - -Every fetch path verifies the NIP-01 id and signature before filter matching, -unique-event budgeting, or returning an event. Repeated event ids preserve -per-relay observation evidence without consuming the unique-event limit. The -unique-event limit is bounded at 1,000 so final stored-event visibility can be -evaluated in one coherent event-store snapshot. A fetch scans at most 64,000 -raw events and 64 MiB of aggregate raw JSON, and rejects any individual raw -event over 256 KiB before Radroots parses adapter raw JSON. Count and byte -budgets are charged globally, in adapter order, before Radroots parsing, -filtering, deduplication, or accepted-event limiting, so malformed and otherwise -rejected events cannot bypass them. The official SDK adapter enforces the same -retained-prefix budgets after SDK frame/event decoding and before retaining its -serialized JSON; the connector's upstream frame parser remains responsible for -its own first-pass network limits. `Truncated` is distinct from relay `EOSE`, -including for later target relays that were not queried after a global count or -byte budget was reached. - -Fetch-ingest receipts report verification, contract admission, immutable -valid-stream eligibility, and current visibility as independent exhaustive -enums. Verification failure is not a contract-invalid result, and unsupported -admission does not hide whether the stored event is current, suppressed, or not -admitted. Persisted events obtain visibility from the event store's central -authority after the complete accepted fetch batch has been ingested, so event -receipts and aggregate visibility counts describe final post-batch state rather -than transient per-item state. Repeated receipt IDs are deduplicated before the -single snapshot lookup and then mapped back to every receipt. Ephemeral events -use the explicit `not_persisted` visibility result. -`admission_code` carries the stable classifier diagnostic when classification -produces one. Inserted, duplicate, and not-persisted persistence outcomes retain -separate flags and aggregate counts. Local event-store failures abort the -operation and remain typed transport errors, so callers can retry without -confusing storage failure with bad relay input. - -`RadrootsRelayUrlPolicy::Public` is for trusted relay configuration. It rejects -non-canonical and known non-global literal destinations, but hostname checks do -not pin DNS resolution in the SDK connector and are not an SSRF boundary for -attacker-controlled relay hostnames. Validate resolved addresses at the network -boundary or use a connector that pins approved resolutions before accepting -untrusted relay configuration. diff --git a/crates/transport_nostr/README.md b/crates/transport_nostr/README.md @@ -0,0 +1,43 @@ +# radroots-transport-nostr + +Deterministic Nostr relay transport substrate for exact signed-event publish, +fetch ingest, and outbox delivery target coordination. + +Every fetch path verifies the NIP-01 id and signature before filter matching, +unique-event budgeting, or returning an event. Repeated event ids preserve +per-relay observation evidence without consuming the unique-event limit. The +unique-event limit is bounded at 1,000 so final stored-event visibility can be +evaluated in one coherent event-store snapshot. A fetch scans at most 64,000 +raw events and 64 MiB of aggregate raw JSON, and rejects any individual raw +event over 256 KiB before Radroots parses adapter raw JSON. Count and byte +budgets are charged globally, in adapter order, before Radroots parsing, +filtering, deduplication, or accepted-event limiting, so malformed and otherwise +rejected events cannot bypass them. The official SDK adapter enforces the same +retained-prefix budgets after SDK frame/event decoding and before retaining its +serialized JSON; the connector's upstream frame parser remains responsible for +its own first-pass network limits. `Truncated` is distinct from relay `EOSE`, +including for later target relays that were not queried after a global count or +byte budget was reached. + +Fetch-ingest receipts report verification, contract admission, immutable +valid-stream eligibility, and current visibility as independent exhaustive +enums. Verification failure is not a contract-invalid result, and unsupported +admission does not hide whether the stored event is current, suppressed, or not +admitted. Persisted events obtain visibility from the event store's central +authority after the complete accepted fetch batch has been ingested, so event +receipts and aggregate visibility counts describe final post-batch state rather +than transient per-item state. Repeated receipt IDs are deduplicated before the +single snapshot lookup and then mapped back to every receipt. Ephemeral events +use the explicit `not_persisted` visibility result. +`admission_code` carries the stable classifier diagnostic when classification +produces one. Inserted, duplicate, and not-persisted persistence outcomes retain +separate flags and aggregate counts. Local event-store failures abort the +operation and remain typed transport errors, so callers can retry without +confusing storage failure with bad relay input. + +`RadrootsRelayUrlPolicy::Public` is for trusted relay configuration. It rejects +non-canonical and known non-global literal destinations, but hostname checks do +not pin DNS resolution in the SDK connector and are not an SSRF boundary for +attacker-controlled relay hostnames. Validate resolved addresses at the network +boundary or use a connector that pins approved resolutions before accepting +untrusted relay configuration. diff --git a/tools/xtask/src/architecture.rs b/tools/xtask/src/architecture.rs @@ -286,12 +286,13 @@ fn validate_public_package_metadata( .values() .find(|repository| repository.url == workspace.workspace.package.repository) .ok_or_else(|| "workspace repository has no architecture allocation".to_owned())?; - let local_packages = repository.packages.iter().collect::<BTreeSet<_>>(); + let local_packages = repository.packages.iter().cloned().collect::<BTreeSet<_>>(); let all_packages = architecture .package .iter() .map(|package| package.name.as_str()) .collect::<BTreeSet<_>>(); + let mut found_local_packages = BTreeSet::new(); for member in &workspace.workspace.members { let manifest_path = workspace_root.join(member).join("Cargo.toml"); @@ -311,11 +312,12 @@ fn validate_public_package_metadata( if !all_packages.contains(name) { continue; } - if !local_packages.contains(&name.to_owned()) { + if !local_packages.contains(name) { return Err(format!( "public package {name} belongs to a different canonical repository" )); } + found_local_packages.insert(name.to_owned()); validate_public_manifest_field( package, "version", @@ -391,6 +393,21 @@ fn validate_public_package_metadata( )); } } + if found_local_packages != local_packages { + let missing = local_packages + .difference(&found_local_packages) + .cloned() + .collect::<Vec<_>>() + .join(", "); + let extra = found_local_packages + .difference(&local_packages) + .cloned() + .collect::<Vec<_>>() + .join(", "); + return Err(format!( + "workspace public package inventory is missing: {missing}; workspace public package inventory has unallocated packages: {extra}" + )); + } Ok(()) } @@ -814,4 +831,15 @@ adr_required = false assert!(error.contains("must inherit the workspace lint policy")); let _ = fs::remove_dir_all(root); } + + #[test] + fn public_package_inventory_requires_every_local_package() { + let root = test_root("public_package_inventory"); + fs::write(root.join("Cargo.toml"), complete_workspace_manifest("")) + .expect("write workspace manifest"); + let error = validate_public_package_metadata(&root, &architecture()) + .expect_err("missing local public package must fail"); + assert!(error.contains("workspace public package inventory is missing: radroots")); + let _ = fs::remove_dir_all(root); + } } diff --git a/tools/xtask/src/contract.rs b/tools/xtask/src/contract.rs @@ -3141,6 +3141,7 @@ struct WorkspaceReleaseClassification { struct CratesReleaseArchitecture { spec_id: String, package_count: usize, + initial_version: String, repositories: CratesReleaseRepositories, package: Vec<CratesReleasePackage>, } @@ -3829,27 +3830,46 @@ fn validate_workspace_version_lockstep( )); } - let exact_requirement = format!("={contract_version}"); + let architecture_path = workspace_root.join("docs/specs/radroots_crates_release_v1.toml"); + let public_versions = if architecture_path.is_file() { + let architecture = parse_toml::<CratesReleaseArchitecture>(&architecture_path)?; + architecture + .repositories + .lib + .packages + .into_iter() + .map(|name| (name, architecture.initial_version.clone())) + .collect::<BTreeMap<_, _>>() + } else { + BTreeMap::new() + }; let mut governed_packages = BTreeMap::new(); for member in &workspace_manifest.workspace.members { let package_path = workspace_root.join(member).join("Cargo.toml"); let package = parse_toml::<VersionedPackageCargoManifest>(&package_path)?; + let expected_version = public_versions + .get(&package.package.name) + .map_or(contract_version, String::as_str); match package.package.version { - PackageVersionSource::Literal(ref version) if version == contract_version => {} + PackageVersionSource::Literal(ref version) if version == expected_version => {} PackageVersionSource::Literal(version) => { return Err(format!( - "workspace member {member} package version {version} must match contract version {contract_version}" + "workspace member {member} package version {version} must match governed version {expected_version}" )); } PackageVersionSource::Workspace { workspace } => { return Err(format!( - "workspace member {member} must set an explicit package version {contract_version}, not version.workspace = {workspace}, so mounted path consumers preserve the public package version" + "workspace member {member} must set an explicit package version {expected_version}, not version.workspace = {workspace}, so mounted path consumers preserve the governed package version" )); } } - governed_packages.insert(member.clone(), package.package.name.clone()); + governed_packages.insert( + member.clone(), + (package.package.name.clone(), expected_version.to_owned()), + ); if package.package.name.starts_with("radroots_") { + let exact_requirement = format!("={expected_version}"); let dependency = workspace_manifest .workspace .dependencies @@ -3879,25 +3899,26 @@ fn validate_workspace_version_lockstep( let Some(path) = dependency.path.as_deref() else { continue; }; - if governed_packages.contains_key(path) - && dependency.version.as_deref() != Some(exact_requirement.as_str()) - { + let Some((_, expected_version)) = governed_packages.get(path) else { + continue; + }; + let exact_requirement = format!("={expected_version}"); + if dependency.version.as_deref() != Some(exact_requirement.as_str()) { return Err(format!( "workspace path dependency {dependency_name} version must be the exact requirement {exact_requirement}" )); } } - validate_cargo_lock_version_lockstep(workspace_root, contract_version, &governed_packages) + validate_cargo_lock_version_lockstep(workspace_root, &governed_packages) } fn validate_cargo_lock_version_lockstep( workspace_root: &Path, - contract_version: &str, - governed_packages: &BTreeMap<String, String>, + governed_packages: &BTreeMap<String, (String, String)>, ) -> Result<(), String> { let lock = parse_toml::<CargoLockManifest>(&workspace_root.join("Cargo.lock"))?; - for (member, package_name) in governed_packages { + for (member, (package_name, expected_version)) in governed_packages { let workspace_entries = lock .package .iter() @@ -3908,9 +3929,9 @@ fn validate_cargo_lock_version_lockstep( "Cargo.lock must contain exactly one source-free entry for workspace member {member} ({package_name})" )); } - if workspace_entries[0].version != contract_version { + if workspace_entries[0].version != *expected_version { return Err(format!( - "Cargo.lock package {package_name} version {} must match contract version {contract_version}", + "Cargo.lock package {package_name} version {} must match governed version {expected_version}", workspace_entries[0].version )); } @@ -12444,7 +12465,7 @@ crates = ["radroots_a"] .collect::<Vec<_>>() .join(", "); let mut architecture = format!( - "spec_id = \"radroots.crates.release.v1\"\npackage_count = 19\n\n[repositories.lib]\npackages = [\"package-01\"]\n\n[repositories.sdk]\npackages = [{external_toml}]\n" + "spec_id = \"radroots.crates.release.v1\"\npackage_count = 19\ninitial_version = \"0.1.0\"\n\n[repositories.lib]\npackages = [\"package-01\"]\n\n[repositories.sdk]\npackages = [{external_toml}]\n" ); for name in &approved { architecture.push_str(&format!("\n[[package]]\nname = \"{name}\"\n")); diff --git a/tools/xtask/src/contract/food_availability_projection.rs b/tools/xtask/src/contract/food_availability_projection.rs @@ -2638,13 +2638,17 @@ fn validate_blossom_dependency_values( .and_then(toml::Value::as_table) .ok_or_else(|| "Cargo.toml must define workspace dependency radroots_blossom".to_owned())?; if workspace_dependency - .get("path") + .get("package") .and_then(toml::Value::as_str) - != Some("crates/blossom") + != Some("radroots-blossom") + || workspace_dependency + .get("path") + .and_then(toml::Value::as_str) + != Some("crates/blossom") || workspace_dependency .get("version") .and_then(toml::Value::as_str) - != Some("=1.0.0-alpha.1") + != Some("=0.1.0") || workspace_dependency .get("default-features") .and_then(toml::Value::as_bool) diff --git a/tools/xtask/src/contract/nip09_reconciliation.rs b/tools/xtask/src/contract/nip09_reconciliation.rs @@ -236,7 +236,7 @@ const GOVERNED_DEPENDENCY_TABLE_SHA256: [(&str, &str); 7] = [ ), ( "Cargo.toml#governed-workspace-dependencies", - "0aa0aeb7988745aad1101c820a340c8ac04a5833c2ec7f7c997b5d9dfac010a3", + "2a5e34872160a39daf03ffd15f32fc824e91b2c0253b4bca84c6610b0238ca32", ), ]; @@ -577,7 +577,11 @@ const RUNTIME_DEPENDENCY_ROOTS: [RuntimeDependencyRootSpec; 10] = [ #[derive(Clone, Copy)] struct CargoPackageFeatureSpec { + // The reconciliation manifest predates the Cargo package-name migration and + // records the Rust crate identifier. Keep that immutable identifier separate + // from the package name validated in the current Cargo manifest. package: &'static str, + cargo_package_name: &'static str, manifest_path: &'static str, default_features_enabled: bool, selected_features: &'static [&'static str], @@ -587,6 +591,7 @@ struct CargoPackageFeatureSpec { const CARGO_PACKAGE_FEATURE_SPECS: &[CargoPackageFeatureSpec] = &[ CargoPackageFeatureSpec { package: "radroots_core", + cargo_package_name: "radroots-core", manifest_path: CORE_CARGO_MANIFEST_RELATIVE, default_features_enabled: false, selected_features: &["serde", "std"], @@ -594,6 +599,7 @@ const CARGO_PACKAGE_FEATURE_SPECS: &[CargoPackageFeatureSpec] = &[ }, CargoPackageFeatureSpec { package: "radroots_event_store", + cargo_package_name: "radroots_event_store", manifest_path: EVENT_STORE_CARGO_MANIFEST_RELATIVE, default_features_enabled: true, selected_features: &["runtime-tokio", "sqlite"], @@ -601,6 +607,7 @@ const CARGO_PACKAGE_FEATURE_SPECS: &[CargoPackageFeatureSpec] = &[ }, CargoPackageFeatureSpec { package: "radroots_event_codec", + cargo_package_name: "radroots-event-codec", manifest_path: EVENT_CODEC_CARGO_MANIFEST_RELATIVE, default_features_enabled: false, selected_features: &["nostr", "serde", "serde_json", "std"], @@ -608,6 +615,7 @@ const CARGO_PACKAGE_FEATURE_SPECS: &[CargoPackageFeatureSpec] = &[ }, CargoPackageFeatureSpec { package: "radroots_event", + cargo_package_name: "radroots-event", manifest_path: EVENT_CARGO_MANIFEST_RELATIVE, default_features_enabled: false, selected_features: &["serde", "std"], @@ -615,6 +623,7 @@ const CARGO_PACKAGE_FEATURE_SPECS: &[CargoPackageFeatureSpec] = &[ }, CargoPackageFeatureSpec { package: "radroots_blossom", + cargo_package_name: "radroots-blossom", manifest_path: BLOSSOM_CARGO_MANIFEST_RELATIVE, default_features_enabled: false, selected_features: &["std"], @@ -14383,22 +14392,54 @@ xtask = "run -q -p xtask --" } let governed_packages = [ - (CORE_CARGO_MANIFEST_RELATIVE, "radroots_core"), - (EVENT_CARGO_MANIFEST_RELATIVE, "radroots_event"), - (EVENT_CODEC_CARGO_MANIFEST_RELATIVE, "radroots_event_codec"), - (BLOSSOM_CARGO_MANIFEST_RELATIVE, "radroots_blossom"), - (EVENT_STORE_CARGO_MANIFEST_RELATIVE, "radroots_event_store"), - (TRANSPORT_CARGO_MANIFEST_RELATIVE, "radroots_transport"), + ( + CORE_CARGO_MANIFEST_RELATIVE, + "radroots-core", + "0.1.0", + Some("radroots_core"), + ), + ( + EVENT_CARGO_MANIFEST_RELATIVE, + "radroots-event", + "0.1.0", + Some("radroots_event"), + ), + ( + EVENT_CODEC_CARGO_MANIFEST_RELATIVE, + "radroots-event-codec", + "0.1.0", + Some("radroots_event_codec"), + ), + ( + BLOSSOM_CARGO_MANIFEST_RELATIVE, + "radroots-blossom", + "0.1.0", + Some("radroots_blossom"), + ), + ( + EVENT_STORE_CARGO_MANIFEST_RELATIVE, + "radroots_event_store", + "1.0.0-alpha.1", + None, + ), + ( + TRANSPORT_CARGO_MANIFEST_RELATIVE, + "radroots-transport", + "0.1.0", + Some("radroots_transport"), + ), ]; let mut actual_identities = Vec::new(); - for (relative, expected_package_name) in governed_packages { + for (relative, expected_package_name, expected_version, expected_crate_name) in + governed_packages + { let manifest = parse_cargo_manifest(workspace_root, relative)?; let package = manifest .get("package") .and_then(toml::Value::as_table) .ok_or_else(|| format!("{relative} must declare [package]"))?; if package.get("name").and_then(toml::Value::as_str) != Some(expected_package_name) - || package.get("version").and_then(toml::Value::as_str) != Some("1.0.0-alpha.1") + || package.get("version").and_then(toml::Value::as_str) != Some(expected_version) || package .get("edition") .and_then(toml::Value::as_table) @@ -14413,9 +14454,22 @@ xtask = "run -q -p xtask --" != Some(true) { return Err(format!( - "{relative} compiler package identity must remain {expected_package_name} 1.0.0-alpha.1 with workspace edition and rust-version" + "{relative} compiler package identity must remain {expected_package_name} {expected_version} with workspace edition and rust-version" )); } + match (expected_crate_name, manifest.get("lib")) { + (None, None) => {} + (Some(expected), Some(lib)) + if lib.as_table().is_some_and(|lib| { + lib.len() == 1 + && lib.get("name").and_then(toml::Value::as_str) == Some(expected) + }) => {} + _ => { + return Err(format!( + "{relative} must preserve its exact Rust crate name without target-path authority" + )); + } + } if [ "build", "autolib", @@ -14434,7 +14488,6 @@ xtask = "run -q -p xtask --" if [ "build-dependencies", "target", - "lib", "bin", "example", "test", @@ -14671,10 +14724,10 @@ fn describe_cargo_package_features( .and_then(|package| package.get("name")) .and_then(toml::Value::as_str) .ok_or_else(|| format!("{} must declare package.name", spec.manifest_path))?; - if package_name != spec.package { + if package_name != spec.cargo_package_name { return Err(format!( "{} package.name must be {}", - spec.manifest_path, spec.package + spec.manifest_path, spec.cargo_package_name )); } let feature_table = manifest @@ -20473,8 +20526,8 @@ pub(crate) fn migration_for_version"#, let cargo_lock_path = workspace.path().join(CARGO_LOCK_RELATIVE); let cargo_lock = fs::read_to_string(&cargo_lock_path).expect("Cargo.lock"); let transport_lock_package = r#"[[package]] -name = "radroots_transport" -version = "1.0.0-alpha.1" +name = "radroots-transport" +version = "0.1.0" dependencies = [ "futures", "serde", @@ -20483,8 +20536,8 @@ dependencies = [ ] "#; let future_transport_lock_package = r#"[[package]] -name = "radroots_transport" -version = "1.0.0-alpha.1" +name = "radroots-transport" +version = "0.1.0" dependencies = [ "futures", "serde", diff --git a/tools/xtask/src/coverage.rs b/tools/xtask/src/coverage.rs @@ -4000,7 +4000,7 @@ test_threads = 0 let out = temp_dir_path("run_crate_runner"); let args = vec![ "--crate".to_string(), - "radroots_core".to_string(), + "radroots-core".to_string(), "--out".to_string(), out.display().to_string(), "--test-threads".to_string(), @@ -4033,13 +4033,13 @@ test_threads = 0 assert!( rendered_commands .iter() - .filter(|rendered| rendered.contains("report -p radroots_core")) + .filter(|rendered| rendered.contains("report -p radroots-core")) .all(|rendered| rendered.contains("--ignore-filename-regex")) ); assert!( rendered_commands .iter() - .filter(|rendered| rendered.contains("report -p radroots_core")) + .filter(|rendered| rendered.contains("report -p radroots-core")) .all(|rendered| rendered.contains(COVERAGE_EXTERNAL_IGNORE_FILENAME_REGEX)) ); fs::remove_dir_all(out).expect("remove run crate output dir"); @@ -4049,7 +4049,7 @@ test_threads = 0 fn coverage_ignore_filename_regex_excludes_external_and_sibling_workspace_paths() { let root = workspace_root(); let ignore_regex = - coverage_ignore_filename_regex(&root, "radroots_core").expect("build ignore regex"); + coverage_ignore_filename_regex(&root, "radroots-core").expect("build ignore regex"); assert!(ignore_regex.contains(COVERAGE_EXTERNAL_IGNORE_FILENAME_REGEX)); assert!(ignore_regex.contains("crates/identity")); assert!(ignore_regex.contains("crates/core/tests")); @@ -4341,7 +4341,7 @@ test_threads = 0 #[test] fn run_crate_with_runner_uses_default_output_dir_when_out_is_missing() { - let args = vec!["--crate".to_string(), "radroots_core".to_string()]; + let args = vec!["--crate".to_string(), "radroots-core".to_string()]; let mut output_path_seen = false; let mut runner = |cmd: Command, _: &str| { let rendered = cmd @@ -4371,7 +4371,7 @@ test_threads = 0 let out = temp_dir_path("run_crate_runner_fail"); let args = vec![ "--crate".to_string(), - "radroots_core".to_string(), + "radroots-core".to_string(), "--out".to_string(), out.display().to_string(), ]; @@ -4384,7 +4384,7 @@ test_threads = 0 write_file(&root.join("blocker"), "x"); let args = vec![ "--crate".to_string(), - "radroots_core".to_string(), + "radroots-core".to_string(), "--out".to_string(), root.join("blocker").join("nested").display().to_string(), ];