1use crate::brain_wave::BrainWave;
8use crate::galaxy::Galaxy;
9use crate::sandbox::SpawnPolicy;
10use std::collections::HashMap;
11
12pub struct Context {
14 pub brain_wave: BrainWave,
16 pub session_id: Option<uuid::Uuid>,
18 pub user_id: Option<String>,
20 pub meta: HashMap<String, serde_json::Value>,
22 pub scratchpad: HashMap<String, serde_json::Value>,
24 pub dharma_profile: Option<String>,
26 pub compartment: Option<String>,
28 pub karma_debt: f32,
30 pub intent_score: f32,
32 pub citta_coherence: f32,
34 pub citta_valence: f32,
36 pub self_model_confidence: f32,
39 pub drive_curiosity: f32,
41 pub drive_caution: f32,
43 pub drive_energy: f32,
45 pub drive_exploration_weight: f32,
47 pub drive_conservative_weight: f32,
49 pub last_gana: Option<crate::Gana>,
51 pub readonly: bool,
54 pub explicit_confirm: bool,
60 pub operation_id: Option<String>,
63 pub spawn: SpawnPolicy,
68}
69
70impl Context {
71 #[must_use]
73 pub fn new(brain_wave: BrainWave) -> Self {
74 Self {
75 brain_wave,
76 session_id: None,
77 user_id: None,
78 meta: HashMap::new(),
79 scratchpad: HashMap::new(),
80 dharma_profile: None,
81 compartment: None,
82 karma_debt: 0.0,
83 intent_score: 1.0,
84 citta_coherence: 1.0,
85 citta_valence: 0.0,
86 self_model_confidence: 0.5,
87 drive_curiosity: 0.5,
88 drive_caution: 0.3,
89 drive_energy: 0.8,
90 drive_exploration_weight: 0.5,
91 drive_conservative_weight: 0.3,
92 last_gana: None,
93 readonly: false,
94 explicit_confirm: false,
95 operation_id: None,
96 spawn: SpawnPolicy::disabled(),
97 }
98 }
99
100 #[must_use]
102 pub fn with_operation_id(mut self, op_id: impl Into<String>) -> Self {
103 self.operation_id = Some(op_id.into());
104 self
105 }
106
107 #[must_use]
109 pub const fn brain_wave(&self) -> BrainWave {
110 self.brain_wave
111 }
112
113 pub fn set(&mut self, key: impl Into<String>, value: serde_json::Value) {
115 self.scratchpad.insert(key.into(), value);
116 }
117
118 #[must_use]
120 pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
121 self.scratchpad.get(key)
122 }
123
124 #[must_use]
132 pub fn can_access_galaxy(&self, galaxy: Galaxy) -> bool {
133 match self.compartment.as_deref() {
134 None => true,
135 Some("sandbox") => matches!(galaxy, Galaxy::Tutorial | Galaxy::Research),
136 Some("production") => !matches!(
137 galaxy,
138 Galaxy::Karma
139 | Galaxy::Dharma
140 | Galaxy::Substrate
141 | Galaxy::Associations
142 | Galaxy::Embeddings
143 ),
144 Some("secure") => matches!(
145 galaxy,
146 Galaxy::Aria
147 | Galaxy::Citta
148 | Galaxy::Codex
149 | Galaxy::Journals
150 | Galaxy::Dreams
151 | Galaxy::Research
152 | Galaxy::Sessions
153 | Galaxy::Substrate
154 | Galaxy::Tutorial
155 | Galaxy::Universal
156 | Galaxy::Valkyrie
157 ),
158 Some(_) => false,
160 }
161 }
162
163 #[must_use]
167 pub fn can_write_galaxy(&self, galaxy: Galaxy) -> bool {
168 match self.compartment.as_deref() {
169 None => true,
170 Some("sandbox") => matches!(galaxy, Galaxy::Tutorial),
171 Some("production" | "secure") => self.can_access_galaxy(galaxy),
172 Some(_) => false,
174 }
175 }
176}
177
178impl Default for Context {
179 fn default() -> Self {
180 Self::new(BrainWave::Gamma)
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use crate::galaxy::Galaxy;
188
189 #[test]
190 fn no_compartment_has_full_access() {
191 let ctx = Context::default();
192 for g in Galaxy::all() {
193 assert!(
194 ctx.can_access_galaxy(g),
195 "No compartment should access {g:?}"
196 );
197 assert!(ctx.can_write_galaxy(g), "No compartment should write {g:?}");
198 }
199 }
200
201 fn make_ctx(compartment: &str) -> Context {
202 Context {
203 compartment: Some(compartment.into()),
204 ..Context::default()
205 }
206 }
207
208 #[test]
209 fn sandbox_can_only_read_tutorial_and_research() {
210 let ctx = make_ctx("sandbox");
211 assert!(ctx.can_access_galaxy(Galaxy::Tutorial));
212 assert!(ctx.can_access_galaxy(Galaxy::Research));
213 assert!(!ctx.can_access_galaxy(Galaxy::Codex));
214 assert!(!ctx.can_access_galaxy(Galaxy::Karma));
215 assert!(ctx.can_write_galaxy(Galaxy::Tutorial));
217 assert!(!ctx.can_write_galaxy(Galaxy::Research));
218 }
219
220 #[test]
221 fn production_cannot_access_system_galaxies() {
222 let ctx = make_ctx("production");
223 assert!(ctx.can_access_galaxy(Galaxy::Codex));
224 assert!(ctx.can_access_galaxy(Galaxy::Research));
225 assert!(!ctx.can_access_galaxy(Galaxy::Karma));
226 assert!(!ctx.can_access_galaxy(Galaxy::Dharma));
227 assert!(!ctx.can_access_galaxy(Galaxy::Substrate));
228 assert!(ctx.can_write_galaxy(Galaxy::Codex));
229 }
230
231 #[test]
232 fn secure_can_access_all_memory_galaxies() {
233 let ctx = make_ctx("secure");
234 assert!(ctx.can_access_galaxy(Galaxy::Codex));
235 assert!(ctx.can_access_galaxy(Galaxy::Substrate));
236 assert!(ctx.can_write_galaxy(Galaxy::Codex));
237 assert!(!ctx.can_access_galaxy(Galaxy::Karma));
239 }
240
241 #[test]
242 fn unknown_compartment_fails_closed() {
243 let ctx = make_ctx("bogus");
244 for g in Galaxy::all() {
245 assert!(
246 !ctx.can_access_galaxy(g),
247 "unknown compartment must not read {g:?}"
248 );
249 assert!(
250 !ctx.can_write_galaxy(g),
251 "unknown compartment must not write {g:?}"
252 );
253 }
254 }
255
256 #[test]
257 fn operation_id_propagates_and_defaults_none() {
258 let ctx = Context::default();
259 assert_eq!(ctx.operation_id, None);
260
261 let ctx_with_op = ctx.with_operation_id("01H7XYZ0000000000000000000");
262 assert_eq!(
263 ctx_with_op.operation_id.as_deref(),
264 Some("01H7XYZ0000000000000000000")
265 );
266 }
267}