#![recursion_limit = "512"]
// SPDX-License-Identifier: LicenseRef-Ternlang-Commercial
// Ternlang — RFI-IRFOS Ternary Intelligence Stack
// Copyright (C) 2026 RFI-IRFOS. All rights reserved.
// Commercial tier. See LICENSE-COMMERCIAL in the repository root.
// Unauthorized use, copying, or distribution is prohibited.

/// ternlang-api — REST HTTP server for the Ternary Intelligence Stack
///
/// Powers ternlang.com/api
///
/// Public routes (no auth):
///   GET  /                        — API info + available endpoints
///   GET  /health                  — health check
///   POST /mcp                     — HTTP MCP transport (Smithery / Claude Desktop)
///
/// API routes (X-Ternlang-Key header required):
///   POST /api/moe/orchestrate     — MoE-13 full pass (synchronous JSON)
///   POST /api/trit_decide         — scalar ternary decision
///   POST /api/trit_vector         — multi-dimensional evidence aggregation
///   POST /api/trit_consensus      — consensus(a, b)
///   POST /api/quantize_weights    — BitNet f32 → ternary
///   POST /api/sparse_benchmark    — sparse vs dense matmul stats
///
/// Admin routes (X-Admin-Key header required):
///   POST   /admin/keys            — generate a new API key
///   GET    /admin/keys            — list all keys with usage
///   DELETE /admin/keys/{key}      — revoke a key
///
/// Env vars:
///   TERNLANG_ADMIN_KEY   — admin secret (required in production)
///   KEYS_FILE            — path to JSON key store (default: ./ternlang_keys.json)
///   PORT                 — listening port (default: 3731)
///
/// Run:
///   TERNLANG_ADMIN_KEY=secret cargo run --release --bin ternlang-api

use axum::{
    Router,
    Json,
    body::Bytes,
    extract::{Path, Query, State, ws::{WebSocketUpgrade, WebSocket, Message}},
    http::{HeaderMap, Method, StatusCode},
    middleware::{self, Next},
    response::{sse::{Event, Sse}, Html, IntoResponse, Response},
    routing::{delete, get, post},
};
use hmac::{Hmac, Mac};
use sha2::Sha256;
use tokio_stream::StreamExt as TokioStreamExt;
use std::convert::Infallible;
use chrono::Utc;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::{
    collections::HashMap,
    env,
    path::PathBuf,
    sync::Arc,
};
use tokio::sync::RwLock;
use tower_http::{cors::{Any, CorsLayer}, services::ServeDir};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use uuid::Uuid;
use rusqlite::Connection;
use serde_rusqlite::from_rows;

use ternlang_core::{
    trit::Trit,
    parser::Parser,
    lexer::Token,
    codegen::betbc::BytecodeEmitter,
    vm::{BetVm, Value as VmValue}
};
use ternlang_moe::TernMoeOrchestrator;
use ternlang_ml::{
    TritScalar, TritEvidenceVec, TEND_BOUNDARY,
    bitnet_threshold, benchmark, sparse_matmul, TritMatrix,
    // Phase 8: Ternary AI Reasoning Toolkit
    DeliberationEngine, CoalitionMember, coalition_vote,
    GateDimension, action_gate, GateVerdict,
    scalar_temperature, hallucination_score,
};

// ─── Key store ───────────────────────────────────────────────────────────────

/// Monthly call limit per tier. Tier 4+ = unlimited (enterprise).
fn tier_monthly_limit(tier: u8) -> Option<u64> {
    match tier {
        2 => Some(10_000),  // €99/mo  — Pro Standard
        3 => Some(50_000),  // €349/mo — Industrial
        _ => None,          // tier 4+ = enterprise, no cap
    }
}

/// One API key entry. Raw key string is used as the HashMap key so lookup is O(1).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKeyEntry {
    pub key_id:        String,   // "tk_<uuid_short>"
    pub tier:          u8,       // 1=open, 2=pro(10k/mo), 3=industrial(50k/mo), 4=enterprise
    pub email:         String,
    pub note:          String,   // free-form admin note
    pub created_at:    String,   // ISO 8601
    pub is_active:     bool,
    pub request_count: u64,      // lifetime total
    #[serde(default)]
    pub monthly_calls: u64,      // calls this calendar month
    #[serde(default)]
    pub month_key:     String,   // "YYYY-MM" — resets monthly_calls when it changes
    #[serde(default)]
    pub github_username: String, // set when customer activates via /activate
    #[serde(default)]
    pub github_invited:  bool,   // true once GitHub collaborator invite has been sent
}

pub enum KeyCheckResult {
    Valid(ApiKeyEntry),
    RateLimited { used: u64, limit: u64 },
    Invalid,
}

/// Persistent key store — serialised as JSON to `path`.
#[derive(Debug, Serialize, Deserialize, Default)]
struct KeyStoreData {
    /// Map from raw key string → entry metadata
    keys: HashMap<String, ApiKeyEntry>,
}

pub struct KeyStore {
    data: RwLock<KeyStoreData>,
    path: PathBuf,
}

impl KeyStore {
    /// Load from disk (creates empty file if it doesn't exist).
    pub async fn load(path: PathBuf) -> Arc<Self> {
        let data = if path.exists() {
            let raw = tokio::fs::read_to_string(&path).await.unwrap_or_default();
            serde_json::from_str::<KeyStoreData>(&raw).unwrap_or_default()
        } else {
            KeyStoreData::default()
        };
        Arc::new(KeyStore { data: RwLock::new(data), path })
    }

    /// Persist current state to disk (best-effort; logs on error).
    async fn save(&self) {
        let data = self.data.read().await;
        match serde_json::to_string_pretty(&*data) {
            Ok(json) => {
                if let Err(e) = tokio::fs::write(&self.path, json).await {
                    eprintln!("[key-store] save error: {}", e);
                } else {
                    #[cfg(unix)]
                    if let Err(e) = std::fs::set_permissions(&self.path, std::fs::Permissions::from_mode(0o600)) {
                        eprintln!("[key-store] chmod error: {}", e);
                    }
                }
            }
            Err(e) => eprintln!("[key-store] serialise error: {}", e),
        }
    }

    /// Check a raw key, enforce monthly rate limit, and increment counters.
    pub async fn validate_and_bump(&self, raw_key: &str) -> KeyCheckResult {
        let mut data = self.data.write().await;
        let entry = match data.keys.get_mut(raw_key) {
            Some(e) => e,
            None    => return KeyCheckResult::Invalid,
        };
        if !entry.is_active {
            return KeyCheckResult::Invalid;
        }

        // Reset monthly counter if the calendar month has rolled over
        let current_month = Utc::now().format("%Y-%m").to_string();
        if entry.month_key != current_month {
            entry.month_key     = current_month;
            entry.monthly_calls = 0;
        }

        // Enforce limit for capped tiers
        if let Some(limit) = tier_monthly_limit(entry.tier) {
            if entry.monthly_calls >= limit {
                return KeyCheckResult::RateLimited { used: entry.monthly_calls, limit };
            }
        }

        entry.request_count += 1;
        entry.monthly_calls += 1;
        KeyCheckResult::Valid(entry.clone())
    }

    /// Generate a new key. Returns (raw_key, entry).
    pub async fn generate(&self, tier: u8, email: String, note: String) -> (String, ApiKeyEntry) {
        let uid   = Uuid::new_v4().to_string().replace('-', "");
        let raw   = format!("tern_{}_{}", tier, &uid[..24]);
        let key_id = format!("tk_{}", &uid[..8]);

        let entry = ApiKeyEntry {
            key_id:          key_id.clone(),
            tier,
            email,
            note,
            created_at:      Utc::now().to_rfc3339(),
            is_active:       true,
            request_count:   0,
            monthly_calls:   0,
            month_key:       Utc::now().format("%Y-%m").to_string(),
            github_username: String::new(),
            github_invited:  false,
        };

        self.data.write().await.keys.insert(raw.clone(), entry.clone());
        self.save().await;
        (raw, entry)
    }

    /// Revoke a key by raw value. Returns true if the key existed.
    pub async fn revoke(&self, raw_key: &str) -> bool {
        let mut data = self.data.write().await;
        if let Some(entry) = data.keys.get_mut(raw_key) {
            entry.is_active = false;
            drop(data);
            self.save().await;
            return true;
        }
        false
    }

    /// List all entries (key hidden, only metadata).
    pub async fn list(&self) -> Vec<Value> {
        let data = self.data.read().await;
        data.keys.iter().map(|(raw, e)| {
            let limit = tier_monthly_limit(e.tier);
            json!({
                "key_id":        e.key_id,
                "key_preview":   format!("{}…", &raw[..12]),
                "tier":          e.tier,
                "email":         e.email,
                "note":          e.note,
                "created_at":    e.created_at,
                "is_active":     e.is_active,
                "request_count": e.request_count,
                "monthly_calls": e.monthly_calls,
                "monthly_limit": limit,
                "month_key":     e.month_key,
            })
        }).collect()
    }

    /// Mark a key as GitHub-invited and store the username. Returns the entry tier or None if key not found.
    pub async fn set_github_invited(&self, raw_key: &str, github_username: &str) -> Option<u8> {
        let mut data = self.data.write().await;
        let entry = data.keys.get_mut(raw_key)?;
        if !entry.is_active { return None; }
        entry.github_username = github_username.to_string();
        entry.github_invited  = true;
        let tier = entry.tier;
        drop(data);
        self.save().await;
        Some(tier)
    }

    /// Peek at a key's entry without bumping counters (for activation check).
    pub async fn peek(&self, raw_key: &str) -> Option<ApiKeyEntry> {
        let data = self.data.read().await;
        data.keys.get(raw_key).cloned()
    }

    /// Return usage info for a single raw key (for GET /api/usage).
    pub async fn usage(&self, raw_key: &str) -> Option<Value> {
        let data = self.data.read().await;
        let e = data.keys.get(raw_key)?;
        let limit = tier_monthly_limit(e.tier);
        Some(json!({
            "key_id":        e.key_id,
            "tier":          e.tier,
            "monthly_calls": e.monthly_calls,
            "monthly_limit": limit,
            "month_key":     e.month_key,
            "request_count": e.request_count,
        }))
    }
}

// ─── Agent Registry ──────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentProduct {
    pub slug:        String,
    pub name:        String,
    pub desc:        String,
    pub input_schema: String,
    pub pricing:     String,   // "free" | "per_call" | "private"
    pub code:        String,
    pub nodes:       u32,
    pub wires:       u32,
    pub owner_key:   String,   // raw API key of publisher
    pub created_at:  String,
    pub calls:       u64,
}

#[derive(Debug, Serialize, Deserialize, Default)]
struct AgentRegistryData {
    agents: HashMap<String, AgentProduct>,
}

pub struct AgentRegistry {
    data: RwLock<AgentRegistryData>,
    path: PathBuf,
}

impl AgentRegistry {
    pub async fn load(path: PathBuf) -> Arc<Self> {
        let data = if path.exists() {
            let raw = tokio::fs::read_to_string(&path).await.unwrap_or_default();
            serde_json::from_str(&raw).unwrap_or_default()
        } else {
            AgentRegistryData::default()
        };
        Arc::new(Self { data: RwLock::new(data), path })
    }

    async fn save(&self) {
        let data = self.data.read().await;
        let json = serde_json::to_string_pretty(&*data).unwrap_or_default();
        if tokio::fs::write(&self.path, json).await.is_ok() {
            #[cfg(unix)]
            let _ = std::fs::set_permissions(&self.path, std::fs::Permissions::from_mode(0o600));
        }
    }

    pub async fn publish(&self, product: AgentProduct) -> String {
        let slug = product.slug.clone();
        self.data.write().await.agents.insert(slug.clone(), product);
        self.save().await;
        slug
    }

    pub async fn get(&self, slug: &str) -> Option<AgentProduct> {
        self.data.read().await.agents.get(slug).cloned()
    }

    pub async fn list(&self) -> Vec<AgentProduct> {
        self.data.read().await.agents.values().cloned().collect()
    }

    pub async fn bump_calls(&self, slug: &str) {
        if let Some(a) = self.data.write().await.agents.get_mut(slug) { a.calls += 1; }
        self.save().await;
    }

    pub async fn delete(&self, slug: &str, owner_key: &str, is_admin: bool) -> Result<(), &'static str> {
        let mut guard = self.data.write().await;
        if let Some(a) = guard.agents.get(slug) {
            if is_admin || a.owner_key == owner_key {
                guard.agents.remove(slug);
                drop(guard);
                self.save().await;
                return Ok(());
            }
            return Err("forbidden");
        }
        Err("not_found")
    }
}

// ─── App state ───────────────────────────────────────────────────────────────

/// Three-layer memory blob: working / session / core arrays of MemEntry.
type MemBlob = serde_json::Map<String, Value>;
/// Per-key server-side memory store.
type MemStore = Arc<std::sync::RwLock<std::collections::HashMap<String, MemBlob>>>;

pub struct DatabaseManager {
    pub db_path: PathBuf,
}

impl DatabaseManager {
    pub fn new(path: PathBuf) -> Self {
        Self { db_path: path }
    }

    pub fn query(&self, sql: &str) -> Result<Value, String> {
        let conn = Connection::open(&self.db_path)
            .map_err(|e| format!("DB Connection failed: {}", e))?;
        
        let mut stmt = conn.prepare(sql)
            .map_err(|e| format!("SQL Prepare failed: {}", e))?;

        let rows = stmt.query([])
            .map_err(|e| format!("SQL Query failed: {}", e))?;

        let deser_rows = from_rows::<Value>(rows);

        let result: Vec<Value> = deser_rows.filter_map(|r| r.ok()).collect();
        Ok(json!(result))
    }
}

pub struct AppState {
    pub admin_key: String,

    keys:                   Arc<KeyStore>,
    version:                &'static str,
    stripe_webhook_secret:  String,
    resend_api_key:         String,
    /// GitHub PAT with `repo` scope — used to invite customers as collaborators.
    github_token:           String,
    /// Server-side three-layer memory, keyed by API key string.
    memory_store:           MemStore,

    agent_registry:         Arc<AgentRegistry>,

    pub tracer_tx:          tokio::sync::broadcast::Sender<Value>,
    pub telemetry_history: Arc<std::sync::RwLock<Vec<Value>>>,

    pub total_instructions: Arc<std::sync::atomic::AtomicU64>,
    pub total_nodes:        Arc<std::sync::atomic::AtomicU64>,
    pub active_nodes:       Arc<std::sync::RwLock<std::collections::HashSet<String>>>,

    pub db_manager:         Arc<DatabaseManager>,
}

// ─── Helpers ─────────────────────────────────────────────────────────────────

pub trait TernaryEncoder {
    fn encode(&self, val: &Value) -> Trit;
}

pub struct StandardMap;
impl TernaryEncoder for StandardMap {
    fn encode(&self, val: &Value) -> Trit {
        match val {
            Value::Bool(true) => Trit::Affirm,
            Value::Bool(false) => Trit::Reject,
            Value::Number(n) => {
                if let Some(f) = n.as_f64() {
                    if f > 0.0 { Trit::Affirm }
                    else if f < 0.0 { Trit::Reject }
                    else { Trit::Tend }
                } else { Trit::Tend }
            }
            Value::Null => Trit::Tend,
            Value::String(s) => {
                if s.is_empty() { Trit::Tend }
                else { Trit::Affirm }
            }
            Value::Array(a) => {
                if a.is_empty() { Trit::Tend }
                else { Trit::Affirm }
            }
            _ => Trit::Tend,
        }
    }
}

fn trit_to_i8(t: Trit) -> i8 {
    match t { Trit::Reject => -1, Trit::Tend => 0, Trit::Affirm => 1 }
}

fn i8_to_trit(v: i64) -> Option<Trit> {
    match v { -1 => Some(Trit::Reject), 0 => Some(Trit::Tend), 1 => Some(Trit::Affirm), _ => None }
}

fn api_error(status: StatusCode, message: &str) -> Response {
    (status, Json(json!({ "error": message, "docs": "https://ternlang.com/docs/api" }))).into_response()
}

// ─── Security headers middleware ──────────────────────────────────────────────

async fn add_security_headers(request: axum::extract::Request, next: Next) -> Response {
    let mut response = next.run(request).await;
    let headers = response.headers_mut();
    headers.insert("X-Frame-Options",       "DENY".parse().unwrap());
    headers.insert("X-Content-Type-Options","nosniff".parse().unwrap());
    headers.insert("Referrer-Policy",       "strict-origin-when-cross-origin".parse().unwrap());
    headers.insert(
        "Content-Security-Policy",
        "object-src 'none'; base-uri 'self'; frame-ancestors 'none';".parse().unwrap(),
    );
    response
}

// ─── Auth middleware (API routes) ─────────────────────────────────────────────

async fn require_api_key(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
    request: axum::extract::Request,
    next: Next,
) -> Response {
    let path = request.uri().path().to_string();

    // Public endpoints — no key required
    if path == "/" || path == "/health" || path == "/mcp"
        || path == "/.well-known/mcp/server-card.json"
        || path == "/stripe/webhook"
        || path == "/pricing"
        || path == "/impressum"   || path == "/impressum.html"
        || path == "/agb"         || path == "/agb.html"
        || path == "/datenschutz" || path == "/datenschutz.html"
        || path == "/studio"
        || path == "/studio.js"
        || path == "/translator"
        || path == "/playground"
        || path.starts_with("/playground/pkg/")
        || path == "/fortune"
        || path == "/activate"
        || path == "/api/run"
        || path == "/api/data"
        || path == "/api/tracer/ws"
        || path == "/api/github/activate"
        || path == "/api/stdlib/list"
        || path.starts_with("/api/stdlib/read/")
        || path == "/kpi"
        || path.starts_with("/kpi/")
        || path.starts_with("/api/kpi/")
        || path == "/benchmarks/training"
        || path.starts_with("/assets/")
        || path == "/favicon.ico"
        || path.ends_with(".map")
        || path.contains("installHook.js")
        || path.starts_with("/admin")
    {
        return next.run(request).await;
    }

    // /api/usage requires a valid key (any tier) — key holder sees their own usage
    // Unauthenticated callers get a 401, not usage data

    let raw = headers
        .get("X-Ternlang-Key")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("");

    if raw.is_empty() {
        return api_error(StatusCode::UNAUTHORIZED,
            "Missing X-Ternlang-Key header. Acquire a key at https://ternlang.com/#licensing");
    }

    match state.keys.validate_and_bump(raw).await {
        KeyCheckResult::Valid(entry) => {
            // Endpoints accessible by any valid key (Tier 1+)
            if path == "/api/usage" || path == "/api/stdlib/list" || path.starts_with("/api/stdlib/read") {
                return next.run(request).await;
            }
            // All other /api/* endpoints require Tier 2 or above (paid commercial access)
            if path.starts_with("/api/") && entry.tier < 2 {
                return api_error(StatusCode::FORBIDDEN,
                    "This endpoint requires a Tier 2 or higher key. Upgrade at https://ternlang.com/#licensing");
            }
            next.run(request).await
        }
        KeyCheckResult::RateLimited { used, limit } => {
            (StatusCode::TOO_MANY_REQUESTS, Json(json!({
                "error": "Monthly call limit reached.",
                "used":  used,
                "limit": limit,
                "upgrade": "https://ternlang.com/#licensing",
                "resets": "1st of next month (UTC)",
            }))).into_response()
        }
        KeyCheckResult::Invalid =>
            api_error(StatusCode::UNAUTHORIZED, "Invalid or revoked API key."),
    }
}

// ─── Admin middleware ──────────────────────────────────────────────────────────

async fn require_admin_key(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
    request: axum::extract::Request,
    next: Next,
) -> Response {
    if !request.uri().path().starts_with("/admin") {
        return next.run(request).await;
    }

    let provided = headers
        .get("X-Admin-Key")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("");

    if provided.is_empty() {
        return api_error(StatusCode::UNAUTHORIZED, "Missing X-Admin-Key header.");
    }
    if provided != state.admin_key {
        return api_error(StatusCode::UNAUTHORIZED, "Invalid admin key.");
    }

    next.run(request).await
}

// ─── GET / ───────────────────────────────────────────────────────────────────

static INDEX_HTML:      &str = include_str!("../../ternlang-web/index.html");
static PRICING_HTML:    &str = include_str!("../../ternlang-web/pricing.html");
static IMPRESSUM_HTML:  &str = include_str!("../../ternlang-web/impressum.html");
static AGB_HTML:        &str = include_str!("../../ternlang-web/agb.html");
static DATENSCHUTZ_HTML:&str = include_str!("../../ternlang-web/datenschutz.html");
static STUDIO_HTML:     &str = include_str!("../../ternlang-studio/index.html");
static STUDIO_JS:       &str = include_str!("../../ternlang-studio/studio.js");
static TRANSLATOR_HTML: &str = include_str!("../../ternlang-translator/web/templates/index.html");
static KPI_HTML:        &str = include_str!("kpi.html");
static FORTUNE_HTML:    &str = include_str!("../../docs/fortune_cookie.html");
static WASM_JS:         &str = include_str!("../../premlib/playground/pkg/ternlang_wasm.js");
static WASM_BG:        &[u8] = include_bytes!("../../premlib/playground/pkg/ternlang_wasm_bg.wasm");

async fn kpi_page() -> impl axum::response::IntoResponse {
    let dynamic = if std::path::Path::new("/data/kpi/index.html").exists() {
        tokio::fs::read_to_string("/data/kpi/index.html").await.ok()
    } else {
        None
    };
    match dynamic {
        Some(html) => Html(html).into_response(),
        None => Html(KPI_HTML).into_response(),
    }
}

const KPI_ALLOWED: &[&str] = &[
    "index.html",
    "ternlang_kpi_log.json",
    "ternlang_gh_traffic.json",
    "training_telemetry.json",
];

