1use serde::{Deserialize, Serialize};
10
11use crate::fact::{ContextFact, ProposedFact};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
15#[cfg_attr(feature = "strum", derive(strum::EnumIter))]
16pub enum ContextKey {
17 Seeds,
19 Hypotheses,
21 Strategies,
23 Constraints,
25 Signals,
27 Competitors,
29 Evaluations,
31 Proposals,
33 Diagnostic,
35 Votes,
37 Disagreements,
40 ConsensusOutcomes,
43}
44
45pub trait Context: Send + Sync {
51 fn has(&self, key: ContextKey) -> bool;
53
54 fn get(&self, key: ContextKey) -> &[ContextFact];
56
57 fn get_proposals(&self, key: ContextKey) -> &[ProposedFact] {
59 let _ = key;
60 &[]
61 }
62
63 fn count(&self, key: ContextKey) -> usize {
65 self.get(key).len()
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 struct MockContext {
74 facts: std::collections::HashMap<ContextKey, Vec<ContextFact>>,
75 }
76
77 impl MockContext {
78 fn empty() -> Self {
79 Self {
80 facts: std::collections::HashMap::new(),
81 }
82 }
83 }
84
85 impl Context for MockContext {
86 fn has(&self, key: ContextKey) -> bool {
87 self.facts.get(&key).is_some_and(|v| !v.is_empty())
88 }
89
90 fn get(&self, key: ContextKey) -> &[ContextFact] {
91 self.facts.get(&key).map_or(&[], Vec::as_slice)
92 }
93 }
94
95 #[test]
96 fn get_proposals_default_returns_empty() {
97 let ctx = MockContext::empty();
98 assert!(ctx.get_proposals(ContextKey::Seeds).is_empty());
99 assert!(ctx.get_proposals(ContextKey::Hypotheses).is_empty());
100 }
101
102 #[test]
103 fn count_default_delegates_to_get() {
104 let ctx = MockContext::empty();
105 assert_eq!(ctx.count(ContextKey::Seeds), 0);
106 }
107
108 #[test]
109 fn has_returns_false_for_empty() {
110 let ctx = MockContext::empty();
111 assert!(!ctx.has(ContextKey::Seeds));
112 }
113
114 #[test]
115 fn count_reflects_facts() {
116 use crate::fact::{
117 FactActor, FactActorKind, FactLocalTrace, FactPromotionRecord, FactTraceLink,
118 FactValidationSummary,
119 };
120 use crate::types::{ContentHash, Timestamp};
121
122 let mut ctx = MockContext::empty();
123 let record = FactPromotionRecord::new_projection(
124 "projection-test",
125 ContentHash::zero(),
126 FactActor::new_projection("test", FactActorKind::System),
127 FactValidationSummary::default(),
128 Vec::new(),
129 FactTraceLink::Local(FactLocalTrace::new_projection("trace", "span", None, true)),
130 Timestamp::epoch(),
131 );
132 ctx.facts.insert(
133 ContextKey::Seeds,
134 vec![ContextFact::new_projection(
135 ContextKey::Seeds,
136 "f1",
137 "a",
138 record,
139 Timestamp::epoch(),
140 )],
141 );
142 assert_eq!(ctx.count(ContextKey::Seeds), 1);
143 assert!(ctx.has(ContextKey::Seeds));
144 assert!(!ctx.has(ContextKey::Hypotheses));
145 }
146}