""" CROVIA OMISSION ORACLE v2.1.0 The Evidence Protocol for AI Training Compliance Building the global standard for AI trust — in public, from day one. """ import json import hashlib import gradio as gr import requests from datetime import datetime, timezone from typing import Dict, List, Any, Optional VERSION = "2.1.0" PROTOCOL_VERSION = "v1.0" TPR_API_URL = "https://registry.croviatrust.com" DATASET_ID = "Crovia/global-ai-training-omissions" # Fallback cards for display FALLBACK_CARDS = [ {"model_id": "meta-llama/Llama-3.1-8B", "score": 72, "badge": "BRONZE", "violations": ["NEC#1", "NEC#7"], "author": "meta-llama", "downloads": "N/A"}, {"model_id": "mistralai/Mistral-7B-v0.3", "score": 94, "badge": "GOLD", "violations": [], "author": "mistralai", "downloads": "N/A"}, ] try: from huggingface_hub import hf_hub_download HF_AVAILABLE = True except ImportError: HF_AVAILABLE = False def load_latest_cards_from_registry() -> List[Dict[str, Any]]: """Load latest observations from live registry API with Shadow Scores.""" try: # Fetch live data from registry response = requests.get(f"{TPR_API_URL}/api/registry/recent?limit=12", timeout=5) if response.status_code == 200: data = response.json() observations = data.get('observations', []) # Get Shadow Scores from dataset (updated by cron) shadow_scores = {} try: if HF_AVAILABLE: index_path = hf_hub_download( repo_id=DATASET_ID, filename="badges/index.json", repo_type="dataset" ) with open(index_path, 'r') as f: badges_data = json.load(f) for badge in badges_data.get("badges", []): shadow_scores[badge.get("model_id")] = badge.get("score", 0) except: pass # Convert observations to card format cards = [] for obs in observations: # Skip test/internal files target_id = obs.get('target_id', '') if any(pattern in target_id.lower() for pattern in ['test-', 'crovia-', 'debug-', 'demo-']): continue # Get Shadow Score if available score = shadow_scores.get(target_id, 0) # Determine badge based on score if score >= 90: badge = "GOLD" elif score >= 70: badge = "SILVER" elif score >= 50: badge = "BRONZE" else: badge = "UNVERIFIED" cards.append({ "model_id": target_id, "score": score, "badge": badge, "violations": [], "author": target_id.split("/")[0] if "/" in target_id else "Unknown", "downloads": "N/A", "observation_type": obs.get('observation_type', 'unknown'), "observed_at": obs.get('observed_at', '') }) if cards: return cards # Fallback to dataset if API fails return load_latest_cards_from_dataset() except Exception as e: print(f"Error loading from registry: {e}") return load_latest_cards_from_dataset() def load_latest_cards_from_dataset() -> List[Dict[str, Any]]: """Load pre-computed model cards from public dataset (fallback).""" try: if not HF_AVAILABLE: return FALLBACK_CARDS index_path = hf_hub_download( repo_id=DATASET_ID, filename="badges/index.json", repo_type="dataset" ) with open(index_path, 'r') as f: data = json.load(f) cards = [] for badge in data.get("badges", [])[:12]: model_id = badge.get("model_id", "Unknown") score = badge.get("score", 0) badge_type = badge.get("badge", "UNVERIFIED") violations = [] try: card_filename = f"cards/{model_id.replace('/', '__')}.json" card_path = hf_hub_download( repo_id=DATASET_ID, filename=card_filename, repo_type="dataset" ) with open(card_path, 'r') as cf: card_data = json.load(cf) violations = card_data.get("violations", []) except: pass author = model_id.split("/")[0] if "/" in model_id else "Unknown" cards.append({ "model_id": model_id, "score": score, "badge": badge_type, "violations": violations, "author": author, "downloads": "N/A" }) return cards if cards else FALLBACK_CARDS except Exception as e: print(f"Error loading cards: {e}") return FALLBACK_CARDS # Load cards from live registry (real-time data) PRELOADED_CARDS = load_latest_cards_from_registry() NECESSITY_CANON = { "NEC#1": {"name": "Missing data provenance", "severity": 75}, "NEC#2": {"name": "Missing license attribution", "severity": 80}, "NEC#7": {"name": "Missing usage scope", "severity": 45}, "NEC#10": {"name": "Missing temporal validity", "severity": 40}, "NEC#13": {"name": "Missing accountable entity", "severity": 70}, } def analyze_model_live(model_id: str) -> Optional[Dict[str, Any]]: """Live analysis via TPR API (if available).""" if not model_id or not model_id.strip(): return None try: response = requests.post( f"{TPR_API_URL}/api/analyze", json={"model_id": model_id.strip()}, timeout=30 ) if response.status_code == 200: result = response.json() result["analyzed_via"] = "tpr-api" result["timestamp"] = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") return result else: return None except: return None def analyze_model_fallback(model_id: str) -> Optional[Dict[str, Any]]: """Fallback analysis using HuggingFace API directly.""" if not model_id or not model_id.strip(): return None model_id = model_id.strip() if not HF_AVAILABLE: return {"error": "Analysis unavailable - HuggingFace Hub not accessible"} try: from huggingface_hub import HfApi api = HfApi() info = api.model_info(model_id) card_data = info.card_data or {} license_val = card_data.get("license") or getattr(info, "license", None) datasets = card_data.get("datasets", []) or [] author = info.author or "" tags = info.tags or [] violations = [] if not datasets: violations.append("NEC#1") if not license_val: violations.append("NEC#2") has_use = any("task" in t.lower() or "use" in t.lower() for t in tags) if not has_use: violations.append("NEC#7") if not author: violations.append("NEC#13") total_severity = sum(NECESSITY_CANON.get(v, {}).get("severity", 30) for v in violations) score = max(0, min(100, 100 - int(total_severity * 0.15))) if score >= 90: badge = "GOLD" elif score >= 75: badge = "SILVER" elif score >= 60: badge = "BRONZE" else: badge = "UNVERIFIED" return { "model_id": model_id, "score": score, "badge": badge, "violations": violations, "author": author or "Unknown", "license": license_val or "Not declared", "evidence": hashlib.sha256(f"{model_id}:{score}".encode()).hexdigest()[:12], "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC"), "analyzed_via": "hf-fallback" } except Exception as e: return {"error": f"Model not found: {model_id}"} def analyze_model(model_id: str) -> Optional[Dict[str, Any]]: """Main analysis function with TPR API and fallback.""" result = analyze_model_live(model_id) if not result: result = analyze_model_fallback(model_id) return result def get_badge_color(badge: str) -> str: colors = { "GOLD": "#F59E0B", "SILVER": "#94A3B8", "BRONZE": "#D97706", "UNVERIFIED": "#EF4444" } return colors.get(badge, "#6B7280") def run_analysis(model_id: str): result = analyze_model(model_id) if not result: return "", "", "" if "error" in result: error_msg = result.get("message", result["error"]) return f'
{error_msg}
', "", "" color = get_badge_color(result["badge"]) score_html = f'''
{result["score"]} SHADOW SCORE
{result["badge"]}
''' info_html = f'''
MODEL {result["model_id"]}
Author: {result.get("author", "Unknown")}
License: {result.get("license", "Not declared")}
Evidence: {result.get("evidence", "N/A")} · {result.get("timestamp", "")}
''' if result.get("violations"): viol_items = "".join([f'{v}' for v in result["violations"]]) viol_html = f'
VIOLATIONS:{viol_items}
' else: viol_html = '
✓ ALL CHECKS PASSED
' if result.get("analyzed_via") == "hf-fallback": viol_html += '
⚠️ Basic analysis mode (TPR API unavailable)
' return score_html, info_html, viol_html def generate_cards_html(cards_data=None): if cards_data is None: cards_data = load_latest_cards_from_registry() cards = "" for c in cards_data: color = get_badge_color(c["badge"]) viols = " ".join([f'{v}' for v in c["violations"]]) if c["violations"] else 'CLEAN' cards += f'''
{c["score"]} {c["badge"]}
{c["model_id"]}
{c["author"]}
{viols}
''' return cards CSS = """ .gradio-container { max-width: 100% !important; padding: 0 !important; margin: 0 !important; background: #030305 !important; } .main, .contain, .wrap { max-width: 100% !important; padding: 0 !important; } footer { display: none !important; } .gr-form { background: transparent !important; border: none !important; } .hero { background: linear-gradient(180deg, #0a0a12 0%, #030305 100%); padding: 60px 20px 40px; text-align: center; border-bottom: 1px solid #1a1a2e; } .hero-badge { display: inline-block; background: #8B5CF620; border: 1px solid #8B5CF640; color: #8B5CF6; padding: 6px 16px; border-radius: 20px; font-size: 11px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 20px; } .hero-logo { width: 70px; height: 70px; border-radius: 16px; margin: 0 auto 20px; background: linear-gradient(135deg, #8B5CF6 0%, #6D28D9 100%); display: flex; align-items: center; justify-content: center; box-shadow: 0 0 40px #8B5CF650; } .hero-logo img { width: 50px; height: 50px; } .hero-title { font-size: 42px; font-weight: 900; color: #fff; letter-spacing: 0.15em; margin: 0 0 12px 0; text-shadow: 0 0 30px #8B5CF640; } .hero-subtitle { color: #9CA3AF; font-size: 18px; font-weight: 500; margin: 0 0 20px 0; letter-spacing: 0.02em; } .hero-desc { color: #6B7280; font-size: 15px; max-width: 700px; margin: 0 auto 30px; line-height: 1.7; } .hero-emphasis { color: #8B5CF6; font-weight: 600; } .hero-stats { display: flex; justify-content: center; gap: 40px; margin: 30px auto 0; max-width: 900px; flex-wrap: wrap; } .stat-item { text-align: center; } .stat-label { color: #6B7280; font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; font-weight: 600; } .stat-value { color: #fff; font-size: 16px; font-weight: 700; margin-top: 4px; } .stat-link { color: #8B5CF6; text-decoration: none; font-size: 16px; font-weight: 700; } .stat-link:hover { color: #A78BFA; } .nav { display: flex; justify-content: center; gap: 24px; padding: 16px; background: #050507; border-bottom: 1px solid #1a1a2e; flex-wrap: wrap; } .nav a { color: #9CA3AF; text-decoration: none; font-size: 13px; font-weight: 500; transition: color 0.2s; } .nav a:hover { color: #8B5CF6; } .search-section { max-width: 700px; margin: 40px auto; padding: 0 20px; } .search-box { display: flex; gap: 12px; } input[type="text"], textarea { flex: 1; background: #0a0a12 !important; border: 2px solid #1a1a2e !important; color: #fff !important; border-radius: 12px !important; padding: 16px 20px !important; font-size: 16px !important; } input:focus, textarea:focus { border-color: #8B5CF6 !important; box-shadow: 0 0 20px #8B5CF620 !important; outline: none !important; } button.primary { background: linear-gradient(135deg, #8B5CF6 0%, #6D28D9 100%) !important; border: none !important; padding: 16px 32px !important; border-radius: 12px !important; font-weight: 700 !important; font-size: 14px !important; letter-spacing: 0.05em !important; color: #fff !important; cursor: pointer !important; transition: all 0.2s; } button.primary:hover { transform: translateY(-2px); box-shadow: 0 10px 30px #8B5CF650 !important; } .results-section { max-width: 800px; margin: 0 auto 40px; padding: 0 20px; } .result-card { background: #0a0a12; border: 1px solid #1a1a2e; border-radius: 16px; padding: 30px; text-align: center; } .score-circle { width: 140px; height: 140px; border-radius: 50%; border: 4px solid; display: flex; flex-direction: column; align-items: center; justify-content: center; margin: 0 auto 20px; background: #050507; } .score-value { font-size: 48px; font-weight: 900; } .score-label { font-size: 10px; color: #6B7280; letter-spacing: 0.1em; } .badge-pill { display: inline-block; padding: 8px 24px; border-radius: 20px; font-weight: 700; font-size: 14px; letter-spacing: 0.1em; } .info-card { background: #0a0a12; border: 1px solid #1a1a2e; border-radius: 16px; padding: 20px; margin-top: 16px; } .info-header { margin-bottom: 12px; } .info-label { color: #8B5CF6; font-size: 10px; letter-spacing: 0.1em; display: block; } .info-value { color: #fff; font-size: 18px; font-weight: 700; } .info-row { color: #9CA3AF; font-size: 13px; margin: 8px 0; } .info-row strong { color: #fff; } .evidence { color: #4B5563; font-size: 11px; font-family: monospace; margin-top: 12px; padding-top: 12px; border-top: 1px solid #1a1a2e; } .violations-box { background: #1a0a0a; border: 1px solid #EF444440; border-radius: 12px; padding: 16px; margin-top: 16px; } .viol-label { color: #EF4444; font-size: 11px; letter-spacing: 0.1em; margin-right: 12px; } .viol-tag { background: #EF444420; color: #EF4444; padding: 4px 12px; border-radius: 6px; font-size: 12px; font-weight: 600; margin: 0 4px; } .clean-box { background: #0a1a0a; border: 1px solid #22C55E40; border-radius: 12px; padding: 16px; margin-top: 16px; color: #22C55E; text-align: center; font-weight: 600; } .note-box { background: #1a1a0a; border: 1px solid #F59E0B40; border-radius: 12px; padding: 12px; margin-top: 12px; color: #F59E0B; text-align: center; font-size: 12px; } .error-box { background: #1a0a0a; border: 1px solid #EF4444; border-radius: 12px; padding: 20px; color: #EF4444; text-align: center; } .cards-section { background: #050507; padding: 40px 20px; border-top: 1px solid #1a1a2e; } .cards-title { text-align: center; color: #fff; font-size: 24px; font-weight: 700; margin-bottom: 8px; } .cards-sub { text-align: center; color: #6B7280; font-size: 13px; margin-bottom: 30px; } .cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; max-width: 1200px; margin: 0 auto; } .model-card { background: #0a0a12; border: 1px solid #1a1a2e; border-radius: 12px; padding: 20px; transition: transform 0.2s, box-shadow 0.2s; } .model-card:hover { transform: translateY(-4px); box-shadow: 0 10px 30px rgba(139, 92, 246, 0.15); border-color: #8B5CF640; } .card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } .card-score { font-size: 32px; font-weight: 900; } .card-badge { padding: 4px 12px; border-radius: 6px; font-size: 11px; font-weight: 700; } .card-model { color: #fff; font-size: 14px; font-weight: 600; margin-bottom: 4px; word-break: break-all; } .card-meta { color: #6B7280; font-size: 12px; margin-bottom: 12px; } .card-violations { display: flex; flex-wrap: wrap; gap: 6px; } .card-viol { background: #EF444420; color: #EF4444; padding: 2px 8px; border-radius: 4px; font-size: 10px; font-weight: 600; } .card-clean { background: #22C55E20; color: #22C55E; padding: 2px 8px; border-radius: 4px; font-size: 10px; font-weight: 600; } .site-footer { background: #030305; border-top: 1px solid #1a1a2e; padding: 40px 20px 30px; text-align: center; } .footer-tagline { color: #8B5CF6; font-size: 14px; font-weight: 600; margin-bottom: 20px; letter-spacing: 0.05em; } .footer-links { display: flex; justify-content: center; gap: 24px; margin-bottom: 20px; flex-wrap: wrap; } .footer-links a { color: #6B7280; text-decoration: none; font-size: 13px; } .footer-links a:hover { color: #8B5CF6; } .footer-copy { color: #4B5563; font-size: 11px; margin-bottom: 8px; } .footer-protocol { color: #374151; font-size: 10px; font-family: monospace; } @media (max-width: 640px) { .hero-title { font-size: 32px; } .hero-subtitle { font-size: 16px; } .hero-stats { gap: 20px; } .nav { gap: 16px; } .search-box { flex-direction: column; } button.primary { width: 100%; } .cards-grid { grid-template-columns: 1fr; } } """ HEADER_HTML = f'''
🔴 FOUNDING STAGE · PROTOCOL {PROTOCOL_VERSION}

