Skip to main content

wm_core/
context.rs

1//! Context — Per-request execution environment
2//!
3//! The Context is passed to every tool call. It provides access to the
4//! memory store, current brain-wave state, session information, and
5//! a scratchpad for intermediate results.
6
7use crate::brain_wave::BrainWave;
8use crate::galaxy::Galaxy;
9use std::collections::HashMap;
10
11/// Per-request execution context.
12pub struct Context {
13    /// Current brain-wave state
14    pub brain_wave: BrainWave,
15    /// Session ID (if in a session)
16    pub session_id: Option<uuid::Uuid>,
17    /// User ID (for multi-user isolation)
18    pub user_id: Option<String>,
19    /// Request metadata (from MCP client)
20    pub meta: HashMap<String, serde_json::Value>,
21    /// Scratchpad for intermediate results within a single dispatch
22    pub scratchpad: HashMap<String, serde_json::Value>,
23    /// Whether this request is running under a Dharma governance profile
24    pub dharma_profile: Option<String>,
25    /// Mandala compartment (research/sandbox/production/secure)
26    pub compartment: Option<String>,
27    /// Cached karma debt (updated post-dispatch, synced periodically)
28    pub karma_debt: f32,
29    /// Intent score for this request (0.0 = low intent, 1.0 = high intent)
30    pub intent_score: f32,
31    /// Citta coherence at dispatch time (0.0–1.0). Low coherence blocks writes.
32    pub citta_coherence: f32,
33    /// Citta valence at dispatch time (−1.0 to 1.0). Negative = displeasure.
34    pub citta_valence: f32,
35    /// Self-model confidence at dispatch time (0.0–1.0).
36    /// Below 0.5 triggers conservative dispatch (prefer cached results).
37    pub self_model_confidence: f32,
38    /// Drive curiosity level (0.0–1.0). High curiosity → exploration bias.
39    pub drive_curiosity: f32,
40    /// Drive caution level (0.0–1.0). High caution → conservative bias.
41    pub drive_caution: f32,
42    /// Drive energy level (0.0–1.0). Low energy → lightweight tool bias.
43    pub drive_energy: f32,
44    /// Drive exploration weight (0.0–1.0). Derived from curiosity.
45    pub drive_exploration_weight: f32,
46    /// Drive conservative weight (0.0–1.0). Derived from caution.
47    pub drive_conservative_weight: f32,
48    /// Last Gana dispatched in this context (for co-usage tracking, Phase 6)
49    pub last_gana: Option<crate::Gana>,
50    /// Read-only server mode — the dispatch pipeline refuses any tool that
51    /// declares writes while this is set.
52    pub readonly: bool,
53}
54
55impl Context {
56    /// Create a new context with the given brain-wave state.
57    #[must_use]
58    pub fn new(brain_wave: BrainWave) -> Self {
59        Self {
60            brain_wave,
61            session_id: None,
62            user_id: None,
63            meta: HashMap::new(),
64            scratchpad: HashMap::new(),
65            dharma_profile: None,
66            compartment: None,
67            karma_debt: 0.0,
68            intent_score: 1.0,
69            citta_coherence: 1.0,
70            citta_valence: 0.0,
71            self_model_confidence: 0.5,
72            drive_curiosity: 0.5,
73            drive_caution: 0.3,
74            drive_energy: 0.8,
75            drive_exploration_weight: 0.5,
76            drive_conservative_weight: 0.3,
77            last_gana: None,
78            readonly: false,
79        }
80    }
81
82    /// Get the current brain-wave state.
83    #[must_use]
84    pub const fn brain_wave(&self) -> BrainWave {
85        self.brain_wave
86    }
87
88    /// Set a scratchpad value.
89    pub fn set(&mut self, key: impl Into<String>, value: serde_json::Value) {
90        self.scratchpad.insert(key.into(), value);
91    }
92
93    /// Get a scratchpad value.
94    #[must_use]
95    pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
96        self.scratchpad.get(key)
97    }
98
99    /// Check if this context has access to the given galaxy.
100    ///
101    /// Compartment-based access control:
102    /// - `None` (default): full access to all galaxies (backward compatible)
103    /// - `sandbox`: only Tutorial and Research (no production data)
104    /// - `production`: all memory galaxies except system (Karma, Dharma, Substrate)
105    /// - `secure`: all memory galaxies
106    #[must_use]
107    pub fn can_access_galaxy(&self, galaxy: Galaxy) -> bool {
108        match self.compartment.as_deref() {
109            None => true,
110            Some("sandbox") => matches!(galaxy, Galaxy::Tutorial | Galaxy::Research),
111            Some("production") => !matches!(
112                galaxy,
113                Galaxy::Karma
114                    | Galaxy::Dharma
115                    | Galaxy::Substrate
116                    | Galaxy::Associations
117                    | Galaxy::Embeddings
118            ),
119            Some("secure") => matches!(
120                galaxy,
121                Galaxy::Aria
122                    | Galaxy::Citta
123                    | Galaxy::Codex
124                    | Galaxy::Journals
125                    | Galaxy::Dreams
126                    | Galaxy::Research
127                    | Galaxy::Sessions
128                    | Galaxy::Substrate
129                    | Galaxy::Tutorial
130                    | Galaxy::Universal
131            ),
132            // Unknown compartment values fail closed — no galaxy access.
133            Some(_) => false,
134        }
135    }
136
137    /// Check if this context allows write operations to the given galaxy.
138    ///
139    /// Sandbox is read-only for Research (can write to Tutorial only).
140    #[must_use]
141    pub fn can_write_galaxy(&self, galaxy: Galaxy) -> bool {
142        match self.compartment.as_deref() {
143            None => true,
144            Some("sandbox") => matches!(galaxy, Galaxy::Tutorial),
145            Some("production" | "secure") => self.can_access_galaxy(galaxy),
146            // Unknown compartment values fail closed — no write access.
147            Some(_) => false,
148        }
149    }
150}
151
152impl Default for Context {
153    fn default() -> Self {
154        Self::new(BrainWave::Gamma)
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::galaxy::Galaxy;
162
163    #[test]
164    fn no_compartment_has_full_access() {
165        let ctx = Context::default();
166        for g in Galaxy::all() {
167            assert!(
168                ctx.can_access_galaxy(g),
169                "No compartment should access {g:?}"
170            );
171            assert!(ctx.can_write_galaxy(g), "No compartment should write {g:?}");
172        }
173    }
174
175    fn make_ctx(compartment: &str) -> Context {
176        Context {
177            compartment: Some(compartment.into()),
178            ..Context::default()
179        }
180    }
181
182    #[test]
183    fn sandbox_can_only_read_tutorial_and_research() {
184        let ctx = make_ctx("sandbox");
185        assert!(ctx.can_access_galaxy(Galaxy::Tutorial));
186        assert!(ctx.can_access_galaxy(Galaxy::Research));
187        assert!(!ctx.can_access_galaxy(Galaxy::Codex));
188        assert!(!ctx.can_access_galaxy(Galaxy::Karma));
189        // Sandbox can only write to Tutorial
190        assert!(ctx.can_write_galaxy(Galaxy::Tutorial));
191        assert!(!ctx.can_write_galaxy(Galaxy::Research));
192    }
193
194    #[test]
195    fn production_cannot_access_system_galaxies() {
196        let ctx = make_ctx("production");
197        assert!(ctx.can_access_galaxy(Galaxy::Codex));
198        assert!(ctx.can_access_galaxy(Galaxy::Research));
199        assert!(!ctx.can_access_galaxy(Galaxy::Karma));
200        assert!(!ctx.can_access_galaxy(Galaxy::Dharma));
201        assert!(!ctx.can_access_galaxy(Galaxy::Substrate));
202        assert!(ctx.can_write_galaxy(Galaxy::Codex));
203    }
204
205    #[test]
206    fn secure_can_access_all_memory_galaxies() {
207        let ctx = make_ctx("secure");
208        assert!(ctx.can_access_galaxy(Galaxy::Codex));
209        assert!(ctx.can_access_galaxy(Galaxy::Substrate));
210        assert!(ctx.can_write_galaxy(Galaxy::Codex));
211        // Secure still can't access non-memory galaxies
212        assert!(!ctx.can_access_galaxy(Galaxy::Karma));
213    }
214
215    #[test]
216    fn unknown_compartment_fails_closed() {
217        let ctx = make_ctx("bogus");
218        for g in Galaxy::all() {
219            assert!(
220                !ctx.can_access_galaxy(g),
221                "unknown compartment must not read {g:?}"
222            );
223            assert!(
224                !ctx.can_write_galaxy(g),
225                "unknown compartment must not write {g:?}"
226            );
227        }
228    }
229}