"""AI control-plane contracts, persistence, and deterministic compilation."""

from __future__ import annotations

import hashlib
import json
import os
import sqlite3
import uuid
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Iterator, Literal

from pydantic import BaseModel, ConfigDict, Field, computed_field

from g_gremlin.scout.models import ScoutScanResult

from .contracts import ActorContext, PlanRisk, PlanSource, PlanStep, PlanV1
from .module_action_budget_store import ModuleActionBudgetStore
from .modules import ModuleValidationError, get_module_definition, resolve_module_execution_contract
from .pending_actions import build_pending_action, write_pending_action
from .policy import AutonomyPolicy
from .tools import ToolRegistry


REJECT_TAXONOMY_VERSION = "v2"
ACTION_KEY_REGISTRY_VERSION = "v3"
DEFAULT_DEDUPE_WINDOW_SECONDS = 6 * 60 * 60
DEFAULT_EVIDENCE_STALE_DAYS = 30
DEFAULT_AGENT_MIN_CONFIDENCE = 0.80

V1_NON_GOALS: tuple[str, ...] = (
    "No auto-execute of AI-origin plans.",
    "No policy auto-mutation from AI outcomes.",
    "No direct LLM-to-CRM writes.",
    "No cross-tenant learning.",
)

ACTION_KEY_REGISTRY: dict[str, dict[str, Any]] = {
    "sequence_pause": {
        "tool": "sequence.pause",
        "module_id": "sequence_pause",
        "supported_scouts": ["sequence_collision", "do_not_contact", "customer_open_ticket_guardrail"],
        "default_reason_code": "SEQUENCE_COLLISION_IN_ACTIVE_SEQUENCE",
    },
    "offboarding_cleanup": {
        "tool": "user.offboard",
        "module_id": "offboarding_cleanup",
        "supported_scouts": ["cross_system_field_drift", "inactive_owners"],
        "default_reason_code": "INACTIVE_OWNER_REASSIGNMENT",
    },
    "auto_enrich_missing_fields": {
        "tool": "record.enrich",
        "module_id": "auto_enrich_missing_fields",
        "supported_scouts": ["cross_system_field_drift", "append_candidates"],
        "default_reason_code": "MISSING_FIRMOGRAPHIC_DATA",
    },
    "follow_up_enforcement": {
        "tool": "meeting.followup.enforce",
        "module_id": "follow_up_enforcement",
        "supported_scouts": ["meeting_followup_breach"],
        "default_reason_code": "FOLLOWUP_OVERDUE",
    },
    "slack_routing": {
        "tool": "notification.slack",
        "module_id": "slack_routing",
        "supported_scouts": ["do_not_contact", "sequence_collision", "customer_open_ticket_guardrail"],
        "default_reason_code": "EXCEPTION_ROUTED",
    },
    "lead_to_account_match_health": {
        "tool": "scout.lead_to_account_match_health",
        "module_id": "lead_to_account",
        "supported_scouts": ["unmatched_leads"],
        "default_reason_code": "MATCH_HEALTH_REVIEW",
    },
    "deal_desk_update_fields": {
        "tool": "sfdc.record.update",
        "module_id": "deal_desk",
        "supported_scouts": ["dealdesk_close_verification"],
        "default_reason_code": "DEALDESK_CLOSE_VERIFICATION",
    },
    "deal_desk_create_renewal": {
        "tool": "dealdesk.renewal.create_shell",
        "module_id": "deal_desk",
        "supported_scouts": ["dealdesk_close_verification"],
        "default_reason_code": "DEALDESK_CLOSE_VERIFICATION",
    },
    "deal_desk_approval_stamp": {
        "tool": "dealdesk.approval.stamp",
        "module_id": "deal_desk",
        "supported_scouts": ["dealdesk_close_verification"],
        "default_reason_code": "DEALDESK_CLOSE_VERIFICATION",
    },
}

_ACTION_KEY_ALIASES: dict[str, str] = {
    "sequence.pause": "sequence_pause",
    "pause_sequence": "sequence_pause",
    "user.offboard": "offboarding_cleanup",
    "reassign_owner": "offboarding_cleanup",
    "deactivate_user": "offboarding_cleanup",
    "sfdc.user.deactivate": "offboarding_cleanup",
    "sfdc.record.reassign_owner": "offboarding_cleanup",
    "record.enrich": "auto_enrich_missing_fields",
    "enrich_missing_fields": "auto_enrich_missing_fields",
    "sfdc.record.enrich_missing_fields": "auto_enrich_missing_fields",
    "meeting.followup.enforce": "follow_up_enforcement",
    "followup_enforcement": "follow_up_enforcement",
    "sfdc.followup.enforce": "follow_up_enforcement",
    "notification.slack": "slack_routing",
    "route_exception": "slack_routing",
    "slack.notify": "slack_routing",
    "lead_to_account_match_health": "lead_to_account_match_health",
    "scout.lead_to_account_match_health": "lead_to_account_match_health",
    "opportunity_field_update": "deal_desk_update_fields",
    "dealdesk_renewal_create": "deal_desk_create_renewal",
    "dealdesk.renewal.create_shell": "deal_desk_create_renewal",
    "dealdesk_stamp": "deal_desk_approval_stamp",
    "dealdesk.approval.stamp": "deal_desk_approval_stamp",
}

REJECT_CODES_V1: tuple[str, ...] = (
    "NO_ACTION_MAPPING",
    "MISSING_ROLLBACK",
    "INVARIANT_UNSATISFIED",
    "BUDGET_EXCEEDED",
    "UNRESOLVED_UNKNOWN_PATTERN",
    "EVIDENCE_STALE",
    "EVIDENCE_REQUIRED",
    "IDEMPOTENCY_SCOPE_INVALID",
    "LOW_CONFIDENCE",
)


def _utc_now() -> datetime:
    return datetime.now(timezone.utc)


def _utc_now_iso() -> str:
    return _utc_now().isoformat()


def _parse_iso(value: str | None) -> datetime | None:
    if not value:
        return None
    raw = str(value).strip()
    if not raw:
        return None
    if raw.endswith("Z"):
        raw = raw[:-1] + "+00:00"
    try:
        parsed = datetime.fromisoformat(raw)
    except Exception:
        return None
    if parsed.tzinfo is None:
        parsed = parsed.replace(tzinfo=timezone.utc)
    return parsed.astimezone(timezone.utc)


def _canonical_json(payload: Any) -> str:
    return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)


def _hash_payload(payload: Any) -> str:
    digest = hashlib.sha256(_canonical_json(payload).encode("utf-8")).hexdigest()
    return f"sha256:{digest}"


def _safe_int(value: Any, default: int = 0) -> int:
    try:
        return int(value)
    except Exception:
        return default


def _safe_float(value: Any, default: float = 0.0) -> float:
    try:
        return float(value)
    except Exception:
        return default


def _normalize_list(values: Any) -> list[str]:
    if not isinstance(values, list):
        return []
    normalized: list[str] = []
    seen: set[str] = set()
    for value in values:
        text = str(value or "").strip()
        if not text or text in seen:
            continue
        normalized.append(text)
        seen.add(text)
    return normalized


def _risk_level_from_tier(tier: str) -> int:
    normalized = str(tier or "").strip().lower()
    if normalized == "critical":
        return 3
    if normalized == "high":
        return 2
    if normalized == "medium":
        return 1
    return 0


def _normalized_policy_tags(action_details: dict[str, Any] | None) -> set[str]:
    if not isinstance(action_details, dict):
        return set()
    raw = action_details.get("policy_tags")
    if not isinstance(raw, list):
        return set()
    return {str(item or "").strip().lower() for item in raw if str(item or "").strip()}


def resolve_deterministic_action_key(
    *,
    explicit_action_key: str | None = None,
    tool_name: str | None = None,
    action_type: str | None = None,
    scout_name: str | None = None,
    action_details: dict[str, Any] | None = None,
) -> str | None:
    explicit = str(explicit_action_key or "").strip()
    if explicit in ACTION_KEY_REGISTRY:
        return explicit

    for raw_value in (tool_name, action_type):
        cleaned = str(raw_value or "").strip().lower()
        if not cleaned:
            continue
        resolved = _ACTION_KEY_ALIASES.get(cleaned)
        if resolved:
            return resolved

    cleaned_tool = str(tool_name or "").strip().lower()
    cleaned_action_type = str(action_type or "").strip().lower()
    cleaned_scout = str(scout_name or "").strip().lower()
    policy_tags = _normalized_policy_tags(action_details)
    if cleaned_tool == "sfdc.record.update" or cleaned_action_type == "sfdc.record.update":
        if cleaned_scout == "dealdesk_close_verification" or "dealdesk" in policy_tags:
            return "deal_desk_update_fields"

    return None


def _effective_compile_tool_definition(
    registry: ToolRegistry,
    *,
    tool_name: str,
    step_args: dict[str, Any],
) -> Any:
    tool_def = registry.get_tool(tool_name)
    if tool_name != "user.offboard":
        return tool_def

    offboard_action = str(step_args.get("offboard_action") or "").strip().lower()
    if offboard_action == "reassign_owner":
        return registry.get_tool("sfdc.record.reassign_owner") or tool_def
    return registry.get_tool("sfdc.user.deactivate") or tool_def


def _window_bucket(now: datetime, window_seconds: int) -> str:
    if window_seconds <= 0:
        return now.isoformat()
    epoch = int(now.timestamp())
    bucket = epoch - (epoch % window_seconds)
    return datetime.fromtimestamp(bucket, tz=timezone.utc).isoformat()


def compute_proposal_signature(
    *,
    scope: str,
    action_family: str,
    evidence_ids: list[str],
    window_bucket: str,
) -> str:
    payload = {
        "scope": str(scope or "").strip(),
        "action_family": str(action_family or "").strip(),
        "evidence_ids": sorted(_normalize_list(evidence_ids)),
        "window_bucket": str(window_bucket or "").strip(),
    }
    return _hash_payload(payload)


class ProposalV1(BaseModel):
    model_config = ConfigDict(extra="ignore")

    proposal_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    tenant_id: str = "default"
    source_kind: Literal["discovery", "scout", "hybrid", "ai_draft"] = "scout"
    source_refs: list[str] = Field(default_factory=list)
    title: str
    objective: str
    action_candidates: list[dict[str, Any]] = Field(default_factory=list)
    confidence: dict[str, Any] = Field(default_factory=dict)
    risk: dict[str, Any] = Field(default_factory=dict)
    evidence_ids: list[str] = Field(default_factory=list)
    why_summary: str = ""
    status: Literal[
        "proposed",
        "compiled",
        "compile_rejected",
        "approved",
        "executed",
        "reverted",
        "overridden",
        "rejected",
        "closed",
    ] = "proposed"
    proposal_signature: str
    dedupe_window_seconds: int = DEFAULT_DEDUPE_WINDOW_SECONDS
    created_at: str = Field(default_factory=_utc_now_iso)
    created_by: str = "ai:control-plane"

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "Proposal/v1"

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(exclude_none=False, by_alias=True)

    @property
    def risk_tier(self) -> str:
        return str(self.risk.get("tier") or "low").strip().lower() or "low"