CROVIA

The Evidence Protocol for AI Training

We're building the global standard for AI compliance verification — in public, from day one. Cryptographic evidence. Multi-regulation. Open protocol.

Protocol
{PROTOCOL_VERSION} · Jan 2026
Public Dataset
Updated Every 6h →
Open Source
GitHub →
''' CARDS_HTML = f'''

Latest Oracle Cards

Auto-generated evidence · Public dataset · Updated every 6 hours

{generate_cards_html()}
''' FOOTER_HTML = ''' ''' with gr.Blocks(css=CSS, title="CROVIA · Omission Oracle") as app: gr.HTML(HEADER_HTML) with gr.Column(elem_classes="search-section"): with gr.Row(elem_classes="search-box"): model_input = gr.Textbox( placeholder="Enter model ID (e.g., meta-llama/Llama-3.1-8B)", show_label=False, container=False, ) analyze_btn = gr.Button("🔍 ANALYZE", variant="primary") with gr.Column(elem_classes="results-section"): score_output = gr.HTML() info_output = gr.HTML() viol_output = gr.HTML() gr.HTML(CARDS_HTML) gr.HTML(FOOTER_HTML) analyze_btn.click(run_analysis, inputs=[model_input], outputs=[score_output, info_output, viol_output]) model_input.submit(run_analysis, inputs=[model_input], outputs=[score_output, info_output, viol_output]) if __name__ == "__main__": app.launch()