codetether_agent/a2a/
task_scope.rs1pub fn check_task_scope(
23 task: &serde_json::Value,
24 worker_id: &str,
25 agent_name: &str,
26 workspace_ids: &[String],
27) -> Result<(), String> {
28 let metadata = task_metadata(task);
29
30 let target_agent = metadata
32 .get("target_agent_name")
33 .and_then(|v| v.as_str())
34 .or_else(|| task.get("target_agent_name").and_then(|v| v.as_str()))
35 .or_else(|| {
36 task.get("task")
37 .and_then(|t| t.get("target_agent_name"))
38 .and_then(|v| v.as_str())
39 });
40 if let Some(target) = target_agent {
41 if !target.is_empty() && target != agent_name {
42 return Err(format!("target_agent_name={target} ≠ agent={agent_name}"));
43 }
44 }
45
46 let target_worker = metadata
48 .get("target_worker_id")
49 .and_then(|v| v.as_str())
50 .or_else(|| task.get("target_worker_id").and_then(|v| v.as_str()));
51 if let Some(target) = target_worker {
52 if !target.is_empty() && target != worker_id {
53 return Err(format!("target_worker_id={target} ≠ worker={worker_id}"));
54 }
55 }
56
57 let ws_id = task
60 .get("workspace_id")
61 .and_then(|v| v.as_str())
62 .or_else(|| metadata.get("workspace_id").and_then(|v| v.as_str()))
63 .unwrap_or("");
64 if !ws_id.is_empty() && ws_id != "global" && !workspace_ids.is_empty() {
65 if !workspace_ids.iter().any(|id| id == ws_id) {
66 return Err(format!("workspace_id={ws_id} not in worker scope"));
67 }
68 }
69
70 Ok(())
71}
72
73fn task_metadata(task: &serde_json::Value) -> serde_json::Map<String, serde_json::Value> {
75 task.get("task")
76 .and_then(|t| t.get("metadata"))
77 .or_else(|| task.get("metadata"))
78 .and_then(|m| m.as_object())
79 .cloned()
80 .unwrap_or_default()
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use serde_json::json;
87
88 #[test]
89 fn accepts_unscoped_task() {
90 let task = json!({"id": "t1", "prompt": "hello"});
91 assert!(check_task_scope(&task, "w1", "builder", &[]).is_ok());
92 }
93
94 #[test]
95 fn rejects_wrong_agent() {
96 let task = json!({"id": "t1", "metadata": {"target_agent_name": "planner"}});
97 assert!(check_task_scope(&task, "w1", "builder", &[]).is_err());
98 }
99
100 #[test]
101 fn accepts_matching_agent() {
102 let task = json!({"id": "t1", "metadata": {"target_agent_name": "builder"}});
103 assert!(check_task_scope(&task, "w1", "builder", &[]).is_ok());
104 }
105
106 #[test]
107 fn rejects_wrong_worker() {
108 let task = json!({"id": "t1", "metadata": {"target_worker_id": "other-worker"}});
109 assert!(check_task_scope(&task, "w1", "builder", &[]).is_err());
110 }
111
112 #[test]
113 fn accepts_matching_worker() {
114 let task = json!({"id": "t1", "metadata": {"target_worker_id": "w1"}});
115 assert!(check_task_scope(&task, "w1", "builder", &[]).is_ok());
116 }
117
118 #[test]
119 fn rejects_unknown_workspace() {
120 let task = json!({"id": "t1", "workspace_id": "ws-999"});
121 let workspaces = vec!["ws-1".into(), "ws-2".into()];
122 assert!(check_task_scope(&task, "w1", "builder", &workspaces).is_err());
123 }
124
125 #[test]
126 fn accepts_known_workspace() {
127 let task = json!({"id": "t1", "workspace_id": "ws-1"});
128 let workspaces = vec!["ws-1".into(), "ws-2".into()];
129 assert!(check_task_scope(&task, "w1", "builder", &workspaces).is_ok());
130 }
131
132 #[test]
133 fn accepts_global_workspace() {
134 let task = json!({"id": "t1", "workspace_id": "global"});
135 let workspaces = vec!["ws-1".into()];
136 assert!(check_task_scope(&task, "w1", "builder", &workspaces).is_ok());
137 }
138
139 #[test]
140 fn accepts_empty_workspace_when_worker_has_none() {
141 let task = json!({"id": "t1"});
142 assert!(check_task_scope(&task, "w1", "builder", &[]).is_ok());
143 }
144
145 #[test]
146 fn nested_task_metadata_agent() {
147 let task = json!({"task": {"id": "t1", "metadata": {"target_agent_name": "builder"}}});
148 assert!(check_task_scope(&task, "w1", "builder", &[]).is_ok());
149 }
150
151 #[test]
152 fn nested_task_metadata_wrong_agent() {
153 let task = json!({"task": {"id": "t1", "metadata": {"target_agent_name": "planner"}}});
154 assert!(check_task_scope(&task, "w1", "builder", &[]).is_err());
155 }
156}