class EvidenceV1(BaseModel):
    model_config = ConfigDict(extra="ignore")

    evidence_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    proposal_id: str
    source_type: Literal[
        "discovery_report",
        "scout_finding",
        "audit_event",
        "crm_snapshot",
        "operator_note",
        "inbox_item",
    ] = "scout_finding"
    source_ref: str
    content_hash: str
    excerpt: str
    selector: dict[str, Any] = Field(default_factory=dict)
    captured_at: str = Field(default_factory=_utc_now_iso)
    provenance_chain: list[str] = Field(default_factory=list)
    trust_score: float = Field(default=0.0, ge=0.0, le=1.0)
    snapshot_id: str
    artifact_version: str
    redaction_class: Literal["none", "partial", "masked"] = "none"

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "Evidence/v1"

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(exclude_none=False, by_alias=True)


class InboxItemV1(BaseModel):
    model_config = ConfigDict(extra="ignore")

    inbox_item_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    pattern_type: Literal[
        "unknown_object",
        "unknown_field",
        "unknown_enum_semantics",
        "unknown_relationship",
        "policy_gap",
    ]
    signature: str
    crm_object: str | None = None
    crm_field: str | None = None
    sample_values: list[str] = Field(default_factory=list)
    candidate_semantics: list[str] = Field(default_factory=list)
    evidence_ids: list[str] = Field(default_factory=list)
    status: Literal["open", "triaged", "resolved", "ignored"] = "open"
    resolution: dict[str, Any] = Field(default_factory=dict)
    first_seen_at: str = Field(default_factory=_utc_now_iso)
    last_seen_at: str = Field(default_factory=_utc_now_iso)
    occurrence_count: int = 1

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "InboxItem/v1"

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(exclude_none=False, by_alias=True)


class CompileResultV1(BaseModel):
    model_config = ConfigDict(extra="ignore")

    compile_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    proposal_id: str
    status: Literal["compiled", "rejected", "blocked_inbox"]
    plan_id: str | None = None
    plan_hash: str | None = None
    gate_checks: dict[str, bool] = Field(default_factory=dict)
    reject_codes: list[str] = Field(default_factory=list)
    reject_taxonomy_version: str = REJECT_TAXONOMY_VERSION
    details: list[str] = Field(default_factory=list)
    compile_gate_snapshot: dict[str, Any] = Field(default_factory=dict)
    compiled_at: str = Field(default_factory=_utc_now_iso)
    compiled_by: str = "compiler@v1"

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "CompileResult/v1"

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(exclude_none=False, by_alias=True)


class ProposalOutcomeEventV1(BaseModel):
    model_config = ConfigDict(extra="ignore")

    event_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    proposal_id: str
    event_type: Literal[
        "proposed",
        "compiled",
        "compile_rejected",
        "blocked_inbox",
        "approved",
        "executed",
        "reverted",
        "overridden",
        "rejected",
        "feedback",
    ]
    event_detail: dict[str, Any] = Field(default_factory=dict)
    actor: str = "system"
    occurred_at: str = Field(default_factory=_utc_now_iso)

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "ProposalOutcomeEvent/v1"

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(exclude_none=False, by_alias=True)


class ControlPlaneJobV1(BaseModel):
    model_config = ConfigDict(extra="ignore")

    job_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    job_type: Literal["generate", "compile", "reference_score"] = "generate"
    status: Literal["pending", "running", "succeeded", "failed"] = "pending"
    request: dict[str, Any] = Field(default_factory=dict)
    result: dict[str, Any] = Field(default_factory=dict)
    error: str | None = None
    actor: str = "system"
    created_at: str = Field(default_factory=_utc_now_iso)
    updated_at: str = Field(default_factory=_utc_now_iso)
    started_at: str | None = None
    completed_at: str | None = None

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "ControlPlaneJob/v1"

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(exclude_none=False, by_alias=True)


class ReferenceSetV1(BaseModel):
    model_config = ConfigDict(extra="ignore")

    reference_set_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    tenant_id: str = "default"
    name: str
    agent_key: str
    entity_type: Literal["lead", "contact", "account", "opportunity", "custom"] = "opportunity"
    description: str = ""
    reference_record_ids: list[str] = Field(default_factory=list)
    status: Literal["active", "paused", "archived"] = "active"
    created_at: str = Field(default_factory=_utc_now_iso)
    updated_at: str = Field(default_factory=_utc_now_iso)
    created_by: str = "system"

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "ReferenceSet/v1"

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(exclude_none=False, by_alias=True)


class ReferenceScoreV1(BaseModel):
    model_config = ConfigDict(extra="ignore")

    score_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    reference_set_id: str
    status: Literal["completed", "empty_profile", "no_candidates"] = "completed"
    threshold: float = Field(default=0.7, ge=0.0, le=1.0)
    scored_at: str = Field(default_factory=_utc_now_iso)
    profile: dict[str, Any] = Field(default_factory=dict)
    summary: dict[str, Any] = Field(default_factory=dict)
    deltas: list[dict[str, Any]] = Field(default_factory=list)

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "ReferenceScore/v1"

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(exclude_none=False, by_alias=True)


@dataclass(frozen=True)
class ControlPlaneLimits:
    kill_switch: bool
    max_tokens_per_run: int
    max_cost_usd_per_run: float
    timeout_seconds: int
    concurrency_cap: int

    @classmethod
    def from_env(cls) -> "ControlPlaneLimits":
        kill_switch = str(os.getenv("G_GREMLIN_AI_CONTROL_PLANE_KILL_SWITCH", "")).strip().lower() in {
            "1",
            "true",
            "yes",
            "on",
        }
        max_tokens = max(_safe_int(os.getenv("G_GREMLIN_AI_CONTROL_PLANE_MAX_TOKENS_PER_RUN"), 6000), 1)
        max_cost = max(_safe_float(os.getenv("G_GREMLIN_AI_CONTROL_PLANE_MAX_COST_USD_PER_RUN"), 2.0), 0.0)
        timeout_seconds = max(_safe_int(os.getenv("G_GREMLIN_AI_CONTROL_PLANE_TIMEOUT_SECONDS"), 45), 5)
        concurrency_cap = max(_safe_int(os.getenv("G_GREMLIN_AI_CONTROL_PLANE_CONCURRENCY_CAP"), 2), 1)
        return cls(
            kill_switch=kill_switch,
            max_tokens_per_run=max_tokens,
            max_cost_usd_per_run=max_cost,
            timeout_seconds=timeout_seconds,
            concurrency_cap=concurrency_cap,
        )


