1#[cfg(feature = "query")]
2use crate::agent::clock::{Clock, SystemClock};
3use crate::cursor::Cursor;
4use crate::error::LaserError;
5use crate::laser::Laser;
6use crate::provenance::AgentTopic;
7use crate::types::{AgentId, MintUlid, PrincipalId};
8use laser_wire::agent::{
9 AgentCard, AgentEnvelope, AgentPresence, ConversationId, Health, OPERATION_CARD,
10 OPERATION_QUARANTINE, OPERATION_UNQUARANTINE, RecordId,
11};
12use laser_wire::content::ContentType;
13use laser_wire::framing::decode_named;
14use std::collections::{HashMap, HashSet, VecDeque};
15use std::sync::{Arc, Mutex};
16
17#[cfg(feature = "query")]
22const PRESENCE_TTL_MICROS: u64 = 2_000_000;
23const APPLIED_FACT_CAPACITY: usize = 16_384;
24
25const MAX_REGISTERED_CARDS: usize = 4_096;
29const APPLIED_FACT_TTL_MICROS: u64 = 86_400_000_000;
30
31#[derive(Default)]
36pub(crate) struct RegistryCache {
37 cards: HashMap<AgentId, RegisteredCard>,
38 quarantined: HashSet<AgentId>,
39 offsets: Vec<u64>,
40 presence: HashMap<AgentId, PresenceEntry>,
41 presence_read_at_micros: Option<u64>,
42 applied_facts: HashSet<RecordId>,
46 applied_fact_order: VecDeque<(RecordId, u64)>,
47}
48
49#[derive(Clone, Debug)]
54pub struct PresenceEntry {
55 pub presence: AgentPresence,
56 pub principal: Option<PrincipalId>,
57}
58
59#[cfg(feature = "query")]
62use laser_wire::clients::{ClientMetadata, ClientMetadataList, ClientMetadataQuery};
63#[cfg(feature = "query")]
64use laser_wire::codes::{
65 AGDX_GET_CLIENTS_METADATA_CODE, AGDX_SET_CLIENT_METADATA_CODE, CLIENT_METADATA_OP_VERSION,
66};
67#[cfg(feature = "query")]
68use laser_wire::framing::encode_named;
69
70#[cfg(feature = "query")]
73const MAX_PAGE_SIZE: usize = laser_wire::limits::MAX_PAGE_SIZE;
74
75#[derive(Clone, Debug)]
78pub struct RegisteredCard {
79 pub agent: AgentId,
80 pub card: AgentCard,
81 pub observed_at_micros: u64,
85}
86
87impl RegisteredCard {
88 pub fn is_fresh(&self, now_micros: u64) -> bool {
91 match self.card.ttl_micros {
92 None => true,
93 Some(ttl) => self.observed_at_micros.saturating_add(ttl) >= now_micros,
94 }
95 }
96
97 pub fn serves(&self, skill_id: &str) -> bool {
99 self.card
100 .capabilities
101 .iter()
102 .any(|capability| capability.skill_id == skill_id)
103 }
104
105 pub fn available_for(&self, skill_id: &str) -> bool {
111 self.card
112 .capabilities
113 .iter()
114 .find(|capability| capability.skill_id == skill_id)
115 .is_some_and(|capability| capability.health != Some(Health::Unavailable))
116 }
117}
118
119pub struct AgentRegistry<'a> {
128 #[cfg_attr(not(feature = "query"), allow(dead_code))]
130 laser: &'a Laser,
131 cursor: Cursor,
132 cards: HashMap<AgentId, RegisteredCard>,
133 presence: HashMap<AgentId, PresenceEntry>,
138 quarantined: HashSet<AgentId>,
142 applied_facts: HashSet<RecordId>,
144 applied_fact_order: VecDeque<(RecordId, u64)>,
145 cache: Arc<Mutex<RegistryCache>>,
148 #[cfg_attr(not(feature = "query"), allow(dead_code))]
151 presence_read_at_micros: Option<u64>,
152 #[cfg(feature = "sign")]
157 verifier: Option<Arc<crate::sign::KeyRegistry>>,
158}
159
160impl<'a> AgentRegistry<'a> {
161 pub async fn refresh(&mut self, now_micros: u64) -> Result<usize, LaserError> {
169 let messages = self.cursor.poll_iggy().await?;
170 let mut folded = 0;
171 for (_, message) in &messages {
172 let Ok(decoded) = crate::agent::consumer::decode_agent_record(
173 message,
174 laser_wire::agent::features::NONE,
175 ) else {
176 continue;
177 };
178 let Some(envelope) = decoded.envelope else {
179 continue;
180 };
181 let operation = envelope.operation.as_deref();
182 let applied = if operation == Some(OPERATION_QUARANTINE)
183 || operation == Some(OPERATION_UNQUARANTINE)
184 {
185 if envelope
194 .record
195 .is_some_and(|id| self.applied_facts.contains(&id))
196 || !self.fact_authorized(
197 &envelope,
198 decoded.signature_context.as_ref(),
199 decoded.observed_at_micros,
200 )
201 {
202 false
203 } else {
204 if let Some(id) = envelope.record {
205 self.applied_facts.insert(id);
206 self.applied_fact_order
207 .push_back((id, decoded.observed_at_micros));
208 }
209 if operation == Some(OPERATION_QUARANTINE) {
210 apply_quarantine(&mut self.quarantined, &envelope)
211 } else {
212 lift_quarantine(&mut self.quarantined, &envelope)
213 }
214 }
215 } else {
216 apply_card(&mut self.cards, &envelope, decoded.observed_at_micros)
217 };
218 if applied {
219 folded += 1;
220 }
221 }
222 self.cards.retain(|_, card| card.is_fresh(now_micros));
225 let cutoff = now_micros.saturating_sub(APPLIED_FACT_TTL_MICROS);
226 while self.applied_fact_order.len() > APPLIED_FACT_CAPACITY
227 || self
228 .applied_fact_order
229 .front()
230 .is_some_and(|(_, observed)| *observed < cutoff)
231 {
232 if let Some((id, _)) = self.applied_fact_order.pop_front() {
233 self.applied_facts.remove(&id);
234 }
235 }
236 if let Ok(mut cache) = self.cache.lock() {
237 cache.cards = self.cards.clone();
238 cache.quarantined = self.quarantined.clone();
239 cache.applied_facts = self.applied_facts.clone();
240 cache.applied_fact_order = self.applied_fact_order.clone();
241 cache.offsets = self.cursor.offsets().to_vec();
242 }
243 Ok(folded)
244 }
245
246 #[cfg(feature = "sign")]
252 fn fact_authorized(
253 &self,
254 envelope: &AgentEnvelope,
255 context: Option<&laser_wire::agent::SignatureContext>,
256 observed_at_micros: u64,
257 ) -> bool {
258 match &self.verifier {
259 Some(registry) => context.is_some_and(|context| {
260 registry
261 .verify_observed_at(envelope, context, observed_at_micros)
262 .is_ok_and(|verified| verified.kind == crate::sign::KeyKind::Operator)
263 }),
264 None => true,
265 }
266 }
267
268 #[cfg(not(feature = "sign"))]
269 fn fact_authorized(
270 &self,
271 _envelope: &AgentEnvelope,
272 _context: Option<&laser_wire::agent::SignatureContext>,
273 _observed_at_micros: u64,
274 ) -> bool {
275 true
276 }
277
278 pub fn agents(&self) -> impl Iterator<Item = &RegisteredCard> {
281 by_agent_id(self.cards.values()).into_iter()
282 }
283
284 pub fn lookup(&self, agent: &AgentId) -> Option<&RegisteredCard> {
286 self.cards.get(agent)
287 }
288
289 pub fn resolve(&self, skill_id: &str, now_micros: u64) -> Vec<&RegisteredCard> {
296 self.agents()
297 .filter(|card| {
298 card.available_for(skill_id)
299 && card.is_fresh(now_micros)
300 && !self.quarantined.contains(&card.agent)
301 })
302 .collect()
303 }
304
305 pub fn is_quarantined(&self, agent: &AgentId) -> bool {
307 self.quarantined.contains(agent)
308 }
309
310 pub fn inbox_for(&self, agent: &AgentId) -> Option<&str> {
315 self.presence
316 .get(agent)
317 .and_then(|entry| entry.presence.inbox.as_deref())
318 }
319
320 pub fn inbox_for_principal(&self, agent: &AgentId, principal: PrincipalId) -> Option<&str> {
326 self.presence.get(agent).and_then(|entry| {
327 (entry.principal == Some(principal))
328 .then_some(entry.presence.inbox.as_deref())
329 .flatten()
330 })
331 }
332
333 pub fn principal_for(&self, agent: &AgentId) -> Option<PrincipalId> {
336 self.presence.get(agent).and_then(|entry| entry.principal)
337 }
338
339 #[cfg(feature = "query")]
351 pub async fn refresh_presence(&mut self) -> Result<usize, LaserError> {
352 let now = SystemClock.now_micros();
353 if let Some(read_at) = self.presence_read_at_micros
357 && now.saturating_sub(read_at) < PRESENCE_TTL_MICROS
358 {
359 return Ok(self.presence.len());
360 }
361 let connections = self
362 .laser
363 .client_metadata()
364 .with_metadata_only(true)
365 .all()
366 .await?;
367 let mut fresh = HashMap::new();
368 for connection in connections {
369 if let Some(payload) = connection.metadata.as_deref() {
370 apply_presence(&mut fresh, payload, connection.user_id);
374 }
375 }
376 let folded = fresh.len();
377 self.presence = fresh;
378 self.presence_read_at_micros = Some(now);
379 if let Ok(mut cache) = self.cache.lock() {
380 cache.presence = self.presence.clone();
381 cache.presence_read_at_micros = Some(now);
382 }
383 Ok(folded)
384 }
385}
386
387#[cfg(feature = "query")]
393fn apply_presence(
394 presence: &mut HashMap<AgentId, PresenceEntry>,
395 metadata: &[u8],
396 user_id: Option<u32>,
397) -> bool {
398 let Ok(body) = decode_named::<AgentPresence>(metadata) else {
399 return false;
400 };
401 if body.validate().is_err() {
404 return false;
405 }
406 let Ok(agent) = AgentId::try_from(body.agent.as_str()) else {
407 return false;
408 };
409 presence.insert(
410 agent,
411 PresenceEntry {
412 presence: body,
413 principal: user_id.map(PrincipalId::new),
414 },
415 );
416 true
417}
418
419impl Laser {
420 pub fn agent_registry(&self) -> Result<AgentRegistry<'_>, LaserError> {
425 let cache = self.registry_cache()?;
426 let (
427 cards,
428 quarantined,
429 applied_facts,
430 applied_fact_order,
431 presence,
432 offsets,
433 presence_read_at_micros,
434 ) = {
435 let guard = cache
436 .lock()
437 .unwrap_or_else(|poisoned| poisoned.into_inner());
438 (
439 guard.cards.clone(),
440 guard.quarantined.clone(),
441 guard.applied_facts.clone(),
442 guard.applied_fact_order.clone(),
443 guard.presence.clone(),
444 guard.offsets.clone(),
445 guard.presence_read_at_micros,
446 )
447 };
448 let cursor = self
449 .reader(AgentTopic::Registry.topic_string())?
450 .from_offsets(offsets);
451 Ok(AgentRegistry {
452 laser: self,
453 cursor,
454 cards,
455 presence,
456 quarantined,
457 applied_facts,
458 applied_fact_order,
459 cache,
460 presence_read_at_micros,
461 #[cfg(feature = "sign")]
462 verifier: self.registry_verifier(),
463 })
464 }
465
466 pub async fn publish_card(&self, source: AgentId, card: &AgentCard) -> Result<(), LaserError> {
473 let body = laser_wire::framing::encode_named(card)
474 .map_err(|error| LaserError::Codec(format!("encode card: {error}")))?;
475 self.agdx(
476 AgentTopic::Registry,
477 source.wire_id(),
478 ConversationId::mint(),
479 )
480 .status(OPERATION_CARD)
481 .body(body)
482 .content_type(ContentType::Cbor)
483 .send()
484 .await?;
485 Ok(())
486 }
487
488 pub async fn quarantine(&self, operator: AgentId, agent: &AgentId) -> Result<(), LaserError> {
497 self.publish_registry_fact(OPERATION_QUARANTINE, operator, agent)
498 .await
499 }
500
501 pub async fn unquarantine(&self, operator: AgentId, agent: &AgentId) -> Result<(), LaserError> {
506 self.publish_registry_fact(OPERATION_UNQUARANTINE, operator, agent)
507 .await
508 }
509
510 async fn publish_registry_fact(
511 &self,
512 operation: &'static str,
513 operator: AgentId,
514 agent: &AgentId,
515 ) -> Result<(), LaserError> {
516 self.agdx(
517 AgentTopic::Registry,
518 operator.wire_id(),
519 ConversationId::mint(),
520 )
521 .status(operation)
522 .body(agent.as_str().as_bytes().to_vec())
523 .send()
524 .await?;
525 Ok(())
526 }
527
528 #[cfg(feature = "sign")]
532 pub async fn quarantine_signed(
533 &self,
534 operator: AgentId,
535 agent: &AgentId,
536 key: &crate::sign::SigningKey,
537 ) -> Result<(), LaserError> {
538 self.publish_registry_fact_signed(OPERATION_QUARANTINE, operator, agent, key)
539 .await
540 }
541
542 #[cfg(feature = "sign")]
545 pub async fn unquarantine_signed(
546 &self,
547 operator: AgentId,
548 agent: &AgentId,
549 key: &crate::sign::SigningKey,
550 ) -> Result<(), LaserError> {
551 self.publish_registry_fact_signed(OPERATION_UNQUARANTINE, operator, agent, key)
552 .await
553 }
554
555 #[cfg(feature = "sign")]
556 async fn publish_registry_fact_signed(
557 &self,
558 operation: &'static str,
559 operator: AgentId,
560 agent: &AgentId,
561 key: &crate::sign::SigningKey,
562 ) -> Result<(), LaserError> {
563 self.agdx(
564 AgentTopic::Registry,
565 operator.wire_id(),
566 ConversationId::mint(),
567 )
568 .status(operation)
569 .body(agent.as_str().as_bytes().to_vec())
570 .signed_by(key)
571 .send()
572 .await?;
573 Ok(())
574 }
575
576 #[cfg(feature = "query")]
586 pub async fn advertise_presence(&self, presence: &AgentPresence) -> Result<(), LaserError> {
587 let requested = AgentId::try_from(presence.agent.as_str())?;
588 self.claim_presence(requested)?;
589 let payload = encode_named(presence)
590 .map_err(|error| LaserError::Codec(format!("encode presence: {error}")))?;
591 self.send_raw_with_response(AGDX_SET_CLIENT_METADATA_CODE, payload)
592 .await?;
593 Ok(())
594 }
595
596 #[cfg(feature = "query")]
600 pub async fn clear_presence(&self) -> Result<(), LaserError> {
601 self.send_raw_with_response(AGDX_SET_CLIENT_METADATA_CODE, Vec::new())
602 .await?;
603 self.release_presence();
604 Ok(())
605 }
606
607 #[cfg(feature = "query")]
615 pub fn client_metadata(&self) -> ClientMetadataRequest<'_> {
616 ClientMetadataRequest {
617 laser: self,
618 query: ClientMetadataQuery {
619 v: CLIENT_METADATA_OP_VERSION,
620 with_metadata_only: false,
621 user_id: None,
622 after_client_id: None,
623 limit: MAX_PAGE_SIZE as u32,
624 },
625 }
626 }
627}
628
629#[cfg(feature = "query")]
632pub struct ClientMetadataPage {
633 pub clients: Vec<ClientMetadata>,
634 pub next_cursor: Option<u32>,
636}
637
638#[cfg(feature = "query")]
640pub struct ClientMetadataRequest<'a> {
641 laser: &'a Laser,
642 query: ClientMetadataQuery,
643}
644
645#[cfg(feature = "query")]
646impl ClientMetadataRequest<'_> {
647 pub fn with_metadata_only(mut self, value: bool) -> Self {
649 self.query.with_metadata_only = value;
650 self
651 }
652
653 pub fn principal(mut self, principal: PrincipalId) -> Self {
655 self.query.user_id = Some(principal.get());
656 self
657 }
658
659 pub fn limit(mut self, limit: u32) -> Self {
661 self.query.limit = limit;
662 self
663 }
664
665 pub fn after(mut self, client_id: u32) -> Self {
667 self.query.after_client_id = Some(client_id);
668 self
669 }
670
671 pub async fn page(&self) -> Result<ClientMetadataPage, LaserError> {
673 let payload = encode_named(&self.query)
674 .map_err(|error| LaserError::Codec(format!("encode request: {error}")))?;
675 let payload = self
676 .laser
677 .send_raw_with_response(AGDX_GET_CLIENTS_METADATA_CODE, payload)
678 .await?;
679 let list: ClientMetadataList = decode_named(&payload)?;
680 Ok(ClientMetadataPage {
681 clients: list.clients,
682 next_cursor: list.next_cursor,
683 })
684 }
685
686 pub async fn all(self) -> Result<Vec<ClientMetadata>, LaserError> {
689 let mut all = Vec::new();
690 let mut cursor = self.query.after_client_id;
691 loop {
692 let mut request = ClientMetadataRequest {
693 laser: self.laser,
694 query: self.query.clone(),
695 };
696 request.query.after_client_id = cursor;
697 let page = request.page().await?;
698 all.extend(page.clients);
699 match page.next_cursor {
700 Some(next) => cursor = Some(next),
701 None => break,
702 }
703 }
704 Ok(all)
705 }
706}
707
708fn apply_card(
713 cards: &mut HashMap<AgentId, RegisteredCard>,
714 envelope: &AgentEnvelope,
715 observed_at_micros: u64,
716) -> bool {
717 if envelope.operation.as_deref() != Some(OPERATION_CARD) {
718 return false;
719 }
720 let Ok(agent) = AgentId::try_from(envelope.source.as_str()) else {
721 return false;
722 };
723 let Ok(card) = decode_named::<AgentCard>(&envelope.body) else {
724 return false;
725 };
726 if card.validate().is_err() {
729 return false;
730 }
731 if cards.len() >= MAX_REGISTERED_CARDS && !cards.contains_key(&agent) {
735 let oldest = cards
736 .iter()
737 .min_by_key(|(_, registered)| registered.observed_at_micros)
738 .map(|(id, _)| id.clone());
739 if let Some(oldest) = oldest {
740 cards.remove(&oldest);
741 }
742 }
743 cards.insert(
744 agent.clone(),
745 RegisteredCard {
746 agent,
747 card,
748 observed_at_micros,
749 },
750 );
751 true
752}
753
754fn by_agent_id<'a>(cards: impl Iterator<Item = &'a RegisteredCard>) -> Vec<&'a RegisteredCard> {
760 let mut cards: Vec<&RegisteredCard> = cards.collect();
761 cards.sort_by(|left, right| left.agent.as_str().cmp(right.agent.as_str()));
762 cards
763}
764
765fn apply_quarantine(quarantined: &mut HashSet<AgentId>, envelope: &AgentEnvelope) -> bool {
766 let Ok(body) = std::str::from_utf8(&envelope.body) else {
767 return false;
768 };
769 let Ok(agent) = AgentId::try_from(body) else {
770 return false;
771 };
772 quarantined.insert(agent)
773}
774
775fn lift_quarantine(quarantined: &mut HashSet<AgentId>, envelope: &AgentEnvelope) -> bool {
780 let Ok(body) = std::str::from_utf8(&envelope.body) else {
781 return false;
782 };
783 let Ok(agent) = AgentId::try_from(body) else {
784 return false;
785 };
786 quarantined.remove(&agent)
787}
788
789#[cfg(test)]
790mod tests {
791 use super::*;
792 use laser_wire::agent::{CapabilityDescriptor, ConversationId, Health, RecordId};
793 use laser_wire::framing::encode_named;
794
795 fn descriptor(skill_id: &str) -> CapabilityDescriptor {
796 CapabilityDescriptor {
797 skill_id: skill_id.to_owned(),
798 input: None,
799 output: None,
800 cost_class: None,
801 latency_class: None,
802 max_concurrency: None,
803 health: Some(Health::Healthy),
804 load: None,
805 }
806 }
807
808 fn card_envelope(source: &str, skills: &[&str], ttl_micros: Option<u64>) -> AgentEnvelope {
809 let card = AgentCard {
810 name: None,
811 version: None,
812 capabilities: skills.iter().map(|skill| descriptor(skill)).collect(),
813 ttl_micros,
814 };
815 let mut envelope = AgentEnvelope::status(
816 RecordId::from_u128(1),
817 ConversationId::from_u128(2),
818 source.parse().expect("valid agent id"),
819 OPERATION_CARD,
820 );
821 envelope.body = encode_named(&card).expect("card encodes");
822 envelope
823 }
824
825 #[test]
826 fn given_cards_when_folded_then_should_keep_the_latest_per_agent_and_resolve_by_capability() {
827 let mut cards = HashMap::new();
828 let planner: AgentId = "planner".parse().unwrap();
829
830 assert!(apply_card(
832 &mut cards,
833 &card_envelope("planner", &["plan"], None),
834 100
835 ));
836 assert!(apply_card(
837 &mut cards,
838 &card_envelope("worker", &["diagnose"], Some(50)),
839 100
840 ));
841 assert_eq!(cards.len(), 2);
842
843 assert!(apply_card(
845 &mut cards,
846 &card_envelope("planner", &["plan", "summarize"], None),
847 200
848 ));
849 assert_eq!(cards.len(), 2);
850 assert_eq!(cards[&planner].observed_at_micros, 200);
851 assert!(cards[&planner].serves("summarize"));
852
853 let not_a_card = AgentEnvelope::event(
855 RecordId::from_u128(9),
856 ConversationId::from_u128(9),
857 "x".parse().unwrap(),
858 b"{}".to_vec(),
859 );
860 assert!(!apply_card(&mut cards, ¬_a_card, 300));
861
862 let worker = cards
864 .values()
865 .find(|c| c.agent.as_str() == "worker")
866 .unwrap();
867 assert!(worker.is_fresh(150));
868 assert!(!worker.is_fresh(151));
869 }
870
871 #[test]
872 fn given_cards_in_any_insertion_order_when_listed_then_should_order_by_agent_id() {
873 let mut cards = HashMap::new();
874 for name in ["zeta", "alpha", "mid", "Beta"] {
875 assert!(apply_card(
876 &mut cards,
877 &card_envelope(name, &["diagnose"], None),
878 100
879 ));
880 }
881 let order: Vec<&str> = by_agent_id(cards.values())
882 .into_iter()
883 .map(|card| card.agent.as_str())
884 .collect();
885 assert_eq!(order, ["Beta", "alpha", "mid", "zeta"]);
886 }
887
888 #[test]
889 fn given_a_quarantine_record_when_folded_then_should_mark_the_named_agent() {
890 let mut quarantined = HashSet::new();
891 let worker: AgentId = "worker".parse().unwrap();
892
893 let mut envelope = AgentEnvelope::status(
894 RecordId::from_u128(1),
895 ConversationId::from_u128(2),
896 "operator".parse().expect("valid agent id"),
897 OPERATION_QUARANTINE,
898 );
899 envelope.body = b"worker".to_vec();
900 assert!(apply_quarantine(&mut quarantined, &envelope));
901 assert!(quarantined.contains(&worker));
902
903 assert!(!apply_quarantine(&mut quarantined, &envelope));
905
906 let mut bad = envelope.clone();
908 bad.body = vec![0xff, 0xfe];
909 assert!(!apply_quarantine(&mut quarantined, &bad));
910
911 let mut lift = AgentEnvelope::status(
913 RecordId::from_u128(3),
914 ConversationId::from_u128(4),
915 "operator".parse().expect("valid agent id"),
916 OPERATION_UNQUARANTINE,
917 );
918 lift.body = b"worker".to_vec();
919 assert!(lift_quarantine(&mut quarantined, &lift));
920 assert!(!quarantined.contains(&worker));
921 assert!(!lift_quarantine(&mut quarantined, &lift));
923 }
924
925 #[test]
926 fn given_advertised_health_when_checking_availability_then_should_skip_only_unavailable() {
927 let with_health = |health| RegisteredCard {
928 agent: "a".parse().expect("valid agent id"),
929 card: AgentCard {
930 name: None,
931 version: None,
932 capabilities: vec![CapabilityDescriptor {
933 health,
934 ..descriptor("diagnose")
935 }],
936 ttl_micros: None,
937 },
938 observed_at_micros: 0,
939 };
940 assert!(with_health(Some(Health::Healthy)).available_for("diagnose"));
941 assert!(with_health(Some(Health::Degraded)).available_for("diagnose"));
942 assert!(with_health(None).available_for("diagnose"));
943 assert!(
944 !with_health(Some(Health::Unavailable)).available_for("diagnose"),
945 "an agent declaring itself unavailable must not be routed to",
946 );
947 assert!(!with_health(Some(Health::Healthy)).available_for("other"));
949 }
950
951 #[cfg(feature = "query")]
952 #[test]
953 fn given_connection_metadata_when_folded_as_presence_then_should_key_by_agent_and_skip_non_presence()
954 {
955 use laser_wire::agent::AgentPresence;
956
957 let mut presence = HashMap::new();
958 let worker: AgentId = "worker".parse().unwrap();
959
960 let body =
963 encode_named(&AgentPresence::new("worker".parse().unwrap()).with_inbox("worker.work"))
964 .expect("presence encodes");
965 assert!(apply_presence(&mut presence, &body, Some(7)));
966 assert_eq!(
967 presence[&worker].presence.inbox.as_deref(),
968 Some("worker.work")
969 );
970 assert_eq!(presence[&worker].principal, Some(PrincipalId::new(7)));
971
972 let moved = encode_named(
974 &AgentPresence::new("worker".parse().unwrap()).with_inbox("worker.work.v2"),
975 )
976 .expect("presence encodes");
977 assert!(apply_presence(&mut presence, &moved, Some(7)));
978 assert_eq!(
979 presence[&worker].presence.inbox.as_deref(),
980 Some("worker.work.v2")
981 );
982
983 assert!(!apply_presence(&mut presence, b"not a presence body", None));
986 assert_eq!(presence.len(), 1);
987 }
988}