lib

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

commit f8867cd6f4608933ae888f5c08f9befb459d3bdd
parent 1e33a55a7b58c3c7296a19655126b76a518b9dde
Author: triesap <tyson@radroots.org>
Date:   Mon, 27 Jul 2026 20:05:15 +0000

contract: execute Phase 1 feature matrix

- Govern every affected host and wasm-relevant profile in one checked-in inventory.
- Validate package, category, target, and transitive feature implications structurally.
- Run the matrix as an isolated Nix check from the staged candidate.
- Align replica sync no-default builds with its documented std database boundary.

Diffstat:
Mbuild/nix/checks.nix | 11+++++++++++
Mbuild/nix/common.nix | 32+++++++++++++++++++++++++++++++-
Mcontracts/outbox_feature_matrix.toml | 14++++++++++++--
Acontracts/phase1_feature_matrix.toml | 687+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcontracts/semantic_validator_inventory.toml | 83++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mcrates/outbox/contracts/migration_authority_v1.manifest.json | 12++++++------
Mcrates/outbox/contracts/migration_authority_v1.manifest.sha256 | 2+-
Mcrates/replica_sync/Cargo.toml | 4+++-
Mcrates/replica_sync/src/error.rs | 2+-
Mcrates/replica_sync/src/lib.rs | 9++++++++-
Mcrates/transport_nostr/Cargo.toml | 4+++-
Mtools/xtask/src/contract/feature_support.rs | 366++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
12 files changed, 1200 insertions(+), 26 deletions(-)