class ControlPlaneStore:
    """SQLite-backed store for proposals, evidence, inbox, compile results, outcomes, and jobs."""

    def __init__(self, db_path: Path | str):
        self.db_path = Path(db_path).resolve()
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    @contextmanager
    def _connect(self) -> Iterator[sqlite3.Connection]:
        conn = sqlite3.connect(self.db_path, timeout=10)
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA journal_mode=WAL")
        conn.execute("PRAGMA busy_timeout=10000")
        try:
            yield conn
            conn.commit()
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()

    def _init_db(self) -> None:
        with self._connect() as conn:
            conn.executescript(
                """
                CREATE TABLE IF NOT EXISTS proposals (
                    proposal_id TEXT PRIMARY KEY,
                    status TEXT NOT NULL,
                    risk_tier TEXT NOT NULL,
                    proposal_signature TEXT NOT NULL,
                    dedupe_window_seconds INTEGER NOT NULL,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    payload_json TEXT NOT NULL
                );
                CREATE INDEX IF NOT EXISTS idx_cp_proposals_status_created
                    ON proposals(status, created_at DESC, proposal_id DESC);
                CREATE INDEX IF NOT EXISTS idx_cp_proposals_risk_created
                    ON proposals(risk_tier, created_at DESC, proposal_id DESC);
                CREATE INDEX IF NOT EXISTS idx_cp_proposals_signature_created
                    ON proposals(proposal_signature, created_at DESC);

                CREATE TABLE IF NOT EXISTS evidence (
                    evidence_id TEXT PRIMARY KEY,
                    proposal_id TEXT NOT NULL,
                    captured_at TEXT NOT NULL,
                    payload_json TEXT NOT NULL
                );
                CREATE INDEX IF NOT EXISTS idx_cp_evidence_proposal
                    ON evidence(proposal_id, captured_at DESC);

                CREATE TABLE IF NOT EXISTS inbox_items (
                    inbox_item_id TEXT PRIMARY KEY,
                    signature TEXT NOT NULL,
                    pattern_type TEXT NOT NULL,
                    status TEXT NOT NULL,
                    last_seen_at TEXT NOT NULL,
                    payload_json TEXT NOT NULL
                );
                CREATE UNIQUE INDEX IF NOT EXISTS idx_cp_inbox_signature
                    ON inbox_items(signature);
                CREATE INDEX IF NOT EXISTS idx_cp_inbox_status_pattern
                    ON inbox_items(status, pattern_type, last_seen_at DESC);

                CREATE TABLE IF NOT EXISTS compile_results (
                    compile_id TEXT PRIMARY KEY,
                    proposal_id TEXT NOT NULL,
                    status TEXT NOT NULL,
                    compiled_at TEXT NOT NULL,
                    payload_json TEXT NOT NULL
                );
                CREATE INDEX IF NOT EXISTS idx_cp_compile_status_time
                    ON compile_results(status, compiled_at DESC, compile_id DESC);
                CREATE INDEX IF NOT EXISTS idx_cp_compile_proposal_time
                    ON compile_results(proposal_id, compiled_at DESC);

                CREATE TABLE IF NOT EXISTS reference_sets (
                    reference_set_id TEXT PRIMARY KEY,
                    status TEXT NOT NULL,
                    agent_key TEXT NOT NULL,
                    entity_type TEXT NOT NULL,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    payload_json TEXT NOT NULL
                );
                CREATE INDEX IF NOT EXISTS idx_cp_ref_sets_status_created
                    ON reference_sets(status, created_at DESC, reference_set_id DESC);
                CREATE INDEX IF NOT EXISTS idx_cp_ref_sets_agent_updated
                    ON reference_sets(agent_key, updated_at DESC, reference_set_id DESC);

                CREATE TABLE IF NOT EXISTS reference_scores (
                    score_id TEXT PRIMARY KEY,
                    reference_set_id TEXT NOT NULL,
                    status TEXT NOT NULL,
                    scored_at TEXT NOT NULL,
                    payload_json TEXT NOT NULL
                );
                CREATE INDEX IF NOT EXISTS idx_cp_ref_scores_set_scored
                    ON reference_scores(reference_set_id, scored_at DESC, score_id DESC);
                CREATE INDEX IF NOT EXISTS idx_cp_ref_scores_status_scored
                    ON reference_scores(status, scored_at DESC, score_id DESC);

                CREATE TABLE IF NOT EXISTS proposal_outcomes (
                    event_id TEXT PRIMARY KEY,
                    proposal_id TEXT NOT NULL,
                    event_type TEXT NOT NULL,
                    occurred_at TEXT NOT NULL,
                    payload_json TEXT NOT NULL
                );
                CREATE INDEX IF NOT EXISTS idx_cp_outcomes_type_time
                    ON proposal_outcomes(event_type, occurred_at DESC);

                CREATE TABLE IF NOT EXISTS control_plane_jobs (
                    job_id TEXT PRIMARY KEY,
                    job_type TEXT NOT NULL,
                    status TEXT NOT NULL,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    started_at TEXT,
                    completed_at TEXT,
                    actor TEXT NOT NULL,
                    request_json TEXT NOT NULL,
                    result_json TEXT,
                    error TEXT
                );
                CREATE INDEX IF NOT EXISTS idx_cp_jobs_status_created
                    ON control_plane_jobs(status, created_at DESC);
                """
            )

    def create_job(self, job_type: str, request: dict[str, Any], *, actor: str) -> ControlPlaneJobV1:
        now = _utc_now_iso()
        job = ControlPlaneJobV1(
            job_type=str(job_type or "generate").strip().lower() or "generate",
            status="pending",
            request=request if isinstance(request, dict) else {},
            actor=actor or "system",
            created_at=now,
            updated_at=now,
        )
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO control_plane_jobs (
                    job_id, job_type, status, created_at, updated_at,
                    started_at, completed_at, actor, request_json, result_json, error
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    job.job_id,
                    job.job_type,
                    job.status,
                    job.created_at,
                    job.updated_at,
                    None,
                    None,
                    job.actor,
                    json.dumps(job.request),
                    None,
                    None,
                ),
            )
        return job

    def mark_job_running(self, job_id: str) -> None:
        now = _utc_now_iso()
        with self._connect() as conn:
            conn.execute(
                """
                UPDATE control_plane_jobs
                SET status = ?, updated_at = ?, started_at = COALESCE(started_at, ?)
                WHERE job_id = ?
                """,
                ("running", now, now, job_id),
            )

    def mark_job_pending(self, job_id: str, error: str | None = None) -> None:
        now = _utc_now_iso()
        with self._connect() as conn:
            conn.execute(
                """
                UPDATE control_plane_jobs
                SET status = ?, updated_at = ?, error = ?, completed_at = NULL
                WHERE job_id = ?
                """,
                ("pending", now, str(error or "").strip() or None, job_id),
            )

    def mark_job_succeeded(self, job_id: str, result: dict[str, Any]) -> None:
        now = _utc_now_iso()
        with self._connect() as conn:
            conn.execute(
                """
                UPDATE control_plane_jobs
                SET status = ?, updated_at = ?, completed_at = ?, result_json = ?, error = NULL
                WHERE job_id = ?
                """,
                ("succeeded", now, now, json.dumps(result or {}), job_id),
            )

    def mark_job_failed(self, job_id: str, error: str) -> None:
        now = _utc_now_iso()
        with self._connect() as conn:
            conn.execute(
                """
                UPDATE control_plane_jobs
                SET status = ?, updated_at = ?, completed_at = ?, error = ?
                WHERE job_id = ?
                """,
                ("failed", now, now, str(error or "unknown error"), job_id),
            )

    def get_job(self, job_id: str) -> ControlPlaneJobV1 | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT * FROM control_plane_jobs WHERE job_id = ?",
                (job_id,),
            ).fetchone()
        if row is None:
            return None
        return ControlPlaneJobV1(
            job_id=row["job_id"],
            job_type=row["job_type"],
            status=row["status"],
            request=json.loads(row["request_json"] or "{}"),
            result=json.loads(row["result_json"] or "{}"),
            error=row["error"],
            actor=row["actor"],
            created_at=row["created_at"],
            updated_at=row["updated_at"],
            started_at=row["started_at"],
            completed_at=row["completed_at"],
        )

    def save_proposal(self, proposal: ProposalV1) -> tuple[ProposalV1, bool]:
        window = max(int(proposal.dedupe_window_seconds or DEFAULT_DEDUPE_WINDOW_SECONDS), 0)
        if proposal.proposal_signature and window > 0:
            cutoff = (_utc_now() - timedelta(seconds=window)).isoformat()
            with self._connect() as conn:
                existing = conn.execute(
                    """
                    SELECT payload_json
                    FROM proposals
                    WHERE proposal_signature = ?
                      AND created_at >= ?
                      AND status IN ('proposed', 'compiled', 'approved', 'executed')
                    ORDER BY created_at DESC
                    LIMIT 1
                    """,
                    (proposal.proposal_signature, cutoff),
                ).fetchone()
            if existing is not None:
                payload = json.loads(existing["payload_json"] or "{}")
                return ProposalV1.model_validate(payload), True

        now = _utc_now_iso()
        normalized = proposal.model_copy(update={"created_at": proposal.created_at or now})
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO proposals (
                    proposal_id, status, risk_tier, proposal_signature, dedupe_window_seconds,
                    created_at, updated_at, payload_json
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    normalized.proposal_id,
                    normalized.status,
                    normalized.risk_tier,
                    normalized.proposal_signature,
                    int(normalized.dedupe_window_seconds),
                    normalized.created_at,
                    now,
                    json.dumps(normalized.to_dict()),
                ),
            )
        return normalized, False

    def update_proposal_status(self, proposal_id: str, status: str) -> None:
        proposal = self.get_proposal(proposal_id)
        if proposal is None:
            return
        updated = proposal.model_copy(update={"status": status})
        now = _utc_now_iso()
        with self._connect() as conn:
            conn.execute(
                """
                UPDATE proposals
                SET status = ?, risk_tier = ?, updated_at = ?, payload_json = ?
                WHERE proposal_id = ?
                """,
                (
                    updated.status,
                    updated.risk_tier,
                    now,
                    json.dumps(updated.to_dict()),
                    proposal_id,
                ),
            )

    def get_proposal(self, proposal_id: str) -> ProposalV1 | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT payload_json FROM proposals WHERE proposal_id = ?",
                (proposal_id,),
            ).fetchone()
        if row is None:
            return None
        return ProposalV1.model_validate(json.loads(row["payload_json"] or "{}"))

    def list_proposals(
        self,
        *,
        status: str | None = None,
        risk_tier: str | None = None,
        cursor: str | None = None,
        limit: int = 50,
    ) -> tuple[list[ProposalV1], str | None]:
        bounded_limit = max(1, min(int(limit), 200))
        filters: list[str] = []
        args: list[Any] = []
        if status:
            filters.append("status = ?")
            args.append(str(status).strip())
        if risk_tier:
            filters.append("risk_tier = ?")
            args.append(str(risk_tier).strip())
        if cursor:
            parts = str(cursor).split("|", 1)
            if len(parts) == 2:
                cursor_created, cursor_id = parts
                filters.append("(created_at < ? OR (created_at = ? AND proposal_id < ?))")
                args.extend([cursor_created, cursor_created, cursor_id])
        where_clause = f"WHERE {' AND '.join(filters)}" if filters else ""
        with self._connect() as conn:
            rows = conn.execute(
                f"""
                SELECT payload_json
                FROM proposals
                {where_clause}
                ORDER BY created_at DESC, proposal_id DESC
                LIMIT ?
                """,
                (*args, bounded_limit + 1),
            ).fetchall()
        parsed = [ProposalV1.model_validate(json.loads(row["payload_json"] or "{}")) for row in rows[:bounded_limit]]
        next_cursor: str | None = None
        if len(rows) > bounded_limit and parsed:
            last = parsed[-1]
            next_cursor = f"{last.created_at}|{last.proposal_id}"
        return parsed, next_cursor

    def save_evidence(self, evidence_rows: list[EvidenceV1]) -> None:
        if not evidence_rows:
            return
        with self._connect() as conn:
            for evidence in evidence_rows:
                conn.execute(
                    """
                    INSERT INTO evidence (evidence_id, proposal_id, captured_at, payload_json)
                    VALUES (?, ?, ?, ?)
                    """,
                    (
                        evidence.evidence_id,
                        evidence.proposal_id,
                        evidence.captured_at,
                        json.dumps(evidence.to_dict()),
                    ),
                )

    def list_evidence_for_proposal(self, proposal_id: str) -> list[EvidenceV1]:
        with self._connect() as conn:
            rows = conn.execute(
                """
                SELECT payload_json
                FROM evidence
                WHERE proposal_id = ?
                ORDER BY captured_at ASC
                """,
                (proposal_id,),
            ).fetchall()
        return [EvidenceV1.model_validate(json.loads(row["payload_json"] or "{}")) for row in rows]

    def upsert_inbox_item(self, item: InboxItemV1) -> InboxItemV1:
        with self._connect() as conn:
            existing = conn.execute(
                "SELECT payload_json FROM inbox_items WHERE signature = ?",
                (item.signature,),
            ).fetchone()
            if existing is None:
                conn.execute(
                    """
                    INSERT INTO inbox_items (
                        inbox_item_id, signature, pattern_type, status, last_seen_at, payload_json
                    ) VALUES (?, ?, ?, ?, ?, ?)
                    """,
                    (
                        item.inbox_item_id,
                        item.signature,
                        item.pattern_type,
                        item.status,
                        item.last_seen_at,
                        json.dumps(item.to_dict()),
                    ),
                )
                return item

            prior = InboxItemV1.model_validate(json.loads(existing["payload_json"] or "{}"))
            merged_evidence = sorted(set(prior.evidence_ids).union(set(item.evidence_ids)))
            updated = prior.model_copy(
                update={
                    "last_seen_at": item.last_seen_at,
                    "occurrence_count": int(prior.occurrence_count) + 1,
                    "evidence_ids": merged_evidence,
                    "sample_values": sorted(set(prior.sample_values).union(set(item.sample_values))),
                }
            )
            conn.execute(
                """
                UPDATE inbox_items
                SET status = ?, last_seen_at = ?, payload_json = ?
                WHERE signature = ?
                """,
                (updated.status, updated.last_seen_at, json.dumps(updated.to_dict()), item.signature),
            )
            return updated

    def get_inbox_item(self, inbox_item_id: str) -> InboxItemV1 | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT payload_json FROM inbox_items WHERE inbox_item_id = ?",
                (inbox_item_id,),
            ).fetchone()
        if row is None:
            return None
        return InboxItemV1.model_validate(json.loads(row["payload_json"] or "{}"))

    def list_inbox(
        self,
        *,
        status: str | None = None,
        pattern_type: str | None = None,
        cursor: str | None = None,
        limit: int = 50,
    ) -> tuple[list[InboxItemV1], str | None]:
        bounded_limit = max(1, min(int(limit), 200))
        filters: list[str] = []
        args: list[Any] = []
        if status:
            filters.append("status = ?")
            args.append(str(status).strip())
        if pattern_type:
            filters.append("pattern_type = ?")
            args.append(str(pattern_type).strip())
        if cursor:
            parts = str(cursor).split("|", 1)
            if len(parts) == 2:
                cursor_seen, cursor_id = parts
                filters.append("(last_seen_at < ? OR (last_seen_at = ? AND inbox_item_id < ?))")
                args.extend([cursor_seen, cursor_seen, cursor_id])
        where_clause = f"WHERE {' AND '.join(filters)}" if filters else ""
        with self._connect() as conn:
            rows = conn.execute(
                f"""
                SELECT payload_json
                FROM inbox_items
                {where_clause}
                ORDER BY last_seen_at DESC, inbox_item_id DESC
                LIMIT ?
                """,
                (*args, bounded_limit + 1),
            ).fetchall()
        parsed = [InboxItemV1.model_validate(json.loads(row["payload_json"] or "{}")) for row in rows[:bounded_limit]]
        next_cursor: str | None = None
        if len(rows) > bounded_limit and parsed:
            last = parsed[-1]
            next_cursor = f"{last.last_seen_at}|{last.inbox_item_id}"
        return parsed, next_cursor

    def update_inbox_status(
        self,
        inbox_item_id: str,
        *,
        status: str,
        resolution: dict[str, Any] | None = None,
    ) -> InboxItemV1 | None:
        current = self.get_inbox_item(inbox_item_id)
        if current is None:
            return None
        update: dict[str, Any] = {"status": status, "last_seen_at": _utc_now_iso()}
        if resolution is not None:
            update["resolution"] = resolution
        updated = current.model_copy(update=update)
        with self._connect() as conn:
            conn.execute(
                """
                UPDATE inbox_items
                SET status = ?, last_seen_at = ?, payload_json = ?
                WHERE inbox_item_id = ?
                """,
                (updated.status, updated.last_seen_at, json.dumps(updated.to_dict()), inbox_item_id),
            )
        return updated

    def has_unresolved_inbox_for_evidence(self, evidence_ids: list[str]) -> bool:
        evidence_set = set(_normalize_list(evidence_ids))
        if not evidence_set:
            return False
        with self._connect() as conn:
            rows = conn.execute(
                "SELECT payload_json FROM inbox_items WHERE status IN ('open', 'triaged')"
            ).fetchall()
        for row in rows:
            payload = InboxItemV1.model_validate(json.loads(row["payload_json"] or "{}"))
            if evidence_set.intersection(payload.evidence_ids):
                return True
        return False

    def save_compile_result(self, result: CompileResultV1) -> None:
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO compile_results (
                    compile_id, proposal_id, status, compiled_at, payload_json
                ) VALUES (?, ?, ?, ?, ?)
                """,
                (
                    result.compile_id,
                    result.proposal_id,
                    result.status,
                    result.compiled_at,
                    json.dumps(result.to_dict()),
                ),
            )

    def get_compile_result(self, compile_id: str) -> CompileResultV1 | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT payload_json FROM compile_results WHERE compile_id = ?",
                (compile_id,),
            ).fetchone()
        if row is None:
            return None
        return CompileResultV1.model_validate(json.loads(row["payload_json"] or "{}"))

    def latest_compile_result(self, proposal_id: str) -> CompileResultV1 | None:
        with self._connect() as conn:
            row = conn.execute(
                """
                SELECT payload_json
                FROM compile_results
                WHERE proposal_id = ?
                ORDER BY compiled_at DESC
                LIMIT 1
                """,
                (proposal_id,),
            ).fetchone()
        if row is None:
            return None
        return CompileResultV1.model_validate(json.loads(row["payload_json"] or "{}"))

    def save_reference_set(self, reference_set: ReferenceSetV1) -> ReferenceSetV1:
        now = _utc_now_iso()
        normalized_ids = _normalize_list(reference_set.reference_record_ids)
        normalized = reference_set.model_copy(
            update={
                "reference_record_ids": normalized_ids,
                "created_at": reference_set.created_at or now,
                "updated_at": now,
            }
        )
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO reference_sets (
                    reference_set_id, status, agent_key, entity_type, created_at, updated_at, payload_json
                ) VALUES (?, ?, ?, ?, ?, ?, ?)
                ON CONFLICT(reference_set_id) DO UPDATE SET
                    status = excluded.status,
                    agent_key = excluded.agent_key,
                    entity_type = excluded.entity_type,
                    updated_at = excluded.updated_at,
                    payload_json = excluded.payload_json
                """,
                (
                    normalized.reference_set_id,
                    normalized.status,
                    normalized.agent_key,
                    normalized.entity_type,
                    normalized.created_at,
                    normalized.updated_at,
                    json.dumps(normalized.to_dict()),
                ),
            )
        return normalized

    def get_reference_set(self, reference_set_id: str) -> ReferenceSetV1 | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT payload_json FROM reference_sets WHERE reference_set_id = ?",
                (reference_set_id,),
            ).fetchone()
        if row is None:
            return None
        return ReferenceSetV1.model_validate(json.loads(row["payload_json"] or "{}"))

    def list_reference_sets(
        self,
        *,
        status: str | None = None,
        agent_key: str | None = None,
        cursor: str | None = None,
        limit: int = 50,
    ) -> tuple[list[ReferenceSetV1], str | None]:
        bounded_limit = max(1, min(int(limit), 200))
        filters: list[str] = []
        args: list[Any] = []
        if status:
            filters.append("status = ?")
            args.append(str(status).strip())
        if agent_key:
            filters.append("agent_key = ?")
            args.append(str(agent_key).strip())
        if cursor:
            parts = str(cursor).split("|", 1)
            if len(parts) == 2:
                cursor_updated, cursor_id = parts
                filters.append("(updated_at < ? OR (updated_at = ? AND reference_set_id < ?))")
                args.extend([cursor_updated, cursor_updated, cursor_id])
        where_clause = f"WHERE {' AND '.join(filters)}" if filters else ""
        with self._connect() as conn:
            rows = conn.execute(
                f"""
                SELECT payload_json
                FROM reference_sets
                {where_clause}
                ORDER BY updated_at DESC, reference_set_id DESC
                LIMIT ?
                """,
                (*args, bounded_limit + 1),
            ).fetchall()
        parsed = [ReferenceSetV1.model_validate(json.loads(row["payload_json"] or "{}")) for row in rows[:bounded_limit]]
        next_cursor: str | None = None
        if len(rows) > bounded_limit and parsed:
            last = parsed[-1]
            next_cursor = f"{last.updated_at}|{last.reference_set_id}"
        return parsed, next_cursor

    def save_reference_score(self, score: ReferenceScoreV1) -> ReferenceScoreV1:
        normalized = score.model_copy(
            update={
                "threshold": max(0.0, min(_safe_float(score.threshold, 0.7), 1.0)),
                "deltas": score.deltas[:500],
            }
        )
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO reference_scores (
                    score_id, reference_set_id, status, scored_at, payload_json
                ) VALUES (?, ?, ?, ?, ?)
                """,
                (
                    normalized.score_id,
                    normalized.reference_set_id,
                    normalized.status,
                    normalized.scored_at,
                    json.dumps(normalized.to_dict()),
                ),
            )
        return normalized

    def latest_reference_score(self, reference_set_id: str) -> ReferenceScoreV1 | None:
        with self._connect() as conn:
            row = conn.execute(
                """
                SELECT payload_json
                FROM reference_scores
                WHERE reference_set_id = ?
                ORDER BY scored_at DESC, score_id DESC
                LIMIT 1
                """,
                (reference_set_id,),
            ).fetchone()
        if row is None:
            return None
        return ReferenceScoreV1.model_validate(json.loads(row["payload_json"] or "{}"))

    def list_reference_scores(self, reference_set_id: str, *, limit: int = 20) -> list[ReferenceScoreV1]:
        bounded_limit = max(1, min(int(limit), 200))
        with self._connect() as conn:
            rows = conn.execute(
                """
                SELECT payload_json
                FROM reference_scores
                WHERE reference_set_id = ?
                ORDER BY scored_at DESC, score_id DESC
                LIMIT ?
                """,
                (reference_set_id, bounded_limit),
            ).fetchall()
        return [ReferenceScoreV1.model_validate(json.loads(row["payload_json"] or "{}")) for row in rows]

    def reference_set_count(self) -> int:
        with self._connect() as conn:
            row = conn.execute("SELECT COUNT(*) AS count FROM reference_sets WHERE status <> 'archived'").fetchone()
        return int((row["count"] if row else 0) or 0)

    def latest_reference_score_any(self) -> ReferenceScoreV1 | None:
        with self._connect() as conn:
            row = conn.execute(
                """
                SELECT payload_json
                FROM reference_scores
                ORDER BY scored_at DESC, score_id DESC
                LIMIT 1
                """
            ).fetchone()
        if row is None:
            return None
        return ReferenceScoreV1.model_validate(json.loads(row["payload_json"] or "{}"))

    def record_outcome(
        self,
        proposal_id: str,
        event_type: str,
        *,
        actor: str,
        detail: dict[str, Any] | None = None,
    ) -> ProposalOutcomeEventV1:
        event = ProposalOutcomeEventV1(
            proposal_id=proposal_id,
            event_type=event_type,  # type: ignore[arg-type]
            event_detail=detail or {},
            actor=actor or "system",
        )
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO proposal_outcomes (
                    event_id, proposal_id, event_type, occurred_at, payload_json
                ) VALUES (?, ?, ?, ?, ?)
                """,
                (
                    event.event_id,
                    event.proposal_id,
                    event.event_type,
                    event.occurred_at,
                    json.dumps(event.to_dict()),
                ),
            )
        return event

    def list_outcomes(self, proposal_id: str) -> list[ProposalOutcomeEventV1]:
        with self._connect() as conn:
            rows = conn.execute(
                """
                SELECT payload_json
                FROM proposal_outcomes
                WHERE proposal_id = ?
                ORDER BY occurred_at DESC
                """,
                (proposal_id,),
            ).fetchall()
        return [ProposalOutcomeEventV1.model_validate(json.loads(row["payload_json"] or "{}")) for row in rows]

    def metrics_summary(self, *, window_days: int = 30) -> dict[str, Any]:
        cutoff = (_utc_now() - timedelta(days=max(1, int(window_days)))).isoformat()
        with self._connect() as conn:
            proposal_rows = conn.execute(
                """
                SELECT status, COUNT(*) AS count
                FROM proposals
                GROUP BY status
                """
            ).fetchall()
            compile_rows = conn.execute(
                """
                SELECT status, COUNT(*) AS count
                FROM compile_results
                WHERE compiled_at >= ?
                GROUP BY status
                """,
                (cutoff,),
            ).fetchall()
            reject_rows = conn.execute(
                """
                SELECT payload_json
                FROM compile_results
                WHERE compiled_at >= ?
                  AND status = 'rejected'
                """,
                (cutoff,),
            ).fetchall()
            inbox_open = conn.execute(
                """
                SELECT COUNT(*)
                FROM inbox_items
                WHERE status IN ('open', 'triaged')
                """
            ).fetchone()[0]
            outcome_rows = conn.execute(
                """
                SELECT event_type, COUNT(*) AS count
                FROM proposal_outcomes
                WHERE occurred_at >= ?
                GROUP BY event_type
                """,
                (cutoff,),
            ).fetchall()
            outcome_event_rows = conn.execute(
                """
                SELECT proposal_id, event_type
                FROM proposal_outcomes
                WHERE occurred_at >= ?
                """,
                (cutoff,),
            ).fetchall()

        proposal_counts = {str(row["status"]): int(row["count"]) for row in proposal_rows}
        compile_counts = {str(row["status"]): int(row["count"]) for row in compile_rows}
        outcome_counts = {str(row["event_type"]): int(row["count"]) for row in outcome_rows}
        outcomes_by_proposal: dict[str, set[str]] = {}
        for row in outcome_event_rows:
            proposal_id = str(row["proposal_id"] or "").strip()
            event_type = str(row["event_type"] or "").strip()
            if not proposal_id or not event_type:
                continue
            outcomes_by_proposal.setdefault(proposal_id, set()).add(event_type)

        reject_distribution: dict[str, int] = {}
        for row in reject_rows:
            payload = CompileResultV1.model_validate(json.loads(row["payload_json"] or "{}"))
            for code in payload.reject_codes:
                reject_distribution[code] = reject_distribution.get(code, 0) + 1

        compiled = compile_counts.get("compiled", 0)
        rejected = compile_counts.get("rejected", 0)
        blocked = compile_counts.get("blocked_inbox", 0)
        total = compiled + rejected + blocked
        pass_rate = (compiled / total) if total else None
        straight_through_count = 0
        human_approved_execution_count = 0
        rejected_or_blocked_count = 0
        awaiting_review_count = 0
        for events in outcomes_by_proposal.values():
            if "executed" in events:
                if "approved" in events:
                    human_approved_execution_count += 1
                else:
                    straight_through_count += 1
                continue
            if "compile_rejected" in events or "blocked_inbox" in events or "rejected" in events:
                rejected_or_blocked_count += 1
                continue
            if "compiled" in events:
                awaiting_review_count += 1
        terminal_total = straight_through_count + human_approved_execution_count + rejected_or_blocked_count
        straight_through_rate = (straight_through_count / terminal_total) if terminal_total else None
        human_intervention_rate = (human_approved_execution_count / terminal_total) if terminal_total else None
        rejection_rate = (rejected_or_blocked_count / terminal_total) if terminal_total else None
        latest_reference_score = self.latest_reference_score_any()
        latest_reference_summary = (
            latest_reference_score.summary if latest_reference_score and isinstance(latest_reference_score.summary, dict) else {}
        )
        latest_reference_profile = (
            latest_reference_score.profile if latest_reference_score and isinstance(latest_reference_score.profile, dict) else {}
        )
        return {
            "window_days": max(1, int(window_days)),
            "proposal_counts": proposal_counts,
            "compile_counts": compile_counts,
            "compile_pass_rate": pass_rate,
            "reject_distribution": reject_distribution,
            "unresolved_inbox_count": int(inbox_open or 0),
            "outcome_counts": outcome_counts,
            "autonomy_flow": {
                "window_days": max(1, int(window_days)),
                "straight_through_count": straight_through_count,
                "human_approved_execution_count": human_approved_execution_count,
                "rejected_or_blocked_count": rejected_or_blocked_count,
                "awaiting_review_count": awaiting_review_count,
                "terminal_count": terminal_total,
                "straight_through_rate": straight_through_rate,
                "human_intervention_rate": human_intervention_rate,
                "rejection_rate": rejection_rate,
            },
            "action_registry_version": ACTION_KEY_REGISTRY_VERSION,
            "reject_taxonomy_version": REJECT_TAXONOMY_VERSION,
            "reference_sets_count": self.reference_set_count(),
            "latest_reference_score": (
                {
                    "reference_set_id": latest_reference_score.reference_set_id if latest_reference_score else None,
                    "status": latest_reference_score.status if latest_reference_score else None,
                    "scored_at": latest_reference_score.scored_at if latest_reference_score else None,
                    "threshold": latest_reference_score.threshold if latest_reference_score else None,
                    "avg_conformance": latest_reference_summary.get("avg_conformance"),
                    "below_threshold_count": latest_reference_summary.get("below_threshold_count"),
                    "candidate_count": latest_reference_summary.get("candidate_count"),
                    "profile_size": latest_reference_profile.get("profile_size"),
                }
                if latest_reference_score
                else None
            ),
        }

    def calibration_metrics(self, *, window_days: int = 30) -> dict[str, Any]:
        cutoff = (_utc_now() - timedelta(days=max(1, int(window_days)))).isoformat()
        with self._connect() as conn:
            proposal_rows = conn.execute(
                """
                SELECT payload_json
                FROM proposals
                WHERE created_at >= ?
                """,
                (cutoff,),
            ).fetchall()
            outcome_rows = conn.execute(
                """
                SELECT proposal_id, event_type
                FROM proposal_outcomes
                WHERE occurred_at >= ?
                """,
                (cutoff,),
            ).fetchall()
        by_proposal: dict[str, set[str]] = {}
        for row in outcome_rows:
            by_proposal.setdefault(str(row["proposal_id"]), set()).add(str(row["event_type"]))

        confidences: list[float] = []
        executed_confidences: list[float] = []
        reverted_count = 0
        overridden_count = 0
        for row in proposal_rows:
            proposal = ProposalV1.model_validate(json.loads(row["payload_json"] or "{}"))
            overall = _safe_float((proposal.confidence or {}).get("overall"), 0.0)
            confidences.append(overall)
            events = by_proposal.get(proposal.proposal_id, set())
            if "executed" in events:
                executed_confidences.append(overall)
            if "reverted" in events:
                reverted_count += 1
            if "overridden" in events:
                overridden_count += 1

        avg_overall = (sum(confidences) / len(confidences)) if confidences else None
        avg_executed = (sum(executed_confidences) / len(executed_confidences)) if executed_confidences else None
        return {
            "window_days": max(1, int(window_days)),
            "sample_count": len(confidences),
            "avg_confidence_overall": avg_overall,
            "avg_confidence_executed": avg_executed,
            "reverted_count": reverted_count,
            "overridden_count": overridden_count,
            "notes": "Calibration is passive recording only in v1.",
        }


