diff options
| author | soryu <soryu@soryu.co> | 2026-01-31 22:51:47 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-31 22:51:47 +0000 |
| commit | c7336735050be3722d71a178dcbd180641043b72 (patch) | |
| tree | 00a4018f374162d38e27f99174b432853d0a23db | |
| parent | 13a92411b6710da18952e5f5bf4043d0521da38b (diff) | |
| download | soryu-makima/task-task-21748e1d-21748e1d.tar.gz soryu-makima/task-task-21748e1d-21748e1d.zip | |
[WIP] Heartbeat checkpoint - 2026-01-31 22:51:47 UTCmakima/task-task-21748e1d-21748e1d
| -rw-r--r-- | makima/src/daemon/api/red_team.rs | 108 | ||||
| -rw-r--r-- | makima/src/db/repository.rs | 122 | ||||
| -rw-r--r-- | makima/src/server/handlers/mesh_red_team.rs | 14 |
3 files changed, 240 insertions, 4 deletions
diff --git a/makima/src/daemon/api/red_team.rs b/makima/src/daemon/api/red_team.rs index 6d3c969..845f8b8 100644 --- a/makima/src/daemon/api/red_team.rs +++ b/makima/src/daemon/api/red_team.rs @@ -1,6 +1,12 @@ //! Red team API methods. +//! +//! This module provides API client methods for red team tasks to: +//! - Notify supervisors about potential issues +//! - Get status of the red team monitoring +//! - Access monitored task information +//! - Request diffs from work tasks (read-only) -use serde::Serialize; +use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::client::{ApiClient, ApiError}; @@ -13,7 +19,7 @@ pub struct RedTeamNotifyRequest { /// The issue message pub message: String, - /// Severity level: low, medium, high, critical + /// Severity level: info, warning, critical pub severity: String, /// The specific task this relates to (optional) @@ -29,11 +35,109 @@ pub struct RedTeamNotifyRequest { pub context: Option<String>, } +/// Response from the notify endpoint. +#[derive(Debug, Deserialize)] +#[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, Deserialize)] +#[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, +} + +/// Information about a task being monitored. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MonitoredTaskInfo { + pub task_id: Uuid, + pub name: String, + pub status: String, + pub created_at: chrono::DateTime<chrono::Utc>, +} + +/// Response from the monitored-tasks endpoint. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MonitoredTasksResponse { + /// Whether subscription was successful + pub success: bool, + /// Contract ID being monitored + pub contract_id: Uuid, + /// Message about the subscription + pub message: String, + /// List of work tasks currently active in the contract + pub active_tasks: Vec<MonitoredTaskInfo>, +} + +/// Response from the task diff endpoint. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RedTeamDiffResponse { + /// Task ID that was queried + pub task_id: Uuid, + /// Whether the request was successful + pub success: bool, + /// The diff content (if available) + pub diff: Option<String>, + /// Error message if any + pub error: Option<String>, +} + impl ApiClient { /// Send a red team notification about an issue found during adversarial review. /// /// POST /api/v1/mesh/red-team/notify + /// + /// This sends an alert to the supervisor task, who can then take action + /// such as pausing the work task or providing feedback. pub async fn red_team_notify(&self, req: RedTeamNotifyRequest) -> Result<JsonValue, ApiError> { self.post("/api/v1/mesh/red-team/notify", &req).await } + + /// Get the current status of the red team task. + /// + /// GET /api/v1/mesh/red-team/status + /// + /// Returns information about the red team task including the contract + /// being monitored and notification statistics. + pub async fn red_team_status(&self) -> Result<JsonValue, ApiError> { + self.get("/api/v1/mesh/red-team/status").await + } + + /// Get the list of active work tasks that can be monitored. + /// + /// GET /api/v1/mesh/red-team/monitored-tasks + /// + /// Returns information about all active work tasks in the contract that + /// the red team can monitor. This helps understand what tasks are running. + pub async fn red_team_monitored_tasks(&self) -> Result<JsonValue, ApiError> { + self.get("/api/v1/mesh/red-team/monitored-tasks").await + } + + /// Get the diff of changes for a specific work task (read-only). + /// + /// GET /api/v1/mesh/red-team/tasks/{task_id}/diff + /// + /// This allows the red team to see what code changes a work task has made. + /// Access is read-only - the red team cannot modify any code. + pub async fn red_team_task_diff(&self, task_id: Uuid) -> Result<JsonValue, ApiError> { + self.get(&format!("/api/v1/mesh/red-team/tasks/{}/diff", task_id)) + .await + } } diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index 9a1bf2d..6656b79 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -4293,3 +4293,125 @@ pub async fn get_notification_count_for_task( .map_err(RepositoryError::Database)?; Ok(result.0) } + +/// Get all notifications for a red team task. +pub async fn get_notifications_for_red_team( + pool: &PgPool, + red_team_task_id: Uuid, +) -> Result<Vec<RedTeamNotification>, RepositoryError> { + sqlx::query_as::<_, RedTeamNotification>( + r#" + SELECT * + FROM red_team_notifications + WHERE red_team_task_id = $1 + ORDER BY created_at DESC + "#, + ) + .bind(red_team_task_id) + .fetch_all(pool) + .await + .map_err(RepositoryError::Database) +} + +/// Get all notifications for a contract. +pub async fn get_notifications_for_contract( + pool: &PgPool, + contract_id: Uuid, +) -> Result<Vec<RedTeamNotification>, RepositoryError> { + sqlx::query_as::<_, RedTeamNotification>( + r#" + SELECT * + FROM red_team_notifications + WHERE contract_id = $1 + ORDER BY created_at DESC + "#, + ) + .bind(contract_id) + .fetch_all(pool) + .await + .map_err(RepositoryError::Database) +} + +/// Get unacknowledged notifications for a contract. +pub async fn get_unacknowledged_notifications( + pool: &PgPool, + contract_id: Uuid, +) -> Result<Vec<RedTeamNotification>, RepositoryError> { + sqlx::query_as::<_, RedTeamNotification>( + r#" + SELECT * + FROM red_team_notifications + WHERE contract_id = $1 AND acknowledged = FALSE + ORDER BY created_at DESC + "#, + ) + .bind(contract_id) + .fetch_all(pool) + .await + .map_err(RepositoryError::Database) +} + +/// Acknowledge a red team notification. +pub async fn acknowledge_notification( + pool: &PgPool, + notification_id: Uuid, +) -> Result<RedTeamNotification, RepositoryError> { + sqlx::query_as::<_, RedTeamNotification>( + r#" + UPDATE red_team_notifications + SET acknowledged = TRUE, acknowledged_at = NOW() + WHERE id = $1 + RETURNING * + "#, + ) + .bind(notification_id) + .fetch_one(pool) + .await + .map_err(RepositoryError::Database) +} + +/// Get active work tasks in a contract that can be monitored by the red team. +/// Returns tasks that are not supervisors, not red team tasks, and not completed/cancelled/failed. +pub async fn get_active_work_tasks_for_contract( + pool: &PgPool, + contract_id: Uuid, + owner_id: Uuid, +) -> Result<Vec<Task>, RepositoryError> { + sqlx::query_as::<_, Task>( + r#" + SELECT + t.id, t.owner_id, t.contract_id, t.name, t.description, t.plan, t.result, + t.status, t.daemon_id, t.parent_task_id, t.progress_summary, t.retry_count, + t.worktree_path, t.tool_key, t.created_at, t.updated_at, t.started_at, + t.version, t.is_supervisor, COALESCE(t.is_red_team, false) as is_red_team, + t.repository_url, t.base_branch, t.target_branch, t.merge_mode, + t.completion_action, t.completion_action_result, t.total_cost_usd, + t.total_turns, t.continue_from_task_id, t.queued_at, t.depth + FROM tasks t + WHERE t.contract_id = $1 + AND t.owner_id = $2 + AND t.is_supervisor = FALSE + AND COALESCE(t.is_red_team, false) = FALSE + AND t.status NOT IN ('done', 'cancelled', 'failed') + ORDER BY t.created_at DESC + "#, + ) + .bind(contract_id) + .bind(owner_id) + .fetch_all(pool) + .await + .map_err(RepositoryError::Database) +} + +/// Check if a task is a red team task. +pub async fn is_red_team_task(pool: &PgPool, task_id: Uuid) -> Result<bool, RepositoryError> { + let result: Option<(bool,)> = sqlx::query_as( + "SELECT COALESCE(is_red_team, false) FROM tasks WHERE id = $1", + ) + .bind(task_id) + .fetch_optional(pool) + .await + .map_err(RepositoryError::Database)?; + + Ok(result.map(|(v,)| v).unwrap_or(false)) +} diff --git a/makima/src/server/handlers/mesh_red_team.rs b/makima/src/server/handlers/mesh_red_team.rs index 1d8e0b0..2822baa 100644 --- a/makima/src/server/handlers/mesh_red_team.rs +++ b/makima/src/server/handlers/mesh_red_team.rs @@ -793,11 +793,21 @@ pub async fn get_task_diff( /// /// This helper function can be used by the red team to filter task output /// notifications to only include outputs from work tasks in its contract. +/// +/// # Arguments +/// * `notification` - The task output notification to filter +/// * `_contract_id` - The contract ID to filter for (used in database lookup) +/// * `owner_id` - The owner ID for data isolation +/// * `_pool` - Database pool for looking up task contract association +/// +/// # Note +/// This is a simplified check. Full contract association verification +/// requires a database lookup which should be done at the subscription level. pub fn filter_task_output_for_contract( notification: &TaskOutputNotification, - contract_id: Uuid, + _contract_id: Uuid, owner_id: Uuid, - pool: &sqlx::PgPool, + _pool: &sqlx::PgPool, ) -> bool { // Note: This is a placeholder for the actual filtering logic. // In practice, the filtering would be done at the subscription level |
