"""Admin Reconciliation Center router — /admin/reconciliation. Single aggregate endpoint feeds the three-tab forensic page. RBAC mirrors PR-A's health console (``require_admin_read`` so auditor + management can scan + drill, write actions live on the per-resource admin pages each row deep-links to). """ from __future__ import annotations from fastapi import APIRouter, Depends, Request from sqlalchemy.ext.asyncio import AsyncSession from app.auth.models import AuthUser from app.core.database import get_db from app.core.dependencies import require_admin_read from app.core.response import success_response from app.domains.reconciliation.service import ( cost_drift, get_reconciliation_overview, leaver_residual, ownership_integrity, provisioning_gap, seat_reconciliation, ) router = APIRouter( prefix="/api/v1/admin/reconciliation", tags=["admin-reconciliation"], ) @router.get( "/overview", summary="Three-lens reconciliation (seat / ownership / leaver) in one round-trip (PR-E)", ) async def overview( request: Request, current_user: AuthUser = Depends(require_admin_read()), db: AsyncSession = Depends(get_db), ): data = await get_reconciliation_overview(db) return success_response(data=data, request=request) @router.get("/seat", summary="Seat-only reconciliation (PR-E)") async def seat( request: Request, current_user: AuthUser = Depends(require_admin_read()), db: AsyncSession = Depends(get_db), ): data = await seat_reconciliation(db) return success_response(data=data, request=request) @router.get("/ownership", summary="Ownership-FK-only reconciliation (PR-E)") async def ownership( request: Request, current_user: AuthUser = Depends(require_admin_read()), db: AsyncSession = Depends(get_db), ): data = await ownership_integrity(db) return success_response(data=data, request=request) @router.get( "/provisioning-gap", summary="Access granted in Helix but never delivered to the vendor", ) async def provisioning_gap_lens( request: Request, current_user: AuthUser = Depends(require_admin_read()), db: AsyncSession = Depends(get_db), ): """The lens that would have caught the 2026-08-06 Zoom-seat incident. Surfaces four buckets that are otherwise invisible: overdue vendor calls (respecting the ``not_before`` defer gate offboarding uses), failed vendor calls, Helix-originated seats whose grant never landed, and approvals still ``pending`` here against an already-terminal vendor snapshot. """ data = await provisioning_gap(db) return success_response(data=data, request=request) @router.get("/leaver", summary="Leaver-residual-only reconciliation (PR-E)") async def leaver( request: Request, current_user: AuthUser = Depends(require_admin_read()), db: AsyncSession = Depends(get_db), ): data = await leaver_residual(db) return success_response(data=data, request=request) @router.get( "/cost", summary="Cost-drift-only reconciliation — rollup↔live recompute per closed month", ) async def cost( request: Request, current_user: AuthUser = Depends(require_admin_read()), db: AsyncSession = Depends(get_db), ): data = await cost_drift(db) return success_response(data=data, request=request)