pub type AgentPubKey = [u8; 32];
pub type CapSecret = Vec<u8>;
pub mod constitutional_envelope;
pub mod consciousness_thresholds;
pub mod consciousness_zkp;
pub mod membership_zkp;
#[cfg(feature = "model-governance")]
pub mod model_governance;
#[cfg(feature = "model-governance")]
pub mod scoring_model;
#[cfg(feature = "model-governance")]
pub mod shadow_evaluation;
pub use consciousness_thresholds as phi_thresholds;
pub use consciousness_thresholds::{ConsciousnessThresholds, PhiThresholds};
pub mod consciousness_profile;
pub use consciousness_profile::{
bootstrap_credential, decay_reputation, evaluate_bootstrap_governance, evaluate_governance,
evaluate_governance_with_reputation, is_bootstrap_eligible, needs_refresh,
requirement_for_basic, requirement_for_constitutional, requirement_for_guardian,
requirement_for_proposal, requirement_for_voting, ConsciousnessCredential,
ConsciousnessProfile, ConsciousnessTier, ExtensionKey, GateAuditInput, GovernanceAuditFilter,
GovernanceAuditResult, GovernanceEligibility, GovernanceRequirement, ReputationState,
GRACE_PERIOD_US, REFRESH_WINDOW_US, REPUTATION_BLACKLIST_THRESHOLD, REPUTATION_DECAY_PER_DAY,
REPUTATION_MAX_SLASHES, REPUTATION_RESTORATION_INTERACTIONS, REPUTATION_SLASH_FACTOR,
};
#[cfg(feature = "hdk")]
pub use consciousness_profile::gate_consciousness;
pub mod sovereign_gate;
#[cfg(feature = "hdk")]
pub use sovereign_gate::gate_civic;
pub use sovereign_profile::weights::DimensionWeights;
pub use sovereign_profile::{
civic_requirement_basic, civic_requirement_constitutional, civic_requirement_guardian,
civic_requirement_proposal, civic_requirement_voting, CivicRequirement, CivicTier,
SovereignCredential, SovereignDimension, SovereignProfile,
};
pub mod offline_credential;
pub mod sub_passport;
#[cfg(feature = "interplanetary")]
pub mod cross_planetary_fl;
#[cfg(feature = "interplanetary")]
pub mod earth_colony_protocol;
#[cfg(feature = "interplanetary")]
pub mod interplanetary_bridge;
#[cfg(all(feature = "interplanetary", feature = "hdk"))]
pub mod mars_isru;
#[cfg(feature = "interplanetary")]
pub mod planetary_governance;
#[cfg(feature = "federated")]
pub mod terrain_fl;
#[cfg(feature = "hdk")]
pub mod validation;
#[cfg(feature = "hdk")]
pub use validation::{check_author_match, check_link_author_match};
pub mod collective_phi;
pub use collective_phi::{
AgentConsciousnessVector, CollectivePhiEngine, CollectivePhiResult, COLLECTIVE_PHI_MAX_SYNC,
};
pub mod routing;
pub use routing::{
resolve_civic_zome, resolve_commons_zome, BridgeDomain, CivicZome, CommonsZome,
CrossClusterRole, CIVIC_DOMAINS, COMMONS_DOMAINS,
};
pub mod routing_registry;
pub mod metrics;
#[cfg(feature = "infrastructure")]
pub mod migration;
pub mod notifications;
#[cfg(feature = "infrastructure")]
pub mod saga;
#[cfg(feature = "infrastructure")]
pub mod license_enforcement;
#[cfg(feature = "infrastructure")]
pub mod merkle_timestamp;
#[cfg(feature = "infrastructure")]
pub mod timestamp_anchor;
#[cfg(kani)]
mod kani_proofs;
#[cfg(feature = "hdk")]
use hdk::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DispatchInput {
pub zome: String,
pub fn_name: String,
pub payload: Vec<u8>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DispatchResult {
pub success: bool,
pub response: Option<Vec<u8>>,
pub error: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error_code: Option<BridgeErrorCode>,
}
impl DispatchResult {
pub fn ok(response: Vec<u8>) -> Self {
Self {
success: true,
response: Some(response),
error: None,
error_code: None,
}
}
pub fn err(code: BridgeErrorCode, message: String) -> Self {
Self {
success: false,
response: None,
error: Some(message),
error_code: Some(code),
}
}
}
#[cfg(feature = "hdk")]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ResolveQueryInput {
pub query_hash: ActionHash,
pub result: String,
pub success: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EventTypeQuery {
pub domain: String,
pub event_type: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BridgeHealth {
pub healthy: bool,
pub agent: String,
pub total_events: u32,
pub total_queries: u32,
pub domains: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BridgeErrorCode {
AllowlistRejected,
LocalNetworkError,
LocalCallRejected,
LocalNoResponse,
LocalCallFailed,
CrossClusterNetworkError,
CrossClusterCallRejected,
CrossClusterNoResponse,
CrossClusterCallFailed,
ValidationFailed,
}
impl BridgeErrorCode {
pub fn as_str(&self) -> &'static str {
match self {
Self::AllowlistRejected => "BRG-001",
Self::LocalNetworkError => "BRG-002",
Self::LocalCallRejected => "BRG-003",
Self::LocalNoResponse => "BRG-004",
Self::LocalCallFailed => "BRG-005",
Self::CrossClusterNetworkError => "BRG-006",
Self::CrossClusterCallRejected => "BRG-007",
Self::CrossClusterNoResponse => "BRG-008",
Self::CrossClusterCallFailed => "BRG-009",
Self::ValidationFailed => "BRG-010",
}
}
}
impl core::fmt::Display for BridgeErrorCode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.as_str())
}
}
pub const MAX_DISPATCH_PAYLOAD_BYTES: usize = 1_048_576;
pub const MAX_DISPATCH_IDENTIFIER_BYTES: usize = 256;
fn validate_dispatch_sizes(zome: &str, fn_name: &str, payload: &[u8]) -> Result<(), String> {
if zome.len() > MAX_DISPATCH_IDENTIFIER_BYTES {
return Err(format!(
"Zome name too long ({} bytes, max {})",
zome.len(),
MAX_DISPATCH_IDENTIFIER_BYTES
));
}
if fn_name.len() > MAX_DISPATCH_IDENTIFIER_BYTES {
return Err(format!(
"Function name too long ({} bytes, max {})",
fn_name.len(),
MAX_DISPATCH_IDENTIFIER_BYTES
));
}
if payload.len() > MAX_DISPATCH_PAYLOAD_BYTES {
return Err(format!(
"Payload too large ({} bytes, max {})",
payload.len(),
MAX_DISPATCH_PAYLOAD_BYTES
));
}
Ok(())
}
#[cfg(feature = "hdk")]
pub fn dispatch_call_checked(
input: &DispatchInput,
allowed_zomes: &[&str],
) -> ExternResult<DispatchResult> {
if let Err(msg) = validate_dispatch_sizes(&input.zome, &input.fn_name, &input.payload) {
metrics::record_error(
&input.zome,
&input.fn_name,
BridgeErrorCode::ValidationFailed.as_str(),
);
return Ok(DispatchResult::err(BridgeErrorCode::ValidationFailed, msg));
}
if !allowed_zomes.contains(&input.zome.as_str()) {
metrics::record_error(
&input.zome,
&input.fn_name,
BridgeErrorCode::AllowlistRejected.as_str(),
);
return Ok(DispatchResult::err(
BridgeErrorCode::AllowlistRejected,
format!(
"Zome '{}' is not in the allowed dispatch list. Valid zomes: {:?}",
input.zome, allowed_zomes
),
));
}
let payload = ExternIO(input.payload.clone());
let start_us = sys_time().ok().map(|t| t.as_micros() as u64);
let result = HDK.with(|h| {
h.borrow().call(vec![Call::new(
CallTarget::ConductorCell(CallTargetCell::Local),
ZomeName::from(input.zome.as_str()),
FunctionName::from(input.fn_name.as_str()),
None,
payload,
)])
});
let elapsed_us = start_us.and_then(|start| {
sys_time()
.ok()
.map(|end| (end.as_micros() as u64).saturating_sub(start))
});
match result {
Ok(responses) => match responses.into_iter().next() {
Some(ZomeCallResponse::Ok(extern_io)) => {
if let Some(latency) = elapsed_us {
metrics::record_success(&input.zome, &input.fn_name, latency);
}
Ok(DispatchResult::ok(extern_io.0))
}
Some(ZomeCallResponse::NetworkError(err)) => {
let code = BridgeErrorCode::LocalNetworkError;
metrics::record_error(&input.zome, &input.fn_name, code.as_str());
Ok(DispatchResult::err(code, format!("Network error: {}", err)))
}
Some(other) => {
let code = BridgeErrorCode::LocalCallRejected;
metrics::record_error(&input.zome, &input.fn_name, code.as_str());
Ok(DispatchResult::err(
code,
format!("Zome call rejected: {:?}", other),
))
}
None => {
let code = BridgeErrorCode::LocalNoResponse;
metrics::record_error(&input.zome, &input.fn_name, code.as_str());
Ok(DispatchResult::err(
code,
"No response from zome call".into(),
))
}
},
Err(e) => {
let code = BridgeErrorCode::LocalCallFailed;
metrics::record_error(&input.zome, &input.fn_name, code.as_str());
Ok(DispatchResult::err(code, format!("Call failed: {:?}", e)))
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum ConstellationTarget {
Internal { role: String },
External {
agent: AgentPubKey,
cap_secret: Option<CapSecret>,
},
}
#[cfg(feature = "hdk")]
pub fn dispatch_constellation_call(
target: &ConstellationTarget,
zome: &str,
fn_name: &str,
payload: Vec<u8>,
) -> ExternResult<DispatchResult> {
let start_us = sys_time().ok().map(|t| t.as_micros() as u64);
let result = match target {
ConstellationTarget::Internal { role } => HDK.with(|h| {
h.borrow().call(vec![Call::new(
CallTarget::ConductorCell(CallTargetCell::OtherRole(role.clone())),
ZomeName::from(zome),
FunctionName::from(fn_name),
None,
ExternIO(payload),
)])
}),
ConstellationTarget::External { agent, cap_secret } => {
match call_remote(
agent.clone(),
zome,
fn_name.into(),
*cap_secret,
ExternIO(payload),
) {
Ok(ZomeCallResponse::Ok(extern_io)) => Ok(vec![ZomeCallResponse::Ok(extern_io)]),
Ok(other) => Ok(vec![other]),
Err(e) => Err(e),
}
}
};
let elapsed_us = start_us.and_then(|start| {
sys_time()
.ok()
.map(|end| (end.as_micros() as u64).saturating_sub(start))
});
match result {
Ok(responses) => match responses.into_iter().next() {
Some(ZomeCallResponse::Ok(extern_io)) => {
if let Some(latency) = elapsed_us {
metrics::record_success(zome, fn_name, latency);
}
Ok(DispatchResult::ok(extern_io.0))
}
Some(ZomeCallResponse::NetworkError(err)) => Ok(DispatchResult::err(
BridgeErrorCode::LocalNetworkError,
format!("Network error: {}", err),
)),
Some(other) => Ok(DispatchResult::err(
BridgeErrorCode::LocalCallRejected,
format!("Rejected: {:?}", other),
)),
None => Ok(DispatchResult::err(
BridgeErrorCode::LocalNoResponse,
"No response".into(),
)),
},
Err(e) => Ok(DispatchResult::err(
BridgeErrorCode::LocalCallFailed,
format!("Call failed: {:?}", e),
)),
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CrossClusterDispatchInput {
pub role: String,
pub zome: String,
pub fn_name: String,
pub payload: Vec<u8>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CorrelatedDispatch {
pub correlation_id: String,
pub target_zome: String,
pub target_fn: String,
pub payload: String,
}
#[cfg(feature = "hdk")]
pub fn dispatch_call_cross_cluster(
input: &CrossClusterDispatchInput,
allowed_zomes: &[&str],
) -> ExternResult<DispatchResult> {
if let Err(msg) = validate_dispatch_sizes(&input.zome, &input.fn_name, &input.payload) {
metrics::record_error(
&input.zome,
&input.fn_name,
BridgeErrorCode::ValidationFailed.as_str(),
);
return Ok(DispatchResult::err(BridgeErrorCode::ValidationFailed, msg));
}
if input.role.len() > MAX_DISPATCH_IDENTIFIER_BYTES {
metrics::record_error(
&input.zome,
&input.fn_name,
BridgeErrorCode::ValidationFailed.as_str(),
);
return Ok(DispatchResult::err(
BridgeErrorCode::ValidationFailed,
format!(
"Role name too long ({} bytes, max {})",
input.role.len(),
MAX_DISPATCH_IDENTIFIER_BYTES
),
));
}
if !allowed_zomes.contains(&input.zome.as_str()) {
metrics::record_error(
&input.zome,
&input.fn_name,
BridgeErrorCode::AllowlistRejected.as_str(),
);
return Ok(DispatchResult::err(
BridgeErrorCode::AllowlistRejected,
format!(
"Zome '{}' is not in the allowed cross-cluster dispatch list. Valid zomes: {:?}",
input.zome, allowed_zomes
),
));
}
metrics::record_cross_cluster();
let target = ConstellationTarget::Internal {
role: input.role.clone(),
};
dispatch_constellation_call(&target, &input.zome, &input.fn_name, input.payload.clone())
}
#[cfg(feature = "hdk")]
pub fn dispatch_call_cross_cluster_commons(
input: &CrossClusterDispatchInput,
allowed_zomes: &[&str],
) -> ExternResult<DispatchResult> {
let role = CommonsZome::resolve_role(&input.zome).unwrap_or("commons_land");
let routed_input = CrossClusterDispatchInput {
role: role.to_string(),
zome: input.zome.clone(),
fn_name: input.fn_name.clone(),
payload: input.payload.clone(),
};
dispatch_call_cross_cluster(&routed_input, allowed_zomes)
}
pub const RATE_LIMIT_MAX_DISPATCH: usize = 100;
pub const RATE_LIMIT_WINDOW_SECS: i64 = 60;
pub fn check_rate_limit_count(recent_count: usize) -> Result<(), String> {
if recent_count >= RATE_LIMIT_MAX_DISPATCH {
Err(format!(
"Rate limit exceeded: {} dispatches in {}s (max {})",
recent_count, RATE_LIMIT_WINDOW_SECS, RATE_LIMIT_MAX_DISPATCH
))
} else {
Ok(())
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PropertyOwnershipQuery {
pub property_id: String,
pub requester_did: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PropertyOwnershipResult {
pub is_owner: bool,
pub owner_did: Option<String>,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CareAvailabilityQuery {
pub skill_needed: String,
pub location: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CareAvailabilityResult {
pub available_count: u32,
pub recommendation: String,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct JusticeAreaQuery {
pub area: String,
pub case_type: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct JusticeAreaResult {
pub active_cases: u32,
pub recommendation: String,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FactcheckStatusQuery {
pub claim_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FactcheckStatusResult {
pub has_factcheck: bool,
pub verdict: Option<String>,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FoodAvailabilityQuery {
pub product_name: Option<String>,
pub market_type: Option<String>,
pub max_distance_km: Option<f64>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FoodAvailabilityResult {
pub available_listings: u32,
pub nearest_market: Option<String>,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TransportRouteQuery {
pub origin_lat: f64,
pub origin_lon: f64,
pub destination_lat: f64,
pub destination_lon: f64,
pub mode: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TransportRouteResult {
pub route_count: u32,
pub estimated_minutes: Option<u32>,
pub estimated_emissions_kg_co2: Option<f64>,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CarbonCreditQuery {
pub agent_did: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CarbonCreditResult {
pub total_credits_kg_co2: f64,
pub trips_logged: u32,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WaterSafetyQuery {
pub area_lat: f64,
pub area_lon: f64,
pub radius_km: f64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WaterSafetyResult {
pub safe_sources: u32,
pub contaminated_sources: u32,
pub total_sources: u32,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EmergencyFoodQuery {
pub area_lat: f64,
pub area_lon: f64,
pub radius_km: f64,
pub people_count: u32,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EmergencyFoodResult {
pub available_kg: f64,
pub distribution_points: u32,
pub estimated_days_supply: f64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ShelterCapacityQuery {
pub area_lat: f64,
pub area_lon: f64,
pub radius_km: f64,
pub beds_needed: u32,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ShelterCapacityResult {
pub available_beds: u32,
pub total_shelters: u32,
pub nearest_shelter_km: f64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EmergencyCareQuery {
pub area_lat: f64,
pub area_lon: f64,
pub skill_needed: String,
pub urgency_level: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EmergencyCareResult {
pub available_providers: u32,
pub nearest_provider_km: f64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AuditTrailQuery {
pub from_us: i64,
pub to_us: i64,
pub domain: Option<String>,
pub event_type: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AuditTrailEntry {
pub domain: String,
pub event_type: String,
pub source_agent: String,
pub payload_preview: String,
pub created_at_us: i64,
#[cfg(feature = "hdk")]
pub action_hash: ActionHash,
#[cfg(not(feature = "hdk"))]
pub action_hash: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AuditTrailResult {
pub entries: Vec<AuditTrailEntry>,
pub total_matched: u32,
pub query_from_us: i64,
pub query_to_us: i64,
}
#[cfg(feature = "hdk")]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HearthMemberQuery {
pub hearth_hash: ActionHash,
pub agent: AgentPubKey,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HearthMemberResult {
pub is_member: bool,
pub role: Option<String>,
pub display_name: Option<String>,
pub error: Option<String>,
}
#[cfg(feature = "hdk")]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HearthCareQuery {
pub hearth_hash: ActionHash,
pub care_type: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HearthCareResult {
pub available_caregivers: u32,
pub active_schedules: u32,
pub error: Option<String>,
}
#[cfg(feature = "hdk")]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HearthEmergencyQuery {
pub hearth_hash: ActionHash,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HearthEmergencyResult {
pub has_active_alerts: bool,
pub active_alert_count: u32,
pub members_checked_in: u32,
pub members_missing: u32,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BudgetProposalQuery {
pub proposal_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BudgetProposalResult {
pub approved: bool,
pub amount: u64,
pub treasury_balance: u64,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CollateralPropertyQuery {
pub property_hash: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CollateralPropertyResult {
pub valid: bool,
pub appraised_value: u64,
pub encumbered: bool,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RestitutionQuery {
pub case_id: String,
pub defendant_did: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RestitutionResult {
pub balance_sufficient: bool,
pub amount_due: u64,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RevocationNotice {
pub credential_hash: String,
pub did: String,
pub reason: String,
pub effective_at: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RevocationAck {
pub received: bool,
pub affected_entries: u32,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConsentedRecordQuery {
pub patient_did: String,
pub record_type: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConsentedRecordResult {
pub authorized: bool,
pub record_hash: Option<String>,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProjectProposalQuery {
pub project_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProjectProposalResult {
pub governance_approved: bool,
pub conditions: Vec<String>,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ClaimVerificationQuery {
pub claim_hash: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ClaimVerificationResult {
pub verified: bool,
pub confidence: f64,
pub sources: Vec<String>,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CarbonOffsetQuery {
pub route_id: String,
pub distance_km: f64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CarbonOffsetResult {
pub credits_earned: f64,
pub offset_hash: Option<String>,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NotificationPriority {
Low = 0,
Normal = 1,
High = 2,
Emergency = 3,
}
impl NotificationPriority {
pub const fn from_u8(v: u8) -> Self {
match v {
0 => Self::Low,
1 => Self::Normal,
2 => Self::High,
3 => Self::Emergency,
_ => Self::Normal,
}
}
pub const fn as_u8(&self) -> u8 {
*self as u8
}
}
#[cfg(feature = "hdk")]
pub fn records_from_links(links: Vec<Link>) -> ExternResult<Vec<Record>> {
let mut records = Vec::new();
for link in links {
let action_hash = ActionHash::try_from(link.target)
.map_err(|_| wasm_error!(WasmErrorInner::Guest("Invalid link target".into())))?;
if let Some(record) = get(action_hash, GetOptions::default())? {
records.push(record);
}
}
Ok(records)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dispatch_rejects_disallowed_zome() {
let input = DispatchInput {
zome: "evil_zome".into(),
fn_name: "steal_data".into(),
payload: vec![],
};
let allowed = &["property_registry", "housing_units"];
let result = dispatch_call_checked(&input, allowed).unwrap();
assert!(!result.success);
assert!(result.response.is_none());
let err = result.error.unwrap();
assert!(err.contains("not in the allowed dispatch list"));
assert!(err.contains("evil_zome"));
}
#[test]
fn dispatch_rejects_empty_allowlist() {
let input = DispatchInput {
zome: "property_registry".into(),
fn_name: "get_property".into(),
payload: vec![],
};
let result = dispatch_call_checked(&input, &[]).unwrap();
assert!(!result.success);
assert!(result.error.is_some());
}
#[test]
fn dispatch_rejects_similar_zome_name() {
let input = DispatchInput {
zome: "property_registry_evil".into(),
fn_name: "get_property".into(),
payload: vec![],
};
let allowed = &["property_registry"];
let result = dispatch_call_checked(&input, allowed).unwrap();
assert!(!result.success);
}
#[test]
fn dispatch_error_lists_valid_zomes() {
let input = DispatchInput {
zome: "bad".into(),
fn_name: "fn".into(),
payload: vec![],
};
let allowed = &["alpha", "beta", "gamma"];
let result = dispatch_call_checked(&input, allowed).unwrap();
let err = result.error.unwrap();
assert!(err.contains("alpha"));
assert!(err.contains("beta"));
assert!(err.contains("gamma"));
}
#[test]
fn dispatch_input_serde_roundtrip() {
let input = DispatchInput {
zome: "property_registry".into(),
fn_name: "get_property".into(),
payload: vec![1, 2, 3, 4],
};
let json = serde_json::to_string(&input).unwrap();
let input2: DispatchInput = serde_json::from_str(&json).unwrap();
assert_eq!(input.zome, input2.zome);
assert_eq!(input.fn_name, input2.fn_name);
assert_eq!(input.payload, input2.payload);
}
#[test]
fn dispatch_result_success_serde_roundtrip() {
let result = DispatchResult::ok(vec![10, 20, 30]);
let json = serde_json::to_string(&result).unwrap();
let r2: DispatchResult = serde_json::from_str(&json).unwrap();
assert!(r2.success);
assert_eq!(r2.response, Some(vec![10, 20, 30]));
assert!(r2.error.is_none());
assert!(r2.error_code.is_none());
}
#[test]
fn dispatch_result_error_serde_roundtrip() {
let result =
DispatchResult::err(BridgeErrorCode::LocalCallFailed, "something failed".into());
let json = serde_json::to_string(&result).unwrap();
assert!(json.contains("error_code")); let r2: DispatchResult = serde_json::from_str(&json).unwrap();
assert!(!r2.success);
assert!(r2.response.is_none());
assert_eq!(r2.error.as_deref(), Some("something failed"));
assert_eq!(r2.error_code, Some(BridgeErrorCode::LocalCallFailed));
}
#[test]
fn dispatch_result_backward_compat_without_error_code() {
let json = r#"{"success":false,"response":null,"error":"old error"}"#;
let r: DispatchResult = serde_json::from_str(json).unwrap();
assert!(!r.success);
assert_eq!(r.error.as_deref(), Some("old error"));
assert_eq!(r.error_code, None); }
#[test]
fn event_type_query_serde_roundtrip() {
let q = EventTypeQuery {
domain: "housing".into(),
event_type: "lease_created".into(),
};
let json = serde_json::to_string(&q).unwrap();
let q2: EventTypeQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q.domain, q2.domain);
assert_eq!(q.event_type, q2.event_type);
}
#[test]
fn cross_cluster_rejects_disallowed_zome() {
let input = CrossClusterDispatchInput {
role: "civic".into(),
zome: "evil_zome".into(),
fn_name: "steal_data".into(),
payload: vec![],
};
let allowed = &["justice_cases", "emergency_incidents"];
let result = dispatch_call_cross_cluster(&input, allowed).unwrap();
assert!(!result.success);
assert!(result.response.is_none());
let err = result.error.unwrap();
assert!(err.contains("not in the allowed cross-cluster dispatch list"));
assert!(err.contains("evil_zome"));
}
#[test]
fn cross_cluster_rejects_empty_allowlist() {
let input = CrossClusterDispatchInput {
role: "commons".into(),
zome: "property_registry".into(),
fn_name: "get_property".into(),
payload: vec![],
};
let result = dispatch_call_cross_cluster(&input, &[]).unwrap();
assert!(!result.success);
assert!(result.error.is_some());
}
#[test]
fn cross_cluster_rejects_similar_zome_name() {
let input = CrossClusterDispatchInput {
role: "civic".into(),
zome: "justice_cases_evil".into(),
fn_name: "get_case".into(),
payload: vec![],
};
let allowed = &["justice_cases"];
let result = dispatch_call_cross_cluster(&input, allowed).unwrap();
assert!(!result.success);
}
#[test]
fn cross_cluster_error_lists_valid_zomes() {
let input = CrossClusterDispatchInput {
role: "civic".into(),
zome: "bad".into(),
fn_name: "fn".into(),
payload: vec![],
};
let allowed = &["justice_cases", "emergency_incidents", "media_publication"];
let result = dispatch_call_cross_cluster(&input, allowed).unwrap();
let err = result.error.unwrap();
assert!(err.contains("justice_cases"));
assert!(err.contains("emergency_incidents"));
assert!(err.contains("media_publication"));
}
#[test]
fn cross_cluster_dispatch_input_serde_roundtrip() {
let input = CrossClusterDispatchInput {
role: "civic".into(),
zome: "justice_cases".into(),
fn_name: "get_case".into(),
payload: vec![5, 6, 7],
};
let json = serde_json::to_string(&input).unwrap();
let input2: CrossClusterDispatchInput = serde_json::from_str(&json).unwrap();
assert_eq!(input.role, input2.role);
assert_eq!(input.zome, input2.zome);
assert_eq!(input.fn_name, input2.fn_name);
assert_eq!(input.payload, input2.payload);
}
#[test]
fn bridge_health_serde_roundtrip() {
let h = BridgeHealth {
healthy: true,
agent: "uhCAk_test_agent".into(),
total_events: 42,
total_queries: 7,
domains: vec!["property".into(), "housing".into()],
};
let json = serde_json::to_string(&h).unwrap();
let h2: BridgeHealth = serde_json::from_str(&json).unwrap();
assert!(h2.healthy);
assert_eq!(h2.total_events, 42);
assert_eq!(h2.total_queries, 7);
assert_eq!(h2.domains.len(), 2);
}
#[test]
fn rate_limit_zero_calls_passes() {
assert!(check_rate_limit_count(0).is_ok());
}
#[test]
fn rate_limit_under_max_passes() {
assert!(check_rate_limit_count(99).is_ok());
}
#[test]
fn rate_limit_at_max_rejects() {
let err = check_rate_limit_count(RATE_LIMIT_MAX_DISPATCH).unwrap_err();
assert!(err.contains("Rate limit exceeded"));
}
#[test]
fn rate_limit_over_max_rejects() {
let err = check_rate_limit_count(1000).unwrap_err();
assert!(err.contains("Rate limit exceeded"));
assert!(err.contains("1000"));
}
#[test]
fn rate_limit_error_includes_window() {
let err = check_rate_limit_count(200).unwrap_err();
assert!(err.contains(&format!("{}s", RATE_LIMIT_WINDOW_SECS)));
}
#[test]
fn property_ownership_query_serde_roundtrip() {
let q = PropertyOwnershipQuery {
property_id: "PROP-001".into(),
requester_did: "did:mycelix:abc".into(),
};
let json = serde_json::to_string(&q).unwrap();
let q2: PropertyOwnershipQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q.property_id, q2.property_id);
assert_eq!(q.requester_did, q2.requester_did);
}
#[test]
fn property_ownership_result_serde_roundtrip() {
let r = PropertyOwnershipResult {
is_owner: true,
owner_did: Some("did:mycelix:owner".into()),
error: None,
};
let json = serde_json::to_string(&r).unwrap();
let r2: PropertyOwnershipResult = serde_json::from_str(&json).unwrap();
assert!(r2.is_owner);
assert_eq!(r2.owner_did, Some("did:mycelix:owner".into()));
}
#[test]
fn care_availability_query_serde_roundtrip() {
let q = CareAvailabilityQuery {
skill_needed: "nursing".into(),
location: None,
};
let json = serde_json::to_string(&q).unwrap();
let q2: CareAvailabilityQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q.skill_needed, q2.skill_needed);
assert!(q2.location.is_none());
}
#[test]
fn justice_area_query_serde_roundtrip() {
let q = JusticeAreaQuery {
area: "north-side".into(),
case_type: Some("civil".into()),
};
let json = serde_json::to_string(&q).unwrap();
let q2: JusticeAreaQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q.area, q2.area);
assert_eq!(q.case_type, q2.case_type);
}
#[test]
fn factcheck_status_query_serde_roundtrip() {
let q = FactcheckStatusQuery {
claim_id: "CL-42".into(),
};
let json = serde_json::to_string(&q).unwrap();
let q2: FactcheckStatusQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q.claim_id, q2.claim_id);
}
#[test]
fn factcheck_status_result_serde_roundtrip() {
let r = FactcheckStatusResult {
has_factcheck: true,
verdict: Some("verified".into()),
error: None,
};
let json = serde_json::to_string(&r).unwrap();
let r2: FactcheckStatusResult = serde_json::from_str(&json).unwrap();
assert!(r2.has_factcheck);
assert_eq!(r2.verdict, Some("verified".into()));
}
#[test]
fn audit_trail_query_full_serde() {
let q = AuditTrailQuery {
from_us: 1_700_000_000_000_000,
to_us: 1_700_001_000_000_000,
domain: Some("property".into()),
event_type: Some("ownership_transferred".into()),
};
let json = serde_json::to_string(&q).unwrap();
let q2: AuditTrailQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q2.from_us, 1_700_000_000_000_000);
assert_eq!(q2.domain.as_deref(), Some("property"));
assert_eq!(q2.event_type.as_deref(), Some("ownership_transferred"));
}
#[test]
fn audit_trail_query_no_filters() {
let q = AuditTrailQuery {
from_us: 0,
to_us: i64::MAX,
domain: None,
event_type: None,
};
let json = serde_json::to_string(&q).unwrap();
let q2: AuditTrailQuery = serde_json::from_str(&json).unwrap();
assert!(q2.domain.is_none());
assert!(q2.event_type.is_none());
}
#[test]
fn audit_trail_entry_serde() {
let e = AuditTrailEntry {
domain: "justice".into(),
event_type: "case_filed".into(),
source_agent: "uhCAk_agent1".into(),
payload_preview: "{\"case_id\":\"CASE-1\"}".into(),
created_at_us: 1_700_000_500_000_000,
action_hash: ActionHash::from_raw_36(vec![0u8; 36]),
};
let json = serde_json::to_string(&e).unwrap();
let e2: AuditTrailEntry = serde_json::from_str(&json).unwrap();
assert_eq!(e2.domain, "justice");
assert_eq!(e2.event_type, "case_filed");
}
#[test]
fn audit_trail_result_serde() {
let r = AuditTrailResult {
entries: vec![],
total_matched: 0,
query_from_us: 0,
query_to_us: 1_000_000,
};
let json = serde_json::to_string(&r).unwrap();
let r2: AuditTrailResult = serde_json::from_str(&json).unwrap();
assert!(r2.entries.is_empty());
assert_eq!(r2.total_matched, 0);
}
#[test]
fn food_availability_query_serde_roundtrip() {
let q = FoodAvailabilityQuery {
product_name: Some("tomatoes".into()),
market_type: Some("FarmersMarket".into()),
max_distance_km: Some(15.0),
};
let json = serde_json::to_string(&q).unwrap();
let q2: FoodAvailabilityQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q2.product_name.as_deref(), Some("tomatoes"));
assert_eq!(q2.market_type.as_deref(), Some("FarmersMarket"));
assert_eq!(q2.max_distance_km, Some(15.0));
}
#[test]
fn food_availability_query_no_filters() {
let q = FoodAvailabilityQuery {
product_name: None,
market_type: None,
max_distance_km: None,
};
let json = serde_json::to_string(&q).unwrap();
let q2: FoodAvailabilityQuery = serde_json::from_str(&json).unwrap();
assert!(q2.product_name.is_none());
}
#[test]
fn food_availability_result_serde_roundtrip() {
let r = FoodAvailabilityResult {
available_listings: 12,
nearest_market: Some("Southside Farmers Market".into()),
error: None,
};
let json = serde_json::to_string(&r).unwrap();
let r2: FoodAvailabilityResult = serde_json::from_str(&json).unwrap();
assert_eq!(r2.available_listings, 12);
assert_eq!(
r2.nearest_market.as_deref(),
Some("Southside Farmers Market")
);
assert!(r2.error.is_none());
}
#[test]
fn transport_route_query_serde_roundtrip() {
let q = TransportRouteQuery {
origin_lat: 32.9483,
origin_lon: -96.7299,
destination_lat: 32.7767,
destination_lon: -96.7970,
mode: Some("Cycling".into()),
};
let json = serde_json::to_string(&q).unwrap();
let q2: TransportRouteQuery = serde_json::from_str(&json).unwrap();
assert!((q2.origin_lat - 32.9483).abs() < 1e-4);
assert_eq!(q2.mode.as_deref(), Some("Cycling"));
}
#[test]
fn transport_route_result_serde_roundtrip() {
let r = TransportRouteResult {
route_count: 3,
estimated_minutes: Some(45),
estimated_emissions_kg_co2: Some(0.0),
error: None,
};
let json = serde_json::to_string(&r).unwrap();
let r2: TransportRouteResult = serde_json::from_str(&json).unwrap();
assert_eq!(r2.route_count, 3);
assert_eq!(r2.estimated_minutes, Some(45));
assert_eq!(r2.estimated_emissions_kg_co2, Some(0.0));
}
#[test]
fn carbon_credit_query_serde_roundtrip() {
let q = CarbonCreditQuery {
agent_did: "did:mycelix:agent123".into(),
};
let json = serde_json::to_string(&q).unwrap();
let q2: CarbonCreditQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q2.agent_did, "did:mycelix:agent123");
}
#[test]
fn carbon_credit_result_serde_roundtrip() {
let r = CarbonCreditResult {
total_credits_kg_co2: 127.5,
trips_logged: 34,
error: None,
};
let json = serde_json::to_string(&r).unwrap();
let r2: CarbonCreditResult = serde_json::from_str(&json).unwrap();
assert!((r2.total_credits_kg_co2 - 127.5).abs() < 1e-6);
assert_eq!(r2.trips_logged, 34);
assert!(r2.error.is_none());
}
#[test]
fn water_safety_query_serde_roundtrip() {
let q = WaterSafetyQuery {
area_lat: 32.9483,
area_lon: -96.7299,
radius_km: 10.0,
};
let json = serde_json::to_string(&q).unwrap();
let q2: WaterSafetyQuery = serde_json::from_str(&json).unwrap();
assert!((q2.area_lat - 32.9483).abs() < 1e-4);
assert!((q2.area_lon - (-96.7299)).abs() < 1e-4);
assert!((q2.radius_km - 10.0).abs() < 1e-6);
}
#[test]
fn water_safety_result_serde_roundtrip() {
let r = WaterSafetyResult {
safe_sources: 8,
contaminated_sources: 2,
total_sources: 10,
};
let json = serde_json::to_string(&r).unwrap();
let r2: WaterSafetyResult = serde_json::from_str(&json).unwrap();
assert_eq!(r2.safe_sources, 8);
assert_eq!(r2.contaminated_sources, 2);
assert_eq!(r2.total_sources, 10);
}
#[test]
fn water_safety_result_all_contaminated() {
let r = WaterSafetyResult {
safe_sources: 0,
contaminated_sources: 5,
total_sources: 5,
};
let json = serde_json::to_string(&r).unwrap();
let r2: WaterSafetyResult = serde_json::from_str(&json).unwrap();
assert_eq!(r2.safe_sources, 0);
assert_eq!(r2.contaminated_sources, 5);
}
#[test]
fn emergency_food_query_serde_roundtrip() {
let q = EmergencyFoodQuery {
area_lat: 29.7604,
area_lon: -95.3698,
radius_km: 25.0,
people_count: 500,
};
let json = serde_json::to_string(&q).unwrap();
let q2: EmergencyFoodQuery = serde_json::from_str(&json).unwrap();
assert!((q2.area_lat - 29.7604).abs() < 1e-4);
assert!((q2.area_lon - (-95.3698)).abs() < 1e-4);
assert!((q2.radius_km - 25.0).abs() < 1e-6);
assert_eq!(q2.people_count, 500);
}
#[test]
fn emergency_food_result_serde_roundtrip() {
let r = EmergencyFoodResult {
available_kg: 2500.5,
distribution_points: 4,
estimated_days_supply: 3.5,
};
let json = serde_json::to_string(&r).unwrap();
let r2: EmergencyFoodResult = serde_json::from_str(&json).unwrap();
assert!((r2.available_kg - 2500.5).abs() < 1e-6);
assert_eq!(r2.distribution_points, 4);
assert!((r2.estimated_days_supply - 3.5).abs() < 1e-6);
}
#[test]
fn emergency_food_result_zero_supply() {
let r = EmergencyFoodResult {
available_kg: 0.0,
distribution_points: 0,
estimated_days_supply: 0.0,
};
let json = serde_json::to_string(&r).unwrap();
let r2: EmergencyFoodResult = serde_json::from_str(&json).unwrap();
assert!((r2.available_kg).abs() < 1e-6);
assert_eq!(r2.distribution_points, 0);
assert!((r2.estimated_days_supply).abs() < 1e-6);
}
#[test]
fn shelter_capacity_query_serde_roundtrip() {
let q = ShelterCapacityQuery {
area_lat: 30.2672,
area_lon: -97.7431,
radius_km: 15.0,
beds_needed: 200,
};
let json = serde_json::to_string(&q).unwrap();
let q2: ShelterCapacityQuery = serde_json::from_str(&json).unwrap();
assert!((q2.area_lat - 30.2672).abs() < 1e-4);
assert!((q2.area_lon - (-97.7431)).abs() < 1e-4);
assert!((q2.radius_km - 15.0).abs() < 1e-6);
assert_eq!(q2.beds_needed, 200);
}
#[test]
fn shelter_capacity_result_serde_roundtrip() {
let r = ShelterCapacityResult {
available_beds: 150,
total_shelters: 3,
nearest_shelter_km: 2.4,
};
let json = serde_json::to_string(&r).unwrap();
let r2: ShelterCapacityResult = serde_json::from_str(&json).unwrap();
assert_eq!(r2.available_beds, 150);
assert_eq!(r2.total_shelters, 3);
assert!((r2.nearest_shelter_km - 2.4).abs() < 1e-6);
}
#[test]
fn shelter_capacity_result_no_shelters() {
let r = ShelterCapacityResult {
available_beds: 0,
total_shelters: 0,
nearest_shelter_km: 0.0,
};
let json = serde_json::to_string(&r).unwrap();
let r2: ShelterCapacityResult = serde_json::from_str(&json).unwrap();
assert_eq!(r2.available_beds, 0);
assert_eq!(r2.total_shelters, 0);
}
#[test]
fn emergency_care_query_serde_roundtrip() {
let q = EmergencyCareQuery {
area_lat: 32.7767,
area_lon: -96.7970,
skill_needed: "trauma_surgeon".into(),
urgency_level: 5,
};
let json = serde_json::to_string(&q).unwrap();
let q2: EmergencyCareQuery = serde_json::from_str(&json).unwrap();
assert!((q2.area_lat - 32.7767).abs() < 1e-4);
assert!((q2.area_lon - (-96.7970)).abs() < 1e-4);
assert_eq!(q2.skill_needed, "trauma_surgeon");
assert_eq!(q2.urgency_level, 5);
}
#[test]
fn emergency_care_query_low_urgency() {
let q = EmergencyCareQuery {
area_lat: 0.0,
area_lon: 0.0,
skill_needed: "first_aid".into(),
urgency_level: 1,
};
let json = serde_json::to_string(&q).unwrap();
let q2: EmergencyCareQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q2.urgency_level, 1);
assert_eq!(q2.skill_needed, "first_aid");
}
#[test]
fn emergency_care_result_serde_roundtrip() {
let r = EmergencyCareResult {
available_providers: 7,
nearest_provider_km: 1.2,
};
let json = serde_json::to_string(&r).unwrap();
let r2: EmergencyCareResult = serde_json::from_str(&json).unwrap();
assert_eq!(r2.available_providers, 7);
assert!((r2.nearest_provider_km - 1.2).abs() < 1e-6);
}
#[test]
fn emergency_care_result_no_providers() {
let r = EmergencyCareResult {
available_providers: 0,
nearest_provider_km: 0.0,
};
let json = serde_json::to_string(&r).unwrap();
let r2: EmergencyCareResult = serde_json::from_str(&json).unwrap();
assert_eq!(r2.available_providers, 0);
}
#[test]
fn water_safety_query_extreme_coordinates() {
let q = WaterSafetyQuery {
area_lat: 90.0,
area_lon: 180.0,
radius_km: 0.001,
};
let json = serde_json::to_string(&q).unwrap();
let q2: WaterSafetyQuery = serde_json::from_str(&json).unwrap();
assert!((q2.area_lat - 90.0).abs() < 1e-6);
assert!((q2.area_lon - 180.0).abs() < 1e-6);
}
#[test]
fn water_safety_query_negative_coordinates() {
let q = WaterSafetyQuery {
area_lat: -90.0,
area_lon: -180.0,
radius_km: 100.0,
};
let json = serde_json::to_string(&q).unwrap();
let q2: WaterSafetyQuery = serde_json::from_str(&json).unwrap();
assert!((q2.area_lat - (-90.0)).abs() < 1e-6);
assert!((q2.area_lon - (-180.0)).abs() < 1e-6);
}
#[test]
fn shelter_capacity_query_zero_beds_needed() {
let q = ShelterCapacityQuery {
area_lat: 0.0,
area_lon: 0.0,
radius_km: 1.0,
beds_needed: 0,
};
let json = serde_json::to_string(&q).unwrap();
let q2: ShelterCapacityQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q2.beds_needed, 0);
}
#[test]
fn emergency_food_query_zero_people() {
let q = EmergencyFoodQuery {
area_lat: 0.0,
area_lon: 0.0,
radius_km: 1.0,
people_count: 0,
};
let json = serde_json::to_string(&q).unwrap();
let q2: EmergencyFoodQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q2.people_count, 0);
}
#[test]
fn emergency_care_query_max_urgency_level() {
let q = EmergencyCareQuery {
area_lat: 0.0,
area_lon: 0.0,
skill_needed: "any".into(),
urgency_level: 255,
};
let json = serde_json::to_string(&q).unwrap();
let q2: EmergencyCareQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q2.urgency_level, 255);
}
#[test]
fn emergency_care_query_empty_skill() {
let q = EmergencyCareQuery {
area_lat: 0.0,
area_lon: 0.0,
skill_needed: "".into(),
urgency_level: 3,
};
let json = serde_json::to_string(&q).unwrap();
let q2: EmergencyCareQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q2.skill_needed, "");
}
#[test]
fn hearth_member_query_serde_roundtrip() {
let q = HearthMemberQuery {
hearth_hash: ActionHash::from_raw_36(vec![1u8; 36]),
agent: AgentPubKey::from_raw_36(vec![2u8; 36]),
};
let json = serde_json::to_string(&q).unwrap();
let q2: HearthMemberQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q.hearth_hash, q2.hearth_hash);
assert_eq!(q.agent, q2.agent);
}
#[test]
fn hearth_member_result_found() {
let r = HearthMemberResult {
is_member: true,
role: Some("Adult".into()),
display_name: Some("Alice".into()),
error: None,
};
let json = serde_json::to_string(&r).unwrap();
let r2: HearthMemberResult = serde_json::from_str(&json).unwrap();
assert!(r2.is_member);
assert_eq!(r2.role.as_deref(), Some("Adult"));
assert_eq!(r2.display_name.as_deref(), Some("Alice"));
assert!(r2.error.is_none());
}
#[test]
fn hearth_member_result_not_found() {
let r = HearthMemberResult {
is_member: false,
role: None,
display_name: None,
error: None,
};
let json = serde_json::to_string(&r).unwrap();
let r2: HearthMemberResult = serde_json::from_str(&json).unwrap();
assert!(!r2.is_member);
assert!(r2.role.is_none());
}
#[test]
fn hearth_care_query_serde_roundtrip() {
let q = HearthCareQuery {
hearth_hash: ActionHash::from_raw_36(vec![3u8; 36]),
care_type: Some("Childcare".into()),
};
let json = serde_json::to_string(&q).unwrap();
let q2: HearthCareQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q.hearth_hash, q2.hearth_hash);
assert_eq!(q2.care_type.as_deref(), Some("Childcare"));
}
#[test]
fn hearth_care_query_no_filter() {
let q = HearthCareQuery {
hearth_hash: ActionHash::from_raw_36(vec![4u8; 36]),
care_type: None,
};
let json = serde_json::to_string(&q).unwrap();
let q2: HearthCareQuery = serde_json::from_str(&json).unwrap();
assert!(q2.care_type.is_none());
}
#[test]
fn hearth_care_result_serde_roundtrip() {
let r = HearthCareResult {
available_caregivers: 3,
active_schedules: 7,
error: None,
};
let json = serde_json::to_string(&r).unwrap();
let r2: HearthCareResult = serde_json::from_str(&json).unwrap();
assert_eq!(r2.available_caregivers, 3);
assert_eq!(r2.active_schedules, 7);
assert!(r2.error.is_none());
}
#[test]
fn hearth_emergency_query_serde_roundtrip() {
let q = HearthEmergencyQuery {
hearth_hash: ActionHash::from_raw_36(vec![5u8; 36]),
};
let json = serde_json::to_string(&q).unwrap();
let q2: HearthEmergencyQuery = serde_json::from_str(&json).unwrap();
assert_eq!(q.hearth_hash, q2.hearth_hash);
}
#[test]
fn hearth_emergency_result_active() {
let r = HearthEmergencyResult {
has_active_alerts: true,
active_alert_count: 2,
members_checked_in: 4,
members_missing: 1,
error: None,
};
let json = serde_json::to_string(&r).unwrap();
let r2: HearthEmergencyResult = serde_json::from_str(&json).unwrap();
assert!(r2.has_active_alerts);
assert_eq!(r2.active_alert_count, 2);
assert_eq!(r2.members_checked_in, 4);
assert_eq!(r2.members_missing, 1);
assert!(r2.error.is_none());
}
#[test]
fn hearth_emergency_result_clear() {
let r = HearthEmergencyResult {
has_active_alerts: false,
active_alert_count: 0,
members_checked_in: 5,
members_missing: 0,
error: None,
};
let json = serde_json::to_string(&r).unwrap();
let r2: HearthEmergencyResult = serde_json::from_str(&json).unwrap();
assert!(!r2.has_active_alerts);
assert_eq!(r2.active_alert_count, 0);
assert_eq!(r2.members_missing, 0);
}
}