summaryrefslogtreecommitdiff
path: root/makima/src/server/handlers/mesh_red_team.rs
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-02 02:34:50 +0000
committersoryu <soryu@soryu.co>2026-02-02 02:34:50 +0000
commit151e9d87e117b7980e6aad522ac8f3633eeca87a (patch)
treee80fb4301361b3b12e5abf8e442603db2d0622dc /makima/src/server/handlers/mesh_red_team.rs
parenta2c147ddd59f55a07b5be0c8970169726b55c876 (diff)
downloadsoryu-151e9d87e117b7980e6aad522ac8f3633eeca87a.tar.gz
soryu-151e9d87e117b7980e6aad522ac8f3633eeca87a.zip
Make makima more opinionated and structured
Diffstat (limited to 'makima/src/server/handlers/mesh_red_team.rs')
-rw-r--r--makima/src/server/handlers/mesh_red_team.rs497
1 files changed, 0 insertions, 497 deletions
diff --git a/makima/src/server/handlers/mesh_red_team.rs b/makima/src/server/handlers/mesh_red_team.rs
deleted file mode 100644
index c5af60e..0000000
--- a/makima/src/server/handlers/mesh_red_team.rs
+++ /dev/null
@@ -1,497 +0,0 @@
-//! HTTP handlers for red team mesh operations.
-//!
-//! These endpoints are used by red team tasks (via the makima CLI) to notify
-//! supervisors of potential issues and query their own status.
-
-use axum::{
- extract::State,
- http::{HeaderMap, StatusCode},
- response::IntoResponse,
- Json,
-};
-use serde::{Deserialize, Serialize};
-use utoipa::ToSchema;
-use uuid::Uuid;
-
-use crate::db::repository;
-use crate::server::handlers::mesh::{extract_auth, AuthSource};
-use crate::server::messages::ApiError;
-use crate::server::state::{DaemonCommand, SharedState};
-
-// =============================================================================
-// Request/Response Types
-// =============================================================================
-
-/// Severity level for red team notifications.
-#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
-#[serde(rename_all = "lowercase")]
-pub enum RedTeamSeverity {
- /// Informational notice - minor issue or suggestion
- Info,
- /// Warning - potential problem that should be reviewed
- Warning,
- /// Critical - serious issue requiring immediate attention
- Critical,
-}
-
-impl Default for RedTeamSeverity {
- fn default() -> Self {
- Self::Warning
- }
-}
-
-impl std::fmt::Display for RedTeamSeverity {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::Info => write!(f, "INFO"),
- Self::Warning => write!(f, "WARNING"),
- Self::Critical => write!(f, "CRITICAL"),
- }
- }
-}
-
-/// Request to notify the supervisor of a potential issue.
-#[derive(Debug, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct RedTeamNotifyRequest {
- /// The issue description/message to send to the supervisor
- pub message: String,
- /// Severity level of the issue
- #[serde(default)]
- pub severity: RedTeamSeverity,
- /// ID of the task being reviewed (optional - if not provided, assumes general contract concern)
- pub related_task_id: Option<Uuid>,
- /// File path related to the issue (optional)
- pub file_path: Option<String>,
- /// Additional context about the issue
- pub context: Option<String>,
-}
-
-/// Response from the notify endpoint.
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct RedTeamNotifyResponse {
- /// Unique ID for this notification
- pub notification_id: Uuid,
- /// Whether the notification was successfully delivered to the supervisor
- pub delivered: bool,
- /// The supervisor task ID that received the notification
- pub supervisor_task_id: Option<Uuid>,
-}
-
-/// Response from the status endpoint.
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct RedTeamStatusResponse {
- /// Contract ID being monitored
- pub contract_id: Uuid,
- /// Red team task ID
- pub red_team_task_id: Uuid,
- /// Current task status
- pub status: String,
- /// Number of notifications sent so far
- pub notifications_sent: i64,
-}
-
-/// Red team notification record stored in database.
-#[derive(Debug, Clone, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct RedTeamNotification {
- pub id: Uuid,
- pub red_team_task_id: Uuid,
- pub contract_id: Uuid,
- pub message: String,
- pub severity: String,
- pub related_task_id: Option<Uuid>,
- pub file_path: Option<String>,
- pub context: Option<String>,
- pub delivered: bool,
- pub created_at: chrono::DateTime<chrono::Utc>,
-}
-
-// =============================================================================
-// Helper Functions
-// =============================================================================
-
-/// Verify the request comes from a red team task and extract ownership info.
-///
-/// Returns (task_id, owner_id, contract_id) on success.
-async fn verify_red_team_auth(
- state: &SharedState,
- headers: &HeaderMap,
-) -> Result<(Uuid, Uuid, Uuid), (StatusCode, Json<ApiError>)> {
- let auth = extract_auth(state, headers);
-
- let task_id = match auth {
- AuthSource::ToolKey(task_id) => task_id,
- _ => {
- return Err((
- StatusCode::UNAUTHORIZED,
- Json(ApiError::new(
- "UNAUTHORIZED",
- "Red team endpoints require tool key auth",
- )),
- ));
- }
- };
-
- // Get the task to verify it's a red team task and get owner_id
- let pool = state.db_pool.as_ref().ok_or_else(|| {
- (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- })?;
-
- let task = repository::get_task(pool, task_id)
- .await
- .map_err(|e| {
- tracing::error!(error = %e, "Failed to get red team task");
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", "Failed to verify red team task")),
- )
- })?
- .ok_or_else(|| {
- (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Task not found")),
- )
- })?;
-
- // Verify task is a red team task
- // NOTE: This requires the is_red_team field to be added to the Task struct.
- // For now, we check if the task name contains "red-team" or "red_team" as a fallback.
- let is_red_team = task.name.to_lowercase().contains("red-team")
- || task.name.to_lowercase().contains("red_team")
- || task.name.to_lowercase().contains("redteam");
-
- if !is_red_team {
- return Err((
- StatusCode::FORBIDDEN,
- Json(ApiError::new(
- "NOT_RED_TEAM",
- "Only red team tasks can use these endpoints",
- )),
- ));
- }
-
- // Red team tasks must be associated with a contract
- let contract_id = task.contract_id.ok_or_else(|| {
- (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new(
- "NO_CONTRACT",
- "Red team task must be associated with a contract",
- )),
- )
- })?;
-
- Ok((task_id, task.owner_id, contract_id))
-}
-
-/// Format an alert message for the supervisor.
-///
-/// Creates a formatted alert with clear visual markers to grab attention.
-fn format_alert_message(
- severity: &RedTeamSeverity,
- message: &str,
- related_task_id: Option<Uuid>,
- file_path: Option<&str>,
- context: Option<&str>,
-) -> String {
- let severity_marker = match severity {
- RedTeamSeverity::Info => "â„šī¸",
- RedTeamSeverity::Warning => "âš ī¸",
- RedTeamSeverity::Critical => "🚨",
- };
-
- let border = match severity {
- RedTeamSeverity::Info => "─".repeat(60),
- RedTeamSeverity::Warning => "━".repeat(60),
- RedTeamSeverity::Critical => "═".repeat(60),
- };
-
- let mut alert = format!(
- r#"
-{}
-{} [RED TEAM ALERT] - {}
-{}
-
-Issue: {}
-"#,
- border, severity_marker, severity, border, message
- );
-
- if let Some(task_id) = related_task_id {
- alert.push_str(&format!("\nRelated Task: {}\n", task_id));
- }
-
- if let Some(path) = file_path {
- alert.push_str(&format!("File: {}\n", path));
- }
-
- if let Some(ctx) = context {
- alert.push_str(&format!("\nContext:\n{}\n", ctx));
- }
-
- // Add action suggestions based on severity
- let actions = match severity {
- RedTeamSeverity::Info => {
- "Suggested Actions:\n- Review when convenient\n- Consider if changes are needed"
- }
- RedTeamSeverity::Warning => {
- "Suggested Actions:\n- Review the flagged item soon\n- Check if this deviates from the contract\n- Consider pausing related work until reviewed"
- }
- RedTeamSeverity::Critical => {
- "Suggested Actions:\n- STOP related work immediately\n- Review the flagged item urgently\n- Verify compliance with contract requirements\n- Consider reverting recent changes if necessary"
- }
- };
-
- alert.push_str(&format!("\n{}\n{}\n", actions, border));
-
- alert
-}
-
-// =============================================================================
-// Handlers
-// =============================================================================
-
-/// Notify the supervisor of a potential issue.
-///
-/// POST /api/v1/mesh/red-team/notify
-///
-/// This endpoint allows red team tasks to alert supervisors about issues they've
-/// identified during code review. The notification is sent as a message to the
-/// supervisor task.
-#[utoipa::path(
- post,
- path = "/api/v1/mesh/red-team/notify",
- request_body = RedTeamNotifyRequest,
- responses(
- (status = 200, description = "Notification sent", body = RedTeamNotifyResponse),
- (status = 401, description = "Unauthorized - tool key required"),
- (status = 403, description = "Forbidden - not a red team task"),
- (status = 404, description = "Task not found"),
- (status = 503, description = "Database not available"),
- (status = 500, description = "Internal server error"),
- ),
- security(
- ("tool_key" = [])
- ),
- tag = "Mesh Red Team"
-)]
-pub async fn notify_supervisor(
- State(state): State<SharedState>,
- headers: HeaderMap,
- Json(request): Json<RedTeamNotifyRequest>,
-) -> impl IntoResponse {
- let (red_team_task_id, owner_id, contract_id) =
- match verify_red_team_auth(&state, &headers).await {
- Ok(ids) => ids,
- Err(e) => return e.into_response(),
- };
-
- let pool = state.db_pool.as_ref().unwrap();
-
- // Generate notification ID
- let notification_id = Uuid::new_v4();
-
- // Get the contract to find the supervisor task
- let contract = match repository::get_contract_for_owner(pool, contract_id, owner_id).await {
- Ok(Some(c)) => c,
- Ok(None) => {
- return (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Contract not found")),
- )
- .into_response();
- }
- Err(e) => {
- tracing::error!(error = %e, "Failed to get contract");
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", "Failed to get contract")),
- )
- .into_response();
- }
- };
-
- let supervisor_task_id = contract.supervisor_task_id;
-
- // Format the alert message
- let alert_message = format_alert_message(
- &request.severity,
- &request.message,
- request.related_task_id,
- request.file_path.as_deref(),
- request.context.as_deref(),
- );
-
- // Record the notification in the database as a history event
- let event_data = serde_json::json!({
- "notification_id": notification_id.to_string(),
- "red_team_task_id": red_team_task_id.to_string(),
- "severity": request.severity.to_string(),
- "message": request.message,
- "related_task_id": request.related_task_id.map(|id| id.to_string()),
- "file_path": request.file_path,
- "context": request.context,
- });
-
- let _ = repository::record_history_event(
- pool,
- owner_id,
- Some(contract_id),
- Some(red_team_task_id),
- "red_team_alert",
- Some(&request.severity.to_string().to_lowercase()),
- Some(&request.message),
- event_data,
- )
- .await;
-
- // Try to send the message to the supervisor
- let mut delivered = false;
- if let Some(sup_task_id) = supervisor_task_id {
- // Get the supervisor task to find its daemon
- if let Ok(Some(supervisor_task)) = repository::get_task(pool, sup_task_id).await {
- if let Some(daemon_id) = supervisor_task.daemon_id {
- // Send the alert message to the supervisor
- let cmd = DaemonCommand::SendMessage {
- task_id: sup_task_id,
- message: alert_message.clone(),
- };
-
- if let Err(e) = state.send_daemon_command(daemon_id, cmd).await {
- tracing::warn!(
- error = %e,
- supervisor_task_id = %sup_task_id,
- daemon_id = %daemon_id,
- "Failed to send red team alert to supervisor"
- );
- } else {
- delivered = true;
- tracing::info!(
- notification_id = %notification_id,
- red_team_task_id = %red_team_task_id,
- supervisor_task_id = %sup_task_id,
- severity = %request.severity,
- "Red team alert delivered to supervisor"
- );
- }
- } else {
- tracing::warn!(
- supervisor_task_id = %sup_task_id,
- "Supervisor task has no assigned daemon - alert not delivered"
- );
- }
- }
- } else {
- tracing::warn!(
- contract_id = %contract_id,
- "Contract has no supervisor task - alert not delivered"
- );
- }
-
- (
- StatusCode::OK,
- Json(RedTeamNotifyResponse {
- notification_id,
- delivered,
- supervisor_task_id,
- }),
- )
- .into_response()
-}
-
-/// Get the status of the red team task.
-///
-/// GET /api/v1/mesh/red-team/status
-///
-/// Returns information about the current red team task including the contract
-/// being monitored and notification statistics.
-#[utoipa::path(
- get,
- path = "/api/v1/mesh/red-team/status",
- responses(
- (status = 200, description = "Red team status", body = RedTeamStatusResponse),
- (status = 401, description = "Unauthorized - tool key required"),
- (status = 403, description = "Forbidden - not a red team task"),
- (status = 404, description = "Task not found"),
- (status = 503, description = "Database not available"),
- (status = 500, description = "Internal server error"),
- ),
- security(
- ("tool_key" = [])
- ),
- tag = "Mesh Red Team"
-)]
-pub async fn get_status(
- State(state): State<SharedState>,
- headers: HeaderMap,
-) -> impl IntoResponse {
- let (red_team_task_id, owner_id, contract_id) =
- match verify_red_team_auth(&state, &headers).await {
- Ok(ids) => ids,
- Err(e) => return e.into_response(),
- };
-
- let pool = state.db_pool.as_ref().unwrap();
-
- // Get the red team task status
- let task = match repository::get_task(pool, red_team_task_id).await {
- Ok(Some(t)) => t,
- Ok(None) => {
- return (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Red team task not found")),
- )
- .into_response();
- }
- Err(e) => {
- tracing::error!(error = %e, "Failed to get red team task");
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", "Failed to get task")),
- )
- .into_response();
- }
- };
-
- // Count notifications sent by this red team task
- // Query history_events for red_team_alert events from this task
- let notifications_sent = match sqlx::query_scalar::<_, i64>(
- r#"
- SELECT COUNT(*)
- FROM history_events
- WHERE owner_id = $1
- AND contract_id = $2
- AND task_id = $3
- AND event_type = 'red_team_alert'
- "#,
- )
- .bind(owner_id)
- .bind(contract_id)
- .bind(red_team_task_id)
- .fetch_one(pool)
- .await
- {
- Ok(count) => count,
- Err(e) => {
- tracing::warn!(error = %e, "Failed to count red team notifications");
- 0
- }
- };
-
- (
- StatusCode::OK,
- Json(RedTeamStatusResponse {
- contract_id,
- red_team_task_id,
- status: task.status,
- notifications_sent,
- }),
- )
- .into_response()
-}