"""Per-tenant control-suppression API (Applicability-Override). GET /v1/controls/suppressions — tenant's suppressions (audit list) POST /v1/controls/suppressions — mark a control not-applicable POST /v1/controls/suppressions/{uuid}/revert — undo (kept for audit) Tenant from X-Tenant-ID. Suppressed controls are hidden from the use-case views (and future repo scans) but never deleted. NOTE: no `from __future__ import annotations` — it breaks Pydantic v2; use Optional[...].""" from typing import Any, Optional from fastapi import APIRouter, Depends from pydantic import BaseModel from sqlalchemy.orm import Session from classroom_engine.database import get_db from compliance.api._http_errors import translate_domain_errors from compliance.api.tenant_utils import get_tenant_id from compliance.services import control_suppression as svc router = APIRouter(prefix="/v1/controls/suppressions", tags=["control-suppressions"]) class SuppressRequest(BaseModel): control_uuid: str reason: Optional[str] = None actor: Optional[str] = None class RevertRequest(BaseModel): reason: Optional[str] = None actor: Optional[str] = None @router.get("") async def list_suppressions( include_reverted: bool = False, tid: str = Depends(get_tenant_id), db: Session = Depends(get_db), ) -> list[dict[str, Any]]: with translate_domain_errors(): return svc.list_suppressions(db, tid, include_reverted) @router.post("") async def create_suppression( body: SuppressRequest, tid: str = Depends(get_tenant_id), db: Session = Depends(get_db), ) -> dict[str, Any]: with translate_domain_errors(): return svc.suppress(db, tid, body.control_uuid, body.reason, body.actor) @router.post("/{control_uuid}/revert") async def revert_suppression( control_uuid: str, body: RevertRequest = RevertRequest(), tid: str = Depends(get_tenant_id), db: Session = Depends(get_db), ) -> dict[str, Any]: with translate_domain_errors(): ok = svc.revert(db, tid, control_uuid, body.actor, body.reason) return {"reverted": ok, "control_uuid": control_uuid}