from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sqlmodel import Session, select from ..db.session import get_session from ..db.models import Policy from ..services.policy_engine import evaluate_policy from ..services.action_registry import action_capability from ..services.audit_logger import log_event router = APIRouter(prefix='/api/policies') DECISIONS = {'allowed', 'approval_required', 'blocked'} RISKS = {'low', 'medium', 'high', 'critical'} class PolicyUpdate(BaseModel): voice: str manual: str risk: str reason: str @router.get('') def list_policies(session: Session = Depends(get_session)): return session.exec(select(Policy).order_by(Policy.action, Policy.target)).all() @router.patch('/{policy_id}') def update_policy(policy_id: str, payload: PolicyUpdate, session: Session = Depends(get_session)): policy = session.get(Policy, policy_id) if not policy: raise HTTPException(status_code=404, detail='Policy not found.') if payload.voice not in DECISIONS or payload.manual not in DECISIONS or payload.risk not in RISKS: raise HTTPException(status_code=422, detail='Invalid policy decision or risk value.') if not payload.reason.strip(): raise HTTPException(status_code=422, detail='A policy reason is required.') capability = action_capability(policy.action) if capability == 'destructive' and (payload.voice != 'blocked' or payload.manual != 'blocked'): raise HTTPException(status_code=409, detail='Destructive actions remain blocked by the platform guardrail.') before = {'voice': policy.voice, 'manual': policy.manual, 'risk': policy.risk, 'reason': policy.reason} policy.voice = payload.voice policy.manual = payload.manual policy.risk = 'critical' if capability == 'destructive' else payload.risk policy.reason = payload.reason.strip() session.add(policy) session.commit() session.refresh(policy) after = {'voice': policy.voice, 'manual': policy.manual, 'risk': policy.risk, 'reason': policy.reason} log_event( session, source='manual', identity='operator', action_id='policy.update', target=policy.id, target_type='policy', risk='medium', policy_decision='allowed', reason='Operator updated policy decisions within enforced platform guardrails.', result='success', before_state=before, after_state=after, redacted_metadata={'protectedGuardrail': capability == 'destructive', 'policyAction': policy.action}, ) return { 'id': policy.id, 'action': policy.action, 'target': policy.target, 'voice': policy.voice, 'manual': policy.manual, 'risk': policy.risk, 'reason': policy.reason, } @router.post('/evaluate') def evaluate(payload: dict, session: Session = Depends(get_session)): action = str(payload.get('action', '')).strip() target = str(payload.get('target', '')).strip() source = str(payload.get('source', 'manual')).strip() or 'manual' result = evaluate_policy(session, action=action, target=target, source=source) return { 'decision': result['decision'], 'reason': result['reason'], 'risk': result['risk'], 'policyId': result['policy_id'], }