lib

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

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:
Acontracts/conformance/vectors/replica/profile_exclusion.v1.json | 20++++++++++++++++++++
Mcrates/outbox/contracts/migration_authority_v1.manifest.json | 4++--
Mcrates/outbox/contracts/migration_authority_v1.manifest.sha256 | 2+-
Mcrates/outbox/contracts/phase1_publication_v1.manifest.json | 4++--
Mcrates/outbox/contracts/phase1_publication_v1.manifest.sha256 | 2+-
Mcrates/replica_sync/src/tests.rs | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Acrates/replica_sync/tests/fixtures/profile_exclusion.v1.json | 20++++++++++++++++++++
Mtools/xtask/src/contract.rs | 79+++++++------------------------------------------------------------------------
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); }