1use serde::{Deserialize, Serialize};
10
11use crate::fact::{ContextFact, ProposedFact};
12use crate::formation::FormationKind;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
16#[cfg_attr(feature = "strum", derive(strum::EnumIter))]
17pub enum ContextKey {
18 Seeds,
20 Hypotheses,
22 Strategies,
24 Constraints,
26 Signals,
28 Competitors,
30 Evaluations,
32 Proposals,
34 Diagnostic,
36 Votes,
38 Disagreements,
41 ConsensusOutcomes,
44}
45
46pub trait Context: Send + Sync {
52 fn has(&self, key: ContextKey) -> bool;
54
55 fn get(&self, key: ContextKey) -> &[ContextFact];
57
58 fn get_proposals(&self, key: ContextKey) -> &[ProposedFact] {
60 let _ = key;
61 &[]
62 }
63
64 fn count(&self, key: ContextKey) -> usize {
66 self.get(key).len()
67 }
68
69 fn formation_kind(&self) -> Option<FormationKind> {
78 None
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 struct MockContext {
87 facts: std::collections::HashMap<ContextKey, Vec<ContextFact>>,
88 }
89
90 impl MockContext {
91 fn empty() -> Self {
92 Self {
93 facts: std::collections::HashMap::new(),
94 }
95 }
96 }
97
98 impl Context for MockContext {
99 fn has(&self, key: ContextKey) -> bool {
100 self.facts.get(&key).is_some_and(|v| !v.is_empty())
101 }
102
103 fn get(&self, key: ContextKey) -> &[ContextFact] {
104 self.facts.get(&key).map_or(&[], Vec::as_slice)
105 }
106 }
107
108 #[test]
109 fn get_proposals_default_returns_empty() {
110 let ctx = MockContext::empty();
111 assert!(ctx.get_proposals(ContextKey::Seeds).is_empty());
112 assert!(ctx.get_proposals(ContextKey::Hypotheses).is_empty());
113 }
114
115 #[test]
116 fn count_default_delegates_to_get() {
117 let ctx = MockContext::empty();
118 assert_eq!(ctx.count(ContextKey::Seeds), 0);
119 }
120
121 #[test]
122 fn has_returns_false_for_empty() {
123 let ctx = MockContext::empty();
124 assert!(!ctx.has(ContextKey::Seeds));
125 }
126
127 #[test]
128 fn count_reflects_facts() {
129 use crate::fact::{
130 FactActor, FactActorKind, FactLocalTrace, FactPromotionRecord, FactTraceLink,
131 FactValidationSummary, TextPayload,
132 };
133 use crate::types::{ContentHash, Timestamp};
134
135 let mut ctx = MockContext::empty();
136 let record = FactPromotionRecord::new_projection(
137 "projection-test",
138 ContentHash::zero(),
139 FactActor::new_projection("test", FactActorKind::System),
140 FactValidationSummary::default(),
141 Vec::new(),
142 FactTraceLink::Local(FactLocalTrace::new_projection("trace", "span", None, true)),
143 Timestamp::epoch(),
144 );
145 ctx.facts.insert(
146 ContextKey::Seeds,
147 vec![ContextFact::new_projection(
148 ContextKey::Seeds,
149 "f1",
150 TextPayload::new("a"),
151 record,
152 Timestamp::epoch(),
153 )],
154 );
155 assert_eq!(ctx.count(ContextKey::Seeds), 1);
156 assert!(ctx.has(ContextKey::Seeds));
157 assert!(!ctx.has(ContextKey::Hypotheses));
158 }
159}