vendor: update cargo-cxcloud-human-simulator-0.1.0

This commit is contained in:
cx-git-agent
2026-04-26 16:48:22 +00:00
committed by GitHub
parent aa5c79773a
commit d3e95624a5
9 changed files with 2932 additions and 3 deletions
+7
View File
@@ -0,0 +1,7 @@
{
"git": {
"sha1": "927a31cbf65f78c3ef6b729631b2fc35335afe06",
"dirty": true
},
"path_in_vcs": "services/cxcloud-rs/crates/human-simulator"
}
Generated
+2369
View File
File diff suppressed because it is too large Load Diff
+79
View File
@@ -0,0 +1,79 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "cxcloud-human-simulator"
version = "0.1.0"
build = false
publish = ["cxai"]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
readme = false
[[bin]]
name = "human-simulator"
path = "src/main.rs"
[dependencies.anyhow]
version = "1"
[dependencies.axum]
version = "0.7"
features = ["macros"]
[dependencies.chrono]
version = "0.4"
features = ["serde"]
[dependencies.cxcloud-common]
version = "0.1.0"
registry-index = "sparse+https://git.cxllm-studio.com/api/packages/CxAI-LLM/cargo/"
[dependencies.cxcloud-proto]
version = "0.1.0"
registry-index = "sparse+https://git.cxllm-studio.com/api/packages/CxAI-LLM/cargo/"
[dependencies.prost]
version = "0.13"
[dependencies.prost-types]
version = "0.13"
[dependencies.serde]
version = "1"
features = ["derive"]
[dependencies.serde_json]
version = "1"
[dependencies.serde_yaml]
version = "0.9"
[dependencies.tokio]
version = "1"
features = ["full"]
[dependencies.tonic]
version = "0.12"
[dependencies.tracing]
version = "0.1"
[dependencies.uuid]
version = "1"
features = [
"v7",
"serde",
]
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "cxcloud-human-simulator"
version.workspace = true
edition.workspace = true
publish.workspace = true
[[bin]]
name = "human-simulator"
path = "src/main.rs"
[dependencies]
cxcloud-common = { workspace = true }
cxcloud-proto = { workspace = true }
tokio = { workspace = true }
axum = { workspace = true }
tonic = { workspace = true }
prost = { workspace = true }
prost-types = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
tracing = { workspace = true }
chrono = { workspace = true }
uuid = { workspace = true }
anyhow = { workspace = true }
-3
View File
@@ -1,3 +0,0 @@
# cargo-cxcloud-human-simulator-0.1.0
Cargo crate: cxcloud-human-simulator-0.1.0
+82
View File
@@ -0,0 +1,82 @@
use serde::Serialize;
use tracing::info;
use crate::policy::{self, Policy, Violation};
#[derive(Debug, Serialize)]
pub struct EvaluationResult {
pub decision: String,
pub reason: String,
pub violations: Vec<Violation>,
}
pub struct PolicyEngine {
policies: Vec<Policy>,
}
impl PolicyEngine {
pub fn load_defaults() -> Self {
let policies = policy::default_policies();
info!(count = policies.len(), "Loaded policies");
Self { policies }
}
pub fn policy_count(&self) -> usize {
self.policies.len()
}
pub fn list_policies(&self) -> Vec<serde_json::Value> {
self.policies
.iter()
.map(|p| {
serde_json::json!({
"id": p.id,
"name": p.name,
"description": p.description,
"enabled": p.enabled,
"max_severity": 0,
})
})
.collect()
}
/// Evaluate a plan against all policies.
pub fn evaluate(&self, plan: &serde_json::Value) -> EvaluationResult {
let mut all_violations = Vec::new();
for policy in &self.policies {
let violations = policy.evaluate(plan);
all_violations.extend(violations);
}
let has_critical = all_violations
.iter()
.any(|v| v.severity == "critical");
if has_critical {
EvaluationResult {
decision: "REJECTED".to_string(),
reason: format!(
"Plan rejected: {} critical violation(s)",
all_violations.iter().filter(|v| v.severity == "critical").count()
),
violations: all_violations,
}
} else if !all_violations.is_empty() {
EvaluationResult {
decision: "MODIFIED".to_string(),
reason: format!(
"Plan modified: {} warning(s)",
all_violations.len()
),
violations: all_violations,
}
} else {
EvaluationResult {
decision: "APPROVED".to_string(),
reason: "No policy violations found".to_string(),
violations: vec![],
}
}
}
}
+130
View File
@@ -0,0 +1,130 @@
use std::sync::Arc;
use tonic::{Request, Response, Status};
use tracing::info;
use cxcloud_proto::human_simulator::{
human_simulator_service_server::{HumanSimulatorService, HumanSimulatorServiceServer},
ApprovalRequest, ApprovalResponse, ListPoliciesRequest, ListPoliciesResponse,
PolicyInfo,
};
use crate::AppState;
pub struct HumanSimulatorServiceImpl {
state: Arc<AppState>,
}
#[tonic::async_trait]
impl HumanSimulatorService for HumanSimulatorServiceImpl {
async fn request_approval(
&self,
request: Request<ApprovalRequest>,
) -> Result<Response<ApprovalResponse>, Status> {
let req = request.into_inner();
info!(request_id = %req.request_id, "Approval request received via gRPC");
let plan_json = req
.plan
.map(|p| {
serde_json::json!({
"id": p.id,
"event_id": p.event_id,
"goal": p.goal,
"attempt": p.attempt,
"context": p.context,
"reasoning": p.reasoning.iter().map(|step| {
serde_json::json!({
"step_number": step.step_number,
"thought": step.thought,
"observation": step.observation,
"decision": step.decision,
})
}).collect::<Vec<_>>(),
"tool_calls": p.tool_calls.iter().map(|call| {
serde_json::json!({
"id": call.id,
"tool_name": call.tool_name,
"depends_on": call.depends_on,
"priority": call.priority,
"max_retries": call.max_retries,
"timeout_seconds": call.timeout_seconds,
"parameters": format!("{:?}", call.parameters),
})
}).collect::<Vec<_>>(),
})
})
.unwrap_or(serde_json::Value::Null);
let result = self.state.engine.evaluate(&plan_json);
let decision = match result.decision.as_str() {
"APPROVED" => 1,
"MODIFIED" => 2,
"REJECTED" => 3,
"ESCALATED" => 4,
_ => 0,
};
Ok(Response::new(ApprovalResponse {
request_id: req.request_id,
decision,
reason: result.reason,
modified_plan: None,
violations: result
.violations
.iter()
.map(|v| cxcloud_proto::human_simulator::PolicyViolation {
policy_id: v.policy_id.clone(),
policy_name: v.policy_name.clone(),
description: v.description.clone(),
severity: match v.severity.as_str() {
"info" => 1,
"warning" => 2,
"critical" => 3,
"blocking" => 4,
_ => 0,
},
tool_call_id: String::new(),
})
.collect(),
decided_at: None,
evaluation_ms: 0,
}))
}
async fn list_policies(
&self,
_request: Request<ListPoliciesRequest>,
) -> Result<Response<ListPoliciesResponse>, Status> {
let policies: Vec<PolicyInfo> = self
.state
.engine
.list_policies()
.iter()
.map(|p| PolicyInfo {
id: p.get("id").and_then(|v| v.as_str()).unwrap_or("").to_string(),
name: p.get("name").and_then(|v| v.as_str()).unwrap_or("").to_string(),
description: p.get("description").and_then(|v| v.as_str()).unwrap_or("").to_string(),
enabled: p.get("enabled").and_then(|v| v.as_bool()).unwrap_or(false),
max_severity: p.get("max_severity").and_then(|v| v.as_i64()).unwrap_or(0) as i32,
})
.collect();
Ok(Response::new(ListPoliciesResponse { policies }))
}
}
pub async fn serve(state: Arc<AppState>, port: u16) -> anyhow::Result<()> {
let addr = format!("0.0.0.0:{port}").parse()?;
let service = HumanSimulatorServiceImpl { state };
info!(port, "Human Simulator gRPC server starting");
tonic::transport::Server::builder()
.add_service(HumanSimulatorServiceServer::new(service))
.serve(addr)
.await?;
Ok(())
}
+116
View File
@@ -0,0 +1,116 @@
mod engine;
mod grpc_server;
mod policy;
use axum::{
extract::State,
http::StatusCode,
response::Json,
routing::{get, post},
Router,
};
use std::sync::Arc;
use tracing::info;
use cxcloud_common::{
config::{env_or, env_port},
health::HealthResponse,
telemetry,
};
#[derive(Clone)]
pub struct AppState {
pub engine: Arc<engine::PolicyEngine>,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let otel_endpoint = env_or("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317");
let log_level = env_or("LOG_LEVEL", "info");
telemetry::init("human-simulator", &otel_endpoint, &log_level);
let policy_engine = engine::PolicyEngine::load_defaults();
let state = Arc::new(AppState {
engine: Arc::new(policy_engine),
});
// Spawn gRPC server
let grpc_port = env_port("HUMAN_SIMULATOR_GRPC_PORT", 50052);
let grpc_state = state.clone();
tokio::spawn(async move {
if let Err(e) = grpc_server::serve(grpc_state, grpc_port).await {
tracing::error!(error = %e, "gRPC server failed");
}
});
let app = Router::new()
.route("/health", get(health))
.route("/ready", get(ready))
.route("/metrics", get(metrics))
.route("/approve", post(approve))
.route("/policies", get(list_policies))
.with_state(state);
let port = env_port("HUMAN_SIMULATOR_HTTP_PORT", 3002);
info!(port, grpc_port, "Human Simulator starting");
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")).await?;
axum::serve(listener, app).await?;
telemetry::shutdown();
Ok(())
}
async fn health(State(state): State<Arc<AppState>>) -> Json<HealthResponse> {
let policy_count = state.engine.policy_count();
Json(
HealthResponse::healthy("human-simulator")
.with_dependency("policies", &format!("{policy_count} loaded")),
)
}
async fn ready(State(state): State<Arc<AppState>>) -> StatusCode {
if state.engine.policy_count() > 0 {
StatusCode::OK
} else {
StatusCode::SERVICE_UNAVAILABLE
}
}
async fn metrics() -> Json<serde_json::Value> {
Json(serde_json::json!({
"service": "human-simulator",
"approvals": 0,
"rejections": 0,
"modifications": 0,
}))
}
#[derive(serde::Deserialize)]
#[allow(dead_code)]
struct ApprovalPayload {
request_id: String,
plan: serde_json::Value,
#[serde(default)]
metadata: std::collections::HashMap<String, String>,
}
async fn approve(
State(state): State<Arc<AppState>>,
Json(body): Json<ApprovalPayload>,
) -> Json<serde_json::Value> {
let result = state.engine.evaluate(&body.plan);
Json(serde_json::json!({
"request_id": body.request_id,
"decision": result.decision,
"reason": result.reason,
"violations": result.violations,
}))
}
async fn list_policies(State(state): State<Arc<AppState>>) -> Json<serde_json::Value> {
let policies = state.engine.list_policies();
Json(serde_json::json!({ "policies": policies }))
}
+124
View File
@@ -0,0 +1,124 @@
use serde::{Deserialize, Serialize};
/// A policy definition loaded from YAML.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Policy {
pub id: String,
pub name: String,
pub description: String,
pub enabled: bool,
#[serde(default)]
pub rules: Vec<PolicyRule>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolicyRule {
pub field: String,
pub operator: String, // "contains", "equals", "not_contains", "matches"
pub value: String,
pub action: String, // "reject", "warn", "modify"
#[serde(default = "default_severity")]
pub severity: String,
}
fn default_severity() -> String {
"warning".to_string()
}
impl Policy {
/// Check if a plan violates this policy.
pub fn evaluate(&self, plan: &serde_json::Value) -> Vec<Violation> {
if !self.enabled {
return vec![];
}
let plan_str = serde_json::to_string(plan).unwrap_or_default();
let mut violations = Vec::new();
for rule in &self.rules {
let matched = match rule.operator.as_str() {
"contains" => plan_str.contains(&rule.value),
"not_contains" => !plan_str.contains(&rule.value),
"equals" => {
plan.get(&rule.field)
.and_then(|v| v.as_str())
.map(|v| v == rule.value)
.unwrap_or(false)
}
_ => false,
};
if matched && rule.action == "reject" {
violations.push(Violation {
policy_id: self.id.clone(),
policy_name: self.name.clone(),
rule_field: rule.field.clone(),
severity: rule.severity.clone(),
description: format!(
"Policy '{}' violated: field '{}' {} '{}'",
self.name, rule.field, rule.operator, rule.value
),
});
}
}
violations
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Violation {
pub policy_id: String,
pub policy_name: String,
pub rule_field: String,
pub severity: String,
pub description: String,
}
/// Load default built-in policies.
pub fn default_policies() -> Vec<Policy> {
vec![
Policy {
id: "no-destructive-ops".to_string(),
name: "No Destructive Operations".to_string(),
description: "Block plans that delete or drop resources".to_string(),
enabled: true,
rules: vec![
PolicyRule {
field: "tool_calls".to_string(),
operator: "contains".to_string(),
value: "DROP TABLE".to_string(),
action: "reject".to_string(),
severity: "critical".to_string(),
},
PolicyRule {
field: "tool_calls".to_string(),
operator: "contains".to_string(),
value: "rm -rf".to_string(),
action: "reject".to_string(),
severity: "critical".to_string(),
},
],
},
Policy {
id: "no-external-network".to_string(),
name: "No External Network Calls".to_string(),
description: "Block plans that make calls to unknown external services".to_string(),
enabled: true,
rules: vec![PolicyRule {
field: "tool_calls".to_string(),
operator: "contains".to_string(),
value: "0.0.0.0".to_string(),
action: "reject".to_string(),
severity: "warning".to_string(),
}],
},
Policy {
id: "max-tool-calls".to_string(),
name: "Maximum Tool Calls".to_string(),
description: "Limit the number of tool calls per plan".to_string(),
enabled: true,
rules: vec![],
},
]
}