from __future__ import annotations import logging from typing import Optional from fastapi import APIRouter, HTTPException, status from pydantic import BaseModel logger = logging.getLogger(__name__) router = APIRouter(prefix='/approval', tags=['approval']) class ApprovalRequest(BaseModel): directive_id: str approved: bool approver_id: str note: Optional[str] = None class PendingApproval(BaseModel): directive_id: str agent: str action: str description: str created_at: str context: dict = {} @router.get('/pending', response_model=list[PendingApproval]) async def list_pending(): from ..app_state import get_db_pool pool = get_db_pool() if pool is None: raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='DB not ready') async with pool.acquire(timeout=10) as conn: rows = await conn.fetch( 'SELECT directive_id, agent_name, action_type, description, created_at, context_data ' 'FROM ab_directive_log WHERE status = $1 ORDER BY created_at ASC', 'pending_approval', ) return [ PendingApproval( directive_id=str(r['directive_id']), agent=r['agent_name'] or '', action=r['action_type'] or '', description=r['description'] or '', created_at=str(r['created_at']), context=r['context_data'] or {}, ) for r in rows ] @router.post('/respond', status_code=status.HTTP_200_OK) async def respond_approval(req: ApprovalRequest): from ..app_state import get_db_pool, get_master_agent pool = get_db_pool() if pool is None: raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='DB not ready') async with pool.acquire(timeout=10) as conn: row = await conn.fetchrow( 'SELECT directive_id, status FROM ab_directive_log WHERE directive_id = $1', req.directive_id, ) if not row: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Directive not found') if row['status'] != 'pending_approval': raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=f'Directive is not pending approval (status={row["status"]})', ) new_status = 'approved' if req.approved else 'rejected' await conn.execute( 'UPDATE ab_directive_log SET status=$1, approver_id=$2, approval_note=$3, updated_at=NOW() ' 'WHERE directive_id=$4', new_status, req.approver_id, req.note, req.directive_id, ) logger.info('Directive %s %s by %s', req.directive_id, new_status, req.approver_id) if req.approved: master = get_master_agent() if master: try: await master.resume_directive(req.directive_id) except Exception as exc: logger.error('resume_directive failed %s: %s', req.directive_id, exc) return {'directive_id': req.directive_id, 'status': new_status}