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:
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"));
+ }
}