Architecture

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

  1. User visits any dashboard page
  2. Layout checks for session via NextAuth
  3. If no session, redirect to /login
  4. User authenticates via Google OAuth, Microsoft 365, or local credentials
  5. First user to sign in is automatically assigned the ADMIN role
  6. 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 for google_workspace and microsoft_365
  • /api/cron/governance-automation and /api/cron/key-usage-rules — Hourly; review and exception renewal, ownership escalation, and key usage rule evaluation
  • /api/cron/run-report-schedules and /api/cron/proxy-health — Every 15 minutes; scheduled report delivery, and an Azure Monitor window pulled into ProxyHealthSnapshot
  • /api/cron/sensitive-scan and /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