def control_plane_store(state_dir: Path) -> ControlPlaneStore:
    return ControlPlaneStore(Path(state_dir) / "db" / "control_plane.sqlite")


def _latest_scan_result_from_receipts(state_dir: Path) -> ScoutScanResult | None:
    receipts_dir = Path(state_dir) / "receipts"
    if not receipts_dir.exists():
        return None

    best_payload: dict[str, Any] | None = None
    best_ts: datetime | None = None
    for run_dir in receipts_dir.iterdir():
        if not run_dir.is_dir():
            continue
        scan_path = run_dir / "scan.json"
        if not scan_path.exists():
            continue
        try:
            payload = json.loads(scan_path.read_text(encoding="utf-8"))
        except Exception:
            continue
        if not isinstance(payload, dict):
            continue
        candidate_ts = _parse_iso(payload.get("ended_at")) or _parse_iso(payload.get("started_at"))
        if candidate_ts is None:
            candidate_ts = datetime.fromtimestamp(scan_path.stat().st_mtime, tz=timezone.utc)
        if best_ts is None or candidate_ts > best_ts:
            best_ts = candidate_ts
            best_payload = payload
    if best_payload is None:
        return None
    try:
        return ScoutScanResult.from_dict(best_payload)
    except Exception:
        return None


def _row_excerpt(row: dict[str, Any]) -> str:
    reason = str(row.get("reason") or "").strip()
    reason_code = str(row.get("reason_code") or "").strip()
    if reason and reason_code:
        return f"{reason_code}: {reason}"
    if reason:
        return reason
    if reason_code:
        return reason_code
    sobject = str(row.get("sobject") or "").strip()
    record_id = str(row.get("record_id") or "").strip()
    return f"{sobject} {record_id}".strip()


