Skip to main content

loong_kernel/
test_support.rs

1#![allow(clippy::expect_used, clippy::panic)]
2
3use std::collections::{BTreeMap, BTreeSet};
4use std::sync::Mutex;
5
6use async_trait::async_trait;
7use serde_json::json;
8
9use crate::connector::{ConnectorExtensionAdapter, CoreConnectorAdapter};
10use crate::contracts::{
11    Capability, ConnectorCommand, ConnectorOutcome, ExecutionRoute, HarnessKind, HarnessOutcome,
12    HarnessRequest,
13};
14use crate::errors::{ConnectorError, PolicyError};
15use crate::harness::HarnessAdapter;
16use crate::memory::{
17    CoreMemoryAdapter, MemoryCoreOutcome, MemoryCoreRequest, MemoryExtensionAdapter,
18    MemoryExtensionOutcome, MemoryExtensionRequest,
19};
20use crate::pack::VerticalPackManifest;
21use crate::policy_ext::{PolicyExtension, PolicyExtensionContext};
22use crate::runtime::{
23    CoreRuntimeAdapter, RuntimeCoreOutcome, RuntimeCoreRequest, RuntimeExtensionAdapter,
24    RuntimeExtensionOutcome, RuntimeExtensionRequest,
25};
26use crate::tool::{
27    CoreToolAdapter, ToolCoreOutcome, ToolCoreRequest, ToolExtensionAdapter, ToolExtensionOutcome,
28    ToolExtensionRequest,
29};
30
31pub struct MockEmbeddedPiHarness {
32    pub seen_tasks: Mutex<Vec<String>>,
33}
34pub struct MockCrmConnector;
35pub struct MockCoreConnector;
36pub struct MockCoreConnectorGrpc;
37pub struct MockPanickingCoreConnector;
38pub struct MockConnectorExtension;
39pub struct MockPanickingConnectorExtension;
40pub struct MockAcpHarness;
41pub struct MockCoreRuntime;
42pub struct MockCoreRuntimeFallback;
43pub struct MockRuntimeExtension;
44pub struct MockCoreTool;
45pub struct MockToolExtension;
46pub struct MockCoreMemory;
47pub struct MockMemoryExtension;
48pub struct NoNetworkEgressPolicyExtension;
49pub const TEST_CAPABILITY_VARIANTS: [Capability; 13] = [
50    Capability::InvokeTool,
51    Capability::InvokeConnector,
52    Capability::MemoryRead,
53    Capability::MemoryWrite,
54    Capability::FilesystemRead,
55    Capability::FilesystemWrite,
56    Capability::NetworkEgress,
57    Capability::ObserveTelemetry,
58    Capability::ControlRead,
59    Capability::ControlWrite,
60    Capability::ControlApprovals,
61    Capability::ControlPairing,
62    Capability::ControlAcp,
63];
64pub const TEST_CAPABILITY_VARIANT_COUNT: u8 = TEST_CAPABILITY_VARIANTS.len() as u8;
65#[derive(Debug, Clone, Copy)]
66pub enum ToolGateMode {
67    Deny,
68}
69#[derive(Debug)]
70pub struct ToolGatePolicyExtension {
71    pub gated_tool: String,
72    pub mode: ToolGateMode,
73}
74impl ToolGatePolicyExtension {
75    pub fn new(gated_tool: &str, mode: ToolGateMode) -> Self {
76        Self {
77            gated_tool: gated_tool.to_owned(),
78            mode,
79        }
80    }
81}
82
83#[async_trait]
84impl HarnessAdapter for MockEmbeddedPiHarness {
85    fn name(&self) -> &str {
86        "pi-local"
87    }
88    fn kind(&self) -> HarnessKind {
89        HarnessKind::EmbeddedPi
90    }
91    async fn execute(
92        &self,
93        request: HarnessRequest,
94    ) -> Result<HarnessOutcome, crate::HarnessError> {
95        self.seen_tasks
96            .lock()
97            .expect("mutex poisoned")
98            .push(request.task_id.clone());
99        Ok(HarnessOutcome {
100            status: "ok".to_owned(),
101            output: json!({"adapter":"pi-local","task_id":request.task_id,"objective":request.objective}),
102        })
103    }
104}
105#[async_trait]
106impl CoreConnectorAdapter for MockCrmConnector {
107    fn name(&self) -> &str {
108        "crm"
109    }
110    async fn invoke_core(
111        &self,
112        command: ConnectorCommand,
113    ) -> Result<ConnectorOutcome, ConnectorError> {
114        Ok(ConnectorOutcome {
115            status: "ok".to_owned(),
116            payload: json!({"operation":command.operation,"echo":command.payload}),
117        })
118    }
119}
120#[async_trait]
121impl CoreConnectorAdapter for MockCoreConnector {
122    fn name(&self) -> &str {
123        "http-core"
124    }
125    async fn invoke_core(
126        &self,
127        command: ConnectorCommand,
128    ) -> Result<ConnectorOutcome, ConnectorError> {
129        Ok(ConnectorOutcome {
130            status: "ok".to_owned(),
131            payload: json!({"tier":"core","adapter":"http-core","connector":command.connector_name,"operation":command.operation,"payload":command.payload}),
132        })
133    }
134}
135#[async_trait]
136impl CoreConnectorAdapter for MockCoreConnectorGrpc {
137    fn name(&self) -> &str {
138        "grpc-core"
139    }
140    async fn invoke_core(
141        &self,
142        command: ConnectorCommand,
143    ) -> Result<ConnectorOutcome, ConnectorError> {
144        Ok(ConnectorOutcome {
145            status: "ok".to_owned(),
146            payload: json!({"tier":"core","adapter":"grpc-core","connector":command.connector_name,"operation":command.operation}),
147        })
148    }
149}
150#[async_trait]
151impl CoreConnectorAdapter for MockPanickingCoreConnector {
152    fn name(&self) -> &str {
153        "panic-core"
154    }
155    async fn invoke_core(
156        &self,
157        _command: ConnectorCommand,
158    ) -> Result<ConnectorOutcome, ConnectorError> {
159        panic!("simulated connector core panic");
160    }
161}
162#[async_trait]
163impl ConnectorExtensionAdapter for MockConnectorExtension {
164    fn name(&self) -> &str {
165        "shielded-bridge"
166    }
167    async fn invoke_extension(
168        &self,
169        command: ConnectorCommand,
170        core: &(dyn CoreConnectorAdapter + Sync),
171    ) -> Result<ConnectorOutcome, ConnectorError> {
172        let core_probe = core
173            .invoke_core(ConnectorCommand {
174                connector_name: command.connector_name.clone(),
175                operation: "probe".to_owned(),
176                required_capabilities: BTreeSet::new(),
177                payload: json!({"mode":"probe"}),
178            })
179            .await?;
180        Ok(ConnectorOutcome {
181            status: "ok".to_owned(),
182            payload: json!({"tier":"extension","extension":"shielded-bridge","operation":command.operation,"core_probe":core_probe.payload,"payload":command.payload}),
183        })
184    }
185}
186#[async_trait]
187impl ConnectorExtensionAdapter for MockPanickingConnectorExtension {
188    fn name(&self) -> &str {
189        "panic-extension"
190    }
191    async fn invoke_extension(
192        &self,
193        _command: ConnectorCommand,
194        _core: &(dyn CoreConnectorAdapter + Sync),
195    ) -> Result<ConnectorOutcome, ConnectorError> {
196        panic!("simulated connector extension panic");
197    }
198}
199#[async_trait]
200impl HarnessAdapter for MockAcpHarness {
201    fn name(&self) -> &str {
202        "acp-gateway"
203    }
204    fn kind(&self) -> HarnessKind {
205        HarnessKind::Acp
206    }
207    async fn execute(
208        &self,
209        request: HarnessRequest,
210    ) -> Result<HarnessOutcome, crate::HarnessError> {
211        Ok(HarnessOutcome {
212            status: "ok".to_owned(),
213            output: json!({"adapter":"acp-gateway","task_id":request.task_id}),
214        })
215    }
216}
217#[async_trait]
218impl CoreRuntimeAdapter for MockCoreRuntime {
219    fn name(&self) -> &str {
220        "native-core"
221    }
222    async fn execute_core(
223        &self,
224        request: RuntimeCoreRequest,
225    ) -> Result<RuntimeCoreOutcome, crate::RuntimePlaneError> {
226        Ok(RuntimeCoreOutcome {
227            status: "ok".to_owned(),
228            payload: json!({"adapter":"native-core","action":request.action,"payload":request.payload}),
229        })
230    }
231}
232#[async_trait]
233impl CoreRuntimeAdapter for MockCoreRuntimeFallback {
234    fn name(&self) -> &str {
235        "fallback-core"
236    }
237    async fn execute_core(
238        &self,
239        request: RuntimeCoreRequest,
240    ) -> Result<RuntimeCoreOutcome, crate::RuntimePlaneError> {
241        Ok(RuntimeCoreOutcome {
242            status: "ok".to_owned(),
243            payload: json!({"adapter":"fallback-core","action":request.action}),
244        })
245    }
246}
247#[async_trait]
248impl RuntimeExtensionAdapter for MockRuntimeExtension {
249    fn name(&self) -> &str {
250        "acp-bridge"
251    }
252    async fn execute_extension(
253        &self,
254        request: RuntimeExtensionRequest,
255        core: &(dyn CoreRuntimeAdapter + Sync),
256    ) -> Result<RuntimeExtensionOutcome, crate::RuntimePlaneError> {
257        let core_probe = core
258            .execute_core(RuntimeCoreRequest {
259                action: "probe".to_owned(),
260                payload: json!({}),
261            })
262            .await?;
263        Ok(RuntimeExtensionOutcome {
264            status: "ok".to_owned(),
265            payload: json!({"extension":"acp-bridge","action":request.action,"core_probe":core_probe.payload,"payload":request.payload}),
266        })
267    }
268}
269#[async_trait]
270impl CoreToolAdapter for MockCoreTool {
271    fn name(&self) -> &str {
272        "core-tools"
273    }
274
275    async fn execute_core_tool(
276        &self,
277        request: ToolCoreRequest,
278    ) -> Result<ToolCoreOutcome, crate::ToolPlaneError> {
279        Ok(ToolCoreOutcome {
280            status: "ok".to_owned(),
281            payload: json!({"tool":request.tool_name,"payload":request.payload}),
282        })
283    }
284}
285#[async_trait]
286impl ToolExtensionAdapter for MockToolExtension {
287    fn name(&self) -> &str {
288        "sql-analytics"
289    }
290    async fn execute_tool_extension(
291        &self,
292        request: ToolExtensionRequest,
293        core: &(dyn CoreToolAdapter + Sync),
294    ) -> Result<ToolExtensionOutcome, crate::ToolPlaneError> {
295        let core_probe = core
296            .execute_core_tool(ToolCoreRequest {
297                tool_name: "schema_probe".to_owned(),
298                payload: json!({}),
299            })
300            .await?;
301        Ok(ToolExtensionOutcome {
302            status: "ok".to_owned(),
303            payload: json!({"extension":"sql-analytics","action":request.extension_action,"core_probe":core_probe.payload}),
304        })
305    }
306}
307#[async_trait]
308impl CoreMemoryAdapter for MockCoreMemory {
309    fn name(&self) -> &str {
310        "kv-core"
311    }
312    async fn execute_core_memory(
313        &self,
314        request: MemoryCoreRequest,
315    ) -> Result<MemoryCoreOutcome, crate::MemoryPlaneError> {
316        Ok(MemoryCoreOutcome {
317            status: "ok".to_owned(),
318            payload: json!({"operation":request.operation,"payload":request.payload}),
319        })
320    }
321}
322#[async_trait]
323impl MemoryExtensionAdapter for MockMemoryExtension {
324    fn name(&self) -> &str {
325        "vector-index"
326    }
327    async fn execute_memory_extension(
328        &self,
329        request: MemoryExtensionRequest,
330        core: &(dyn CoreMemoryAdapter + Sync),
331    ) -> Result<MemoryExtensionOutcome, crate::MemoryPlaneError> {
332        let core_probe = core
333            .execute_core_memory(MemoryCoreRequest {
334                operation: "read".to_owned(),
335                payload: json!({"key":"seed"}),
336            })
337            .await?;
338        Ok(MemoryExtensionOutcome {
339            status: "ok".to_owned(),
340            payload: json!({"extension":"vector-index","operation":request.operation,"core_probe":core_probe.payload}),
341        })
342    }
343}
344impl PolicyExtension for NoNetworkEgressPolicyExtension {
345    fn name(&self) -> &str {
346        "no-network-egress"
347    }
348    fn authorize_extension(&self, context: &PolicyExtensionContext<'_>) -> Result<(), PolicyError> {
349        if context
350            .required_capabilities
351            .contains(&Capability::NetworkEgress)
352        {
353            return Err(PolicyError::ExtensionDenied {
354                extension: self.name().to_owned(),
355                reason: "network egress is blocked for this environment".to_owned(),
356            });
357        }
358        Ok(())
359    }
360}
361impl PolicyExtension for ToolGatePolicyExtension {
362    fn name(&self) -> &str {
363        "tool-gate"
364    }
365    fn authorize_extension(&self, context: &PolicyExtensionContext<'_>) -> Result<(), PolicyError> {
366        let Some(params) = context.request_parameters else {
367            return Ok(());
368        };
369        let tool_name = params
370            .get("tool_name")
371            .and_then(|v| v.as_str())
372            .unwrap_or("");
373        if tool_name != self.gated_tool {
374            return Ok(());
375        }
376        match self.mode {
377            ToolGateMode::Deny => Err(PolicyError::ToolCallDenied {
378                tool_name: tool_name.to_owned(),
379                reason: "blocked by deterministic policy rule".to_owned(),
380            }),
381        }
382    }
383}
384pub fn sample_pack() -> VerticalPackManifest {
385    VerticalPackManifest {
386        pack_id: "sales-intel".to_owned(),
387        domain: "sales".to_owned(),
388        version: "0.1.0".to_owned(),
389        default_route: ExecutionRoute {
390            harness_kind: HarnessKind::EmbeddedPi,
391            adapter: Some("pi-local".to_owned()),
392        },
393        allowed_connectors: BTreeSet::from(["crm".to_owned()]),
394        granted_capabilities: BTreeSet::from([
395            Capability::InvokeTool,
396            Capability::InvokeConnector,
397            Capability::MemoryRead,
398        ]),
399        metadata: BTreeMap::from([("owner".to_owned(), "revenue-team".to_owned())]),
400    }
401}
402pub fn acp_pack_without_explicit_adapter() -> VerticalPackManifest {
403    VerticalPackManifest {
404        pack_id: "code-review".to_owned(),
405        domain: "engineering".to_owned(),
406        version: "0.1.0".to_owned(),
407        default_route: ExecutionRoute {
408            harness_kind: HarnessKind::Acp,
409            adapter: None,
410        },
411        allowed_connectors: BTreeSet::new(),
412        granted_capabilities: BTreeSet::from([Capability::InvokeTool]),
413        metadata: BTreeMap::new(),
414    }
415}
416pub fn capability_from_bit(bit: u8) -> Capability {
417    let bit_index = usize::from(bit);
418    TEST_CAPABILITY_VARIANTS
419        .get(bit_index)
420        .copied()
421        .expect("test capability bit should be in range")
422}
423pub fn capability_set_from_mask(mask: u16) -> BTreeSet<Capability> {
424    let mut capabilities = BTreeSet::new();
425    for (bit_index, capability) in TEST_CAPABILITY_VARIANTS.iter().copied().enumerate() {
426        let bit_mask = 1_u16 << bit_index;
427        let is_enabled = (mask & bit_mask) != 0;
428        if is_enabled {
429            capabilities.insert(capability);
430        }
431    }
432    capabilities
433}