1use crate::affordance_key::AffordanceKey;
9use crate::arg_id::ArgumentId;
10use crate::error::Error;
11use argumentation_schemes::instance::SchemeInstance;
12use argumentation_schemes::registry::CatalogRegistry;
13use argumentation_values::Audience;
14use argumentation_weighted::types::Budget;
15use argumentation_weighted_bipolar::WeightedBipolarFramework;
16use std::collections::HashMap;
17use std::sync::Mutex;
18
19pub struct EncounterArgumentationState {
23 #[allow(dead_code)]
25 registry: CatalogRegistry,
26 framework: WeightedBipolarFramework<ArgumentId>,
28 actors_by_argument: HashMap<ArgumentId, Vec<String>>,
31 instances_by_argument: HashMap<ArgumentId, Vec<SchemeInstance>>,
33 argument_id_by_affordance: HashMap<AffordanceKey, ArgumentId>,
40 intensity: Mutex<Budget>,
46 audiences: Mutex<HashMap<String, Audience>>,
51 errors: Mutex<Vec<Error>>,
57}
58
59impl EncounterArgumentationState {
60 #[must_use]
69 pub fn new(registry: CatalogRegistry) -> Self {
70 Self {
71 registry,
72 framework: WeightedBipolarFramework::new(),
73 actors_by_argument: HashMap::new(),
74 instances_by_argument: HashMap::new(),
75 argument_id_by_affordance: HashMap::new(),
76 intensity: Mutex::new(Budget::zero()),
77 audiences: Mutex::new(HashMap::new()),
78 errors: Mutex::new(Vec::new()),
79 }
80 }
81
82 #[must_use]
84 pub fn intensity(&self) -> Budget {
85 *self.intensity_guard()
86 }
87
88 fn intensity_guard(&self) -> std::sync::MutexGuard<'_, Budget> {
96 self.intensity.lock().unwrap_or_else(|e| e.into_inner())
97 }
98
99 fn errors_guard(&self) -> std::sync::MutexGuard<'_, Vec<Error>> {
106 self.errors.lock().unwrap_or_else(|e| e.into_inner())
107 }
108
109 fn audiences_guard(&self) -> std::sync::MutexGuard<'_, HashMap<String, Audience>> {
117 self.audiences.lock().unwrap_or_else(|e| e.into_inner())
118 }
119
120 #[must_use]
122 pub fn argument_count(&self) -> usize {
123 self.framework.argument_count()
124 }
125
126 #[must_use]
128 pub fn edge_count(&self) -> usize {
129 self.framework.edge_count()
130 }
131
132 pub fn add_scheme_instance(
138 &mut self,
139 actor: &str,
140 instance: SchemeInstance,
141 ) -> ArgumentId {
142 let id: ArgumentId = (&instance.conclusion).into();
143 self.framework.add_argument(id.clone());
144 self.actors_by_argument
145 .entry(id.clone())
146 .or_default()
147 .push(actor.to_string());
148 self.instances_by_argument
149 .entry(id.clone())
150 .or_default()
151 .push(instance);
152 id
153 }
154
155 pub fn add_scheme_instance_for_affordance(
176 &mut self,
177 actor: &str,
178 affordance_name: &str,
179 bindings: &std::collections::HashMap<String, String>,
180 instance: SchemeInstance,
181 ) -> ArgumentId {
182 let id = self.add_scheme_instance(actor, instance);
183 let key = AffordanceKey::new(actor, affordance_name, bindings);
184 self.argument_id_by_affordance.insert(key, id.clone());
185 id
186 }
187
188 #[must_use]
191 pub fn argument_id_for(
192 &self,
193 key: &AffordanceKey,
194 ) -> Option<ArgumentId> {
195 self.argument_id_by_affordance.get(key).cloned()
196 }
197
198 #[must_use]
201 pub fn actors_for(&self, id: &ArgumentId) -> &[String] {
202 self.actors_by_argument
203 .get(id)
204 .map(Vec::as_slice)
205 .unwrap_or(&[])
206 }
207
208 #[must_use]
214 pub fn actors_by_argument(&self) -> &HashMap<ArgumentId, Vec<String>> {
215 &self.actors_by_argument
216 }
217
218 #[must_use]
221 pub fn instances_for(&self, id: &ArgumentId) -> &[SchemeInstance] {
222 self.instances_by_argument
223 .get(id)
224 .map(Vec::as_slice)
225 .unwrap_or(&[])
226 }
227
228 #[must_use]
242 pub fn attackers_of(&self, target: &ArgumentId) -> Vec<ArgumentId> {
243 self.framework
244 .attacks()
245 .filter(|atk| &atk.target == target)
246 .map(|atk| atk.attacker.clone())
247 .collect()
248 }
249
250 pub fn has_accepted_counter_by(
278 &self,
279 responder: &str,
280 target: &ArgumentId,
281 ) -> Result<bool, Error> {
282 for attacker in self.attackers_of(target) {
283 let asserted_by_responder = self
284 .actors_for(&attacker)
285 .iter()
286 .any(|a| a == responder);
287 if !asserted_by_responder {
288 continue;
289 }
290 if self.is_credulously_accepted(&attacker)? {
291 return Ok(true);
292 }
293 }
294 Ok(false)
295 }
296
297 pub fn add_weighted_attack(
301 &mut self,
302 attacker: &ArgumentId,
303 target: &ArgumentId,
304 weight: f64,
305 ) -> Result<(), Error> {
306 self.framework
307 .add_weighted_attack(attacker.clone(), target.clone(), weight)?;
308 Ok(())
309 }
310
311 pub fn add_weighted_support(
315 &mut self,
316 supporter: &ArgumentId,
317 supported: &ArgumentId,
318 weight: f64,
319 ) -> Result<(), Error> {
320 self.framework
321 .add_weighted_support(supporter.clone(), supported.clone(), weight)?;
322 Ok(())
323 }
324
325 #[must_use]
328 pub fn at_intensity(self, intensity: Budget) -> Self {
329 *self.intensity_guard() = intensity;
330 self
331 }
332
333 pub fn set_intensity(&self, intensity: Budget) {
341 *self.intensity_guard() = intensity;
342 }
343
344 pub fn set_audience(&self, actor: &str, audience: Audience) {
347 let mut map = self.audiences_guard();
348 map.insert(actor.to_string(), audience);
349 }
350
351 #[must_use]
354 pub fn audience_for(&self, actor: &str) -> Option<Audience> {
355 let map = self.audiences_guard();
356 map.get(actor).cloned()
357 }
358
359 #[must_use]
362 pub fn audiences(&self) -> Vec<(String, Audience)> {
363 let map = self.audiences_guard();
364 map.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
365 }
366
367 pub fn is_credulously_accepted(&self, arg: &ArgumentId) -> Result<bool, Error> {
371 Ok(argumentation_weighted_bipolar::is_credulously_accepted_at(
372 &self.framework,
373 arg,
374 self.intensity(),
375 )?)
376 }
377
378 pub fn is_skeptically_accepted(&self, arg: &ArgumentId) -> Result<bool, Error> {
382 Ok(argumentation_weighted_bipolar::is_skeptically_accepted_at(
383 &self.framework,
384 arg,
385 self.intensity(),
386 )?)
387 }
388
389 pub fn coalitions(&self) -> Result<Vec<argumentation_bipolar::Coalition<ArgumentId>>, Error> {
398 let residuals = argumentation_weighted_bipolar::wbipolar_residuals(
401 &self.framework,
402 Budget::zero(),
403 )?;
404 let bipolar = residuals
405 .into_iter()
406 .next()
407 .expect("zero-budget residual always includes the empty subset");
408 Ok(argumentation_bipolar::detect_coalitions(&bipolar))
409 }
410
411 #[must_use]
420 pub fn drain_errors(&self) -> Vec<Error> {
421 std::mem::take(&mut *self.errors_guard())
422 }
423
424 pub(crate) fn record_error(&self, err: Error) {
431 self.errors_guard().push(err);
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438 use argumentation_schemes::catalog::default_catalog;
439
440 #[test]
441 fn new_state_is_empty() {
442 let state = EncounterArgumentationState::new(default_catalog());
443 assert_eq!(state.argument_count(), 0);
444 assert_eq!(state.edge_count(), 0);
445 }
446
447 #[test]
448 fn new_state_has_zero_intensity() {
449 let state = EncounterArgumentationState::new(default_catalog());
450 assert_eq!(state.intensity().value(), 0.0);
451 }
452
453 #[test]
454 fn add_scheme_instance_creates_argument_node() {
455 let registry = default_catalog();
456 let scheme = registry.by_key("argument_from_expert_opinion").unwrap();
457 let instance = scheme
458 .instantiate(
459 &[
460 ("expert".to_string(), "alice".to_string()),
461 ("domain".to_string(), "military".to_string()),
462 ("claim".to_string(), "fortify_east".to_string()),
463 ]
464 .into_iter()
465 .collect(),
466 )
467 .unwrap();
468
469 let mut state = EncounterArgumentationState::new(registry);
470 let id = state.add_scheme_instance("alice", instance);
471
472 assert_eq!(id.as_str(), "fortify_east");
473 assert_eq!(state.argument_count(), 1);
474 }
475
476 #[test]
477 fn add_scheme_instance_associates_actor_and_instance() {
478 let registry = default_catalog();
479 let scheme = registry.by_key("argument_from_expert_opinion").unwrap();
480 let instance = scheme
481 .instantiate(
482 &[
483 ("expert".to_string(), "alice".to_string()),
484 ("domain".to_string(), "military".to_string()),
485 ("claim".to_string(), "fortify_east".to_string()),
486 ]
487 .into_iter()
488 .collect(),
489 )
490 .unwrap();
491 let mut state = EncounterArgumentationState::new(registry);
492 let id = state.add_scheme_instance("alice", instance);
493 assert_eq!(state.actors_for(&id), &["alice".to_string()]);
494 assert_eq!(state.instances_for(&id).len(), 1);
495 }
496
497 #[test]
498 fn add_two_instances_with_same_conclusion_share_node() {
499 let registry = default_catalog();
500 let scheme = registry.by_key("argument_from_expert_opinion").unwrap();
501
502 let inst1 = scheme
503 .instantiate(
504 &[
505 ("expert".to_string(), "alice".to_string()),
506 ("domain".to_string(), "military".to_string()),
507 ("claim".to_string(), "fortify_east".to_string()),
508 ]
509 .into_iter()
510 .collect(),
511 )
512 .unwrap();
513 let inst2 = scheme
514 .instantiate(
515 &[
516 ("expert".to_string(), "bob".to_string()),
517 ("domain".to_string(), "logistics".to_string()),
518 ("claim".to_string(), "fortify_east".to_string()),
519 ]
520 .into_iter()
521 .collect(),
522 )
523 .unwrap();
524
525 let mut state = EncounterArgumentationState::new(registry);
526 let id1 = state.add_scheme_instance("alice", inst1);
527 let id2 = state.add_scheme_instance("bob", inst2);
528 assert_eq!(id1, id2);
529 assert_eq!(state.argument_count(), 1);
530 assert_eq!(
531 state.actors_for(&id1),
532 &["alice".to_string(), "bob".to_string()]
533 );
534 assert_eq!(state.instances_for(&id1).len(), 2);
535 }
536
537 #[test]
538 fn add_weighted_attack_propagates_to_framework() {
539 let mut state = EncounterArgumentationState::new(default_catalog());
540 let a = ArgumentId::new("a");
541 let b = ArgumentId::new("b");
542 state.add_weighted_attack(&a, &b, 0.5).unwrap();
543 assert_eq!(state.edge_count(), 1);
544 }
545
546 #[test]
547 fn add_weighted_support_propagates_to_framework() {
548 let mut state = EncounterArgumentationState::new(default_catalog());
549 let a = ArgumentId::new("a");
550 let b = ArgumentId::new("b");
551 state.add_weighted_support(&a, &b, 0.5).unwrap();
552 assert_eq!(state.edge_count(), 1);
553 }
554
555 #[test]
556 fn add_weighted_support_rejects_self_support() {
557 let mut state = EncounterArgumentationState::new(default_catalog());
558 let a = ArgumentId::new("a");
559 let err = state.add_weighted_support(&a, &a, 0.5).unwrap_err();
560 assert!(matches!(err, Error::WeightedBipolar(_)));
561 }
562
563 #[test]
564 fn add_weighted_attack_rejects_invalid_weight() {
565 let mut state = EncounterArgumentationState::new(default_catalog());
566 let a = ArgumentId::new("a");
567 let b = ArgumentId::new("b");
568 let err = state.add_weighted_attack(&a, &b, -0.1).unwrap_err();
569 assert!(matches!(err, Error::WeightedBipolar(_)));
570 }
571
572 #[test]
573 fn at_intensity_sets_budget() {
574 let state = EncounterArgumentationState::new(default_catalog())
575 .at_intensity(Budget::new(0.5).unwrap());
576 assert_eq!(state.intensity().value(), 0.5);
577 }
578
579 #[test]
580 fn at_intensity_is_chainable_with_add() {
581 let mut state = EncounterArgumentationState::new(default_catalog())
582 .at_intensity(Budget::new(0.25).unwrap());
583 state
584 .add_weighted_attack(&ArgumentId::new("a"), &ArgumentId::new("b"), 0.3)
585 .unwrap();
586 assert_eq!(state.intensity().value(), 0.25);
587 assert_eq!(state.edge_count(), 1);
588 }
589
590 #[test]
591 fn unattacked_argument_is_credulously_accepted() {
592 let mut state = EncounterArgumentationState::new(default_catalog());
593 let a = ArgumentId::new("a");
594 state.add_weighted_attack(&a, &ArgumentId::new("unused"), 0.0).unwrap();
595 assert!(state.is_credulously_accepted(&a).unwrap());
597 }
598
599 #[test]
600 fn attacked_argument_is_not_credulously_accepted_at_zero_intensity() {
601 let mut state = EncounterArgumentationState::new(default_catalog());
602 let a = ArgumentId::new("a");
603 let b = ArgumentId::new("b");
604 state.add_weighted_attack(&a, &b, 0.5).unwrap();
605 assert!(!state.is_credulously_accepted(&b).unwrap());
607 }
608
609 #[test]
610 fn raising_intensity_flips_acceptance_when_budget_covers_attack() {
611 let mut state = EncounterArgumentationState::new(default_catalog())
612 .at_intensity(Budget::new(0.5).unwrap());
613 let a = ArgumentId::new("a");
614 let b = ArgumentId::new("b");
615 state.add_weighted_attack(&a, &b, 0.4).unwrap();
616 assert!(state.is_credulously_accepted(&b).unwrap());
619 }
620
621 #[test]
622 fn skeptical_is_stricter_than_credulous() {
623 let mut state = EncounterArgumentationState::new(default_catalog())
624 .at_intensity(Budget::new(0.5).unwrap());
625 let a = ArgumentId::new("a");
626 let b = ArgumentId::new("b");
627 state.add_weighted_attack(&a, &b, 0.4).unwrap();
628 assert!(state.is_credulously_accepted(&b).unwrap());
631 assert!(!state.is_skeptically_accepted(&b).unwrap());
632 }
633
634 #[test]
635 fn no_supports_means_all_coalitions_are_singletons() {
636 let mut state = EncounterArgumentationState::new(default_catalog());
637 state.add_weighted_attack(&ArgumentId::new("a"), &ArgumentId::new("b"), 0.5).unwrap();
638 let coalitions = state.coalitions().unwrap();
639 assert!(coalitions.iter().all(|c| c.members.len() == 1));
643 }
644
645 #[test]
646 fn mutual_support_forms_coalition() {
647 let mut state = EncounterArgumentationState::new(default_catalog());
648 let a = ArgumentId::new("a");
649 let b = ArgumentId::new("b");
650 state.add_weighted_support(&a, &b, 0.5).unwrap();
651 state.add_weighted_support(&b, &a, 0.5).unwrap();
652 let coalitions = state.coalitions().unwrap();
653 assert!(coalitions.iter().any(|c| c.members.len() == 2
655 && c.members.contains(&a)
656 && c.members.contains(&b)));
657 }
658
659 #[test]
660 fn set_intensity_mutates_through_shared_ref() {
661 let state = EncounterArgumentationState::new(default_catalog())
662 .at_intensity(Budget::new(0.2).unwrap());
663 assert_eq!(state.intensity().value(), 0.2);
664 state.set_intensity(Budget::new(0.6).unwrap());
667 assert_eq!(state.intensity().value(), 0.6);
668 }
669
670 #[test]
671 fn intensity_is_mutable_from_two_shared_refs_in_sequence() {
672 let state = EncounterArgumentationState::new(default_catalog());
673 fn bump(s: &EncounterArgumentationState, b: f64) {
674 s.set_intensity(Budget::new(b).unwrap());
675 }
676 bump(&state, 0.3);
677 bump(&state, 0.5);
678 assert_eq!(state.intensity().value(), 0.5);
679 }
680
681 #[test]
682 fn add_scheme_instance_for_affordance_indexes_by_key() {
683 let registry = default_catalog();
684 let scheme = registry.by_key("argument_from_expert_opinion").unwrap();
685 let mut bindings = std::collections::HashMap::new();
686 bindings.insert("expert".to_string(), "alice".to_string());
687 bindings.insert("domain".to_string(), "military".to_string());
688 bindings.insert("claim".to_string(), "fortify_east".to_string());
689 let instance = scheme.instantiate(&bindings).unwrap();
690
691 let mut state = EncounterArgumentationState::new(registry);
692 let id = state.add_scheme_instance_for_affordance(
693 "alice",
694 "argue_fortify_east",
695 &bindings,
696 instance,
697 );
698
699 let key = AffordanceKey::new("alice", "argue_fortify_east", &bindings);
700 let looked_up = state.argument_id_for(&key);
701 assert_eq!(looked_up, Some(id));
702 }
703
704 #[test]
705 fn argument_id_for_returns_none_for_unseeded_key() {
706 let bindings = std::collections::HashMap::new();
707 let state = EncounterArgumentationState::new(default_catalog());
708 let key = AffordanceKey::new("nobody", "nothing", &bindings);
709 assert_eq!(state.argument_id_for(&key), None);
710 }
711
712 #[test]
713 fn add_scheme_instance_for_affordance_is_consistent_with_add_scheme_instance() {
714 let registry = default_catalog();
715 let scheme = registry.by_key("argument_from_expert_opinion").unwrap();
716 let mut bindings = std::collections::HashMap::new();
717 bindings.insert("expert".to_string(), "alice".to_string());
718 bindings.insert("domain".to_string(), "military".to_string());
719 bindings.insert("claim".to_string(), "fortify_east".to_string());
720 let instance = scheme.instantiate(&bindings).unwrap();
721 let mut state = EncounterArgumentationState::new(registry);
722 let id = state.add_scheme_instance_for_affordance(
723 "alice",
724 "argue_fortify_east",
725 &bindings,
726 instance,
727 );
728 assert_eq!(state.actors_for(&id), &["alice".to_string()]);
729 assert_eq!(state.instances_for(&id).len(), 1);
730 }
731
732 #[test]
733 fn two_distinct_affordance_keys_with_same_conclusion_point_at_shared_id() {
734 let registry = default_catalog();
735 let scheme = registry.by_key("argument_from_expert_opinion").unwrap();
736
737 let mut alice_bindings = std::collections::HashMap::new();
738 alice_bindings.insert("expert".to_string(), "alice".to_string());
739 alice_bindings.insert("domain".to_string(), "military".to_string());
740 alice_bindings.insert("claim".to_string(), "fortify_east".to_string());
741 let alice_instance = scheme.instantiate(&alice_bindings).unwrap();
742
743 let mut bob_bindings = std::collections::HashMap::new();
744 bob_bindings.insert("expert".to_string(), "bob".to_string());
745 bob_bindings.insert("domain".to_string(), "logistics".to_string());
746 bob_bindings.insert("claim".to_string(), "fortify_east".to_string());
747 let bob_instance = scheme.instantiate(&bob_bindings).unwrap();
748
749 let mut state = EncounterArgumentationState::new(registry);
750 let alice_id = state.add_scheme_instance_for_affordance(
751 "alice",
752 "argue_fortify_east",
753 &alice_bindings,
754 alice_instance,
755 );
756 let bob_id = state.add_scheme_instance_for_affordance(
757 "bob",
758 "second_expert_opinion",
759 &bob_bindings,
760 bob_instance,
761 );
762
763 assert_eq!(alice_id, bob_id, "same conclusion literal → shared ArgumentId");
764 assert_eq!(state.argument_count(), 1);
765
766 let alice_key = AffordanceKey::new("alice", "argue_fortify_east", &alice_bindings);
767 let bob_key = AffordanceKey::new("bob", "second_expert_opinion", &bob_bindings);
768 assert_eq!(state.argument_id_for(&alice_key), Some(alice_id.clone()));
769 assert_eq!(state.argument_id_for(&bob_key), Some(alice_id.clone()));
770
771 assert_eq!(
772 state.actors_for(&alice_id),
773 &["alice".to_string(), "bob".to_string()]
774 );
775 }
776
777 #[test]
778 fn attackers_of_returns_all_direct_attackers() {
779 let mut state = EncounterArgumentationState::new(default_catalog());
780 let target = ArgumentId::new("target");
781 let a1 = ArgumentId::new("a1");
782 let a2 = ArgumentId::new("a2");
783 let unrelated = ArgumentId::new("unrelated");
784 state.add_weighted_attack(&a1, &target, 0.5).unwrap();
785 state.add_weighted_attack(&a2, &target, 0.3).unwrap();
786 state.add_weighted_attack(&unrelated, &ArgumentId::new("x"), 0.5).unwrap();
787 let attackers: std::collections::HashSet<_> =
788 state.attackers_of(&target).into_iter().collect();
789 assert_eq!(attackers.len(), 2);
790 assert!(attackers.contains(&a1));
791 assert!(attackers.contains(&a2));
792 }
793
794 #[test]
795 fn attackers_of_returns_empty_for_unattacked() {
796 let state = EncounterArgumentationState::new(default_catalog());
797 let lonely = ArgumentId::new("lonely");
798 assert!(state.attackers_of(&lonely).is_empty());
799 }
800
801 #[test]
802 fn attackers_of_preserves_duplicate_edges() {
803 let mut state = EncounterArgumentationState::new(default_catalog());
804 let target = ArgumentId::new("target");
805 let a1 = ArgumentId::new("a1");
806 state.add_weighted_attack(&a1, &target, 0.5).unwrap();
807 state.add_weighted_attack(&a1, &target, 0.7).unwrap();
808 assert_eq!(state.attackers_of(&target).len(), 2);
809 }
810
811 #[test]
812 fn has_accepted_counter_by_detects_responder_attacker_at_beta() {
813 let registry = default_catalog();
814 let scheme = registry.by_key("argument_from_expert_opinion").unwrap();
815 let mut target_bindings = std::collections::HashMap::new();
816 target_bindings.insert("expert".to_string(), "alice".to_string());
817 target_bindings.insert("domain".to_string(), "military".to_string());
818 target_bindings.insert("claim".to_string(), "fortify_east".to_string());
819 let target_instance = scheme.instantiate(&target_bindings).unwrap();
820 let mut counter_bindings = std::collections::HashMap::new();
821 counter_bindings.insert("expert".to_string(), "bob".to_string());
822 counter_bindings.insert("domain".to_string(), "logistics".to_string());
823 counter_bindings.insert("claim".to_string(), "abandon_east".to_string());
824 let counter_instance = scheme.instantiate(&counter_bindings).unwrap();
825
826 let mut state = EncounterArgumentationState::new(registry);
827 let target_id = state.add_scheme_instance("alice", target_instance);
828 let counter_id = state.add_scheme_instance("bob", counter_instance);
829 state.add_weighted_attack(&counter_id, &target_id, 0.5).unwrap();
830
831 assert!(state.has_accepted_counter_by("bob", &target_id).unwrap());
835 assert!(!state.has_accepted_counter_by("alice", &target_id).unwrap());
836 }
837
838 #[test]
839 fn drain_errors_round_trips_stashed_errors() {
840 let state = EncounterArgumentationState::new(default_catalog());
841 assert!(state.drain_errors().is_empty());
842 state.record_error(Error::SchemeNotFound("x".into()));
843 state.record_error(Error::SchemeNotFound("y".into()));
844 let errs = state.drain_errors();
845 assert_eq!(errs.len(), 2);
846 assert!(matches!(&errs[0], Error::SchemeNotFound(s) if s == "x"));
847 assert!(matches!(&errs[1], Error::SchemeNotFound(s) if s == "y"));
848 assert!(state.drain_errors().is_empty());
850 }
851
852 #[test]
853 fn actors_by_argument_exposes_actor_map() {
854 let registry = default_catalog();
855 let scheme = registry.by_key("argument_from_expert_opinion").unwrap();
856 let instance = scheme
857 .instantiate(
858 &[
859 ("expert".to_string(), "alice".to_string()),
860 ("domain".to_string(), "military".to_string()),
861 ("claim".to_string(), "fortify_east".to_string()),
862 ]
863 .into_iter()
864 .collect(),
865 )
866 .unwrap();
867 let mut state = EncounterArgumentationState::new(registry);
868 let id = state.add_scheme_instance("alice", instance);
869 let map = state.actors_by_argument();
870 assert_eq!(map.len(), 1);
871 assert_eq!(map.get(&id), Some(&vec!["alice".to_string()]));
872 }
873
874 #[test]
875 fn actors_by_argument_is_empty_on_new_state() {
876 let state = EncounterArgumentationState::new(default_catalog());
877 assert!(state.actors_by_argument().is_empty());
878 }
879
880 #[test]
881 fn audiences_round_trip() {
882 use argumentation_values::{Audience, Value};
883 let registry = argumentation_schemes::catalog::default_catalog();
884 let state = EncounterArgumentationState::new(registry);
885 let audience = Audience::total([Value::new("life"), Value::new("property")]);
886 state.set_audience("alice", audience.clone());
887 let read = state.audience_for("alice").unwrap();
888 assert_eq!(read.value_count(), 2);
889 assert!(state.audience_for("bob").is_none());
890 }
891
892 #[test]
893 fn state_is_send_and_sync() {
894 fn assert_send_sync<T: Send + Sync>() {}
901 assert_send_sync::<EncounterArgumentationState>();
902 }
903}