def _build_unknown_inbox_items(evidence: list[EvidenceV1], raw_rows: list[dict[str, Any]]) -> list[InboxItemV1]:
    items: list[InboxItemV1] = []
    for index, row in enumerate(raw_rows):
        evidence_id = evidence[index].evidence_id if index < len(evidence) else None
        sobject = str(row.get("sobject") or "").strip()
        if sobject.endswith("__c"):
            signature = _hash_payload({"type": "unknown_object", "crm_object": sobject})
            items.append(
                InboxItemV1(
                    pattern_type="unknown_object",
                    signature=signature,
                    crm_object=sobject,
                    sample_values=[str(row.get("record_id") or "")],
                    evidence_ids=[evidence_id] if evidence_id else [],
                )
            )
        fields = row.get("fields") if isinstance(row.get("fields"), dict) else {}
        for key, raw_value in fields.items():
            field_name = str(key or "").strip()
            if not field_name.endswith("__c"):
                continue
            signature = _hash_payload({"type": "unknown_field", "crm_object": sobject, "crm_field": field_name})
            sample_value = str(raw_value)[:120] if raw_value not in (None, "") else ""
            items.append(
                InboxItemV1(
                    pattern_type="unknown_field",
                    signature=signature,
                    crm_object=sobject or None,
                    crm_field=field_name,
                    sample_values=[sample_value] if sample_value else [],
                    evidence_ids=[evidence_id] if evidence_id else [],
                )
            )
    return items