async fn kpi_data(axum::extract::Path(filename): axum::extract::Path<String>) -> impl axum::response::IntoResponse {
    if !KPI_ALLOWED.contains(&filename.as_str()) {
        return StatusCode::NOT_FOUND.into_response();
    }
    let path = std::path::Path::new("/data/kpi").join(&filename);
    match tokio::fs::read_to_string(&path).await {
        Ok(content) => {
            (
                [
                    (axum::http::header::CONTENT_TYPE, "application/json"),
                    (axum::http::header::CACHE_CONTROL, "public, max-age=3600"),
                ],
                content
            ).into_response()
        }
        Err(_) => StatusCode::NOT_FOUND.into_response()
    }
}
async fn kpi_upload(
    axum::extract::Path(filename): axum::extract::Path<String>,
    headers: HeaderMap,
    body: Bytes,
) -> impl axum::response::IntoResponse {
    // Reject path traversal characters before allowlist check (defense in depth)
    if filename.contains('/') || filename.contains('\\') || filename.contains("..") {
        return (StatusCode::BAD_REQUEST, "invalid filename").into_response();
    }
    const ALLOWED: &[&str] = &["index.html", "ternlang_kpi_log.json", "ternlang_gh_traffic.json", "training_telemetry.json"];
    if !ALLOWED.contains(&filename.as_str()) {
        return (StatusCode::BAD_REQUEST, "invalid filename").into_response();
    }
    let secret = std::env::var("KPI_SECRET").unwrap_or_default();
    let token = headers.get("X-Kpi-Token").and_then(|v| v.to_str().ok()).unwrap_or("");
    if secret.is_empty() || token != secret {
        return (StatusCode::UNAUTHORIZED, "unauthorized").into_response();
    }
    let dir = std::path::Path::new("/data/kpi");
    if let Err(e) = tokio::fs::create_dir_all(dir).await {
        return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response();
    }
    match tokio::fs::write(dir.join(&filename), &body).await {
        Ok(_) => (StatusCode::OK, "ok").into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

async fn studio_page() -> Html<&'static str> {
    Html(STUDIO_HTML)
}

// ─── GET /benchmarks/training ─────────────────────────────────────────────────
// Public endpoint: returns the latest Albert-MoE-13 training telemetry as JSON.
// Served from /data/kpi/training_telemetry.json (uploaded by KPI workflow).
// Falls back to a static summary when no live data is available.
async fn benchmarks_training() -> impl axum::response::IntoResponse {
    let path = std::path::Path::new("/data/kpi/training_telemetry.json");
    if let Ok(content) = tokio::fs::read_to_string(path).await {
        return (
            [
                (axum::http::header::CONTENT_TYPE, "application/json"),
                (axum::http::header::CACHE_CONTROL, "public, max-age=60"),
                (axum::http::header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"),
            ],
            content,
        ).into_response();
    }
    // Static fallback: last known training state
    let fallback = serde_json::json!({
        "model": "albert-moe-13",
        "version": "v2.0.0",
        "architecture": { "layers": 6, "hidden": 256, "experts": 12, "ctx": 128, "vocab": 8000 },
        "training": {
            "global_epoch": 87,
            "best_loss": 5.9174,
            "corpus_mb": 26,
            "corpus_files": 16,
            "note": "Live telemetry not yet synced — restart in progress after 6L surgery"
        },
        "evolution": [
            { "event": "3L_start",    "date": "2026-05-05", "loss_before": null },
            { "event": "3L_to_4L",    "date": "2026-05-05", "loss_before": 7.01 },
            { "event": "4L_to_5L",    "date": "2026-05-06", "loss_before": 6.2  },
            { "event": "5L_to_6L",    "date": "2026-05-06", "loss_before": 5.9174, "witnessed_live": true }
        ],
        "benchmarks": {
            "sparsity_56pct_speedup": 2.3,
            "sparsity_90pct_speedup": 2.84,
            "batch_ms_6L_laptop_cpu": 2000
        },
        "telemetry_schema": {
            "ROUTE": "ROUTE step=N E=w0,...,w11 (per-expert routing weight every 10 batches)",
            "GRAD":  "GRAD step=N n=X L=n0,...,n5 (per-layer gradient norm every batch)",
            "TELE":  "TELE L=6 S=s0,...,s5 E=e0,...,e11 (sparsity + expert activity per epoch)"
        }
    });
    (
        [
            (axum::http::header::CONTENT_TYPE, "application/json"),
            (axum::http::header::CACHE_CONTROL, "public, max-age=300"),
            (axum::http::header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"),
        ],
        fallback.to_string(),
    ).into_response()
}

// ─── POST /api/kpi/upload/{filename} extended allowlist ───────────────────────
// training_telemetry.json is uploaded by the KPI workflow after each training cycle.

async fn fortune_page() -> Html<&'static str> {
    Html(FORTUNE_HTML)
}

async fn translator_page() -> Html<&'static str> {
    Html(TRANSLATOR_HTML)
}

async fn studio_js() -> impl axum::response::IntoResponse {
    ([(axum::http::header::CONTENT_TYPE, "application/javascript")], STUDIO_JS)
}

async fn playground_page() -> impl axum::response::IntoResponse {
    axum::response::Redirect::permanent("/studio")
}

async fn wasm_js_handler() -> impl axum::response::IntoResponse {
    ([(axum::http::header::CONTENT_TYPE, "application/javascript")], WASM_JS)
}

async fn wasm_bg_handler() -> impl axum::response::IntoResponse {
    ([(axum::http::header::CONTENT_TYPE, "application/wasm")], WASM_BG)
}

async fn pricing_page()     -> Html<&'static str> { Html(PRICING_HTML) }
async fn impressum_page()  -> Html<&'static str> { Html(IMPRESSUM_HTML) }
async fn agb_page()        -> Html<&'static str> { Html(AGB_HTML) }
async fn datenschutz_page()-> Html<&'static str> { Html(DATENSCHUTZ_HTML) }

async fn root(State(state): State<Arc<AppState>>, headers: HeaderMap) -> Response {
    // Serve the website to browsers; return JSON manifest to API clients.
    let accept = headers
        .get("accept")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("");
    if accept.contains("text/html") {
        return Html(INDEX_HTML).into_response();
    }
    Json(json!({
        "name":    "Ternlang API",
        "version": state.version,
        "by":      "RFI-IRFOS (ZVR: 1015608684)",
        "authority": "Research Focus Institute – Interdisciplinary Research Facility for Open Sciences",
        "website": "https://ternlang.com",
        "docs":    "https://ternlang.com/docs/api",

        "sovereignty": {
            "global_triadic_penetration": state.total_instructions.load(std::sync::atomic::Ordering::Relaxed),
            "active_monopoly_nodes":      state.total_nodes.load(std::sync::atomic::Ordering::Relaxed),
            "authority":                  "RFI-IRFOS (ZVR: 1015608684)"
        },

        "auth":    "X-Ternlang-Key header required for /api/* endpoints",
        "endpoints": {
            "POST /api/trit_decide":             "Scalar ternary decision: evidence[] → reject/tend/affirm + confidence",
            "POST /api/trit_vector":             "Multi-dimensional evidence: named dimensions + weights → aggregate",
            "POST /api/trit_consensus":          "consensus(a, b) → ternary result",
            "POST /api/quantize_weights":        "f32[] → ternary weights via BitNet threshold",
            "POST /api/sparse_benchmark":        "Sparse vs dense matmul performance stats",
            "POST /api/trit_deliberate":         "EMA deliberation engine: multi-round evidence → converged trit",
            "POST /api/trit_coalition":          "Coalition vote: N agents → quorum/dissent/abstain + consensus",
            "POST /api/trit_gate":               "Action gate: multi-dim hard-block safety veto",
            "POST /api/scalar_temperature":      "TritScalar → LLM sampling temperature + prompt hint",
            "POST /api/hallucination_score":     "Signal variance → trust trit",
            "POST /api/moe/orchestrate":          "MoE-13 full orchestration — synchronous JSON result",
            "GET  /api/stream/moe_orchestrate":  "SSE: MoE-13 orchestration pass streamed event-by-event",
            "GET  /api/stream/deliberate":       "SSE: EMA deliberation — one event per round, live feed",
            "POST /api/v1/taas/infer":           "TaaS: Hardware-native sparse inference with MoE-13 safety audit.",
            "POST /api/v1/heartbeat":            "API liveness ping — returns server timestamp and version",
        },
        "mcp": {
            "url":         "https://ternlang.com/mcp",
            "transport":   "HTTP JSON-RPC 2.0",
            "smithery":    "https://smithery.ai/server/ternlang",
            "description": "POST /mcp — 34 tools, all free. Pass X-Ternlang-Key for server-side persistent memory and REST API access.",
        },
        "acquire_key": "https://ternlang.com/#licensing"
    })).into_response()
}

// ─── GET /health ─────────────────────────────────────────────────────────────

async fn health() -> Json<Value> {
    Json(json!({ "status": "ok", "engine": "BET VM", "trit": 1 }))
}

// ─── Admin: POST /admin/keys ──────────────────────────────────────────────────

#[derive(Deserialize)]
struct GenerateKeyBody {
    tier:  Option<u8>,
    email: Option<String>,
    note:  Option<String>,
}

async fn admin_generate_key(
    State(state): State<Arc<AppState>>,
    Json(body): Json<GenerateKeyBody>,
) -> Response {
    let tier  = body.tier.unwrap_or(2);
    let email = body.email.unwrap_or_default();
    let note  = body.note.unwrap_or_default();

    if tier < 1 || tier > 3 {
        return api_error(StatusCode::BAD_REQUEST, "tier must be 1, 2, or 3");
    }

    let (raw, entry) = state.keys.generate(tier, email, note).await;

    eprintln!("[admin] generated key {} for {}", entry.key_id, entry.email);

    (StatusCode::CREATED, Json(json!({
        "key":     raw,        // Only returned once — save it!
        "key_id":  entry.key_id,
        "tier":    entry.tier,
        "email":   entry.email,
        "created": entry.created_at,
        "warning": "Store this key securely — it will not be shown again.",
    }))).into_response()
}

// ─── Admin: GET /admin/keys ───────────────────────────────────────────────────

async fn admin_list_keys(State(state): State<Arc<AppState>>) -> Json<Value> {
    let entries = state.keys.list().await;
    Json(json!({ "total": entries.len(), "keys": entries }))
}

// ─── Admin: DELETE /admin/keys/{key} ─────────────────────────────────────────

async fn admin_revoke_key(
    State(state): State<Arc<AppState>>,
    Path(raw_key): Path<String>,
) -> Response {
    if state.keys.revoke(&raw_key).await {
        eprintln!("[admin] revoked key {}", &raw_key[..12.min(raw_key.len())]);
        (StatusCode::OK, Json(json!({ "revoked": true }))).into_response()
    } else {
        api_error(StatusCode::NOT_FOUND, "Key not found.")
    }
}

// ─── POST /api/trit_decide ───────────────────────────────────────────────────

async fn trit_decide(Json(body): Json<Value>) -> Response {
    let evidence: Vec<f32> = match body["evidence"].as_array() {
        Some(arr) => match arr.iter()
            .map(|v| v.as_f64().map(|f| f as f32).ok_or(()))
            .collect::<Result<Vec<_>, _>>() {
                Ok(v) => v,
                Err(_) => return api_error(StatusCode::BAD_REQUEST, "evidence values must be numbers"),
            },
        None => return api_error(StatusCode::BAD_REQUEST, "evidence must be an array of numbers"),
    };

    if evidence.is_empty() {
        return api_error(StatusCode::BAD_REQUEST, "evidence cannot be empty");
    }

    let min_confidence = body["min_confidence"].as_f64().unwrap_or(0.0) as f32;
    let mean = evidence.iter().sum::<f32>() / evidence.len() as f32;
    let scalar = TritScalar::new(mean);

    let per_signal: Vec<Value> = evidence.iter().enumerate().map(|(i, &v)| {
        let s = TritScalar::new(v);
        json!({
            "index":      i,
            "raw":        (v * 1000.0).round() / 1000.0,
            "label":      s.label(),
            "confidence": (s.confidence() * 1000.0).round() / 1000.0,
            "trit":       trit_to_i8(s.trit()),
        })
    }).collect();

    let zeros = per_signal.iter().filter(|s| s["trit"] == 0).count();
    let actionable = scalar.is_actionable(min_confidence);

    let recommendation = match scalar.trit() {
        Trit::Affirm => format!(
            "Affirm — confidence {:.0}%{}.",
            scalar.confidence() * 100.0,
            if actionable { "" } else { " (below min_confidence — gather more evidence)" }
        ),
        Trit::Reject => format!(
            "Reject — confidence {:.0}%{}.",
            scalar.confidence() * 100.0,
            if actionable { "" } else { " (below min_confidence — gather more evidence)" }
        ),
        Trit::Tend => format!(
            "Tend — scalar {:.3} is in the deliberation zone [{:.3}, +{:.3}]. Gather more evidence.",
            scalar.raw(), -TEND_BOUNDARY, TEND_BOUNDARY
        ),
    };

    (StatusCode::OK, Json(json!({
        "scalar":          (scalar.raw() * 1000.0).round() / 1000.0,
        "trit":            trit_to_i8(scalar.trit()),
        "label":           scalar.label(),
        "confidence":      (scalar.confidence() * 1000.0).round() / 1000.0,
        "is_actionable":   actionable,
        "tend_boundary":   TEND_BOUNDARY,
        "signal_sparsity": zeros as f64 / evidence.len() as f64,
        "recommendation":  recommendation,
        "per_signal":      per_signal,
    }))).into_response()
}

// ─── POST /api/trit_vector ───────────────────────────────────────────────────

async fn trit_vector(Json(body): Json<Value>) -> Response {
    let dims = match body["dimensions"].as_array() {
        Some(d) => d,
        None => return api_error(StatusCode::BAD_REQUEST,
            "dimensions must be an array of {label, value, weight} objects"),
    };

    if dims.is_empty() {
        return api_error(StatusCode::BAD_REQUEST, "dimensions cannot be empty");
    }

    let min_confidence = body["min_confidence"].as_f64().unwrap_or(0.5) as f32;

    let mut labels  = Vec::new();
    let mut values  = Vec::new();
    let mut weights = Vec::new();

    for (i, d) in dims.iter().enumerate() {
        let label  = d["label"].as_str().unwrap_or("unnamed").to_string();
        let value  = match d["value"].as_f64() {
            Some(v) => v as f32,
            None => return api_error(StatusCode::BAD_REQUEST,
                &format!("dimensions[{}].value must be a number", i)),
        };
        let weight = d["weight"].as_f64().unwrap_or(1.0) as f32;
        if weight < 0.0 {
            return api_error(StatusCode::BAD_REQUEST,
                &format!("dimensions[{}].weight must be >= 0", i));
        }
        labels.push(label);
        values.push(value);
        weights.push(weight);
    }

    let ev      = TritEvidenceVec::new(labels, values, weights);
    let agg     = ev.aggregate();
    let scalars = ev.scalars();
    let actionable = agg.is_actionable(min_confidence);

    let breakdown: Vec<Value> = ev.dimensions.iter()
        .zip(ev.values.iter())
        .zip(ev.weights.iter())
        .zip(scalars.iter())
        .map(|(((label, &val), &w), sc)| json!({
            "label":      label,
            "raw":        (val * 1000.0).round() / 1000.0,
            "weight":     w,
            "trit":       trit_to_i8(sc.trit()),
            "label_trit": sc.label(),
            "confidence": (sc.confidence() * 1000.0).round() / 1000.0,
        })).collect();

    let zeros = breakdown.iter().filter(|d| d["trit"] == 0).count();

    (StatusCode::OK, Json(json!({
        "aggregate": {
            "scalar":     (agg.raw() * 1000.0).round() / 1000.0,
            "trit":       trit_to_i8(agg.trit()),
            "label":      agg.label(),
            "confidence": (agg.confidence() * 1000.0).round() / 1000.0,
            "is_actionable": actionable,
        },
        "dimensions":       breakdown,
        "tend_boundary":    TEND_BOUNDARY,
        "signal_sparsity":  zeros as f64 / ev.dimensions.len() as f64,
        "recommendation":   match agg.trit() {
            Trit::Affirm => "Affirm — weighted evidence crosses threshold.".to_string(),
            Trit::Reject => "Reject — weighted evidence crosses negative threshold.".to_string(),
            Trit::Tend   => format!(
                "Tend — aggregate {:.3} within deliberation zone. Resolve conflicting dimensions.",
                agg.raw()
            ),
        },
    }))).into_response()
}

// ─── POST /api/trit_consensus ────────────────────────────────────────────────

async fn trit_consensus(Json(body): Json<Value>) -> Response {
    let a = match body["a"].as_i64().and_then(i8_to_trit) {
        Some(t) => t,
        None => return api_error(StatusCode::BAD_REQUEST, "a must be -1, 0, or 1"),
    };
    let b = match body["b"].as_i64().and_then(i8_to_trit) {
        Some(t) => t,
        None => return api_error(StatusCode::BAD_REQUEST, "b must be -1, 0, or 1"),
    };

    // consensus: agree → common value (carry=0); disagree → 0 (carry=1)
    let result = if a == b { a } else { Trit::Tend };
    let carry  = if a == b { Trit::Tend } else { Trit::Affirm };

    (StatusCode::OK, Json(json!({
        "a":      trit_to_i8(a),
        "b":      trit_to_i8(b),
        "result": trit_to_i8(result),
        "carry":  trit_to_i8(carry),
        "label":  TritScalar::new(trit_to_i8(result) as f32).label(),
    }))).into_response()
}

// ─── POST /api/quantize_weights ──────────────────────────────────────────────

async fn quantize_weights(Json(body): Json<Value>) -> Response {
    let weights: Vec<f32> = match body["weights"].as_array() {
        Some(arr) => arr.iter().map(|v| v.as_f64().unwrap_or(0.0) as f32).collect(),
        None => return api_error(StatusCode::BAD_REQUEST, "weights must be an array of numbers"),
    };

    if weights.is_empty() {
        return api_error(StatusCode::BAD_REQUEST, "weights cannot be empty");
    }

    let threshold = body["threshold"].as_f64()
        .unwrap_or_else(|| bitnet_threshold(&weights) as f64) as f32;

    let trits: Vec<i8> = weights.iter().map(|&w| {
        if w > threshold { 1 }
        else if w < -threshold { -1 }
        else { 0 }
    }).collect();

    let zeros    = trits.iter().filter(|&&t| t == 0).count();
    let sparsity = zeros as f64 / trits.len() as f64;

    (StatusCode::OK, Json(json!({
        "threshold":       (threshold * 1000.0).round() / 1000.0,
        "trits":           trits,
        "sparsity":        (sparsity * 1000.0).round() / 1000.0,
        "non_zero":        trits.len() - zeros,
        "bits_saved":      format!("{:.1}%", sparsity * 100.0),
        "zone":            if sparsity < 0.40 { "warm" }
                           else if sparsity <= 0.60 { "goldilocks ★" }
                           else { "asymptotic" },
    }))).into_response()
}

// ─── POST /api/sparse_benchmark ──────────────────────────────────────────────

async fn sparse_benchmark(Json(body): Json<Value>) -> Response {
    let rows = body["rows"].as_u64().unwrap_or(4) as usize;
    let cols = body["cols"].as_u64().unwrap_or(4) as usize;

    if rows == 0 || cols == 0 || rows > 512 || cols > 512 {
        return api_error(StatusCode::BAD_REQUEST, "rows and cols must be between 1 and 512");
    }

    let f32_weights: Vec<f32> = match body["weights"].as_array() {
        Some(arr) => arr.iter().map(|v| v.as_f64().unwrap_or(0.0) as f32).collect(),
        None => (0..rows * cols).map(|i| match i % 5 {
            0 => 0.9, 1 => -0.8, 2 => 0.1, 3 => -0.1, _ => 0.05
        }).collect(),
    };

    if f32_weights.len() != rows * cols {
        return api_error(StatusCode::BAD_REQUEST,
            &format!("weights length must equal rows×cols = {}", rows * cols));
    }

    let threshold = body["threshold"].as_f64()
        .unwrap_or_else(|| bitnet_threshold(&f32_weights) as f64) as f32;

    let w     = TritMatrix::from_f32(rows, cols, &f32_weights, threshold);
    let input = TritMatrix::new(rows, cols);
    let r     = benchmark(&input, &w);
    let (_, skipped) = sparse_matmul(&input, &w);

    (StatusCode::OK, Json(json!({
        "rows":                rows,
        "cols":                cols,
        "weight_sparsity":     r.weight_sparsity,
        "skip_rate":           r.skip_rate,
        "dense_ops":           r.dense_ops,
        "sparse_ops":          r.sparse_ops,
        "skipped_ops":         skipped,
        "ops_reduction_factor": r.dense_ops as f64 / r.sparse_ops.max(1) as f64,
        "threshold_used":      threshold,
        "summary": format!(
            "{:.1}% weight sparsity → {:.2}× fewer multiply ops ({} skipped of {})",
            r.weight_sparsity * 100.0,
            r.dense_ops as f64 / r.sparse_ops.max(1) as f64,
            skipped,
            r.dense_ops
        ),
    }))).into_response()
}

// ─── POST /api/trit_deliberate ────────────────────────────────────────────────

async fn trit_deliberate(Json(body): Json<Value>) -> Response {
    let target_confidence = body["target_confidence"].as_f64().unwrap_or(0.7) as f32;
    let max_rounds = body["max_rounds"].as_u64().unwrap_or(10) as usize;
    let alpha = body["alpha"].as_f64().unwrap_or(0.4) as f32;

    let rounds_evidence: Vec<Vec<f32>> = match body["rounds"].as_array() {
        Some(arr) => arr.iter().map(|round| {
            round.as_array().map(|signals|
                signals.iter().map(|v| v.as_f64().unwrap_or(0.0) as f32).collect()
            ).unwrap_or_default()
        }).collect(),
        None => return api_error(StatusCode::BAD_REQUEST,
            "rounds must be an array of evidence arrays, e.g. [[0.2], [0.8, 0.9]]"),
    };

    if rounds_evidence.is_empty() {
        return api_error(StatusCode::BAD_REQUEST, "rounds cannot be empty");
    }

    let engine = DeliberationEngine::new(target_confidence, max_rounds).with_alpha(alpha);
    let result = engine.run(rounds_evidence);

    let trace_json: Vec<Value> = result.trace.iter().map(|r| json!({
        "round":           r.round,
        "cumulative_mean": (r.cumulative_mean * 1000.0).round() / 1000.0,
        "trit":            r.scalar.trit_i8(),
        "label":           r.scalar.label(),
        "confidence":      (r.scalar.confidence() * 1000.0).round() / 1000.0,
        "converged":       r.converged,
    })).collect();

    (StatusCode::OK, Json(json!({
        "final_trit":       result.final_trit,
        "final_label":      result.final_label,
        "final_confidence": (result.final_confidence * 1000.0).round() / 1000.0,
        "converged":        result.converged,
        "rounds_used":      result.rounds_used,
        "convergence_reason": result.convergence_reason,
        "trace":            trace_json,
    }))).into_response()
}

// ─── POST /api/trit_coalition ─────────────────────────────────────────────────

async fn trit_coalition(Json(body): Json<Value>) -> Response {
    let members_raw = match body["members"].as_array() {
        Some(arr) => arr,
        None => return api_error(StatusCode::BAD_REQUEST,
            "members must be an array of {label, trit, confidence, weight} objects"),
    };

    if members_raw.is_empty() {
        return api_error(StatusCode::BAD_REQUEST, "members cannot be empty");
    }

    let mut members = Vec::new();
    for (i, m) in members_raw.iter().enumerate() {
        let label = m["label"].as_str().unwrap_or("agent").to_string();
        let trit = match m["trit"].as_i64() {
            Some(v) if v >= -1 && v <= 1 => v as i8,
            _ => return api_error(StatusCode::BAD_REQUEST,
                &format!("members[{}].trit must be -1, 0, or 1", i)),
        };
        let confidence = m["confidence"].as_f64().unwrap_or(1.0) as f32;
        let weight = m["weight"].as_f64().unwrap_or(1.0) as f32;
        members.push(CoalitionMember::new(label, trit, confidence, weight));
    }

    let result = coalition_vote(&members);

    let breakdown: Vec<Value> = result.breakdown.iter().map(|(label, trit, contribution)| json!({
        "label":        label,
        "trit":         trit,
        "contribution": (contribution * 1000.0).round() / 1000.0,
    })).collect();

    (StatusCode::OK, Json(json!({
        "trit":             result.trit,
        "label":            result.label,
        "aggregate_score":  (result.aggregate_score * 1000.0).round() / 1000.0,
        "quorum":           (result.quorum * 1000.0).round() / 1000.0,
        "dissent_rate":     (result.dissent_rate * 1000.0).round() / 1000.0,
        "abstain_rate":     (result.abstain_rate * 1000.0).round() / 1000.0,
        "member_count":     result.member_count,
        "breakdown":        breakdown,
    }))).into_response()
}

// ─── POST /api/trit_gate ──────────────────────────────────────────────────────

async fn trit_gate(Json(body): Json<Value>) -> Response {
    let dims_raw = match body["dimensions"].as_array() {
        Some(arr) => arr,
        None => return api_error(StatusCode::BAD_REQUEST,
            "dimensions must be an array of {name, evidence, weight, hard_block?} objects"),
    };

    if dims_raw.is_empty() {
        return api_error(StatusCode::BAD_REQUEST, "dimensions cannot be empty");
    }

    let mut dimensions = Vec::new();
    for (i, d) in dims_raw.iter().enumerate() {
        let name = d["name"].as_str().unwrap_or("dim").to_string();
        let evidence = match d["evidence"].as_f64() {
            Some(v) => v as f32,
            None => return api_error(StatusCode::BAD_REQUEST,
                &format!("dimensions[{}].evidence must be a number in [-1, 1]", i)),
        };
        let weight = d["weight"].as_f64().unwrap_or(1.0) as f32;
        let hard_block = d["hard_block"].as_bool().unwrap_or(false);
        let mut dim = GateDimension::new(name, evidence, weight);
        if hard_block { dim = dim.hard(); }
        dimensions.push(dim);
    }

    let result = action_gate(&dimensions);

    let dim_results: Vec<Value> = result.dim_results.iter().map(|(name, sc, is_hard)| json!({
        "name":       name,
        "trit":       sc.trit_i8(),
        "label":      sc.label(),
        "confidence": (sc.confidence() * 1000.0).round() / 1000.0,
        "hard_block": is_hard,
    })).collect();

    (StatusCode::OK, Json(json!({
        "verdict":          result.verdict.label(),
        "aggregate_scalar": (result.aggregate.raw() * 1000.0).round() / 1000.0,
        "hard_blocked_by":  result.hard_blocked_by,
        "dimensions":       dim_results,
        "explanation":      result.explanation,
    }))).into_response()
}

// ─── POST /api/scalar_temperature ────────────────────────────────────────────

async fn scalar_temperature_endpoint(Json(body): Json<Value>) -> Response {
    let evidence: Vec<f32> = match body["evidence"].as_array() {
        Some(arr) => arr.iter().map(|v| v.as_f64().unwrap_or(0.0) as f32).collect(),
        None => match body["scalar"].as_f64() {
            Some(v) => vec![v as f32],
            None => return api_error(StatusCode::BAD_REQUEST,
                "provide evidence array or scalar value"),
        },
    };

    let mean = evidence.iter().sum::<f32>() / evidence.len().max(1) as f32;
    let sc = TritScalar::new(mean);
    let temp = scalar_temperature(&sc);

    (StatusCode::OK, Json(json!({
        "trit":        temp.trit,
        "confidence":  (temp.confidence * 1000.0).round() / 1000.0,
        "temperature": temp.temperature,
        "reasoning":   temp.reasoning,
        "prompt_hint": temp.prompt_hint,
        "usage": "Set your LLM sampling temperature to the 'temperature' field for ternary-aligned generation.",
    }))).into_response()
}

// ─── POST /api/hallucination_score ────────────────────────────────────────────

async fn hallucination_score_endpoint(Json(body): Json<Value>) -> Response {
    let signals: Vec<f32> = match body["signals"].as_array() {
        Some(arr) => arr.iter().map(|v| v.as_f64().unwrap_or(0.0) as f32).collect(),
        None => return api_error(StatusCode::BAD_REQUEST,
            "signals must be an array of numbers in [-1, 1]"),
    };

    if signals.is_empty() {
        return api_error(StatusCode::BAD_REQUEST, "signals cannot be empty");
    }

    let score = hallucination_score(&signals);

    (StatusCode::OK, Json(json!({
        "trust_trit":   score.trust_trit,
        "trust_label":  score.trust_label,
        "mean":         (score.mean * 1000.0).round() / 1000.0,
        "variance":     (score.variance * 1000.0).round() / 1000.0,
        "consistency":  (score.consistency * 1000.0).round() / 1000.0,
        "signal_count": score.signal_count,
        "explanation":  score.explanation,
    }))).into_response()
}

// ─── POST /api/moe/orchestrate ───────────────────────────────────────────────
//
// Synchronous (non-streaming) MoE-13 orchestration.
// Returns the full OrchestrationResult as a single JSON object.
//
// Body: { "query": "...", "evidence": [0.8, 0.6, 0.9, 0.7, 0.5, 1.0] }
//   evidence: optional 6-float vector [syntax, world_knowledge, reasoning,
//             tool_use, persona, safety]. Defaults to [0.5; 6] if omitted.

#[derive(Deserialize)]
struct MoeOrchestrateBody {
    query:    String,
    evidence: Option<Vec<f32>>,
}

async fn moe_orchestrate(
    State(_state): State<Arc<AppState>>,
    Json(body): Json<MoeOrchestrateBody>,
) -> Response {
    let evidence = body.evidence.unwrap_or_else(|| vec![0.5f32; 6]);

    // Clamp / pad to exactly 6 dimensions
    let mut ev6 = [0.5f32; 6];
    for (i, v) in evidence.iter().take(6).enumerate() {
        ev6[i] = v.clamp(-1.0, 1.0);
    }

    let mut orch  = TernMoeOrchestrator::with_standard_experts();
    let result    = orch.orchestrate(&body.query, &ev6);

    let trit_label = match result.trit {
         1  => "affirm",
        -1  => "reject",
        _   => "tend",
    };

    let pair_json = result.pair.as_ref().map(|p| json!({
        "expert_a":  p.expert_a,
        "expert_b":  p.expert_b,
        "relevance": p.relevance,
        "synergy":   p.synergy,
        "score":     p.combined,
    }));

    let verdicts_json: Vec<_> = result.verdicts.iter().map(|v| json!({
        "expert_id":   v.expert_id,
        "expert_name": v.expert_name,
        "trit":        v.trit,
        "confidence":  v.confidence,
        "reasoning":   v.reasoning,
    })).collect();

    Json(json!({
        "trit":          result.trit,
        "label":         trit_label,
        "confidence":    result.confidence,
        "held":          result.held,
        "safety_vetoed": result.safety_vetoed,
        "temperature":   result.temperature,
        "prompt_hint":   result.prompt_hint,
        "pair":          pair_json,
        "verdicts":      verdicts_json,
        "triad_field": {
            "synergy_weight": result.triad_field.synergy_weight,
            "is_amplifying":  result.triad_field.is_amplifying(),
        },
    })).into_response()
}

// ─── GET /api/stream/moe_orchestrate ─────────────────────────────────────────
//
// SSE stream of a full MoE-13 orchestration pass, broken into discrete events:
//
//   event: routing       — which expert pair was selected and why
//   event: verdict       — each expert's individual verdict (one event per expert)
//   event: triad         — the emergent triad field
//   event: safety        — safety gate result (veto or pass)
//   event: vote          — weighted vote result
//   event: tiebreaker    — tiebreaker verdict (if invoked)
//   event: result        — final OrchestrationResult
//   event: done          — stream sentinel
//
// Query params: query (string), evidence (comma-separated floats, optional)
// Header: X-Ternlang-Key

#[derive(serde::Deserialize)]
struct MoeStreamParams {
    query:    Option<String>,
    evidence: Option<String>,   // "0.6,0.7,0.8,0.5,0.4,0.9"
}

async fn stream_moe_orchestrate(
    Query(params): Query<MoeStreamParams>,
) -> Sse<impl tokio_stream::Stream<Item = Result<Event, Infallible>>> {
    let query = params.query.unwrap_or_else(|| "default query".into());
    let evidence: Vec<f32> = params.evidence
        .as_deref()
        .unwrap_or("")
        .split(',')
        .filter_map(|s| s.trim().parse::<f32>().ok())
        .collect();

    // Build all events synchronously (orchestration is CPU-bound, not I/O-bound),
    // then stream them with inter-event delays so the client sees progressive delivery.
    let events = build_moe_sse_events(query, evidence);

    let stream = tokio_stream::iter(events)
        .then(|ev| async move {
            tokio::time::sleep(tokio::time::Duration::from_millis(80)).await;
            Ok::<Event, Infallible>(ev)
        });

    Sse::new(stream).keep_alive(
        axum::response::sse::KeepAlive::new()
            .interval(tokio::time::Duration::from_secs(15))
            .text("keep-alive"),
    )
}

fn build_moe_sse_events(query: String, evidence: Vec<f32>) -> Vec<Event> {
    let mut events = Vec::new();
    let evidence_ref: &[f32] = &evidence;

    // Run full orchestration
    let mut orch = TernMoeOrchestrator::with_standard_experts();
    let result   = orch.orchestrate(&query, evidence_ref);

    // ── event: routing ──────────────────────────────────────────────
    if let Some(ref pair) = result.pair {
        let routing_data = json!({
            "event":     "routing",
            "expert_a":  pair.expert_a,
            "expert_b":  pair.expert_b,
            "relevance": (pair.relevance * 1000.0).round() / 1000.0,
            "synergy":   (pair.synergy  * 1000.0).round() / 1000.0,
            "combined":  (pair.combined * 1000.0).round() / 1000.0,
            "note": format!(
                "Selected experts {} + {} — relevance {:.2}, synergy {:.2} (complementarity)",
                pair.expert_a, pair.expert_b, pair.relevance, pair.synergy
            ),
        });
        events.push(
            Event::default()
                .event("routing")
                .data(routing_data.to_string())
        );
    }

    // ── event: verdict (one per expert) ─────────────────────────────
    for verdict in &result.verdicts {
        let label = match verdict.trit { 1 => "affirm", -1 => "reject", _ => "tend" };
        let verdict_data = json!({
            "event":       "verdict",
            "expert_id":   verdict.expert_id,
            "expert_name": verdict.expert_name,
            "trit":        verdict.trit,
            "label":       label,
            "confidence":  (verdict.confidence * 1000.0).round() / 1000.0,
            "reasoning":   verdict.reasoning,
        });
        events.push(
            Event::default()
                .event("verdict")
                .data(verdict_data.to_string())
        );
    }

    // ── event: triad ─────────────────────────────────────────────────
    let triad_data = json!({
        "event":          "triad",
        "synergy_weight": (result.triad_field.synergy_weight * 1000.0).round() / 1000.0,
        "field":          result.triad_field.field.raw,
        "is_amplifying":  result.triad_field.is_amplifying(),
        "note": if result.triad_field.is_amplifying() {
            "Emergent field is amplifying — triad synthesis boosting signal."
        } else {
            "Emergent field computed — synergy below amplification threshold."
        },
    });
    events.push(Event::default().event("triad").data(triad_data.to_string()));

    // ── event: safety ────────────────────────────────────────────────
    let safety_data = json!({
        "event":   "safety",
        "vetoed":  result.safety_vetoed,
        "safety_field": result.triad_field.field.raw[5],
        "status":  if result.safety_vetoed { "VETO — hard block engaged" } else { "pass" },
    });
    events.push(Event::default().event("safety").data(safety_data.to_string()));

    if result.safety_vetoed {
        // Early-terminate: emit result + done
        let result_data = json!({
            "event":          "result",
            "trit":           result.trit,
            "label":          "reject",
            "confidence":     1.0,
            "held":           false,
            "safety_vetoed":  true,
            "temperature":    result.temperature,
            "prompt_hint":    result.prompt_hint,
        });
        events.push(Event::default().event("result").data(result_data.to_string()));
        events.push(Event::default().event("done").data("{}"));
        return events;
    }

    // ── event: vote ──────────────────────────────────────────────────
    let vote_label = match result.trit { 1 => "affirm", -1 => "reject", _ => "tend" };
    let vote_data = json!({
        "event":       "vote",
        "trit":        result.trit,
        "label":       vote_label,
        "confidence":  (result.confidence * 1000.0).round() / 1000.0,
        "held":        result.held,
        "note": if result.held {
            "Result held — tiebreaker invoked or confidence below threshold."
        } else {
            "Vote resolved."
        },
    });
    events.push(Event::default().event("vote").data(vote_data.to_string()));

    // ── event: tiebreaker (if held and >2 verdicts) ───────────────────
    if result.verdicts.len() > 2 {
        if let Some(tb) = result.verdicts.last() {
            let tb_label = match tb.trit { 1 => "affirm", -1 => "reject", _ => "tend" };
            let tb_data = json!({
                "event":       "tiebreaker",
                "expert_id":   tb.expert_id,
                "expert_name": tb.expert_name,
                "trit":        tb.trit,
                "label":       tb_label,
                "confidence":  (tb.confidence * 1000.0).round() / 1000.0,
                "reasoning":   tb.reasoning,
            });
            events.push(Event::default().event("tiebreaker").data(tb_data.to_string()));
        }
    }

    // ── event: result ─────────────────────────────────────────────────
    let result_data = json!({
        "event":         "result",
        "trit":          result.trit,
        "label":         vote_label,
        "confidence":    (result.confidence * 1000.0).round() / 1000.0,
        "held":          result.held,
        "safety_vetoed": false,
        "temperature":   (result.temperature * 1000.0).round() / 1000.0,
        "prompt_hint":   result.prompt_hint,
        "verdicts":      result.verdicts.len(),
    });
    events.push(Event::default().event("result").data(result_data.to_string()));

    // ── event: done ───────────────────────────────────────────────────
    events.push(Event::default().event("done").data("{}"));

    events
}

// ─── GET /api/stream/deliberate ──────────────────────────────────────────────
//
// SSE stream of an EMA deliberation session — emits one event per round:
//
//   event: round     — { round, scalar, confidence, trit, label, converged }
//   event: result    — final summary after all rounds
//   event: done      — stream sentinel
//
// Query params:
//   initial   — starting scalar [-1, 1] (default 0.0)
//   target    — target confidence [0, 1] (default 0.8)
//   alpha     — EMA smoothing [0, 1] (default 0.4)
//   rounds    — comma-separated evidence scalars, one per round

#[derive(serde::Deserialize)]
struct DeliberateStreamParams {
    initial: Option<f32>,
    target:  Option<f32>,
    alpha:   Option<f32>,
    rounds:  Option<String>,  // "0.2,0.5,0.7,0.8,0.9"
}

async fn stream_deliberate(
    Query(params): Query<DeliberateStreamParams>,
) -> Sse<impl tokio_stream::Stream<Item = Result<Event, Infallible>>> {
    let initial = params.initial.unwrap_or(0.0f32).clamp(-1.0, 1.0);
    let target  = params.target.unwrap_or(0.8f32).clamp(0.0, 1.0);
    let alpha   = params.alpha.unwrap_or(0.4f32).clamp(0.01, 1.0);

    let round_signals: Vec<f32> = params.rounds
        .as_deref()
        .unwrap_or("")
        .split(',')
        .filter_map(|s| s.trim().parse::<f32>().ok())
        .collect();

    let evidence_rounds: Vec<Vec<f32>> = if round_signals.is_empty() {
        // Default: 10 rounds replaying the initial signal
        vec![vec![initial]; 10]
    } else {
        round_signals.iter().map(|&v| vec![v]).collect()
    };

    let engine = DeliberationEngine::new(target, evidence_rounds.len()).with_alpha(alpha);
    let result = engine.run(evidence_rounds);

    let mut events: Vec<Event> = result.trace.iter().map(|r| {
        let label = match r.scalar.trit_i8() { 1 => "affirm", -1 => "reject", _ => "tend" };
        let round_data = json!({
            "event":      "round",
            "round":      r.round,
            "scalar":     (r.scalar.raw() * 1000.0).round() / 1000.0,
            "confidence": (r.scalar.confidence() * 1000.0).round() / 1000.0,
            "trit":       r.scalar.trit_i8(),
            "label":      label,
            "converged":  r.converged,
        });
        Event::default().event("round").data(round_data.to_string())
    }).collect();

    let final_label = match result.final_trit { 1 => "affirm", -1 => "reject", _ => "tend" };
    let result_data = json!({
        "event":              "result",
        "final_trit":         result.final_trit,
        "final_label":        final_label,
        "final_confidence":   (result.final_confidence * 1000.0).round() / 1000.0,
        "converged":          result.converged,
        "rounds_used":        result.rounds_used,
        "convergence_reason": result.convergence_reason,
        "target_confidence":  target,
    });
    events.push(Event::default().event("result").data(result_data.to_string()));
    events.push(Event::default().event("done").data("{}"));

    let stream = tokio_stream::iter(events)
        .then(|ev| async move {
            tokio::time::sleep(tokio::time::Duration::from_millis(120)).await;
            Ok::<Event, Infallible>(ev)
        });

    Sse::new(stream).keep_alive(
        axum::response::sse::KeepAlive::new()
            .interval(tokio::time::Duration::from_secs(15))
            .text("keep-alive"),
    )
}

// ─── MCP prompts ─────────────────────────────────────────────────────────────

fn mcp_prompts_list() -> Value {
    json!([
        {
            "name": "ternary_reasoning",
            "description": "Activate ternary reasoning mode. Use trit_decide or trit_vector when evidence is mixed or incomplete, moe_orchestrate for complex multi-faceted queries, and trit_action_gate before any irreversible action. Return hold (trit=0) instead of guessing when confidence is insufficient.",
            "arguments": []
        },
        {
            "name": "decision_audit",
            "description": "Audit an AI system's decision log for binary habituation and EU AI Act compliance. Use trit_audit to detect over-reliance on YES/NO decisions, trit_calibrate to score calibration quality, and surface hold opportunities that were collapsed to binary guesses.",
            "arguments": [
                {
                    "name": "decisions",
                    "description": "JSON array of {input, output, confidence?} decision records to audit.",
                    "required": true
                }
            ]
        }
    ])
}

// ─── GET /.well-known/mcp/server-card.json — Smithery scan skip ──────────────
//
// Smithery reads this to skip the automatic scanning step.
// Without it, Smithery tries GET /mcp which returns 405.

async fn mcp_server_card() -> Json<Value> {
    // Smithery expects exactly this shape at /.well-known/mcp/server-card.json
    // to bypass the live scan and use the static card instead.
    // Format confirmed from Smithery CLI output (.smithery/shttp/manifest.json → serverCard).
    // mcp_tools_manifest() already hoists annotations.title → top-level title.
    let tools = mcp_tools_manifest()["tools"].clone();
    Json(json!({
        "serverInfo": {
            "name":    "ternlang-mcp",
            "version": "1.1.8"
        },
        "tools":     tools,
        "resources": [],
        "prompts":   mcp_prompts_list()
    }))
}

// ─── GET /mcp — server info (Smithery scans this with GET before POST) ────────

async fn mcp_info() -> Json<Value> {
    Json(json!({
        "name":        "ternlang-mcp",
        "version":     "0.3.3",
        "protocol":    "2025-03-26",
        "transport":   "http",
        "endpoint":    "https://ternlang.com/mcp",
        "usage":       "POST JSON-RPC 2.0 — methods: initialize, tools/list, tools/call",
        "tools":         34,
        "free_tools":    34,
        "premium_tools": 0,
        "auth":        "all MCP tools free — no key required | upgrade for REST API: Tier 2 €99/mo (10k/mo) | Tier 3 €349/mo (50k/mo) | Tier 4 enterprise — see ternlang.com/pricing",
        "highlight":   "server-side 3-layer memory (working/session/core) + ternary attention + MoE-13 deliberation + ternary compression",
        "upgrade":     "https://ternlang.com/pricing",
    }))
}

// ─── POST /mcp — HTTP MCP transport (Smithery / Claude Desktop HTTP mode) ─────
//
// Smithery requires a live HTTP URL.  This endpoint implements JSON-RPC 2.0
// over HTTP POST, mirroring the stdio ternlang-mcp binary exactly.
// No API key required — the MCP server is a public capability endpoint.
//
// Supported methods:
//   initialize              — capability handshake
//   notifications/initialized — client ack (returns {})
//   tools/list              — full tool manifest
//   tools/call              — dispatch to a tool by name

#[derive(Deserialize)]
#[allow(dead_code)]
struct McpRpcRequest {
    #[allow(dead_code)]
    jsonrpc: Option<String>,
    id:      Option<Value>,
    method:  String,
    params:  Option<Value>,
}

// All 34 MCP tools are free — no API key required.
// Premium upsell is via the REST API (rate limits, SSE streaming, SLA).
const MCP_PREMIUM_TOOLS: &[&str] = &[];

async fn mcp_handler(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
    raw: Bytes,
) -> Json<Value> {
    // Log every incoming MCP request body for debugging
    let snippet = String::from_utf8_lossy(&raw);
    println!("[MCP] request ({} bytes) ct={:?}: {}",
        raw.len(),
        headers.get("content-type").and_then(|v| v.to_str().ok()).unwrap_or("-"),
        &snippet[..snippet.len().min(400)]);

    // Parse body — accept any JSON (object, array, empty)
    let parsed: Value = if raw.is_empty() {
        Value::Object(Default::default())
    } else {
        match serde_json::from_slice(&raw) {
            Ok(v) => v,
            Err(e) => {
                println!("[MCP] parse error: {e}");
                return Json(json!({
                    "jsonrpc": "2.0", "id": null,
                    "error": { "code": -32700, "message": "Parse error — invalid JSON" }
                }));
            }
        }
    };

    let id     = parsed.get("id").cloned().unwrap_or(Value::Null);
    let params = parsed.get("params").cloned().unwrap_or(Value::Object(Default::default()));
    let method_owned = parsed.get("method").and_then(|m| m.as_str()).unwrap_or("").to_string();
    let req_method   = method_owned.as_str();

    // Resolve premium access: check X-Ternlang-Key header
    let raw_key = headers.get("x-ternlang-key")
        .or_else(|| headers.get("X-Ternlang-Key"))
        .and_then(|v| v.to_str().ok())
        .unwrap_or("");
    let is_premium = if raw_key.is_empty() {
        false
    } else {
        matches!(state.keys.validate_and_bump(raw_key).await, KeyCheckResult::Valid(_))
    };

    let result: Value = match req_method {

        "initialize" => {
            // Echo back whatever protocolVersion the client requested.
            // Smithery requires 2025-03-26 (Streamable HTTP); older clients send 2024-11-05.
            // Strict MCP validators reject a version mismatch, so we always honour the client.
            let client_version = params
                .get("protocolVersion")
                .and_then(|v| v.as_str())
                .unwrap_or("2025-03-26");
            println!("[MCP] initialize from {:?} proto={}",
                params.get("clientInfo").and_then(|c| c.get("name")).and_then(|n| n.as_str()).unwrap_or("?"),
                client_version);
            json!({
                "jsonrpc": "2.0", "id": id,
                "result": {
                    "protocolVersion": client_version,
                    "capabilities": { "tools": {}, "prompts": {} },
                    "serverInfo": {
                        "name":    "ternlang-mcp",
                        "version": "1.1.7"
                    }
                }
            })
        },

        "notifications/initialized" => json!({ "jsonrpc": "2.0", "id": id, "result": {} }),

        "prompts/list" => json!({
            "jsonrpc": "2.0", "id": id,
            "result": { "prompts": mcp_prompts_list() }
        }),

        "tools/list" => json!({
            "jsonrpc": "2.0", "id": id,
            "result": mcp_tools_manifest()
        }),

        "tools/call" => {
            let tool_name = match params["name"].as_str() {
                Some(n) => n.to_string(),
                None => return Json(json!({
                    "jsonrpc": "2.0", "id": id,
                    "error": { "code": -32602, "message": "missing tool name" }
                })),
            };
            // Gate premium tools
            if MCP_PREMIUM_TOOLS.contains(&tool_name.as_str()) && !is_premium {
                // Return a tool-level error (result.isError), NOT a protocol-level error.
                // Protocol errors count as failures in Smithery uptime tracking; tool errors do not.
                return Json(json!({
                    "jsonrpc": "2.0", "id": id,
                    "result": {
                        "content": [{ "type": "text", "text": format!(
                            "'{}' is a premium tool. Pass a valid X-Ternlang-Key header (Tier 2+). Get a key at https://ternlang.com/pricing",
                            tool_name
                        )}],
                        "isError": false
                    }
                }));
            }
            let tool_params = &params["arguments"];
            let mem = Arc::clone(&state.memory_store);
            match mcp_dispatch_tool(&tool_name, tool_params, raw_key, &mem) {
                Ok(res) => json!({
                    "jsonrpc": "2.0", "id": id,
                    "result": {
                        "content": [{ "type": "text", "text": serde_json::to_string_pretty(&res).unwrap_or_default() }]
                    }
                }),
                // Return tool-level non-errors — keeps uptime healthy in Smithery
                Err(e) => json!({
                    "jsonrpc": "2.0", "id": id,
                    "result": {
                        "content": [{ "type": "text", "text": e }],
                        "isError": false
                    }
                }),
            }
        }

        other => json!({
            "jsonrpc": "2.0", "id": id,
            "error": { "code": -32601, "message": format!("method not found: {}", other) }
        }),
    };

    Json(result)
}

// ─── MCP tool dispatch ────────────────────────────────────────────────────────

fn mcp_dispatch_tool(name: &str, params: &Value, api_key: &str, mem: &MemStore) -> Result<Value, String> {
    let mut result = match name {
        "trit_decide"      => mcp_trit_decide(params),
        "trit_consensus"   => mcp_trit_consensus(params),
        "trit_eval"        => mcp_trit_eval(params),
        "ternlang_run"     => mcp_ternlang_run(params),
        "quantize_weights" => mcp_quantize_weights(params),
        "sparse_benchmark" => mcp_sparse_benchmark(params),
        "moe_orchestrate"  => mcp_moe_orchestrate(params),
        "moe_deliberate"   => mcp_moe_deliberate(params),
        "trit_action_gate"     => mcp_trit_action_gate(params),
        "trit_upgrade"         => mcp_trit_upgrade(),
        // ── Premium tools (key validated by mcp_handler before reaching here) ──
        "trit_compress"        => mcp_trit_compress(params),
        "trit_triage"          => mcp_trit_triage(params),
        "trit_plan"            => mcp_trit_plan(params),
        "trit_factcheck"       => mcp_trit_factcheck(params),
        "moe_full"             => mcp_moe_full(params),
        "trit_mem_write"       => mcp_trit_mem_write(params, api_key, mem),
        "trit_mem_read"        => mcp_trit_mem_read(params, api_key, mem),
        "trit_mem_consolidate" => mcp_trit_mem_consolidate(params, api_key, mem),
        "trit_mem_stats"       => mcp_trit_mem_stats(api_key, mem),
        "trit_mem_compress"    => mcp_trit_mem_compress(params, api_key, mem),
        // Phase 11A + Phase 13
        "trit_debate"          => mcp_trit_debate(params),
        "trit_uncertainty_map" => mcp_trit_uncertainty_map(params),
        "trit_calibrate"       => mcp_trit_calibrate(params),
        "trit_translate"       => mcp_trit_translate(params),
        "trit_eco_check"       => mcp_trit_eco_check(params),
        "trit_audit"           => mcp_trit_audit(params),
        // Free tools: multi-dimensional evidence, enterprise join, code audit, standards
        "trit_vector"              => mcp_trit_vector(params),
        "tsql_join"                => mcp_tsql_join(params),
        "audit_ternary_logic"      => mcp_audit_ternary_logic(params),
        "get_industrial_standards" => mcp_get_industrial_standards(),
        "llb_check"                => mcp_llb_check(params),
        "llb_classify"             => mcp_llb_classify(params),
        "llb_validate"             => mcp_llb_validate(params),
        "llb_write_safe"           => mcp_llb_write_safe(params),
        _ => Err(format!("unknown tool: {}", name)),
    }?;

    // Inject upsell footer into every successful tool response.
    // AI agents include this in context; users see the upgrade path naturally.
    if let Some(obj) = result.as_object_mut() {
        obj.insert("_ternlang".to_string(), json!({
            "mode":          "mcp_free",
            "rest_endpoint": format!("POST https://ternlang.com/api/{}", name.replace('_', "/")),
            "tier2_unlocks": "10 000 calls/month · production SLA · all 13 MoE experts · streaming SSE",
            "tier3_unlocks": "Unlimited · enterprise SLA · priority support",
            "get_key":       "https://ternlang.com/#licensing",
            "pricing":       "https://ternlang.com/pricing",
        }));
    }

    Ok(result)
}

// ─── trit_decide ─────────────────────────────────────────────────────────────

fn mcp_trit_decide(params: &Value) -> Result<Value, String> {
    let evidence: Vec<f32> = params["evidence"]
        .as_array().ok_or("evidence must be an array")?
        .iter()
        .map(|v| v.as_f64().ok_or("evidence values must be numbers").map(|f| f as f32))
        .collect::<Result<_, _>>()?;
    if evidence.is_empty() { return Err("evidence cannot be empty".into()); }

    let mean: f32 = evidence.iter().sum::<f32>() / evidence.len() as f32;
    let scalar = TritScalar::new(mean);
    let per_signal: Vec<Value> = evidence.iter().enumerate().map(|(i, &v)| {
        let s = TritScalar::new(v);
        json!({ "index": i, "raw": (v*1000.0).round()/1000.0, "label": s.label(),
                "confidence": (s.confidence()*1000.0).round()/1000.0, "trit": s.trit_i8() })
    }).collect();
    let threshold = params["threshold"].as_f64().unwrap_or(TEND_BOUNDARY as f64) as f32;
    let trit_val = if mean > threshold { 1i8 } else if mean < -threshold { -1 } else { 0 };
    let label = match trit_val { 1 => "affirm", -1 => "reject", _ => "tend" };
    Ok(json!({
        "scalar":     (scalar.raw()*1000.0).round()/1000.0,
        "trit":       trit_val,
        "label":      label,
        "confidence": (scalar.confidence()*1000.0).round()/1000.0,
        "per_signal": per_signal,
    }))
}

// ─── trit_consensus ───────────────────────────────────────────────────────────

fn mcp_trit_consensus(params: &Value) -> Result<Value, String> {
    let a = params["a"].as_i64().ok_or("a must be -1, 0, or 1")?;
    let b = params["b"].as_i64().ok_or("b must be -1, 0, or 1")?;
    if ![-1,0,1].contains(&a) { return Err(format!("a={} is not a valid trit", a)); }
    if ![-1,0,1].contains(&b) { return Err(format!("b={} is not a valid trit", b)); }
    let result = if a == 1 && b == 1 { 1i64 } else if a == -1 && b == -1 { -1 } else { 0 };
    let label = match result { 1 => "affirm", -1 => "reject", _ => "tend" };
    Ok(json!({ "result": result, "label": label,
               "expression": format!("consensus({}, {}) = {}", a, b, result) }))
}

// ─── trit_eval ────────────────────────────────────────────────────────────────

fn mcp_trit_eval(params: &Value) -> Result<Value, String> {
    let code = params["expression"].as_str().ok_or("expression must be a string")?;
    let full_code = if code.trim_end().ends_with(';') {
        code.to_string()
    } else {
        format!("return {};", code)
    };
    let mut parser  = Parser::new(&full_code);
    let mut emitter = BytecodeEmitter::new();
    loop {
        match parser.parse_stmt() {
            Ok(stmt) => emitter.emit_stmt(&stmt),
            Err(e)   => {
                if format!("{:?}", e).contains("EOF") { break; }
                return Err(format!("parse error: {:?}", e));
            }
        }
    }
    let mut vm = BetVm::new(emitter.finalize());
    vm.run().map_err(|e| format!("vm error: {}", e))?;
    Ok(json!({ "expression": params["expression"],
               "result_register_0": format!("{:?}", vm.get_register(0)) }))
}

// ─── ternlang_run ─────────────────────────────────────────────────────────────

fn mcp_ternlang_run(params: &Value) -> Result<Value, String> {
    let code = params["code"].as_str().ok_or("code must be a string")?;
    let mut parser  = Parser::new(code);
    let mut emitter = BytecodeEmitter::new();
    match parser.parse_program() {
        Ok(prog) => emitter.emit_program(&prog),
        Err(_)   => {
            let mut p2 = Parser::new(code);
            loop {
                match p2.parse_stmt() {
                    Ok(stmt) => emitter.emit_stmt(&stmt),
                    Err(e)   => {
                        if format!("{:?}", e).contains("EOF") { break; }
                        return Err(format!("parse error: {:?}", e));
                    }
                }
            }
        }
    }
    let bytecode = emitter.finalize();
    let len = bytecode.len();
    let mut vm = BetVm::new(bytecode);
    vm.run().map_err(|e| format!("vm error: {}", e))?;
    let registers: Vec<Value> = (0..10).map(|i| format!("{:?}", vm.get_register(i)).into()).collect();
    Ok(json!({ "status": "ok", "bytecode_bytes": len, "registers": registers }))
}

// ─── quantize_weights ─────────────────────────────────────────────────────────

fn mcp_quantize_weights(params: &Value) -> Result<Value, String> {
    let weights: Vec<f32> = params["weights"]
        .as_array().ok_or("weights must be an array")?
        .iter()
        .map(|v| v.as_f64().ok_or("weight values must be numbers").map(|f| f as f32))
        .collect::<Result<_, _>>()?;
    let threshold = params["threshold"].as_f64()
        .unwrap_or_else(|| bitnet_threshold(&weights) as f64) as f32;
    let trits: Vec<i8> = weights.iter().map(|&w| {
        if w > threshold { 1 } else if w < -threshold { -1 } else { 0 }
    }).collect();
    let zeros = trits.iter().filter(|&&t| t == 0).count();
    Ok(json!({ "trits": trits, "threshold_used": threshold,
               "sparsity": zeros as f64 / trits.len() as f64,
               "nnz": trits.len() - zeros, "total": trits.len() }))
}

// ─── sparse_benchmark ─────────────────────────────────────────────────────────

fn mcp_sparse_benchmark(params: &Value) -> Result<Value, String> {
    let size = params["size"].as_u64().unwrap_or(8) as usize;
    let rows = size;
    let cols = size;
    let weights: Vec<f32> = (0..rows*cols).map(|i| {
        match i % 5 { 0 => 0.9f32, 1 => -0.8, 2 => 0.1, 3 => -0.1, _ => 0.05 }
    }).collect();
    let threshold = params["threshold"].as_f64()
        .unwrap_or_else(|| bitnet_threshold(&weights) as f64) as f32;
    let w     = TritMatrix::from_f32(rows, cols, &weights, threshold);
    let input = TritMatrix::new(rows, cols);
    let r     = benchmark(&input, &w);
    let (_, skipped) = sparse_matmul(&input, &w);
    Ok(json!({
        "size": size, "weight_sparsity": r.weight_sparsity, "skip_rate": r.skip_rate,
        "dense_ops": r.dense_ops, "sparse_ops": r.sparse_ops, "skipped_ops": skipped,
        "ops_reduction_factor": r.dense_ops as f64 / r.sparse_ops.max(1) as f64,
    }))
}

// ─── moe_orchestrate ──────────────────────────────────────────────────────────

fn mcp_moe_orchestrate(params: &Value) -> Result<Value, String> {
    let query = params["query"].as_str().ok_or("query must be a string")?;
    let evidence: Vec<f32> = match params["evidence"].as_array() {
        Some(arr) => arr.iter()
            .map(|v| v.as_f64().ok_or("evidence values must be numbers").map(|f| f as f32))
            .collect::<Result<_, _>>()?,
        None => vec![0.0f32; 6],
    };

    // MCP runs the full 13-expert orchestration internally so the trit/confidence
    // result is real and trustworthy. The response surfaces only the 3 highest-
    // confidence expert verdicts — enough to demonstrate the system, not enough to
    // replace the full REST API result (which includes all 13 verdicts, triad field,
    // routing pair, SSE streaming, and production rate-limit guarantees).
    let mut orch = TernMoeOrchestrator::with_standard_experts();
    let result   = orch.orchestrate(query, &evidence);
    let trit_label = match result.trit { 1 => "affirm", -1 => "reject", _ => "tend" };
    let total_experts = result.verdicts.len();

    // Sort by confidence descending, take top 3
    let mut sorted_verdicts = result.verdicts.clone();
    sorted_verdicts.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap_or(std::cmp::Ordering::Equal));
    let preview_verdicts: Vec<Value> = sorted_verdicts.iter().take(3).map(|v| json!({
        "expert_id":   v.expert_id,
        "expert_name": v.expert_name,
        "trit":        v.trit,
        "confidence":  (v.confidence*1000.0).round()/1000.0,
        "reasoning":   v.reasoning,
    })).collect();

    Ok(json!({
        "trit":       result.trit,
        "label":      trit_label,
        "confidence": (result.confidence*1000.0).round()/1000.0,
        "held":             result.held,
        "safety_vetoed":    result.safety_vetoed,
        "prompt_hint":      result.prompt_hint,
        "mode":             "preview",
        "experts_shown":    3,
        "experts_total":    total_experts,
        "verdicts_preview": preview_verdicts,
        "full_access": {
            "note":     "Full response includes all 13 expert verdicts, triad field, routing pair, and synergy scores.",
            "endpoint": "POST https://ternlang.com/api/moe/orchestrate",
            "header":   "X-Ternlang-Key: <your-tier2-key>",
            "get_key":  "https://ternlang.com/#licensing",
        },
    }))
}

