"""Exec approval for Synology Chat channel. Provides approval workflow for privileged operations in the Synology Chat context. """ from __future__ import annotations import uuid from typing import Any from yuxi.utils.logging_config import logger class ApprovalManager: def __init__(self, config: dict[str, Any]): self._config = config self._approvers: list[str] = config.get("security", {}).get("approvers", []) self._pending_approvals: dict[str, dict[str, Any]] = {} def get_approvers(self) -> list[str]: return list(self._approvers) def is_approver(self, user_id: str) -> bool: return user_id in self._approvers def request_approval( self, action: str, requester_id: str, details: dict[str, Any] | None = None, ) -> dict[str, Any]: request_id = f"approval:{action}:{requester_id}:{uuid.uuid4().hex[:12]}" self._pending_approvals[request_id] = { "request_id": request_id, "action": action, "requester_id": requester_id, "status": "pending", "details": details or {}, } logger.info(f"[SynologyChat] Approval requested: {action} by {requester_id}") return {"request_id": request_id, "status": "pending"} def approve(self, request_id: str, approver_id: str) -> dict[str, Any]: if not self.is_approver(approver_id): return {"status": "error", "message": f"User {approver_id} is not an approver"} if request_id not in self._pending_approvals: return {"status": "error", "message": f"Approval request {request_id} not found"} self._pending_approvals[request_id]["status"] = "approved" self._pending_approvals[request_id]["approved_by"] = approver_id logger.info(f"[SynologyChat] Approval {request_id} approved by {approver_id}") return {"request_id": request_id, "status": "approved"} def deny(self, request_id: str, approver_id: str, reason: str = "") -> dict[str, Any]: if not self.is_approver(approver_id): return {"status": "error", "message": f"User {approver_id} is not an approver"} if request_id not in self._pending_approvals: return {"status": "error", "message": f"Approval request {request_id} not found"} self._pending_approvals[request_id]["status"] = "denied" self._pending_approvals[request_id]["denied_by"] = approver_id self._pending_approvals[request_id]["deny_reason"] = reason logger.info(f"[SynologyChat] Approval {request_id} denied by {approver_id}: {reason}") return {"request_id": request_id, "status": "denied"} def get_pending(self) -> list[dict[str, Any]]: return [r for r in self._pending_approvals.values() if r["status"] == "pending"]