diff --git a/build/nix/checks.nix b/build/nix/checks.nix @@ -62,6 +62,16 @@ let installPhaseCommand = "mkdir -p $out"; } ); + phase1FeatureMatrixCheck = common.craneLib.mkCargoDerivation ( + common.commonCraneArgs + // { + inherit (common) cargoArtifacts; + pname = "radroots-phase1-feature-matrix-check"; + doCheck = false; + buildPhaseCargoCommand = common.phase1FeatureMatrixCommand; + installPhaseCommand = "mkdir -p $out"; + } + ); blossomRasterDecodeTest = common.craneLib.mkCargoDerivation ( common.commonCraneArgs // { @@ -122,6 +132,7 @@ in nostr-bare-check = nostrBareCheck; blossom-raster-decode-test = blossomRasterDecodeTest; outbox-feature-matrix = outboxFeatureMatrixCheck; + phase1-feature-matrix = phase1FeatureMatrixCheck; blossom-decoder-fuzz-smoke = common.mkRepoCheck { name = "radroots-blossom-decoder-fuzz-smoke"; runtimeInputs = common.runtimeInputs.decoderSecurityFuzz; diff --git a/build/nix/common.nix b/build/nix/common.nix @@ -7,7 +7,12 @@ let root = ../..; cargoToml = builtins.fromTOML (builtins.readFile ../../Cargo.toml); - outboxFeatureMatrix = builtins.fromTOML (builtins.readFile ../../contracts/outbox_feature_matrix.toml); + outboxFeatureMatrix = builtins.fromTOML ( + builtins.readFile ../../contracts/outbox_feature_matrix.toml + ); + phase1FeatureMatrix = builtins.fromTOML ( + builtins.readFile ../../contracts/phase1_feature_matrix.toml + ); version = cargoToml.workspace.package.version; darwinBuildInputs = lib.optionals pkgs.stdenv.isDarwin [ pkgs.libiconv @@ -231,6 +236,30 @@ let profile: "cargo check -q -p ${lib.escapeShellArg outboxFeatureMatrix.package} ${lib.escapeShellArgs profile.cargo_args}" ) outboxFeatureMatrix.profiles; + phase1FeatureMatrixCommand = lib.concatMapStringsSep "\n" ( + profile: + let + args = [ + "check" + "-q" + "-p" + profile.package + ] + ++ lib.optional profile.no_default_features "--no-default-features" + ++ lib.optional profile.all_features "--all-features" + ++ lib.optionals (profile.features != [ ]) [ + "--features" + (lib.concatStringsSep "," profile.features) + ] + ++ lib.optional profile.all_targets "--all-targets" + ++ lib.optional (!profile.all_targets) "--lib" + ++ lib.optionals (profile.target != "host") [ + "--target" + profile.target + ]; + in + "cargo ${lib.escapeShellArgs args}" + ) phase1FeatureMatrix.profiles; contractCommand = '' cargo run -q -p xtask -- hygiene forbidden-identifiers cargo check -q ${coreContractCargoArgs} @@ -583,6 +612,7 @@ in fuzzCargoDeps mkRepoCheck outboxFeatureMatrixCommand + phase1FeatureMatrixCommand releasePreflightCommand coreContractCargoArgs sharedEnv diff --git a/contracts/outbox_feature_matrix.toml b/contracts/outbox_feature_matrix.toml @@ -28,11 +28,21 @@ cargo_args = ["--no-default-features", "--features", "sqlite", "--all-targets"] [[profiles]] id = "sqlite-runtime-tokio" -cargo_args = ["--no-default-features", "--features", "sqlite,runtime-tokio", "--all-targets"] +cargo_args = [ + "--no-default-features", + "--features", + "sqlite,runtime-tokio", + "--all-targets", +] [[profiles]] id = "event-store-adapter" -cargo_args = ["--no-default-features", "--features", "event-store-adapter", "--all-targets"] +cargo_args = [ + "--no-default-features", + "--features", + "event-store-adapter", + "--all-targets", +] [[profiles]] id = "all-features" diff --git a/contracts/phase1_feature_matrix.toml b/contracts/phase1_feature_matrix.toml @@ -0,0 +1,687 @@ +schema_version = 1 +wasm_target = "wasm32-unknown-unknown" +required_categories = [ + "all-features", + "minimal", + "raster", + "runtime", + "serde", + "signature", + "sqlite", + "std", + "wasm", +] + +[packages] +radroots_authority = "crates/authority/Cargo.toml" +radroots_blossom = "crates/blossom/Cargo.toml" +radroots_event = "crates/event/Cargo.toml" +radroots_event_codec = "crates/event_codec/Cargo.toml" +radroots_event_store = "crates/event_store/Cargo.toml" +radroots_nostr = "crates/nostr/Cargo.toml" +radroots_outbox = "crates/outbox/Cargo.toml" +radroots_replica_sync = "crates/replica_sync/Cargo.toml" +radroots_runtime = "crates/runtime/Cargo.toml" +radroots_transport = "crates/transport/Cargo.toml" +radroots_transport_nostr = "crates/transport_nostr/Cargo.toml" +radroots_transport_reticulum = "crates/transport_reticulum/Cargo.toml" + +[[profiles]] +id = "authority-minimal" +package = "radroots_authority" +category = "minimal" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = false +target = "host" + +[[profiles]] +id = "authority-std" +package = "radroots_authority" +category = "std" +no_default_features = true +all_features = false +features = ["std"] +requires = ["std"] +all_targets = false +target = "host" + +[[profiles]] +id = "authority-local-signer" +package = "radroots_authority" +category = "signature" +no_default_features = true +all_features = false +features = ["local_signer"] +requires = ["std"] +all_targets = true +target = "host" + +[[profiles]] +id = "authority-all-features" +package = "radroots_authority" +category = "all-features" +no_default_features = false +all_features = true +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "blossom-minimal" +package = "radroots_blossom" +category = "minimal" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = false +target = "host" + +[[profiles]] +id = "blossom-serde" +package = "radroots_blossom" +category = "serde" +no_default_features = true +all_features = false +features = ["serde"] +requires = ["serde"] +all_targets = false +target = "host" + +[[profiles]] +id = "blossom-std" +package = "radroots_blossom" +category = "std" +no_default_features = true +all_features = false +features = ["std"] +requires = ["std"] +all_targets = false +target = "host" + +[[profiles]] +id = "blossom-raster" +package = "radroots_blossom" +category = "raster" +no_default_features = true +all_features = false +features = ["raster-decode", "serde"] +requires = ["serde", "std"] +all_targets = true +target = "host" + +[[profiles]] +id = "blossom-all-features" +package = "radroots_blossom" +category = "all-features" +no_default_features = false +all_features = true +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "blossom-wasm-serde" +package = "radroots_blossom" +category = "wasm" +no_default_features = true +all_features = false +features = ["serde"] +requires = ["serde"] +all_targets = false +target = "wasm32-unknown-unknown" + +[[profiles]] +id = "event-minimal" +package = "radroots_event" +category = "minimal" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = false +target = "host" + +[[profiles]] +id = "event-serde" +package = "radroots_event" +category = "serde" +no_default_features = true +all_features = false +features = ["serde"] +requires = ["serde"] +all_targets = false +target = "host" + +[[profiles]] +id = "event-std" +package = "radroots_event" +category = "std" +no_default_features = true +all_features = false +features = ["std"] +requires = ["std"] +all_targets = false +target = "host" + +[[profiles]] +id = "event-signature" +package = "radroots_event" +category = "signature" +no_default_features = true +all_features = false +features = ["signature"] +requires = ["signature"] +all_targets = false +target = "host" + +[[profiles]] +id = "event-all-features" +package = "radroots_event" +category = "all-features" +no_default_features = false +all_features = true +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "event-wasm-serde" +package = "radroots_event" +category = "wasm" +no_default_features = true +all_features = false +features = ["serde"] +requires = ["serde"] +all_targets = false +target = "wasm32-unknown-unknown" + +[[profiles]] +id = "event-codec-minimal" +package = "radroots_event_codec" +category = "minimal" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = false +target = "host" + +[[profiles]] +id = "event-codec-serde" +package = "radroots_event_codec" +category = "serde" +no_default_features = true +all_features = false +features = ["serde"] +requires = ["serde"] +all_targets = false +target = "host" + +[[profiles]] +id = "event-codec-serde-json" +package = "radroots_event_codec" +category = "serde" +no_default_features = true +all_features = false +features = ["serde_json"] +requires = ["serde"] +all_targets = false +target = "host" + +[[profiles]] +id = "event-codec-std" +package = "radroots_event_codec" +category = "std" +no_default_features = true +all_features = false +features = ["std"] +requires = ["std"] +all_targets = false +target = "host" + +[[profiles]] +id = "event-codec-nostr" +package = "radroots_event_codec" +category = "signature" +no_default_features = true +all_features = false +features = ["nostr", "serde_json"] +requires = ["serde", "std"] +all_targets = true +target = "host" + +[[profiles]] +id = "event-codec-all-features" +package = "radroots_event_codec" +category = "all-features" +no_default_features = false +all_features = true +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "event-codec-wasm-serde-json" +package = "radroots_event_codec" +category = "wasm" +no_default_features = true +all_features = false +features = ["serde_json"] +requires = ["serde"] +all_targets = false +target = "wasm32-unknown-unknown" + +[[profiles]] +id = "event-store-minimal" +package = "radroots_event_store" +category = "minimal" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = false +target = "host" + +[[profiles]] +id = "event-store-sqlite" +package = "radroots_event_store" +category = "sqlite" +no_default_features = true +all_features = false +features = ["sqlite"] +requires = ["sqlite"] +all_targets = true +target = "host" + +[[profiles]] +id = "event-store-runtime" +package = "radroots_event_store" +category = "runtime" +no_default_features = true +all_features = false +features = ["runtime-tokio", "sqlite"] +requires = ["runtime-tokio", "sqlite"] +all_targets = true +target = "host" + +[[profiles]] +id = "event-store-all-features" +package = "radroots_event_store" +category = "all-features" +no_default_features = false +all_features = true +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "nostr-minimal" +package = "radroots_nostr" +category = "minimal" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = false +target = "host" + +[[profiles]] +id = "nostr-std" +package = "radroots_nostr" +category = "std" +no_default_features = true +all_features = false +features = ["std"] +requires = ["std"] +all_targets = false +target = "host" + +[[profiles]] +id = "nostr-codec" +package = "radroots_nostr" +category = "serde" +no_default_features = true +all_features = false +features = ["codec"] +requires = ["codec"] +all_targets = false +target = "host" + +[[profiles]] +id = "nostr-events" +package = "radroots_nostr" +category = "signature" +no_default_features = true +all_features = false +features = ["events"] +requires = ["std"] +all_targets = true +target = "host" + +[[profiles]] +id = "nostr-client" +package = "radroots_nostr" +category = "runtime" +no_default_features = true +all_features = false +features = ["client"] +requires = ["std"] +all_targets = false +target = "host" + +[[profiles]] +id = "nostr-all-features" +package = "radroots_nostr" +category = "all-features" +no_default_features = false +all_features = true +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "outbox-minimal" +package = "radroots_outbox" +category = "minimal" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "outbox-sqlite" +package = "radroots_outbox" +category = "sqlite" +no_default_features = true +all_features = false +features = ["sqlite"] +requires = ["sqlite"] +all_targets = true +target = "host" + +[[profiles]] +id = "outbox-runtime" +package = "radroots_outbox" +category = "runtime" +no_default_features = true +all_features = false +features = ["runtime-tokio", "sqlite"] +requires = ["runtime-tokio", "sqlite"] +all_targets = true +target = "host" + +[[profiles]] +id = "outbox-event-store-adapter" +package = "radroots_outbox" +category = "runtime" +no_default_features = true +all_features = false +features = ["event-store-adapter"] +requires = ["runtime-tokio", "sqlite"] +all_targets = true +target = "host" + +[[profiles]] +id = "outbox-all-features" +package = "radroots_outbox" +category = "all-features" +no_default_features = false +all_features = true +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "replica-sync-minimal" +package = "radroots_replica_sync" +category = "minimal" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = false +target = "host" + +[[profiles]] +id = "replica-sync-std" +package = "radroots_replica_sync" +category = "std" +no_default_features = true +all_features = false +features = ["std"] +requires = ["std"] +all_targets = true +target = "host" + +[[profiles]] +id = "replica-sync-legacy" +package = "radroots_replica_sync" +category = "runtime" +no_default_features = true +all_features = false +features = ["legacy-ingest"] +requires = ["std"] +all_targets = true +target = "host" + +[[profiles]] +id = "replica-sync-all-features" +package = "radroots_replica_sync" +category = "all-features" +no_default_features = false +all_features = true +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "replica-sync-wasm-minimal" +package = "radroots_replica_sync" +category = "wasm" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = false +target = "wasm32-unknown-unknown" + +[[profiles]] +id = "runtime-minimal" +package = "radroots_runtime" +category = "minimal" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "runtime-transport" +package = "radroots_runtime" +category = "runtime" +no_default_features = true +all_features = false +features = ["transport"] +requires = ["transport"] +all_targets = true +target = "host" + +[[profiles]] +id = "runtime-workers" +package = "radroots_runtime" +category = "runtime" +no_default_features = true +all_features = false +features = ["transport-workers"] +requires = ["transport"] +all_targets = true +target = "host" + +[[profiles]] +id = "runtime-all-features" +package = "radroots_runtime" +category = "all-features" +no_default_features = false +all_features = true +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "transport-minimal" +package = "radroots_transport" +category = "minimal" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = false +target = "host" + +[[profiles]] +id = "transport-serde" +package = "radroots_transport" +category = "serde" +no_default_features = true +all_features = false +features = ["serde"] +requires = ["serde"] +all_targets = true +target = "host" + +[[profiles]] +id = "transport-all-features" +package = "radroots_transport" +category = "all-features" +no_default_features = false +all_features = true +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "transport-wasm-serde" +package = "radroots_transport" +category = "wasm" +no_default_features = true +all_features = false +features = ["serde"] +requires = ["serde"] +all_targets = false +target = "wasm32-unknown-unknown" + +[[profiles]] +id = "transport-nostr-minimal" +package = "radroots_transport_nostr" +category = "minimal" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "transport-nostr-std" +package = "radroots_transport_nostr" +category = "std" +no_default_features = true +all_features = false +features = ["std"] +requires = ["std"] +all_targets = true +target = "host" + +[[profiles]] +id = "transport-nostr-client" +package = "radroots_transport_nostr" +category = "runtime" +no_default_features = true +all_features = false +features = ["client", "std"] +requires = ["client", "std"] +all_targets = true +target = "host" + +[[profiles]] +id = "transport-nostr-runtime" +package = "radroots_transport_nostr" +category = "runtime" +no_default_features = true +all_features = false +features = ["runtime-tokio", "std"] +requires = ["client", "storage", "std"] +all_targets = true +target = "host" + +[[profiles]] +id = "transport-nostr-all-features" +package = "radroots_transport_nostr" +category = "all-features" +no_default_features = false +all_features = true +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "transport-reticulum-minimal" +package = "radroots_transport_reticulum" +category = "minimal" +no_default_features = true +all_features = false +features = [] +requires = [] +all_targets = false +target = "host" + +[[profiles]] +id = "transport-reticulum-serde" +package = "radroots_transport_reticulum" +category = "serde" +no_default_features = true +all_features = false +features = ["serde"] +requires = ["serde"] +all_targets = true +target = "host" + +[[profiles]] +id = "transport-reticulum-all-features" +package = "radroots_transport_reticulum" +category = "all-features" +no_default_features = false +all_features = true +features = [] +requires = [] +all_targets = true +target = "host" + +[[profiles]] +id = "transport-reticulum-wasm-serde" +package = "radroots_transport_reticulum" +category = "wasm" +no_default_features = true +all_features = false +features = ["serde"] +requires = ["serde"] +all_targets = false +target = "wasm32-unknown-unknown" diff --git a/contracts/semantic_validator_inventory.toml b/contracts/semantic_validator_inventory.toml @@ -11,7 +11,15 @@ byte_hash_scopes = ["generated_contract_artifacts"] [[validators]] id = "event_store_successors" semantic = true -parsers = ["executable_vector", "governed_bytes", "json", "json_schema", "rust_ast", "sqlite_catalog", "toml"] +parsers = [ + "executable_vector", + "governed_bytes", + "json", + "json_schema", + "rust_ast", + "sqlite_catalog", + "toml", +] implementation_paths = [ "tools/xtask/src/contract/food_availability_projection.rs", "tools/xtask/src/contract/nip09_reconciliation.rs", @@ -23,14 +31,23 @@ governed_inputs = [ "crates/event_store/migrations", "crates/event_store/src", ] -byte_hash_scopes = ["canonical_vectors_and_mirrors", "generated_contract_artifacts", "sql_migrations"] +byte_hash_scopes = [ + "canonical_vectors_and_mirrors", + "generated_contract_artifacts", + "sql_migrations", +] [[validators]] id = "feature_support" semantic = true parsers = ["rust_ast", "toml"] implementation_paths = ["tools/xtask/src/contract/feature_support.rs"] -governed_inputs = ["Cargo.toml", "crates/nostr/Cargo.toml", "crates/nostr/src/lib.rs"] +governed_inputs = [ + "Cargo.toml", + "contracts/phase1_feature_matrix.toml", + "crates/nostr/Cargo.toml", + "crates/nostr/src/lib.rs", +] [[validators]] id = "immutable_predecessors" @@ -53,7 +70,14 @@ byte_hash_scopes = ["immutable_predecessor_artifacts"] [[validators]] id = "operations_and_boundaries" semantic = true -parsers = ["executable_vector", "governed_bytes", "json", "markdown_table", "rust_ast", "toml"] +parsers = [ + "executable_vector", + "governed_bytes", + "json", + "markdown_table", + "rust_ast", + "toml", +] implementation_paths = [ "tools/xtask/src/contract.rs", "tools/xtask/src/contract/admission_authority.rs", @@ -75,7 +99,15 @@ byte_hash_scopes = ["canonical_vectors_and_mirrors"] [[validators]] id = "outbox_successors" semantic = true -parsers = ["executable_vector", "governed_bytes", "json", "json_schema", "rust_ast", "sqlite_catalog", "toml"] +parsers = [ + "executable_vector", + "governed_bytes", + "json", + "json_schema", + "rust_ast", + "sqlite_catalog", + "toml", +] implementation_paths = [ "tools/xtask/src/contract/outbox_migration.rs", "tools/xtask/src/contract/outbox_phase1_publication.rs", @@ -88,12 +120,23 @@ governed_inputs = [ "crates/outbox/migrations", "crates/outbox/src", ] -byte_hash_scopes = ["canonical_vectors_and_mirrors", "generated_contract_artifacts", "sql_migrations"] +byte_hash_scopes = [ + "canonical_vectors_and_mirrors", + "generated_contract_artifacts", + "sql_migrations", +] [[validators]] id = "phase1_media_successors" semantic = true -parsers = ["executable_vector", "governed_bytes", "json", "json_schema", "rust_ast", "toml"] +parsers = [ + "executable_vector", + "governed_bytes", + "json", + "json_schema", + "rust_ast", + "toml", +] implementation_paths = [ "tools/xtask/src/contract/blossom_raster_decoder_security.rs", "tools/xtask/src/contract/phase1_publication_media_readiness.rs", @@ -108,20 +151,35 @@ governed_inputs = [ "fuzz/corpus", "fuzz/fuzz_targets", ] -byte_hash_scopes = ["canonical_vectors_and_mirrors", "generated_contract_artifacts", "raw_fuzz_seeds"] +byte_hash_scopes = [ + "canonical_vectors_and_mirrors", + "generated_contract_artifacts", + "raw_fuzz_seeds", +] [[validators]] id = "registry_v7" semantic = true parsers = ["governed_bytes", "json", "json_schema"] implementation_paths = ["tools/xtask/src/contract/registry_v7.rs"] -governed_inputs = ["contracts/event_store", "crates/event/src/contract/registry_v7.rs"] +governed_inputs = [ + "contracts/event_store", + "crates/event/src/contract/registry_v7.rs", +] byte_hash_scopes = ["generated_contract_artifacts"] [[validators]] id = "release_closure" semantic = true -parsers = ["cargo_metadata", "git_object", "governed_bytes", "json", "json_schema", "tar_archive", "toml"] +parsers = [ + "cargo_metadata", + "git_object", + "governed_bytes", + "json", + "json_schema", + "tar_archive", + "toml", +] implementation_paths = [ "tools/xtask/src/contract/release_package.rs", "tools/xtask/src/contract/release_provenance.rs", @@ -134,4 +192,7 @@ id = "validator_inventory" semantic = true parsers = ["rust_ast", "toml"] implementation_paths = ["tools/xtask/src/contract/validator_inventory.rs"] -governed_inputs = ["contracts/semantic_validator_inventory.toml", "tools/xtask/src/contract"] +governed_inputs = [ + "contracts/semantic_validator_inventory.toml", + "tools/xtask/src/contract", +] diff --git a/crates/outbox/contracts/migration_authority_v1.manifest.json b/crates/outbox/contracts/migration_authority_v1.manifest.json @@ -82,10 +82,10 @@ } ], "source": { - "byte_length": 971, + "byte_length": 993, "hash_algorithm": "sha256_bytes_v1", "path": "contracts/outbox_feature_matrix.toml", - "sha256": "a668160e547ac00b6ff9bb8d64277190fc62eb45a63ec343b6b1771440c398cb" + "sha256": "7489116bba5bf46c2f2f0e73176bd7906387944b8b507466b6b78cdfc41e4fbe" } }, "generated_runtime": { @@ -323,19 +323,19 @@ }, { "file": { - "byte_length": 21116, + "byte_length": 22002, "hash_algorithm": "sha256_bytes_v1", "path": "build/nix/common.nix", - "sha256": "37ee0871648c4ac379935e7c3030384c297d7ca715999e5b4347daaf31674cb7" + "sha256": "6ba87f6039ee7a7e06c1179293bb46cf2b5137a6aaa4f0fecbe61e27caa2259e" }, "role": "nix_feature_executor" }, { "file": { - "byte_length": 4987, + "byte_length": 5378, "hash_algorithm": "sha256_bytes_v1", "path": "build/nix/checks.nix", - "sha256": "8fb9d256a234a91c2f863594aa6962dcc458bce82d499dd6a993811b11182e2c" + "sha256": "ce66cbf81365342c87ba5b994d69c6b4d55e2f4d9c9f823b8b53575a8eebe758" }, "role": "nix_feature_check" }, diff --git a/crates/outbox/contracts/migration_authority_v1.manifest.sha256 b/crates/outbox/contracts/migration_authority_v1.manifest.sha256 @@ -1 +1 @@ -264233c2333f787390e9d394e63a6818a8067998bab95473fcf5ec132feaaad4 +c68aa083a8877e74aa597e3920321cf570080b29888e7b10c300623a429602fe diff --git a/crates/replica_sync/Cargo.toml b/crates/replica_sync/Cargo.toml @@ -20,6 +20,8 @@ default = ["std"] std = [ "radroots_event/std", "radroots_event_codec/std", + "dep:radroots_replica_store", + "radroots_replica_store/std", "radroots_sql_core/std", ] legacy-ingest = ["std", "radroots_event_codec/nostr", "dep:base64", "dep:uuid"] @@ -39,7 +41,7 @@ radroots_event_codec = { workspace = true, default-features = false, features = ] } radroots_sql_core = { workspace = true } radroots_replica_schema = { workspace = true } -radroots_replica_store = { workspace = true } +radroots_replica_store = { workspace = true, optional = true } hex = { workspace = true } serde = { workspace = true, default-features = false, features = [ "alloc", diff --git a/crates/replica_sync/src/error.rs b/crates/replica_sync/src/error.rs @@ -1,5 +1,5 @@ #[cfg(not(feature = "std"))] -use alloc::string::{String, ToString}; +use alloc::string::String; use core::fmt; diff --git a/crates/replica_sync/src/lib.rs b/crates/replica_sync/src/lib.rs @@ -5,16 +5,22 @@ #[cfg(not(feature = "std"))] extern crate alloc; +#[cfg(feature = "std")] mod canonical; +#[cfg(feature = "std")] pub mod emit; pub mod error; +#[cfg(feature = "std")] mod event_head; +#[cfg(feature = "std")] mod geo; #[cfg(feature = "legacy-ingest")] pub mod ingest; +#[cfg(feature = "std")] pub mod sync_state; pub mod types; +#[cfg(feature = "std")] pub use emit::{ radroots_replica_farm_event, radroots_replica_list_set_events, radroots_replica_membership_claim_events, radroots_replica_plot_events, @@ -26,6 +32,7 @@ pub use ingest::{ RadrootsReplicaIdFactory, RadrootsReplicaIngestOutcome, radroots_replica_ingest_event_head, radroots_replica_ingest_event_with_factory, }; +#[cfg(feature = "std")] pub use sync_state::{ RadrootsReplicaPendingPublishBatch, RadrootsReplicaPendingPublishEvent, RadrootsReplicaSyncStatus, radroots_replica_pending_publish_batch, @@ -39,5 +46,5 @@ pub use types::{ #[cfg(feature = "legacy-ingest")] pub use ingest::{RadrootsReplicaDefaultIdFactory, radroots_replica_ingest_event}; -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests; diff --git a/crates/transport_nostr/Cargo.toml b/crates/transport_nostr/Cargo.toml @@ -59,7 +59,9 @@ radroots_outbox = { workspace = true, optional = true, default-features = false, "sqlite", "runtime-tokio", ] } -radroots_transport = { workspace = true, default-features = false, features = ["serde"] } +radroots_transport = { workspace = true, default-features = false, features = [ + "serde", +] } futures = { workspace = true } hex = { workspace = true } nostr = { workspace = true } diff --git a/tools/xtask/src/contract/feature_support.rs b/tools/xtask/src/contract/feature_support.rs @@ -1,3 +1,5 @@ +use serde::Deserialize; +use std::collections::{BTreeMap, BTreeSet}; use std::fs; use std::path::Path; use syn::parse::Parser; @@ -6,13 +8,330 @@ use syn::{Attribute, Meta, Token}; const NOSTR_MANIFEST_RELATIVE: &str = "crates/nostr/Cargo.toml"; const NOSTR_LIB_RELATIVE: &str = "crates/nostr/src/lib.rs"; +const PHASE1_FEATURE_MATRIX_RELATIVE: &str = "contracts/phase1_feature_matrix.toml"; +const WASM_TARGET: &str = "wasm32-unknown-unknown"; +const REQUIRED_CATEGORIES: &[FeatureCategory] = &[ + FeatureCategory::AllFeatures, + FeatureCategory::Minimal, + FeatureCategory::Raster, + FeatureCategory::Runtime, + FeatureCategory::Serde, + FeatureCategory::Signature, + FeatureCategory::Sqlite, + FeatureCategory::Std, + FeatureCategory::Wasm, +]; +const AFFECTED_PACKAGES: &[(&str, &str)] = &[ + ("radroots_authority", "crates/authority/Cargo.toml"), + ("radroots_blossom", "crates/blossom/Cargo.toml"), + ("radroots_event", "crates/event/Cargo.toml"), + ("radroots_event_codec", "crates/event_codec/Cargo.toml"), + ("radroots_event_store", "crates/event_store/Cargo.toml"), + ("radroots_nostr", "crates/nostr/Cargo.toml"), + ("radroots_outbox", "crates/outbox/Cargo.toml"), + ("radroots_replica_sync", "crates/replica_sync/Cargo.toml"), + ("radroots_runtime", "crates/runtime/Cargo.toml"), + ("radroots_transport", "crates/transport/Cargo.toml"), + ( + "radroots_transport_nostr", + "crates/transport_nostr/Cargo.toml", + ), + ( + "radroots_transport_reticulum", + "crates/transport_reticulum/Cargo.toml", + ), +]; + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct Phase1FeatureMatrix { + schema_version: u32, + wasm_target: String, + required_categories: Vec<FeatureCategory>, + packages: BTreeMap<String, String>, + profiles: Vec<FeatureProfile>, +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "kebab-case")] +enum FeatureCategory { + AllFeatures, + Minimal, + Raster, + Runtime, + Serde, + Signature, + Sqlite, + Std, + Wasm, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct FeatureProfile { + id: String, + package: String, + category: FeatureCategory, + no_default_features: bool, + all_features: bool, + features: Vec<String>, + requires: Vec<String>, + all_targets: bool, + target: String, +} pub(super) fn validate_feature_support(workspace_root: &Path) -> Result<(), String> { validate_nostr_manifest(workspace_root)?; let path = workspace_root.join(NOSTR_LIB_RELATIVE); let source = fs::read_to_string(&path).map_err(|error| format!("read {}: {error}", path.display()))?; - validate_nostr_std_only_source(&source) + validate_nostr_std_only_source(&source)?; + let matrix = load_phase1_feature_matrix(workspace_root)?; + validate_phase1_feature_matrix(workspace_root, &matrix) +} + +fn load_phase1_feature_matrix(workspace_root: &Path) -> Result<Phase1FeatureMatrix, String> { + let path = workspace_root.join(PHASE1_FEATURE_MATRIX_RELATIVE); + let source = + fs::read_to_string(&path).map_err(|error| format!("read {}: {error}", path.display()))?; + toml::from_str(&source).map_err(|error| format!("parse {}: {error}", path.display())) +} + +fn validate_phase1_feature_matrix( + workspace_root: &Path, + matrix: &Phase1FeatureMatrix, +) -> Result<(), String> { + if matrix.schema_version != 1 || matrix.wasm_target != WASM_TARGET { + return Err( + "Phase 1 feature matrix identity must remain schema v1 with the governed wasm target" + .to_owned(), + ); + } + let expected_categories = REQUIRED_CATEGORIES.iter().copied().collect::<BTreeSet<_>>(); + let actual_categories = unique_values( + matrix.required_categories.iter().copied(), + "Phase 1 required feature categories", + )?; + if actual_categories != expected_categories { + return Err("Phase 1 feature matrix required categories drifted".to_owned()); + } + let expected_packages = AFFECTED_PACKAGES + .iter() + .map(|(package, manifest)| ((*package).to_owned(), (*manifest).to_owned())) + .collect::<BTreeMap<_, _>>(); + if matrix.packages != expected_packages { + return Err("Phase 1 feature matrix affected package inventory drifted".to_owned()); + } + + let mut cargo_features = BTreeMap::new(); + for (package, relative) in &matrix.packages { + cargo_features.insert( + package.as_str(), + load_cargo_features(workspace_root, package, relative)?, + ); + } + + let mut ids = BTreeSet::new(); + let mut observed_categories = BTreeSet::new(); + let mut package_categories = BTreeMap::<&str, BTreeSet<FeatureCategory>>::new(); + for profile in &matrix.profiles { + if profile.id.trim().is_empty() || !ids.insert(profile.id.as_str()) { + return Err(format!( + "Phase 1 feature profile id must be nonempty and unique: {}", + profile.id + )); + } + let features = cargo_features + .get(profile.package.as_str()) + .ok_or_else(|| { + format!( + "Phase 1 feature profile {} references unknown package {}", + profile.id, profile.package + ) + })?; + validate_feature_profile(profile, features)?; + observed_categories.insert(profile.category); + package_categories + .entry(profile.package.as_str()) + .or_default() + .insert(profile.category); + } + if observed_categories != expected_categories { + return Err("Phase 1 feature profiles do not execute every required category".to_owned()); + } + for package in matrix.packages.keys() { + let categories = package_categories + .get(package.as_str()) + .ok_or_else(|| format!("Phase 1 feature matrix has no profiles for {package}"))?; + if !categories.contains(&FeatureCategory::Minimal) + || !categories.contains(&FeatureCategory::AllFeatures) + { + return Err(format!( + "Phase 1 feature matrix must execute minimal and all-features profiles for {package}" + )); + } + } + Ok(()) +} + +fn load_cargo_features( + workspace_root: &Path, + expected_package: &str, + relative: &str, +) -> Result<BTreeMap<String, Vec<String>>, String> { + let path = workspace_root.join(relative); + let source = + fs::read_to_string(&path).map_err(|error| format!("read {}: {error}", path.display()))?; + let manifest = toml::from_str::<toml::Value>(&source) + .map_err(|error| format!("parse {}: {error}", path.display()))?; + let actual_package = manifest + .get("package") + .and_then(|value| value.get("name")) + .and_then(toml::Value::as_str) + .ok_or_else(|| format!("{relative} must declare package.name"))?; + if actual_package != expected_package { + return Err(format!( + "{relative} package name drifted: expected {expected_package}, found {actual_package}" + )); + } + let table = manifest + .get("features") + .and_then(toml::Value::as_table) + .ok_or_else(|| format!("{relative} must declare [features]"))?; + table + .iter() + .map(|(feature, value)| { + let edges = value + .as_array() + .ok_or_else(|| format!("{relative} feature {feature} must be an array"))? + .iter() + .map(|edge| { + edge.as_str().map(str::to_owned).ok_or_else(|| { + format!("{relative} feature {feature} edges must be strings") + }) + }) + .collect::<Result<Vec<_>, _>>()?; + Ok((feature.clone(), edges)) + }) + .collect() +} + +fn validate_feature_profile( + profile: &FeatureProfile, + feature_edges: &BTreeMap<String, Vec<String>>, +) -> Result<(), String> { + let selected = unique_values( + profile.features.iter().map(String::as_str), + &format!("Phase 1 feature profile {} features", profile.id), + )?; + let required = unique_values( + profile.requires.iter().map(String::as_str), + &format!("Phase 1 feature profile {} requirements", profile.id), + )?; + if profile.all_features && (!selected.is_empty() || !required.is_empty()) { + return Err(format!( + "Phase 1 all-features profile {} must not duplicate explicit features", + profile.id + )); + } + for feature in selected.iter().chain(&required) { + if !feature_edges.contains_key(*feature) { + return Err(format!( + "Phase 1 feature profile {} references undeclared feature {feature}", + profile.id + )); + } + } + match profile.category { + FeatureCategory::Minimal => { + if !profile.no_default_features + || profile.all_features + || !selected.is_empty() + || profile.target != "host" + { + return Err(format!( + "Phase 1 minimal profile {} must be a host no-default check", + profile.id + )); + } + } + FeatureCategory::AllFeatures => { + if profile.no_default_features + || !profile.all_features + || !profile.all_targets + || profile.target != "host" + { + return Err(format!( + "Phase 1 all-features profile {} must be a host all-target check", + profile.id + )); + } + } + FeatureCategory::Wasm => { + if !profile.no_default_features + || profile.all_features + || profile.all_targets + || profile.target != WASM_TARGET + { + return Err(format!( + "Phase 1 wasm profile {} must be a no-default wasm lib check", + profile.id + )); + } + } + _ => { + if !profile.no_default_features + || profile.all_features + || selected.is_empty() + || profile.target != "host" + { + return Err(format!( + "Phase 1 named profile {} must select explicit host features", + profile.id + )); + } + } + } + + let mut closure = if profile.all_features { + feature_edges + .keys() + .map(String::as_str) + .collect::<BTreeSet<_>>() + } else { + selected + }; + if !profile.no_default_features { + closure.insert("default"); + } + let mut pending = closure.iter().copied().collect::<Vec<_>>(); + while let Some(feature) = pending.pop() { + for edge in feature_edges.get(feature).into_iter().flatten() { + if feature_edges.contains_key(edge) && closure.insert(edge) { + pending.push(edge); + } + } + } + if !required.is_subset(&closure) { + return Err(format!( + "Phase 1 feature profile {} does not imply every declared requirement", + profile.id + )); + } + Ok(()) +} + +fn unique_values<T: Ord>( + values: impl IntoIterator<Item = T>, + label: &str, +) -> Result<BTreeSet<T>, String> { + let mut unique = BTreeSet::new(); + for value in values { + if !unique.insert(value) { + return Err(format!("{label} contains a duplicate")); + } + } + Ok(unique) } fn validate_nostr_manifest(workspace_root: &Path) -> Result<(), String> { @@ -89,6 +408,11 @@ fn meta_contains_no_std(meta: &Meta) -> bool { #[cfg(test)] mod tests { use super::*; + use std::path::PathBuf; + + fn workspace_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..") + } #[test] fn std_only_source_rejects_direct_and_conditional_no_std_modes() { @@ -101,4 +425,44 @@ mod tests { assert!(validate_nostr_std_only_source(invalid).is_err()); } } + + #[test] + fn phase1_feature_matrix_closes_profiles_and_declared_implications() { + let root = workspace_root(); + let matrix = load_phase1_feature_matrix(&root).expect("load feature matrix"); + validate_phase1_feature_matrix(&root, &matrix).expect("validate feature matrix"); + + let mut missing_all_features = load_phase1_feature_matrix(&root).unwrap(); + missing_all_features + .profiles + .retain(|profile| profile.id != "authority-all-features"); + assert!( + validate_phase1_feature_matrix(&root, &missing_all_features) + .unwrap_err() + .contains("minimal and all-features") + ); + + let mut invalid_implication = load_phase1_feature_matrix(&root).unwrap(); + invalid_implication.profiles[0] + .requires + .push("undeclared".to_owned()); + assert!( + validate_phase1_feature_matrix(&root, &invalid_implication) + .unwrap_err() + .contains("undeclared feature") + ); + } + + #[test] + fn phase1_feature_matrix_schema_rejects_unknown_fields() { + let root = workspace_root(); + let source = fs::read_to_string(root.join(PHASE1_FEATURE_MATRIX_RELATIVE)).unwrap(); + let mutated = source.replacen( + "schema_version = 1", + "schema_version = 1\nunknown = true", + 1, + ); + let error = toml::from_str::<Phase1FeatureMatrix>(&mutated).unwrap_err(); + assert!(error.to_string().contains("unknown field")); + } }