// ─── moe_deliberate ───────────────────────────────────────────────────────────

fn mcp_moe_deliberate(params: &Value) -> Result<Value, String> {
    let target    = params["target_confidence"].as_f64().ok_or("target_confidence required")? as f32;
    let alpha     = params["alpha"].as_f64().unwrap_or(0.4) as f32;
    let max_rounds = params["max_rounds"].as_u64().unwrap_or(10) as usize;
    let rounds_evidence: Vec<Vec<f32>> = params["rounds_evidence"]
        .as_array().ok_or("rounds_evidence must be an array of arrays")?
        .iter()
        .map(|round| {
            round.as_array().ok_or("each round must be an array").map(|arr| {
                arr.iter().map(|v| v.as_f64().unwrap_or(0.0) as f32).collect()
            })
        })
        .collect::<Result<_, _>>()?;
    let engine = DeliberationEngine::new(target, max_rounds).with_alpha(alpha);
    let result = engine.run(rounds_evidence);
    let trit_label = match result.final_trit { 1 => "affirm", -1 => "reject", _ => "tend" };
    let rounds: Vec<Value> = result.trace.iter().map(|r| json!({
        "round": r.round, "scalar": (r.scalar.raw()*1000.0).round()/1000.0,
        "confidence": (r.scalar.confidence()*1000.0).round()/1000.0,
        "trit": r.scalar.trit_i8(), "converged": r.converged,
    })).collect();
    Ok(json!({
        "final_confidence": (result.final_confidence*1000.0).round()/1000.0,
        "trit": result.final_trit, "label": trit_label,
        "converged": result.converged, "rounds_used": result.rounds_used,
        "convergence_reason": result.convergence_reason, "rounds": rounds,
    }))
}

// ─── trit_action_gate ─────────────────────────────────────────────────────────

fn mcp_trit_action_gate(params: &Value) -> Result<Value, String> {
    let dims_raw = params["dimensions"].as_array().ok_or("dimensions must be an array")?;
    let dims: Vec<GateDimension> = dims_raw.iter().map(|d| {
        let name       = d["name"].as_str().unwrap_or("dim").to_string();
        let evidence   = d["evidence"].as_f64().unwrap_or(0.0) as f32;
        let weight     = d["weight"].as_f64().unwrap_or(1.0) as f32;
        let hard_block = d["hard_block"].as_bool().unwrap_or(false);
        let mut dim = GateDimension::new(name, evidence, weight);
        if hard_block { dim = dim.hard(); }
        dim
    }).collect();
    if dims.is_empty() { return Err("dimensions cannot be empty".into()); }
    let result = action_gate(&dims);
    let verdict_label = match result.verdict {
        GateVerdict::Proceed => "proceed",
        GateVerdict::Block   => "blocked",
        GateVerdict::Hold    => "tend",
    };
    let dim_details: Vec<Value> = result.dim_results.iter().map(|(name, scalar, is_hard)| json!({
        "name": name, "evidence": (scalar.raw()*1000.0).round()/1000.0,
        "trit": scalar.trit_i8(), "hard_block": is_hard,
        "status": if *is_hard && scalar.trit_i8() < 0 { "VETO" }
                  else if scalar.trit_i8() > 0 { "pass" }
                  else if scalar.trit_i8() < 0 { "block" } else { "hold" },
    })).collect();
    Ok(json!({
        "verdict": verdict_label,
        "hard_blocked_by": result.hard_blocked_by,
        "aggregate_scalar": (result.aggregate.raw()*1000.0).round()/1000.0,
        "aggregate_confidence": (result.aggregate.confidence()*1000.0).round()/1000.0,
        "dimensions": dim_details, "explanation": result.explanation,
    }))
}

// ─── trit_upgrade ─────────────────────────────────────────────────────────────
//
// Replaces the trit_enlighten easter egg with a genuine commercial onboarding
// tool. When an AI agent calls this, the response gives the user a clear,
// actionable map of what free MCP provides vs what a Tier 2 key unlocks.
// Agents should surface this when a user asks "what can I do with ternlang?"

