//! Per-workspace default placement node for new agent sandboxes. use cm_domain::{AgentId, WorkspaceId}; use sqlx::{PgPool, Row}; use crate::DbError; /// The placement node for the workspace that owns `agent_id` (None ⇒ local). pub async fn for_agent(pool: &PgPool, agent_id: AgentId) -> Result, DbError> { let row = sqlx::query( "SELECT p.node_id FROM workspace_placement p JOIN agents a ON a.workspace_id = p.workspace_id WHERE a.id = $1", ) .bind(agent_id.as_uuid()) .fetch_optional(pool) .await?; Ok(row.map(|r| r.get("node_id"))) } /// A workspace's placement node (None ⇒ local). pub async fn get(pool: &PgPool, workspace_id: WorkspaceId) -> Result, DbError> { let row = sqlx::query("SELECT node_id FROM workspace_placement WHERE workspace_id = $1") .bind(workspace_id.as_uuid()) .fetch_optional(pool) .await?; Ok(row.map(|r| r.get("node_id"))) } /// Set the workspace's placement node. pub async fn set(pool: &PgPool, workspace_id: WorkspaceId, node_id: &str) -> Result<(), DbError> { sqlx::query( "INSERT INTO workspace_placement (workspace_id, node_id, updated_at) VALUES ($1, $2, now()) ON CONFLICT (workspace_id) DO UPDATE SET node_id = excluded.node_id, updated_at = now()", ) .bind(workspace_id.as_uuid()) .bind(node_id) .execute(pool) .await?; Ok(()) } /// Clear the workspace's placement (revert to local). pub async fn clear(pool: &PgPool, workspace_id: WorkspaceId) -> Result<(), DbError> { sqlx::query("DELETE FROM workspace_placement WHERE workspace_id = $1") .bind(workspace_id.as_uuid()) .execute(pool) .await?; Ok(()) }