def _iter_scan_payloads(state_dir: Path) -> list[tuple[datetime, str, dict[str, Any]]]:
    receipts_dir = Path(state_dir) / "receipts"
    if not receipts_dir.exists():
        return []
    rows: list[tuple[datetime, str, dict[str, Any]]] = []
    for run_dir in receipts_dir.iterdir():
        if not run_dir.is_dir():
            continue
        scan_path = run_dir / "scan.json"
        if not scan_path.exists():
            continue
        try:
            payload = json.loads(scan_path.read_text(encoding="utf-8"))
        except Exception:
            continue
        if not isinstance(payload, dict):
            continue
        run_id = str(payload.get("run_id") or run_dir.name).strip() or run_dir.name
        ts = _parse_iso(payload.get("ended_at")) or _parse_iso(payload.get("started_at"))
        if ts is None:
            ts = datetime.fromtimestamp(scan_path.stat().st_mtime, tz=timezone.utc)
        rows.append((ts, run_id, payload))
    rows.sort(key=lambda item: item[0])
    return rows


def _scan_evidence_rows(state_dir: Path) -> list[dict[str, Any]]:
    evidence_rows: list[dict[str, Any]] = []
    for ts, run_id, payload in _iter_scan_payloads(state_dir):
        findings = payload.get("findings") if isinstance(payload.get("findings"), list) else []
        for finding_index, finding in enumerate(findings):
            if not isinstance(finding, dict):
                continue
            scout_name = str(finding.get("scout_name") or "").strip()
            evidence = finding.get("evidence") if isinstance(finding.get("evidence"), list) else []
            for evidence_index, evidence_row in enumerate(evidence):
                if not isinstance(evidence_row, dict):
                    continue
                record_id = str(evidence_row.get("record_id") or "").strip()
                if not record_id:
                    continue
                row = dict(evidence_row)
                row["_meta"] = {
                    "run_id": run_id,
                    "captured_at": ts.isoformat(),
                    "finding_index": finding_index,
                    "evidence_index": evidence_index,
                    "scout_name": scout_name,
                    "schema_version": str(payload.get("schema_version") or "1"),
                }
                evidence_rows.append(row)
    return evidence_rows


def _reference_field_map(row: dict[str, Any]) -> dict[str, str]:
    fields = row.get("fields") if isinstance(row.get("fields"), dict) else {}
    normalized: dict[str, str] = {}
    for key, value in fields.items():
        name = str(key or "").strip()
        if not name:
            continue
        text = str(value or "").strip()
        if not text:
            continue
        normalized[name] = text
    return normalized


def _build_reference_profile(reference_rows: list[dict[str, Any]]) -> dict[str, Any]:
    if not reference_rows:
        return {}
    total = len(reference_rows)
    field_presence_count: dict[str, int] = {}
    reason_count: dict[str, int] = {}
    sobject_count: dict[str, int] = {}
    scout_count: dict[str, int] = {}
    for row in reference_rows:
        sobject = str(row.get("sobject") or "").strip()
        if sobject:
            sobject_count[sobject] = sobject_count.get(sobject, 0) + 1
        reason_code = str(row.get("reason_code") or "").strip()
        if reason_code:
            reason_count[reason_code] = reason_count.get(reason_code, 0) + 1
        meta = row.get("_meta") if isinstance(row.get("_meta"), dict) else {}
        scout_name = str(meta.get("scout_name") or "").strip()
        if scout_name:
            scout_count[scout_name] = scout_count.get(scout_name, 0) + 1
        for field_name in _reference_field_map(row):
            field_presence_count[field_name] = field_presence_count.get(field_name, 0) + 1

    field_presence = {
        field_name: round(count / total, 4)
        for field_name, count in sorted(field_presence_count.items(), key=lambda item: (-item[1], item[0]))
    }
    required_fields = sorted(
        [field_name for field_name, ratio in field_presence.items() if ratio >= 0.8]
    )
    if not required_fields and field_presence:
        required_fields = [next(iter(field_presence))]
    dominant_sobject = max(sobject_count.items(), key=lambda item: item[1])[0] if sobject_count else None
    preferred_reason_codes = [key for key, _ in sorted(reason_count.items(), key=lambda item: (-item[1], item[0]))[:3]]
    preferred_scouts = [key for key, _ in sorted(scout_count.items(), key=lambda item: (-item[1], item[0]))[:3]]
    return {
        "profile_size": total,
        "dominant_sobject": dominant_sobject,
        "field_presence": field_presence,
        "required_fields": required_fields,
        "preferred_reason_codes": preferred_reason_codes,
        "preferred_scouts": preferred_scouts,
    }


def _score_candidate_row(row: dict[str, Any], profile: dict[str, Any], *, threshold: float) -> dict[str, Any]:
    required_fields = set(_normalize_list(profile.get("required_fields")))
    reference_fields = set(_normalize_list(list((profile.get("field_presence") or {}).keys())))
    candidate_fields = set(_reference_field_map(row).keys())
    completeness = (
        len(required_fields.intersection(candidate_fields)) / len(required_fields)
        if required_fields
        else 1.0
    )
    union_fields = reference_fields.union(candidate_fields)
    shape_similarity = (
        len(reference_fields.intersection(candidate_fields)) / len(union_fields)
        if union_fields
        else 1.0
    )
    reason_code = str(row.get("reason_code") or "").strip()
    preferred_reason_codes = set(_normalize_list(profile.get("preferred_reason_codes")))
    if not preferred_reason_codes:
        reason_alignment = 1.0
    else:
        reason_alignment = 1.0 if reason_code in preferred_reason_codes else 0.0
    score = (0.5 * completeness) + (0.3 * shape_similarity) + (0.2 * reason_alignment)
    missing_required_fields = sorted(required_fields.difference(candidate_fields))
    meta = row.get("_meta") if isinstance(row.get("_meta"), dict) else {}
    source_ref = (
        f"receipts/{meta.get('run_id')}/scan.json"
        f"#finding:{meta.get('finding_index')}:evidence:{meta.get('evidence_index')}"
    )
    return {
        "record_id": str(row.get("record_id") or "").strip(),
        "sobject": str(row.get("sobject") or "").strip(),
        "score": round(score, 4),
        "below_threshold": score < threshold,
        "missing_required_fields": missing_required_fields,
        "reason_code": reason_code or None,
        "reason_alignment": round(reason_alignment, 4),
        "completeness": round(completeness, 4),
        "shape_similarity": round(shape_similarity, 4),
        "source_ref": source_ref,
    }


def score_reference_set(
    *,
    store: ControlPlaneStore,
    state_dir: Path,
    reference_set_id: str,
    threshold: float = 0.7,
    max_candidates: int = 250,
) -> ReferenceScoreV1:
    reference_set = store.get_reference_set(reference_set_id)
    if reference_set is None:
        raise KeyError(f"Unknown reference_set_id: {reference_set_id}")
    bounded_threshold = max(0.0, min(_safe_float(threshold, 0.7), 1.0))
    bounded_candidates = max(1, min(_safe_int(max_candidates, 250), 1000))

    evidence_rows = _scan_evidence_rows(state_dir)
    latest_by_record_id: dict[str, dict[str, Any]] = {}
    for row in evidence_rows:
        record_id = str(row.get("record_id") or "").strip()
        if not record_id:
            continue
        latest_by_record_id[record_id] = row

    reference_rows: list[dict[str, Any]] = []
    missing_reference_ids: list[str] = []
    for record_id in _normalize_list(reference_set.reference_record_ids):
        matched = latest_by_record_id.get(record_id)
        if matched is None:
            missing_reference_ids.append(record_id)
            continue
        reference_rows.append(matched)

    if not reference_rows:
        score = ReferenceScoreV1(
            reference_set_id=reference_set.reference_set_id,
            status="empty_profile",
            threshold=bounded_threshold,
            profile={"profile_size": 0, "missing_reference_ids": missing_reference_ids},
            summary={
                "candidate_count": 0,
                "avg_conformance": None,
                "below_threshold_count": 0,
                "threshold": bounded_threshold,
                "missing_reference_ids": missing_reference_ids,
            },
            deltas=[],
        )
        return store.save_reference_score(score)

    profile = _build_reference_profile(reference_rows)
    dominant_sobject = str(profile.get("dominant_sobject") or "").strip()
    reference_id_set = set(_normalize_list(reference_set.reference_record_ids))
    preferred_reasons = set(_normalize_list(profile.get("preferred_reason_codes")))
    preferred_scouts = set(_normalize_list(profile.get("preferred_scouts")))
    candidate_rows: list[dict[str, Any]] = []
    for row in latest_by_record_id.values():
        record_id = str(row.get("record_id") or "").strip()
        if not record_id or record_id in reference_id_set:
            continue
        if dominant_sobject and str(row.get("sobject") or "").strip() != dominant_sobject:
            continue
        candidate_rows.append(row)
    if preferred_reasons:
        matched = [row for row in candidate_rows if str(row.get("reason_code") or "").strip() in preferred_reasons]
        if matched:
            candidate_rows = matched
    if preferred_scouts:
        matched = [
            row
            for row in candidate_rows
            if str((row.get("_meta") or {}).get("scout_name") or "").strip() in preferred_scouts
        ]
        if matched:
            candidate_rows = matched

    candidate_rows = sorted(
        candidate_rows,
        key=lambda row: str((row.get("_meta") or {}).get("captured_at") or ""),
        reverse=True,
    )[:bounded_candidates]

    if not candidate_rows:
        score = ReferenceScoreV1(
            reference_set_id=reference_set.reference_set_id,
            status="no_candidates",
            threshold=bounded_threshold,
            profile=profile,
            summary={
                "candidate_count": 0,
                "avg_conformance": None,
                "below_threshold_count": 0,
                "threshold": bounded_threshold,
                "missing_reference_ids": missing_reference_ids,
            },
            deltas=[],
        )
        return store.save_reference_score(score)

    deltas = [_score_candidate_row(row, profile, threshold=bounded_threshold) for row in candidate_rows]
    deltas.sort(key=lambda item: (item.get("score", 0.0), item.get("record_id") or ""))
    avg_score = sum(_safe_float(item.get("score"), 0.0) for item in deltas) / len(deltas)
    below = [item for item in deltas if bool(item.get("below_threshold"))]
    missing_field_counts: dict[str, int] = {}
    for item in below:
        for field_name in item.get("missing_required_fields") or []:
            key = str(field_name or "").strip()
            if not key:
                continue
            missing_field_counts[key] = missing_field_counts.get(key, 0) + 1
    top_gaps = [
        {"field_name": field_name, "count": count}
        for field_name, count in sorted(missing_field_counts.items(), key=lambda kv: (-kv[1], kv[0]))[:10]
    ]
    summary = {
        "candidate_count": len(deltas),
        "avg_conformance": round(avg_score, 4),
        "below_threshold_count": len(below),
        "threshold": bounded_threshold,
        "missing_reference_ids": missing_reference_ids,
        "top_gaps": top_gaps,
    }
    score = ReferenceScoreV1(
        reference_set_id=reference_set.reference_set_id,
        status="completed",
        threshold=bounded_threshold,
        profile=profile,
        summary=summary,
        deltas=deltas,
    )
    return store.save_reference_score(score)