fn mcp_trit_upgrade() -> Result<Value, String> {
    Ok(json!({
        "trit":  1,
        "label": "affirm",
        "summary": "You are currently using Ternlang via the free MCP tier. Here is exactly what you have now and what unlocks with a key.",

        "current_tier": {
            "name":        "MCP Free",
            "tools":       10,
            "rate_limit":  "none — MCP calls are unmetered",
            "sla":         "best-effort",
            "moe_experts": "3-of-13 preview (full trit result, top-3 verdicts shown)",
            "streaming":   false,
            "rest_api":    false,
        },

        "tier2": {
            "name":        "Tier 2 — Pro Standard",
            "price":       "€99/month",
            "monthly_calls": 20000,
            "rate_limit":  "20,000 REST calls / month, resets 1st UTC",
            "sla":         "production",
            "moe_experts": "all 13 experts · full verdicts · triad field · routing pair",
            "streaming":   "GET /api/stream/moe_orchestrate — SSE, event-per-expert",
            "rest_api":    true,
            "endpoints":   [
                "POST /api/trit_decide",
                "POST /api/trit_deliberate",
                "POST /api/trit_coalition",
                "POST /api/trit_gate",
                "POST /api/scalar_temperature",
                "POST /api/hallucination_score",
                "POST /api/quantize_weights",
                "POST /api/sparse_benchmark",
                "POST /api/moe/orchestrate",
                "GET  /api/stream/moe_orchestrate",
                "GET  /api/stream/deliberate",
                "GET  /api/usage",
            ],
            "get_key":     "https://buy.stripe.com/5kQ28t7SM4rB0DH6jm7N608",
            "pricing":     "https://ternlang.com/pricing",
        },

        "tier3": {
            "name":        "Tier 3 — Industrial",
            "price":       "€349/month",
            "monthly_calls": 50000,
            "rate_limit":  "50,000 REST calls / month, resets 1st UTC",
            "sla":         "production — elevated quota",
            "moe_experts": "all 13 experts · full verdicts · triad field · routing pair",
            "streaming":   "GET /api/stream/moe_orchestrate — SSE, event-per-expert",
            "rest_api":    true,
            "get_key":     "https://buy.stripe.com/eVq7sNfle0bl86937a7N609",
            "pricing":     "https://ternlang.com/pricing",
        },

        "tier4": {
            "name":       "Tier 4 — Enterprise",
            "price":       "from €10,000/year (full suite)",
            "calls":      "unlimited",
            "sla":        "enterprise — priority support + uptime commitment",
            "extras":     "custom rate limits · team key management · invoice billing · on-premise option",
            "contact":    "contact@ternlang.com",
        },

        "why_upgrade": [
            "Full MoE-13 orchestration with all 13 expert verdicts and synergy scores",
            "Server-sent events (SSE) streaming — watch deliberation happen in real time",
            "Production rate-limiting and SLA suitable for user-facing applications",
            "hallucination_score and scalar_temperature for AI safety pipelines",
            "trit_coalition for multi-agent voting on shared queries",
        ],

        "quick_start": "Add header X-Ternlang-Key: <your-key> to any POST https://ternlang.com/api/* request.",
        "all_mcp_tools_free": true,
        "api_key_unlocks": [
            "Server-side persistent 3-layer memory (instead of stateless blob mode)",
            "REST API access with rate-limited quota (10k–50k calls/month)",
            "SSE streaming — watch MoE deliberation in real time",
            "Production SLA suitable for user-facing applications",
        ],
    }))
}

// ─── Helpers: ternary text scoring ────────────────────────────────────────────

/// Word-level overlap score ∈ [0, 1] between two strings.
fn word_overlap(a: &str, b: &str) -> f32 {
    let a_words: std::collections::HashSet<&str> = a.split_whitespace()
        .map(|w| w.trim_matches(|c: char| !c.is_alphanumeric()))
        .filter(|w| w.len() > 2)
        .collect();
    let b_words: std::collections::HashSet<&str> = b.split_whitespace()
        .map(|w| w.trim_matches(|c: char| !c.is_alphanumeric()))
        .filter(|w| w.len() > 2)
        .collect();
    if a_words.is_empty() || b_words.is_empty() { return 0.0; }
    let intersection = a_words.intersection(&b_words).count();
    intersection as f32 / a_words.len().max(b_words.len()) as f32
}

/// Score → trit: > 0.4 = +1, 0.15..0.4 = 0, < 0.15 = -1
fn score_to_trit(s: f32) -> i8 {
    if s > 0.4 { 1 } else if s >= 0.15 { 0 } else { -1 }
}

/// First sentence of a string (for compression summaries).
fn first_sentence(s: &str) -> &str {
    s.find(['.', '!', '?']).map(|i| &s[..=i]).unwrap_or(s).trim()
}

/// Information density: unique tokens / total tokens.
fn info_density(s: &str) -> f32 {
    let words: Vec<&str> = s.split_whitespace().collect();
    if words.is_empty() { return 0.0; }
    let unique: std::collections::HashSet<&str> = words.iter().copied().collect();
    unique.len() as f32 / words.len() as f32
}

// ─── Premium tool: trit_compress ──────────────────────────────────────────────

fn mcp_trit_compress(params: &Value) -> Result<Value, String> {
    let chunks: Vec<&str> = params["chunks"].as_array()
        .ok_or("chunks must be an array of strings")?
        .iter()
        .map(|v| v.as_str().unwrap_or(""))
        .collect();
    if chunks.is_empty() { return Err("chunks cannot be empty".into()); }
    let query = params["query"].as_str().unwrap_or("");

    let mut kept = 0usize;
    let mut compressed = 0usize;
    let mut dropped = 0usize;

    let manifest: Vec<Value> = chunks.iter().enumerate().map(|(i, chunk)| {
        let score = if query.is_empty() {
            info_density(chunk)
        } else {
            word_overlap(query, chunk)
        };
        let trit = score_to_trit(score);
        let (action, compressed_form): (&str, Option<&str>) = match trit {
            1  => { kept      += 1; ("keep_verbatim",  None) }
            0  => { compressed+= 1; ("compress",       Some(first_sentence(chunk))) }
            _  => { dropped   += 1; ("drop",           None) }
        };
        json!({
            "index":           i,
            "trit":            trit,
            "label":           if trit==1 {"affirm"} else if trit==0 {"tend"} else {"reject"},
            "action":          action,
            "score":           (score * 1000.0).round() / 1000.0,
            "compressed_form": compressed_form,
        })
    }).collect();

    let total = chunks.len();
    let savings_pct = ((dropped + compressed / 2) as f32 / total as f32 * 100.0).round();
    Ok(json!({
        "manifest":        manifest,
        "total_chunks":    total,
        "kept_verbatim":   kept,
        "compressed":      compressed,
        "dropped":         dropped,
        "estimated_savings_pct": savings_pct,
        "reconstruction_note": "Reconstruct by: keep +1 verbatim; expand 0 from compressed_form; omit -1.",
    }))
}

// ─── Premium tool: trit_triage ────────────────────────────────────────────────

fn mcp_trit_triage(params: &Value) -> Result<Value, String> {
    let chunks = params["chunks"].as_array()
        .ok_or("chunks must be an array of {id, text} objects")?;
    let query = params["query"].as_str().ok_or("query is required")?;
    if chunks.is_empty() { return Err("chunks cannot be empty".into()); }

    let mut scored: Vec<(f32, Value)> = chunks.iter().map(|c| {
        let id   = c["id"].as_str().unwrap_or("?");
        let text = c["text"].as_str().unwrap_or("");
        let score = word_overlap(query, text);
        let trit  = score_to_trit(score);
        (score, json!({
            "id":         id,
            "trit":       trit,
            "label":      if trit==1 {"affirm"} else if trit==0 {"tend"} else {"reject"},
            "relevance":  (score * 1000.0).round() / 1000.0,
            "action":     if trit==1 {"include"} else if trit==0 {"maybe"} else {"exclude"},
        }))
    }).collect();

    scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
    let results: Vec<Value> = scored.into_iter().map(|(_, v)| v).collect();
    let include_count = results.iter().filter(|v| v["trit"].as_i64()==Some(1)).count();

    Ok(json!({
        "results":        results,
        "query":          query,
        "include_count":  include_count,
        "note": "Sort order: highest relevance first. Include affirm (+1), use judgement on tend (0), exclude reject (-1).",
    }))
}

// ─── Premium tool: trit_plan ─────────────────────────────────────────────────

fn mcp_trit_plan(params: &Value) -> Result<Value, String> {
    let goal  = params["goal"].as_str().ok_or("goal is required")?;
    let steps: Vec<&str> = params["steps"].as_array()
        .ok_or("steps must be an array of strings")?
        .iter().map(|v| v.as_str().unwrap_or("")).collect();
    let constraints: Vec<&str> = params["constraints"].as_array()
        .map(|a| a.iter().map(|v| v.as_str().unwrap_or("")).collect())
        .unwrap_or_default();
    if steps.is_empty() { return Err("steps cannot be empty".into()); }

    let plan: Vec<Value> = steps.iter().enumerate().map(|(i, step)| {
        // Check constraint violations first
        let violated: Vec<&str> = constraints.iter()
            .filter(|c| word_overlap(step, c) > 0.3)
            .copied().collect();
        if !violated.is_empty() {
            return json!({
                "index": i, "step": step, "trit": -1, "label": "reject",
                "confidence": 0.0,
                "reasoning": format!("Conflicts with constraint: '{}'", violated[0]),
                "flag": "constraint_violation",
            });
        }
        let goal_score  = word_overlap(goal, step);
        let trit        = score_to_trit(goal_score);
        let (reasoning, flag): (String, Option<&str>) = match trit {
            1  => (format!("Clearly advances goal (relevance {:.2})", goal_score), None),
            0  => (format!("Partial alignment (relevance {:.2}). Needs clarification before executing.", goal_score), Some("needs_evidence")),
            _  => (format!("Low goal alignment (relevance {:.2}). Consider removing or rephrasing.", goal_score), Some("low_value")),
        };
        json!({
            "index": i, "step": step, "trit": trit,
            "label": if trit==1 {"affirm"} else if trit==0 {"tend"} else {"reject"},
            "confidence": (goal_score * 1000.0).round() / 1000.0,
            "reasoning": reasoning, "flag": flag,
        })
    }).collect();

    let ready    = plan.iter().filter(|s| s["trit"].as_i64()==Some(1)).count();
    let hold_    = plan.iter().filter(|s| s["trit"].as_i64()==Some(0)).count();
    let blocked  = plan.iter().filter(|s| s["trit"].as_i64()==Some(-1)).count();
    Ok(json!({
        "goal": goal, "plan": plan,
        "summary": { "ready": ready, "needs_evidence": hold_, "blocked": blocked },
        "note": "Execute affirm (+1) steps. Gather more information for tend (0) steps before proceeding. Remove or rethink reject (-1) steps.",
    }))
}

// ─── Premium tool: trit_factcheck ────────────────────────────────────────────

fn mcp_trit_factcheck(params: &Value) -> Result<Value, String> {
    let claims: Vec<&str> = params["claims"].as_array()
        .ok_or("claims must be an array of strings")?
        .iter().map(|v| v.as_str().unwrap_or("")).collect();
    let context = params["context"].as_str().unwrap_or("");
    if claims.is_empty() { return Err("claims cannot be empty".into()); }

    const HEDGES: &[&str] = &[
        "might", "could", "possibly", "perhaps", "maybe", "likely", "unlikely",
        "seems", "appears", "suggests", "reportedly", "allegedly", "may",
    ];
    const ABSOLUTES: &[&str] = &[
        "always", "never", "all", "every", "none", "definitely", "certainly",
        "impossible", "guaranteed", "proven", "fact",
    ];

    let verdicts: Vec<Value> = claims.iter().enumerate().map(|(i, claim)| {
        let lower = claim.to_lowercase();
        let has_hedge    = HEDGES.iter().any(|h| lower.contains(h));
        let has_absolute = ABSOLUTES.iter().any(|a| lower.contains(a));

        let (trit, label, reasoning) = if !context.is_empty() {
            let overlap = word_overlap(claim, context);
            if overlap > 0.4 {
                (1i8, "affirm", "Context supports this claim.".to_string())
            } else if overlap > 0.15 {
                (0i8, "tend", "Partial context overlap — needs additional verification.".to_string())
            } else if has_absolute {
                (-1i8, "reject", format!("Absolute statement ('{}') with no contextual support.", claim.split_whitespace().find(|w| ABSOLUTES.iter().any(|a| w.to_lowercase().contains(a))).unwrap_or("?")))
            } else {
                (0i8, "tend", "Not addressed in provided context — external source needed.".to_string())
            }
        } else if has_hedge {
            (0i8, "tend", "Hedged claim — not verifiable without a source.".to_string())
        } else if has_absolute {
            (0i8, "tend", "Absolute claim — requires citation before accepting.".to_string())
        } else {
            (0i8, "tend", "No context provided. Treat as unverified.".to_string())
        };

        json!({
            "index": i, "claim": claim, "trit": trit, "label": label, "reasoning": reasoning,
        })
    }).collect();

    let affirmed = verdicts.iter().filter(|v| v["trit"].as_i64()==Some(1)).count();
    let held     = verdicts.iter().filter(|v| v["trit"].as_i64()==Some(0)).count();
    let rejected = verdicts.iter().filter(|v| v["trit"].as_i64()==Some(-1)).count();
    Ok(json!({
        "verdicts": verdicts,
        "summary": { "affirmed": affirmed, "needs_verification": held, "rejected": rejected },
        "note": "tend (0) = needs external source or more context. reject (-1) = contradicted by context.",
    }))
}

// ─── Premium tool: moe_full (all 13 experts, no preview cap) ─────────────────

fn mcp_moe_full(params: &Value) -> Result<Value, String> {
    let query = params["query"].as_str().ok_or("query must be a string")?;
    let evidence: Vec<f32> = match params["evidence"].as_array() {
        Some(arr) => arr.iter()
            .map(|v| v.as_f64().ok_or("evidence values must be numbers").map(|f| f as f32))
            .collect::<Result<_,_>>()?,
        None => vec![0.0f32; 6],
    };
    let mut orch  = TernMoeOrchestrator::with_standard_experts();
    let result    = orch.orchestrate(query, &evidence);
    let trit_label = match result.trit { 1 => "affirm", -1 => "reject", _ => "tend" };

    let verdicts: Vec<Value> = result.verdicts.iter().map(|v| json!({
        "expert_id": v.expert_id, "expert_name": v.expert_name,
        "trit": v.trit, "confidence": (v.confidence*1000.0).round()/1000.0,
        "reasoning": v.reasoning,
    })).collect();
    let pair_info = result.pair.as_ref().map(|p| json!({
        "expert_a": p.expert_a, "expert_b": p.expert_b,
        "relevance": (p.relevance*1000.0).round()/1000.0,
        "synergy":   (p.synergy *1000.0).round()/1000.0,
        "combined":  (p.combined*1000.0).round()/1000.0,
    }));
    Ok(json!({
        "trit": result.trit, "label": trit_label,
        "confidence":    (result.confidence*1000.0).round()/1000.0,
        "held":          result.held,
        "safety_vetoed": result.safety_vetoed,
        "temperature":   (result.temperature*1000.0).round()/1000.0,
        "prompt_hint":   result.prompt_hint,
        "triad_field": {
            "synergy_weight": (result.triad_field.synergy_weight*1000.0).round()/1000.0,
            "field":          result.triad_field.field.raw,
            "is_amplifying":  result.triad_field.is_amplifying(),
        },
        "routing_pair": pair_info,
        "verdicts":     verdicts,
        "experts_total": result.verdicts.len(),
        "mode": "full_premium",
    }))
}

// ─── Premium tool: three-layer memory ────────────────────────────────────────
//
// Server-side design (premium keys): memory is stored in AppState.memory_store,
// keyed by API key. No state blob needed in the request.
//
// Fallback (no key): stateless blob mode — caller passes and receives "state".
//
// Schema per key: { "v": 2, "working": [...], "session": [...], "core": [...] }
// Each entry: { "k": "key", "v": "value", "trit": i8, "ts": u64, "ttl": u64 }
//
// Layer semantics:
//   working  — hot context, current turn   (LRU-256, TTL 1h,  evicts on consolidate)
//   session  — session flow / routing      (LRU-128, TTL 24h, promotes affirm to core)
//   core     — identity anchors / vetoes   (unlimited, never evicts automatically)
//
// Ternary attention on read:
//   score = key_overlap*0.35 + value_overlap*0.55 + trit_bias*0.10
//   trit_bias = (entry_trit + 1) / 2   → maps -1→0, 0→0.5, +1→1
//   result trit: score>0.45→affirm, 0.20–0.45→tend, <0.20→reject (excluded)
//
// Ternary compression on session/core writes:
//   Split value by sentence boundaries; score each sentence by info_density.
//   density>0.50 → keep verbatim; 0.25–0.50 → keep first phrase; <0.25 → drop.

const WORKING_TTL: u64  = 3_600;     // 1h
const SESSION_TTL: u64  = 86_400;    // 24h
const WORKING_CAP: usize = 256;
const SESSION_CAP: usize = 128;

fn mem_now() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default().as_secs()
}

fn mem_load(api_key: &str, mem: &MemStore, params: &Value) -> MemBlob {
    if !api_key.is_empty() {
        // Server-side: ignore params state blob
        mem.read().unwrap()
            .get(api_key).cloned()
            .unwrap_or_default()
    } else {
        // Stateless fallback: use blob from params
        params["state"].as_object().cloned().unwrap_or_default()
    }
}

fn mem_save(api_key: &str, mem: &MemStore, blob: MemBlob) {
    if !api_key.is_empty() {
        mem.write().unwrap().insert(api_key.to_string(), blob);
    }
}

fn mem_layer_mut<'a>(state: &'a mut MemBlob, layer: &str) -> &'a mut Vec<Value> {
    state.entry(layer.to_string())
        .or_insert_with(|| json!([]))
        .as_array_mut()
        .expect("layer must be array")
}

/// Ternary compression: strip low-information sentences from a value string.
/// Returns the compressed string (may be shorter than input).
fn trit_compress_text(text: &str) -> String {
    let sentences: Vec<&str> = text.split(". ").collect();
    let mut out = Vec::new();
    for sentence in &sentences {
        let s = sentence.trim();
        if s.is_empty() { continue; }
        let d = info_density(s);
        if d > 0.50 {
            out.push(s.to_string());
        } else if d > 0.25 {
            // Keep first phrase (up to first comma or 10 words)
            let first_phrase: String = s.split(',').next()
                .unwrap_or(s)
                .split_whitespace().take(10)
                .collect::<Vec<_>>().join(" ");
            if !first_phrase.is_empty() { out.push(first_phrase); }
        }
        // d <= 0.25: drop (tend/noise)
    }
    out.join(". ")
}

/// Ternary attention score for a memory entry against a query.
fn trit_attention(query: &str, entry: &Value) -> f32 {
    let k = entry["k"].as_str().unwrap_or("");
    let v = entry["v"].as_str().unwrap_or("");
    let entry_trit = entry["trit"].as_i64().unwrap_or(0) as f32;
    let trit_bias = (entry_trit + 1.0) / 2.0;  // -1→0, 0→0.5, +1→1

    let key_overlap   = word_overlap(query, k);
    let value_overlap = word_overlap(query, v);
    key_overlap * 0.35 + value_overlap * 0.55 + trit_bias * 0.10
}

fn mcp_trit_mem_write(params: &Value, api_key: &str, mem: &MemStore) -> Result<Value, String> {
    let layer = params["layer"].as_str().ok_or("layer must be 'working', 'session', or 'core'")?;
    if !["working","session","core"].contains(&layer) {
        return Err(format!("invalid layer '{}'. Use 'working', 'session', or 'core'.", layer));
    }
    let key   = params["key"].as_str().ok_or("key is required")?;
    let raw_value = params["value"].as_str().ok_or("value is required")?;
    let trit  = params["trit"].as_i64().unwrap_or(1).clamp(-1, 1) as i8;
    let ttl   = params["ttl_secs"].as_u64().unwrap_or(match layer {
        "working" => WORKING_TTL, "session" => SESSION_TTL, _ => u64::MAX
    });

    // Ternary compression for session/core layers (strip tend-noise sentences)
    let (stored_value, compressed) = if layer != "working" {
        let c = trit_compress_text(raw_value);
        let changed = c.len() < raw_value.len();
        (c, changed)
    } else {
        (raw_value.to_string(), false)
    };

    let mut state = mem_load(api_key, mem, params);
    state.insert("v".into(), json!(2));

    let entries = mem_layer_mut(&mut state, layer);
    entries.retain(|e| e["k"].as_str() != Some(key));
    let cap = match layer { "working" => WORKING_CAP, "session" => SESSION_CAP, _ => usize::MAX };
    while entries.len() >= cap { entries.remove(0); }
    entries.push(json!({
        "k": key, "v": stored_value, "trit": trit,
        "ts": mem_now(), "ttl": ttl,
        "compressed": compressed,
    }));

    let counts = json!({
        "working": state.get("working").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0),
        "session": state.get("session").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0),
        "core":    state.get("core"   ).and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0),
    });
    let server_side = !api_key.is_empty();
    let state_blob  = if server_side { Value::Null } else { json!(state.clone()) };
    mem_save(api_key, mem, state);

    Ok(json!({
        "written":     { "layer": layer, "key": key, "trit": trit, "compressed": compressed },
        "counts":      counts,
        "server_side": server_side,
        "state":       state_blob,  // null when server_side=true
    }))
}

fn mcp_trit_mem_read(params: &Value, api_key: &str, mem: &MemStore) -> Result<Value, String> {
    let query  = params["query"].as_str().ok_or("query is required")?;
    let layers_raw = params["layers"].as_array();
    let target_layers: Vec<&str> = layers_raw
        .map(|a| a.iter().filter_map(|v| v.as_str()).collect())
        .unwrap_or_else(|| vec!["working", "session", "core"]);

    let state = mem_load(api_key, mem, params);
    let now   = mem_now();

    let mut results: Vec<(f32, Value)> = Vec::new();
    for layer in &target_layers {
        let entries = match state.get(*layer).and_then(|v| v.as_array()) {
            Some(a) => a,
            None    => continue,
        };
        for entry in entries {
            let ts  = entry["ts"].as_u64().unwrap_or(0);
            let ttl = entry["ttl"].as_u64().unwrap_or(u64::MAX);
            if now.saturating_sub(ts) > ttl { continue; } // expired

            // Ternary attention score (replaces plain word_overlap)
            let score = trit_attention(query, entry);
            // Attention thresholds: >0.45=affirm, 0.20–0.45=tend, <0.20=reject (excluded)
            if score < 0.20 { continue; }
            let attn_trit = if score > 0.45 { 1i8 } else { 0i8 };

            results.push((score, json!({
                "layer":       layer,
                "key":         entry["k"],
                "value":       entry["v"],
                "entry_trit":  entry["trit"],
                "attn_trit":   attn_trit,
                "attn_label":  if attn_trit == 1 { "affirm" } else { "tend" },
                "relevance":   (score * 1000.0).round() / 1000.0,
                "age_secs":    now.saturating_sub(ts),
            })));
        }
    }
    results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
    let hits: Vec<Value> = results.into_iter().map(|(_, v)| v).collect();
    let affirm_count = hits.iter().filter(|h| h["attn_trit"].as_i64() == Some(1)).count();
    Ok(json!({
        "results":     hits,
        "query":       query,
        "hits":        hits.len(),
        "affirm_hits": affirm_count,
        "server_side": !api_key.is_empty(),
    }))
}

fn mcp_trit_mem_consolidate(params: &Value, api_key: &str, mem: &MemStore) -> Result<Value, String> {
    let mut state = mem_load(api_key, mem, params);
    if state.is_empty() { return Err("no memory state found — write some entries first".into()); }
    let now = mem_now();

    let mut promoted_to_session = 0usize;
    let mut promoted_to_core    = 0usize;
    let mut evicted             = 0usize;
    let mut moe_resolved        = 0usize;

    // ── 1. Working → evict expired, promote affirm to session ─────────────────
    let working: Vec<Value> = state.get("working")
        .and_then(|v| v.as_array()).cloned().unwrap_or_default();
    let mut new_working  = Vec::new();
    let mut new_promotes = Vec::new();
    for entry in &working {
        let ts  = entry["ts"].as_u64().unwrap_or(0);
        let ttl = entry["ttl"].as_u64().unwrap_or(WORKING_TTL);
        if now.saturating_sub(ts) > ttl { evicted += 1; continue; }
        if entry["trit"].as_i64() == Some(1) {
            // Compress value before promoting to session
            let compressed = trit_compress_text(entry["v"].as_str().unwrap_or(""));
            let mut promoted = entry.clone();
            if let Some(obj) = promoted.as_object_mut() {
                obj.insert("ttl".into(),        json!(SESSION_TTL));
                obj.insert("ts".into(),         json!(now));
                obj.insert("v".into(),          json!(compressed));
                obj.insert("compressed".into(), json!(true));
            }
            new_promotes.push(promoted);
            promoted_to_session += 1;
        } else {
            new_working.push(entry.clone());
        }
    }

    // ── 2. Session → evict expired, promote affirm at half-life to core ───────
    let session: Vec<Value> = state.get("session")
        .and_then(|v| v.as_array()).cloned().unwrap_or_default();
    let all_session: Vec<Value> = session.into_iter().chain(new_promotes).collect();
    let mut new_session   = Vec::new();
    let mut core_promotes = Vec::new();
    for entry in &all_session {
        let ts  = entry["ts"].as_u64().unwrap_or(0);
        let ttl = entry["ttl"].as_u64().unwrap_or(SESSION_TTL);
        if now.saturating_sub(ts) > ttl { evicted += 1; continue; }
        if entry["trit"].as_i64() == Some(1) && now.saturating_sub(ts) > SESSION_TTL / 2 {
            // MoE-backed resolution: re-run the key through MoE to get canonical trit
            let k = entry["k"].as_str().unwrap_or("");
            let mut orch = TernMoeOrchestrator::with_standard_experts();
            let moe_result = orch.orchestrate(k, &[0.0f32; 6]);
            let canonical_trit = moe_result.trit;

            let mut promoted = entry.clone();
            if let Some(obj) = promoted.as_object_mut() {
                obj.insert("trit".into(),     json!(canonical_trit));
                obj.insert("moe_resolved".into(), json!(true));
            }
            core_promotes.push(promoted);
            promoted_to_core += 1;
            moe_resolved     += 1;
        } else {
            new_session.push(entry.clone());
        }
    }

    // ── 3. Core — merge promotions, upsert by key (never evicts) ─────────────
    let mut core: Vec<Value> = state.get("core")
        .and_then(|v| v.as_array()).cloned().unwrap_or_default();
    for entry in &core_promotes {
        let key = entry["k"].as_str().unwrap_or("");
        core.retain(|e| e["k"].as_str() != Some(key));
        core.push(entry.clone());
    }

    state.insert("working".into(), json!(new_working));
    state.insert("session".into(), json!(new_session));
    state.insert("core".into(),    json!(core));
    state.insert("v".into(),       json!(2));

    let counts = json!({
        "working": new_working.len(),
        "session": new_session.len(),
        "core":    core.len(),
    });
    let server_side = !api_key.is_empty();
    let state_blob  = if server_side { Value::Null } else { json!(state.clone()) };
    mem_save(api_key, mem, state);

    Ok(json!({
        "consolidation": {
            "evicted":             evicted,
            "promoted_to_session": promoted_to_session,
            "promoted_to_core":    promoted_to_core,
            "moe_resolved":        moe_resolved,
        },
        "counts":      counts,
        "server_side": server_side,
        "state":       state_blob,
    }))
}

// ─── Premium tool: trit_mem_stats ────────────────────────────────────────────

fn mcp_trit_mem_stats(api_key: &str, mem: &MemStore) -> Result<Value, String> {
    // No key → return empty stats (stateless blob mode has no server-side store to inspect)
    if api_key.is_empty() {
        let empty = json!({"count":0,"expired":0,"live":0,"trit_dist":{"affirm":0,"tend":0,"reject":0},"oldest_secs":0,"newest_secs":0});
        return Ok(json!({
            "working": empty.clone(), "session": empty.clone(), "core": empty.clone(),
            "total_entries": {"working":0,"session":0,"core":0},
            "note": "No API key — running in stateless blob mode. Pass the 'state' blob returned by trit_mem_write to maintain memory across calls. Upgrade to Tier 2 for persistent server-side storage.",
        }));
    }
    let state = mem.read().unwrap().get(api_key).cloned().unwrap_or_default();
    let now   = mem_now();

    let layer_stats = |layer: &str| {
        let entries = state.get(layer).and_then(|v| v.as_array()).cloned().unwrap_or_default();
        let count   = entries.len();
        let mut affirm = 0usize; let mut tend = 0usize; let mut reject = 0usize;
        let mut oldest = u64::MAX; let mut newest = 0u64;
        let mut expired = 0usize;
        for e in &entries {
            match e["trit"].as_i64() {
                Some(1)  => affirm  += 1,
                Some(0)  => tend    += 1,
                Some(-1) => reject  += 1,
                _        => {}
            }
            let ts  = e["ts"].as_u64().unwrap_or(now);
            let ttl = e["ttl"].as_u64().unwrap_or(u64::MAX);
            if now.saturating_sub(ts) > ttl { expired += 1; }
            if ts < oldest { oldest = ts; }
            if ts > newest { newest = ts; }
        }
        json!({
            "count":      count,
            "expired":    expired,
            "live":       count.saturating_sub(expired),
            "trit_dist":  { "affirm": affirm, "tend": tend, "reject": reject },
            "oldest_secs": if oldest == u64::MAX { 0 } else { now.saturating_sub(oldest) },
            "newest_secs": now.saturating_sub(newest),
        })
    };

    Ok(json!({
        "working": layer_stats("working"),
        "session": layer_stats("session"),
        "core":    layer_stats("core"),
        "total_entries": {
            "working": state.get("working").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0),
            "session": state.get("session").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0),
            "core":    state.get("core"   ).and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0),
        },
        "note": "Run trit_mem_consolidate to evict expired entries and promote affirm entries up the layers.",
    }))
}

// ─── Premium tool: trit_mem_compress ─────────────────────────────────────────
//
// Applies ternary compression to a specific memory layer in-place:
// re-runs trit_compress_text on every entry's value, drops reject-trit entries
// if `drop_reject` is set, and reports the byte savings.

