Files
microdao-daarion/services/city-service/routes_governance.py
Apple e078a24540 feat(city-service): add Governance, Audit, Incidents API endpoints
- Added repo_governance.py with database operations
- Added routes_governance.py (/api/v1/governance/*)
- Added routes_audit.py (/api/v1/audit/*)
- Added routes_incidents.py (/api/v1/incidents/*)
- Updated main.py to include new routers
2025-11-29 17:01:58 -08:00

278 lines
8.2 KiB
Python

"""
Governance API Routes для DAARION City Service
/api/v1/governance/*
"""
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel, Field
from typing import Optional, List
import logging
import repo_governance as repo
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/v1/governance", tags=["governance"])
# =============================================================================
# Pydantic Models
# =============================================================================
class AgentSummary(BaseModel):
id: str
display_name: str
kind: Optional[str] = None
avatar_url: Optional[str] = None
status: Optional[str] = None
gov_level: Optional[str] = None
node_id: Optional[str] = None
role: Optional[str] = None
is_core: Optional[bool] = None
class AgentRolesResponse(BaseModel):
agent: dict
assignments: List[dict]
permissions: List[dict]
class PromoteRequest(BaseModel):
agent_id: str
new_level: str
actor_id: str = "dais-demo-user"
class RevokeRequest(BaseModel):
agent_id: str
reason: str
revocation_type: str = "soft" # soft or hard
actor_id: str = "dais-demo-user"
class SuspendRequest(BaseModel):
agent_id: str
reason: str
actor_id: str = "dais-demo-user"
class ReinstateRequest(BaseModel):
agent_id: str
actor_id: str = "dais-demo-user"
class CheckPermissionRequest(BaseModel):
agent_id: str
action: str
target: str
scope_type: Optional[str] = None
scope_id: Optional[str] = None
class CheckPermissionResponse(BaseModel):
allowed: bool
reason: str
# =============================================================================
# Routes
# =============================================================================
@router.get("/agents/city", response_model=List[AgentSummary])
async def get_city_governance_agents():
"""
Отримати City Governance агентів (DAARWIZZ, DARIO, DARIA)
"""
try:
agents = await repo.get_city_governance_agents()
return agents
except Exception as e:
logger.error(f"Error fetching city governance agents: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/agents/district/{district_id}", response_model=List[AgentSummary])
async def get_district_agents(district_id: str):
"""
Отримати агентів дистрикту (District Lead + core-team)
"""
try:
agents = await repo.get_district_agents(district_id)
return agents
except Exception as e:
logger.error(f"Error fetching district agents: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/agents/microdao/{microdao_id}", response_model=List[AgentSummary])
async def get_microdao_agents(microdao_id: str):
"""
Отримати агентів MicroDAO (Orchestrator + workers)
"""
try:
agents = await repo.get_microdao_agents(microdao_id)
return agents
except Exception as e:
logger.error(f"Error fetching microdao agents: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/agents/by-level/{level}", response_model=List[AgentSummary])
async def get_agents_by_level(level: str):
"""
Отримати агентів за рівнем gov_level
Рівні: guest, personal, member, worker, core_team, orchestrator, district_lead, city_governance
"""
try:
agents = await repo.get_agents_by_level(level)
return agents
except Exception as e:
logger.error(f"Error fetching agents by level: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/agent/{agent_id}/roles", response_model=AgentRolesResponse)
async def get_agent_roles(agent_id: str):
"""
Отримати ролі та повноваження агента
"""
try:
result = await repo.get_agent_roles(agent_id)
if "error" in result:
raise HTTPException(status_code=404, detail=result["error"])
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Error fetching agent roles: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/agent/promote")
async def promote_agent(request: PromoteRequest):
"""
Підвищити агента до нового рівня
"""
try:
result = await repo.promote_agent(
agent_id=request.agent_id,
new_level=request.new_level,
actor_id=request.actor_id
)
if not result:
raise HTTPException(status_code=404, detail="Agent not found")
return {"success": True, "agent": result}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error promoting agent: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/agent/demote")
async def demote_agent(request: PromoteRequest):
"""
Понизити рівень агента
"""
try:
result = await repo.demote_agent(
agent_id=request.agent_id,
new_level=request.new_level,
actor_id=request.actor_id
)
if not result:
raise HTTPException(status_code=404, detail="Agent not found")
return {"success": True, "agent": result}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error demoting agent: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/agent/revoke")
async def revoke_agent(request: RevokeRequest):
"""
Відкликати агента (soft/hard)
- soft: тимчасове призупинення
- hard: повне відкликання з блокуванням DAIS ключів
"""
try:
result = await repo.revoke_agent(
agent_id=request.agent_id,
actor_id=request.actor_id,
reason=request.reason,
revocation_type=request.revocation_type
)
if not result:
raise HTTPException(status_code=404, detail="Agent not found")
return {"success": True, "agent": result}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error revoking agent: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/agent/suspend")
async def suspend_agent(request: SuspendRequest):
"""
Тимчасово призупинити агента
"""
try:
result = await repo.suspend_agent(
agent_id=request.agent_id,
actor_id=request.actor_id,
reason=request.reason
)
if not result:
raise HTTPException(status_code=404, detail="Agent not found")
return {"success": True, "agent": result}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error suspending agent: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/agent/reinstate")
async def reinstate_agent(request: ReinstateRequest):
"""
Відновити призупиненого агента
"""
try:
result = await repo.reinstate_agent(
agent_id=request.agent_id,
actor_id=request.actor_id
)
if not result:
raise HTTPException(status_code=404, detail="Agent not found")
return {"success": True, "agent": result}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error reinstating agent: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/check", response_model=CheckPermissionResponse)
async def check_permission(request: CheckPermissionRequest):
"""
Перевірити чи має агент право на дію
"""
try:
result = await repo.check_permission(
agent_id=request.agent_id,
action=request.action,
target=request.target,
scope_type=request.scope_type,
scope_id=request.scope_id
)
return result
except Exception as e:
logger.error(f"Error checking permission: {e}")
raise HTTPException(status_code=500, detail=str(e))