aster/blueprint/
blueprint_context.rs1use chrono::{DateTime, Utc};
12use once_cell::sync::Lazy;
13use std::collections::HashMap;
14use tokio::sync::RwLock;
15
16use super::boundary_checker::{create_boundary_checker, BoundaryCheckResult, BoundaryChecker};
17use super::types::Blueprint;
18
19#[derive(Debug, Clone)]
25pub struct ActiveTaskContext {
26 pub blueprint_id: String,
28 pub task_id: String,
30 pub module_id: Option<String>,
32 pub worker_id: String,
34 pub started_at: DateTime<Utc>,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum FileOperation {
41 Read,
42 #[default]
43 Write,
44 Delete,
45}
46
47struct BlueprintContextInner {
53 current_blueprint: Option<Blueprint>,
55 boundary_checker: Option<BoundaryChecker>,
57 active_tasks: HashMap<String, ActiveTaskContext>,
59 boundary_check_enabled: bool,
61}
62
63impl Default for BlueprintContextInner {
64 fn default() -> Self {
65 Self {
66 current_blueprint: None,
67 boundary_checker: None,
68 active_tasks: HashMap::new(),
69 boundary_check_enabled: true,
70 }
71 }
72}
73
74pub struct BlueprintContextManager {
76 inner: RwLock<BlueprintContextInner>,
77}
78
79impl BlueprintContextManager {
80 fn new() -> Self {
82 Self {
83 inner: RwLock::new(BlueprintContextInner::default()),
84 }
85 }
86
87 pub async fn set_blueprint(&self, blueprint: Blueprint) {
93 let mut inner = self.inner.write().await;
94 let checker = create_boundary_checker(blueprint.clone(), None);
95 inner.current_blueprint = Some(blueprint);
96 inner.boundary_checker = Some(checker);
97 }
98
99 pub async fn clear_blueprint(&self) {
101 let mut inner = self.inner.write().await;
102 inner.current_blueprint = None;
103 inner.boundary_checker = None;
104 inner.active_tasks.clear();
105 }
106
107 pub async fn get_blueprint(&self) -> Option<Blueprint> {
109 let inner = self.inner.read().await;
110 inner.current_blueprint.clone()
111 }
112
113 pub async fn set_active_task(&self, context: ActiveTaskContext) {
119 let mut inner = self.inner.write().await;
120 inner
121 .active_tasks
122 .insert(context.worker_id.clone(), context);
123 }
124
125 pub async fn get_active_task(&self, worker_id: &str) -> Option<ActiveTaskContext> {
127 let inner = self.inner.read().await;
128 inner.active_tasks.get(worker_id).cloned()
129 }
130
131 pub async fn clear_active_task(&self, worker_id: &str) {
133 let mut inner = self.inner.write().await;
134 inner.active_tasks.remove(worker_id);
135 }
136
137 pub async fn get_all_active_tasks(&self) -> Vec<ActiveTaskContext> {
139 let inner = self.inner.read().await;
140 inner.active_tasks.values().cloned().collect()
141 }
142
143 pub async fn get_current_task_context(&self) -> Option<ActiveTaskContext> {
146 let tasks = self.get_all_active_tasks().await;
147 if tasks.len() == 1 {
149 return tasks.into_iter().next();
150 }
151 None
153 }
154
155 pub async fn set_boundary_check_enabled(&self, enabled: bool) {
161 let mut inner = self.inner.write().await;
162 inner.boundary_check_enabled = enabled;
163 }
164
165 pub async fn check_file_operation(
167 &self,
168 file_path: &str,
169 _operation: FileOperation,
170 worker_id: Option<&str>,
171 ) -> BoundaryCheckResult {
172 let inner = self.inner.read().await;
173
174 if !inner.boundary_check_enabled {
176 return BoundaryCheckResult::allow();
177 }
178
179 let checker = match &inner.boundary_checker {
181 Some(c) => c,
182 None => return BoundaryCheckResult::allow(),
183 };
184
185 if inner.active_tasks.is_empty() {
187 return BoundaryCheckResult::allow();
188 }
189
190 let context = if let Some(wid) = worker_id {
192 inner.active_tasks.get(wid).cloned()
193 } else if inner.active_tasks.len() == 1 {
194 inner.active_tasks.values().next().cloned()
195 } else {
196 None
197 };
198
199 if let Some(ctx) = context {
201 if let Some(ref module_id) = ctx.module_id {
202 return checker.check_task_boundary(Some(module_id.as_str()), file_path);
203 }
204 }
205
206 checker.check_task_boundary(None, file_path)
208 }
209
210 pub async fn enforce_file_operation(
213 &self,
214 file_path: &str,
215 operation: FileOperation,
216 worker_id: Option<&str>,
217 ) -> Result<(), String> {
218 let result = self
219 .check_file_operation(file_path, operation, worker_id)
220 .await;
221 if !result.allowed {
222 Err(format!(
223 "[蓝图边界检查] {}",
224 result.reason.unwrap_or_default()
225 ))
226 } else {
227 Ok(())
228 }
229 }
230
231 pub async fn get_status(&self) -> BlueprintContextStatus {
237 let inner = self.inner.read().await;
238 BlueprintContextStatus {
239 has_blueprint: inner.current_blueprint.is_some(),
240 blueprint_id: inner.current_blueprint.as_ref().map(|b| b.id.clone()),
241 boundary_check_enabled: inner.boundary_check_enabled,
242 active_task_count: inner.active_tasks.len(),
243 active_tasks: inner.active_tasks.values().cloned().collect(),
244 }
245 }
246}
247
248#[derive(Debug, Clone)]
250pub struct BlueprintContextStatus {
251 pub has_blueprint: bool,
252 pub blueprint_id: Option<String>,
253 pub boundary_check_enabled: bool,
254 pub active_task_count: usize,
255 pub active_tasks: Vec<ActiveTaskContext>,
256}
257
258static BLUEPRINT_CONTEXT: Lazy<BlueprintContextManager> = Lazy::new(BlueprintContextManager::new);
264
265pub fn get_blueprint_context() -> &'static BlueprintContextManager {
267 &BLUEPRINT_CONTEXT
268}
269
270pub async fn set_blueprint(blueprint: Blueprint) {
276 get_blueprint_context().set_blueprint(blueprint).await;
277}
278
279pub async fn clear_blueprint() {
281 get_blueprint_context().clear_blueprint().await;
282}
283
284pub async fn set_active_task(context: ActiveTaskContext) {
286 get_blueprint_context().set_active_task(context).await;
287}
288
289pub async fn clear_active_task(worker_id: &str) {
291 get_blueprint_context().clear_active_task(worker_id).await;
292}
293
294pub async fn check_file_operation(
296 file_path: &str,
297 operation: FileOperation,
298 worker_id: Option<&str>,
299) -> BoundaryCheckResult {
300 get_blueprint_context()
301 .check_file_operation(file_path, operation, worker_id)
302 .await
303}
304
305pub async fn enforce_file_operation(
307 file_path: &str,
308 operation: FileOperation,
309 worker_id: Option<&str>,
310) -> Result<(), String> {
311 get_blueprint_context()
312 .enforce_file_operation(file_path, operation, worker_id)
313 .await
314}