summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-31 22:51:47 +0000
committersoryu <soryu@soryu.co>2026-01-31 22:51:47 +0000
commitc7336735050be3722d71a178dcbd180641043b72 (patch)
tree00a4018f374162d38e27f99174b432853d0a23db
parent13a92411b6710da18952e5f5bf4043d0521da38b (diff)
downloadsoryu-c7336735050be3722d71a178dcbd180641043b72.tar.gz
soryu-c7336735050be3722d71a178dcbd180641043b72.zip
[WIP] Heartbeat checkpoint - 2026-01-31 22:51:47 UTCmakima/task-task-21748e1d-21748e1d
-rw-r--r--makima/src/daemon/api/red_team.rs108
-rw-r--r--makima/src/db/repository.rs122
-rw-r--r--makima/src/server/handlers/mesh_red_team.rs14
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