def generate_sequence_pause_proposals(
    *,
    store: ControlPlaneStore,
    state_dir: Path,
    dedupe_window_seconds: int = DEFAULT_DEDUPE_WINDOW_SECONDS,
    actor: str = "ai:control-plane",
) -> dict[str, Any]:
    scan = _latest_scan_result_from_receipts(state_dir)
    if scan is None:
        return {"generated": 0, "deduped": 0, "inbox_created": 0, "proposal_ids": [], "run_id": None}

    generated = 0
    deduped = 0
    inbox_created = 0
    proposal_ids: list[str] = []
    now = _utc_now()

    scan_payload = scan.to_dict(include_fields=True, include_reason=True)
    findings = scan_payload.get("findings") if isinstance(scan_payload.get("findings"), list) else []
    for finding_index, finding in enumerate(findings):
        if not isinstance(finding, dict):
            continue
        if str(finding.get("scout_name") or "").strip() != "sequence_collision":
            continue
        if _safe_int(finding.get("findings_count"), 0) <= 0:
            continue

        evidence_rows = finding.get("evidence") if isinstance(finding.get("evidence"), list) else []
        scoped_rows = [row for row in evidence_rows if isinstance(row, dict) and str(row.get("record_id") or "").strip()]
        if not scoped_rows:
            continue
        record_ids = sorted({str(row.get("record_id")).strip() for row in scoped_rows if str(row.get("record_id")).strip()})
        if not record_ids:
            continue

        evidence_ids = [str(uuid.uuid4()) for _ in scoped_rows]
        signature_evidence = [
            _hash_payload(
                {
                    "record_id": row.get("record_id"),
                    "reason_code": row.get("reason_code"),
                    "sobject": row.get("sobject"),
                }
            )
            for row in scoped_rows
        ]
        signature = compute_proposal_signature(
            scope=f"{scan.run_id}:sequence_collision",
            action_family="sequence_pause",
            evidence_ids=signature_evidence,
            window_bucket=_window_bucket(now, max(int(dedupe_window_seconds), 0)),
        )
        reason_code = next(
            (str(row.get("reason_code")).strip() for row in scoped_rows if str(row.get("reason_code") or "").strip()),
            ACTION_KEY_REGISTRY["sequence_pause"]["default_reason_code"],
        )
        confidence = 0.78 if len(record_ids) <= 10 else 0.72
        risk_tier: Literal["low", "medium", "high", "critical"] = "low" if len(record_ids) <= 25 else "medium"

        proposal = ProposalV1(
            tenant_id="default",
            source_kind="scout",
            source_refs=[f"scan:{scan.run_id}:finding:{finding_index}"],
            title=f"Pause sequence for {len(record_ids)} collision record(s)",
            objective="Prevent duplicate sequence touches and reduce collision risk.",
            action_candidates=[
                {
                    "candidate_id": str(uuid.uuid4()),
                    "deterministic_action_key": "sequence_pause",
                    "target_selector": {"entity_type": "lead", "query": f"record_ids:{len(record_ids)}"},
                    "params": {
                        "record_ids": record_ids,
                        "reason_code": reason_code,
                        "scout_name": "sequence_collision",
                    },
                    "estimated_impact": {"entities": len(record_ids), "benefit": "reduce sequence collisions"},
                }
            ],
            confidence={
                "overall": confidence,
                "model_score": confidence,
                "calibrated_score": max(0.0, confidence - 0.04),
                "calibration_version": "v1",
            },
            risk={"tier": risk_tier, "score": 0.3 if risk_tier == "low" else 0.45, "factors": ["reversible_action"]},
            evidence_ids=evidence_ids,
            why_summary=f"sequence_collision surfaced {len(record_ids)} record(s) in active sequence collisions.",
            status="proposed",
            proposal_signature=signature,
            dedupe_window_seconds=max(int(dedupe_window_seconds), 0),
            created_by=actor,
        )
        saved, was_deduped = store.save_proposal(proposal)
        proposal_ids.append(saved.proposal_id)
        if was_deduped:
            deduped += 1
            continue

        evidence_rows_payload: list[EvidenceV1] = []
        for evidence_id, row_index, row in zip(evidence_ids, range(len(scoped_rows)), scoped_rows):
            redaction_class: Literal["none", "partial", "masked"] = "partial" if row.get("fields") else "none"
            evidence_rows_payload.append(
                EvidenceV1(
                    evidence_id=evidence_id,
                    proposal_id=saved.proposal_id,
                    source_type="scout_finding",
                    source_ref=f"receipts/{scan.run_id}/scan.json#finding:{finding_index}:evidence:{row_index}",
                    content_hash=_hash_payload(row),
                    excerpt=_row_excerpt(row),
                    selector={"entity_id": row.get("record_id")},
                    captured_at=scan.ended_at or scan.started_at or _utc_now_iso(),
                    provenance_chain=[f"scan:{scan.run_id}", f"finding:{finding_index}"],
                    trust_score=0.85,
                    snapshot_id=scan.run_id,
                    artifact_version=str(scan.schema_version or "1"),
                    redaction_class=redaction_class,
                )
            )
        store.save_evidence(evidence_rows_payload)
        for item in _build_unknown_inbox_items(evidence_rows_payload, scoped_rows):
            store.upsert_inbox_item(item)
            inbox_created += 1
        store.record_outcome(saved.proposal_id, "proposed", actor=actor, detail={"run_id": scan.run_id})
        generated += 1

    return {
        "generated": generated,
        "deduped": deduped,
        "inbox_created": inbox_created,
        "proposal_ids": proposal_ids,
        "run_id": scan.run_id,
    }


def _reject_code_set(codes: list[str]) -> list[str]:
    normalized = sorted({str(code).strip() for code in codes if str(code).strip()})
    return [code for code in normalized if code in set(REJECT_CODES_V1)]