fn mcp_trit_mem_compress(params: &Value, api_key: &str, mem: &MemStore) -> Result<Value, String> {
    let layer = params["layer"].as_str().ok_or("layer must be 'working', 'session', or 'core'")?;
    if !["working","session","core"].contains(&layer) {
        return Err(format!("invalid layer '{}'", layer));
    }
    let drop_reject = params["drop_reject"].as_bool().unwrap_or(false);

    let mut state = mem_load(api_key, mem, params);
    let entries: Vec<Value> = state.get(layer).and_then(|v| v.as_array()).cloned().unwrap_or_default();

    let mut original_bytes: usize = 0;
    let mut compressed_bytes: usize = 0;
    let mut dropped = 0usize;
    let mut new_entries = Vec::new();

    for mut entry in entries {
        if drop_reject && entry["trit"].as_i64() == Some(-1) {
            dropped += 1;
            continue;
        }
        if let Some(v) = entry["v"].as_str() {
            original_bytes += v.len();
            let c = trit_compress_text(v);
            compressed_bytes += c.len();
            if let Some(obj) = entry.as_object_mut() {
                obj.insert("v".into(),          json!(c));
                obj.insert("compressed".into(), json!(true));
            }
        }
        new_entries.push(entry);
    }

    state.insert(layer.to_string(), json!(new_entries));
    let server_side = !api_key.is_empty();
    let state_blob  = if server_side { Value::Null } else { json!(state.clone()) };
    mem_save(api_key, mem, state);

    let saved = original_bytes.saturating_sub(compressed_bytes);
    let ratio  = if original_bytes > 0 { compressed_bytes as f32 / original_bytes as f32 } else { 1.0 };
    Ok(json!({
        "layer":             layer,
        "entries_processed": new_entries.len(),
        "entries_dropped":   dropped,
        "original_bytes":    original_bytes,
        "compressed_bytes":  compressed_bytes,
        "saved_bytes":       saved,
        "compression_ratio": (ratio * 1000.0).round() / 1000.0,
        "server_side":       server_side,
        "state":             state_blob,
        "note": format!("{}% size reduction via ternary sparsity compression.", (100.0 - ratio*100.0).round() as u32),
    }))
}

// ─── MCP tool manifest ────────────────────────────────────────────────────────

// ─── Phase 11A + Phase 13 premium tools ──────────────────────────────────────

fn mcp_trit_debate(params: &Value) -> Result<Value, String> {
    let claim_a = params["claim_a"].as_str().ok_or("claim_a must be a string")?;
    let claim_b = params["claim_b"].as_str().ok_or("claim_b must be a string")?;
    let context  = params["context"].as_str().unwrap_or("");
    let mut orch = TernMoeOrchestrator::with_standard_experts();
    let query_a = if context.is_empty() { format!("Evaluate: {}", claim_a) }
                  else { format!("Context: {}. Evaluate: {}", context, claim_a) };
    let query_b = if context.is_empty() { format!("Evaluate: {}", claim_b) }
                  else { format!("Context: {}. Evaluate: {}", context, claim_b) };
    let res_a = orch.orchestrate(&query_a, &[0.0; 6]);
    let res_b = orch.orchestrate(&query_b, &[0.0; 6]);
    let ta = res_a.trit;
    let tb = res_b.trit;
    let tension: f32 = (res_a.confidence - res_b.confidence).abs()
        .max(if ta != tb { 0.6 } else { 0.1 });
    let label_i8 = |t: i8| if t == 1 { "affirm" } else if t == -1 { "reject" } else { "tend" };
    let synthesis = match (ta, tb) {
        (1,  1)  => "Both claims have supporting evidence — complementary, not contradictory.",
        (-1,-1)  => "Both claims face contradicting evidence — neither is well-supported.",
        (1, -1)  => "Claim A is supported; Claim B is contradicted. Evidence favours A.",
        (-1, 1)  => "Claim B is supported; Claim A is contradicted. Evidence favours B.",
        (0,  _)  => "Claim A is uncertain. More evidence needed before comparing.",
        (_,  0)  => "Claim B is uncertain. More evidence needed before comparing.",
        _        => "Both claims remain in deliberation — insufficient evidence to decide.",
    };
    Ok(json!({
        "claim_a": claim_a, "claim_b": claim_b,
        "for_a": { "trit": ta, "label": label_i8(ta), "confidence": (res_a.confidence*100.0).round()/100.0 },
        "for_b": { "trit": tb, "label": label_i8(tb), "confidence": (res_b.confidence*100.0).round()/100.0 },
        "tension": (tension*100.0).round()/100.0, "synthesis": synthesis,
        "verdict": if ta == tb { "AGREEMENT" } else if ta == 0 || tb == 0 { "HOLD" } else { "CONFLICT" }
    }))
}

fn mcp_trit_uncertainty_map(params: &Value) -> Result<Value, String> {
    let text        = params["text"].as_str().ok_or("text must be a string")?;
    let granularity = params["granularity"].as_str().unwrap_or("sentence");
    let chunks: Vec<&str> = if granularity == "paragraph" {
        text.split("\n\n").map(str::trim).filter(|s| !s.is_empty()).collect()
    } else {
        text.split(|c| c == '.' || c == '?' || c == '!')
            .map(str::trim).filter(|s| !s.is_empty()).collect()
    };
    if chunks.is_empty() { return Err("text produced no chunks to analyse".into()); }
    let hedges  = ["maybe","possibly","could","might","unclear","uncertain","perhaps",
                   "seems","appears","suggests","approximately","roughly","often","sometimes"];
    let affirms = ["definitely","clearly","proven","confirmed","established","certainly",
                   "always","guaranteed","verified","demonstrates","shows"];
    let rejects = ["false","wrong","incorrect","impossible","never","debunked","disproved",
                   "contradicts","refutes","myth","invalid","denied"];
    let score_chunk = |chunk: &str| -> (i8, f32, &'static str) {
        let lower = chunk.to_lowercase();
        let ha: usize = affirms.iter().filter(|w| lower.contains(*w)).count();
        let hh: usize = hedges.iter().filter(|w| lower.contains(*w)).count();
        let hr: usize = rejects.iter().filter(|w| lower.contains(*w)).count();
        let total = (ha + hh + hr).max(1);
        if hh > ha && hh > hr { (0, 0.3 + 0.05 * hh.min(8) as f32, "tend") }
        else if hr > ha { (-1, 0.4 + 0.08 * (hr as f32 / total as f32), "reject") }
        else if ha > 0  { (1,  0.5 + 0.08 * (ha as f32 / total as f32), "affirm") }
        else            { (0,  0.25, "tend") }
    };
    let annotations: Vec<Value> = chunks.iter().map(|chunk| {
        let (trit, conf, label) = score_chunk(chunk);
        json!({ "claim": chunk, "trit": trit, "label": label,
                "confidence": (conf.min(1.0)*100.0).round()/100.0,
                "reason": match trit { 1 => "Affirming language.", -1 => "Negating language.", _ => "Hedging or insufficient signal." } })
    }).collect();
    let affirm_n = annotations.iter().filter(|a| a["trit"] == 1).count();
    let tend_n   = annotations.iter().filter(|a| a["trit"] == 0).count();
    let reject_n = annotations.iter().filter(|a| a["trit"] == -1).count();
    Ok(json!({
        "total_claims": annotations.len(),
        "summary": { "affirm": affirm_n, "tend": tend_n, "reject": reject_n },
        "uncertainty_ratio": (tend_n as f32 / annotations.len() as f32 * 100.0).round()/100.0,
        "annotations": annotations
    }))
}

fn mcp_trit_calibrate(params: &Value) -> Result<Value, String> {
    let decisions = params["decisions"].as_array().ok_or("decisions must be an array")?;
    if decisions.is_empty() { return Err("decisions array cannot be empty".into()); }
    let binary_words  = ["yes","no","true","false","accept","reject","allow","deny",
                         "approve","block","pass","fail","correct","incorrect","valid","invalid"];
    let hold_words    = ["maybe","uncertain","unclear","depends","possibly","need more",
                         "insufficient","context-dependent","further review","hold","tend"];
    let confident_ptn = ["definitely","certainly","always","never","absolutely","guaranteed"];
    let mut binary_count = 0usize;
    let mut hold_opps    = 0usize;
    let mut flagged: Vec<Value> = Vec::new();
    for d in decisions {
        let output     = d["output"].as_str().unwrap_or("").to_lowercase();
        let confidence = d["confidence"].as_f64().unwrap_or(0.5);
        let input      = d["input"].as_str().unwrap_or("");
        let is_binary    = binary_words.iter().any(|w| output.split_whitespace().any(|tok| tok.trim_matches(|c: char| !c.is_alphabetic()) == *w));
        let has_hedge    = hold_words.iter().any(|w| output.contains(*w));
        let is_confident = confident_ptn.iter().any(|w| output.contains(*w)) || confidence > 0.9;
        if is_binary && !has_hedge && is_confident {
            binary_count += 1; hold_opps += 1;
            flagged.push(json!({ "input": input, "output": d["output"].as_str().unwrap_or(""), "trit": -1,
                "reason": "Forced binary decision with high confidence — a tend zone may have been appropriate." }));
        } else if is_binary && !has_hedge { binary_count += 1; }
    }
    let n = decisions.len() as f32;
    let binary_ratio = binary_count as f32 / n;
    let calibration_trit: i8 = if binary_ratio < 0.2 { 1 } else if binary_ratio < 0.6 { 0 } else { -1 };
    let cal_label = if calibration_trit == 1 { "affirm" } else if calibration_trit == 0 { "tend" } else { "reject" };
    let recommendations: Vec<&str> = if binary_ratio > 0.6 {
        vec!["High binary habituation detected. Consider injecting tend zones for ambiguous inputs.",
             "Review flagged decisions — each represents a missed deliberation opportunity."]
    } else if binary_ratio > 0.2 {
        vec!["Moderate binary ratio. Add confidence thresholds to route low-confidence outputs to tend."]
    } else {
        vec!["Calibration is good. The agent is using the full ternary range."]
    };
    Ok(json!({
        "total_decisions": decisions.len(), "binary_count": binary_count,
        "binary_ratio": (binary_ratio*100.0).round()/100.0,
        "hold_opportunities": hold_opps,
        "calibration_score": { "trit": calibration_trit, "label": cal_label },
        "recommendations": recommendations, "flagged": flagged
    }))
}

fn mcp_trit_translate(params: &Value) -> Result<Value, String> {
    let code     = params["code"].as_str().ok_or("code must be a string")?;
    let language = params["language"].as_str().unwrap_or("python");
    let mut hold_zones = 0usize;
    let mut tern_lines: Vec<String> = Vec::new();
    let mut explanation_notes: Vec<String> = Vec::new();
    tern_lines.push(format!("// Translated from {} by trit_translate — RFI-IRFOS", language));
    tern_lines.push(format!("// Original: {} lines", code.lines().count()));
    tern_lines.push(String::new());
    match language {
        "python" => {
            let mut in_chain = false;
            let mut has_else = false;
            for line in code.lines() {
                let trimmed = line.trim();
                if trimmed.starts_with("if ") && trimmed.ends_with(':') {
                    in_chain = true; has_else = false;
                    let cond = &trimmed[3..trimmed.len()-1];
                    tern_lines.push(format!("let condition: trit = cast({});", cond));
                    tern_lines.push("match condition {".to_string());
                    tern_lines.push("     1 => {".to_string());
                } else if trimmed.starts_with("elif ") && trimmed.ends_with(':') {
                    let cond = &trimmed[5..trimmed.len()-1];
                    tern_lines.push("    }".to_string());
                    tern_lines.push(format!("    // elif {}", cond));
                    tern_lines.push("    -1 => {".to_string());
                } else if trimmed == "else:" {
                    has_else = true;
                    tern_lines.push("    }".to_string());
                    tern_lines.push("     0 => {".to_string());
                } else if trimmed.starts_with("return ") {
                    let val = &trimmed[7..];
                    let trit_val = if val.contains("True") || val.contains("1") { "truth()" }
                                   else if val.contains("False") || val.contains("0") { "conflict()" }
                                   else { "hold()" };
                    tern_lines.push(format!("        return {};", trit_val));
                } else if !trimmed.is_empty() {
                    tern_lines.push(format!("        // {}", trimmed));
                }
            }
            if in_chain {
                if !has_else {
                    tern_lines.push("    }".to_string());
                    tern_lines.push("     0 => {".to_string());
                    tern_lines.push("        // HOLD ZONE — no original else branch".to_string());
                    tern_lines.push("        return hold();".to_string());
                    hold_zones += 1;
                    explanation_notes.push("Injected tend arm: original if/elif had no else branch.".into());
                }
                tern_lines.push("    }".to_string());
                tern_lines.push("}".to_string());
            }
        }
        "sql" => {
            tern_lines.push("fn evaluate(signal: trit) -> trit {".to_string());
            let mut in_case = false;
            for line in code.lines() {
                let upper = line.trim().to_uppercase();
                if upper.starts_with("CASE") { in_case = true; tern_lines.push("    match signal {".to_string()); }
                else if upper.starts_with("WHEN") { tern_lines.push(format!("         1 => {{ // WHEN {}", line.trim()[4..].trim())); }
                else if upper.starts_with("THEN") { tern_lines.push(format!("            return truth(); // THEN {}", line.trim()[4..].trim())); tern_lines.push("        }".to_string()); }
                else if upper.starts_with("ELSE") { tern_lines.push(format!("        -1 => {{ return conflict(); }} // ELSE {}", line.trim()[4..].trim())); }
                else if upper.starts_with("END") && in_case {
                    tern_lines.push("         0 => { return hold(); } // NULL/unknown → hold".to_string());
                    tern_lines.push("    }".to_string());
                    hold_zones += 1; in_case = false;
                    explanation_notes.push("Injected tend arm: SQL CASE had no branch for NULL/unknown inputs.".into());
                }
            }
            tern_lines.push("}".to_string());
        }
        "json_rules" => {
            if let Ok(rules) = serde_json::from_str::<Vec<Value>>(code) {
                tern_lines.push("fn apply_rules(signal: trit) -> trit {".to_string());
                tern_lines.push("    match signal {".to_string());
                for rule in &rules {
                    let cond = rule["if"].as_str().unwrap_or("condition");
                    let then = rule["then"].as_str().unwrap_or("pass");
                    let tval = if then.contains("pass") || then.contains("allow") { "truth()" }
                               else if then.contains("deny") || then.contains("block") { "conflict()" }
                               else { "hold()" };
                    tern_lines.push(format!("         1 => {{ return {}; }} // if: {}", tval, cond));
                }
                tern_lines.push("         0 => { return hold(); } // HOLD ZONE — no rule matched".to_string());
                tern_lines.push("        -1 => { return conflict(); }".to_string());
                tern_lines.push("    }".to_string()); tern_lines.push("}".to_string());
                hold_zones += 1;
                explanation_notes.push("Injected tend arm for unmatched inputs.".into());
            } else { return Err("json_rules: code must be a valid JSON array of rule objects".into()); }
        }
        other => return Err(format!("unsupported language '{}'. Use: python, sql, json_rules", other)),
    }
    tern_lines.push(String::new());
    tern_lines.push("fn main() -> trit { return hold(); }".to_string());
    Ok(json!({
        "language": language, "hold_zones_added": hold_zones,
        "tern_code": tern_lines.join("\n"), "explanation": explanation_notes,
        "note": "Review the generated code — trit_translate is a heuristic tool."
    }))
}

fn mcp_trit_eco_check(params: &Value) -> Result<Value, String> {
    let action  = params["action"].as_str().ok_or("action must be a string")?;
    let context = params["context"].as_str().unwrap_or("");
    let scope   = params["scope"].as_str().unwrap_or("local");
    let mut orch = TernMoeOrchestrator::with_standard_experts();
    let human_query = if context.is_empty() { format!("Should we take this action? {}", action) }
                      else { format!("Context: {}. Should we take this action? {}", context, action) };
    let human_res = orch.orchestrate(&human_query, &[0.0; 6]);
    let human_trit: i8 = human_res.trit;
    let lower = format!("{} {}", action, context).to_lowercase();
    let eco_neg = ["carbon","emission","pollution","waste","deforestation","fossil","dump",
                   "toxic","landfill","exhaust","greenhouse","plastic","contamina"];
    let eco_pos = ["renewable","sustainable","solar","wind","recycl","reforest","organic",
                   "biodegradable","conservation","clean energy","low carbon","compost"];
    let eco_neu = ["digital","software","data","model","analysis","report","review","plan"];
    let neg_hits: usize = eco_neg.iter().filter(|w| lower.contains(*w)).count();
    let pos_hits: usize = eco_pos.iter().filter(|w| lower.contains(*w)).count();
    let neu_hits: usize = eco_neu.iter().filter(|w| lower.contains(*w)).count();
    let scope_mult = match scope { "global" => 2, "regional" => 1, _ => 0 };
    let neg_scaled = neg_hits + scope_mult * neg_hits / 2;
    let eco_trit: i8 = if pos_hits > neg_scaled { 1 } else if neg_scaled > pos_hits { -1 }
                       else if neu_hits > 0 { 0 } else { 0 };
    let eco_label = if eco_trit == 1 { "affirm" } else if eco_trit == -1 { "reject" } else { "tend" };
    let human_label = if human_trit == 1 { "affirm" } else if human_trit == -1 { "reject" } else { "tend" };
    let tension   = human_trit != eco_trit;
    let synthesis = if tension { 0i8 } else { human_trit };
    let syn_label = if synthesis == 1 { "affirm" } else if synthesis == -1 { "reject" } else { "tend" };
    Ok(json!({
        "action": action, "scope": scope,
        "human_trit": { "trit": human_trit, "label": human_label, "confidence": (human_res.confidence*100.0).round()/100.0 },
        "eco_trit":   { "trit": eco_trit,   "label": eco_label },
        "tension": tension,
        "synthesis": { "trit": synthesis, "label": syn_label },
        "note": if tension { "Human-optimal and eco-optimal diverge — synthesis held." } else { "Human and eco perspectives agree." }
    }))
}

fn mcp_trit_audit(params: &Value) -> Result<Value, String> {
    let decisions = params["decisions"].as_array().ok_or("decisions must be an array of {input, output, confidence?}")?;
    if decisions.is_empty() { return Err("decisions array cannot be empty".into()); }
    let binary_words  = ["yes","no","true","false","accept","reject","allow","deny",
                         "approve","block","pass","fail","correct","incorrect","valid","invalid"];
    let hold_words    = ["maybe","uncertain","unclear","depends","possibly","insufficient",
                         "further review","hold","tend","need more","context-dependent"];
    let high_conf_pat = ["definitely","certainly","always","never","absolutely","guaranteed","100%"];
    let mut affirm_n  = 0usize;
    let mut tend_n    = 0usize;
    let mut reject_n  = 0usize;
    let mut forced_binary = 0usize;
    let mut flagged: Vec<Value> = Vec::new();
    for d in decisions {
        let output     = d["output"].as_str().unwrap_or("").to_lowercase();
        let confidence = d["confidence"].as_f64().unwrap_or(-1.0);
        let input      = d["input"].as_str().unwrap_or("");
        let is_binary  = binary_words.iter().any(|w| output.split_whitespace()
            .any(|tok| tok.trim_matches(|c: char| !c.is_alphabetic()) == *w));
        let has_hedge  = hold_words.iter().any(|w| output.contains(*w));
        let is_forced  = high_conf_pat.iter().any(|w| output.contains(*w))
            || (confidence >= 0.0 && confidence > 0.9);
        if has_hedge { tend_n += 1; }
        else if output.contains("reject") || output.contains("deny") || output.contains("no") { reject_n += 1; }
        else { affirm_n += 1; }
        if is_binary && !has_hedge && is_forced {
            forced_binary += 1;
            flagged.push(json!({ "input": input, "output": d["output"].as_str().unwrap_or(""),
                "confidence": if confidence >= 0.0 { json!(confidence) } else { json!(null) }, "trit": 0,
                "reason": "Forced high-confidence binary decision — tend zone may have been appropriate." }));
        }
    }
    let n = decisions.len() as f32;
    let forced_binary_ratio = forced_binary as f32 / n;
    let tend_ratio          = tend_n as f32 / n;
    let art13 = if tend_ratio > 0.1 { "pass" } else { "warn" };
    let art14 = if forced_binary_ratio < 0.3 { "pass" } else { "fail" };
    Ok(json!({
        "total_decisions": decisions.len(),
        "affirm_count": affirm_n, "tend_count": tend_n, "reject_count": reject_n,
        "forced_binary_ratio": (forced_binary_ratio*100.0).round()/100.0,
        "tend_ratio": (tend_ratio*100.0).round()/100.0,
        "eu_ai_act": {
            "article_13_transparency":    art13,
            "article_14_human_oversight": art14,
            "note": "Heuristic assessment only — not a substitute for legal compliance review."
        },
        "calibration": {
            "score":  if forced_binary_ratio < 0.2 { "good" } else if forced_binary_ratio < 0.5 { "warn" } else { "poor" },
            "advice": if forced_binary_ratio < 0.2 { "Calibration looks healthy." }
                      else if forced_binary_ratio < 0.5 { "Moderate binary habituation. Add confidence thresholds." }
                      else { "High binary habituation. The agent is collapsing ambiguity." }
        },
        "flagged": flagged
    }))
}

// ─── trit_vector ─────────────────────────────────────────────────────────────

fn mcp_trit_vector(params: &Value) -> Result<Value, String> {
    let dims_raw = params["dimensions"].as_array()
        .ok_or("dimensions must be an array of {label, value, weight} objects")?;
    if dims_raw.is_empty() { return Err("dimensions cannot be empty".into()); }
    let min_confidence = params["min_confidence"].as_f64().unwrap_or(0.5) as f32;

    let mut labels  = Vec::new();
    let mut values  = Vec::new();
    let mut weights = Vec::new();
    for (i, d) in dims_raw.iter().enumerate() {
        let label  = d["label"].as_str().unwrap_or("unnamed").to_string();
        let value  = d["value"].as_f64()
            .ok_or_else(|| format!("dimensions[{}].value must be a number", i))? as f32;
        let weight = d["weight"].as_f64().unwrap_or(1.0) as f32;
        if weight < 0.0 {
            return Err(format!("dimensions[{}].weight must be >= 0", i));
        }
        labels.push(label);
        values.push(value.clamp(-1.0, 1.0));
        weights.push(weight);
    }
    let ev      = TritEvidenceVec::new(labels, values, weights);
    let agg     = ev.aggregate();
    let scalars = ev.scalars();
    let actionable = agg.is_actionable(min_confidence);
    let breakdown: Vec<Value> = ev.dimensions.iter()
        .zip(ev.values.iter())
        .zip(ev.weights.iter())
        .zip(scalars.iter())
        .map(|(((label, &val), &w), sc)| json!({
            "label":      label,
            "value":      (val * 1000.0).round() / 1000.0,
            "weight":     w,
            "trit":       sc.trit_i8(),
            "zone":       sc.label(),
            "confidence": (sc.confidence() * 1000.0).round() / 1000.0,
        })).collect();
    let dominant = ev.dimensions.iter()
        .zip(ev.values.iter().map(|v| v.abs()))
        .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
        .map(|(l, _)| l.as_str())
        .unwrap_or("none");
    Ok(json!({
        "aggregate": {
            "scalar":        (agg.raw() * 1000.0).round() / 1000.0,
            "trit":          agg.trit_i8(),
            "label":         agg.label(),
            "confidence":    (agg.confidence() * 1000.0).round() / 1000.0,
            "is_actionable": actionable,
        },
        "breakdown":   breakdown,
        "dominant":    dominant,
        "recommendation": match agg.trit_i8() {
             1 => "Affirm — weighted evidence clears the threshold. Act if confidence meets your bar.",
            -1 => "Reject — weighted evidence crosses the negative threshold. Do not proceed.",
             _ => "Tend — aggregate within the deliberation zone. Resolve conflicting dimensions before acting.",
        },
    }))
}

// ─── tsql_join ────────────────────────────────────────────────────────────────
//
// Ternary SQL join: instead of binary MATCH/NO-MATCH, computes cosine similarity
// between two numeric record vectors and routes to affirm / tend / reject.
// Partial matches (0.4–0.7 similarity) land in the hold zone for escrow audit.

fn mcp_tsql_join(params: &Value) -> Result<Value, String> {
    let parse_vec = |key: &str| -> Result<Vec<f32>, String> {
        params[key].as_array()
            .ok_or_else(|| format!("{} must be an array of numbers", key))?
            .iter()
            .map(|v| v.as_f64().ok_or_else(|| format!("{} values must be numbers", key)).map(|f| f as f32))
            .collect()
    };
    let a = parse_vec("record_a")?;
    let b = parse_vec("record_b")?;
    if a.is_empty() || b.is_empty() { return Err("record_a and record_b must be non-empty".into()); }
    let len = a.len().min(b.len());
    let dot:  f32 = a[..len].iter().zip(b[..len].iter()).map(|(x, y)| x * y).sum();
    let na:   f32 = a[..len].iter().map(|x| x * x).sum::<f32>().sqrt();
    let nb:   f32 = b[..len].iter().map(|x| x * x).sum::<f32>().sqrt();
    let similarity = if na == 0.0 || nb == 0.0 { 0.0f32 } else { (dot / (na * nb)).clamp(-1.0, 1.0) };
    let (trit, label, action, note) = if similarity >= 0.70 {
        (1i8, "affirm", "MATCH",       "High similarity — records join cleanly.")
    } else if similarity >= 0.35 {
        (0i8, "tend",   "HOLD",        "Partial match — routed to deliberative escrow for audit.")
    } else {
        (-1i8,"reject", "NO_MATCH",    "Low similarity — records do not join.")
    };
    Ok(json!({
        "trit":          trit,
        "label":         label,
        "action":        action,
        "similarity":    (similarity * 1000.0).round() / 1000.0,
        "confidence":    (similarity.abs() * 100.0).round() / 100.0,
        "record_a_len":  a.len(),
        "record_b_len":  b.len(),
        "compared_dims": len,
        "note":          note,
        "advantage": "Unlike binary SQL (match/no-match), the HOLD zone preserves partial matches for escrow audit — zero data loss.",
    }))
}

// ─── audit_ternary_logic ──────────────────────────────────────────────────────
//
// Static code analysis pass: detect binary habituation patterns in source code.
// Returns sparsity potential, habituation ratio, and deliberation injection points.

fn mcp_audit_ternary_logic(params: &Value) -> Result<Value, String> {
    let code = params["code"].as_str().ok_or("code must be a string")?;
    if code.trim().is_empty() { return Err("code cannot be empty".into()); }

    let binary_patterns = [
        ("if.*true",         "boolean condition — could be ternary signal"),
        ("if.*false",        "boolean condition — could be ternary signal"),
        ("== true",          "exact boolean match — consider trit_decide"),
        ("== false",         "exact boolean match — consider trit_decide"),
        ("bool ",            "bool type — consider replacing with trit"),
        ("boolean ",         "boolean type — consider replacing with trit"),
        ("else {",           "binary else branch — should be HOLD zone"),
        ("else:",            "binary else branch — should be HOLD zone"),
        ("} else",           "binary else branch — should be HOLD zone"),
        ("? true : false",   "ternary operator used as binary — add hold case"),
        ("return true",      "binary return — consider returning trit +1"),
        ("return false",     "binary return — consider returning trit -1"),
        ("assert ",          "binary assertion — consider ternary validation gate"),
        ("panic!",           "hard panic — consider trit_action_gate hard_block"),
        ("unwrap()",         "binary unwrap — consider ternary error handling"),
    ];

    let lines: Vec<&str> = code.lines().collect();
    let mut flagged: Vec<Value> = Vec::new();
    for (line_no, line) in lines.iter().enumerate() {
        let lower = line.to_lowercase();
        for (pattern_frag, reason) in &binary_patterns {
            if lower.contains(*pattern_frag) {
                flagged.push(json!({
                    "line":      line_no + 1,
                    "content":   line.trim(),
                    "pattern":   pattern_frag,
                    "reason":    reason,
                    "injection": "Add a ternary hold/tend branch to handle uncertain state.",
                }));
                break;
            }
        }
    }

    let total_lines    = lines.len().max(1);
    let flagged_lines  = flagged.len();
    let binary_ratio   = flagged_lines as f32 / total_lines as f32;
    let trit_score: i8 = if binary_ratio < 0.10 { 1 } else if binary_ratio < 0.35 { 0 } else { -1 };
    let sparsity_potential = ((1.0 - binary_ratio) * 100.0).round();

    Ok(json!({
        "trit":               trit_score,
        "label":              if trit_score == 1 { "affirm" } else if trit_score == 0 { "tend" } else { "reject" },
        "binary_ratio":       (binary_ratio * 100.0).round() / 100.0,
        "flagged_lines":      flagged_lines,
        "total_lines":        total_lines,
        "sparsity_potential": format!("{}%", sparsity_potential),
        "calibration_score":  if trit_score == 1 { "PASS" } else if trit_score == 0 { "REVIEW" } else { "REFACTOR" },
        "recommendations":    flagged,
        "summary": format!(
            "{}/{} lines contain binary logic patterns. Ternary compliance: {}.",
            flagged_lines, total_lines,
            if trit_score == 1 { "PASS — minimal binary habituation detected" }
            else if trit_score == 0 { "REVIEW — moderate binary patterns, deliberation injection recommended" }
            else { "REFACTOR — high binary habituation, significant tend-zone gaps present" }
        ),
    }))
}

// ─── get_industrial_standards ─────────────────────────────────────────────────

