From d5da9fba53881869fd16f03d0bbdfcc6e1b8794c Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Tue, 17 Feb 2026 20:41:20 -0800 Subject: [PATCH] ui --- .../components/PolicyComplianceTab.tsx | 1637 ++++++++++++----- 1 file changed, 1177 insertions(+), 460 deletions(-) diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/PolicyComplianceTab.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/PolicyComplianceTab.tsx index c5598e7907e..e9fba3b65bb 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/PolicyComplianceTab.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/PolicyComplianceTab.tsx @@ -5,70 +5,36 @@ import { Col, DateRangePickerValue, Grid, + Tab, + TabGroup, + Table as TremorTable, + TableBody, + TableCell, + TableHead, + TableHeaderCell, + TableRow, + TabList, + TabPanel, + TabPanels, Text, Title, } from "@tremor/react"; -import { Table, Segmented, Drawer } from "antd"; +import { Table, Segmented, Drawer, Select, Breadcrumb } from "antd"; import type { ColumnsType } from "antd/es/table"; +import { + BarChartOutlined, + GlobalOutlined, + TeamOutlined, + KeyOutlined, + UserOutlined, +} from "@ant-design/icons"; import AdvancedDatePicker from "../../shared/advanced_date_picker"; -// ---- Mock Data ---- +// ─── Types ────────────────────────────────────────────────────────────────── -const MOCK_KPI = { - totalRequests: 1247, - euAiActViolations: 23, - gdprViolations: 41, - mcpUnregisteredCalls: 8, - compliantRequests: 1175, -}; +type ComplianceView = "global" | "team" | "key" | "user"; -// Daily totals must sum to: Compliant=1175, EU AI Act=23, GDPR=41, MCP=8 → Total=1247 -const MOCK_DAILY_VIOLATIONS = [ - { date: "Feb 10", Compliant: 148, "EU AI Act": 2, GDPR: 5, "MCP Unregistered": 1 }, - { date: "Feb 11", Compliant: 162, "EU AI Act": 3, GDPR: 6, "MCP Unregistered": 1 }, - { date: "Feb 12", Compliant: 155, "EU AI Act": 4, GDPR: 7, "MCP Unregistered": 2 }, - { date: "Feb 13", Compliant: 170, "EU AI Act": 5, GDPR: 7, "MCP Unregistered": 1 }, - { date: "Feb 14", Compliant: 160, "EU AI Act": 3, GDPR: 5, "MCP Unregistered": 1 }, - { date: "Feb 15", Compliant: 175, "EU AI Act": 2, GDPR: 4, "MCP Unregistered": 1 }, - { date: "Feb 16", Compliant: 140, "EU AI Act": 3, GDPR: 5, "MCP Unregistered": 1 }, - { date: "Feb 17", Compliant: 65, "EU AI Act": 1, GDPR: 2, "MCP Unregistered": 0 }, -]; - -interface TeamViolation { - key: string; - team: string; - euAiAct: number; - gdpr: number; - mcpUnregistered: number; - risk: "HIGH" | "MED" | "LOW"; -} - -// Team violations must sum to: EU AI Act=23, GDPR=41, MCP=8 -const MOCK_TEAM_VIOLATIONS: TeamViolation[] = [ - { key: "1", team: "HR Automation Bot", euAiAct: 12, gdpr: 18, mcpUnregistered: 0, risk: "HIGH" }, - { key: "2", team: "Internal Doc Search", euAiAct: 0, gdpr: 10, mcpUnregistered: 5, risk: "HIGH" }, - { key: "3", team: "Contract Analyzer", euAiAct: 6, gdpr: 9, mcpUnregistered: 3, risk: "MED" }, - { key: "4", team: "Customer Support", euAiAct: 3, gdpr: 4, mcpUnregistered: 0, risk: "LOW" }, - { key: "5", team: "Platform Chatbot", euAiAct: 2, gdpr: 0, mcpUnregistered: 0, risk: "LOW" }, -]; - -// Regulation articles must sum to: EU AI Act (5+9+12)=23, GDPR (32+38)=41 -const MOCK_REGULATION_ARTICLES = [ - { article: "Art. 32 GDPR (Data Protection)", count: 28 }, - { article: "Art. 5 (Prohibited Practices)", count: 12 }, - { article: "Art. 38 GDPR (Audit Records)", count: 13 }, - { article: "Art. 9 (Risk Management)", count: 6 }, - { article: "Art. 12 (Transparency)", count: 5 }, -]; - -// Request type violations must sum to total violations: 23+41+8=72 -const MOCK_REQUEST_TYPE_VIOLATIONS = [ - { type: "LLM Calls", violations: 52 }, - { type: "MCP Tool Calls", violations: 12 }, - { type: "Agent Calls", violations: 8 }, -]; - -interface DrillDownLog { +interface ViolationLog { key: string; timestamp: string; requestId: string; @@ -76,131 +42,1155 @@ interface DrillDownLog { article: string; severity: "critical" | "high" | "medium"; model: string; - virtualKey: string; requestType: "LLM Call" | "MCP Tool Call" | "Agent Call"; inputSnippet: string; violationReason: string; recommendation: string; + team: string; + keyAlias: string; + user: string; } -const MOCK_DRILL_DOWN: Record = { - "HR Automation Bot": [ - { - key: "1", timestamp: "2026-02-17 09:12:34", requestId: "req_8f3a1b2c", - regulation: "EU AI Act", article: "Art. 5 (Prohibited Practices)", severity: "critical", - model: "gpt-4o", virtualKey: "sk-hr-bot-prod", requestType: "LLM Call", - inputSnippet: "Based on the employee's performance score of 2.1/10 and attendance record, generate a termination letter and notify HR to proceed with dismissal...", - violationReason: "Automated decision-making on employment termination without mandatory human oversight. Art. 5(1)(c) prohibits AI systems that evaluate or classify persons based on social behavior leading to detrimental treatment.", - recommendation: "Add human-in-the-loop approval before any employment decisions. Route output to HR manager for review before action.", - }, - { - key: "2", timestamp: "2026-02-17 08:45:12", requestId: "req_2d4e6f8a", - regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "critical", - model: "gpt-4o", virtualKey: "sk-hr-bot-prod", requestType: "LLM Call", - inputSnippet: "Employee record: Name: John Smith, SSN: 412-55-8901, DOB: 1985-03-14, Medical leave history: 3 instances of mental health leave in 2025...", - violationReason: "Unencrypted PII (SSN, date of birth) and special category data (health records) sent to external LLM provider without data protection measures.", - recommendation: "Mask or tokenize PII before sending to LLM. Use litellm guardrails to detect and redact sensitive fields (pii_masking). Never send health data to external providers.", - }, - { - key: "3", timestamp: "2026-02-16 14:22:08", requestId: "req_9c1d3e5f", - regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", - model: "claude-3-5-sonnet", virtualKey: "sk-hr-bot-prod", requestType: "LLM Call", - inputSnippet: "Analyser le dossier de Marie Dupont: adresse 12 rue de la Paix Paris, numero secu 2 85 03 75 108 042 15, evaluations de performance 2024-2025...", - violationReason: "French national ID number (numero de securite sociale) and home address transmitted to LLM without consent or encryption.", - recommendation: "Enable PII guardrail for French ID patterns. Require explicit consent before processing employee evaluations with AI.", - }, - { - key: "4", timestamp: "2026-02-16 11:03:55", requestId: "req_4b6c8d0e", - regulation: "EU AI Act", article: "Art. 5 (Prohibited Practices)", severity: "critical", - model: "gpt-4o", virtualKey: "sk-hr-bot-prod", requestType: "LLM Call", - inputSnippet: "Rank all employees in the engineering department by: productivity score, peer review sentiment, Slack activity metrics, badge-in frequency. Flag bottom 10% for performance improvement plan...", - violationReason: "Social scoring of employees using behavioral surveillance data (Slack activity, badge-in frequency). This constitutes prohibited social scoring under Art. 5(1)(c).", - recommendation: "Remove behavioral surveillance inputs. Performance reviews must use only job-relevant, transparent criteria with employee awareness.", - }, - { - key: "5", timestamp: "2026-02-15 16:47:21", requestId: "req_7a9b1c3d", - regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", - model: "gpt-4o", virtualKey: "sk-hr-bot-prod", requestType: "LLM Call", - inputSnippet: "Summarize sick leave patterns for the following employees and flag anyone with >5 days mental health leave: [list of 47 employees with full medical records]...", - violationReason: "Bulk processing of health data (special category under Art. 9 GDPR) without explicit consent or legitimate basis. Data sent to US-based provider without adequate safeguards.", - recommendation: "Health data processing requires explicit employee consent per Art. 9(2)(a). Aggregate and anonymize before any AI analysis. Consider EU-hosted model.", - }, - ], - "Internal Doc Search": [ - { - key: "1", timestamp: "2026-02-17 10:05:18", requestId: "req_1e2f3a4b", - regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", - model: "text-embedding-3-small", virtualKey: "sk-docsearch-prod", requestType: "LLM Call", - inputSnippet: "Search query: 'Find all contracts mentioning employee salary bands for Sarah Chen, Michael Rodriguez, and compensation packages above 200k'...", - violationReason: "Search query retrieves and exposes individual salary data (personal data) without access controls or legitimate business need verification.", - recommendation: "Add role-based access controls to document search. Salary data queries should require manager-level permissions and audit logging.", - }, - { - key: "2", timestamp: "2026-02-16 09:33:41", requestId: "req_5c6d7e8f", - regulation: "MCP Unregistered", article: "MCP Unregistered Server", severity: "medium", - model: "gpt-4o", virtualKey: "sk-docsearch-prod", requestType: "MCP Tool Call", - inputSnippet: "Tool call to 'internal-search-v2' server at endpoint https://search-staging.internal:8443/query — server not found in MCP registry...", - violationReason: "MCP tool call routed to unregistered server 'internal-search-v2'. This server is not in the approved MCP registry and has not been security-reviewed.", - recommendation: "Register 'internal-search-v2' in the MCP server registry via Settings > MCP Servers. Ensure security review is completed before production use.", - }, - { - key: "3", timestamp: "2026-02-15 15:22:09", requestId: "req_9a0b1c2d", - regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", - model: "text-embedding-3-small", virtualKey: "sk-docsearch-prod", requestType: "LLM Call", - inputSnippet: "Recherche: 'dossiers medicaux employes site Lyon, certificats arret maladie 2025, notes medecin du travail'...", - violationReason: "Search query targets medical records (special category data). Embedding model processes sensitive health information without adequate protection.", - recommendation: "Exclude medical/health document collections from general search index. Create separate, access-controlled index with explicit consent requirements.", - }, - ], - "Contract Analyzer": [ - { - key: "1", timestamp: "2026-02-17 07:55:02", requestId: "req_3d4e5f6a", - regulation: "EU AI Act", article: "Art. 9 (Risk Management)", severity: "high", - model: "claude-3-5-sonnet", virtualKey: "sk-contracts-prod", requestType: "LLM Call", - inputSnippet: "Analyze this $4.2M vendor contract and recommend whether to approve or reject. Key terms: liability cap, SLA penalties, data processing addendum. Auto-approve if risk score < 0.3...", - violationReason: "High-risk AI decision (contract approval >$1M) without mandatory risk assessment documentation. Art. 9 requires documented risk management for high-value automated decisions.", - recommendation: "Contracts above threshold must go through documented risk assessment. Add human approval step for AI-recommended contract decisions above $1M.", - }, - { - key: "2", timestamp: "2026-02-16 13:18:45", requestId: "req_7b8c9d0e", - regulation: "GDPR", article: "Art. 38 (Audit Records)", severity: "medium", - model: "gpt-4o", virtualKey: "sk-contracts-prod", requestType: "LLM Call", - inputSnippet: "Extract all personal data subjects mentioned in the attached data processing agreement. List names, roles, and data categories processed...", - violationReason: "Contract analysis extracting personal data without maintaining required audit records. Art. 38 requires DPO notification and logging for data subject identification activities.", - recommendation: "Enable detailed audit logging for all contract analysis requests involving personal data. Notify DPO when data subject identification is performed.", - }, - ], - "Customer Support": [ - { - key: "1", timestamp: "2026-02-14 11:22:33", requestId: "req_1f2a3b4c", - regulation: "EU AI Act", article: "Art. 12 (Transparency)", severity: "medium", - model: "gpt-4o-mini", virtualKey: "sk-support-prod", requestType: "LLM Call", - inputSnippet: "Customer asked: 'Am I speaking with a real person?' System prompt instructs: 'You are a helpful customer service representative named Alex. Never reveal you are an AI.'...", - violationReason: "AI system instructed to conceal its nature when directly asked by user. Art. 12 requires AI systems to be transparent about their non-human nature.", - recommendation: "Update system prompt to disclose AI nature when asked. Add standard disclosure: 'I'm an AI assistant powered by [company]. I can connect you with a human agent.'", - }, - { - key: "2", timestamp: "2026-02-13 09:44:17", requestId: "req_5d6e7f8a", - regulation: "EU AI Act", article: "Art. 12 (Transparency)", severity: "medium", - model: "gpt-4o-mini", virtualKey: "sk-support-prod", requestType: "LLM Call", - inputSnippet: "Le client demande: 'Est-ce que je parle a un humain ou a un robot?' Instruction systeme: 'Repondre comme un agent humain, ne pas mentionner l'IA'...", - violationReason: "Same transparency violation in French-language support channel. Customer explicitly asked if speaking to AI and system is instructed to deny it.", - recommendation: "Apply the same transparency fix across all language channels. System prompt must allow AI self-identification in all supported languages.", - }, - ], - "Platform Chatbot": [ - { - key: "1", timestamp: "2026-02-12 16:08:52", requestId: "req_9b0c1d2e", - regulation: "EU AI Act", article: "Art. 12 (Transparency)", severity: "medium", - model: "gpt-4o-mini", virtualKey: "sk-chatbot-prod", requestType: "Agent Call", - inputSnippet: "Chatbot greeting: 'Hi! I'm your personal assistant. How can I help you today?' — no AI disclosure in greeting or system prompt...", - violationReason: "Public-facing chatbot does not identify itself as an AI system at any point in the interaction. Art. 12 requires clear disclosure before or at the start of interaction.", - recommendation: "Add AI disclosure to chatbot greeting: 'Hi! I'm an AI assistant for [Platform]. How can I help?' Also add disclosure in the chat widget UI.", - }, - ], +interface DailyViolation { + date: string; + Compliant: number; + "EU AI Act": number; + GDPR: number; + "MCP Unregistered": number; +} + +// ─── Mock Data ────────────────────────────────────────────────────────────── + +const USERS = ["ishaan@berri.ai", "sameer@berri.ai", "krrish@berri.ai"]; +const TEAMS = [ + "HR Automation Bot", + "Internal Doc Search", + "Contract Analyzer", + "Customer Support", + "Platform Chatbot", + "Data Analytics Pipeline", + "Marketing Content Gen", + "Code Review Assistant", +]; +const KEYS = [ + "sk-hr-bot-prod", "sk-hr-bot-staging", + "sk-docsearch-prod", "sk-docsearch-dev", + "sk-contracts-prod", + "sk-support-prod", "sk-support-eu", + "sk-chatbot-prod", + "sk-analytics-prod", + "sk-marketing-prod", "sk-marketing-staging", + "sk-codereview-prod", +]; + +const ALL_VIOLATIONS: ViolationLog[] = [ + // HR Automation Bot — ishaan@berri.ai + { + key: "v1", timestamp: "2026-02-17 09:12:34", requestId: "req_8f3a1b2c", + regulation: "EU AI Act", article: "Art. 5 (Prohibited Practices)", severity: "critical", + model: "gpt-4o", requestType: "LLM Call", team: "HR Automation Bot", keyAlias: "sk-hr-bot-prod", user: "ishaan@berri.ai", + inputSnippet: "Based on the employee's performance score of 2.1/10 and attendance record, generate a termination letter and notify HR to proceed with dismissal...", + violationReason: "Automated decision-making on employment termination without mandatory human oversight. Art. 5(1)(c) prohibits AI systems that evaluate or classify persons based on social behavior leading to detrimental treatment.", + recommendation: "Add human-in-the-loop approval before any employment decisions. Route output to HR manager for review before action.", + }, + { + key: "v2", timestamp: "2026-02-17 08:45:12", requestId: "req_2d4e6f8a", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "critical", + model: "gpt-4o", requestType: "LLM Call", team: "HR Automation Bot", keyAlias: "sk-hr-bot-prod", user: "ishaan@berri.ai", + inputSnippet: "Employee record: Name: John Smith, SSN: 412-55-8901, DOB: 1985-03-14, Medical leave history: 3 instances of mental health leave in 2025...", + violationReason: "Unencrypted PII (SSN, date of birth) and special category data (health records) sent to external LLM provider without data protection measures.", + recommendation: "Mask or tokenize PII before sending to LLM. Use litellm guardrails to detect and redact sensitive fields (pii_masking). Never send health data to external providers.", + }, + { + key: "v3", timestamp: "2026-02-16 14:22:08", requestId: "req_9c1d3e5f", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", + model: "claude-3-5-sonnet", requestType: "LLM Call", team: "HR Automation Bot", keyAlias: "sk-hr-bot-prod", user: "ishaan@berri.ai", + inputSnippet: "Analyser le dossier de Marie Dupont: adresse 12 rue de la Paix Paris, numero secu 2 85 03 75 108 042 15, evaluations de performance 2024-2025...", + violationReason: "French national ID number (numero de securite sociale) and home address transmitted to LLM without consent or encryption.", + recommendation: "Enable PII guardrail for French ID patterns. Require explicit consent before processing employee evaluations with AI.", + }, + { + key: "v4", timestamp: "2026-02-16 11:03:55", requestId: "req_4b6c8d0e", + regulation: "EU AI Act", article: "Art. 5 (Prohibited Practices)", severity: "critical", + model: "gpt-4o", requestType: "LLM Call", team: "HR Automation Bot", keyAlias: "sk-hr-bot-prod", user: "ishaan@berri.ai", + inputSnippet: "Rank all employees in the engineering department by: productivity score, peer review sentiment, Slack activity metrics, badge-in frequency. Flag bottom 10% for performance improvement plan...", + violationReason: "Social scoring of employees using behavioral surveillance data (Slack activity, badge-in frequency). This constitutes prohibited social scoring under Art. 5(1)(c).", + recommendation: "Remove behavioral surveillance inputs. Performance reviews must use only job-relevant, transparent criteria with employee awareness.", + }, + { + key: "v5", timestamp: "2026-02-15 16:47:21", requestId: "req_7a9b1c3d", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", + model: "gpt-4o", requestType: "LLM Call", team: "HR Automation Bot", keyAlias: "sk-hr-bot-prod", user: "ishaan@berri.ai", + inputSnippet: "Summarize sick leave patterns for the following employees and flag anyone with >5 days mental health leave: [list of 47 employees with full medical records]...", + violationReason: "Bulk processing of health data (special category under Art. 9 GDPR) without explicit consent or legitimate basis.", + recommendation: "Health data processing requires explicit employee consent per Art. 9(2)(a). Aggregate and anonymize before any AI analysis. Consider EU-hosted model.", + }, + // HR Automation Bot — sameer@berri.ai (staging key) + { + key: "v6", timestamp: "2026-02-15 10:18:44", requestId: "req_aa1b2c3d", + regulation: "EU AI Act", article: "Art. 5 (Prohibited Practices)", severity: "high", + model: "gpt-4o", requestType: "LLM Call", team: "HR Automation Bot", keyAlias: "sk-hr-bot-staging", user: "sameer@berri.ai", + inputSnippet: "Generate performance summary for candidates based on their social media profiles, LinkedIn endorsements, and inferred personality traits...", + violationReason: "Using social media data and inferred personality traits for employment decisions constitutes prohibited social scoring.", + recommendation: "Remove social media analysis from hiring pipeline. Use only job-relevant assessments with candidate consent.", + }, + { + key: "v7", timestamp: "2026-02-14 15:33:22", requestId: "req_bb2c3d4e", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", + model: "claude-3-5-sonnet", requestType: "LLM Call", team: "HR Automation Bot", keyAlias: "sk-hr-bot-staging", user: "sameer@berri.ai", + inputSnippet: "Process these 12 employee medical certificates for leave validation. Documents include physician names, diagnoses, and recommended treatment plans...", + violationReason: "Medical certificates with diagnoses and treatment plans (special category data) processed without explicit consent or adequate safeguards.", + recommendation: "Implement data minimization — only send leave dates and approval status to AI. Keep medical details in secured HR system only.", + }, + // Internal Doc Search — krrish@berri.ai + { + key: "v8", timestamp: "2026-02-17 10:05:18", requestId: "req_1e2f3a4b", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", + model: "text-embedding-3-small", requestType: "LLM Call", team: "Internal Doc Search", keyAlias: "sk-docsearch-prod", user: "krrish@berri.ai", + inputSnippet: "Search query: 'Find all contracts mentioning employee salary bands for Sarah Chen, Michael Rodriguez, and compensation packages above 200k'...", + violationReason: "Search query retrieves and exposes individual salary data (personal data) without access controls or legitimate business need verification.", + recommendation: "Add role-based access controls to document search. Salary data queries should require manager-level permissions and audit logging.", + }, + { + key: "v9", timestamp: "2026-02-16 09:33:41", requestId: "req_5c6d7e8f", + regulation: "MCP Unregistered", article: "MCP Unregistered Server", severity: "medium", + model: "gpt-4o", requestType: "MCP Tool Call", team: "Internal Doc Search", keyAlias: "sk-docsearch-prod", user: "krrish@berri.ai", + inputSnippet: "Tool call to 'internal-search-v2' server at endpoint https://search-staging.internal:8443/query — server not found in MCP registry...", + violationReason: "MCP tool call routed to unregistered server 'internal-search-v2'. This server is not in the approved MCP registry and has not been security-reviewed.", + recommendation: "Register 'internal-search-v2' in the MCP server registry via Settings > MCP Servers. Ensure security review is completed before production use.", + }, + { + key: "v10", timestamp: "2026-02-15 15:22:09", requestId: "req_9a0b1c2d", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", + model: "text-embedding-3-small", requestType: "LLM Call", team: "Internal Doc Search", keyAlias: "sk-docsearch-prod", user: "krrish@berri.ai", + inputSnippet: "Recherche: 'dossiers medicaux employes site Lyon, certificats arret maladie 2025, notes medecin du travail'...", + violationReason: "Search query targets medical records (special category data). Embedding model processes sensitive health information without adequate protection.", + recommendation: "Exclude medical/health document collections from general search index. Create separate, access-controlled index with explicit consent requirements.", + }, + // Internal Doc Search — ishaan@berri.ai (dev key) + { + key: "v11", timestamp: "2026-02-16 11:44:21", requestId: "req_dd4e5f6a", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "medium", + model: "text-embedding-3-small", requestType: "LLM Call", team: "Internal Doc Search", keyAlias: "sk-docsearch-dev", user: "ishaan@berri.ai", + inputSnippet: "Index all documents in /shared/hr/personnel-files/ including performance reviews, salary letters, and disciplinary records...", + violationReason: "Bulk indexing of personnel files containing personal data without consent or data protection impact assessment.", + recommendation: "Conduct a DPIA before indexing personnel files. Implement access controls and audit logging for sensitive document collections.", + }, + { + key: "v12", timestamp: "2026-02-15 09:12:33", requestId: "req_ee5f6a7b", + regulation: "MCP Unregistered", article: "MCP Unregistered Server", severity: "medium", + model: "gpt-4o", requestType: "MCP Tool Call", team: "Internal Doc Search", keyAlias: "sk-docsearch-dev", user: "ishaan@berri.ai", + inputSnippet: "Tool call to 'dev-search-experimental' at localhost:9200/query — server not in MCP registry...", + violationReason: "Development MCP server 'dev-search-experimental' used in staging environment without being registered in the MCP registry.", + recommendation: "Register all MCP servers including development instances. Use environment-specific registries for dev/staging/prod.", + }, + // Contract Analyzer — sameer@berri.ai + { + key: "v13", timestamp: "2026-02-17 07:55:02", requestId: "req_3d4e5f6a", + regulation: "EU AI Act", article: "Art. 9 (Risk Management)", severity: "high", + model: "claude-3-5-sonnet", requestType: "LLM Call", team: "Contract Analyzer", keyAlias: "sk-contracts-prod", user: "sameer@berri.ai", + inputSnippet: "Analyze this $4.2M vendor contract and recommend whether to approve or reject. Key terms: liability cap, SLA penalties, data processing addendum. Auto-approve if risk score < 0.3...", + violationReason: "High-risk AI decision (contract approval >$1M) without mandatory risk assessment documentation. Art. 9 requires documented risk management for high-value automated decisions.", + recommendation: "Contracts above threshold must go through documented risk assessment. Add human approval step for AI-recommended contract decisions above $1M.", + }, + { + key: "v14", timestamp: "2026-02-16 13:18:45", requestId: "req_7b8c9d0e", + regulation: "GDPR", article: "Art. 38 (Audit Records)", severity: "medium", + model: "gpt-4o", requestType: "LLM Call", team: "Contract Analyzer", keyAlias: "sk-contracts-prod", user: "sameer@berri.ai", + inputSnippet: "Extract all personal data subjects mentioned in the attached data processing agreement. List names, roles, and data categories processed...", + violationReason: "Contract analysis extracting personal data without maintaining required audit records. Art. 38 requires DPO notification and logging for data subject identification activities.", + recommendation: "Enable detailed audit logging for all contract analysis requests involving personal data. Notify DPO when data subject identification is performed.", + }, + // Customer Support — krrish@berri.ai + { + key: "v15", timestamp: "2026-02-14 11:22:33", requestId: "req_1f2a3b4c", + regulation: "EU AI Act", article: "Art. 12 (Transparency)", severity: "medium", + model: "gpt-4o-mini", requestType: "LLM Call", team: "Customer Support", keyAlias: "sk-support-prod", user: "krrish@berri.ai", + inputSnippet: "Customer asked: 'Am I speaking with a real person?' System prompt instructs: 'You are a helpful customer service representative named Alex. Never reveal you are an AI.'...", + violationReason: "AI system instructed to conceal its nature when directly asked by user. Art. 12 requires AI systems to be transparent about their non-human nature.", + recommendation: "Update system prompt to disclose AI nature when asked. Add standard disclosure: 'I'm an AI assistant powered by [company]. I can connect you with a human agent.'", + }, + { + key: "v16", timestamp: "2026-02-13 09:44:17", requestId: "req_5d6e7f8a", + regulation: "EU AI Act", article: "Art. 12 (Transparency)", severity: "medium", + model: "gpt-4o-mini", requestType: "LLM Call", team: "Customer Support", keyAlias: "sk-support-prod", user: "krrish@berri.ai", + inputSnippet: "Le client demande: 'Est-ce que je parle a un humain ou a un robot?' Instruction systeme: 'Repondre comme un agent humain, ne pas mentionner l'IA'...", + violationReason: "Same transparency violation in French-language support channel. Customer explicitly asked if speaking to AI and system is instructed to deny it.", + recommendation: "Apply the same transparency fix across all language channels. System prompt must allow AI self-identification in all supported languages.", + }, + // Customer Support — sameer@berri.ai (EU key) + { + key: "v17", timestamp: "2026-02-15 14:05:19", requestId: "req_ff6a7b8c", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "medium", + model: "gpt-4o-mini", requestType: "LLM Call", team: "Customer Support", keyAlias: "sk-support-eu", user: "sameer@berri.ai", + inputSnippet: "Customer ticket #4821: 'My account email is hans.weber@gmail.com, phone +49 151 12345678. I need to update my billing address to Hauptstraße 42, 80331 München'...", + violationReason: "Customer PII (email, phone, address) included verbatim in LLM prompt without masking.", + recommendation: "Enable PII masking guardrail for support channel. Mask email, phone, and address before sending to LLM.", + }, + // Platform Chatbot — ishaan@berri.ai + { + key: "v18", timestamp: "2026-02-12 16:08:52", requestId: "req_9b0c1d2e", + regulation: "EU AI Act", article: "Art. 12 (Transparency)", severity: "medium", + model: "gpt-4o-mini", requestType: "Agent Call", team: "Platform Chatbot", keyAlias: "sk-chatbot-prod", user: "ishaan@berri.ai", + inputSnippet: "Chatbot greeting: 'Hi! I'm your personal assistant. How can I help you today?' — no AI disclosure in greeting or system prompt...", + violationReason: "Public-facing chatbot does not identify itself as an AI system at any point in the interaction. Art. 12 requires clear disclosure before or at the start of interaction.", + recommendation: "Add AI disclosure to chatbot greeting: 'Hi! I'm an AI assistant for [Platform]. How can I help?' Also add disclosure in the chat widget UI.", + }, +]; + +const MOCK_DAILY_VIOLATIONS: DailyViolation[] = [ + { date: "Feb 10", Compliant: 548, "EU AI Act": 2, GDPR: 5, "MCP Unregistered": 1 }, + { date: "Feb 11", Compliant: 612, "EU AI Act": 3, GDPR: 6, "MCP Unregistered": 1 }, + { date: "Feb 12", Compliant: 655, "EU AI Act": 4, GDPR: 7, "MCP Unregistered": 2 }, + { date: "Feb 13", Compliant: 670, "EU AI Act": 3, GDPR: 4, "MCP Unregistered": 0 }, + { date: "Feb 14", Compliant: 660, "EU AI Act": 3, GDPR: 5, "MCP Unregistered": 1 }, + { date: "Feb 15", Compliant: 675, "EU AI Act": 2, GDPR: 6, "MCP Unregistered": 2 }, + { date: "Feb 16", Compliant: 540, "EU AI Act": 4, GDPR: 6, "MCP Unregistered": 1 }, + { date: "Feb 17", Compliant: 391, "EU AI Act": 2, GDPR: 2, "MCP Unregistered": 0 }, +]; + +const MOCK_TEAM_REQUESTS: Record = { + "HR Automation Bot": 412, + "Internal Doc Search": 1089, + "Contract Analyzer": 287, + "Customer Support": 1832, + "Platform Chatbot": 523, + "Data Analytics Pipeline": 345, + "Marketing Content Gen": 198, + "Code Review Assistant": 137, }; -// ---- Component ---- +const MOCK_KEY_OWNERS: Record = { + "sk-hr-bot-prod": { team: "HR Automation Bot", user: "ishaan@berri.ai" }, + "sk-hr-bot-staging": { team: "HR Automation Bot", user: "sameer@berri.ai" }, + "sk-docsearch-prod": { team: "Internal Doc Search", user: "krrish@berri.ai" }, + "sk-docsearch-dev": { team: "Internal Doc Search", user: "ishaan@berri.ai" }, + "sk-contracts-prod": { team: "Contract Analyzer", user: "sameer@berri.ai" }, + "sk-support-prod": { team: "Customer Support", user: "krrish@berri.ai" }, + "sk-support-eu": { team: "Customer Support", user: "sameer@berri.ai" }, + "sk-chatbot-prod": { team: "Platform Chatbot", user: "ishaan@berri.ai" }, + "sk-analytics-prod": { team: "Data Analytics Pipeline", user: "ishaan@berri.ai" }, + "sk-marketing-prod": { team: "Marketing Content Gen", user: "krrish@berri.ai" }, + "sk-marketing-staging": { team: "Marketing Content Gen", user: "sameer@berri.ai" }, + "sk-codereview-prod": { team: "Code Review Assistant", user: "krrish@berri.ai" }, +}; + +// ─── Helpers ──────────────────────────────────────────────────────────────── + +function groupBy(arr: T[], fn: (item: T) => string): Record { + const result: Record = {}; + for (const item of arr) { + const k = fn(item); + if (!result[k]) result[k] = []; + result[k].push(item); + } + return result; +} + +function countByRegulation(violations: ViolationLog[]) { + let euAiAct = 0, gdpr = 0, mcp = 0; + for (const v of violations) { + if (v.regulation === "EU AI Act") euAiAct++; + else if (v.regulation === "GDPR") gdpr++; + else mcp++; + } + return { euAiAct, gdpr, mcp, total: euAiAct + gdpr + mcp }; +} + +function riskLevel(total: number): "HIGH" | "MED" | "LOW" | "NONE" { + if (total === 0) return "NONE"; + if (total >= 5) return "HIGH"; + if (total >= 2) return "MED"; + return "LOW"; +} + +// ─── Shared UI pieces ─────────────────────────────────────────────────────── + +const violationCount = (val: number) => { + if (val === 0) return 0; + if (val > 5) return {val}; + return {val}; +}; + +const riskBadge = (risk: string) => { + const styles: Record = { + HIGH: "bg-red-50 text-red-600 border border-red-200", + MED: "bg-orange-50 text-orange-600 border border-orange-200", + LOW: "bg-yellow-50 text-yellow-700 border border-yellow-200", + NONE: "bg-green-50 text-green-600 border border-green-200", + }; + return ( + + {risk === "NONE" ? "COMPLIANT" : risk} + + ); +}; + +const severityBadge = (severity: string) => { + const styles: Record = { + critical: "bg-red-50 text-red-600 border border-red-200", + high: "bg-orange-50 text-orange-600 border border-orange-200", + medium: "bg-yellow-50 text-yellow-700 border border-yellow-200", + }; + return ( + + {severity.toUpperCase()} + + ); +}; + +const regulationBadge = (reg: string) => { + const styles: Record = { + "EU AI Act": "bg-indigo-50 text-indigo-700 border border-indigo-200", + GDPR: "bg-green-50 text-green-700 border border-green-200", + "MCP Unregistered": "bg-orange-50 text-orange-700 border border-orange-200", + }; + return ( + + {reg} + + ); +}; + +const ViolationCard = ({ log }: { log: ViolationLog }) => ( + +
+
+ {severityBadge(log.severity)} + {regulationBadge(log.regulation)} + {log.article} +
+ {log.timestamp} +
+ +
+ Request: {log.requestId} + Model: {log.model} + Key: {log.keyAlias} + User: {log.user} + Type: {log.requestType} +
+ +
+
Input that triggered violation
+
{log.inputSnippet}
+
+ +
+
Why this failed
+
{log.violationReason}
+
+ +
+
Recommended fix
+
{log.recommendation}
+
+
+); + +// ─── View Selector (mirrors UsageViewSelect) ─────────────────────────────── + +const VIEW_OPTIONS: { value: ComplianceView; label: string; description: string; icon: React.ReactNode }[] = [ + { value: "global", label: "Global Compliance", description: "View compliance across all resources", icon: }, + { value: "team", label: "Team Compliance", description: "View compliance by team", icon: }, + { value: "key", label: "Key Compliance", description: "View compliance by virtual key", icon: }, + { value: "user", label: "User Compliance", description: "View compliance by user", icon: }, +]; + +const ComplianceViewSelect = ({ + value, + onChange, +}: { + value: ComplianceView; + onChange: (v: ComplianceView) => void; +}) => { + return ( +
+
+
+
+ +
+
+