def compile_proposal(
    *,
    store: ControlPlaneStore,
    state_dir: Path,
    policy: AutonomyPolicy,
    proposal_id: str,
    emit_pending: bool = True,
    actor: str = "compiler@v1",
) -> tuple[CompileResultV1, PlanV1 | None]:
    proposal = store.get_proposal(proposal_id)
    if proposal is None:
        raise KeyError(f"Unknown proposal_id: {proposal_id}")

    evidence_rows = store.list_evidence_for_proposal(proposal_id)
    gate_checks: dict[str, bool] = {
        "mapping_valid": True,
        "rollback_valid": True,
        "invariants_valid": True,
        "budget_valid": True,
        "idempotency_valid": True,
        "unknown_patterns_resolved": True,
        "evidence_valid": True,
        "confidence_valid": True,
    }
    reject_codes: list[str] = []
    details: list[str] = []

    if not proposal.action_candidates:
        gate_checks["mapping_valid"] = False
        reject_codes.append("NO_ACTION_MAPPING")
        details.append("Proposal has no action candidates.")

    if store.has_unresolved_inbox_for_evidence(proposal.evidence_ids):
        gate_checks["unknown_patterns_resolved"] = False
        reject_codes.append("UNRESOLVED_UNKNOWN_PATTERN")
        details.append("Compile blocked by unresolved inbox items.")

    stale_days = max(_safe_int(os.getenv("G_GREMLIN_AI_CONTROL_PLANE_EVIDENCE_STALE_DAYS"), DEFAULT_EVIDENCE_STALE_DAYS), 1)
    stale_cutoff = _utc_now() - timedelta(days=stale_days)
    stale_count = 0
    for evidence in evidence_rows:
        captured = _parse_iso(evidence.captured_at)
        if captured is None or captured < stale_cutoff:
            stale_count += 1
    if stale_count:
        reject_codes.append("EVIDENCE_STALE")
        details.append(f"{stale_count} evidence row(s) are stale.")

    if proposal.source_kind == "ai_draft":
        if not evidence_rows:
            gate_checks["evidence_valid"] = False
            reject_codes.append("EVIDENCE_REQUIRED")
            details.append("AI draft proposals require persisted evidence rows.")
        confidence_payload = proposal.confidence if isinstance(proposal.confidence, dict) else {}
        confidence_score = _safe_float(
            confidence_payload.get("calibrated_score"),
            _safe_float(confidence_payload.get("overall"), 0.0),
        )
        confidence_threshold = min(
            max(
                _safe_float(
                    os.getenv("G_GREMLIN_AI_CONTROL_PLANE_AGENT_MIN_CONFIDENCE"),
                    DEFAULT_AGENT_MIN_CONFIDENCE,
                ),
                0.0,
            ),
            1.0,
        )
        if proposal.action_candidates and confidence_score < confidence_threshold:
            gate_checks["confidence_valid"] = False
            reject_codes.append("LOW_CONFIDENCE")
            details.append(
                f"AI draft confidence {confidence_score:.2f} is below the {confidence_threshold:.2f} minimum."
            )

    steps: list[PlanStep] = []
    planned_total = 0
    day_key = _utc_now().date().isoformat()
    budget_store = ModuleActionBudgetStore(Path(state_dir) / "db" / "module_action_budgets.sqlite")
    registry = ToolRegistry()

    for idx, candidate in enumerate(proposal.action_candidates):
        if not isinstance(candidate, dict):
            continue
        action_key = str(candidate.get("deterministic_action_key") or "").strip()
        action_spec = ACTION_KEY_REGISTRY.get(action_key)
        if action_spec is None:
            gate_checks["mapping_valid"] = False
            reject_codes.append("NO_ACTION_MAPPING")
            details.append(f"Unknown deterministic_action_key: {action_key or '<empty>'}")
            continue

        module_id = str(action_spec.get("module_id") or "").strip()
        tool_name = str(action_spec.get("tool") or "").strip()
        module_category = "general"
        try:
            module_category = str(get_module_definition(module_id).category or "general").strip().lower() or "general"
        except ModuleValidationError:
            module_category = "general"
        reversibility, manifest = resolve_module_execution_contract(module_id, None)
        rollback_required = module_category != "notifications" and action_key != "slack_routing"
        if rollback_required and (not reversibility.reversible or not str(reversibility.undo_action_type or "").strip()):
            gate_checks["rollback_valid"] = False
            reject_codes.append("MISSING_ROLLBACK")
            details.append(f"Module {module_id} has no rollback contract.")
        invariants_required = module_category in {"compliance"}
        if invariants_required and not manifest.invariants:
            gate_checks["invariants_valid"] = False
            reject_codes.append("INVARIANT_UNSATISFIED")
            details.append(f"Module {module_id} has no invariants.")
        if not str(manifest.idempotency_scope or "").strip():
            gate_checks["idempotency_valid"] = False
            reject_codes.append("IDEMPOTENCY_SCOPE_INVALID")
            details.append(f"Module {module_id} has empty idempotency scope.")

        params = candidate.get("params") if isinstance(candidate.get("params"), dict) else {}
        target_selector = (
            candidate.get("target_selector")
            if isinstance(candidate.get("target_selector"), dict)
            else {}
        )
        record_ids = _normalize_list(params.get("record_ids"))
        if not record_ids:
            gate_checks["mapping_valid"] = False
            reject_codes.append("NO_ACTION_MAPPING")
            details.append("Candidate missing record_ids.")
            continue

        planned_count = len(record_ids)
        planned_total += planned_count

        if planned_count > manifest.blast_radius_max_records_per_run:
            gate_checks["budget_valid"] = False
            reject_codes.append("BUDGET_EXCEEDED")
            details.append(
                f"Run budget exceeded for {module_id}: {planned_count} > {manifest.blast_radius_max_records_per_run}."
            )
        if planned_count > policy.defaults.max_bulk_batch:
            gate_checks["budget_valid"] = False
            reject_codes.append("BUDGET_EXCEEDED")
            details.append(f"Policy max_bulk_batch exceeded: {planned_count} > {policy.defaults.max_bulk_batch}.")
        daily_used = budget_store.daily_usage(module_id=module_id, day_key=day_key)
        if daily_used + planned_count > manifest.blast_radius_max_records_per_day:
            gate_checks["budget_valid"] = False
            reject_codes.append("BUDGET_EXCEEDED")
            details.append(
                f"Daily budget exceeded: {daily_used + planned_count} > {manifest.blast_radius_max_records_per_day}."
            )
        over_entity_budget = False
        for record_id in record_ids:
            entity_used = budget_store.entity_usage(module_id=module_id, day_key=day_key, entity_key=record_id)
            if entity_used + 1 > manifest.max_actions_per_entity_per_day:
                over_entity_budget = True
                break
        if over_entity_budget:
            gate_checks["budget_valid"] = False
            reject_codes.append("BUDGET_EXCEEDED")
            details.append("Entity budget exceeded for one or more records.")

        reason_code = str(params.get("reason_code") or action_spec.get("default_reason_code") or "").strip()
        supported_scouts_raw = action_spec.get("supported_scouts") if isinstance(action_spec.get("supported_scouts"), list) else []
        default_scout_name = next((str(value).strip() for value in supported_scouts_raw if str(value).strip()), "")
        scout_name = (
            str(params.get("scout_name") or default_scout_name or "sequence_collision").strip()
            or default_scout_name
            or "sequence_collision"
        )
        step_args = dict(params)
        step_args["record_ids"] = record_ids
        if reason_code:
            step_args["reason_code"] = reason_code
        step_args["scout_name"] = scout_name
        step_args["source_kind"] = proposal.source_kind
        step_args["proposal_id"] = proposal.proposal_id
        step_args["deterministic_action_key"] = action_key
        entity_type = str(target_selector.get("entity_type") or "").strip()
        if entity_type:
            step_args.setdefault("entity_type", entity_type)
        query = str(target_selector.get("query") or "").strip()
        if query:
            step_args.setdefault("target_query", query)
        effective_tool_def = _effective_compile_tool_definition(
            registry,
            tool_name=tool_name,
            step_args=step_args,
        )
        effective_risk_level = _risk_level_from_tier(proposal.risk_tier)
        if effective_tool_def is not None:
            try:
                effective_risk_level = max(
                    effective_risk_level,
                    int(getattr(effective_tool_def, "risk_level", 0) or 0),
                )
            except Exception:
                effective_risk_level = max(effective_risk_level, 0)
        steps.append(
            PlanStep(
                step_id=f"s{idx + 1}",
                tool=tool_name,
                args=step_args,
                risk_level=max(effective_risk_level, 1),
                idempotency_key=f"{proposal.proposal_signature}:{idx + 1}",
                prechecks=list(getattr(effective_tool_def, "default_prechecks", []) or []),
                postchecks=list(getattr(effective_tool_def, "default_postchecks", []) or []),
                evidence_ids=list(proposal.evidence_ids),
                rollback_on_failure=True,
            )
        )

    if planned_total > policy.defaults.max_records_per_run:
        gate_checks["budget_valid"] = False
        reject_codes.append("BUDGET_EXCEEDED")
        details.append(f"Policy max_records_per_run exceeded: {planned_total} > {policy.defaults.max_records_per_run}.")

    has_gate_fail = not all(gate_checks.values())
    status: Literal["compiled", "rejected", "blocked_inbox"] = "compiled"
    if not gate_checks["unknown_patterns_resolved"]:
        status = "blocked_inbox"
    elif has_gate_fail or bool(_reject_code_set(reject_codes)):
        status = "rejected"

    compile_id = str(uuid.uuid4())
    gate_snapshot = {
        "gate_checks": gate_checks,
        "reject_taxonomy_version": REJECT_TAXONOMY_VERSION,
        "action_registry_version": ACTION_KEY_REGISTRY_VERSION,
        "checked_at": _utc_now_iso(),
    }

    plan: PlanV1 | None = None
    plan_id: str | None = None
    plan_hash: str | None = None
    if status == "compiled":
        plan_id = f"proposal_{proposal.proposal_id[:8]}_{_utc_now().strftime('%Y%m%dT%H%M%S')}"
        plan = PlanV1(
            run_id=plan_id,
            source=PlanSource(type="ai_proposal", scan_id=proposal.source_refs[0] if proposal.source_refs else None),
            summary=proposal.title,
            risk=PlanRisk(
                max_risk_level=max((step.risk_level for step in steps), default=1),
                irreversible=False,
                blast_radius="bulk_records",
                estimated_count=planned_total,
                target_object="Lead",
            ),
            steps=steps,
            approvals_required=True,
            verification=["control_plane_compile"],
            source_kind=proposal.source_kind,
            proposal_id=proposal.proposal_id,
            compile_id=compile_id,
            compile_gate_snapshot=gate_snapshot,
            proposal_confidence=dict(proposal.confidence),
            proposal_risk=dict(proposal.risk),
            why_summary=proposal.why_summary,
            evidence_ids=list(proposal.evidence_ids),
        )
        plan_hash = plan.plan_hash
        if emit_pending:
            pending_dir = Path(state_dir) / "pending" / plan.run_id
            pending_dir.mkdir(parents=True, exist_ok=True)
            (pending_dir / "plan.json").write_text(json.dumps(plan.to_dict(), indent=2), encoding="utf-8")
            pending_action = build_pending_action(
                plan=plan,
                actor=ActorContext(
                    actor_id=str(actor or "").strip() or "compiler@v1",
                    roles=["revops"],
                    auth_source="control_plane",
                ),
            )
            write_pending_action(state_dir, pending_action)

    result = CompileResultV1(
        compile_id=compile_id,
        proposal_id=proposal.proposal_id,
        status=status,
        plan_id=plan_id,
        plan_hash=plan_hash,
        gate_checks=gate_checks,
        reject_codes=_reject_code_set(reject_codes),
        reject_taxonomy_version=REJECT_TAXONOMY_VERSION,
        details=details,
        compile_gate_snapshot=gate_snapshot,
        compiled_by=actor,
    )
    store.save_compile_result(result)
    if status == "compiled":
        store.update_proposal_status(proposal.proposal_id, "compiled")
        store.record_outcome(proposal.proposal_id, "compiled", actor=actor, detail={"compile_id": compile_id, "plan_id": plan_id})
    elif status == "blocked_inbox":
        store.update_proposal_status(proposal.proposal_id, "compile_rejected")
        store.record_outcome(proposal.proposal_id, "blocked_inbox", actor=actor, detail={"compile_id": compile_id, "reject_codes": result.reject_codes})
    else:
        store.update_proposal_status(proposal.proposal_id, "compile_rejected")
        store.record_outcome(proposal.proposal_id, "compile_rejected", actor=actor, detail={"compile_id": compile_id, "reject_codes": result.reject_codes})
    return result, plan


def proposal_detail_payload(store: ControlPlaneStore, proposal_id: str) -> dict[str, Any] | None:
    proposal = store.get_proposal(proposal_id)
    if proposal is None:
        return None
    evidence = store.list_evidence_for_proposal(proposal_id)
    compile_result = store.latest_compile_result(proposal_id)
    outcomes = store.list_outcomes(proposal_id)
    return {
        "proposal": proposal.to_dict(),
        "evidence": [item.to_dict() for item in evidence],
        "latest_compile_result": compile_result.to_dict() if compile_result else None,
        "outcomes": [item.to_dict() for item in outcomes],
        "action_registry_version": ACTION_KEY_REGISTRY_VERSION,
        "reject_taxonomy_version": REJECT_TAXONOMY_VERSION,
    }


__all__ = [
    "ACTION_KEY_REGISTRY",
    "ACTION_KEY_REGISTRY_VERSION",
    "ControlPlaneJobV1",
    "ControlPlaneLimits",
    "ControlPlaneStore",
    "CompileResultV1",
    "EvidenceV1",
    "InboxItemV1",
    "ReferenceScoreV1",
    "ReferenceSetV1",
    "ProposalOutcomeEventV1",
    "ProposalV1",
    "REJECT_CODES_V1",
    "REJECT_TAXONOMY_VERSION",
    "V1_NON_GOALS",
    "compile_proposal",
    "compute_proposal_signature",
    "control_plane_store",
    "generate_sequence_pause_proposals",
    "proposal_detail_payload",
    "resolve_deterministic_action_key",
    "score_reference_set",
]