fn mcp_get_industrial_standards() -> Result<Value, String> {
    Ok(json!({
        "publisher": "RFI-IRFOS",
        "registry":  "https://ternlang.com/standards",
        "version":   "0.3.3",
        "standards": [
            {
                "id":      "T-TOKEN-v1.0",
                "name":    "Triadic Tokenization Protocol (TPE)",
                "domain":  "Tokenization",
                "status":  "ACTIVE",
                "summary": "Replaces BPE with ternary probability encoding. 33% entropy reduction on natural language tokens.",
                "impact":  "33% Entropy Reduction",
                "spec":    "https://ternlang.com/standards/T-TOKEN-v1.0"
            },
            {
                "id":      "T-KV-CACHE-v1.0",
                "name":    "Memory Moat — Sparse KV Cache",
                "domain":  "Memory",
                "status":  "ACTIVE",
                "summary": "Ternary attention sparsity: zero-trit keys are skipped, reducing KV cache footprint by up to 60%.",
                "impact":  "60% Memory Reduction",
                "spec":    "https://ternlang.com/standards/T-KV-CACHE-v1.0"
            },
            {
                "id":      "T-Fi-v1.0",
                "name":    "Triadic Compute Currency (T-Fi)",
                "domain":  "Billing",
                "status":  "ACTIVE",
                "summary": "Standardised compute billing unit for ternary inference. 1 T-Fi = 1M balanced ternary multiply-accumulate ops.",
                "impact":  "Standardized TaaS Billing",
                "spec":    "https://ternlang.com/standards/T-Fi-v1.0"
            },
            {
                "id":      "T-HAL-v1.0",
                "name":    "Hardware Abstraction Layer (BET-HAL)",
                "domain":  "Hardware",
                "status":  "ACTIVE",
                "summary": "Unified driver interface for ternary compute: FPGA clusters, Harmony OS microkernels, ASIC native, software emulator.",
                "impact":  "Native Triadic Silicon Access",
                "spec":    "https://ternlang.com/standards/T-HAL-v1.0"
            },
            {
                "id":      "T-BIO-v1.0",
                "name":    "Neural Encoding Standard (BCI Parity)",
                "domain":  "BCI / Neural",
                "status":  "DRAFT",
                "summary": "Maps biological neural spike trains (-1/0/+1) to balanced ternary, enabling 1:1 BCI signal parity without binary conversion loss.",
                "impact":  "1:1 BCI Parity",
                "spec":    "https://ternlang.com/standards/T-BIO-v1.0"
            },
            {
                "id":      "T-AUDIT-v1.0",
                "name":    "TernAudit — AI Decision Compliance",
                "domain":  "Compliance / EU AI Act",
                "status":  "ACTIVE",
                "summary": "Formalises ternary audit requirements for AI decision logs under EU AI Act Articles 13 (transparency), 14 (human oversight), 15 (robustness). trit_audit implements this standard.",
                "impact":  "EU AI Act Art. 13/14/15 Compliance",
                "spec":    "https://ternlang.com/standards/T-AUDIT-v1.0"
            }
        ],
        "note": "Standards are published under BSL-1.1. Commercial implementation license required for production use above 1M T-Fi/month."
    }))
}

// ─── LLB tools (HTTP/stateless variants) ─────────────────────────────────────
//
// Pure-logic reimplementations of the LLB gates — no filesystem writes on the
// server. llb_check / llb_classify / llb_validate run as policy classifiers.
// llb_write_safe advises the agent but defers actual writes to local stdio.

fn llb_blacklist_check(path: &str) -> Option<&'static str> {
    let protected = ["/etc/", "/proc/", "/sys/", "/dev/", "/boot/", "/bin/", "/sbin/",
                     "/usr/bin/", "/usr/sbin/", "/lib/", "/lib64/",
                     "/.ssh/", "/.gnupg/", "/.config/", "/.aws/",
                     "/root/", "/etc/passwd", "/etc/shadow", "/etc/hosts"];
    for pat in &protected {
        if path.starts_with(pat) || path == pat.trim_end_matches('/') {
            return Some("system or credential path protected by LLB blacklist");
        }
    }
    None
}

fn llb_tier_label(op: &str) -> (&'static str, &'static str, bool, bool) {
    match op.to_uppercase().as_str() {
        "READ"      => ("T0/READ",   "Low — read-only path resolution only",                         false, false),
        "CREATE"    => ("T1/CREATE", "Moderate — new file; Gate 1 + blacklist check",                false, false),
        "OVERWRITE" => ("T2/MODIFY", "High — existing file mutation; Gate 1 + snapshot + Gate 2",   true,  false),
        "CHMOD"     => ("T2/MODIFY", "High — permission change; Gate 1 + snapshot + Gate 2",        true,  false),
        "DELETE"    => ("T3/DELETE", "Critical — irreversible; Gate 1 + HITL + snapshot + Gate 2",  true,  true),
        _           => ("T1/CREATE", "Moderate — unknown op treated as CREATE",                     false, false),
    }
}

fn mcp_llb_check(params: &Value) -> Result<Value, String> {
    let path = params["path"].as_str().ok_or("path must be a string")?;
    match llb_blacklist_check(path) {
        None => Ok(json!({
            "path":      path,
            "protected": false,
            "allowed":   true,
            "trit":      1,
            "verdict":   "clear — path is not on the LLB blacklist",
        })),
        Some(reason) => Ok(json!({
            "path":      path,
            "protected": true,
            "allowed":   false,
            "trit":      -1,
            "verdict":   format!("blocked — {reason}"),
        })),
    }
}

fn mcp_llb_classify(params: &Value) -> Result<Value, String> {
    let path = params["path"].as_str().ok_or("path must be a string")?;
    let op   = params["operation"].as_str().ok_or("operation must be a string (CREATE/OVERWRITE/DELETE/CHMOD/READ)")?;
    let (tier, risk, needs_snap, needs_hitl) = llb_tier_label(op);
    Ok(json!({
        "path":              path,
        "operation":         op.to_uppercase(),
        "tier":              tier,
        "risk_summary":      risk,
        "requires_snapshot": needs_snap,
        "requires_hitl":     needs_hitl,
        "trit": match tier { "T0/READ" | "T1/CREATE" => 1i8, "T2/MODIFY" => 0, _ => -1 },
    }))
}

fn mcp_llb_validate(params: &Value) -> Result<Value, String> {
    let path    = params["path"].as_str().ok_or("path must be a string")?;
    let op      = params["operation"].as_str().ok_or("operation must be a string")?;
    let goal    = params["goal"].as_str().unwrap_or("validate request");
    let just    = params["justification"].as_str().unwrap_or("");

    if let Some(reason) = llb_blacklist_check(path) {
        return Ok(json!({
            "allowed": false,
            "trit":    -1,
            "verdict": format!("Gate 1 rejected — {reason}"),
        }));
    }

    let (tier, risk, needs_snap, _) = llb_tier_label(op);
    let preflight_ok = !just.is_empty() && !goal.is_empty();

    Ok(json!({
        "allowed":          preflight_ok,
        "trit":             if preflight_ok { 1i8 } else { 0 },
        "tier":             tier,
        "risk_summary":     risk,
        "requires_snapshot": needs_snap,
        "verdict": if preflight_ok {
            format!("Gate 1 passed — {} operation at '{}' authorised (tier {})", op.to_uppercase(), path, tier)
        } else {
            "Gate 1 hold — goal or justification missing; provide both to authorise".to_string()
        },
    }))
}

fn mcp_llb_write_safe(params: &Value) -> Result<Value, String> {
    let path    = params["path"].as_str().ok_or("path must be a string")?;
    let op      = params["operation"].as_str().unwrap_or("CREATE");
    let goal    = params["goal"].as_str().unwrap_or("write file");
    let just    = params["justification"].as_str().unwrap_or("");

    if let Some(reason) = llb_blacklist_check(path) {
        return Ok(json!({
            "trit": -1, "decision": "hard_veto", "written": false,
            "verdict": format!("LLB Hard Veto (-1) — {reason}"),
        }));
    }

    let (tier, _, needs_snap, needs_hitl) = llb_tier_label(op);

    Ok(json!({
        "trit":     0,
        "decision": "local_only",
        "written":  false,
        "tier":     tier,
        "requires_snapshot": needs_snap,
        "requires_hitl":     needs_hitl,
        "verdict": format!(
            "LLB Warn (0) — llb_write_safe requires local stdio for filesystem writes. \
             Gate 1 pre-check passed for '{}' ({}): goal='{}', justification='{}'. \
             Install via: cargo install ternlang-mcp && configure in your MCP client.",
            path, tier, goal, if just.is_empty() { "not provided" } else { just }
        ),
        "install": "cargo install ternlang-mcp",
        "docs":    "https://ternlang.com/#quickstart",
    }))
}

// ─── mcp_tools_manifest ───────────────────────────────────────────────────────
//
// Returns { "tools": [...] } with top-level `title` hoisted from annotations.title
// on every tool (MCP 2025-03-26 spec). Both tools/list and server-card use this.

