use super::*; impl PostgresRegistry { pub async fn create_approval_request( &self, request: CreateApprovalRequest<'_>, ) -> Result<(), RegistryError> { sqlx::query( "insert into approval_requests ( id, workspace_id, agent_id, operation_id, operation_version, status, risk_level, confirmation_title, confirmation_body, request_payload_json, response_payload_json, created_at, expires_at, decided_at, decided_by_key_id, decision_note ) values ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12::timestamptz, $13::timestamptz, $14::timestamptz, $15, $16 )", ) .bind(request.approval.id.as_str()) .bind(request.approval.workspace_id.as_str()) .bind(request.approval.agent_id.as_str()) .bind(request.approval.operation_id.as_str()) .bind(to_db_version(request.approval.operation_version)) .bind(serialize_enum_text( &request.approval.status, "approval_status", )?) .bind(serialize_enum_text( &request.approval.risk_level, "approval_risk_level", )?) .bind(&request.approval.confirmation_title) .bind(&request.approval.confirmation_body) .bind(Json(&request.approval.request_payload)) .bind(request.approval.response_payload.as_ref().map(Json)) .bind(request.approval.created_at) .bind(request.approval.expires_at) .bind(request.approval.decided_at) .bind( request .approval .decided_by_key_id .as_ref() .map(PlatformApiKeyId::as_str), ) .bind(request.approval.decision_note.as_deref()) .execute(&self.pool) .await?; Ok(()) } pub async fn list_pending_approval_requests_for_agent( &self, workspace_id: &WorkspaceId, agent_id: &AgentId, ) -> Result, RegistryError> { let rows = sqlx::query( "select id, workspace_id, agent_id, operation_id, operation_version, status, risk_level, confirmation_title, confirmation_body, request_payload_json, response_payload_json, created_at, expires_at, decided_at, decided_by_key_id, decision_note from approval_requests where workspace_id = $1 and agent_id = $2 and status = 'pending' and expires_at > now() order by created_at asc", ) .bind(workspace_id.as_str()) .bind(agent_id.as_str()) .fetch_all(&self.pool) .await?; rows.into_iter().map(map_approval_request_row).collect() } pub async fn get_approval_request_for_agent( &self, workspace_id: &WorkspaceId, agent_id: &AgentId, approval_id: &ApprovalRequestId, ) -> Result, RegistryError> { let row = sqlx::query( "select id, workspace_id, agent_id, operation_id, operation_version, status, risk_level, confirmation_title, confirmation_body, request_payload_json, response_payload_json, created_at, expires_at, decided_at, decided_by_key_id, decision_note from approval_requests where workspace_id = $1 and agent_id = $2 and id = $3 limit 1", ) .bind(workspace_id.as_str()) .bind(agent_id.as_str()) .bind(approval_id.as_str()) .fetch_optional(&self.pool) .await?; row.map(map_approval_request_row).transpose() } pub async fn decide_approval_request( &self, request: DecideApprovalRequest<'_>, ) -> Result, RegistryError> { let row = sqlx::query( "update approval_requests set status = $1, response_payload_json = $2, decided_at = $3::timestamptz, decided_by_key_id = $4, decision_note = $5 where workspace_id = $6 and agent_id = $7 and id = $8 and status = 'pending' and expires_at > $3::timestamptz returning id, workspace_id, agent_id, operation_id, operation_version, status, risk_level, confirmation_title, confirmation_body, request_payload_json, response_payload_json, created_at, expires_at, decided_at, decided_by_key_id, decision_note", ) .bind(serialize_enum_text(&request.status, "approval_status")?) .bind(request.response_payload.as_ref().map(Json)) .bind(request.decided_at) .bind(request.decided_by_key_id.as_str()) .bind(request.decision_note) .bind(request.workspace_id.as_str()) .bind(request.agent_id.as_str()) .bind(request.approval_id.as_str()) .fetch_optional(&self.pool) .await?; row.map(map_approval_request_row).transpose() } } fn map_approval_request_row(row: PgRow) -> Result { Ok(ApprovalRequestRecord { approval: ApprovalRequest { id: ApprovalRequestId::new(row.get::("id")), workspace_id: WorkspaceId::new(row.get::("workspace_id")), agent_id: AgentId::new(row.get::("agent_id")), operation_id: OperationId::new(row.get::("operation_id")), operation_version: from_db_version(row.get("operation_version"), "operation_version")?, status: deserialize_enum_text(&row.get::("status"), "approval_status")?, risk_level: deserialize_enum_text( &row.get::("risk_level"), "approval_risk_level", )?, confirmation_title: row.get("confirmation_title"), confirmation_body: row.get("confirmation_body"), request_payload: row.get::("request_payload_json"), response_payload: row.get::, _>("response_payload_json"), created_at: row.get("created_at"), expires_at: row.get("expires_at"), decided_at: row.get("decided_at"), decided_by_key_id: row .get::, _>("decided_by_key_id") .map(PlatformApiKeyId::new), decision_note: row.get("decision_note"), }, }) }