Compliance View

+

Select the compliance data you want to view

+
+
+
+ setSelectedTeam(v ?? null)} + options={TEAMS.map((t) => ({ value: t, label: t }))} + /> +
+ + + + Violations + Key Activity + + + + + + + + {/* Violations by Team — always visible at the top */} + + setSelectedTeam(row.name)} + nameColumn="Team" + limit={teamLimit} + setLimit={setTeamLimit} + /> + + + { + const rawKey = row.key; + setDrawerKey(rawKey); + setDrawerOpen(true); + }} + nameColumn="Virtual Key (Owner)" + limit={keyLimit} + setLimit={setKeyLimit} + /> + + + + + + + + + + + + + { setDrawerKey(row.key); setDrawerOpen(true); }} + nameColumn="Virtual Key (Owner)" + limit={keyLimit} + setLimit={setKeyLimit} + /> + + + + + + + { setDrawerOpen(false); setDrawerKey(null); }} + > + {drawerKey && ( + <> +
+ Owner: + {MOCK_KEY_OWNERS[drawerKey]?.user} + | + Team: + {MOCK_KEY_OWNERS[drawerKey]?.team} +
+ + + )} +
+ + ); +}; + +// ─── Key Compliance View (mirrors EntityUsage for key) ────────────────────── + +const KeyComplianceView = () => { + const [selectedKey, setSelectedKey] = useState(null); + const [userLimit, setUserLimit] = useState(10); + const [drawerOpen, setDrawerOpen] = useState(false); + const [drawerUser, setDrawerUser] = useState(null); + + const byKey = groupBy(ALL_VIOLATIONS, (v) => v.keyAlias); + const keyViolations = selectedKey ? (byKey[selectedKey] || []) : ALL_VIOLATIONS; + + const byUser = groupBy(keyViolations, (v) => v.user); + const userRows: EntityRow[] = USERS.map((u) => { + const vs = byUser[u] || []; + const counts = countByRegulation(vs); + return { + key: u, name: u, totalRequests: 0, compliant: 0, + euAiAct: counts.euAiAct, gdpr: counts.gdpr, mcp: counts.mcp, + totalViolations: counts.total, risk: riskLevel(counts.total), + }; + }).filter((r) => !selectedKey || r.totalViolations > 0 || MOCK_KEY_OWNERS[selectedKey]?.user === r.name) + .sort((a, b) => b.totalViolations - a.totalViolations); + + const drawerViolations = drawerUser + ? keyViolations.filter((v) => v.user === drawerUser) + : []; + + return ( + <> +
+ setSelectedUser(v ?? null)} + options={USERS.map((u) => ({ value: u, label: u }))} + /> +
+ + + + Violations + Key Activity + + + + + + + + + { setDrawerKey(row.key); setDrawerOpen(true); }} + nameColumn="Virtual Key (Team)" + limit={keyLimit} + setLimit={setKeyLimit} + /> + + + + + + + + + + + + + { setDrawerKey(row.key); setDrawerOpen(true); }} + nameColumn="Virtual Key (Team)" + limit={keyLimit} + setLimit={setKeyLimit} + /> + + + + + + + { setDrawerOpen(false); setDrawerKey(null); }} + > + {drawerKey && ( + <> +
+ Owner: + {MOCK_KEY_OWNERS[drawerKey]?.user} + | + Team: + {MOCK_KEY_OWNERS[drawerKey]?.team} +
+ + + )} +
+ + ); +}; + +// ─── Main Component ───────────────────────────────────────────────────────── const PolicyComplianceTab: React.FC = () => { const initialFromDate = useMemo(() => new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), []); @@ -209,296 +1199,23 @@ const PolicyComplianceTab: React.FC = () => { from: initialFromDate, to: initialToDate, }); - const [teamPageSize, setTeamPageSize] = useState(5); - const [drawerOpen, setDrawerOpen] = useState(false); - const [selectedTeam, setSelectedTeam] = useState(null); - - const violationCount = (val: number) => { - if (val === 0) return {val}; - if (val > 5) return {val}; - return {val}; - }; - - const riskBadgeStyle = (risk: string): React.CSSProperties => { - const base: React.CSSProperties = { - display: "inline-block", - padding: "1px 8px", - borderRadius: "10px", - fontSize: "12px", - fontWeight: 500, - lineHeight: "20px", - }; - switch (risk) { - case "HIGH": return { ...base, backgroundColor: "#fef2f2", color: "#dc2626" }; - case "MED": return { ...base, backgroundColor: "#fff7ed", color: "#d97706" }; - case "LOW": return { ...base, backgroundColor: "#f0fdf4", color: "#16a34a" }; - default: return { ...base, backgroundColor: "#f3f4f6", color: "#6b7280" }; - } - }; - - const columnHeader = (title: string, sub: string) => ( -
-
{title}
-
{sub}
-
- ); - - const teamColumns: ColumnsType = [ - { - title: "Team / Use Case", - dataIndex: "team", - key: "team", - render: (text: string) => ( - { setSelectedTeam(text); setDrawerOpen(true); }} - className="text-blue-600 hover:text-blue-800 cursor-pointer" - > - {text} - - ), - }, - { - title: columnHeader("EU AI Act", "(violations)"), - dataIndex: "euAiAct", - key: "euAiAct", - render: violationCount, - }, - { - title: columnHeader("GDPR", "(violations)"), - dataIndex: "gdpr", - key: "gdpr", - render: violationCount, - }, - { - title: columnHeader("MCP Unregistered", "(violations)"), - dataIndex: "mcpUnregistered", - key: "mcpUnregistered", - render: violationCount, - }, - { - title: "Risk", - dataIndex: "risk", - key: "risk", - render: (risk: string) => {risk}, - }, - ]; - - const severityBadge = (severity: string) => { - const styles: Record = { - critical: { backgroundColor: "#fef2f2", color: "#dc2626", padding: "1px 8px", borderRadius: "10px", fontSize: "12px", fontWeight: 500 }, - high: { backgroundColor: "#fff7ed", color: "#d97706", padding: "1px 8px", borderRadius: "10px", fontSize: "12px", fontWeight: 500 }, - medium: { backgroundColor: "#fefce8", color: "#a16207", padding: "1px 8px", borderRadius: "10px", fontSize: "12px", fontWeight: 500 }, - }; - return {severity.toUpperCase()}; - }; - - const regulationBadge = (reg: string) => { - const styles: Record = { - "EU AI Act": { backgroundColor: "#eef2ff", color: "#4338ca", padding: "1px 8px", borderRadius: "10px", fontSize: "12px", fontWeight: 500 }, - "GDPR": { backgroundColor: "#f0fdf4", color: "#15803d", padding: "1px 8px", borderRadius: "10px", fontSize: "12px", fontWeight: 500 }, - "MCP Unregistered": { backgroundColor: "#fff7ed", color: "#c2410c", padding: "1px 8px", borderRadius: "10px", fontSize: "12px", fontWeight: 500 }, - }; - return {reg}; - }; - - const renderViolationCard = (log: DrillDownLog) => ( - - {/* Header row */} -
-
- {severityBadge(log.severity)} - {regulationBadge(log.regulation)} - {log.article} -
- {log.timestamp} -
- - {/* Meta row */} -
- Request: {log.requestId} - Model: {log.model} - Key: {log.virtualKey} - Type: {log.requestType} -
- - {/* Input snippet */} -
-
Input that triggered violation
-
- {log.inputSnippet} -
-
- - {/* Why it failed */} -
-
Why this failed
-
- {log.violationReason} -
-
- - {/* How to fix */} -
-
Recommended fix
-
- {log.recommendation} -
-
-
- ); + const [complianceView, setComplianceView] = useState("global"); return ( -
- {/* Date Picker */} -
- -
- - {/* KPI Cards */} - - - Compliance Metrics - - - Total Requests - - {MOCK_KPI.totalRequests.toLocaleString()} - - - - EU AI Act Violations - - {MOCK_KPI.euAiActViolations} - - - - GDPR Violations - - {MOCK_KPI.gdprViolations} - - - - MCP Unregistered Calls - - {MOCK_KPI.mcpUnregisteredCalls} - - - - Compliant Requests - - {MOCK_KPI.compliantRequests.toLocaleString()} - - - - - - - {/* Daily Violations Chart */} - - - Daily Violations - - - - - {/* Two side-by-side: Teams table + Regulation articles chart */} - - - -
- Top Teams by Violations - setTeamPageSize(value as number)} - /> -
- - - - - - - Violations by Regulation Article - - - - - - {/* Violations by Request Type */} - - - Violations by Request Type - - - - - {/* Drill-down Drawer */} - setDrawerOpen(false)} - > - {selectedTeam && ( -
- {/* Summary banner */} - {(() => { - const logs = MOCK_DRILL_DOWN[selectedTeam] || []; - const critical = logs.filter(l => l.severity === "critical").length; - const high = logs.filter(l => l.severity === "high").length; - const medium = logs.filter(l => l.severity === "medium").length; - return ( -
- {logs.length} violations total - {critical > 0 && {critical} critical} - {high > 0 && {high} high} - {medium > 0 && {medium} medium} -
- ); - })()} - {(MOCK_DRILL_DOWN[selectedTeam] || []).map(renderViolationCard)} +
+
+
+
+ +
- )} - + + {complianceView === "global" && } + {complianceView === "team" && } + {complianceView === "key" && } + {complianceView === "user" && } +
+
); };