"""Phase 5.11 — Compliance, Audit & Governance API.""" from __future__ import annotations from uuid import UUID from fastapi import APIRouter, Depends, status from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_db, get_pagination, require_tenant from app.core.security import get_current_user from app.models.compliance import CompliancePolicy, GovernanceRule from app.models.types import PolicyViolationSeverity, RiskLevel from app.repositories.base import TenantBaseRepository from app.services.compliance_service import AuditFramework, ComplianceEngine, GovernanceService from shared.security import CurrentUser router = APIRouter() class AuditRecordCreate(BaseModel): action: str resource_type: str resource_id: str source_module: str | None = None before_value: str | None = None after_value: str | None = None reason: str | None = None class CompliancePolicyCreate(BaseModel): code: str name: str policy_type: str rules_config: str country_code: str | None = None class ApprovalRequestCreate(BaseModel): workflow_id: UUID resource_type: str resource_id: str class RejectRequest(BaseModel): reason: str class RiskCreate(BaseModel): code: str title: str risk_category: str risk_level: RiskLevel risk_score: int = 0 description: str | None = None class CompliancePolicyRepo(TenantBaseRepository[CompliancePolicy]): model = CompliancePolicy @router.get("/audit-records") async def list_audit_records( tenant_id: UUID = Depends(require_tenant), pagination=Depends(get_pagination), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ): from app.services.compliance_service import AuditRecordRepo repo = AuditRecordRepo(db) records = await repo.list_by_tenant(tenant_id, offset=pagination.offset, limit=pagination.page_size) return [ { "id": str(r.id), "action": r.action, "resource_type": r.resource_type, "actor_user_id": r.actor_user_id, "created_at": r.created_at.isoformat(), } for r in records ] @router.post("/audit-records", status_code=status.HTTP_201_CREATED) async def create_audit_record( body: AuditRecordCreate, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(get_current_user), ): audit = AuditFramework(db) record = await audit.record( tenant_id, actor_user_id=user.user_id, action=body.action, resource_type=body.resource_type, resource_id=body.resource_id, source_module=body.source_module, before_value=body.before_value, after_value=body.after_value, reason=body.reason, ) await db.commit() return {"id": str(record.id)} @router.get("/policies") async def list_compliance_policies( tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ): rows = await CompliancePolicyRepo(db).list_by_tenant(tenant_id, limit=200) return [ { "id": str(p.id), "code": p.code, "name": p.name, "policy_type": p.policy_type, "is_active": getattr(p, "is_active", True), } for p in rows ] @router.post("/policies", status_code=status.HTTP_201_CREATED) async def create_compliance_policy( body: CompliancePolicyCreate, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ): repo = CompliancePolicyRepo(db) entity = CompliancePolicy(tenant_id=tenant_id, **body.model_dump()) await repo.add(entity) await db.commit() return {"id": str(entity.id)} @router.post("/approvals") async def request_approval( body: ApprovalRequestCreate, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(get_current_user), ): gov = GovernanceService(db) request = await gov.request_approval( tenant_id, workflow_id=body.workflow_id, resource_type=body.resource_type, resource_id=body.resource_id, requested_by=user.user_id, ) await db.commit() return {"id": str(request.id), "status": request.status.value} @router.post("/approvals/{request_id}/approve") async def approve_request( request_id: UUID, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(get_current_user), ): gov = GovernanceService(db) request = await gov.approve(tenant_id, request_id, approver_user_id=user.user_id) await db.commit() return {"id": str(request.id), "status": request.status.value} @router.post("/approvals/{request_id}/reject") async def reject_request( request_id: UUID, body: RejectRequest, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(get_current_user), ): gov = GovernanceService(db) request = await gov.reject(tenant_id, request_id, approver_user_id=user.user_id, reason=body.reason) await db.commit() return {"id": str(request.id), "status": request.status.value} @router.get("/risks") async def list_risks( tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ): from app.models.compliance import RiskRecord from sqlalchemy import select stmt = ( select(RiskRecord) .where(RiskRecord.tenant_id == tenant_id) .order_by(RiskRecord.created_at.desc()) .limit(200) ) rows = list((await db.execute(stmt)).scalars().all()) return [ { "id": str(r.id), "code": r.code, "title": r.title, "risk_category": r.risk_category, "risk_level": r.risk_level.value, "risk_score": r.risk_score, "is_resolved": r.is_resolved, } for r in rows ] @router.post("/risks", status_code=status.HTTP_201_CREATED) async def create_risk( body: RiskCreate, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ): gov = GovernanceService(db) risk = await gov.create_risk(tenant_id, **body.model_dump()) await db.commit() return {"id": str(risk.id), "risk_level": risk.risk_level.value}