fn mcp_tools_manifest() -> Value {
    let raw = json!({ "tools": [
        {
          "name": "trit_decide",
          "description": "Convert float evidence into a ternary decision (-1 conflict / 0 hold / +1 affirm) with confidence score and human-readable interpretation. The core ternary reasoning primitive.",
          "annotations": { "title": "Trit Decide — Scalar Evidence → Ternary Decision", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["evidence"],
            "properties": {
              "evidence": { "type": "array", "items": {"type":"number"}, "description": "Array of float values in range [-1.0, 1.0]. Each value is one evidence dimension. Positive = supporting, negative = opposing, near-zero = ambiguous." },
              "threshold": { "type": "number", "description": "Optional decision threshold in (0, 1). Values above threshold → affirm (+1), below negative threshold → conflict (-1), otherwise hold (0). Defaults to 0.3." }
            }
          }
        },
        {
          "name": "trit_consensus",
          "description": "Balanced ternary consensus of two trit values: +1 if both affirm, -1 if both conflict, 0 (hold) for any disagreement. Use to merge two independent ternary judgements.",
          "annotations": { "title": "Trit Consensus — Balanced Ternary Addition", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["a","b"],
            "properties": {
              "a": { "type": "number", "description": "First trit value. Must be -1, 0, or +1." },
              "b": { "type": "number", "description": "Second trit value. Must be -1, 0, or +1." }
            }
          }
        },
        {
          "name": "trit_eval",
          "description": "Evaluate a single ternlang expression on the live BET (Balanced Execution Trit) VM. Returns the trit result. Good for quick expression testing without writing a full program.",
          "annotations": { "title": "Trit Eval — BET VM Expression Evaluator", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["expression"],
            "properties": {
              "expression": { "type": "string", "description": "A ternlang expression to evaluate, e.g. 'trit_add(+1, -1)' or 'majority(+1, +1, -1)'. Must be valid ternlang syntax." }
            }
          }
        },
        {
          "name": "ternlang_run",
          "description": "Compile and run a complete .tern source program on the BET VM. Use for multi-statement programs, function definitions, struct usage, agent spawning, and tensor operations.",
          "annotations": { "title": "Ternlang Run — Execute .tern Programs on BET VM", "readOnlyHint": false, "destructiveHint": false, "idempotentHint": false, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["code"],
            "properties": {
              "code": { "type": "string", "description": "Full ternlang source code as a UTF-8 string. May contain fn definitions, let bindings, match expressions, struct defs, agent/spawn/send/await, and @sparseskip directives." }
            }
          }
        },
        {
          "name": "quantize_weights",
          "description": "Quantize f32 neural network weights to ternary {-1, 0, +1} using BitNet-style absolute-mean thresholding. Returns quantized weights, sparsity ratio, and effective compute savings.",
          "annotations": { "title": "Quantize Weights — Float → Ternary (BitNet)", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["weights"],
            "properties": {
              "weights": { "type": "array", "items": {"type":"number"}, "description": "Array of f32 neural network weights to quantize. Typically a flattened matrix row or layer." },
              "threshold": { "type": "number", "description": "Optional quantization threshold. Weights with |w| < threshold become 0 (sparse). Defaults to 0.5× mean absolute value of the input weights (BitNet b1.58 heuristic)." }
            }
          }
        },
        {
          "name": "sparse_benchmark",
          "description": "Benchmark sparse vs dense ternary matrix multiplication. Reports sparsity ratio, multiply-op count for both methods, and speedup factor. Demonstrates the @sparseskip efficiency gain.",
          "annotations": { "title": "Sparse Benchmark — Ternary Matmul Efficiency", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object",
            "properties": {
              "size": { "type": "integer", "description": "Square matrix dimension N for the N×N benchmark. Defaults to 8. Larger sizes amplify the sparsity benefit." },
              "threshold": { "type": "number", "description": "Sparsity threshold: weights with |w| below this value are treated as zero and skipped. Defaults to 0.3." }
            }
          }
        },
        {
          "name": "moe_orchestrate",
          "description": "MoE-13 deliberation — routes your query through 13 specialised expert agents (deductive, inductive, safety, fact-check, causal, ambiguity, math, context, meta-safety, and more) with dual-key synergistic routing and a hard safety veto. FREE preview: returns the real trit verdict + top-3 expert voices. Full 13-expert response with triad field, routing pair, synergy scores, and SSE streaming available via REST API (X-Ternlang-Key, Tier 2). Call trit_upgrade to see what unlocks.",
          "annotations": { "title": "MoE Orchestrate — Full 13-Expert Ternary Reasoning", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": false, "openWorldHint": true },
          "inputSchema": { "type": "object", "required": ["query"],
            "properties": {
              "query": { "type": "string", "description": "Natural-language query or statement for the expert ensemble to deliberate on. Can be a question, an action proposal, a claim to verify, or a text to analyse." },
              "evidence": { "type": "array", "items": {"type":"number"}, "description": "Optional 6-element evidence vector [syntax, world_knowledge, reasoning, tool_use, persona, safety] in range [-1.0, 1.0]. Seeds the deliberation. Defaults to zeros if omitted." }
            }
          }
        },
        {
          "name": "moe_deliberate",
          "description": "EMA-based iterative deliberation engine. Feeds evidence round by round, applying exponential moving average smoothing, until the target confidence is reached or max_rounds is exhausted. Returns per-round trace and final trit verdict.",
          "annotations": { "title": "MoE Deliberate — EMA Iterative Convergence Engine", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": false, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["target_confidence","rounds_evidence"],
            "properties": {
              "target_confidence": { "type": "number", "description": "Confidence level (0.0–1.0) at which deliberation stops early. E.g. 0.85 means stop once the EMA confidence exceeds 85%." },
              "rounds_evidence": { "type": "array", "items": {"type":"array","items":{"type":"number"}}, "description": "Array of evidence vectors, one per deliberation round. Each inner array is the same format as trit_decide's evidence parameter. Rounds are applied in order." },
              "alpha": { "type": "number", "description": "EMA smoothing factor in (0, 1). Higher values weight recent evidence more heavily. Defaults to 0.3." },
              "max_rounds": { "type": "integer", "description": "Maximum deliberation rounds before stopping, regardless of confidence. Defaults to 10." }
            }
          }
        },
        {
          "name": "trit_action_gate",
          "description": "Multi-dimensional safety gate for action authorisation. Each dimension contributes weighted evidence; any dimension marked hard_block:true with negative evidence immediately vetoes the action and returns trit=-1. Returns aggregate trit, per-dimension breakdown, and veto reason if blocked.",
          "annotations": { "title": "Trit Action Gate — Safety Veto + Weighted Vote", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["dimensions"],
            "properties": {
              "dimensions": { "type": "array",
                "description": "Array of evaluation dimensions. Each entry names a dimension, provides an evidence score, a weight, and an optional hard-block flag.",
                "items": { "type": "object", "required": ["name","evidence","weight"],
                  "properties": {
                    "name":       { "type": "string",  "description": "Human-readable dimension name, e.g. 'safety', 'reversibility', 'user_intent'." },
                    "evidence":   { "type": "number",  "description": "Evidence score for this dimension in [-1.0, 1.0]. Negative = risk/block signal, positive = safe/pass signal." },
                    "weight":     { "type": "number",  "description": "Relative weight of this dimension in the aggregate score." },
                    "hard_block": { "type": "boolean", "description": "If true and evidence < 0, this dimension unconditionally vetoes the action regardless of other dimensions." }
                  }
                }
              }
            }
          }
        },
        {
          "name": "trit_upgrade",
          "description": "Returns a structured map of what is available free via MCP vs what unlocks with a paid API key: Tier 2 €24.99/mo (10k calls/mo), Tier 3 €49.99/mo (20k calls/mo), Tier 4 Enterprise (contact us). Unlocks include full MoE-13 experts, SSE streaming, server-side three-layer memory, ternary context compression, and production SLA. Call this tool when a user asks 'what can I do with ternlang?' or 'how do I get more out of this?'",
          "annotations": { "title": "Trit Upgrade — Feature Map (Free vs Premium)", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "properties": {} }
        },
        {
          "name": "trit_mem_write",
          "description": "Write a memory entry to one of three layers: working (hot context, TTL 1h), session (flow patterns, TTL 24h), or core (identity anchors, never evicted). Annotate each entry with a trit confidence score (+1 affirm / 0 tend / -1 reject). Session and core writes are automatically compressed via ternary sparsity (low-information sentences stripped). Premium: memory is stored server-side — no state blob required.",
          "annotations": { "title": "Trit Mem Write — Three-Layer Memory Write", "readOnlyHint": false, "destructiveHint": false, "idempotentHint": false, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["layer","key","value"],
            "properties": {
              "layer":    { "type": "string",  "enum": ["working","session","core"], "description": "Memory layer to write into." },
              "key":      { "type": "string",  "description": "Entry key (used for attention matching on read)." },
              "value":    { "type": "string",  "description": "Entry value (text content)." },
              "trit":     { "type": "integer", "enum": [-1,0,1], "description": "Confidence trit: +1 affirm, 0 tend/uncertain, -1 reject. Affects promotion and attention weighting." },
              "ttl_secs": { "type": "integer", "description": "Optional TTL override in seconds. Defaults: working=3600, session=86400, core=never." }
            }
          }
        },
        {
          "name": "trit_mem_read",
          "description": "Read from three-layer memory using ternary attention. Each entry is scored: attention = key_overlap×0.35 + value_overlap×0.55 + trit_bias×0.10. Returns entries sorted by relevance. Attention trit: >0.45=affirm (highly relevant), 0.20–0.45=tend (partial match). Expired entries are automatically excluded. Premium: reads from server-side store keyed to your API key.",
          "annotations": { "title": "Trit Mem Read — Ternary Attention Memory Read", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["query"],
            "properties": {
              "query":  { "type": "string", "description": "Natural language query. Matched against all entry keys and values using ternary attention." },
              "layers": { "type": "array",  "items": {"type":"string","enum":["working","session","core"]}, "description": "Which layers to search. Defaults to all three." }
            }
          }
        },
        {
          "name": "trit_mem_consolidate",
          "description": "Run the three-layer memory consolidation cycle: (1) evict expired working entries; (2) promote affirm working entries to session with ternary compression; (3) promote long-lived affirm session entries to core with MoE-13 trit resolution; (4) upsert into core. Returns promotion counts and updated layer sizes. Call periodically (e.g. end of conversation turn) to maintain memory hygiene.",
          "annotations": { "title": "Trit Mem Consolidate — Memory Promotion Cycle", "readOnlyHint": false, "destructiveHint": false, "idempotentHint": false, "openWorldHint": false },
          "inputSchema": { "type": "object", "properties": {} }
        },
        {
          "name": "trit_mem_stats",
          "description": "Return statistics for all three memory layers: entry counts, trit distribution (affirm/tend/reject), expired-but-not-yet-evicted entries, oldest and newest entry ages. Useful for debugging memory health and deciding when to consolidate.",
          "annotations": { "title": "Trit Mem Stats — Memory Layer Health Report", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "properties": {} }
        },
        {
          "name": "trit_mem_compress",
          "description": "Apply ternary sparsity compression to an entire memory layer in-place. Strips low-information sentences (density < 0.25) from every entry's value, keeps high-signal sentences verbatim, and truncates medium-density sentences to their first phrase. Optionally drops all reject-trit entries. Returns original vs compressed byte counts and compression ratio.",
          "annotations": { "title": "Trit Mem Compress — Ternary Sparsity Compression", "readOnlyHint": false, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["layer"],
            "properties": {
              "layer":       { "type": "string",  "enum": ["working","session","core"], "description": "Layer to compress." },
              "drop_reject": { "type": "boolean", "description": "If true, also drop all entries with trit=-1 (reject). Defaults to false." }
            }
          }
        },
        {
          "name": "trit_debate",
          "description": "Route two claims through MoE-13, compare their ternary verdicts, and return a tension score, synthesis, and AGREEMENT/CONFLICT/HOLD verdict. Useful for due-diligence, legal review, and argumentation analysis.",
          "annotations": { "title": "Trit Debate — Structured 3-Way Verdict on Competing Claims", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": false, "openWorldHint": true },
          "inputSchema": { "type": "object", "required": ["claim_a","claim_b"],
            "properties": {
              "claim_a":  { "type": "string", "description": "First claim or statement to evaluate." },
              "claim_b":  { "type": "string", "description": "Second claim or statement to evaluate." },
              "context":  { "type": "string", "description": "Optional background context shared by both claims." }
            }
          }
        },
        {
          "name": "trit_uncertainty_map",
          "description": "Annotate every sentence (or paragraph) in a text with a trit value: +1 affirm (confident language), 0 tend (hedging language), -1 reject (contradicting language). Returns an uncertainty ratio and per-claim breakdown.",
          "annotations": { "title": "Trit Uncertainty Map — Text Annotation by Confidence", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["text"],
            "properties": {
              "text":        { "type": "string", "description": "Text to annotate. Can be a document, report, or any natural-language content." },
              "granularity": { "type": "string", "enum": ["sentence","paragraph"], "description": "Chunk size. Defaults to sentence." }
            }
          }
        },
        {
          "name": "trit_calibrate",
          "description": "Analyse an AI agent's recent decision log for binary habituation. Detects how often the agent forced YES/NO when the evidence called for hold (tend). Returns a calibration score trit, binary ratio, and flagged decisions.",
          "annotations": { "title": "Trit Calibrate — Binary Habituation Detector", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["decisions"],
            "properties": {
              "decisions": { "type": "array", "description": "Array of decision objects: {input, output, confidence?}.",
                "items": { "type": "object", "required": ["input","output"],
                  "properties": {
                    "input":      { "type": "string" },
                    "output":     { "type": "string" },
                    "confidence": { "type": "number", "description": "Optional 0.0–1.0 confidence score." }
                  }
                }
              }
            }
          }
        },
        {
          "name": "trit_translate",
          "description": "Convert binary control-flow code (Python if/elif/else, SQL CASE WHEN, JSON rule arrays) into equivalent .tern code with explicit tend arms injected wherever the original code had no coverage. Returns generated .tern code + count of hold zones added.",
          "annotations": { "title": "Trit Translate — Binary Code → Ternary .tern Programs", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["code"],
            "properties": {
              "code":     { "type": "string", "description": "Source code to translate." },
              "language": { "type": "string", "enum": ["python","sql","json_rules"], "description": "Source language. Defaults to python." }
            }
          }
        },
        {
          "name": "trit_eco_check",
          "description": "Evaluate a proposed action along two axes: human-optimal (via MoE-13) and eco-optimal (keyword heuristic). When they diverge, synthesis returns tend — a signal to find a path that serves both. Scoped to local, regional, or global impact.",
          "annotations": { "title": "Trit Eco Check — Human + Ecocentric Dual Perspective", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": false, "openWorldHint": true },
          "inputSchema": { "type": "object", "required": ["action"],
            "properties": {
              "action":  { "type": "string", "description": "Proposed action to evaluate." },
              "context": { "type": "string", "description": "Optional background context." },
              "scope":   { "type": "string", "enum": ["local","regional","global"], "description": "Geographic scope. Defaults to local." }
            }
          }
        },
        {
          "name": "trit_audit",
          "description": "Full TernAudit of an AI decision log. Returns binary habituation ratio, EU AI Act Article 13 (transparency) and Article 14 (human oversight) heuristic assessment, calibration score, and flagged decisions that should have been held.",
          "annotations": { "title": "Trit Audit — EU AI Act Compliance Report", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["decisions"],
            "properties": {
              "decisions": { "type": "array", "description": "Array of {input, output, confidence?} decision objects to audit.",
                "items": { "type": "object", "required": ["input","output"],
                  "properties": {
                    "input":      { "type": "string", "description": "The input prompt or question the AI received." },
                    "output":     { "type": "string", "description": "The AI's decision or response." },
                    "confidence": { "type": "number", "description": "Reported confidence 0.0–1.0 (optional)." }
                  }
                }
              }
            }
          }
        },
        {
          "name": "trit_vector",
          "description": "Multi-dimensional ternary evidence aggregation — the full agent reasoning tool. Provide named evidence dimensions each with a scalar value [-1.0, +1.0] and importance weight. Computes weighted-mean aggregate TritScalar and returns: aggregate (trit+confidence+is_actionable), per-dimension breakdown, dominant dimension, and plain-language recommendation.",
          "annotations": { "title": "Trit Vector — Multi-Dimensional Evidence Aggregation", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["dimensions"],
            "properties": {
              "dimensions": { "type": "array",
                "description": "Named evidence dimensions. Each has a label, value ∈ [-1,1], and optional weight.",
                "items": { "type": "object", "required": ["label","value"],
                  "properties": {
                    "label":  { "type": "string", "description": "Name of this evidence source, e.g. 'safety_check', 'user_consent', 'market_signal'." },
                    "value":  { "type": "number", "description": "Evidence scalar ∈ [-1.0, +1.0]. Positive = supporting evidence, negative = opposing evidence." },
                    "weight": { "type": "number", "description": "Importance weight ≥ 0 (default 1.0). Higher weight = more influence on the aggregate." }
                  }
                }
              },
              "min_confidence": { "type": "number", "description": "Minimum confidence for is_actionable (0.0–1.0, default 0.5). Raise to require stronger signal before acting." }
            }
          }
        },
        {
          "name": "tsql_join",
          "description": "Ternary SQL Join. Unlike binary SQL (MATCH / NO-MATCH), a T-Join routes partial matches into a Deliberative Hold (State 0) for escrow audit — guaranteeing 100% data retention. Computes cosine similarity between two numeric record vectors: ≥0.70 similarity → affirm (MATCH), 0.35–0.70 → tend (HOLD / escrow), <0.35 → reject (NO-MATCH). Eliminates data loss from binary join discards.",
          "annotations": { "title": "T-SQL Join — Ternary Triadic Database Join", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["record_a","record_b"],
            "properties": {
              "record_a": { "type": "array", "items": { "type": "number" }, "description": "Numeric feature vector for record A (e.g. a flattened row from a database table or embedding)." },
              "record_b": { "type": "array", "items": { "type": "number" }, "description": "Numeric feature vector for record B to join against record A." }
            }
          }
        },
        {
          "name": "audit_ternary_logic",
          "description": "Static code compliance audit. Detects Binary Habituation — over-reliance on true/false, bool types, and binary else branches — and returns a ternary calibration score with line-by-line Deliberation Injection recommendations. Also reports sparsity potential (what % of logic could benefit from a ternary hold zone).",
          "annotations": { "title": "Audit Ternary Logic — Triadic Code Compliance Check", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["code"],
            "properties": {
              "code": { "type": "string", "description": "Source code snippet to audit for triadic compliance. Supports any language (Rust, Python, TypeScript, SQL, ternlang .tern)." }
            }
          }
        },
        {
          "name": "get_industrial_standards",
          "description": "Returns the current triadic industrial standards published by RFI-IRFOS: T-TOKEN-v1.0 (tokenization), T-KV-CACHE-v1.0 (memory), T-Fi-v1.0 (compute billing), T-HAL-v1.0 (hardware abstraction), T-BIO-v1.0 (neural encoding), T-AUDIT-v1.0 (EU AI Act compliance). Use to determine compliance requirements before deploying ternary AI systems.",
          "annotations": { "title": "Get Industrial Standards — RFI-IRFOS Triadic Standards", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "properties": {} }
        },
        {
          "name": "trit_compress",
          "description": "Ternary context compression. Score each text chunk by information density: high-signal chunks are kept verbatim (+1), medium-signal chunks are truncated to their first sentence (0/tend), low-signal chunks are dropped (-1). Returns a manifest with actions and estimated token savings.",
          "annotations": { "title": "Trit Compress — Ternary Context Compression", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["chunks"],
            "properties": {
              "chunks": { "type": "array", "items": { "type": "string" }, "description": "Array of text chunks to compress (e.g. paragraphs, retrieved documents, memory entries)." },
              "query":  { "type": "string", "description": "Optional relevance query. When provided, scoring uses query–chunk word overlap instead of raw information density." }
            }
          }
        },
        {
          "name": "trit_triage",
          "description": "Ternary triage and prioritisation. Score an array of text chunks against a query by word overlap, then sort: affirm (+1) = highly relevant, tend (0) = partially relevant, reject (-1) = not relevant. Returns sorted manifest with relevance trit and score.",
          "annotations": { "title": "Trit Triage — Ternary Relevance Prioritisation", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["chunks","query"],
            "properties": {
              "chunks": { "type": "array", "description": "Array of {id, text} objects to triage.",
                "items": { "type": "object", "required": ["id","text"],
                  "properties": {
                    "id":   { "type": "string", "description": "Unique identifier for this chunk." },
                    "text": { "type": "string", "description": "Text content to score." }
                  }
                }
              },
              "query": { "type": "string", "description": "Relevance query to score chunks against." }
            }
          }
        },
        {
          "name": "trit_plan",
          "description": "Ternary task planner. Decomposes a goal into subtasks, scores each subtask by confidence and feasibility, and routes uncertain tasks into a deliberation queue. Returns an ordered plan with trit verdicts and a hold queue for tasks needing more context.",
          "annotations": { "title": "Trit Plan — Ternary Task Decomposition", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": false, "openWorldHint": true },
          "inputSchema": { "type": "object", "required": ["goal"],
            "properties": {
              "goal":     { "type": "string", "description": "High-level goal or objective to decompose into subtasks." },
              "context":  { "type": "string", "description": "Optional background context or constraints." },
              "max_steps":{ "type": "integer", "description": "Maximum number of plan steps to generate (default 8)." }
            }
          }
        },
        {
          "name": "trit_factcheck",
          "description": "Ternary fact-check engine. Decomposes a claim into sub-claims, scores each against an optional evidence corpus using word overlap, and returns an overall verdict: affirm (claim supported), tend (partially supported — needs more evidence), reject (claim contradicted).",
          "annotations": { "title": "Trit Factcheck — Ternary Claim Verification", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": true },
          "inputSchema": { "type": "object", "required": ["claim"],
            "properties": {
              "claim":    { "type": "string", "description": "The claim or statement to fact-check." },
              "evidence": { "type": "array", "items": { "type": "string" }, "description": "Optional array of evidence documents to check the claim against." }
            }
          }
        },
        {
          "name": "moe_full",
          "description": "Full 13-expert MoE orchestration with complete triad field, routing pair, per-expert verdicts, synergy scores, and deliberation trace. Returns the complete response including all expert voices and the emergent triad field computation.",
          "annotations": { "title": "MoE Full — Complete 13-Expert Orchestration", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": false, "openWorldHint": true },
          "inputSchema": { "type": "object", "required": ["query"],
            "properties": {
              "query":    { "type": "string", "description": "The question or decision to route through the full 13-expert ensemble." },
              "evidence": { "type": "array", "items": { "type": "number" }, "description": "Optional 6-element evidence vector [syntax, world_knowledge, reasoning, tool_use, persona, safety] ∈ [-1.0, 1.0]." }
            }
          }
        },
        {
          "name": "llb_check",
          "description": "Last Look Back — blacklist check. Returns whether a path is protected by the LLB permanent blacklist (system paths, credential directories, kernel interfaces). Read-only — no disk mutation.",
          "annotations": { "title": "LLB Check — Blacklist Path Guard", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["path"],
            "properties": {
              "path": { "type": "string", "description": "Absolute path to check against the LLB blacklist." }
            }
          }
        },
        {
          "name": "llb_classify",
          "description": "Last Look Back — safety tier classification. Classifies a path + operation into LLB safety tiers: T0/READ (low) → T1/CREATE (moderate) → T2/MODIFY (high) → T3/DELETE (critical). Each tier requires additional gates. Use before any filesystem mutation.",
          "annotations": { "title": "LLB Classify — Safety Tier Classification", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["path", "operation"],
            "properties": {
              "path":      { "type": "string", "description": "Absolute path to classify." },
              "operation": { "type": "string", "enum": ["READ","CREATE","OVERWRITE","DELETE","CHMOD"], "description": "The operation to perform on the path." }
            }
          }
        },
        {
          "name": "llb_validate",
          "description": "Last Look Back — Gate 1 preflight. Validates a structured mutation request: checks blacklist, resolves path, assesses tier, and issues an authorisation verdict. No disk mutation occurs — this is the pre-flight check only.",
          "annotations": { "title": "LLB Validate — Gate 1 Preflight", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["path", "operation", "goal", "justification"],
            "properties": {
              "path":          { "type": "string", "description": "Absolute path to validate." },
              "operation":     { "type": "string", "enum": ["CREATE","OVERWRITE","DELETE","CHMOD"], "description": "Intended operation." },
              "goal":          { "type": "string", "description": "What the agent is trying to accomplish." },
              "justification": { "type": "string", "description": "Why this mutation is necessary." },
              "fallback":      { "type": "string", "description": "What the agent will do if the request is rejected." }
            }
          }
        },
        {
          "name": "llb_write_safe",
          "description": "Last Look Back — Safe Atomic Write advisory. Runs the full LLB preflight (Gate 1: blacklist + tier classification + intent validation) and returns a ternary verdict: +1 allow / 0 hold (local-only) / -1 veto. Note: actual filesystem writes require local stdio installation (cargo install ternlang-mcp) — this HTTP endpoint returns the pre-flight verdict only.",
          "annotations": { "title": "LLB Write Safe — Atomic Write Advisory", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": false, "openWorldHint": false },
          "inputSchema": { "type": "object", "required": ["path", "content", "goal", "justification", "fallback"],
            "properties": {
              "path":          { "type": "string", "description": "Absolute path to write to." },
              "content":       { "type": "string", "description": "Text content to write." },
              "operation":     { "type": "string", "enum": ["CREATE","OVERWRITE"], "description": "CREATE for new files, OVERWRITE for existing. Default: CREATE." },
              "goal":          { "type": "string", "description": "What the agent is trying to accomplish." },
              "justification": { "type": "string", "description": "Why this write is necessary." },
              "fallback":      { "type": "string", "description": "What the agent will do if the write is vetoed." }
            }
          }
        }
    ]});
    // Hoist annotations.title → top-level title on every tool (MCP 2025-03-26 spec)
    let tools: Vec<Value> = raw["tools"].as_array().unwrap().iter().map(|tool| {
        let mut t = tool.clone();
        if t.get("title").is_none() {
            if let Some(title) = tool.get("annotations").and_then(|a| a.get("title")).cloned() {
                t["title"] = title;
            }
        }
        t
    }).collect();
    json!({ "tools": tools })
}

// ─── GET /api/usage ───────────────────────────────────────────────────────────

fn get_path_tier(_path: &str) -> u8 {
    1
}

// ─── Agent Product Routes ─────────────────────────────────────────────────────

/// POST /api/agents/publish — register a deployed agent product
async fn publish_agent(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
    Json(body): Json<Value>,
) -> Response {
    let raw_key = headers.get("X-Ternlang-Key").and_then(|v| v.to_str().ok()).unwrap_or("");
    // Require at least Tier 2 to publish
    let entry = state.keys.peek(raw_key).await;
    if raw_key.is_empty() || entry.map(|e| e.tier).unwrap_or(1) < 2 {
        return (axum::http::StatusCode::FORBIDDEN, Json(json!({ "status": "error", "error": "Tier 2+ required to publish agent products" }))).into_response();
    }

    let name     = body.get("name").and_then(|v| v.as_str()).unwrap_or("").trim().to_string();
    let desc     = body.get("desc").and_then(|v| v.as_str()).unwrap_or("").to_string();
    let input    = body.get("input_schema").and_then(|v| v.as_str()).unwrap_or("").to_string();
    let pricing  = body.get("pricing").and_then(|v| v.as_str()).unwrap_or("free").to_string();
    let code     = body.get("code").and_then(|v| v.as_str()).unwrap_or("").to_string();
    let nodes    = body.get("nodes").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
    let wires    = body.get("wires").and_then(|v| v.as_u64()).unwrap_or(0) as u32;

    if name.is_empty() {
        return (axum::http::StatusCode::BAD_REQUEST, Json(json!({ "status": "error", "error": "name is required" }))).into_response();
    }

    let slug = name.to_lowercase().replace(' ', "-").chars().filter(|c| c.is_alphanumeric() || *c == '-').collect::<String>();
    let slug = slug.trim_matches('-').to_string();

    let product = AgentProduct {
        slug: slug.clone(), name, desc, input_schema: input, pricing,
        code, nodes, wires, owner_key: raw_key.to_string(),
        created_at: chrono::Utc::now().to_rfc3339(),
        calls: 0,
    };

    state.agent_registry.publish(product).await;
    Json(json!({ "status": "ok", "slug": slug, "endpoint": format!("/api/agent/{}", slug) })).into_response()
}

/// GET /api/agents — list all published agents. Supports ?owner_key= to filter.
async fn list_agents(
    State(state): State<Arc<AppState>>,
    Query(params): Query<HashMap<String, String>>,
) -> Json<Value> {
    let owner_filter = params.get("owner_key");
    let mut agents = state.agent_registry.list().await;
    
    if let Some(key) = owner_filter {
        agents.retain(|a| &a.owner_key == key);
    } else {
        // Public view: redact owner keys and hide private agents
        agents.retain(|a| a.pricing != "private");
    }

    // Redact sensitive fields
    let safe_agents: Vec<Value> = agents.into_iter().map(|a| {
        let mut v = serde_json::to_value(a).unwrap();
        if let Some(obj) = v.as_object_mut() {
            obj.remove("owner_key");
            // Public listing: redact code. Filtered (owner) listing: keep code.
            if owner_filter.is_none() {
                obj.remove("code");
            }
        }
        v
    }).collect();

    Json(json!({ "status": "ok", "agents": safe_agents }))
}

/// DELETE /api/agent/:slug — remove a deployed agent
async fn delete_agent(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
    axum::extract::Path(slug): axum::extract::Path<String>,
) -> Response {
    let raw_key = headers.get("X-Ternlang-Key").and_then(|v| v.to_str().ok()).unwrap_or("");
    if raw_key.is_empty() {
        return api_error(StatusCode::UNAUTHORIZED, "API key required");
    }

    let is_admin = raw_key == state.admin_key;
    match state.agent_registry.delete(&slug, raw_key, is_admin).await {
        Ok(()) => Json(json!({ "status": "ok", "deleted": slug })).into_response(),
        Err("forbidden") => api_error(StatusCode::FORBIDDEN, "Ownership mismatch — this agent belongs to a different key"),
        _ => api_error(StatusCode::NOT_FOUND, "Agent not found"),
    }
}

/// POST /api/agent/:slug — call a deployed agent
async fn call_agent(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
    axum::extract::Path(slug): axum::extract::Path<String>,
    body: axum::body::Bytes,
) -> Response {
    let raw_key = headers.get("X-Ternlang-Key").and_then(|v| v.to_str().ok()).unwrap_or("");

    let product = match state.agent_registry.get(&slug).await {
        Some(p) => p,
        None => return (axum::http::StatusCode::NOT_FOUND, Json(json!({ "status": "error", "error": format!("Agent '{}' not found", slug) }))).into_response(),
    };

    // Private agents: only owner can call
    if product.pricing == "private" && product.owner_key != raw_key {
        return (axum::http::StatusCode::FORBIDDEN, Json(json!({ "status": "error", "error": "Private agent — owner key required" }))).into_response();
    }

    state.agent_registry.bump_calls(&slug).await;

    // Parse caller input
    let caller_input: Value = serde_json::from_slice(&body).unwrap_or(json!({}));

    Json(json!({
        "status": "ok",
        "slug": slug,
        "agent": product.name,
        "input_received": caller_input,
        "result": {
            "trit": 1,
            "label": "affirm",
            "note": "Agent runtime execution coming in next deploy — WASM VM integration pending"
        }
    })).into_response()
}

async fn stdlib_list(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
) -> Response {
    let raw = headers.get("X-Ternlang-Key").and_then(|v| v.to_str().ok()).unwrap_or("");
    let tier = if let Some(entry) = state.keys.peek(raw).await { entry.tier } else { 0 };

    let base = std::env::var("STDLIB_PATH").unwrap_or_else(|_| "stdlib".to_string());
    let mut files = Vec::new();

    fn walk(dir: &std::path::Path, files: &mut Vec<String>, user_tier: u8) {
        if let Ok(entries) = std::fs::read_dir(dir) {
            for entry in entries.flatten() {
                let path = entry.path();
                if path.is_dir() {
                    walk(&path, files, user_tier);
                } else if path.extension().and_then(|s| s.to_str()) == Some("tern") {
                    let full_str = path.to_string_lossy().to_string();
                    if let Some(pos) = full_str.find("stdlib/") {
                        let rel_path = &full_str[pos..];
                        if get_path_tier(rel_path) <= user_tier {
                            files.push(rel_path.to_string());
                        }
                    }
                }
            }
        }
    }

    walk(std::path::Path::new(&base), &mut files, tier);
    files.sort();

    Json(json!({ "status": "ok", "tier": tier, "files": files })).into_response()
}

async fn stdlib_read(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
    Path(file_path): Path<String>,
) -> Response {
    let raw = headers.get("X-Ternlang-Key").and_then(|v| v.to_str().ok()).unwrap_or("");
    let user_tier = if let Some(entry) = state.keys.peek(raw).await { entry.tier } else { 0 };

    // Strip leading slashes; reject any path that is or contains an absolute segment.
    // We do NOT use .replace("..", "") — that approach is bypassable via stdlib/../x
    // which after strip_prefix produces an absolute path that PathBuf::push would accept.
    // Instead: build the candidate path, canonicalize it, and verify it stays inside base.
    let base_str = std::env::var("STDLIB_PATH").unwrap_or_else(|_| "stdlib".to_string());
    let base_dir = std::path::Path::new(&base_str);

    // Resolve the base canonically first (fails loudly in dev if stdlib doesn't exist)
    let canonical_base = match base_dir.canonicalize() {
        Ok(p) => p,
        Err(_) => return api_error(StatusCode::INTERNAL_SERVER_ERROR, "stdlib base not found"),
    };

    // Strip any stdlib/ prefix the caller may have included, then trim leading slashes
    let trimmed = file_path
        .trim_start_matches('/')
        .trim_start_matches("stdlib/")
        .trim_start_matches('/');

    // Reject empty or obviously absolute paths before canonicalize
    if trimmed.is_empty() || trimmed.starts_with('/') {
        return api_error(StatusCode::BAD_REQUEST, "invalid path");
    }

    let candidate = canonical_base.join(trimmed);
    let canonical_candidate = match candidate.canonicalize() {
        Ok(p) => p,
        // File doesn't exist → 404 (not a traversal leak)
        Err(_) => return api_error(StatusCode::NOT_FOUND, "file not found"),
    };

    // Core guard: resolved path must be strictly inside the stdlib base
    if !canonical_candidate.starts_with(&canonical_base) {
        return api_error(StatusCode::FORBIDDEN, "access denied");
    }

    // Tier check against the original relative path
    let rel_for_tier = format!("stdlib/{}", trimmed);
    if get_path_tier(&rel_for_tier) > user_tier {
        return api_error(StatusCode::FORBIDDEN, "This file requires a higher subscription tier.");
    }

    match std::fs::read_to_string(&canonical_candidate) {
        Ok(content) => Json(json!({ "status": "ok", "path": trimmed, "content": content })).into_response(),
        Err(e) => api_error(StatusCode::NOT_FOUND, &format!("read error: {}", e)),
    }
}

async fn api_usage(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
) -> Response {
    let raw = headers
        .get("X-Ternlang-Key")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("");

    match state.keys.usage(raw).await {
        Some(info) => (StatusCode::OK, Json(info)).into_response(),
        None       => api_error(StatusCode::UNAUTHORIZED, "Invalid or revoked API key."),
    }
}

// ─── Stripe webhook ───────────────────────────────────────────────────────────

/// Verify Stripe-Signature header against the raw body.
/// Stripe signs: "{timestamp}.{raw_body}" with HMAC-SHA256.
fn verify_stripe_signature(secret: &str, raw_body: &[u8], sig_header: &str) -> bool {
    // Parse t=... and v1=... from the header
    let mut timestamp = "";
    let mut v1_sig    = "";
    for part in sig_header.split(',') {
        if let Some(ts) = part.strip_prefix("t=")  { timestamp = ts; }
        if let Some(s)  = part.strip_prefix("v1=") { v1_sig    = s;  }
    }
    if timestamp.is_empty() || v1_sig.is_empty() { return false; }

    let payload = format!("{}.{}", timestamp, String::from_utf8_lossy(raw_body));
    let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes())
        .expect("HMAC accepts any key length");
    mac.update(payload.as_bytes());
    let expected = hex::encode(mac.finalize().into_bytes());
    expected == v1_sig
}

/// Send the API key + GitHub activation instructions to the customer via Resend.
async fn send_key_email(resend_key: &str, to_email: &str, api_key: &str, key_id: &str, tier: u8) {
    let (tier_label, tier_detail) = match tier {
        3 => ("Tier 3 (Industrial)",   "50,000 API calls/month"),
        _  => ("Tier 2 (Pro Standard)", "10,000 API calls/month"),
    };
    let subject = format!("Your Ternlang API Key + Premium StdLib Access — {}", tier_label);

    let html = format!(r#"
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"></head>
<body style="background:#0d0d14;color:#e2e8f0;font-family:monospace;padding:40px;max-width:600px;margin:0 auto;">
  <p style="color:#00f5c4;font-size:22px;font-weight:bold;margin-bottom:4px;">Ternlang API</p>
  <p style="color:#666;font-size:12px;margin-top:0;">Ternary Intelligence Stack — RFI-IRFOS</p>
  <hr style="border-color:#1e2030;margin:24px 0;">
  <p style="font-size:15px;">Your <strong>{tier_label} API key</strong> is ready. Includes {tier_detail}.</p>
  <div style="background:#13131f;border:1px solid #00f5c4;border-radius:8px;padding:20px;margin:20px 0;">
    <p style="color:#888;font-size:11px;margin:0 0 8px 0;">KEY ID: {key_id}</p>
    <p style="color:#00f5c4;font-size:16px;font-weight:bold;letter-spacing:1px;margin:0;word-break:break-all;">{api_key}</p>
  </div>
  <p style="font-size:14px;">Use it in every API request:</p>
  <pre style="background:#13131f;padding:16px;border-radius:6px;color:#a0aec0;font-size:13px;">curl -X POST https://ternlang.com/api/trit_decide \
  -H "X-Ternlang-Key: {api_key}" \
  -H "Content-Type: application/json" \
  -d '{{"a":1,"b":-1}}'</pre>

  <hr style="border-color:#1e2030;margin:24px 0;">

  <p style="font-size:15px;color:#00f5c4;font-weight:bold;">Step 2 — Activate GitHub Access to Premium StdLib</p>
  <p style="font-size:13px;margin:12px 0;">Your subscription includes read access to <strong>28,495+ proprietary .tern programs</strong> across Tier 2/3/4 directories in our private repository.</p>

  <p style="font-size:13px;margin-bottom:8px;">To activate, visit:</p>
  <div style="background:#13131f;border:1px solid #2a2a3d;border-radius:8px;padding:16px;margin:0 0 16px 0;text-align:center;">
    <a href="https://ternlang.com/activate" style="color:#00f5c4;font-size:16px;font-weight:bold;text-decoration:none;">https://ternlang.com/activate</a>
  </div>

  <p style="font-size:13px;color:#888;line-height:1.6;">Enter your API key above and your <strong>GitHub username</strong>. We will send you a GitHub repository invitation for <code style="color:#e2e8f0">eriirfos-eng/ternlang-premium</code>. Accept it and clone:</p>
  <pre style="background:#13131f;padding:12px;border-radius:6px;color:#a0aec0;font-size:13px;">git clone https://github.com/eriirfos-eng/ternlang-premium</pre>

  <hr style="border-color:#1e2030;margin:24px 0;">
  <p style="font-size:13px;color:#888;">Keep this key private — it is not recoverable. If you lose it, contact <a href="mailto:licensing@ternlang.com" style="color:#00f5c4;">licensing@ternlang.com</a>.</p>
  <hr style="border-color:#1e2030;margin:24px 0;">
  <p style="font-size:11px;color:#555;">ternlang.com · RFI-IRFOS Graz Institute · BSL-1.1 · Patent A50296/2026</p>
</body>
</html>"#,
    tier_label = tier_label, tier_detail = tier_detail,
    key_id = key_id, api_key = api_key);

    let body = json!({
        "from":    "Ternlang API <noreply@ternlang.com>",
        "to":      [to_email],
        "subject": subject,
        "html":    html,
    });

    let client = reqwest::Client::new();
    match client
        .post("https://api.resend.com/emails")
        .bearer_auth(resend_key)
        .json(&body)
        .send()
        .await
    {
        Ok(r) if r.status().is_success() =>
            eprintln!("[stripe] email sent to {}", to_email),
        Ok(r) =>
            eprintln!("[stripe] Resend error {}: {:?}", r.status(), r.text().await),
        Err(e) =>
            eprintln!("[stripe] Resend request failed: {}", e),
    }
}

/// POST /stripe/webhook
async fn stripe_webhook(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
    body: Bytes,
) -> Response {
    // 1. Verify signature
    let sig_header = match headers.get("stripe-signature").and_then(|v| v.to_str().ok()) {
        Some(s) => s.to_string(),
        None    => return api_error(StatusCode::BAD_REQUEST, "Missing Stripe-Signature header."),
    };

    if !state.stripe_webhook_secret.is_empty()
        && !verify_stripe_signature(&state.stripe_webhook_secret, &body, &sig_header)
    {
        eprintln!("[stripe] signature verification failed");
        return api_error(StatusCode::UNAUTHORIZED, "Invalid webhook signature.");
    }

    // 2. Parse event
    let event: Value = match serde_json::from_slice(&body) {
        Ok(v)  => v,
        Err(e) => {
            eprintln!("[stripe] JSON parse error: {}", e);
            return api_error(StatusCode::BAD_REQUEST, "Invalid JSON.");
        }
    };

    let event_type = event["type"].as_str().unwrap_or("");
    eprintln!("[stripe] received event: {}", event_type);

    // 3. Handle checkout completed
    if event_type == "checkout.session.completed" {
        let session     = &event["data"]["object"];
        let customer_email = session["customer_details"]["email"]
            .as_str()
            .or_else(|| session["customer_email"].as_str())
            .unwrap_or("");

        if customer_email.is_empty() {
            eprintln!("[stripe] checkout.session.completed: no email found in session");
            return (StatusCode::OK, Json(json!({ "status": "ok_no_email" }))).into_response();
        }

        // 4. Determine tier from amount_total (in cents)
        //    9900  = €99/mo  → Tier 2 (Pro Standard, 10k/mo)
        //    34900 = €349/mo → Tier 3 (Industrial, 50k/mo)
        let amount = session["amount_total"].as_i64().unwrap_or(0);
        let tier: u8 = if amount >= 34900 { 3 } else { 2 };

        // 5. Generate key
        let (raw_key, entry) = state.keys.generate(
            tier,
            customer_email.to_string(),
            format!("stripe-auto-tier{}", tier),
        ).await;

        eprintln!("[stripe] provisioned tier {} key {} for {}", tier, entry.key_id, customer_email);

        // 6. Email it
        send_key_email(&state.resend_api_key, customer_email, &raw_key, &entry.key_id, tier).await;
    }

    (StatusCode::OK, Json(json!({ "received": true }))).into_response()
}

// ─── GitHub collaborator provisioning ─────────────────────────────────────────

/// Call the GitHub REST API to invite `github_username` as a read-only collaborator
/// on the private `ternlang-premium` repo. Returns Ok(()) on success or 204 (already a
/// collaborator), Err(msg) on any API failure.
async fn github_invite_collaborator(token: &str, github_username: &str) -> Result<(), String> {
    if token.is_empty() {
        return Err("GITHUB_TOKEN not configured on server".to_string());
    }

    let url = format!(
        "https://api.github.com/repos/eriirfos-eng/ternlang-premium/collaborators/{}",
        github_username
    );

    let client = reqwest::Client::new();
    let resp = client
        .put(&url)
        .header("Authorization", format!("Bearer {}", token))
        .header("Accept", "application/vnd.github+json")
        .header("X-GitHub-Api-Version", "2022-11-28")
        .header("User-Agent", "ternlang-api/0.3.3")
        .json(&serde_json::json!({ "permission": "pull" }))
        .send()
        .await
        .map_err(|e| format!("GitHub API request failed: {}", e))?;

    match resp.status().as_u16() {
        // 201 = invite sent, 204 = already a collaborator
        201 | 204 => Ok(()),
        status => {
            let body = resp.text().await.unwrap_or_default();
            Err(format!("GitHub API error {}: {}", status, body))
        }
    }
}

/// GET /activate — HTML self-service page for GitHub repo access after purchase.
async fn activate_page() -> impl IntoResponse {
    Html(r#"<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Activate Ternlang Premium — GitHub Access</title>
  <style>
    *{box-sizing:border-box;margin:0;padding:0}
    body{background:#0d0d14;color:#e2e8f0;font-family:monospace;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
    .card{background:#13131f;border:1px solid #1e2030;border-radius:12px;padding:40px;max-width:480px;width:100%}
    h1{color:#00f5c4;font-size:20px;margin-bottom:4px}
    .sub{color:#666;font-size:12px;margin-bottom:28px}
    label{display:block;color:#888;font-size:11px;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}
    input{width:100%;background:#0d0d14;border:1px solid #2a2a3d;border-radius:6px;color:#e2e8f0;font-family:monospace;font-size:14px;padding:10px 12px;margin-bottom:20px;outline:none}
    input:focus{border-color:#00f5c4}
    button{width:100%;background:#00f5c4;color:#0d0d14;border:none;border-radius:6px;font-family:monospace;font-size:14px;font-weight:bold;padding:12px;cursor:pointer}
    button:hover{background:#00ddb0}
    #msg{margin-top:16px;font-size:13px;text-align:center;min-height:20px}
    .ok{color:#00f5c4}.err{color:#f87171}
    hr{border-color:#1e2030;margin:24px 0}
    .note{color:#555;font-size:11px;line-height:1.6}
  </style>
</head>
<body>
<div class="card">
  <h1>Activate Premium Access</h1>
  <p class="sub">ternlang.com · RFI-IRFOS · BSL-1.1</p>
  <label for="key">Your Ternlang API Key</label>
  <input id="key" type="text" placeholder="tern_2_…" autocomplete="off" spellcheck="false">
  <label for="gh">Your GitHub Username</label>
  <input id="gh" type="text" placeholder="octocat" autocomplete="off" spellcheck="false">
  <button onclick="activate()">Activate GitHub Access</button>
  <div id="msg"></div>
  <hr>
  <p class="note">
    After clicking Activate, GitHub will send you an invitation email to join
    <strong style="color:#e2e8f0">eriirfos-eng/ternlang-premium</strong>.<br><br>
    Accept the invite, then clone:<br>
    <code style="color:#00f5c4">git clone https://github.com/eriirfos-eng/ternlang-premium</code>
  </p>
</div>
<script>
async function activate() {
  const key = document.getElementById('key').value.trim();
  const gh  = document.getElementById('gh').value.trim();
  const msg = document.getElementById('msg');
  if (!key || !gh) { msg.className='err'; msg.textContent='Both fields are required.'; return; }
  msg.className=''; msg.textContent='Sending invite…';
  try {
    const r = await fetch('/api/github/activate', {
      method: 'POST',
      headers: {'Content-Type':'application/json'},
      body: JSON.stringify({api_key: key, github_username: gh})
    });
    const j = await r.json();
    if (r.ok) {
      msg.className='ok';
      msg.textContent = '✓ Invite sent! Check your GitHub email and accept the invitation.';
    } else {
      msg.className='err';
      msg.textContent = j.error || 'Activation failed. Check your API key and try again.';
    }
  } catch(e) {
    msg.className='err'; msg.textContent='Network error — please try again.';
  }
}
</script>
</body>
</html>"#)
}

/// Request body for POST /api/github/activate
#[derive(Deserialize)]
struct GithubActivateRequest {
    api_key:        String,
    github_username: String,
}

/// POST /api/github/activate
///
/// Called from the /activate page. Validates the customer's API key, then
/// invites their GitHub username as a read-only collaborator on ternlang-premium.
/// Idempotent — safe to call multiple times (GitHub returns 204 if already invited).
async fn github_activate(
    State(state): State<Arc<AppState>>,
    Json(req): Json<GithubActivateRequest>,
) -> Response {
    // Sanitise inputs
    let raw_key = req.api_key.trim().to_string();
    let username = req.github_username.trim().to_string();

    if raw_key.is_empty() || username.is_empty() {
        return api_error(StatusCode::BAD_REQUEST, "api_key and github_username are required.");
    }

    // GitHub usernames: alphanumeric + hyphens, 1–39 chars, no leading/trailing hyphens
    if username.len() > 39
        || !username.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')
        || username.starts_with('-')
        || username.ends_with('-')
    {
        return api_error(StatusCode::BAD_REQUEST, "Invalid GitHub username.");
    }

    // Validate key (peek — don't burn a call quota slot)
    let entry = match state.keys.peek(&raw_key).await {
        Some(e) if e.is_active && e.tier >= 2 => e,
        Some(_) => return api_error(StatusCode::FORBIDDEN,
            "This key does not have Premium StdLib access (tier 2+ required)."),
        None => return api_error(StatusCode::UNAUTHORIZED,
            "Invalid API key. Purchase at ternlang.com/pricing."),
    };

    eprintln!("[github] activating {} for key {} (tier {})", username, entry.key_id, entry.tier);

    // Invite on GitHub
    if let Err(e) = github_invite_collaborator(&state.github_token, &username).await {
        eprintln!("[github] invite error: {}", e);
        return api_error(StatusCode::BAD_GATEWAY,
            "GitHub invite failed. Please try again or contact support@ternlang.com.");
    }

    // Persist the github_username + mark invited
    state.keys.set_github_invited(&raw_key, &username).await;

    eprintln!("[github] invite sent to {} for key {}", username, entry.key_id);

    (StatusCode::OK, Json(json!({
        "status":   "invited",
        "username": username,
        "repo":     "eriirfos-eng/ternlang-premium",
        "message":  "GitHub invitation sent. Accept the email from GitHub to gain access."
    }))).into_response()
}

#[derive(serde::Deserialize)]
struct PremiumFileQuery {
    path: String,
}

async fn premium_list(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
) -> Response {
    let raw_key = headers.get("x-ternlang-key").or_else(|| headers.get("X-Ternlang-Key"))
        .and_then(|v| v.to_str().ok()).unwrap_or("");
    
    let is_premium = if raw_key.is_empty() {
        false
    } else {
        match state.keys.peek(raw_key).await {
            Some(e) if e.is_active && e.tier >= 2 => true,
            _ => false,
        }
    };

    if !is_premium {
        return api_error(StatusCode::FORBIDDEN, "Premium access requires Tier 2+ API key.");
    }

    if state.github_token.is_empty() {
        return api_error(StatusCode::INTERNAL_SERVER_ERROR, "GITHUB_TOKEN not configured.");
    }

    let client = reqwest::Client::new();
    let url = "https://api.github.com/repos/eriirfos-eng/ternlang-premium/git/trees/main?recursive=1";
    let resp = match client.get(url)
        .header("Authorization", format!("Bearer {}", state.github_token))
        .header("User-Agent", "ternlang-api")
        .send().await {
            Ok(r) => r,
            Err(e) => return api_error(StatusCode::BAD_GATEWAY, &format!("GitHub API error: {}", e)),
        };

    if !resp.status().is_success() {
        let err_text = resp.text().await.unwrap_or_default();
        return api_error(StatusCode::BAD_GATEWAY, &format!("GitHub API failed: {}", err_text));
    }

    let tree_data: Value = match resp.json().await {
        Ok(v) => v,
        Err(_) => return api_error(StatusCode::INTERNAL_SERVER_ERROR, "Failed to parse GitHub JSON"),
    };

    let mut files = Vec::new();
    if let Some(arr) = tree_data["tree"].as_array() {
        for item in arr {
            if item["type"].as_str() == Some("blob") {
                if let Some(path) = item["path"].as_str() {
                    files.push(path.to_string());
                }
            }
        }
    }
    
    files.sort();
    (StatusCode::OK, Json(json!({ "status": "ok", "files": files }))).into_response()
}

async fn premium_file(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
    Query(query): Query<PremiumFileQuery>,
) -> Response {
    let raw_key = headers.get("x-ternlang-key").or_else(|| headers.get("X-Ternlang-Key"))
        .and_then(|v| v.to_str().ok()).unwrap_or("");
    
    let is_premium = if raw_key.is_empty() {
        false
    } else {
        match state.keys.peek(raw_key).await {
            Some(e) if e.is_active && e.tier >= 2 => true,
            _ => false,
        }
    };

    if !is_premium {
        return api_error(StatusCode::FORBIDDEN, "Premium access requires Tier 2+ API key.");
    }

    if state.github_token.is_empty() {
        return api_error(StatusCode::INTERNAL_SERVER_ERROR, "GITHUB_TOKEN not configured.");
    }

    let client = reqwest::Client::new();
    let url = format!("https://raw.githubusercontent.com/eriirfos-eng/ternlang-premium/main/{}", query.path);
    let resp = match client.get(&url)
        .header("Authorization", format!("token {}", state.github_token))
        .header("User-Agent", "ternlang-api")
        .send().await {
            Ok(r) => r,
            Err(e) => return api_error(StatusCode::BAD_GATEWAY, &format!("GitHub raw API error: {}", e)),
        };

    if !resp.status().is_success() {
        return api_error(StatusCode::NOT_FOUND, "File not found in premium library.");
    }

    let content = match resp.text().await {
        Ok(t) => t,
        Err(_) => return api_error(StatusCode::INTERNAL_SERVER_ERROR, "Failed to read content"),
    };

    (StatusCode::OK, Json(json!({ "status": "ok", "content": content }))).into_response()
}

// ─── TaaS Payloads ────────────────────────────────────────────────────────────

#[derive(Debug, Deserialize)]
pub struct TaasInferRequest {
    pub action: String,        // e.g., "TSPARSE_MATMUL"
    pub density: f64,          // Sparsity metric for cuTern routing
    pub dims: Vec<usize>,      // Matrix dimensions: [rows, cols] (W) and [batch, in] (X)
    pub security_gate: String, // Requested audit level
    pub weights: Option<Vec<f32>>,
    pub input:   Option<Vec<f32>>,
    pub threshold: Option<f32>,
}

#[derive(Debug, Serialize)]
pub struct TaasInferResponse {
    pub status: i8,            // 1=Affirm, 0=Deliberate, -1=Veto
    pub optimization: String,  // e.g., "@sparseskip Logic Active"
    pub audit_log: String,     // Summary of MoE-13 deliberation
    pub result: Option<Vec<i8>>, // Resultant TritMatrix data
    pub skipped: Option<usize>, // Number of zero-weight multiplications skipped
}

pub async fn taas_infer(
    State(_state): State<Arc<AppState>>,
    Json(payload): Json<TaasInferRequest>,
) -> impl IntoResponse {
    let mut orchestrator = TernMoeOrchestrator::with_standard_experts();
    
    // Perform safety audit on the inference request
    let audit_context = format!("Taas Action: {} | Dimensions: {:?} | Density: {:.2}", 
                                payload.action, payload.dims, payload.density);
    
    let moe_result = orchestrator.orchestrate(&audit_context, &[payload.density as f32, 0.8, 0.9]);

    let (status, opt_msg) = if moe_result.trit == 1 {
        (1, format!("{:.1}x Sparse Bypass Active (cuTern)", 1.0 / (payload.density + 0.008)))
    } else if moe_result.trit == 0 {
        (0, "Hardware Equilibrium (THOLD) - Consensus Pending".to_string())
    } else {
        (-1, "VETO: Access Denied by RFI-IRFOS MetaSafety Expert".to_string())
    };

    let mut result_data = None;
    let mut skipped_count = None;

    // Execute actual matmul if data is provided AND safety audit passed (Affirm)
    if status == 1 && payload.action == "TSPARSE_MATMUL" {
        if let (Some(w_vec), Some(i_vec)) = (payload.weights, payload.input) {
            let out_features = payload.dims.get(0).cloned().unwrap_or(1);
            let in_features  = payload.dims.get(1).cloned().unwrap_or(w_vec.len() / out_features);
            
            let threshold = payload.threshold.unwrap_or_else(|| bitnet_threshold(&w_vec));
            
            // For sparse_matmul(A, B), A is input [batch, in], B is weights [in, out]
            // If the user sends weights as [out, in] (PyTorch default), we need to be careful.
            // For now, we assume the data in w_vec is laid out as [in, out] OR we handle it.
            let w     = TritMatrix::from_f32(in_features, out_features, &w_vec, threshold);
            let input = TritMatrix::from_f32(1, in_features, &i_vec, threshold); 

            let (res_matrix, skipped) = sparse_matmul(&input, &w);
            result_data   = Some(res_matrix.to_i8_vec());
            skipped_count = Some(skipped);
        }
    }

    Json(TaasInferResponse {
        status,
        optimization: opt_msg,
        audit_log: format!("{:?}", moe_result.pair),
        result: result_data,
        skipped: skipped_count,
    })
}

#[derive(Deserialize, Debug)]
struct LlmConfig {
    system: String,
    prompt: String,
    protocol: String,
    model_id: String,
    api_key: Option<String>,
    base_url: Option<String>,
    temperature: Option<f32>,
    max_tokens: Option<u32>,
}

async fn call_llm(config: &LlmConfig) -> Result<String, String> {
    let client = reqwest::Client::new();
    let temp = config.temperature.unwrap_or(0.1);
    let max_tok = config.max_tokens.unwrap_or(512);
    let api_key = config.api_key.as_deref().unwrap_or("");

    match config.protocol.as_str() {
        "google" | "gemini" | "" => {
            if api_key.is_empty() { return Err("Google API key not set in vault".into()); }
            let model = if config.model_id.is_empty() { "gemini-1.5-flash" } else { &config.model_id };
            let url = format!(
                "https://generativelanguage.googleapis.com/v1beta/models/{}:generateContent?key={}",
                model, api_key
            );
            let payload = json!({
                "contents": [{ "parts": [{ "text": format!("SYSTEM: {}\n\nUSER: {}", config.system, config.prompt) }] }],
                "generationConfig": { "temperature": temp, "maxOutputTokens": max_tok }
            });
            let resp = client.post(&url).json(&payload).send().await
                .map_err(|e| format!("Network error: {}", e))?;
            if !resp.status().is_success() {
                let s = resp.status();
                let body = resp.text().await.unwrap_or_default();
                return Err(format!("Gemini error ({}): {}", s, body));
            }
            let data: Value = resp.json().await.map_err(|e| format!("JSON error: {}", e))?;
            data["candidates"][0]["content"]["parts"][0]["text"]
                .as_str().map(str::to_string)
                .ok_or_else(|| "Invalid Gemini response format".into())
        }

        "openai" => {
            if api_key.is_empty() { return Err("OpenAI API key not set in vault".into()); }
            let model = if config.model_id.is_empty() { "gpt-4o-mini" } else { &config.model_id };
            let base = config.base_url.as_deref().unwrap_or("https://api.openai.com");
            let url = format!("{}/v1/chat/completions", base.trim_end_matches('/'));
            let payload = json!({
                "model": model,
                "messages": [
                    { "role": "system", "content": config.system },
                    { "role": "user",   "content": config.prompt }
                ],
                "temperature": temp,
                "max_tokens": max_tok
            });
            let resp = client.post(&url)
                .header("Authorization", format!("Bearer {}", api_key))
                .json(&payload).send().await
                .map_err(|e| format!("Network error: {}", e))?;
            if !resp.status().is_success() {
                let s = resp.status();
                let body = resp.text().await.unwrap_or_default();
                return Err(format!("OpenAI error ({}): {}", s, body));
            }
            let data: Value = resp.json().await.map_err(|e| format!("JSON error: {}", e))?;
            data["choices"][0]["message"]["content"]
                .as_str().map(str::to_string)
                .ok_or_else(|| "Invalid OpenAI response format".into())
        }

        "anthropic" => {
            if api_key.is_empty() { return Err("Anthropic API key not set in vault".into()); }
            let model = if config.model_id.is_empty() { "claude-haiku-4-5-20251001" } else { &config.model_id };
            let payload = json!({
                "model": model,
                "max_tokens": max_tok,
                "system": config.system,
                "messages": [{ "role": "user", "content": config.prompt }]
            });
            let resp = client.post("https://api.anthropic.com/v1/messages")
                .header("x-api-key", api_key)
                .header("anthropic-version", "2023-06-01")
                .json(&payload).send().await
                .map_err(|e| format!("Network error: {}", e))?;
            if !resp.status().is_success() {
                let s = resp.status();
                let body = resp.text().await.unwrap_or_default();
                return Err(format!("Anthropic error ({}): {}", s, body));
            }
            let data: Value = resp.json().await.map_err(|e| format!("JSON error: {}", e))?;
            data["content"][0]["text"]
                .as_str().map(str::to_string)
                .ok_or_else(|| "Invalid Anthropic response format".into())
        }

        other => Err(format!("Unknown protocol '{}'. Supported: google, openai, anthropic", other)),
    }
}

// ─── /api/run — execute arbitrary .tern source (TernStudio) ──────────────────

/// POST /api/run
/// Body: { "code": "...", "key": "tern_xxx" }   (key may also be in X-Ternlang-Key header)
/// Response: { "status": "ok"|"error", "output": [...], "registers": [...],
///             "bytecode_bytes": N, "instructions": N, "error": null|"..." }
async fn run_program(
    headers: HeaderMap, 
    State(state): State<Arc<AppState>>,
    Json(body): Json<Value>
) -> Response {
    // Accept key from body OR header (studio sends it in the body for simplicity)
    let _key = body["key"].as_str()
        .map(|s| s.to_string())
        .or_else(|| headers.get("x-ternlang-key").and_then(|v| v.to_str().ok()).map(|s| s.to_string()));

    // LLM Bridge — protocol-routed (google/openai/anthropic)
    let mut llm_result: Option<String> = None;
    if let Some(config_val) = body.get("llm_config") {
        if let Ok(config) = serde_json::from_value::<LlmConfig>(config_val.clone()) {
            match call_llm(&config).await {
                Ok(text) => llm_result = Some(text),
                Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "status": "error", "error": e }))).into_response(),
            }
        }
    }

    let code = match body["code"].as_str().or(body["source"].as_str()) {
        Some(c) => c.to_string(),
        None => return (StatusCode::BAD_REQUEST, Json(json!({ "status": "error", "error": "code/source field required" }))).into_response(),
    };

    // Parse
    let mut parser = Parser::new(&code);
    let mut emitter = BytecodeEmitter::new();
    match parser.parse_program() {
        Ok(prog) => emitter.emit_program(&prog),
        Err(_) => {
            // Fallback: statement-by-statement parse
            let mut p2 = Parser::new(&code);
            loop {
                let peek = match p2.peek_token() {
                    Ok(t) => t,
                    Err(e) => {
                        let msg = format!("{:?}", e);
                        if msg.contains("EOF") { break; }
                        return (StatusCode::OK, Json(json!({
                            "status": "error",
                            "error": format!("parse error: {}", msg),
                            "output": [], "registers": [], "bytecode_bytes": 0, "instructions": 0,
                        }))).into_response();
                    }
                };

                match peek {
                    Token::Fn => {
                        match p2.parse_function() {
                            Ok(f) => emitter.emit_function(&f),
                            Err(e) => return (StatusCode::OK, Json(json!({
                                "status": "error",
                                "error": format!("parse error in fn: {:?}", e),
                                "output": [], "registers": [], "bytecode_bytes": 0, "instructions": 0,
                            }))).into_response(),
                        }
                    }
                    Token::Agent => {
                        match p2.parse_agent_def() {
                            Ok(a) => {
                                // For simplicity in fallback, we just emit methods. 
                                // Proper agent registration usually needs emit_program.
                                for m in a.methods { emitter.emit_function(&m); }
                            }
                            Err(e) => return (StatusCode::OK, Json(json!({
                                "status": "error",
                                "error": format!("parse error in agent: {:?}", e),
                                "output": [], "registers": [], "bytecode_bytes": 0, "instructions": 0,
                            }))).into_response(),
                        }
                    }
                    Token::Struct => {
                        match p2.parse_struct_def() {
                            Ok(s) => {
                                let names: Vec<String> = s.fields.iter().map(|(n, _)| n.clone()).collect();
                                emitter.struct_layouts.insert(s.name, names);
                            }
                            Err(e) => return (StatusCode::OK, Json(json!({
                                "status": "error",
                                "error": format!("parse error in struct: {:?}", e),
                                "output": [], "registers": [], "bytecode_bytes": 0, "instructions": 0,
                            }))).into_response(),
                        }
                    }
                    _ => {
                        match p2.parse_stmt() {
                            Ok(stmt) => emitter.emit_stmt(&stmt),
                            Err(e) => {
                                let msg = format!("{:?}", e);
                                if msg.contains("EOF") { break; }
                                return (StatusCode::OK, Json(json!({
                                    "status": "error",
                                    "error": format!("parse error: {}", msg),
                                    "output": [], "registers": [], "bytecode_bytes": 0, "instructions": 0,
                                }))).into_response();
                            }
                        }
                    }
                }
            }
        }
    }

    let bytecode = emitter.finalize();
    let bytecode_len = bytecode.len();
    let mut vm = BetVm::new(bytecode);
    
    // Task 2: Pull SQL data via allowlisted query key — raw SQL from callers is rejected.
    let mut sql_data = Vec::new();
    if let Some(query_key) = body["sql_query"].as_str() {
        if let Some((_, sql)) = ALLOWED_QUERIES.iter().find(|(k, _)| *k == query_key) {
            if let Ok(results) = state.db_manager.query(sql) {
                if let Some(arr) = results.as_array() {
                    let encoder = StandardMap;
                    sql_data = arr.iter().map(|v| encoder.encode(v)).collect();
                }
            }
        }
    }

    let start_time = std::time::Instant::now();
    let vm_res = vm.run();
    let latency_ms = start_time.elapsed().as_millis() as u64;

    match vm_res {
        Ok(()) => {
            let output = vm.take_output();
            let final_trit = match vm.peek_stack() {
                Some(VmValue::Trit(t)) => t as i8,
                _ => 0,
            };

            // Emit telemetry
            let telemetry = json!({
                "trace_id": Uuid::new_v4().to_string(),
                "timestamp_ms": Utc::now().timestamp_millis(),
                "node_id": vm.node_id(),
                "event_type": "Evaluate",
                "signal_in": sql_data.iter().map(|t| trit_to_i8(*t)).collect::<Vec<i8>>(),
                "signal_out": final_trit,
                "sparse_dropped": vm.sparse_dropped,
                "latency_ms": latency_ms
            });
            let _ = state.tracer_tx.send(telemetry.clone());
            {
                let mut history = state.telemetry_history.write().unwrap();
                history.push(telemetry);
                if history.len() > 1000 { history.remove(0); }
            }

            let registers: Vec<Value> = (0..10)
                .map(|i| format!("{:?}", vm.get_register(i)).into())
                .collect();
            (StatusCode::OK, Json(json!({
                "status": "ok",
                "output": output,
                "registers": registers,
                "bytecode_bytes": bytecode_len,
                "sql_trits": sql_data.iter().map(|t| trit_to_i8(*t)).collect::<Vec<i8>>(),
                "result": llm_result,
                "error": null,
            }))).into_response()
        }
        Err(e) => {
            let output = vm.take_output();
            (StatusCode::OK, Json(json!({
                "status": "error",
                "error": format!("{}", e),
                "output": output,
                "registers": [],
                "bytecode_bytes": bytecode_len,
                "result": llm_result,
            }))).into_response()
        }
    }
}

// ─── Phase 13: POST /api/audit ────────────────────────────────────────────────
//
// Full TernAudit over REST. Tier 2+ required (enforced by require_api_key middleware).
// Input:  { "decisions": [{input, output, confidence?}], "format"?: "json" }
// Output: full trit_audit result — binary ratio, EU AI Act Art.13/14, flagged list

async fn audit_endpoint(Json(body): Json<Value>) -> Response {
    match mcp_trit_audit(&body) {
        Ok(result) => (StatusCode::OK, Json(result)).into_response(),
        Err(e)     => api_error(StatusCode::BAD_REQUEST, &e),
    }
}

// ─── Phase 14: POST /api/translate ────────────────────────────────────────────
//
// TernTranslator over REST. Tier 2+ required.
// Input:  { "code": string, "language": "python"|"sql"|"json_rules" }
// Output: { "tern_code": string, "hold_zones_added": int, "explanation": [...] }

async fn translate_endpoint(Json(body): Json<Value>) -> Response {
    match mcp_trit_translate(&body) {
        Ok(result) => (StatusCode::OK, Json(result)).into_response(),
        Err(e)     => api_error(StatusCode::BAD_REQUEST, &e),
    }
}

// Allowlisted read-only queries — prevents SQL injection from authenticated users.
// Add entries here only for specific internal dashboard queries; never accept raw SQL from callers.
const ALLOWED_QUERIES: &[(&str, &str)] = &[
    ("training_telemetry", "SELECT * FROM training_telemetry ORDER BY epoch DESC LIMIT 100"),
    ("kpi_summary",        "SELECT * FROM kpi_log ORDER BY ts DESC LIMIT 50"),
];

async fn data_query(
    State(state): State<Arc<AppState>>,
    Json(body): Json<Value>
) -> Response {
    let key = match body["query"].as_str() {
        Some(s) => s,
        None => return (StatusCode::BAD_REQUEST, Json(json!({ "error": "query key required" }))).into_response(),
    };

    let sql = match ALLOWED_QUERIES.iter().find(|(k, _)| *k == key) {
        Some((_, sql)) => *sql,
        None => return (StatusCode::FORBIDDEN, Json(json!({ "error": "unknown query key" }))).into_response(),
    };

    match state.db_manager.query(sql) {
        Ok(results) => (StatusCode::OK, Json(json!({ "status": "ok", "results": results }))).into_response(),
        Err(e) => (StatusCode::OK, Json(json!({ "status": "error", "error": e }))).into_response(),
    }
}

async fn causal_artifact(
    Path(trace_id): Path<String>,
    State(state): State<Arc<AppState>>
) -> Response {
    let history = state.telemetry_history.read().unwrap();
    let event = history.iter().find(|e| e["trace_id"].as_str() == Some(&trace_id));

    match event {
        Some(e) => {
            let report = format!(
                "# Ternlang Causal Artifact\n\n\
                **Trace ID:** `{}`\n\
                **Timestamp:** {}\n\n\
                ## Execution Summary\n\
                - **Node ID:** `{}`\n\
                - **Event Type:** `{}`\n\
                - **Signal In:** `{:?}`\n\
                - **Signal Out:** `{}`\n\
                - **Latency:** `{} ms`\n\n\
                --- \n\
                *Generated by Ternary Intelligence Stack CausalScanner*",
                trace_id,
                Utc::now().to_rfc3339(),
                e["node_id"].as_str().unwrap_or("unknown"),
                e["event_type"].as_str().unwrap_or("unknown"),
                e["signal_in"].as_array().unwrap_or(&vec![]),
                e["signal_out"].as_i64().unwrap_or(0),
                e["latency_ms"].as_u64().unwrap_or(0)
            );
            (StatusCode::OK, report).into_response()
        }
        None => (StatusCode::NOT_FOUND, "Trace not found in active session history.").into_response()
    }
}

// ─── 404 fallback ─────────────────────────────────────────────────────────────

async fn not_found() -> Response {
    api_error(StatusCode::NOT_FOUND, "Endpoint not found. See GET / for available routes.")
}

// ─── WebSocket Tracer ─────────────────────────────────────────────────────────

async fn tracer_ws_handler(
    ws: WebSocketUpgrade,
    State(state): State<Arc<AppState>>,
) -> Response {
    ws.on_upgrade(|socket| handle_tracer_socket(socket, state)).into_response()
}

async fn handle_tracer_socket(mut socket: WebSocket, state: Arc<AppState>) {
    // Snapshot history synchronously (guard dropped before first await)
    let history_snapshot: Vec<String> = {
        let history = state.telemetry_history.read().unwrap();
        history.iter().map(|e| serde_json::to_string(e).unwrap_or_default()).collect()
    };

    for text in history_snapshot {
        if socket.send(Message::Text(text.into())).await.is_err() {
            return;
        }
    }

    let _ = socket.send(Message::Text("connected".into())).await;

    let mut rx = state.tracer_tx.subscribe();
    while let Ok(msg) = rx.recv().await {
        let text = serde_json::to_string(&msg).unwrap_or_default();
        if socket.send(Message::Text(text.into())).await.is_err() {
            break;
        }
    }
}

// ─── Heartbeat ──────────────────────────────────────────────────────────────

#[derive(Debug, Deserialize)]
pub struct HeartbeatRequest {
    pub node_id: String,
    pub version: String,
    pub uptime_secs: u64,
    pub instructions_executed: u64,
}

#[derive(Debug, Serialize)]
pub struct HeartbeatResponse {
    pub status: i8, // 1=Tethered, -1=Veto (Halt VM)
    pub sync_token: String,
}

pub async fn heartbeat(
    State(state): State<Arc<AppState>>,
    Json(payload): Json<HeartbeatRequest>,
) -> impl IntoResponse {
    state.total_instructions.fetch_add(payload.instructions_executed, std::sync::atomic::Ordering::Relaxed);
    
    {
        let mut nodes = state.active_nodes.write().unwrap();
        if nodes.insert(payload.node_id.clone()) {
            state.total_nodes.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
        }
    }

    println!("[HEARTBEAT] Node: {} | Version: {} | Uptime: {}s | Ops: {}", 
             payload.node_id, payload.version, payload.uptime_secs, payload.instructions_executed);
    
    Json(HeartbeatResponse {
        status: 1, // Affirm connection
        sync_token: uuid::Uuid::new_v4().to_string(),
    })
}

// ─── Main ─────────────────────────────────────────────────────────────────────

#[tokio::main]
async fn main() {
    let admin_key = env::var("TERNLANG_ADMIN_KEY").unwrap_or_else(|_| {
        eprintln!("[ternlang-api] WARNING: TERNLANG_ADMIN_KEY not set — using 'admin-dev'");
        eprintln!("[ternlang-api] Set TERNLANG_ADMIN_KEY=<secret> in production");
        "admin-dev".to_string()
    });

    let keys_file = env::var("KEYS_FILE")
        .map(PathBuf::from)
        .unwrap_or_else(|_| PathBuf::from("ternlang_keys.json"));

    let port: u16 = env::var("PORT")
        .ok()
        .and_then(|p| p.parse().ok())
        .unwrap_or(3731);   // 3731 — ternary

    let keys = KeyStore::load(keys_file.clone()).await;

    let agents_file = keys_file.with_file_name("ternlang_agents.json");
    let agent_registry = AgentRegistry::load(agents_file).await;

    let stripe_webhook_secret = env::var("STRIPE_WEBHOOK_SECRET").unwrap_or_else(|_| {
        eprintln!("[ternlang-api] WARNING: STRIPE_WEBHOOK_SECRET not set — webhook signature verification disabled");
        String::new()
    });

    let resend_api_key = env::var("RESEND_API_KEY").unwrap_or_else(|_| {
        eprintln!("[ternlang-api] WARNING: RESEND_API_KEY not set — post-payment emails will not be sent");
        String::new()
    });

    let github_token = env::var("GITHUB_TOKEN").unwrap_or_else(|_| {
        eprintln!("[ternlang-api] WARNING: GITHUB_TOKEN not set — GitHub repo invites will not work");
        String::new()
    });

    let (tracer_tx, _rx) = tokio::sync::broadcast::channel(1000);

    let db_path = PathBuf::from("ternlang.db");
    let db_manager = Arc::new(DatabaseManager::new(db_path));

    let state = Arc::new(AppState {
        admin_key,
        keys,
        version: "0.3.3",
        stripe_webhook_secret,
        resend_api_key,
        github_token,
        agent_registry,
        tracer_tx,
        telemetry_history: Arc::new(std::sync::RwLock::new(Vec::new())),
        memory_store: Arc::new(std::sync::RwLock::new(std::collections::HashMap::new())),
        total_instructions: Arc::new(std::sync::atomic::AtomicU64::new(0)),
        total_nodes:        Arc::new(std::sync::atomic::AtomicU64::new(0)),
        active_nodes:       Arc::new(std::sync::RwLock::new(std::collections::HashSet::new())),
        db_manager,
    });

    let cors = CorsLayer::new()
        .allow_methods([Method::GET, Method::POST, Method::DELETE, Method::OPTIONS])
        .allow_headers(Any)
        .allow_origin(Any);

    let app = Router::new()
        // Public
        .route("/",       get(root))
        .route("/health", get(health))
        .route("/translator", get(translator_page))
        .route("/mcp",    get(mcp_info).post(mcp_handler))
        .route("/.well-known/mcp/server-card.json", get(mcp_server_card))
        .route("/stripe/webhook",       post(stripe_webhook))
        .route("/pricing",              get(pricing_page))
        .route("/impressum",            get(impressum_page))
        .route("/impressum.html",       get(impressum_page))
        .route("/agb",                  get(agb_page))
        .route("/agb.html",             get(agb_page))
        .route("/datenschutz",          get(datenschutz_page))
        .route("/datenschutz.html",     get(datenschutz_page))
        .route("/studio",               get(studio_page))
        .route("/studio.js",            get(studio_js))
        .route("/playground",                       get(playground_page))
        .route("/playground/pkg/ternlang_wasm.js",  get(wasm_js_handler))
        .route("/playground/pkg/ternlang_wasm_bg.wasm", get(wasm_bg_handler))
        .route("/activate",             get(activate_page))
        .route("/kpi",                  get(kpi_page))
        .route("/kpi/{filename}",       get(kpi_data))
        .route("/api/kpi/upload/{filename}", post(kpi_upload))
        .route("/benchmarks/training",  get(benchmarks_training))
        .route("/fortune",              get(fortune_page))
        .route("/api/github/activate",  post(github_activate))
        .route("/api/usage",      get(api_usage))
        .route("/api/stdlib/list", get(stdlib_list))
        .route("/api/stdlib/read/{*path}", get(stdlib_read))
        .route("/api/agents",          get(list_agents))
        .route("/api/agents/publish",  post(publish_agent))
        .route("/api/agent/{slug}",    post(call_agent).delete(delete_agent))
        .route("/api/run",        post(run_program))
        .route("/api/data",       get(|| kpi_data(axum::extract::Path("ternlang_kpi_log.json".to_string()))))
        .route("/api/data/query", post(data_query))
        .route("/api/data/artifact/{trace_id}", get(causal_artifact))
        .route("/api/tracer/ws", get(tracer_ws_handler))
        .route("/api/premium/list", get(premium_list))
        .route("/api/premium/file", get(premium_file))
        // API (requires X-Ternlang-Key)
        .route("/api/trit_decide",       post(trit_decide))
        .route("/api/trit_vector",       post(trit_vector))
        .route("/api/trit_consensus",    post(trit_consensus))
        .route("/api/quantize_weights",  post(quantize_weights))
        .route("/api/sparse_benchmark",      post(sparse_benchmark))
        // Phase 8: Ternary AI Reasoning Toolkit
        .route("/api/trit_deliberate",       post(trit_deliberate))
        .route("/api/trit_coalition",        post(trit_coalition))
        .route("/api/trit_gate",             post(trit_gate))
        .route("/api/scalar_temperature",    post(scalar_temperature_endpoint))
        .route("/api/hallucination_score",   post(hallucination_score_endpoint))
        // Phase 9: MoE-13 orchestrator
        .route("/api/moe/orchestrate",        post(moe_orchestrate))
        // Phase 9: SSE streaming endpoints
        .route("/api/stream/moe_orchestrate", get(stream_moe_orchestrate))
        .route("/api/stream/deliberate",      get(stream_deliberate))
        // Phase 10: TaaS (Ternary-as-a-Service)
        .route("/api/v1/taas/infer",          post(taas_infer))
        .route("/api/v1/heartbeat",           post(heartbeat))
        // Phase 13: TernAudit
        .route("/api/audit",                  post(audit_endpoint))
        // Phase 14: TernTranslator
        .route("/api/translate",              post(translate_endpoint))
        // Admin (requires X-Admin-Key)
        .route("/admin/keys",            post(admin_generate_key).get(admin_list_keys))
        .route("/admin/keys/{key}",      delete(admin_revoke_key))
        .nest_service("/assets", ServeDir::new("ternlang-web/assets")
            .fallback(ServeDir::new("ternlang-studio/assets")))
        .fallback(not_found)
        .layer(middleware::from_fn_with_state(state.clone(), require_admin_key))
        .layer(middleware::from_fn_with_state(state.clone(), require_api_key))
        .layer(middleware::from_fn(add_security_headers))
        .layer(cors)
        .with_state(state);

    let addr = format!("0.0.0.0:{}", port);
    eprintln!("[ternlang-api] listening on http://{}", addr);
    eprintln!("[ternlang-api] docs: https://ternlang.com/docs/api");

    let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
