commit 52eca30465aa7daf5f8e60c50a3fedff636b1282
parent fbb51079d6a34617b2627805dbc9e69a137dbdfc
Author: triesap <tyson@radroots.org>
Date: Mon, 27 Jul 2026 14:12:49 +0000
contract: execute replica profile exclusion
- bind canonical and packaged profile exclusion vectors
- exercise stored profiles through transfer and pending APIs
- reject every kind-zero draft through observed behavior
- remove lexical emitter identifier and literal scans
Diffstat:
8 files changed, 134 insertions(+), 84 deletions(-)
diff --git a/contracts/conformance/vectors/replica/profile_exclusion.v1.json b/contracts/conformance/vectors/replica/profile_exclusion.v1.json
@@ -0,0 +1,20 @@
+{
+ "suite": "replica_profile_exclusion_v1",
+ "contract_version": "1.0.0",
+ "vectors": [
+ {
+ "id": "replica_profile_exclusion_stored_rows_001",
+ "kind": "replica.profile_exclusion",
+ "input": {
+ "farm_profile_name": "Farm Profile",
+ "owner_profile_name": "Owner"
+ },
+ "expected": {
+ "stored_profile_rows": 2,
+ "transfer_event_count": 7,
+ "pending_event_count": 7,
+ "forbidden_kind": 0
+ }
+ }
+ ]
+}
diff --git a/crates/outbox/contracts/migration_authority_v1.manifest.json b/crates/outbox/contracts/migration_authority_v1.manifest.json
@@ -305,10 +305,10 @@
},
{
"file": {
- "byte_length": 494914,
+ "byte_length": 492083,
"hash_algorithm": "sha256_bytes_v1",
"path": "tools/xtask/src/contract.rs",
- "sha256": "d01a9e90f179f6bd4bd01edfb0e8fc87e9a92fa555f9ea53364518cac286863a"
+ "sha256": "a71ba50241e9569642060cc555f370e4ef8c0a525ee98235ec079372b2848469"
},
"role": "contract_dispatch"
},
diff --git a/crates/outbox/contracts/migration_authority_v1.manifest.sha256 b/crates/outbox/contracts/migration_authority_v1.manifest.sha256
@@ -1 +1 @@
-398598a40de3a85688076383c9f1971fbcd26a2f99d07a9c9001ed50e2f5038e
+67186bb3671dd4a8a36a2ae078b9ade0436bfb9e704f42de5b5fde28f5cf3728
diff --git a/crates/outbox/contracts/phase1_publication_v1.manifest.json b/crates/outbox/contracts/phase1_publication_v1.manifest.json
@@ -112,9 +112,9 @@
},
{
"file": {
- "byte_length": 494914,
+ "byte_length": 492083,
"path": "tools/xtask/src/contract.rs",
- "sha256": "d01a9e90f179f6bd4bd01edfb0e8fc87e9a92fa555f9ea53364518cac286863a"
+ "sha256": "a71ba50241e9569642060cc555f370e4ef8c0a525ee98235ec079372b2848469"
},
"role": "contract_dispatch"
},
diff --git a/crates/outbox/contracts/phase1_publication_v1.manifest.sha256 b/crates/outbox/contracts/phase1_publication_v1.manifest.sha256
@@ -1 +1 @@
-51fccb99431ed736313d1513b325ce5daf165aca16342eb314704e9c733659af
+79e03855f5cf0d8e18049a604de4f121fe0f6af688b0c9b4c4a2e547ba83dcfa
diff --git a/crates/replica_sync/src/tests.rs b/crates/replica_sync/src/tests.rs
@@ -1,6 +1,6 @@
use crate::{
RADROOTS_REPLICA_TRANSFER_VERSION, RadrootsReplicaFarmSelector, RadrootsReplicaSyncRequest,
- radroots_replica_sync_all,
+ radroots_replica_pending_publish_batch, radroots_replica_sync_all,
};
use radroots_event::gcs::{RadrootsGeoJsonPoint, RadrootsGeoJsonPolygon};
use radroots_event::kinds::{KIND_FARM, KIND_LIST_SET_GENERIC, KIND_PLOT};
@@ -11,7 +11,7 @@ use radroots_replica_schema::farm_member::IFarmMemberFields;
use radroots_replica_schema::farm_member_claim::IFarmMemberClaimFields;
use radroots_replica_schema::farm_tag::IFarmTagFields;
use radroots_replica_schema::gcs_location::IGcsLocationFields;
-use radroots_replica_schema::nostr_profile::INostrProfileFields;
+use radroots_replica_schema::nostr_profile::{INostrProfileFields, INostrProfileFindMany};
use radroots_replica_schema::plot::IPlotFields;
use radroots_replica_schema::plot_gcs_location::IPlotGcsLocationFields;
use radroots_replica_schema::plot_tag::IPlotTagFields;
@@ -21,8 +21,42 @@ use radroots_replica_store::{
};
use radroots_sql_core::SqlxSqliteExecutor;
use radroots_sql_core::error::SqlError;
+use serde::Deserialize;
use std::panic;
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct ProfileExclusionSuite {
+ suite: String,
+ contract_version: String,
+ vectors: Vec<ProfileExclusionCase>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct ProfileExclusionCase {
+ id: String,
+ kind: String,
+ input: ProfileExclusionInput,
+ expected: ProfileExclusionExpected,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct ProfileExclusionInput {
+ farm_profile_name: String,
+ owner_profile_name: String,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct ProfileExclusionExpected {
+ stored_profile_rows: usize,
+ transfer_event_count: usize,
+ pending_event_count: usize,
+ forbidden_kind: u32,
+}
+
#[cfg_attr(coverage_nightly, coverage(off))]
fn unwrap_sql<T>(result: Result<T, ReplicaSchemaError<SqlError>>, label: &str) -> T {
result
@@ -31,7 +65,18 @@ fn unwrap_sql<T>(result: Result<T, ReplicaSchemaError<SqlError>>, label: &str) -
}
#[test]
-fn sync_all_emits_expected_order_without_lossy_profiles() {
+fn profile_exclusion_vector_executes_sync_and_pending_publication() {
+ let suite = serde_json::from_str::<ProfileExclusionSuite>(include_str!(
+ "../tests/fixtures/profile_exclusion.v1.json"
+ ))
+ .expect("profile exclusion vector");
+ assert_eq!(suite.suite, "replica_profile_exclusion_v1");
+ assert_eq!(suite.contract_version, "1.0.0");
+ assert_eq!(suite.vectors.len(), 1);
+ let case = &suite.vectors[0];
+ assert_eq!(case.id, "replica_profile_exclusion_stored_rows_001");
+ assert_eq!(case.kind, "replica.profile_exclusion");
+
let exec = SqlxSqliteExecutor::open_memory().expect("exec");
migrations::run_all_up(&exec).expect("migrations");
@@ -172,7 +217,7 @@ fn sync_all_emits_expected_order_without_lossy_profiles() {
&INostrProfileFields {
public_key: farm_pubkey.clone(),
profile_type: "farm".to_string(),
- name: "Farm Profile".to_string(),
+ name: case.input.farm_profile_name.clone(),
display_name: None,
about: None,
website: None,
@@ -192,7 +237,7 @@ fn sync_all_emits_expected_order_without_lossy_profiles() {
&INostrProfileFields {
public_key: owner_pubkey.clone(),
profile_type: "individual".to_string(),
- name: "Owner".to_string(),
+ name: case.input.owner_profile_name.clone(),
display_name: None,
about: None,
website: None,
@@ -205,6 +250,19 @@ fn sync_all_emits_expected_order_without_lossy_profiles() {
),
"owner_profile",
);
+ let stored_profiles = unwrap_sql(
+ nostr_profile::find_many(
+ &exec,
+ &INostrProfileFindMany::Filter {
+ filter: Box::new(None),
+ },
+ ),
+ "stored_profiles",
+ );
+ assert_eq!(
+ stored_profiles.results.len(),
+ case.expected.stored_profile_rows
+ );
let request = RadrootsReplicaSyncRequest {
farm: RadrootsReplicaFarmSelector {
@@ -217,7 +275,7 @@ fn sync_all_emits_expected_order_without_lossy_profiles() {
let bundle = radroots_replica_sync_all(&exec, &request).expect("sync");
assert_eq!(bundle.version, RADROOTS_REPLICA_TRANSFER_VERSION);
- assert_eq!(bundle.events.len(), 7);
+ assert_eq!(bundle.events.len(), case.expected.transfer_event_count);
let kinds = bundle
.events
.iter()
@@ -226,6 +284,23 @@ fn sync_all_emits_expected_order_without_lossy_profiles() {
assert_eq!(kinds[0], KIND_FARM);
assert_eq!(kinds[1], KIND_PLOT);
assert!(kinds[2..].iter().all(|kind| *kind == KIND_LIST_SET_GENERIC));
+ assert!(
+ kinds
+ .iter()
+ .all(|kind| *kind != case.expected.forbidden_kind)
+ );
+
+ let pending = radroots_replica_pending_publish_batch(&exec).expect("pending publication");
+ assert_eq!(
+ pending.pending_events.len(),
+ case.expected.pending_event_count
+ );
+ assert!(
+ pending
+ .pending_events
+ .iter()
+ .all(|event| event.kind != case.expected.forbidden_kind)
+ );
}
#[test]
diff --git a/crates/replica_sync/tests/fixtures/profile_exclusion.v1.json b/crates/replica_sync/tests/fixtures/profile_exclusion.v1.json
@@ -0,0 +1,20 @@
+{
+ "suite": "replica_profile_exclusion_v1",
+ "contract_version": "1.0.0",
+ "vectors": [
+ {
+ "id": "replica_profile_exclusion_stored_rows_001",
+ "kind": "replica.profile_exclusion",
+ "input": {
+ "farm_profile_name": "Farm Profile",
+ "owner_profile_name": "Owner"
+ },
+ "expected": {
+ "stored_profile_rows": 2,
+ "transfer_event_count": 7,
+ "pending_event_count": 7,
+ "forbidden_kind": 0
+ }
+ }
+ ]
+}
diff --git a/tools/xtask/src/contract.rs b/tools/xtask/src/contract.rs
@@ -193,7 +193,9 @@ const REPLICA_CONTRACT_RELATIVE: &str = "contracts/replica.toml";
const REPLICA_CONTRACT_NAME: &str = "radroots_replica_contract";
const REPLICA_TRANSFER_CONSTANT: &str = "RADROOTS_REPLICA_TRANSFER_VERSION";
const REPLICA_TRANSFER_VERSION: u32 = 2;
-const CONFORMANCE_VECTOR_MIRRORS: [(&str, &str); 31] = [
+const REPLICA_PROFILE_EXCLUSION_VECTOR_RELATIVE: &str =
+ "contracts/conformance/vectors/replica/profile_exclusion.v1.json";
+const CONFORMANCE_VECTOR_MIRRORS: [(&str, &str); 32] = [
(
"contracts/conformance/vectors/blossom/bud11_claims.v1.json",
"crates/blossom/tests/fixtures/bud11_claims.v1.json",
@@ -311,6 +313,10 @@ const CONFORMANCE_VECTOR_MIRRORS: [(&str, &str); 31] = [
"crates/event_codec/tests/fixtures/phase1_publication_media_readiness.v1.json",
),
(
+ REPLICA_PROFILE_EXCLUSION_VECTOR_RELATIVE,
+ "crates/replica_sync/tests/fixtures/profile_exclusion.v1.json",
+ ),
+ (
"contracts/conformance/vectors/trade/parse_classified_listing_address.v1.json",
"crates/trade/tests/fixtures/parse_classified_listing_address.v1.json",
),
@@ -5669,50 +5675,6 @@ fn validate_replica_policy_source_witnesses(sync_root: &Path) -> Result<(), Stri
));
}
- let emit_path = sync_root.join("src/emit.rs");
- let emit_source = fs::read_to_string(&emit_path)
- .map_err(|error| format!("read {}: {error}", emit_path.display()))?;
- let test_module_marker = "#[cfg(test)]\nmod tests {";
- let test_module_start = emit_source.rfind(test_module_marker).ok_or_else(|| {
- format!(
- "replica emit source {} must keep its bottom test module behind #[cfg(test)]",
- emit_path.display()
- )
- })?;
- let production_source = &emit_source[..test_module_start];
- if !production_source.contains("pub fn radroots_replica_sync_all_with_options(") {
- return Err(format!(
- "replica emit source {} is missing radroots_replica_sync_all_with_options",
- emit_path.display()
- ));
- }
- let production_code = production_source
- .lines()
- .filter(|line| !line.trim_start().starts_with("//"))
- .collect::<Vec<_>>()
- .join("\n");
- for identifier in production_code
- .split(|character: char| !(character.is_ascii_alphanumeric() || character == '_'))
- .filter(|identifier| !identifier.is_empty())
- {
- if identifier.to_ascii_lowercase().contains("profile") {
- return Err(format!(
- "replica emit production source {} must not contain Profile-related identifier {identifier}",
- emit_path.display()
- ));
- }
- }
- let compact_production = production_code
- .chars()
- .filter(|character| !character.is_ascii_whitespace())
- .collect::<String>();
- if compact_production.contains("kind:0") {
- return Err(format!(
- "replica emit production source {} must not construct a literal kind-0 event",
- emit_path.display()
- ));
- }
-
Ok(())
}
@@ -10906,11 +10868,9 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"]
let cargo_path = root.join("crates/b/Cargo.toml");
let lib_path = root.join("crates/b/src/lib.rs");
let types_path = root.join("crates/b/src/types.rs");
- let emit_path = root.join("crates/b/src/emit.rs");
let cargo = fs::read_to_string(&cargo_path).expect("read replica cargo manifest");
let lib = fs::read_to_string(&lib_path).expect("read replica lib source");
let types = fs::read_to_string(&types_path).expect("read replica types source");
- let emit = fs::read_to_string(&emit_path).expect("read replica emit source");
write_file(
&cargo_path,
@@ -10995,31 +10955,6 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"]
.expect_err("retired include_profiles identifier must fail");
assert!(retired_error.contains("include_profiles is forbidden"));
- write_file(&types_path, &types);
- write_file(
- &emit_path,
- &emit.replace(
- "#[cfg(test)]\nmod tests {}",
- "fn emit_profile_event() {}\n\n#[cfg(test)]\nmod tests {}",
- ),
- );
- let bundle = load_contract_bundle(&root).expect("load profile-emitter contract");
- let profile_error = validate_replica_contract(&bundle, &root)
- .expect_err("Profile-related production emitter must fail");
- assert!(profile_error.contains("Profile-related identifier emit_profile_event"));
-
- write_file(
- &emit_path,
- &emit.replace(
- "#[cfg(test)]\nmod tests {}",
- "fn emit_kind_zero() { let _event = Event { kind: 0 }; }\n\n#[cfg(test)]\nmod tests {}",
- ),
- );
- let bundle = load_contract_bundle(&root).expect("load literal-kind-zero contract");
- let kind_error = validate_replica_contract(&bundle, &root)
- .expect_err("literal kind-0 production emitter must fail");
- assert!(kind_error.contains("must not construct a literal kind-0 event"));
-
let _ = fs::remove_dir_all(root);
}