UrNammu is built on Next.js 16 with the App Router pattern, using server components by default and client components only where interactivity is required.
High-level architecture
The platform consists of two main components:
- Main App (Next.js on Vercel) — The dashboard, API routes, scheduled maintenance, and all governance features
- API Proxy (Azure Functions) — Transparent proxy for Claude, OpenAI, Azure OpenAI, Gemini, and Amazon Bedrock API calls that logs usage, enforces policy-as-code and per-agent MCP allowlists, refuses suspended or retired agents, evaluates human-review triggers against tool-call arguments (withholding the call and queueing it for review in Enforce mode), records observed tool calls, and runs dangerous-prompt and inline DLP detection
Both components share the same PostgreSQL database. Developer-tool telemetry (Claude Code,
Cowork, Cursor) arrives through an OpenTelemetry collector that posts to the app's ingestion
endpoints; prompt and code content are stripped at ingest. Ingest is idempotent — every
flattened row carries a content-derived dedupeKey, so a collector retrying a
timed-out batch does not double-write, and the response reports accepted and duplicate counts.
TELEMETRY_MAX_ROWS (default 5000) caps the rows one request may flatten to;
larger bodies are rejected with 413.
Tech stack
- Runtime: Node.js 22 or later (
engines.node >= 22) - Framework: Next.js 16 (App Router, Turbopack) + TypeScript + React 19
- Styling: Tailwind CSS 4 + Radix UI primitives + CVA
- Database: PostgreSQL via Prisma ORM
- Auth: NextAuth v4 — Google OAuth + Microsoft 365 + Local accounts
- AI: Provider-agnostic via
ai-provider.ts(Anthropic Claude, OpenAI) - Charts: Recharts
- Tables: @tanstack/react-table
App structure
All pages live under src/app/(dashboard)/ with a shared layout that provides
the sidebar navigation and top bar. The layout redirects to /login if no session exists.
src/app/
(auth)/login/ # Login page (public)
(dashboard)/ # All authenticated pages
dashboard/ # Command center
executive/ # Executive posture dashboard
reports/ # Report builder, templates, schedules
registry/ # AI system registry (list + detail + approvals + EU AI Act)
agents/ # AI agent registry: charter, approval gate, kill switch, review triggers, MCP governance
shadow-ai/ # Shadow AI discovery + triage + enforcement
oversight/endpoints/ # Endpoint agent fleet and per-device detections
sensitive-scan/ # Leakage probes + inline DLP findings
risk-center/ # Risk scoring + heat maps + assessments
oversight/ # Usage, cost, budgets, investigations, vendors,
# provider posture + security, per-surface dashboards,
# session traces, MCP activity
compliance/ # Policies, framework coverage, denials, audit trail
alerts/ # Alert inbox, prompt rules, key usage rules, exceptions
proxy-health/ # Live-ops board for the AI proxy
integrations/ # Integration hub
settings/ # App configuration
api/ # API routes for CRUD, ingestion, crons, and the proxy fallback Data model
The Prisma schema defines 60+ models. Key entities include:
- AISystem — Core entity for registered AI systems with status, risk level, owner, department, and vendor
- AIAgent — Autonomous agents linked to parent AI systems with autonomy level, HITL config, and MCP server/tool allowlists
- GovernanceReview / SystemApproval / GovernanceException / EvidenceArtifact / GovernanceIncident — Staged sign-off, top-level approval decisions, time-bound waivers, attached evidence, and incidents per system
- FrameworkControl / ControlCrosswalk / ComplianceMapping — Seeded framework control catalog, the cross-framework links that drive coverage inheritance, and per-system control ratings with evidence
- EuAiActClassification — Per-system EU AI Act tier, role, obligation flags, and applicable articles
- AgentToolCall / AgentToolProfile — MCP tool invocations observed by the proxy and their first/last-seen roll-up per agent
- RiskAssessment — Multi-dimensional risk scores per system
- Policy / PolicyAssignment / PolicyDenial — Governance policies with framework mappings, their assignments, and every request the proxy blocked or flagged
- EndpointDevice / EndpointDetection — Machines running the endpoint agent and what each observed. A device holds only the hash of its per-device token; a detection records the signal (app, browser, network, runtime), the matched tool, and the evidence — a bundle id, a bare hostname, or a runtime and port. Never prompt or page content
- DiscoveredAITool / ScanHistory — Shadow AI discoveries from scans and imports, and the scan runs that produced them. A discovery stores the user emails and OAuth scopes observed, first and last seen timestamps, and the registry category it matched
- SensitiveScan / SensitiveFinding — Leakage probe runs and redacted findings from probes and inline response DLP
- ProviderSecurityScan / ProviderSecurityResult — Provider security and privacy posture scans with per-check outcomes and grades
- VendorProfile — Contract status, security review, data residency, approved use cases, and subprocessors per vendor
- UsageBucket / CostBucket / APIUsageLog — Provider and gateway telemetry, and the proxy's per-call log (including the upstream request id that joins it to session traces). Both bucket tables carry the same attribution columns — API key, provider-side workspace, and the governed system the row resolves to — so spend can be rolled up by system and by key
- ProviderSyncRun / ProviderSyncWatermark / ProviderRawSnapshot — Each sync attempt, the per-provider watermark and earliest ingested day that drive incremental windows and backfill, and the raw upstream payloads
- AssistantDailyStat — One row per person per day (per product where the provider reports several) for Claude Code, Cursor, GitHub Copilot, Claude Enterprise, and ChatGPT Enterprise
- ComplianceActivity / ComplianceSession — Auth, admin-audit, and API-key lifecycle events from the ChatGPT Enterprise and Anthropic compliance feeds, keyed by upstream event id, and Claude app session metadata. Metadata only — message content and transcripts are never pulled
- ClaudeCodeEvent / ClaudeCodeMetric / CursorMetric / CursorSpan — Developer-tool telemetry from the OpenTelemetry pipeline, metadata only
- DirectoryPerson — People synced from Google Workspace or Microsoft Entra ID, with aliases, department, manager, and active state
- SpendBudget — Monthly budgets by provider, system, or department
- Alert / Investigation / PromptRiskRule / PromptRiskException / KeyUsageRule / ApiKeyProfile — The alert inbox, investigations, and the two tunable rule engines behind dangerous-prompt and key-usage alerts
- ReportDefinition / ReportSchedule / ReportRun — Saved reports, schedules, and delivery history
- AuditLog — Full audit trail of all mutations
- AppSetting — Runtime configuration key-value store, encrypted where sensitive
Authentication flow
- User visits any dashboard page
- Layout checks for session via NextAuth
- If no session, redirect to
/login - User authenticates via Google OAuth, Microsoft 365, or local credentials
- First user to sign in is automatically assigned the ADMIN role
- Subsequent users are VIEWER by default (admins can change roles, suspend, or delete accounts)
API routes are protected by withAuth() and withRole() wrappers
that validate session and role before executing the handler.
Background automation
There is one cron entry per background job rather than a single maintenance endpoint.
Every route authenticates with Authorization: Bearer $CRON_SECRET, and each
hourly route checks its own enable flag and interval before doing work, so the tick is
cheap when nothing is due. The full list and cadences live in vercel.json.
/api/cron/provider-sync/<provider>— One entry per telemetry provider:anthropic,claude_code,cursor,github_copilot,gemini,openai,openrouter,helicone,portkey,litellm,chatgpt_enterprise,anthropic_compliance,claude_enterprise. Staggered across the hour/api/cron/discovery-scan/<source>— One entry per shadow AI source:google_workspace,microsoft_365,hexnode,crowdstrike/api/cron/endpoint-agent-sweep— Marks endpoint agents that have stopped reporting as stale, so a coverage gap shows as “no data” rather than “no AI activity”/api/cron/directory-sync/<source>— Daily people-directory sync forgoogle_workspaceandmicrosoft_365/api/cron/governance-automationand/api/cron/key-usage-rules— Hourly; review and exception renewal, ownership escalation, and key usage rule evaluation/api/cron/run-report-schedulesand/api/cron/proxy-health— Every 15 minutes; scheduled report delivery, and an Azure Monitor window pulled intoProxyHealthSnapshot/api/cron/sensitive-scanand/api/cron/provider-security-scan— Daily leakage probes and provider posture audits/api/cron/prune-claude-code-metrics,/api/cron/prune-cursor-metrics,/api/cron/prune-collection— Daily retention enforcement
Running each provider and each scan source in its own function means a slow or failing one cannot delay the others, and each provider's schedule is tracked from its own last success.
Incremental sync and backfill
Every provider sync pulls an explicit from/to window derived from its
ProviderSyncWatermark row rather than a fixed trailing period. An overlap
(default 2 days, provider_sync_overlap_days) re-pulls the most recent days so
late corrections are captured, and the maximum lookback is per provider. The watermark only
advances after a run succeeds, and bucket writes are idempotent upserts, so overlapping
windows never double count. Deeper history is pulled from Settings → Provider
Admin APIs → Sync History & Backfill, which walks a UTC date range in
7-day chunks. When a paginated upstream read hits its page cap the run is marked truncated
and a provider_sync_truncated alert is raised.
Retention
/api/cron/prune-collection enforces settings-driven retention over the raw
collection tables: provider raw snapshots (14 days by default), the proxy request log and
agent tool calls (180), policy denials, compliance activity, and scan runs (365), and proxy
health snapshots (90). Windows are set in Settings → General → Data
Retention; 0 disables pruning for that table. Rows are deleted
oldest-first in batches of 5,000 inside a fixed time budget, so a large backlog drains over
several nights. UsageBucket and CostBucket are never pruned —
they are the long-term aggregate the raw rows roll up into, so dashboard totals do not change.
AI integration
The AI provider is configurable at runtime via Settings. The
generateAIResponse(systemPrompt, userPrompt) function in
ai-provider.ts abstracts over the Anthropic and OpenAI APIs. It reads
the configured provider from the AppSetting table and falls back to environment variables.
AI is used for:
- Risk classification — Automated risk level assessment based on system metadata
- Compliance gap analysis — Summarizing gaps between a system's control evidence and framework requirements
- Agent risk review — A structured review of an agent's autonomy, capabilities, and MCP exposure
- Shadow AI auto-fill — Drafting a registry entry when a discovered tool is converted to a governed system
- Sensitive Scan probes — Leakage probes are issued with the configured provider and model