1pub type AgentPubKey = [u8; 32];
2pub type CapSecret = Vec<u8>;
3
4pub mod constitutional_envelope;
21pub mod consciousness_thresholds;
23pub mod consciousness_zkp;
24pub mod membership_zkp;
25#[cfg(feature = "model-governance")]
26pub mod model_governance;
27#[cfg(feature = "model-governance")]
28pub mod scoring_model;
29#[cfg(feature = "model-governance")]
30pub mod shadow_evaluation;
31pub use consciousness_thresholds as phi_thresholds;
33pub use consciousness_thresholds::{ConsciousnessThresholds, PhiThresholds};
34
35pub mod consciousness_profile;
36pub use consciousness_profile::{
38 bootstrap_credential, decay_reputation, evaluate_bootstrap_governance, evaluate_governance,
39 evaluate_governance_with_reputation, is_bootstrap_eligible, needs_refresh,
40 requirement_for_basic, requirement_for_constitutional, requirement_for_guardian,
41 requirement_for_proposal, requirement_for_voting, ConsciousnessCredential,
42 ConsciousnessProfile, ConsciousnessTier, ExtensionKey, GateAuditInput, GovernanceAuditFilter,
43 GovernanceAuditResult, GovernanceEligibility, GovernanceRequirement, ReputationState,
44 GRACE_PERIOD_US, REFRESH_WINDOW_US, REPUTATION_BLACKLIST_THRESHOLD, REPUTATION_DECAY_PER_DAY,
45 REPUTATION_MAX_SLASHES, REPUTATION_RESTORATION_INTERACTIONS, REPUTATION_SLASH_FACTOR,
46};
47#[cfg(feature = "hdk")]
49pub use consciousness_profile::gate_consciousness;
50
51pub mod sovereign_gate;
53#[cfg(feature = "hdk")]
54pub use sovereign_gate::gate_civic;
55pub use sovereign_profile::weights::DimensionWeights;
56pub use sovereign_profile::{
57 civic_requirement_basic, civic_requirement_constitutional, civic_requirement_guardian,
58 civic_requirement_proposal, civic_requirement_voting, CivicRequirement, CivicTier,
59 SovereignCredential, SovereignDimension, SovereignProfile,
60};
61
62pub mod offline_credential;
63pub mod sub_passport;
64
65#[cfg(feature = "interplanetary")]
67pub mod cross_planetary_fl;
68#[cfg(feature = "interplanetary")]
69pub mod earth_colony_protocol;
70#[cfg(feature = "interplanetary")]
71pub mod interplanetary_bridge;
72#[cfg(all(feature = "interplanetary", feature = "hdk"))]
73pub mod mars_isru;
74#[cfg(feature = "interplanetary")]
75pub mod planetary_governance;
76
77#[cfg(feature = "federated")]
79pub mod terrain_fl;
80#[cfg(feature = "hdk")]
86pub mod validation;
87#[cfg(feature = "hdk")]
88pub use validation::{check_author_match, check_link_author_match};
89
90pub mod collective_phi;
91pub use collective_phi::{
92 AgentConsciousnessVector, CollectivePhiEngine, CollectivePhiResult, COLLECTIVE_PHI_MAX_SYNC,
93};
94
95pub mod routing;
96pub use routing::{
97 resolve_civic_zome, resolve_commons_zome, BridgeDomain, CivicZome, CommonsZome,
98 CrossClusterRole, CIVIC_DOMAINS, COMMONS_DOMAINS,
99};
100
101pub mod routing_registry;
102
103pub mod metrics;
104
105#[cfg(feature = "infrastructure")]
107pub mod migration;
108pub mod notifications;
109#[cfg(feature = "infrastructure")]
110pub mod saga; #[cfg(feature = "infrastructure")]
113pub mod license_enforcement;
114#[cfg(feature = "infrastructure")]
115pub mod merkle_timestamp;
116#[cfg(feature = "infrastructure")]
117pub mod timestamp_anchor;
118
119#[cfg(kani)]
120mod kani_proofs;
121
122#[cfg(feature = "hdk")]
123use hdk::prelude::*;
124use serde::{Deserialize, Serialize};
125
126#[derive(Serialize, Deserialize, Debug, Clone)]
132pub struct DispatchInput {
133 pub zome: String,
136 pub fn_name: String,
138 pub payload: Vec<u8>,
140}
141
142#[derive(Serialize, Deserialize, Debug, Clone)]
144pub struct DispatchResult {
145 pub success: bool,
147 pub response: Option<Vec<u8>>,
149 pub error: Option<String>,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
155 pub error_code: Option<BridgeErrorCode>,
156}
157
158impl DispatchResult {
159 pub fn ok(response: Vec<u8>) -> Self {
161 Self {
162 success: true,
163 response: Some(response),
164 error: None,
165 error_code: None,
166 }
167 }
168
169 pub fn err(code: BridgeErrorCode, message: String) -> Self {
171 Self {
172 success: false,
173 response: None,
174 error: Some(message),
175 error_code: Some(code),
176 }
177 }
178}
179
180#[cfg(feature = "hdk")]
182#[derive(Serialize, Deserialize, Debug, Clone)]
183pub struct ResolveQueryInput {
184 pub query_hash: ActionHash,
185 pub result: String,
186 pub success: bool,
187}
188
189#[derive(Serialize, Deserialize, Debug, Clone)]
191pub struct EventTypeQuery {
192 pub domain: String,
193 pub event_type: String,
194}
195
196#[derive(Serialize, Deserialize, Debug, Clone)]
198pub struct BridgeHealth {
199 pub healthy: bool,
200 pub agent: String,
201 pub total_events: u32,
202 pub total_queries: u32,
203 pub domains: Vec<String>,
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
219pub enum BridgeErrorCode {
220 AllowlistRejected,
222 LocalNetworkError,
224 LocalCallRejected,
226 LocalNoResponse,
228 LocalCallFailed,
230 CrossClusterNetworkError,
232 CrossClusterCallRejected,
234 CrossClusterNoResponse,
236 CrossClusterCallFailed,
238 ValidationFailed,
240}
241
242impl BridgeErrorCode {
243 pub fn as_str(&self) -> &'static str {
245 match self {
246 Self::AllowlistRejected => "BRG-001",
247 Self::LocalNetworkError => "BRG-002",
248 Self::LocalCallRejected => "BRG-003",
249 Self::LocalNoResponse => "BRG-004",
250 Self::LocalCallFailed => "BRG-005",
251 Self::CrossClusterNetworkError => "BRG-006",
252 Self::CrossClusterCallRejected => "BRG-007",
253 Self::CrossClusterNoResponse => "BRG-008",
254 Self::CrossClusterCallFailed => "BRG-009",
255 Self::ValidationFailed => "BRG-010",
256 }
257 }
258}
259
260impl core::fmt::Display for BridgeErrorCode {
261 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
262 write!(f, "{}", self.as_str())
263 }
264}
265
266pub const MAX_DISPATCH_PAYLOAD_BYTES: usize = 1_048_576;
273
274pub const MAX_DISPATCH_IDENTIFIER_BYTES: usize = 256;
276
277fn validate_dispatch_sizes(zome: &str, fn_name: &str, payload: &[u8]) -> Result<(), String> {
279 if zome.len() > MAX_DISPATCH_IDENTIFIER_BYTES {
280 return Err(format!(
281 "Zome name too long ({} bytes, max {})",
282 zome.len(),
283 MAX_DISPATCH_IDENTIFIER_BYTES
284 ));
285 }
286 if fn_name.len() > MAX_DISPATCH_IDENTIFIER_BYTES {
287 return Err(format!(
288 "Function name too long ({} bytes, max {})",
289 fn_name.len(),
290 MAX_DISPATCH_IDENTIFIER_BYTES
291 ));
292 }
293 if payload.len() > MAX_DISPATCH_PAYLOAD_BYTES {
294 return Err(format!(
295 "Payload too large ({} bytes, max {})",
296 payload.len(),
297 MAX_DISPATCH_PAYLOAD_BYTES
298 ));
299 }
300 Ok(())
301}
302
303#[cfg(feature = "hdk")]
317pub fn dispatch_call_checked(
318 input: &DispatchInput,
319 allowed_zomes: &[&str],
320) -> ExternResult<DispatchResult> {
321 if let Err(msg) = validate_dispatch_sizes(&input.zome, &input.fn_name, &input.payload) {
322 metrics::record_error(
323 &input.zome,
324 &input.fn_name,
325 BridgeErrorCode::ValidationFailed.as_str(),
326 );
327 return Ok(DispatchResult::err(BridgeErrorCode::ValidationFailed, msg));
328 }
329 if !allowed_zomes.contains(&input.zome.as_str()) {
330 metrics::record_error(
331 &input.zome,
332 &input.fn_name,
333 BridgeErrorCode::AllowlistRejected.as_str(),
334 );
335 return Ok(DispatchResult::err(
336 BridgeErrorCode::AllowlistRejected,
337 format!(
338 "Zome '{}' is not in the allowed dispatch list. Valid zomes: {:?}",
339 input.zome, allowed_zomes
340 ),
341 ));
342 }
343
344 let payload = ExternIO(input.payload.clone());
345 let start_us = sys_time().ok().map(|t| t.as_micros() as u64);
346
347 let result = HDK.with(|h| {
348 h.borrow().call(vec![Call::new(
349 CallTarget::ConductorCell(CallTargetCell::Local),
350 ZomeName::from(input.zome.as_str()),
351 FunctionName::from(input.fn_name.as_str()),
352 None,
353 payload,
354 )])
355 });
356
357 let elapsed_us = start_us.and_then(|start| {
358 sys_time()
359 .ok()
360 .map(|end| (end.as_micros() as u64).saturating_sub(start))
361 });
362
363 match result {
364 Ok(responses) => match responses.into_iter().next() {
365 Some(ZomeCallResponse::Ok(extern_io)) => {
366 if let Some(latency) = elapsed_us {
367 metrics::record_success(&input.zome, &input.fn_name, latency);
368 }
369 Ok(DispatchResult::ok(extern_io.0))
370 }
371 Some(ZomeCallResponse::NetworkError(err)) => {
372 let code = BridgeErrorCode::LocalNetworkError;
373 metrics::record_error(&input.zome, &input.fn_name, code.as_str());
374 Ok(DispatchResult::err(code, format!("Network error: {}", err)))
375 }
376 Some(other) => {
377 let code = BridgeErrorCode::LocalCallRejected;
378 metrics::record_error(&input.zome, &input.fn_name, code.as_str());
379 Ok(DispatchResult::err(
380 code,
381 format!("Zome call rejected: {:?}", other),
382 ))
383 }
384 None => {
385 let code = BridgeErrorCode::LocalNoResponse;
386 metrics::record_error(&input.zome, &input.fn_name, code.as_str());
387 Ok(DispatchResult::err(
388 code,
389 "No response from zome call".into(),
390 ))
391 }
392 },
393 Err(e) => {
394 let code = BridgeErrorCode::LocalCallFailed;
395 metrics::record_error(&input.zome, &input.fn_name, code.as_str());
396 Ok(DispatchResult::err(code, format!("Call failed: {:?}", e)))
397 }
398 }
399}
400
401#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
412pub enum ConstellationTarget {
413 Internal { role: String },
415 External {
417 agent: AgentPubKey,
418 cap_secret: Option<CapSecret>,
419 },
420}
421
422#[cfg(feature = "hdk")]
427pub fn dispatch_constellation_call(
428 target: &ConstellationTarget,
429 zome: &str,
430 fn_name: &str,
431 payload: Vec<u8>,
432) -> ExternResult<DispatchResult> {
433 let start_us = sys_time().ok().map(|t| t.as_micros() as u64);
434
435 let result = match target {
436 ConstellationTarget::Internal { role } => HDK.with(|h| {
437 h.borrow().call(vec![Call::new(
438 CallTarget::ConductorCell(CallTargetCell::OtherRole(role.clone())),
439 ZomeName::from(zome),
440 FunctionName::from(fn_name),
441 None,
442 ExternIO(payload),
443 )])
444 }),
445 ConstellationTarget::External { agent, cap_secret } => {
446 match call_remote(
449 agent.clone(),
450 zome,
451 fn_name.into(),
452 *cap_secret,
453 ExternIO(payload),
454 ) {
455 Ok(ZomeCallResponse::Ok(extern_io)) => Ok(vec![ZomeCallResponse::Ok(extern_io)]),
456 Ok(other) => Ok(vec![other]),
457 Err(e) => Err(e),
458 }
459 }
460 };
461
462 let elapsed_us = start_us.and_then(|start| {
463 sys_time()
464 .ok()
465 .map(|end| (end.as_micros() as u64).saturating_sub(start))
466 });
467
468 match result {
469 Ok(responses) => match responses.into_iter().next() {
470 Some(ZomeCallResponse::Ok(extern_io)) => {
471 if let Some(latency) = elapsed_us {
472 metrics::record_success(zome, fn_name, latency);
473 }
474 Ok(DispatchResult::ok(extern_io.0))
475 }
476 Some(ZomeCallResponse::NetworkError(err)) => Ok(DispatchResult::err(
477 BridgeErrorCode::LocalNetworkError,
478 format!("Network error: {}", err),
479 )),
480 Some(other) => Ok(DispatchResult::err(
481 BridgeErrorCode::LocalCallRejected,
482 format!("Rejected: {:?}", other),
483 )),
484 None => Ok(DispatchResult::err(
485 BridgeErrorCode::LocalNoResponse,
486 "No response".into(),
487 )),
488 },
489 Err(e) => Ok(DispatchResult::err(
490 BridgeErrorCode::LocalCallFailed,
491 format!("Call failed: {:?}", e),
492 )),
493 }
494}
495
496#[derive(Serialize, Deserialize, Debug, Clone)]
502pub struct CrossClusterDispatchInput {
503 pub role: String,
505 pub zome: String,
507 pub fn_name: String,
509 pub payload: Vec<u8>,
511}
512
513#[derive(Serialize, Deserialize, Debug, Clone)]
519pub struct CorrelatedDispatch {
520 pub correlation_id: String,
523 pub target_zome: String,
525 pub target_fn: String,
527 pub payload: String,
529}
530
531#[cfg(feature = "hdk")]
539pub fn dispatch_call_cross_cluster(
540 input: &CrossClusterDispatchInput,
541 allowed_zomes: &[&str],
542) -> ExternResult<DispatchResult> {
543 if let Err(msg) = validate_dispatch_sizes(&input.zome, &input.fn_name, &input.payload) {
544 metrics::record_error(
545 &input.zome,
546 &input.fn_name,
547 BridgeErrorCode::ValidationFailed.as_str(),
548 );
549 return Ok(DispatchResult::err(BridgeErrorCode::ValidationFailed, msg));
550 }
551 if input.role.len() > MAX_DISPATCH_IDENTIFIER_BYTES {
552 metrics::record_error(
553 &input.zome,
554 &input.fn_name,
555 BridgeErrorCode::ValidationFailed.as_str(),
556 );
557 return Ok(DispatchResult::err(
558 BridgeErrorCode::ValidationFailed,
559 format!(
560 "Role name too long ({} bytes, max {})",
561 input.role.len(),
562 MAX_DISPATCH_IDENTIFIER_BYTES
563 ),
564 ));
565 }
566
567 if !allowed_zomes.contains(&input.zome.as_str()) {
568 metrics::record_error(
569 &input.zome,
570 &input.fn_name,
571 BridgeErrorCode::AllowlistRejected.as_str(),
572 );
573 return Ok(DispatchResult::err(
574 BridgeErrorCode::AllowlistRejected,
575 format!(
576 "Zome '{}' is not in the allowed cross-cluster dispatch list. Valid zomes: {:?}",
577 input.zome, allowed_zomes
578 ),
579 ));
580 }
581
582 metrics::record_cross_cluster();
583
584 let target = ConstellationTarget::Internal {
586 role: input.role.clone(),
587 };
588
589 dispatch_constellation_call(&target, &input.zome, &input.fn_name, input.payload.clone())
590}
591
592#[cfg(feature = "hdk")]
601pub fn dispatch_call_cross_cluster_commons(
602 input: &CrossClusterDispatchInput,
603 allowed_zomes: &[&str],
604) -> ExternResult<DispatchResult> {
605 let role = CommonsZome::resolve_role(&input.zome).unwrap_or("commons_land");
607
608 let routed_input = CrossClusterDispatchInput {
609 role: role.to_string(),
610 zome: input.zome.clone(),
611 fn_name: input.fn_name.clone(),
612 payload: input.payload.clone(),
613 };
614 dispatch_call_cross_cluster(&routed_input, allowed_zomes)
615}
616
617pub const RATE_LIMIT_MAX_DISPATCH: usize = 100;
623
624pub const RATE_LIMIT_WINDOW_SECS: i64 = 60;
626
627pub fn check_rate_limit_count(recent_count: usize) -> Result<(), String> {
634 if recent_count >= RATE_LIMIT_MAX_DISPATCH {
635 Err(format!(
636 "Rate limit exceeded: {} dispatches in {}s (max {})",
637 recent_count, RATE_LIMIT_WINDOW_SECS, RATE_LIMIT_MAX_DISPATCH
638 ))
639 } else {
640 Ok(())
641 }
642}
643
644#[derive(Serialize, Deserialize, Debug, Clone)]
650pub struct PropertyOwnershipQuery {
651 pub property_id: String,
652 pub requester_did: String,
653}
654
655#[derive(Serialize, Deserialize, Debug, Clone)]
657pub struct PropertyOwnershipResult {
658 pub is_owner: bool,
659 pub owner_did: Option<String>,
660 pub error: Option<String>,
661}
662
663#[derive(Serialize, Deserialize, Debug, Clone)]
665pub struct CareAvailabilityQuery {
666 pub skill_needed: String,
667 pub location: Option<String>,
668}
669
670#[derive(Serialize, Deserialize, Debug, Clone)]
672pub struct CareAvailabilityResult {
673 pub available_count: u32,
674 pub recommendation: String,
675 pub error: Option<String>,
676}
677
678#[derive(Serialize, Deserialize, Debug, Clone)]
680pub struct JusticeAreaQuery {
681 pub area: String,
682 pub case_type: Option<String>,
683}
684
685#[derive(Serialize, Deserialize, Debug, Clone)]
687pub struct JusticeAreaResult {
688 pub active_cases: u32,
689 pub recommendation: String,
690 pub error: Option<String>,
691}
692
693#[derive(Serialize, Deserialize, Debug, Clone)]
695pub struct FactcheckStatusQuery {
696 pub claim_id: String,
697}
698
699#[derive(Serialize, Deserialize, Debug, Clone)]
701pub struct FactcheckStatusResult {
702 pub has_factcheck: bool,
703 pub verdict: Option<String>,
704 pub error: Option<String>,
705}
706
707#[derive(Serialize, Deserialize, Debug, Clone)]
709pub struct FoodAvailabilityQuery {
710 pub product_name: Option<String>,
711 pub market_type: Option<String>,
712 pub max_distance_km: Option<f64>,
713}
714
715#[derive(Serialize, Deserialize, Debug, Clone)]
717pub struct FoodAvailabilityResult {
718 pub available_listings: u32,
719 pub nearest_market: Option<String>,
720 pub error: Option<String>,
721}
722
723#[derive(Serialize, Deserialize, Debug, Clone)]
725pub struct TransportRouteQuery {
726 pub origin_lat: f64,
727 pub origin_lon: f64,
728 pub destination_lat: f64,
729 pub destination_lon: f64,
730 pub mode: Option<String>,
731}
732
733#[derive(Serialize, Deserialize, Debug, Clone)]
735pub struct TransportRouteResult {
736 pub route_count: u32,
737 pub estimated_minutes: Option<u32>,
738 pub estimated_emissions_kg_co2: Option<f64>,
739 pub error: Option<String>,
740}
741
742#[derive(Serialize, Deserialize, Debug, Clone)]
744pub struct CarbonCreditQuery {
745 pub agent_did: String,
746}
747
748#[derive(Serialize, Deserialize, Debug, Clone)]
750pub struct CarbonCreditResult {
751 pub total_credits_kg_co2: f64,
752 pub trips_logged: u32,
753 pub error: Option<String>,
754}
755
756#[derive(Serialize, Deserialize, Debug, Clone)]
762pub struct WaterSafetyQuery {
763 pub area_lat: f64,
764 pub area_lon: f64,
765 pub radius_km: f64,
766}
767
768#[derive(Serialize, Deserialize, Debug, Clone)]
770pub struct WaterSafetyResult {
771 pub safe_sources: u32,
772 pub contaminated_sources: u32,
773 pub total_sources: u32,
774}
775
776#[derive(Serialize, Deserialize, Debug, Clone)]
778pub struct EmergencyFoodQuery {
779 pub area_lat: f64,
780 pub area_lon: f64,
781 pub radius_km: f64,
782 pub people_count: u32,
783}
784
785#[derive(Serialize, Deserialize, Debug, Clone)]
787pub struct EmergencyFoodResult {
788 pub available_kg: f64,
789 pub distribution_points: u32,
790 pub estimated_days_supply: f64,
791}
792
793#[derive(Serialize, Deserialize, Debug, Clone)]
795pub struct ShelterCapacityQuery {
796 pub area_lat: f64,
797 pub area_lon: f64,
798 pub radius_km: f64,
799 pub beds_needed: u32,
800}
801
802#[derive(Serialize, Deserialize, Debug, Clone)]
804pub struct ShelterCapacityResult {
805 pub available_beds: u32,
806 pub total_shelters: u32,
807 pub nearest_shelter_km: f64,
808}
809
810#[derive(Serialize, Deserialize, Debug, Clone)]
812pub struct EmergencyCareQuery {
813 pub area_lat: f64,
814 pub area_lon: f64,
815 pub skill_needed: String,
816 pub urgency_level: u8,
817}
818
819#[derive(Serialize, Deserialize, Debug, Clone)]
821pub struct EmergencyCareResult {
822 pub available_providers: u32,
823 pub nearest_provider_km: f64,
824}
825
826#[derive(Serialize, Deserialize, Debug, Clone)]
832pub struct AuditTrailQuery {
833 pub from_us: i64,
835 pub to_us: i64,
837 pub domain: Option<String>,
839 pub event_type: Option<String>,
841}
842
843#[derive(Serialize, Deserialize, Debug, Clone)]
845pub struct AuditTrailEntry {
846 pub domain: String,
847 pub event_type: String,
848 pub source_agent: String,
849 pub payload_preview: String,
850 pub created_at_us: i64,
851 #[cfg(feature = "hdk")]
852 pub action_hash: ActionHash,
853 #[cfg(not(feature = "hdk"))]
854 pub action_hash: String,
855}
856
857#[derive(Serialize, Deserialize, Debug, Clone)]
859pub struct AuditTrailResult {
860 pub entries: Vec<AuditTrailEntry>,
861 pub total_matched: u32,
862 pub query_from_us: i64,
863 pub query_to_us: i64,
864}
865
866#[cfg(feature = "hdk")]
871#[derive(Serialize, Deserialize, Debug, Clone)]
873pub struct HearthMemberQuery {
874 pub hearth_hash: ActionHash,
875 pub agent: AgentPubKey,
876}
877
878#[derive(Serialize, Deserialize, Debug, Clone)]
880pub struct HearthMemberResult {
881 pub is_member: bool,
882 pub role: Option<String>,
883 pub display_name: Option<String>,
884 pub error: Option<String>,
885}
886
887#[cfg(feature = "hdk")]
888#[derive(Serialize, Deserialize, Debug, Clone)]
890pub struct HearthCareQuery {
891 pub hearth_hash: ActionHash,
892 pub care_type: Option<String>,
893}
894
895#[derive(Serialize, Deserialize, Debug, Clone)]
897pub struct HearthCareResult {
898 pub available_caregivers: u32,
899 pub active_schedules: u32,
900 pub error: Option<String>,
901}
902
903#[cfg(feature = "hdk")]
904#[derive(Serialize, Deserialize, Debug, Clone)]
906pub struct HearthEmergencyQuery {
907 pub hearth_hash: ActionHash,
908}
909
910#[derive(Serialize, Deserialize, Debug, Clone)]
912pub struct HearthEmergencyResult {
913 pub has_active_alerts: bool,
914 pub active_alert_count: u32,
915 pub members_checked_in: u32,
916 pub members_missing: u32,
917 pub error: Option<String>,
918}
919
920#[derive(Serialize, Deserialize, Debug, Clone)]
926pub struct BudgetProposalQuery {
927 pub proposal_id: String,
928}
929
930#[derive(Serialize, Deserialize, Debug, Clone)]
932pub struct BudgetProposalResult {
933 pub approved: bool,
934 pub amount: u64,
935 pub treasury_balance: u64,
936 pub error: Option<String>,
937}
938
939#[derive(Serialize, Deserialize, Debug, Clone)]
941pub struct CollateralPropertyQuery {
942 pub property_hash: String,
943}
944
945#[derive(Serialize, Deserialize, Debug, Clone)]
947pub struct CollateralPropertyResult {
948 pub valid: bool,
949 pub appraised_value: u64,
950 pub encumbered: bool,
951 pub error: Option<String>,
952}
953
954#[derive(Serialize, Deserialize, Debug, Clone)]
956pub struct RestitutionQuery {
957 pub case_id: String,
958 pub defendant_did: String,
959}
960
961#[derive(Serialize, Deserialize, Debug, Clone)]
963pub struct RestitutionResult {
964 pub balance_sufficient: bool,
965 pub amount_due: u64,
966 pub error: Option<String>,
967}
968
969#[derive(Serialize, Deserialize, Debug, Clone)]
971pub struct RevocationNotice {
972 pub credential_hash: String,
973 pub did: String,
974 pub reason: String,
975 pub effective_at: u64,
976}
977
978#[derive(Serialize, Deserialize, Debug, Clone)]
980pub struct RevocationAck {
981 pub received: bool,
982 pub affected_entries: u32,
983 pub error: Option<String>,
984}
985
986#[derive(Serialize, Deserialize, Debug, Clone)]
988pub struct ConsentedRecordQuery {
989 pub patient_did: String,
990 pub record_type: String,
991}
992
993#[derive(Serialize, Deserialize, Debug, Clone)]
995pub struct ConsentedRecordResult {
996 pub authorized: bool,
997 pub record_hash: Option<String>,
998 pub error: Option<String>,
999}
1000
1001#[derive(Serialize, Deserialize, Debug, Clone)]
1003pub struct ProjectProposalQuery {
1004 pub project_id: String,
1005}
1006
1007#[derive(Serialize, Deserialize, Debug, Clone)]
1009pub struct ProjectProposalResult {
1010 pub governance_approved: bool,
1011 pub conditions: Vec<String>,
1012 pub error: Option<String>,
1013}
1014
1015#[derive(Serialize, Deserialize, Debug, Clone)]
1017pub struct ClaimVerificationQuery {
1018 pub claim_hash: String,
1019}
1020
1021#[derive(Serialize, Deserialize, Debug, Clone)]
1023pub struct ClaimVerificationResult {
1024 pub verified: bool,
1025 pub confidence: f64,
1026 pub sources: Vec<String>,
1027 pub error: Option<String>,
1028}
1029
1030#[derive(Serialize, Deserialize, Debug, Clone)]
1032pub struct CarbonOffsetQuery {
1033 pub route_id: String,
1034 pub distance_km: f64,
1035}
1036
1037#[derive(Serialize, Deserialize, Debug, Clone)]
1039pub struct CarbonOffsetResult {
1040 pub credits_earned: f64,
1041 pub offset_hash: Option<String>,
1042 pub error: Option<String>,
1043}
1044
1045#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
1047pub enum NotificationPriority {
1048 Low = 0,
1050 Normal = 1,
1052 High = 2,
1054 Emergency = 3,
1056}
1057
1058impl NotificationPriority {
1059 pub const fn from_u8(v: u8) -> Self {
1060 match v {
1061 0 => Self::Low,
1062 1 => Self::Normal,
1063 2 => Self::High,
1064 3 => Self::Emergency,
1065 _ => Self::Normal,
1066 }
1067 }
1068
1069 pub const fn as_u8(&self) -> u8 {
1070 *self as u8
1071 }
1072}
1073
1074#[cfg(feature = "hdk")]
1080pub fn records_from_links(links: Vec<Link>) -> ExternResult<Vec<Record>> {
1081 let mut records = Vec::new();
1082 for link in links {
1083 let action_hash = ActionHash::try_from(link.target)
1084 .map_err(|_| wasm_error!(WasmErrorInner::Guest("Invalid link target".into())))?;
1085 if let Some(record) = get(action_hash, GetOptions::default())? {
1086 records.push(record);
1087 }
1088 }
1089 Ok(records)
1090}
1091
1092#[cfg(test)]
1093mod tests {
1094 use super::*;
1095
1096 #[test]
1100 fn dispatch_rejects_disallowed_zome() {
1101 let input = DispatchInput {
1102 zome: "evil_zome".into(),
1103 fn_name: "steal_data".into(),
1104 payload: vec![],
1105 };
1106 let allowed = &["property_registry", "housing_units"];
1107 let result = dispatch_call_checked(&input, allowed).unwrap();
1108 assert!(!result.success);
1109 assert!(result.response.is_none());
1110 let err = result.error.unwrap();
1111 assert!(err.contains("not in the allowed dispatch list"));
1112 assert!(err.contains("evil_zome"));
1113 }
1114
1115 #[test]
1116 fn dispatch_rejects_empty_allowlist() {
1117 let input = DispatchInput {
1118 zome: "property_registry".into(),
1119 fn_name: "get_property".into(),
1120 payload: vec![],
1121 };
1122 let result = dispatch_call_checked(&input, &[]).unwrap();
1123 assert!(!result.success);
1124 assert!(result.error.is_some());
1125 }
1126
1127 #[test]
1128 fn dispatch_rejects_similar_zome_name() {
1129 let input = DispatchInput {
1130 zome: "property_registry_evil".into(),
1131 fn_name: "get_property".into(),
1132 payload: vec![],
1133 };
1134 let allowed = &["property_registry"];
1135 let result = dispatch_call_checked(&input, allowed).unwrap();
1136 assert!(!result.success);
1137 }
1138
1139 #[test]
1140 fn dispatch_error_lists_valid_zomes() {
1141 let input = DispatchInput {
1142 zome: "bad".into(),
1143 fn_name: "fn".into(),
1144 payload: vec![],
1145 };
1146 let allowed = &["alpha", "beta", "gamma"];
1147 let result = dispatch_call_checked(&input, allowed).unwrap();
1148 let err = result.error.unwrap();
1149 assert!(err.contains("alpha"));
1150 assert!(err.contains("beta"));
1151 assert!(err.contains("gamma"));
1152 }
1153
1154 #[test]
1157 fn dispatch_input_serde_roundtrip() {
1158 let input = DispatchInput {
1159 zome: "property_registry".into(),
1160 fn_name: "get_property".into(),
1161 payload: vec![1, 2, 3, 4],
1162 };
1163 let json = serde_json::to_string(&input).unwrap();
1164 let input2: DispatchInput = serde_json::from_str(&json).unwrap();
1165 assert_eq!(input.zome, input2.zome);
1166 assert_eq!(input.fn_name, input2.fn_name);
1167 assert_eq!(input.payload, input2.payload);
1168 }
1169
1170 #[test]
1171 fn dispatch_result_success_serde_roundtrip() {
1172 let result = DispatchResult::ok(vec![10, 20, 30]);
1173 let json = serde_json::to_string(&result).unwrap();
1174 let r2: DispatchResult = serde_json::from_str(&json).unwrap();
1175 assert!(r2.success);
1176 assert_eq!(r2.response, Some(vec![10, 20, 30]));
1177 assert!(r2.error.is_none());
1178 assert!(r2.error_code.is_none());
1179 }
1180
1181 #[test]
1182 fn dispatch_result_error_serde_roundtrip() {
1183 let result =
1184 DispatchResult::err(BridgeErrorCode::LocalCallFailed, "something failed".into());
1185 let json = serde_json::to_string(&result).unwrap();
1186 assert!(json.contains("error_code")); let r2: DispatchResult = serde_json::from_str(&json).unwrap();
1188 assert!(!r2.success);
1189 assert!(r2.response.is_none());
1190 assert_eq!(r2.error.as_deref(), Some("something failed"));
1191 assert_eq!(r2.error_code, Some(BridgeErrorCode::LocalCallFailed));
1192 }
1193
1194 #[test]
1195 fn dispatch_result_backward_compat_without_error_code() {
1196 let json = r#"{"success":false,"response":null,"error":"old error"}"#;
1198 let r: DispatchResult = serde_json::from_str(json).unwrap();
1199 assert!(!r.success);
1200 assert_eq!(r.error.as_deref(), Some("old error"));
1201 assert_eq!(r.error_code, None); }
1203
1204 #[test]
1205 fn event_type_query_serde_roundtrip() {
1206 let q = EventTypeQuery {
1207 domain: "housing".into(),
1208 event_type: "lease_created".into(),
1209 };
1210 let json = serde_json::to_string(&q).unwrap();
1211 let q2: EventTypeQuery = serde_json::from_str(&json).unwrap();
1212 assert_eq!(q.domain, q2.domain);
1213 assert_eq!(q.event_type, q2.event_type);
1214 }
1215
1216 #[test]
1219 fn cross_cluster_rejects_disallowed_zome() {
1220 let input = CrossClusterDispatchInput {
1221 role: "civic".into(),
1222 zome: "evil_zome".into(),
1223 fn_name: "steal_data".into(),
1224 payload: vec![],
1225 };
1226 let allowed = &["justice_cases", "emergency_incidents"];
1227 let result = dispatch_call_cross_cluster(&input, allowed).unwrap();
1228 assert!(!result.success);
1229 assert!(result.response.is_none());
1230 let err = result.error.unwrap();
1231 assert!(err.contains("not in the allowed cross-cluster dispatch list"));
1232 assert!(err.contains("evil_zome"));
1233 }
1234
1235 #[test]
1236 fn cross_cluster_rejects_empty_allowlist() {
1237 let input = CrossClusterDispatchInput {
1238 role: "commons".into(),
1239 zome: "property_registry".into(),
1240 fn_name: "get_property".into(),
1241 payload: vec![],
1242 };
1243 let result = dispatch_call_cross_cluster(&input, &[]).unwrap();
1244 assert!(!result.success);
1245 assert!(result.error.is_some());
1246 }
1247
1248 #[test]
1249 fn cross_cluster_rejects_similar_zome_name() {
1250 let input = CrossClusterDispatchInput {
1251 role: "civic".into(),
1252 zome: "justice_cases_evil".into(),
1253 fn_name: "get_case".into(),
1254 payload: vec![],
1255 };
1256 let allowed = &["justice_cases"];
1257 let result = dispatch_call_cross_cluster(&input, allowed).unwrap();
1258 assert!(!result.success);
1259 }
1260
1261 #[test]
1262 fn cross_cluster_error_lists_valid_zomes() {
1263 let input = CrossClusterDispatchInput {
1264 role: "civic".into(),
1265 zome: "bad".into(),
1266 fn_name: "fn".into(),
1267 payload: vec![],
1268 };
1269 let allowed = &["justice_cases", "emergency_incidents", "media_publication"];
1270 let result = dispatch_call_cross_cluster(&input, allowed).unwrap();
1271 let err = result.error.unwrap();
1272 assert!(err.contains("justice_cases"));
1273 assert!(err.contains("emergency_incidents"));
1274 assert!(err.contains("media_publication"));
1275 }
1276
1277 #[test]
1278 fn cross_cluster_dispatch_input_serde_roundtrip() {
1279 let input = CrossClusterDispatchInput {
1280 role: "civic".into(),
1281 zome: "justice_cases".into(),
1282 fn_name: "get_case".into(),
1283 payload: vec![5, 6, 7],
1284 };
1285 let json = serde_json::to_string(&input).unwrap();
1286 let input2: CrossClusterDispatchInput = serde_json::from_str(&json).unwrap();
1287 assert_eq!(input.role, input2.role);
1288 assert_eq!(input.zome, input2.zome);
1289 assert_eq!(input.fn_name, input2.fn_name);
1290 assert_eq!(input.payload, input2.payload);
1291 }
1292
1293 #[test]
1294 fn bridge_health_serde_roundtrip() {
1295 let h = BridgeHealth {
1296 healthy: true,
1297 agent: "uhCAk_test_agent".into(),
1298 total_events: 42,
1299 total_queries: 7,
1300 domains: vec!["property".into(), "housing".into()],
1301 };
1302 let json = serde_json::to_string(&h).unwrap();
1303 let h2: BridgeHealth = serde_json::from_str(&json).unwrap();
1304 assert!(h2.healthy);
1305 assert_eq!(h2.total_events, 42);
1306 assert_eq!(h2.total_queries, 7);
1307 assert_eq!(h2.domains.len(), 2);
1308 }
1309
1310 #[test]
1313 fn rate_limit_zero_calls_passes() {
1314 assert!(check_rate_limit_count(0).is_ok());
1315 }
1316
1317 #[test]
1318 fn rate_limit_under_max_passes() {
1319 assert!(check_rate_limit_count(99).is_ok());
1320 }
1321
1322 #[test]
1323 fn rate_limit_at_max_rejects() {
1324 let err = check_rate_limit_count(RATE_LIMIT_MAX_DISPATCH).unwrap_err();
1325 assert!(err.contains("Rate limit exceeded"));
1326 }
1327
1328 #[test]
1329 fn rate_limit_over_max_rejects() {
1330 let err = check_rate_limit_count(1000).unwrap_err();
1331 assert!(err.contains("Rate limit exceeded"));
1332 assert!(err.contains("1000"));
1333 }
1334
1335 #[test]
1336 fn rate_limit_error_includes_window() {
1337 let err = check_rate_limit_count(200).unwrap_err();
1338 assert!(err.contains(&format!("{}s", RATE_LIMIT_WINDOW_SECS)));
1339 }
1340
1341 #[test]
1344 fn property_ownership_query_serde_roundtrip() {
1345 let q = PropertyOwnershipQuery {
1346 property_id: "PROP-001".into(),
1347 requester_did: "did:mycelix:abc".into(),
1348 };
1349 let json = serde_json::to_string(&q).unwrap();
1350 let q2: PropertyOwnershipQuery = serde_json::from_str(&json).unwrap();
1351 assert_eq!(q.property_id, q2.property_id);
1352 assert_eq!(q.requester_did, q2.requester_did);
1353 }
1354
1355 #[test]
1356 fn property_ownership_result_serde_roundtrip() {
1357 let r = PropertyOwnershipResult {
1358 is_owner: true,
1359 owner_did: Some("did:mycelix:owner".into()),
1360 error: None,
1361 };
1362 let json = serde_json::to_string(&r).unwrap();
1363 let r2: PropertyOwnershipResult = serde_json::from_str(&json).unwrap();
1364 assert!(r2.is_owner);
1365 assert_eq!(r2.owner_did, Some("did:mycelix:owner".into()));
1366 }
1367
1368 #[test]
1369 fn care_availability_query_serde_roundtrip() {
1370 let q = CareAvailabilityQuery {
1371 skill_needed: "nursing".into(),
1372 location: None,
1373 };
1374 let json = serde_json::to_string(&q).unwrap();
1375 let q2: CareAvailabilityQuery = serde_json::from_str(&json).unwrap();
1376 assert_eq!(q.skill_needed, q2.skill_needed);
1377 assert!(q2.location.is_none());
1378 }
1379
1380 #[test]
1381 fn justice_area_query_serde_roundtrip() {
1382 let q = JusticeAreaQuery {
1383 area: "north-side".into(),
1384 case_type: Some("civil".into()),
1385 };
1386 let json = serde_json::to_string(&q).unwrap();
1387 let q2: JusticeAreaQuery = serde_json::from_str(&json).unwrap();
1388 assert_eq!(q.area, q2.area);
1389 assert_eq!(q.case_type, q2.case_type);
1390 }
1391
1392 #[test]
1393 fn factcheck_status_query_serde_roundtrip() {
1394 let q = FactcheckStatusQuery {
1395 claim_id: "CL-42".into(),
1396 };
1397 let json = serde_json::to_string(&q).unwrap();
1398 let q2: FactcheckStatusQuery = serde_json::from_str(&json).unwrap();
1399 assert_eq!(q.claim_id, q2.claim_id);
1400 }
1401
1402 #[test]
1403 fn factcheck_status_result_serde_roundtrip() {
1404 let r = FactcheckStatusResult {
1405 has_factcheck: true,
1406 verdict: Some("verified".into()),
1407 error: None,
1408 };
1409 let json = serde_json::to_string(&r).unwrap();
1410 let r2: FactcheckStatusResult = serde_json::from_str(&json).unwrap();
1411 assert!(r2.has_factcheck);
1412 assert_eq!(r2.verdict, Some("verified".into()));
1413 }
1414
1415 #[test]
1418 fn audit_trail_query_full_serde() {
1419 let q = AuditTrailQuery {
1420 from_us: 1_700_000_000_000_000,
1421 to_us: 1_700_001_000_000_000,
1422 domain: Some("property".into()),
1423 event_type: Some("ownership_transferred".into()),
1424 };
1425 let json = serde_json::to_string(&q).unwrap();
1426 let q2: AuditTrailQuery = serde_json::from_str(&json).unwrap();
1427 assert_eq!(q2.from_us, 1_700_000_000_000_000);
1428 assert_eq!(q2.domain.as_deref(), Some("property"));
1429 assert_eq!(q2.event_type.as_deref(), Some("ownership_transferred"));
1430 }
1431
1432 #[test]
1433 fn audit_trail_query_no_filters() {
1434 let q = AuditTrailQuery {
1435 from_us: 0,
1436 to_us: i64::MAX,
1437 domain: None,
1438 event_type: None,
1439 };
1440 let json = serde_json::to_string(&q).unwrap();
1441 let q2: AuditTrailQuery = serde_json::from_str(&json).unwrap();
1442 assert!(q2.domain.is_none());
1443 assert!(q2.event_type.is_none());
1444 }
1445
1446 #[test]
1447 fn audit_trail_entry_serde() {
1448 let e = AuditTrailEntry {
1449 domain: "justice".into(),
1450 event_type: "case_filed".into(),
1451 source_agent: "uhCAk_agent1".into(),
1452 payload_preview: "{\"case_id\":\"CASE-1\"}".into(),
1453 created_at_us: 1_700_000_500_000_000,
1454 action_hash: ActionHash::from_raw_36(vec![0u8; 36]),
1455 };
1456 let json = serde_json::to_string(&e).unwrap();
1457 let e2: AuditTrailEntry = serde_json::from_str(&json).unwrap();
1458 assert_eq!(e2.domain, "justice");
1459 assert_eq!(e2.event_type, "case_filed");
1460 }
1461
1462 #[test]
1463 fn audit_trail_result_serde() {
1464 let r = AuditTrailResult {
1465 entries: vec![],
1466 total_matched: 0,
1467 query_from_us: 0,
1468 query_to_us: 1_000_000,
1469 };
1470 let json = serde_json::to_string(&r).unwrap();
1471 let r2: AuditTrailResult = serde_json::from_str(&json).unwrap();
1472 assert!(r2.entries.is_empty());
1473 assert_eq!(r2.total_matched, 0);
1474 }
1475
1476 #[test]
1479 fn food_availability_query_serde_roundtrip() {
1480 let q = FoodAvailabilityQuery {
1481 product_name: Some("tomatoes".into()),
1482 market_type: Some("FarmersMarket".into()),
1483 max_distance_km: Some(15.0),
1484 };
1485 let json = serde_json::to_string(&q).unwrap();
1486 let q2: FoodAvailabilityQuery = serde_json::from_str(&json).unwrap();
1487 assert_eq!(q2.product_name.as_deref(), Some("tomatoes"));
1488 assert_eq!(q2.market_type.as_deref(), Some("FarmersMarket"));
1489 assert_eq!(q2.max_distance_km, Some(15.0));
1490 }
1491
1492 #[test]
1493 fn food_availability_query_no_filters() {
1494 let q = FoodAvailabilityQuery {
1495 product_name: None,
1496 market_type: None,
1497 max_distance_km: None,
1498 };
1499 let json = serde_json::to_string(&q).unwrap();
1500 let q2: FoodAvailabilityQuery = serde_json::from_str(&json).unwrap();
1501 assert!(q2.product_name.is_none());
1502 }
1503
1504 #[test]
1505 fn food_availability_result_serde_roundtrip() {
1506 let r = FoodAvailabilityResult {
1507 available_listings: 12,
1508 nearest_market: Some("Southside Farmers Market".into()),
1509 error: None,
1510 };
1511 let json = serde_json::to_string(&r).unwrap();
1512 let r2: FoodAvailabilityResult = serde_json::from_str(&json).unwrap();
1513 assert_eq!(r2.available_listings, 12);
1514 assert_eq!(
1515 r2.nearest_market.as_deref(),
1516 Some("Southside Farmers Market")
1517 );
1518 assert!(r2.error.is_none());
1519 }
1520
1521 #[test]
1522 fn transport_route_query_serde_roundtrip() {
1523 let q = TransportRouteQuery {
1524 origin_lat: 32.9483,
1525 origin_lon: -96.7299,
1526 destination_lat: 32.7767,
1527 destination_lon: -96.7970,
1528 mode: Some("Cycling".into()),
1529 };
1530 let json = serde_json::to_string(&q).unwrap();
1531 let q2: TransportRouteQuery = serde_json::from_str(&json).unwrap();
1532 assert!((q2.origin_lat - 32.9483).abs() < 1e-4);
1533 assert_eq!(q2.mode.as_deref(), Some("Cycling"));
1534 }
1535
1536 #[test]
1537 fn transport_route_result_serde_roundtrip() {
1538 let r = TransportRouteResult {
1539 route_count: 3,
1540 estimated_minutes: Some(45),
1541 estimated_emissions_kg_co2: Some(0.0),
1542 error: None,
1543 };
1544 let json = serde_json::to_string(&r).unwrap();
1545 let r2: TransportRouteResult = serde_json::from_str(&json).unwrap();
1546 assert_eq!(r2.route_count, 3);
1547 assert_eq!(r2.estimated_minutes, Some(45));
1548 assert_eq!(r2.estimated_emissions_kg_co2, Some(0.0));
1549 }
1550
1551 #[test]
1552 fn carbon_credit_query_serde_roundtrip() {
1553 let q = CarbonCreditQuery {
1554 agent_did: "did:mycelix:agent123".into(),
1555 };
1556 let json = serde_json::to_string(&q).unwrap();
1557 let q2: CarbonCreditQuery = serde_json::from_str(&json).unwrap();
1558 assert_eq!(q2.agent_did, "did:mycelix:agent123");
1559 }
1560
1561 #[test]
1562 fn carbon_credit_result_serde_roundtrip() {
1563 let r = CarbonCreditResult {
1564 total_credits_kg_co2: 127.5,
1565 trips_logged: 34,
1566 error: None,
1567 };
1568 let json = serde_json::to_string(&r).unwrap();
1569 let r2: CarbonCreditResult = serde_json::from_str(&json).unwrap();
1570 assert!((r2.total_credits_kg_co2 - 127.5).abs() < 1e-6);
1571 assert_eq!(r2.trips_logged, 34);
1572 assert!(r2.error.is_none());
1573 }
1574
1575 #[test]
1578 fn water_safety_query_serde_roundtrip() {
1579 let q = WaterSafetyQuery {
1580 area_lat: 32.9483,
1581 area_lon: -96.7299,
1582 radius_km: 10.0,
1583 };
1584 let json = serde_json::to_string(&q).unwrap();
1585 let q2: WaterSafetyQuery = serde_json::from_str(&json).unwrap();
1586 assert!((q2.area_lat - 32.9483).abs() < 1e-4);
1587 assert!((q2.area_lon - (-96.7299)).abs() < 1e-4);
1588 assert!((q2.radius_km - 10.0).abs() < 1e-6);
1589 }
1590
1591 #[test]
1592 fn water_safety_result_serde_roundtrip() {
1593 let r = WaterSafetyResult {
1594 safe_sources: 8,
1595 contaminated_sources: 2,
1596 total_sources: 10,
1597 };
1598 let json = serde_json::to_string(&r).unwrap();
1599 let r2: WaterSafetyResult = serde_json::from_str(&json).unwrap();
1600 assert_eq!(r2.safe_sources, 8);
1601 assert_eq!(r2.contaminated_sources, 2);
1602 assert_eq!(r2.total_sources, 10);
1603 }
1604
1605 #[test]
1606 fn water_safety_result_all_contaminated() {
1607 let r = WaterSafetyResult {
1608 safe_sources: 0,
1609 contaminated_sources: 5,
1610 total_sources: 5,
1611 };
1612 let json = serde_json::to_string(&r).unwrap();
1613 let r2: WaterSafetyResult = serde_json::from_str(&json).unwrap();
1614 assert_eq!(r2.safe_sources, 0);
1615 assert_eq!(r2.contaminated_sources, 5);
1616 }
1617
1618 #[test]
1619 fn emergency_food_query_serde_roundtrip() {
1620 let q = EmergencyFoodQuery {
1621 area_lat: 29.7604,
1622 area_lon: -95.3698,
1623 radius_km: 25.0,
1624 people_count: 500,
1625 };
1626 let json = serde_json::to_string(&q).unwrap();
1627 let q2: EmergencyFoodQuery = serde_json::from_str(&json).unwrap();
1628 assert!((q2.area_lat - 29.7604).abs() < 1e-4);
1629 assert!((q2.area_lon - (-95.3698)).abs() < 1e-4);
1630 assert!((q2.radius_km - 25.0).abs() < 1e-6);
1631 assert_eq!(q2.people_count, 500);
1632 }
1633
1634 #[test]
1635 fn emergency_food_result_serde_roundtrip() {
1636 let r = EmergencyFoodResult {
1637 available_kg: 2500.5,
1638 distribution_points: 4,
1639 estimated_days_supply: 3.5,
1640 };
1641 let json = serde_json::to_string(&r).unwrap();
1642 let r2: EmergencyFoodResult = serde_json::from_str(&json).unwrap();
1643 assert!((r2.available_kg - 2500.5).abs() < 1e-6);
1644 assert_eq!(r2.distribution_points, 4);
1645 assert!((r2.estimated_days_supply - 3.5).abs() < 1e-6);
1646 }
1647
1648 #[test]
1649 fn emergency_food_result_zero_supply() {
1650 let r = EmergencyFoodResult {
1651 available_kg: 0.0,
1652 distribution_points: 0,
1653 estimated_days_supply: 0.0,
1654 };
1655 let json = serde_json::to_string(&r).unwrap();
1656 let r2: EmergencyFoodResult = serde_json::from_str(&json).unwrap();
1657 assert!((r2.available_kg).abs() < 1e-6);
1658 assert_eq!(r2.distribution_points, 0);
1659 assert!((r2.estimated_days_supply).abs() < 1e-6);
1660 }
1661
1662 #[test]
1663 fn shelter_capacity_query_serde_roundtrip() {
1664 let q = ShelterCapacityQuery {
1665 area_lat: 30.2672,
1666 area_lon: -97.7431,
1667 radius_km: 15.0,
1668 beds_needed: 200,
1669 };
1670 let json = serde_json::to_string(&q).unwrap();
1671 let q2: ShelterCapacityQuery = serde_json::from_str(&json).unwrap();
1672 assert!((q2.area_lat - 30.2672).abs() < 1e-4);
1673 assert!((q2.area_lon - (-97.7431)).abs() < 1e-4);
1674 assert!((q2.radius_km - 15.0).abs() < 1e-6);
1675 assert_eq!(q2.beds_needed, 200);
1676 }
1677
1678 #[test]
1679 fn shelter_capacity_result_serde_roundtrip() {
1680 let r = ShelterCapacityResult {
1681 available_beds: 150,
1682 total_shelters: 3,
1683 nearest_shelter_km: 2.4,
1684 };
1685 let json = serde_json::to_string(&r).unwrap();
1686 let r2: ShelterCapacityResult = serde_json::from_str(&json).unwrap();
1687 assert_eq!(r2.available_beds, 150);
1688 assert_eq!(r2.total_shelters, 3);
1689 assert!((r2.nearest_shelter_km - 2.4).abs() < 1e-6);
1690 }
1691
1692 #[test]
1693 fn shelter_capacity_result_no_shelters() {
1694 let r = ShelterCapacityResult {
1695 available_beds: 0,
1696 total_shelters: 0,
1697 nearest_shelter_km: 0.0,
1698 };
1699 let json = serde_json::to_string(&r).unwrap();
1700 let r2: ShelterCapacityResult = serde_json::from_str(&json).unwrap();
1701 assert_eq!(r2.available_beds, 0);
1702 assert_eq!(r2.total_shelters, 0);
1703 }
1704
1705 #[test]
1706 fn emergency_care_query_serde_roundtrip() {
1707 let q = EmergencyCareQuery {
1708 area_lat: 32.7767,
1709 area_lon: -96.7970,
1710 skill_needed: "trauma_surgeon".into(),
1711 urgency_level: 5,
1712 };
1713 let json = serde_json::to_string(&q).unwrap();
1714 let q2: EmergencyCareQuery = serde_json::from_str(&json).unwrap();
1715 assert!((q2.area_lat - 32.7767).abs() < 1e-4);
1716 assert!((q2.area_lon - (-96.7970)).abs() < 1e-4);
1717 assert_eq!(q2.skill_needed, "trauma_surgeon");
1718 assert_eq!(q2.urgency_level, 5);
1719 }
1720
1721 #[test]
1722 fn emergency_care_query_low_urgency() {
1723 let q = EmergencyCareQuery {
1724 area_lat: 0.0,
1725 area_lon: 0.0,
1726 skill_needed: "first_aid".into(),
1727 urgency_level: 1,
1728 };
1729 let json = serde_json::to_string(&q).unwrap();
1730 let q2: EmergencyCareQuery = serde_json::from_str(&json).unwrap();
1731 assert_eq!(q2.urgency_level, 1);
1732 assert_eq!(q2.skill_needed, "first_aid");
1733 }
1734
1735 #[test]
1736 fn emergency_care_result_serde_roundtrip() {
1737 let r = EmergencyCareResult {
1738 available_providers: 7,
1739 nearest_provider_km: 1.2,
1740 };
1741 let json = serde_json::to_string(&r).unwrap();
1742 let r2: EmergencyCareResult = serde_json::from_str(&json).unwrap();
1743 assert_eq!(r2.available_providers, 7);
1744 assert!((r2.nearest_provider_km - 1.2).abs() < 1e-6);
1745 }
1746
1747 #[test]
1748 fn emergency_care_result_no_providers() {
1749 let r = EmergencyCareResult {
1750 available_providers: 0,
1751 nearest_provider_km: 0.0,
1752 };
1753 let json = serde_json::to_string(&r).unwrap();
1754 let r2: EmergencyCareResult = serde_json::from_str(&json).unwrap();
1755 assert_eq!(r2.available_providers, 0);
1756 }
1757
1758 #[test]
1761 fn water_safety_query_extreme_coordinates() {
1762 let q = WaterSafetyQuery {
1763 area_lat: 90.0,
1764 area_lon: 180.0,
1765 radius_km: 0.001,
1766 };
1767 let json = serde_json::to_string(&q).unwrap();
1768 let q2: WaterSafetyQuery = serde_json::from_str(&json).unwrap();
1769 assert!((q2.area_lat - 90.0).abs() < 1e-6);
1770 assert!((q2.area_lon - 180.0).abs() < 1e-6);
1771 }
1772
1773 #[test]
1774 fn water_safety_query_negative_coordinates() {
1775 let q = WaterSafetyQuery {
1776 area_lat: -90.0,
1777 area_lon: -180.0,
1778 radius_km: 100.0,
1779 };
1780 let json = serde_json::to_string(&q).unwrap();
1781 let q2: WaterSafetyQuery = serde_json::from_str(&json).unwrap();
1782 assert!((q2.area_lat - (-90.0)).abs() < 1e-6);
1783 assert!((q2.area_lon - (-180.0)).abs() < 1e-6);
1784 }
1785
1786 #[test]
1787 fn shelter_capacity_query_zero_beds_needed() {
1788 let q = ShelterCapacityQuery {
1789 area_lat: 0.0,
1790 area_lon: 0.0,
1791 radius_km: 1.0,
1792 beds_needed: 0,
1793 };
1794 let json = serde_json::to_string(&q).unwrap();
1795 let q2: ShelterCapacityQuery = serde_json::from_str(&json).unwrap();
1796 assert_eq!(q2.beds_needed, 0);
1797 }
1798
1799 #[test]
1800 fn emergency_food_query_zero_people() {
1801 let q = EmergencyFoodQuery {
1802 area_lat: 0.0,
1803 area_lon: 0.0,
1804 radius_km: 1.0,
1805 people_count: 0,
1806 };
1807 let json = serde_json::to_string(&q).unwrap();
1808 let q2: EmergencyFoodQuery = serde_json::from_str(&json).unwrap();
1809 assert_eq!(q2.people_count, 0);
1810 }
1811
1812 #[test]
1813 fn emergency_care_query_max_urgency_level() {
1814 let q = EmergencyCareQuery {
1815 area_lat: 0.0,
1816 area_lon: 0.0,
1817 skill_needed: "any".into(),
1818 urgency_level: 255,
1819 };
1820 let json = serde_json::to_string(&q).unwrap();
1821 let q2: EmergencyCareQuery = serde_json::from_str(&json).unwrap();
1822 assert_eq!(q2.urgency_level, 255);
1823 }
1824
1825 #[test]
1826 fn emergency_care_query_empty_skill() {
1827 let q = EmergencyCareQuery {
1828 area_lat: 0.0,
1829 area_lon: 0.0,
1830 skill_needed: "".into(),
1831 urgency_level: 3,
1832 };
1833 let json = serde_json::to_string(&q).unwrap();
1834 let q2: EmergencyCareQuery = serde_json::from_str(&json).unwrap();
1835 assert_eq!(q2.skill_needed, "");
1836 }
1837
1838 #[test]
1841 fn hearth_member_query_serde_roundtrip() {
1842 let q = HearthMemberQuery {
1843 hearth_hash: ActionHash::from_raw_36(vec![1u8; 36]),
1844 agent: AgentPubKey::from_raw_36(vec![2u8; 36]),
1845 };
1846 let json = serde_json::to_string(&q).unwrap();
1847 let q2: HearthMemberQuery = serde_json::from_str(&json).unwrap();
1848 assert_eq!(q.hearth_hash, q2.hearth_hash);
1849 assert_eq!(q.agent, q2.agent);
1850 }
1851
1852 #[test]
1853 fn hearth_member_result_found() {
1854 let r = HearthMemberResult {
1855 is_member: true,
1856 role: Some("Adult".into()),
1857 display_name: Some("Alice".into()),
1858 error: None,
1859 };
1860 let json = serde_json::to_string(&r).unwrap();
1861 let r2: HearthMemberResult = serde_json::from_str(&json).unwrap();
1862 assert!(r2.is_member);
1863 assert_eq!(r2.role.as_deref(), Some("Adult"));
1864 assert_eq!(r2.display_name.as_deref(), Some("Alice"));
1865 assert!(r2.error.is_none());
1866 }
1867
1868 #[test]
1869 fn hearth_member_result_not_found() {
1870 let r = HearthMemberResult {
1871 is_member: false,
1872 role: None,
1873 display_name: None,
1874 error: None,
1875 };
1876 let json = serde_json::to_string(&r).unwrap();
1877 let r2: HearthMemberResult = serde_json::from_str(&json).unwrap();
1878 assert!(!r2.is_member);
1879 assert!(r2.role.is_none());
1880 }
1881
1882 #[test]
1883 fn hearth_care_query_serde_roundtrip() {
1884 let q = HearthCareQuery {
1885 hearth_hash: ActionHash::from_raw_36(vec![3u8; 36]),
1886 care_type: Some("Childcare".into()),
1887 };
1888 let json = serde_json::to_string(&q).unwrap();
1889 let q2: HearthCareQuery = serde_json::from_str(&json).unwrap();
1890 assert_eq!(q.hearth_hash, q2.hearth_hash);
1891 assert_eq!(q2.care_type.as_deref(), Some("Childcare"));
1892 }
1893
1894 #[test]
1895 fn hearth_care_query_no_filter() {
1896 let q = HearthCareQuery {
1897 hearth_hash: ActionHash::from_raw_36(vec![4u8; 36]),
1898 care_type: None,
1899 };
1900 let json = serde_json::to_string(&q).unwrap();
1901 let q2: HearthCareQuery = serde_json::from_str(&json).unwrap();
1902 assert!(q2.care_type.is_none());
1903 }
1904
1905 #[test]
1906 fn hearth_care_result_serde_roundtrip() {
1907 let r = HearthCareResult {
1908 available_caregivers: 3,
1909 active_schedules: 7,
1910 error: None,
1911 };
1912 let json = serde_json::to_string(&r).unwrap();
1913 let r2: HearthCareResult = serde_json::from_str(&json).unwrap();
1914 assert_eq!(r2.available_caregivers, 3);
1915 assert_eq!(r2.active_schedules, 7);
1916 assert!(r2.error.is_none());
1917 }
1918
1919 #[test]
1920 fn hearth_emergency_query_serde_roundtrip() {
1921 let q = HearthEmergencyQuery {
1922 hearth_hash: ActionHash::from_raw_36(vec![5u8; 36]),
1923 };
1924 let json = serde_json::to_string(&q).unwrap();
1925 let q2: HearthEmergencyQuery = serde_json::from_str(&json).unwrap();
1926 assert_eq!(q.hearth_hash, q2.hearth_hash);
1927 }
1928
1929 #[test]
1930 fn hearth_emergency_result_active() {
1931 let r = HearthEmergencyResult {
1932 has_active_alerts: true,
1933 active_alert_count: 2,
1934 members_checked_in: 4,
1935 members_missing: 1,
1936 error: None,
1937 };
1938 let json = serde_json::to_string(&r).unwrap();
1939 let r2: HearthEmergencyResult = serde_json::from_str(&json).unwrap();
1940 assert!(r2.has_active_alerts);
1941 assert_eq!(r2.active_alert_count, 2);
1942 assert_eq!(r2.members_checked_in, 4);
1943 assert_eq!(r2.members_missing, 1);
1944 assert!(r2.error.is_none());
1945 }
1946
1947 #[test]
1948 fn hearth_emergency_result_clear() {
1949 let r = HearthEmergencyResult {
1950 has_active_alerts: false,
1951 active_alert_count: 0,
1952 members_checked_in: 5,
1953 members_missing: 0,
1954 error: None,
1955 };
1956 let json = serde_json::to_string(&r).unwrap();
1957 let r2: HearthEmergencyResult = serde_json::from_str(&json).unwrap();
1958 assert!(!r2.has_active_alerts);
1959 assert_eq!(r2.active_alert_count, 0);
1960 assert_eq!(r2.members_missing, 0);
1961 }
1962}