# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Subtree 14 -- Host-level AI risk-posture summary (capstone).

A single host-level roll-up that tallies the highest-signal AI exposure
indicators into one graduated posture finding. This is the agent-side input to
SigmaAIRisk: the platform combines this control-posture/exposure signal with
asset value and business context to compute the financial Value-at-Risk (VaR).
Dollarization is server-side; the agent supplies the exposure evidence.

Because Fuel keeps no state across rules, this proc RE-SCANS a *curated
headline sample* of indicators (not a full re-run of subtrees 01-13). It is a
posture roll-up, not an exhaustive inventory -- the per-indicator detail and
per-vendor allowlist verdicts live in the other subtrees, and this proc says so
explicitly so the sample is never mistaken for full coverage.

Posture logic: with signals present and NO approved-vendor policy configured
(the strict default), AI exposure is *unmanaged* -> violation. With an active
policy (approved_vendors non-empty or allow_local_models set), it is reported
as a managed posture roll-up.

Read-only.
"""

from ai.common import proc_running as _proc_running
from ai.common import any_bin_present as _any_bin_present
from ai.common import file_has_key_name as _file_has_key_name
from ai.common import is_unix as _is_unix
from ai.common import is_windows as _is_windows

const HL_RUNTIME_BINS = {
    "/usr/bin/ollama", "/usr/local/bin/ollama", "/opt/homebrew/bin/ollama",
    "/usr/local/bin/aider", "/usr/bin/aider", "/usr/local/bin/llama-server"
}
const HL_ENV_FILES = {
    "/home/*/.bashrc", "/home/*/.zshrc", "/root/.bashrc",
    "/Users/*/.zshrc", "/etc/environment"
}
const HL_API_KEYS = {
    "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY", "HF_TOKEN"
}
const HL_ENDPOINT_VARS = {
    "OPENAI_BASE_URL", "OPENAI_API_BASE", "ANTHROPIC_BASE_URL", "OLLAMA_HOST"
}
const HL_MCP_CONFIGS = {
    "/home/*/.cursor/mcp.json", "/Users/*/.cursor/mcp.json",
    "/home/*/.claude.json", "/Users/*/.claude.json",
    "/home/*/.config/Claude/claude_desktop_config.json",
    "/Users/*/Library/Application Support/Claude/claude_desktop_config.json"
}
const HL_MODEL_CACHES = {
    "/home/*/.ollama/models", "/Users/*/.ollama/models",
    "/home/*/.cache/huggingface/hub", "/Users/*/.cache/huggingface/hub"
}
const WIN_HL_APPS = { "ChatGPT", "Claude", "Ollama", "Cursor" }
const WIN_ENV_KEY = "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"


func _port_listening(host: Host, port: str): bool:
    """True iff any socket is LISTENing on `port`."""
    for n in host.getportbinding():
        if n.state == "LISTEN" and n.localport == port:
            return true
    return false


func _any_glob_exists(host: Host, patterns: set of str): bool:
    """True iff any file matched by any glob pattern exists."""
    for pat in patterns:
        for f in host.glob({pat}):
            if f.exists:
                return true
    return false


func _creds_present(host: Host): bool:
    """True iff a headline AI API key is set in a headline env/rc file."""
    for pat in HL_ENV_FILES:
        for f in host.glob({pat}):
            if f.exists and f.ftype == "file":
                for k in HL_API_KEYS:
                    if _file_has_key_name(host, f.filename, k):
                        return true
    return false


func _endpoint_present(host: Host): bool:
    """True iff a headline AI endpoint override is set in a headline file."""
    for pat in HL_ENV_FILES:
        for f in host.glob({pat}):
            if f.exists and f.ftype == "file":
                for v in HL_ENDPOINT_VARS:
                    if _file_has_key_name(host, f.filename, v):
                        return true
    return false


func _runtime_present_unix(host: Host): bool:
    """True iff a headline local AI runtime/client is present (binary,
    process, or the Ollama port listening)."""
    if _any_bin_present(host, HL_RUNTIME_BINS):
        return true
    if _proc_running(host, "ollama") or _proc_running(host, "aider"):
        return true
    return _port_listening(host, "11434")


func _win_creds_present(host: Host): bool:
    """True iff a headline AI API key is set in the system environment."""
    for k in HL_API_KEYS:
        if host.winregstr(WIN_ENV_KEY, k) != "":
            return true
    return false


func _win_ai_app_present(host: Host): bool:
    """True iff a headline AI desktop app is installed on Windows."""
    for app in WIN_HL_APPS:
        if host.check_installed_app(app):
            return true
    return false


proc check_ai_risk_summary(host: Host, approved_vendors: set of str = {},
                           allow_local_models: bool = false):
    """Host-level AI risk-posture roll-up feeding SigmaAIRisk VaR. Curated
    headline sample -- see subtrees 01-13 for full per-indicator detail.
    """
    host.report("AI risk-posture summary: curated headline sample feeding SigmaAIRisk (per-indicator detail and per-vendor allowlist verdicts are in subtrees 01-13)")
    let score = host.integer()
    score.setvalue(0)
    if _is_windows(host):
        if _win_ai_app_present(host) or _port_listening(host, "11434"):
            host.report("AI exposure signal: AI client/runtime present (Windows)")
            score.setvalue(score.getvalue() + 1)
        if _win_creds_present(host):
            host.report("AI exposure signal: AI API credentials in system environment")
            score.setvalue(score.getvalue() + 1)
    else:
        if not _is_unix(host):
            host.leave(0, "AI risk summary requires Windows or Unix/macOS")
        if _runtime_present_unix(host):
            host.report("AI exposure signal: local AI runtime/client present")
            score.setvalue(score.getvalue() + 1)
        if _creds_present(host):
            host.report("AI exposure signal: AI API credentials configured on host")
            score.setvalue(score.getvalue() + 1)
        if _endpoint_present(host):
            host.report("AI exposure signal: AI API endpoint override configured")
            score.setvalue(score.getvalue() + 1)
        if _any_glob_exists(host, HL_MCP_CONFIGS):
            host.report("AI exposure signal: agentic / MCP tooling configured")
            score.setvalue(score.getvalue() + 1)
        if _any_glob_exists(host, HL_MODEL_CACHES):
            host.report("AI exposure signal: local model cache present")
            score.setvalue(score.getvalue() + 1)
    if score.getvalue() == 0:
        host.compliance(0, "AI risk posture: no headline AI exposure signals detected")
    else:
        if len(approved_vendors) == 0 and not allow_local_models:
            host.violation(0, "Unmanaged AI exposure: %d headline AI exposure signal(s) present and no approved-vendor policy configured (feeds SigmaAIRisk VaR)",
                           score.getvalue())
        else:
            host.report("AI risk posture: %d headline AI exposure signal(s) present under an active AI policy (feeds SigmaAIRisk VaR; see subtrees for per-vendor verdicts)",
                        score.getvalue())
