"""FastAPI server for Gremlin Autopilot.

Provides a localhost web UI for RevOps users with:
- CSRF protection via session tokens and Origin/Referer validation
- Localhost-only binding by default
- Remote access with pairing code authentication
"""

from __future__ import annotations

import asyncio
from collections import defaultdict
import hashlib
import json
import logging
import os
import secrets
import shutil
import sqlite3
import sys
import time as _time
from datetime import datetime, timedelta, timezone
from importlib import resources as importlib_resources
from pathlib import Path
from statistics import median
from types import SimpleNamespace
from typing import TYPE_CHECKING, Any, Optional
from urllib.error import HTTPError, URLError
from urllib.parse import parse_qs, quote, urlencode, urlparse
from urllib.request import Request as UrlRequest, urlopen

from fastapi import FastAPI, HTTPException, Query, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, PlainTextResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
from g_gremlin.paths import gremlin_path
import jwt

from .models import (
    ChatBudgetUsed,
    ChatMode,
    ChatRequest,
    ChatResponse,
    ChatSource,
    ExecuteRequest,
    ExecuteResponse,
    HealthResponse,
    JobStatus,
    PairRequest,
    PairResponse,
    PlayListResponse,
    RunPlayRequest,
)
from .policy import apply_policy_overlays, compute_policy_hash, generate_default_policy, load_policy
from .receipts import ConfigReceiptWriter, ReceiptWriter
from .state import resolve_state_dir
from .token_store import TokenStore
from .audit_log import AuditLog
from .scout_config_store import ScoutConfigStore
from .daemon import (
    bump_runtime_policy_revision,
    read_runtime_policy_revision,
    read_runtime_status,
    write_runtime_reload_signal,
)
from .control_plane_job_queue import (
    ControlPlaneJobQueue,
    DEFAULT_VISIBILITY_TIMEOUT_SECONDS as CONTROL_PLANE_QUEUE_VISIBILITY_TIMEOUT_SECONDS,
    control_plane_job_queue_db_path,
)
from .event_processor import CrmEventProcessor
from .event_queue import CrmEventQueue, event_queue_db_path
from .hubspot_webhooks import verify_hubspot_signature
from .module_policy_bridge import compile_runtime_policy, diff_module_revision, simulate_module_on_samples
from .modules import (
    ModuleCompileError,
    ModuleEnvelope,
    ModulePutPayload,
    ModuleStore,
    ModuleValidationError,
    RevisionConflictError,
    default_module_envelope,
    get_module_definition,
    infer_module_write_class_from_tool,
    list_module_definitions,
    resolve_module_execution_contract,
    validate_module_envelope,
)
from .approvals import ApprovalTokenStore
from .executor import PlanExecutor
from .tools import get_default_registry
from .contracts import ActorContext, PlanStep, PlanV1, ReceiptApprovedBy, ReceiptV1
from .discovery import DiscoveryRunner, DiscoveryStore
from .concierge_planner import ConciergePlanner
from .concierge_executor import ConciergeExecutor
from .gate_metrics import GateMetricStore
from .ai_provider import AIProviderConfig, AIProviderStore, ai_generate_text, test_ai_provider
from .control_plane import (
    ACTION_KEY_REGISTRY,
    ACTION_KEY_REGISTRY_VERSION,
    DEFAULT_DEDUPE_WINDOW_SECONDS,
    EvidenceV1,
    ProposalV1,
    REJECT_CODES_V1,
    REJECT_TAXONOMY_VERSION,
    V1_NON_GOALS,
    ControlPlaneLimits,
    ReferenceSetV1,
    compile_proposal,
    compute_proposal_signature,
    control_plane_store,
    generate_sequence_pause_proposals,
    proposal_detail_payload,
    resolve_deterministic_action_key,
    score_reference_set,
)
from .briefing import compose_briefing, load_latest_briefing, write_briefing
from .llm_provider import resolve_provider
from .pending_actions import (
    build_pending_action,
    load_pending_action,
    pending_action_path as pending_action_file_path,
    write_pending_action,
)
from .readiness_gate import autonomy_operational_health, check_autonomy_readiness
from .revops_chat import RevOpsChatRequest, RevOpsChatResponse, RevOpsChatService
from .slack_notify import (
    create_client as slack_create_client,
    post_anomaly_alert,
    post_approval_request,
    post_briefing,
    post_receipt_summary,
    resolve_channel as resolve_slack_channel,
)
from .slack_dealdesk import (
    build_close_review_submission_blocks,
    build_deal_lookup_blocks,
    build_contact_role_result_blocks,
    build_seller_contact_role_modal,
    build_field_fix_result_blocks,
    build_revops_close_review_alert_blocks,
    build_seller_field_fix_modal,
    build_seller_deal_health_blocks,
    build_seller_quote_revision_approval_request_blocks,
    build_seller_quote_revision_apply_result_blocks,
    build_seller_quote_revision_preview_blocks,
    build_seller_quote_status_blocks,
    parse_seller_command,
    seller_contact_modal_fallback_text,
    seller_fix_modal_fallback_text,
    seller_command_usage_text,
)
from .slack_verify import verify_slack_signature
from .support_bundle import build_support_bundle
from .tuning import compute_tuning_suggestions
from .workflow_composer import WorkflowCompositionError, expand_selected_step_ids
from g_gremlin.scout.engine import ScoutEngine
from g_gremlin.scout.loader import SCAN_DEFAULTS_FILE, USER_SCOUTS_DIR, ScoutLoader, load_scan_config
from g_gremlin.scout.models import ScanConfig
from g_gremlin.dealdesk.forensics import fetch_historical_opportunities, normalize_deal_record
from g_gremlin.dealdesk.service import (
    apply_seller_contact_role_add,
    apply_seller_missing_field_fix,
    apply_seller_quote_revision,
    build_quote_intent_v1,
    get_seller_deal_health_summary,
    get_seller_quote_status_summary,
    load_seller_quote_revision_approval_context,
    lookup_seller_active_deals,
    preview_seller_quote_revision,
    run_close_certification,
    SellerQuoteRevisionApplyResultV1,
    submit_deal_for_close_review,
)

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

logger = logging.getLogger(__name__)

# Session token storage location
SESSION_KEY_PATH = gremlin_path("autopilot", "session.key")
LOCALHOSTS = {"localhost", "127.0.0.1", "::1"}
_CACHE_TTL = 30  # seconds for in-process caches
RUNTIME_POLICY_META_PATH = "policy_runtime.meta.json"
RUNTIME_POLICY_PATH = "policy_runtime.yaml"
MODULE_CONFIG_RECEIPTS_DIR = "config_receipts"
OAUTH_STATE_FILE = "oauth_states.json"
OAUTH_STATE_TTL_SECONDS = 15 * 60
OUTREACH_OAUTH_SESSION_FILE = "outreach_oauth_session.json"
OUTREACH_OAUTH_SESSION_TTL_SECONDS = 60 * 60
CRM_INTEGRATIONS = ("salesforce", "hubspot", "dynamics")
ADMIN_INTEGRATIONS = CRM_INTEGRATIONS + ("outreach",)
INTEGRATION_STATUS_FILE = "integration_status.json"
MODULE_PRECEDENCE_MATRIX = [
    "global kill switches and deny tags",
    "policy-level red blocks",
    "module enabled=false block",
    "module risk tiers (green/yellow/red)",
    "policy approval rules for yellow",
    "auto-apply only for green",
]
PENDING_CLUSTER_WINDOW_SECONDS = 6 * 60 * 60
PENDING_BATCH_IDEMPOTENCY_TTL_SECONDS = 24 * 60 * 60
PENDING_CLUSTER_CONTRACT_VERSION = "v1"
AGENT_TRUST_DELTA_LOOKBACK_DAYS = 7
AGENT_TRUST_SNAPSHOT_RETENTION_DAYS = 120

FEATURE_FLAG_DEFAULTS: dict[str, bool] = {
    "control_tower_v2": True,
    "exceptions_report_v2": True,
    "learning_dashboard_v1": False,
}

OPERATOR_INTENT_LABELS: dict[str, str] = {
    "do_more_like_this": "Do more like this",
    "too_risky": "Too risky",
    "wrong_target": "Wrong target",
    "pause_agent": "Pause this agent",
}
ACTOR_ROLES: tuple[str, ...] = ("admin", "revops", "approver", "viewer")
_ROLE_IMPLIED_BY: dict[str, set[str]] = {
    "admin": {"admin", "revops", "approver", "viewer"},
    "revops": {"revops", "approver", "viewer"},
    "approver": {"approver", "viewer"},
    "viewer": {"viewer"},
}
_OIDC_DEFAULT_TOKEN_HEADERS: tuple[str, ...] = (
    "cf-access-jwt-assertion",
    "x-forwarded-access-token",
    "x-auth-request-access-token",
    "x-autopilot-oidc-token",
)
_OIDC_DEFAULT_ALLOWED_ALGORITHMS: tuple[str, ...] = (
    "RS256",
    "RS384",
    "RS512",
    "ES256",
    "ES384",
    "ES512",
)


def _env_bool(name: str, default: bool) -> bool:
    raw = str(os.getenv(name, "")).strip().lower()
    if not raw:
        return bool(default)
    if raw in {"1", "true", "yes", "on"}:
        return True
    if raw in {"0", "false", "no", "off"}:
        return False
    return bool(default)


def _default_rbac_role(default: str = "revops") -> str:
    raw = str(os.getenv("G_GREMLIN_RBAC_DEFAULT_ROLE", "")).strip().lower()
    if raw in ACTOR_ROLES:
        return raw
    return default if default in ACTOR_ROLES else "revops"


def _resolve_hubspot_webhook_secret() -> str | None:
    for env_name in ("G_GREMLIN_HUBSPOT_WEBHOOK_SECRET", "G_GREMLIN_HUBSPOT_CLIENT_SECRET"):
        value = str(os.getenv(env_name, "")).strip()
        if value:
            return value
    return None


def _trust_identity_headers() -> bool:
    return _env_bool("G_GREMLIN_TRUST_IDENTITY_HEADERS", False)


def _approval_allowed_roles(policy: Any) -> set[str]:
    defaults = getattr(policy, "defaults", None)
    configured = getattr(defaults, "approver_roles", []) if defaults is not None else []
    allowed = {str(role).strip().lower() for role in (configured or []) if str(role).strip().lower() in ACTOR_ROLES}
    if allowed:
        return allowed
    return {"admin", "revops", "approver"}


def _principal_fingerprint(secret: str) -> str:
    encoded = str(secret or "").encode("utf-8")
    return hashlib.sha256(encoded).hexdigest()[:12]


def _service_principal_actor_id(auth_source: str, secret: str) -> str:
    prefix = str(auth_source or "service").strip().lower() or "service"
    return f"{prefix}:{_principal_fingerprint(secret)}"


def _oidc_trusted_issuer() -> str | None:
    raw = str(os.getenv("G_GREMLIN_TRUSTED_OIDC_ISSUER", "")).strip()
    return raw or None


def _oidc_jwks_uri() -> str | None:
    raw = str(os.getenv("G_GREMLIN_OIDC_JWKS_URI", "")).strip()
    return raw or None


def _oidc_audience() -> str | None:
    raw = str(os.getenv("G_GREMLIN_OIDC_AUDIENCE", "")).strip()
    return raw or None


def _oidc_claim_name(env_var: str, default: str) -> str:
    raw = str(os.getenv(env_var, "")).strip()
    return raw or default


def _oidc_groups_claim_name() -> str:
    return _oidc_claim_name("G_GREMLIN_OIDC_GROUPS_CLAIM", "groups")


def _oidc_sub_claim_name() -> str:
    return _oidc_claim_name("G_GREMLIN_OIDC_SUB_CLAIM", "sub")


def _oidc_email_claim_name() -> str:
    return _oidc_claim_name("G_GREMLIN_OIDC_EMAIL_CLAIM", "email")


def _oidc_enabled() -> bool:
    return bool(_oidc_trusted_issuer() and _oidc_jwks_uri())


def _oidc_jwks_cache_ttl_seconds(default: int = 300) -> int:
    raw = str(os.getenv("G_GREMLIN_OIDC_JWKS_CACHE_TTL_SECONDS", "")).strip()
    if not raw:
        return default
    try:
        value = int(raw)
    except Exception:
        return default
    return value if value > 0 else default


def _oidc_leeway_seconds(default: int = 30) -> int:
    raw = str(os.getenv("G_GREMLIN_OIDC_LEEWAY_SECONDS", "")).strip()
    if not raw:
        return default
    try:
        value = int(raw)
    except Exception:
        return default
    return value if value >= 0 else default


def _oidc_allowed_algorithms() -> list[str]:
    raw = str(os.getenv("G_GREMLIN_OIDC_ALLOWED_ALGORITHMS", "")).strip()
    values = (
        [part.strip().upper() for part in raw.replace(";", ",").split(",")]
        if raw
        else list(_OIDC_DEFAULT_ALLOWED_ALGORITHMS)
    )
    ordered: list[str] = []
    seen: set[str] = set()
    for value in values:
        if not value or value == "NONE" or value in seen:
            continue
        seen.add(value)
        ordered.append(value)
    return ordered or list(_OIDC_DEFAULT_ALLOWED_ALGORITHMS)


def _oidc_role_map() -> dict[str, list[str]]:
    raw = str(os.getenv("G_GREMLIN_OIDC_ROLE_MAP", "")).strip()
    if not raw:
        return {}
    try:
        payload = json.loads(raw)
    except Exception:
        return {}
    if not isinstance(payload, dict):
        return {}
    mapping: dict[str, list[str]] = {}
    for group_name, raw_roles in payload.items():
        key = str(group_name).strip()
        if not key:
            continue
        roles = _normalize_actor_roles(raw_roles)
        if roles:
            mapping[key] = roles
    return mapping


def _extract_header_bearer_token(raw: str) -> str:
    value = str(raw or "").strip()
    if not value:
        return ""
    if value.lower().startswith("bearer "):
        return value[7:].strip()
    return value


def _oidc_token_header_names() -> tuple[str, ...]:
    configured = str(os.getenv("G_GREMLIN_OIDC_TOKEN_HEADER", "")).strip().lower()
    names: list[str] = []
    seen: set[str] = set()
    if configured:
        names.append(configured)
    names.extend(_OIDC_DEFAULT_TOKEN_HEADERS)
    ordered: list[str] = []
    for name in names:
        candidate = str(name).strip().lower()
        if not candidate or candidate in seen:
            continue
        seen.add(candidate)
        ordered.append(candidate)
    return tuple(ordered)


def _extract_oidc_proxy_token(request: Request) -> tuple[str | None, str | None]:
    for header_name in _oidc_token_header_names():
        value = _extract_header_bearer_token(request.headers.get(header_name, ""))
        if value:
            return value, header_name
    return None, None


def _oidc_groups_from_claims(claims: dict[str, Any]) -> list[str]:
    raw_value = claims.get(_oidc_groups_claim_name())
    if raw_value is None:
        return []
    if isinstance(raw_value, list):
        values = [str(value).strip() for value in raw_value]
    else:
        values = [part.strip() for part in str(raw_value).replace(";", ",").split(",")]
    return [value for value in values if value]


def _oidc_roles_from_claims(claims: dict[str, Any]) -> list[str]:
    roles: list[str] = []
    groups = _oidc_groups_from_claims(claims)
    role_map = _oidc_role_map()
    for group_name in groups:
        direct_roles = _normalize_actor_roles([group_name.lower()])
        if direct_roles:
            roles.extend(direct_roles)
        mapped_roles = role_map.get(group_name)
        if mapped_roles:
            roles.extend(mapped_roles)
    normalized = _normalize_actor_roles(roles)
    return normalized or ["viewer"]


def _oidc_actor_id(issuer: str, subject: str) -> str:
    return f"oidc:{_principal_fingerprint(issuer)}:{subject}"


def _load_oidc_jwks(app: FastAPI) -> dict[str, Any]:
    jwks_uri = _oidc_jwks_uri()
    if not jwks_uri:
        raise ValueError("OIDC JWKS URI is not configured.")
    cache = getattr(app.state, "oidc_jwks_cache", None)
    now = _time.time()
    if isinstance(cache, dict):
        cached_uri = str(cache.get("uri") or "").strip()
        expires_at = float(cache.get("expires_at") or 0.0)
        payload = cache.get("payload")
        if cached_uri == jwks_uri and expires_at > now and isinstance(payload, dict):
            return payload

    status, payload, text = _http_request_json(
        "GET",
        jwks_uri,
        headers={"Accept": "application/json"},
        timeout=10.0,
    )
    if status >= 400:
        raise ValueError(_response_error_message(payload, text, default=f"OIDC JWKS fetch failed with HTTP {status}"))
    if not isinstance(payload, dict) or not isinstance(payload.get("keys"), list):
        raise ValueError("OIDC JWKS payload is invalid.")
    app.state.oidc_jwks_cache = {
        "uri": jwks_uri,
        "payload": payload,
        "expires_at": now + float(_oidc_jwks_cache_ttl_seconds()),
    }
    return payload


def _resolve_oidc_signing_key(token: str, jwks_payload: dict[str, Any]) -> tuple[Any, str]:
    try:
        header = jwt.get_unverified_header(token)
    except jwt.PyJWTError as exc:
        raise ValueError(f"OIDC JWT header is invalid: {exc}") from exc
    if not isinstance(header, dict):
        raise ValueError("OIDC JWT header is invalid.")
    algorithm = str(header.get("alg") or "").strip().upper()
    if algorithm not in _oidc_allowed_algorithms():
        raise ValueError(f"OIDC JWT algorithm {algorithm or 'unknown'} is not allowed.")
    key_id = str(header.get("kid") or "").strip()
    keys = jwks_payload.get("keys")
    if not isinstance(keys, list):
        raise ValueError("OIDC JWKS payload is invalid.")
    candidates = [entry for entry in keys if isinstance(entry, dict)]
    if key_id:
        candidates = [entry for entry in candidates if str(entry.get("kid") or "").strip() == key_id]
    if not candidates:
        raise ValueError("OIDC JWT signing key was not found in JWKS.")
    for candidate in candidates:
        try:
            jwk = jwt.PyJWK.from_dict(candidate)
        except Exception:
            continue
        if str(getattr(jwk, "algorithm_name", "") or algorithm).strip().upper() != algorithm:
            continue
        return jwk.key, algorithm
    raise ValueError("OIDC JWT signing key is unusable.")


def _validate_oidc_token(app: FastAPI, token: str) -> dict[str, Any]:
    issuer = _oidc_trusted_issuer()
    if not issuer:
        raise ValueError("OIDC issuer is not configured.")
    jwks_payload = _load_oidc_jwks(app)
    signing_key, algorithm = _resolve_oidc_signing_key(token, jwks_payload)
    audience = _oidc_audience()
    decode_kwargs: dict[str, Any] = {
        "key": signing_key,
        "algorithms": [algorithm],
        "issuer": issuer,
        "leeway": _oidc_leeway_seconds(),
    }
    if audience:
        decode_kwargs["audience"] = audience
    else:
        decode_kwargs["options"] = {"verify_aud": False}
    try:
        claims = jwt.decode(token, **decode_kwargs)
    except jwt.PyJWTError as exc:
        raise ValueError(f"OIDC JWT validation failed: {exc}") from exc
    if not isinstance(claims, dict):
        raise ValueError("OIDC JWT claims payload is invalid.")
    return claims


def _build_oidc_actor_context(app: FastAPI, token: str) -> ActorContext:
    claims = _validate_oidc_token(app, token)
    issuer = _oidc_trusted_issuer() or "oidc"
    subject_claim = _oidc_sub_claim_name()
    email_claim = _oidc_email_claim_name()
    subject = str(claims.get(subject_claim) or "").strip()
    if not subject:
        raise ValueError(f"OIDC JWT is missing required claim {subject_claim!r}.")
    email = str(claims.get(email_claim) or "").strip() or None
    return ActorContext(
        actor_id=_oidc_actor_id(issuer, subject),
        roles=_oidc_roles_from_claims(claims),
        auth_source="oidc_jwt",
        mapped_email=email,
        principal_type="user",
        verified_identity=True,
    )


def _bind_authenticated_principal(
    request: Request,
    *,
    auth_source: str,
    actor_id: str,
    roles: list[str],
    principal_type: str = "service",
    mapped_email: str | None = None,
    verified_identity: bool = False,
) -> None:
    setattr(
        request.state,
        "actor_context",
        ActorContext(
            actor_id=str(actor_id or "").strip() or "unknown",
            roles=_normalize_actor_roles(roles) or ["viewer"],
            auth_source=str(auth_source or "unknown").strip() or "unknown",
            mapped_email=str(mapped_email or "").strip() or None,
            principal_type=str(principal_type or "").strip() or None,
            verified_identity=bool(verified_identity),
        ),
    )


def _feature_flags_payload() -> dict[str, bool]:
    return {
        "control_tower_v2": _env_bool("G_GREMLIN_FEATURE_CONTROL_TOWER_V2", FEATURE_FLAG_DEFAULTS["control_tower_v2"]),
        "exceptions_report_v2": _env_bool("G_GREMLIN_FEATURE_EXCEPTIONS_REPORT_V2", FEATURE_FLAG_DEFAULTS["exceptions_report_v2"]),
        "learning_dashboard_v1": _env_bool("G_GREMLIN_FEATURE_LEARNING_DASHBOARD_V1", FEATURE_FLAG_DEFAULTS["learning_dashboard_v1"]),
    }


def _stable_sha256(payload: dict[str, Any]) -> str:
    encoded = json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8")
    return f"sha256:{hashlib.sha256(encoded).hexdigest()}"


def _window_bucket_timestamp(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 _scan_defaults_override_path() -> Path:
    return USER_SCOUTS_DIR / SCAN_DEFAULTS_FILE


def _coerce_schedule_interval_hours(value: Any) -> int | None:
    if value is None:
        return None
    if isinstance(value, str):
        candidate_raw = value.strip()
        if not candidate_raw:
            return None
    else:
        candidate_raw = value
    try:
        candidate = int(candidate_raw)
    except Exception as exc:
        raise HTTPException(status_code=400, detail="schedule_interval_hours must be an integer") from exc
    if candidate <= 0:
        return None
    if candidate > 24 * 7:
        raise HTTPException(status_code=400, detail="schedule_interval_hours must be between 1 and 168")
    return candidate


def _save_scan_config_override(scan_cfg: ScanConfig) -> Path:
    path = _scan_defaults_override_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    payload = {
        "scouts_enabled": list(scan_cfg.scouts_enabled),
        "top_n": int(scan_cfg.top_n),
        "per_scout_params": scan_cfg.per_scout_params,
        "output_format": scan_cfg.output_format,
        "org_alias": scan_cfg.org_alias,
        "crm": scan_cfg.crm,
        "hubspot_profile": scan_cfg.hubspot_profile,
        "dynamics_profile": scan_cfg.dynamics_profile,
        "schedule_interval_hours": scan_cfg.schedule_interval_hours,
    }
    filtered_payload = {key: value for key, value in payload.items() if value is not None}
    try:
        import yaml

        text = yaml.safe_dump(
            filtered_payload,
            sort_keys=False,
            default_flow_style=False,
            allow_unicode=False,
        )
    except Exception:
        text = json.dumps(filtered_payload, indent=2)
    path.write_text(text, encoding="utf-8")
    return path


def _batch_actions_db_path(state_dir: Path) -> Path:
    return Path(state_dir) / "db" / "pending_batch_actions.sqlite"


def _init_batch_actions_db(db_path: Path) -> None:
    db_path.parent.mkdir(parents=True, exist_ok=True)
    with sqlite3.connect(db_path) as conn:
        conn.execute("PRAGMA journal_mode=WAL")
        conn.execute("PRAGMA busy_timeout=5000")
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS pending_batch_actions (
                batch_id TEXT PRIMARY KEY,
                actor TEXT NOT NULL,
                action TEXT NOT NULL,
                idempotency_key TEXT NOT NULL,
                request_hash TEXT NOT NULL,
                response_json TEXT NOT NULL,
                created_at TEXT NOT NULL
            )
            """
        )
        conn.execute(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS idx_pending_batch_actions_idempotency
            ON pending_batch_actions(actor, action, idempotency_key)
            """
        )


def _load_idempotent_batch_response(
    db_path: Path,
    *,
    actor: str,
    action: str,
    idempotency_key: str,
) -> dict[str, Any] | None:
    entry = _load_idempotent_batch_entry(
        db_path,
        actor=actor,
        action=action,
        idempotency_key=idempotency_key,
    )
    if not isinstance(entry, dict):
        return None
    try:
        payload = json.loads(str(entry.get("response_json") or "{}"))
    except Exception:
        return None
    return payload if isinstance(payload, dict) else None


def _load_idempotent_batch_entry(
    db_path: Path,
    *,
    actor: str,
    action: str,
    idempotency_key: str,
) -> dict[str, Any] | None:
    _init_batch_actions_db(db_path)
    with sqlite3.connect(db_path) as conn:
        conn.row_factory = sqlite3.Row
        row = conn.execute(
            """
            SELECT request_hash, response_json, created_at
            FROM pending_batch_actions
            WHERE actor = ? AND action = ? AND idempotency_key = ?
            LIMIT 1
            """,
            (actor, action, idempotency_key),
        ).fetchone()
    if row is None:
        return None
    created_at = _parse_iso(row["created_at"])
    if created_at is None:
        return None
    if (datetime.now(timezone.utc) - created_at).total_seconds() > PENDING_BATCH_IDEMPOTENCY_TTL_SECONDS:
        return None
    return {
        "request_hash": str(row["request_hash"] or "").strip(),
        "response_json": str(row["response_json"] or "{}"),
        "created_at": row["created_at"],
    }


def _persist_idempotent_batch_response(
    db_path: Path,
    *,
    batch_id: str,
    actor: str,
    action: str,
    idempotency_key: str,
    request_hash: str,
    response: dict[str, Any],
) -> None:
    _init_batch_actions_db(db_path)
    created_at = datetime.now(timezone.utc).isoformat()
    with sqlite3.connect(db_path) as conn:
        conn.execute("PRAGMA journal_mode=WAL")
        conn.execute("PRAGMA busy_timeout=5000")
        conn.execute(
            """
            INSERT INTO pending_batch_actions (
                batch_id, actor, action, idempotency_key, request_hash, response_json, created_at
            )
            VALUES (?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(actor, action, idempotency_key) DO UPDATE SET
                request_hash = excluded.request_hash,
                response_json = excluded.response_json,
                created_at = excluded.created_at
            """,
            (
                batch_id,
                actor,
                action,
                idempotency_key,
                request_hash,
                json.dumps(response, ensure_ascii=True),
                created_at,
            ),
        )


def _agent_trust_snapshots_db_path(state_dir: Path) -> Path:
    return Path(state_dir) / "db" / "agent_trust_snapshots.sqlite"


def _init_agent_trust_snapshots_db(db_path: Path) -> None:
    db_path.parent.mkdir(parents=True, exist_ok=True)
    with sqlite3.connect(db_path) as conn:
        conn.execute("PRAGMA journal_mode=WAL")
        conn.execute("PRAGMA busy_timeout=5000")
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS agent_trust_snapshots (
                snapshot_id TEXT PRIMARY KEY,
                day_key TEXT NOT NULL,
                module_id TEXT NOT NULL,
                execution_tier TEXT,
                trust_score REAL,
                override_rate REAL,
                total_runs INTEGER,
                created_at TEXT NOT NULL
            )
            """
        )
        conn.execute(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_trust_snapshots_day_module
            ON agent_trust_snapshots(day_key, module_id)
            """
        )
        conn.execute(
            """
            CREATE INDEX IF NOT EXISTS idx_agent_trust_snapshots_module_day
            ON agent_trust_snapshots(module_id, day_key DESC)
            """
        )


def _persist_agent_trust_snapshots(state_dir: Path, module_rows: list[dict[str, Any]]) -> None:
    db_path = _agent_trust_snapshots_db_path(state_dir)
    _init_agent_trust_snapshots_db(db_path)

    now = datetime.now(timezone.utc)
    day_key = now.strftime("%Y-%m-%d")
    created_at = now.isoformat()
    retention_cutoff = (now - timedelta(days=AGENT_TRUST_SNAPSHOT_RETENTION_DAYS)).strftime("%Y-%m-%d")

    with sqlite3.connect(db_path) as conn:
        conn.execute("PRAGMA journal_mode=WAL")
        conn.execute("PRAGMA busy_timeout=5000")
        conn.execute("DELETE FROM agent_trust_snapshots WHERE day_key < ?", (retention_cutoff,))
        for row in module_rows:
            if not isinstance(row, dict):
                continue
            module_id = str(row.get("module_id") or "").strip()
            if not module_id:
                continue
            metrics = row.get("metrics") if isinstance(row.get("metrics"), dict) else {}
            trust_score = (
                float(metrics.get("trust_score"))
                if isinstance(metrics.get("trust_score"), (int, float))
                else None
            )
            override_rate = (
                float(metrics.get("override_rate"))
                if isinstance(metrics.get("override_rate"), (int, float))
                else None
            )
            total_runs = (
                int(metrics.get("total_runs"))
                if isinstance(metrics.get("total_runs"), (int, float))
                else None
            )
            execution_tier = str(row.get("stage") or "").strip() or None
            snapshot_id = f"{day_key}:{module_id}"
            conn.execute(
                """
                INSERT INTO agent_trust_snapshots (
                    snapshot_id,
                    day_key,
                    module_id,
                    execution_tier,
                    trust_score,
                    override_rate,
                    total_runs,
                    created_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                ON CONFLICT(day_key, module_id) DO UPDATE SET
                    snapshot_id = excluded.snapshot_id,
                    execution_tier = excluded.execution_tier,
                    trust_score = excluded.trust_score,
                    override_rate = excluded.override_rate,
                    total_runs = excluded.total_runs,
                    created_at = excluded.created_at
                """,
                (
                    snapshot_id,
                    day_key,
                    module_id,
                    execution_tier,
                    trust_score,
                    override_rate,
                    total_runs,
                    created_at,
                ),
            )


def _agent_trust_delta_lookback(
    state_dir: Path,
    *,
    module_id: str,
    current_trust_score: float | None,
    lookback_days: int = AGENT_TRUST_DELTA_LOOKBACK_DAYS,
) -> float | None:
    if current_trust_score is None:
        return None
    normalized_module_id = str(module_id or "").strip()
    if not normalized_module_id:
        return None
    db_path = _agent_trust_snapshots_db_path(state_dir)
    if not db_path.exists():
        return None
    _init_agent_trust_snapshots_db(db_path)

    target_day = (datetime.now(timezone.utc) - timedelta(days=max(1, int(lookback_days)))).strftime("%Y-%m-%d")
    with sqlite3.connect(db_path) as conn:
        conn.row_factory = sqlite3.Row
        row = conn.execute(
            """
            SELECT trust_score
            FROM agent_trust_snapshots
            WHERE module_id = ?
              AND day_key <= ?
              AND trust_score IS NOT NULL
            ORDER BY day_key DESC
            LIMIT 1
            """,
            (normalized_module_id, target_day),
        ).fetchone()
    if row is None:
        return None
    prior_score = row["trust_score"]
    if not isinstance(prior_score, (int, float)):
        return None
    return round(float(current_trust_score) - float(prior_score), 4)


def get_or_create_session_token(session_key_path: Path | None = None) -> str:
    """Get existing session token or create a new one."""
    token_path = session_key_path or SESSION_KEY_PATH
    token_path.parent.mkdir(parents=True, exist_ok=True)

    if token_path.exists():
        return token_path.read_text().strip()

    # Generate new 256-bit token
    token = secrets.token_urlsafe(32)
    token_path.write_text(token)
    # Restrict permissions (owner only)
    token_path.chmod(0o600)
    logger.info(f"Created new session token at {token_path}")
    return token


def generate_pairing_code() -> str:
    """Generate human-readable pairing code, valid for 5 minutes."""
    chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"  # No ambiguous chars
    code = "-".join("".join(secrets.choice(chars) for _ in range(4)) for _ in range(3))
    return code  # e.g., "GREM-7X2K-9PLM"


def _api_key_file_path() -> Path:
    return gremlin_path("autopilot", "api_key")


def _resolve_api_key() -> str | None:
    """Read API key from env, then persisted file."""
    env_key = os.environ.get("G_GREMLIN_API_KEY", "").strip()
    if env_key:
        return env_key

    key_path = _api_key_file_path()
    if not key_path.exists():
        return None
    try:
        key = key_path.read_text(encoding="utf-8").strip()
    except Exception:
        return None
    return key or None


def _generate_and_persist_api_key() -> str:
    """Generate and persist a new API key with strict file permissions."""
    key = secrets.token_urlsafe(32)
    key_path = _api_key_file_path()
    key_path.parent.mkdir(parents=True, exist_ok=True)
    key_path.write_text(key, encoding="utf-8")
    try:
        key_path.chmod(0o600)
    except Exception:
        # Best effort for platforms without POSIX chmod semantics.
        pass
    logger.info("Generated API key: %s... (saved to %s)", key[:8], key_path)
    return key


def _origin_matches_request(
    origin: str,
    request: Request,
    allow_remote: bool,
    expected_port: int,
) -> bool:
    """Validate Origin/Referer against current request."""
    if not origin:
        return True

    parsed = urlparse(origin)
    if not parsed.scheme or not parsed.hostname:
        return False

    if allow_remote:
        if parsed.hostname != request.url.hostname:
            return False
    else:
        if parsed.hostname not in LOCALHOSTS:
            return False

    if parsed.port is None:
        return expected_port in (80, 443)

    return parsed.port == expected_port


def create_app(
    allow_remote: bool = False,
    port: int = 7777,
    session_key_path: Path | None = None,
    user_credentials: set[str] | None = None,
    api_key: str | None = None,
    *,
    policy_path: Path | None = None,
    state_dir: Path | None = None,
) -> FastAPI:
    """Create and configure the FastAPI application.

    Args:
        allow_remote: If True, bind to all interfaces and enable pairing.
                     Default is False (localhost only).
        port: Port to listen on (used for CORS origin checks).
        session_key_path: Optional path override for session token storage.
        user_credentials: Set of credential types the user has configured.
    """
    app = FastAPI(
        title="Gremlin Autopilot",
        description="Localhost web UI for RevOps workflows",
        version="0.1.0",
    )

    # Store configuration on app state
    app.state.allow_remote = allow_remote
    app.state.port = port
    app.state.session_token = get_or_create_session_token(session_key_path)
    app.state.session_key_path = session_key_path or SESSION_KEY_PATH
    app.state.authenticated_ips: set[str] = set()
    if api_key is not None:
        resolved_api_key = api_key.strip() or None
    elif allow_remote:
        resolved_api_key = _resolve_api_key()
    else:
        # Preserve legacy localhost behavior: do not auto-enable API-key mode
        # from env/file unless remote mode is explicitly enabled.
        resolved_api_key = None
    if allow_remote and api_key is None and not resolved_api_key:
        resolved_api_key = _generate_and_persist_api_key()
    app.state.api_key = resolved_api_key

    resolved_policy_path = _resolve_policy_path(policy_path)
    if resolved_policy_path and resolved_policy_path.exists():
        policy = load_policy(resolved_policy_path)
        app.state.policy_path = resolved_policy_path
    else:
        policy = generate_default_policy("local")
        app.state.policy_path = None
    app.state.policy = policy
    if state_dir is not None:
        app.state.state_dir = Path(state_dir).resolve()
    else:
        app.state.state_dir = resolve_state_dir(policy, create=True)

    # Initialize play loader
    from .plays import PlayLoader

    app.state.play_loader = PlayLoader(user_credentials=user_credentials)
    app.state.ui_dist_path = Path(__file__).parent / "ui" / "dist"

    # Initialize in-process caches (avoids module-level globals)
    app.state.credentials_cache = None  # (timestamp, data)
    app.state.recent_24h_cache = None   # (timestamp, data)
    app.state.integration_status_cache = None  # (timestamp, data)
    app.state.refresh_locks = {}
    app.state.oidc_jwks_cache = None
    app.state.audit_log = AuditLog(_platform_db_path(Path(app.state.state_dir)))
    app.state.control_plane_limits = ControlPlaneLimits.from_env()
    app.state.control_plane_job_queue = ControlPlaneJobQueue(
        control_plane_job_queue_db_path(Path(app.state.state_dir))
    )
    app.state.control_plane_job_worker_stop = asyncio.Event()
    app.state.control_plane_job_worker_task = None
    app.state.crm_event_queue = CrmEventQueue(event_queue_db_path(Path(app.state.state_dir)))
    app.state.crm_event_processor = CrmEventProcessor(
        state_dir=Path(app.state.state_dir),
        policy=policy,
        queue=app.state.crm_event_queue,
        job_queue=app.state.control_plane_job_queue,
    )

    if allow_remote and not app.state.api_key:
        app.state.pairing_code = generate_pairing_code()
        app.state.pairing_valid_until = datetime.now(timezone.utc) + timedelta(minutes=5)
    else:
        app.state.pairing_code = None
        app.state.pairing_valid_until = None

    # CORS middleware
    if allow_remote:
        allow_origins = ["*"]
        allow_credentials = False
    else:
        allow_origins = [
            f"http://localhost:{port}",
            f"http://127.0.0.1:{port}",
        ]
        allow_credentials = True

    app.add_middleware(
        CORSMiddleware,
        allow_origins=allow_origins,
        allow_credentials=allow_credentials,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    # Register auth middleware
    @app.middleware("http")
    async def auth_middleware(
        request: Request,
        call_next: Callable[[Request], Awaitable[JSONResponse]],
    ) -> JSONResponse:
        """API-key auth mode + legacy CSRF flow."""
        path = request.url.path
        method = request.method

        if method == "OPTIONS":
            return await call_next(request)

        # CRM webhooks are server-to-server and are authenticated at the route level.
        if path.startswith("/api/webhooks/"):
            return await call_next(request)
        # Slack interactions and slash commands are server-to-server and are authenticated at the route level.
        if path in {"/api/slack/interactions", "/api/slack/commands"}:
            return await call_next(request)

        # API-key mode: protect all /api/* routes except /api/health.
        if app.state.api_key:
            if path == "/api/health":
                return await call_next(request)

            # OAuth callbacks must remain publicly reachable from provider redirects.
            if method in ("GET", "HEAD") and (
                path.startswith("/api/admin/oauth/") or path.startswith("/api/admin/ai/oauth/")
            ):
                return await call_next(request)

            if method in ("GET", "HEAD") and path == "/status":
                return await call_next(request)

            if method in ("GET", "HEAD") and not path.startswith("/api/"):
                return await call_next(request)

            if path == "/api/pair":
                return JSONResponse(
                    {"error": "Pairing is disabled when API key authentication is enabled."},
                    status_code=403,
                )

            if path.startswith("/api/"):
                if _oidc_enabled():
                    proxy_token, _proxy_header = _extract_oidc_proxy_token(request)
                    if proxy_token:
                        try:
                            oidc_actor = _build_oidc_actor_context(app, proxy_token)
                        except ValueError as exc:
                            return JSONResponse({"error": f"Invalid OIDC token: {exc}"}, status_code=401)
                        setattr(request.state, "actor_context", oidc_actor)
                        return await call_next(request)

                auth_header = request.headers.get("authorization", "")
                if auth_header.startswith("Bearer "):
                    provided_key = auth_header[7:].strip()
                    if secrets.compare_digest(provided_key, app.state.api_key):
                        _bind_authenticated_principal(
                            request,
                            auth_source="api_key",
                            actor_id=_service_principal_actor_id("api_key", provided_key),
                            roles=[_default_rbac_role()],
                            principal_type="service",
                            verified_identity=False,
                        )
                        return await call_next(request)
                    if _oidc_enabled():
                        try:
                            oidc_actor = _build_oidc_actor_context(app, provided_key)
                        except ValueError as exc:
                            return JSONResponse({"error": f"Invalid bearer token: {exc}"}, status_code=401)
                        setattr(request.state, "actor_context", oidc_actor)
                        return await call_next(request)
                    return JSONResponse({"error": "Invalid API key"}, status_code=401)

                token = (
                    request.headers.get("x-autopilot-token", "").strip()
                    or request.query_params.get("token", "").strip()
                )
                if token and secrets.compare_digest(token, app.state.session_token):
                    _bind_authenticated_principal(
                        request,
                        auth_source="session_token",
                        actor_id=_service_principal_actor_id("session_token", token),
                        roles=[_default_rbac_role()],
                        principal_type="service",
                        verified_identity=False,
                    )
                    return await call_next(request)

                return JSONResponse(
                    {"error": "Authentication required. Use API key, trusted OIDC token, or session token."},
                    status_code=401,
                )

            return await call_next(request)

        # Legacy localhost CSRF flow when no API key is configured.
        if method in ("GET", "HEAD"):
            return await call_next(request)

        # Get client IP
        client_ip = request.client.host if request.client else "unknown"

        # Check remote access authentication
        if app.state.allow_remote:
            # Localhost always allowed
            if client_ip not in ("127.0.0.1", "::1", "localhost"):
                # Check if this IP is authenticated
                if client_ip not in app.state.authenticated_ips:
                    # Allow pairing endpoint without authentication
                    if path == "/api/pair":
                        return await call_next(request)
                    return JSONResponse(
                        {"error": "Pairing required. Enter pairing code to authenticate."},
                        status_code=403,
                    )

        # Validate headers
        origin = request.headers.get("origin", "")
        referer = request.headers.get("referer", "")
        token = request.headers.get("x-autopilot-token", "")

        # Validate Origin if present
        if origin and not _origin_matches_request(origin, request, allow_remote, app.state.port):
            return JSONResponse({"error": "Invalid origin"}, status_code=403)

        # Validate Referer if present
        if referer and not _origin_matches_request(referer, request, allow_remote, app.state.port):
            return JSONResponse({"error": "Invalid referer"}, status_code=403)

        # Validate session token (always required except for pairing)
        if path != "/api/pair" and token != app.state.session_token:
            return JSONResponse({"error": "Invalid session token"}, status_code=403)

        _bind_authenticated_principal(
            request,
            auth_source="session_token",
            actor_id=_service_principal_actor_id("session_token", token),
            roles=[_default_rbac_role()],
            principal_type="service",
            verified_identity=False,
        )
        return await call_next(request)

    @app.middleware("http")
    async def spa_fallback_middleware(
        request: Request,
        call_next: Callable[[Request], Awaitable[JSONResponse]],
    ) -> JSONResponse:
        """Serve React index.html for unknown client-side routes."""
        response = await call_next(request)
        if request.method not in ("GET", "HEAD"):
            return response
        if response.status_code != 404:
            return response

        path = request.url.path
        if path.startswith("/api/"):
            return response
        if path in ("/status", "/docs", "/redoc", "/openapi.json"):
            return response
        # Avoid rewriting missing static assets to index.
        if Path(path).suffix:
            return response

        ui_dist_path = Path(getattr(app.state, "ui_dist_path", ""))
        index_path = ui_dist_path / "index.html"
        if index_path.exists():
            return HTMLResponse(index_path.read_text(encoding="utf-8"))
        return response

    # --- Health Check ---

    @app.get("/api/health", response_model=HealthResponse)
    async def health_check() -> HealthResponse:
        """Health check endpoint with daemon heartbeat, pending count, and recent activity."""
        sd = Path(app.state.state_dir)
        current_policy = apply_policy_overlays(app.state.policy, sd)

        # Read daemon heartbeat (written atomically by the daemon process)
        heartbeat = _read_daemon_heartbeat(sd)
        daemon_running = _daemon_running(sd)
        next_scheduled_run: str | None = None
        schedule_profile: str | None = None
        last_scan_at = _latest_scan_timestamp(sd)

        if heartbeat:
            schedule_profile = heartbeat.get("profile")
            # Only trust heartbeat if updated_at is recent (< 2x expected interval or 4h fallback)
            updated_at = _parse_iso(heartbeat.get("updated_at"))
            stale_threshold = _heartbeat_stale_threshold(heartbeat)
            if updated_at and (datetime.now(timezone.utc) - updated_at) < stale_threshold:
                next_scheduled_run = heartbeat.get("next_run_at")
                if heartbeat.get("last_scan_at"):
                    last_scan_at = heartbeat["last_scan_at"]
            else:
                # Stale heartbeat — daemon likely crashed
                daemon_running = False

        # Count pending approvals
        pending_count = _count_pending(sd)

        # Tally recent 24h outcomes (cached)
        recent_24h = _get_recent_24h_cached(app, sd)

        return HealthResponse(
            daemon_running=daemon_running,
            last_scan_at=last_scan_at,
            policy_hash=compute_policy_hash(current_policy),
            next_scheduled_run=next_scheduled_run,
            schedule_profile=schedule_profile,
            pending_count=pending_count,
            recent_24h=recent_24h,
        )

    # --- Pairing ---

    @app.post("/api/pair", response_model=PairResponse)
    async def pair_client(request: Request, body: PairRequest) -> PairResponse:
        """Pair a remote client using the pairing code."""
        if app.state.api_key:
            raise HTTPException(
                status_code=403,
                detail="Pairing is disabled when API key authentication is enabled.",
            )

        if not app.state.allow_remote:
            return PairResponse(success=False, message="Remote access is not enabled")

        # Check if pairing code has expired
        if datetime.now(timezone.utc) > app.state.pairing_valid_until:
            return PairResponse(success=False, message="Pairing code has expired")

        # Validate pairing code (normalize both sides: uppercase, remove spaces and dashes)
        user_code = body.code.upper().replace(" ", "").replace("-", "")
        expected_code = app.state.pairing_code.replace("-", "")
        if user_code != expected_code:
            return PairResponse(success=False, message="Invalid pairing code")

        # Add client IP to authenticated set
        client_ip = request.client.host if request.client else None
        if client_ip:
            app.state.authenticated_ips.add(client_ip)
            logger.info(f"Authenticated remote client: {client_ip}")
            return PairResponse(success=True, message=f"Client {client_ip} authenticated")

        return PairResponse(success=False, message="Could not determine client IP")

    # --- Plays ---

    @app.get("/api/plays", response_model=PlayListResponse)
    async def list_plays() -> PlayListResponse:
        """List available plays."""
        play_loader = app.state.play_loader
        return PlayListResponse(plays=play_loader.list_plays())

    @app.get("/api/plays/{play_name}")
    async def get_play(play_name: str) -> dict:
        """Get details of a specific play."""
        play_loader = app.state.play_loader
        play = play_loader.get_play(play_name)
        if not play:
            return {"error": "Play not found"}
        return play.model_dump()

    @app.post("/api/plays/run")
    async def run_play(body: RunPlayRequest) -> ChatResponse:
        """Run a specific play with parameters."""
        from .plays import PlayExecutor

        play_loader = app.state.play_loader
        play = play_loader.get_play(body.play_name)

        if not play:
            return ChatResponse(
                status=JobStatus.FAILED,
                message=f"Play '{body.play_name}' not found",
                job_id=None,
            )

        executor = PlayExecutor(play_loader)

        # Validate parameters
        errors = executor.validate_parameters(play, body.parameters)
        if errors:
            return ChatResponse(
                status=JobStatus.FAILED,
                message=f"Parameter validation failed: {', '.join(errors)}",
                job_id=None,
            )

        # Prepare steps
        steps = executor.prepare_steps(play, body.parameters)

        # Convert to PlanActions and create a plan
        from .models import PlanAction
        from uuid import uuid4

        actions = [
            PlanAction(
                tool=step["tool"],
                args=step["args"],
                label=step["label"],
                destructive=step.get("destructive", False),
            )
            for step in steps
        ]

        job_id = f"play_{uuid4().hex[:8]}"

        return ChatResponse(
            status=JobStatus.PLAN_READY,
            message=f"Play '{body.play_name}' ready for execution ({len(actions)} steps)",
            job_id=job_id,
            actions=actions,
        )

    # --- Webhooks ---

    @app.post("/api/webhooks/salesforce/platform-events")
    async def receive_salesforce_platform_events(
        request: Request,
        process_now: bool = Query(default=False),
        process_limit: int = Query(default=25, ge=1, le=200),
    ) -> dict[str, Any]:
        """Receive Salesforce Platform Events, enqueue, and process in near real-time."""
        required_secret = str(os.getenv("G_GREMLIN_SF_WEBHOOK_SECRET", "")).strip()
        if not required_secret:
            raise HTTPException(
                status_code=503,
                detail="Salesforce webhook receiver is not configured (missing G_GREMLIN_SF_WEBHOOK_SECRET).",
            )

        provided_secret = str(request.headers.get("x-gremlin-webhook-secret", "")).strip()
        if not provided_secret or not secrets.compare_digest(provided_secret, required_secret):
            raise HTTPException(status_code=401, detail="Invalid webhook secret")

        try:
            payload = await request.json()
        except Exception as exc:
            raise HTTPException(status_code=400, detail="Invalid JSON payload") from exc
        if not isinstance(payload, (dict, list)):
            raise HTTPException(status_code=400, detail="Payload must be a JSON object or list")

        processor: CrmEventProcessor = app.state.crm_event_processor
        queued_events = processor.enqueue_salesforce_platform_events(payload)
        deduped_count = sum(1 for row in queued_events if bool(row.get("deduped")))
        new_event_ids = [str(row.get("event_id") or "") for row in queued_events if not bool(row.get("deduped"))]

        processed_results: list[dict[str, Any]] = []
        if process_now:
            for event_id in new_event_ids[:process_limit]:
                result = processor.process_event(event_id)
                if isinstance(result, dict):
                    processed_results.append(result)

        queue_counts = app.state.crm_event_queue.queue_counts()
        return {
            "status": "accepted",
            "received": len(queued_events),
            "enqueued": len(new_event_ids),
            "deduped": deduped_count,
            "processed_now": len(processed_results),
            "process_now": bool(process_now),
            "processed": processed_results,
            "queue": queue_counts,
        }

    @app.post("/api/webhooks/hubspot/events")
    async def receive_hubspot_webhook_events(
        request: Request,
        process_now: bool = Query(default=False),
        process_limit: int = Query(default=25, ge=1, le=200),
    ) -> dict[str, Any]:
        """Receive HubSpot webhook notifications, enqueue, and optionally process them immediately."""
        required_secret = _resolve_hubspot_webhook_secret()
        if not required_secret:
            raise HTTPException(
                status_code=503,
                detail=(
                    "HubSpot webhook receiver is not configured "
                    "(missing G_GREMLIN_HUBSPOT_WEBHOOK_SECRET or G_GREMLIN_HUBSPOT_CLIENT_SECRET)."
                ),
            )

        raw_body = await request.body()
        signature_header = (
            str(request.headers.get("x-hubspot-signature-v3", "")).strip()
            or str(request.headers.get("x-hubspot-signature", "")).strip()
        )
        signature_version = str(request.headers.get("x-hubspot-signature-version", "")).strip().lower()
        if not signature_version:
            signature_version = "v3" if request.headers.get("x-hubspot-signature-v3") else "v1"
        timestamp = str(request.headers.get("x-hubspot-request-timestamp", "")).strip() or None
        if not verify_hubspot_signature(
            required_secret,
            signature_version=signature_version,
            signature=signature_header,
            method=request.method,
            request_uri=str(request.url),
            body=raw_body,
            timestamp=timestamp,
        ):
            raise HTTPException(status_code=401, detail="Invalid HubSpot webhook signature")

        try:
            payload = json.loads(raw_body.decode("utf-8")) if raw_body else []
        except Exception as exc:
            raise HTTPException(status_code=400, detail="Invalid JSON payload") from exc
        if not isinstance(payload, (dict, list)):
            raise HTTPException(status_code=400, detail="Payload must be a JSON object or list")

        processor: CrmEventProcessor = app.state.crm_event_processor
        queued_events = processor.enqueue_hubspot_webhook_events(payload)
        deduped_count = sum(1 for row in queued_events if bool(row.get("deduped")))
        new_event_ids = [str(row.get("event_id") or "") for row in queued_events if not bool(row.get("deduped"))]

        processed_results: list[dict[str, Any]] = []
        if process_now:
            for event_id in new_event_ids[:process_limit]:
                result = processor.process_event(event_id)
                if isinstance(result, dict):
                    processed_results.append(result)

        queue_counts = app.state.crm_event_queue.queue_counts()
        return {
            "status": "accepted",
            "received": len(queued_events),
            "enqueued": len(new_event_ids),
            "deduped": deduped_count,
            "processed_now": len(processed_results),
            "process_now": bool(process_now),
            "processed": processed_results,
            "queue": queue_counts,
        }

    @app.post("/api/admin/webhooks/events/process")
    async def process_webhook_events(request: Request, limit: int = Query(default=25, ge=1, le=200)) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="webhook_events_process",
        )
        processor: CrmEventProcessor = app.state.crm_event_processor
        results = processor.process_pending(limit=limit)
        return {
            "processed_count": len(results),
            "processed": results,
            "queue": app.state.crm_event_queue.queue_counts(),
        }

    @app.get("/api/admin/webhooks/events/queue")
    async def webhook_event_queue_status(request: Request) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="webhook_events_queue",
        )
        health = app.state.crm_event_queue.queue_health()
        return {
            "queue": health.get("counts", {}),
            "health": health,
            "updated_at": health.get("updated_at") or datetime.now(timezone.utc).isoformat(),
        }

    @app.get("/api/admin/webhooks/events")
    async def list_webhook_events(
        request: Request,
        status: str | None = Query(None),
        provider: str | None = Query(None),
        source_kind: str | None = Query(None),
        limit: int = Query(100, ge=1, le=500),
    ) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="webhook_events_list",
        )
        normalized_status = str(status or "").strip().lower() or None
        if normalized_status and normalized_status not in {"pending", "processing", "processed", "dead_letter"}:
            raise HTTPException(status_code=400, detail="Invalid status filter")
        queue = app.state.crm_event_queue
        return {
            "items": queue.list_events(
                status=normalized_status,
                provider=provider,
                source_kind=source_kind,
                limit=limit,
            ),
            "counts": queue.queue_counts(),
            "health": queue.queue_health(),
            "updated_at": datetime.now(timezone.utc).isoformat(),
        }

    @app.get("/api/admin/webhooks/events/stats")
    async def webhook_event_stats(
        request: Request,
        window_hours: int = Query(24, ge=1, le=168),
    ) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="webhook_events_stats",
        )
        queue = app.state.crm_event_queue
        throughput = queue.operational_metrics(window_hours=window_hours)
        dead_letter_items = queue.list_events(status="dead_letter", limit=10)
        recent_failures = [
            {
                "event_id": str(item.get("event_id") or ""),
                "provider": str(item.get("provider") or ""),
                "event_type": str(item.get("event_type") or ""),
                "source_event_id": str(item.get("source_event_id") or ""),
                "last_error": str(item.get("last_error") or ""),
                "updated_at": str(item.get("updated_at") or ""),
            }
            for item in dead_letter_items
        ]
        return {
            "queue_counts": queue.queue_counts(),
            "health": queue.queue_health(),
            "throughput": throughput,
            "recent_failures": recent_failures,
        }

    @app.post("/api/admin/webhooks/events/replay")
    async def replay_webhook_event(request: Request, process_now: bool = Query(default=False)) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="webhook_events_replay",
        )
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        event_id = str(payload.get("event_id") or "").strip()
        if not event_id:
            raise HTTPException(status_code=400, detail="event_id is required")

        queue = app.state.crm_event_queue
        existing = queue.get_event(event_id)
        if existing is None:
            raise HTTPException(status_code=404, detail="Webhook event not found")
        if str(existing.get("status") or "") != "dead_letter":
            raise HTTPException(status_code=409, detail="Only dead-letter events can be replayed")

        replayed = queue.replay_event(event_id=event_id)
        if replayed is None:
            raise HTTPException(status_code=409, detail="Webhook event could not be replayed")

        processed: dict[str, Any] | None = None
        if process_now:
            result = app.state.crm_event_processor.process_event(event_id)
            processed = result if isinstance(result, dict) else None

        return {
            "status": "replayed",
            "event": replayed,
            "processed_now": bool(process_now and processed is not None),
            "processed": processed,
            "counts": queue.queue_counts(),
            "health": queue.queue_health(),
        }

    @app.get("/api/admin/webhooks/events/{event_id}")
    async def webhook_event_detail(request: Request, event_id: str) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="webhook_events_detail",
        )
        event = app.state.crm_event_queue.get_event(event_id)
        if event is None:
            raise HTTPException(status_code=404, detail="Webhook event not found")
        return event

    # --- Scouts ---

    @app.get("/api/scouts")
    @app.get("/api/admin/scouts")
    async def list_scouts() -> dict[str, Any]:
        state_dir = Path(app.state.state_dir)
        configured_integrations = _configured_integrations()
        scan_cfg = load_scan_config()
        loader = ScoutLoader()
        definitions = loader.list_scouts()
        scheduled_names = set(scan_cfg.scouts_enabled) if scan_cfg.scouts_enabled else {d.name for d in definitions}
        latest_by_name = _latest_scan_finding_by_scout(state_dir)
        latest_scan = _latest_scan_result(state_dir)

        scouts: list[dict[str, Any]] = []
        for definition in sorted(definitions, key=lambda item: item.display_name.lower()):
            required_creds = list(definition.requires_credentials or [])
            # OR semantics: scout is available if ANY supported CRM is connected
            credentials_configured = (
                any(cred in configured_integrations for cred in required_creds)
                if required_creds
                else True
            )
            matched_creds = [c for c in required_creds if c in configured_integrations]
            missing_creds = [c for c in required_creds if c not in configured_integrations]
            latest = latest_by_name.get(definition.name)
            latest_finding = latest.get("finding", {}) if latest else {}
            scouts.append(
                {
                    "name": definition.name,
                    "display_name": definition.display_name,
                    "description": definition.description,
                    "category": definition.category,
                    "severity_default": definition.severity_default,
                    "enabled": bool(definition.enabled),
                    "scheduled": definition.name in scheduled_names,
                    "requires_credentials": required_creds,
                    "credentials_configured": credentials_configured,
                    "matched_credentials": matched_creds,
                    "missing_credentials": missing_creds,
                    "latest_status": str(latest_finding.get("status") or "never"),
                    "latest_findings_count": int(latest_finding.get("findings_count") or 0),
                    "latest_total_records_scanned": int(latest_finding.get("total_records_scanned") or 0),
                    "latest_run_id": latest.get("run_id") if latest else None,
                    "latest_started_at": latest.get("started_at") if latest else None,
                    "latest_ended_at": latest.get("ended_at") if latest else None,
                }
            )

        latest_scan_summary = None
        if latest_scan:
            latest_scan_summary = {
                "run_id": latest_scan.get("run_id"),
                "started_at": latest_scan.get("started_at"),
                "ended_at": latest_scan.get("ended_at"),
                "org_alias": latest_scan.get("org_alias"),
                "total_duration_ms": int(latest_scan.get("total_duration_ms") or 0),
                "errors_count": len(latest_scan.get("errors") or []),
                "findings_total": sum(
                    int(finding.get("findings_count") or 0)
                    for finding in latest_scan.get("findings") or []
                    if isinstance(finding, dict)
                ),
            }

        return {
            "scouts": scouts,
            "scan_defaults": scan_cfg.to_dict(),
            "configured_integrations": sorted(configured_integrations),
            "latest_scan": latest_scan_summary,
        }

    @app.get("/api/scouts/schedule")
    @app.get("/api/admin/scouts/schedule")
    async def get_scout_schedule() -> dict[str, Any]:
        state_dir = Path(app.state.state_dir)
        scan_cfg = load_scan_config()
        definitions = ScoutLoader().list_scouts()
        available_names = {definition.name for definition in definitions}
        scheduled = [name for name in scan_cfg.scouts_enabled if name in available_names]
        if not scheduled:
            scheduled = sorted(available_names)
        heartbeat = _read_daemon_heartbeat(state_dir) or {}
        try:
            schedule_interval_hours = _coerce_schedule_interval_hours(scan_cfg.schedule_interval_hours)
        except HTTPException:
            schedule_interval_hours = None
        return {
            "scouts_enabled": scheduled,
            "schedule_interval_hours": schedule_interval_hours,
            "schedule_enabled": schedule_interval_hours is not None,
            "available_intervals_hours": [4, 8, 12, 24],
            "daemon_running": _daemon_running(state_dir),
            "active_profile": heartbeat.get("profile"),
            "next_run_at": heartbeat.get("next_run_at"),
            "updated_at": datetime.now(timezone.utc).isoformat(),
        }

    @app.put("/api/scouts/schedule")
    @app.put("/api/admin/scouts/schedule")
    async def put_scout_schedule(request: Request) -> dict[str, Any]:
        try:
            parsed_payload = await request.json()
        except Exception:
            parsed_payload = {}
        if parsed_payload is None:
            parsed_payload = {}
        if not isinstance(parsed_payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed_payload

        scan_cfg = load_scan_config()
        updated = ScanConfig.from_dict(scan_cfg.to_dict())
        definitions = ScoutLoader().list_scouts()
        definitions_by_name = {definition.name: definition for definition in definitions}

        if "scouts_enabled" in payload:
            raw_names = payload.get("scouts_enabled")
            if raw_names is None:
                normalized_names: list[str] = []
            elif isinstance(raw_names, list):
                normalized_names = []
                seen_names: set[str] = set()
                for value in raw_names:
                    if value is None:
                        continue
                    name = str(value).strip()
                    if not name:
                        continue
                    if name in seen_names:
                        continue
                    if name not in definitions_by_name:
                        raise HTTPException(status_code=404, detail=f"Unknown scout: {name}")
                    seen_names.add(name)
                    normalized_names.append(name)
            else:
                raise HTTPException(status_code=400, detail="scouts_enabled must be a list")
            updated.scouts_enabled = normalized_names

        if "schedule_interval_hours" in payload:
            updated.schedule_interval_hours = _coerce_schedule_interval_hours(
                payload.get("schedule_interval_hours")
            )

        path = _save_scan_config_override(updated)
        heartbeat = _read_daemon_heartbeat(Path(app.state.state_dir)) or {}
        return {
            "status": "saved",
            "path": str(path),
            "scouts_enabled": list(updated.scouts_enabled),
            "schedule_interval_hours": updated.schedule_interval_hours,
            "schedule_enabled": updated.schedule_interval_hours is not None,
            "daemon_running": _daemon_running(Path(app.state.state_dir)),
            "active_profile": heartbeat.get("profile"),
            "next_run_at": heartbeat.get("next_run_at"),
            "updated_at": datetime.now(timezone.utc).isoformat(),
        }

    @app.post("/api/scouts/run")
    @app.post("/api/admin/scouts/run")
    async def run_scouts(request: Request) -> dict[str, Any]:
        try:
            parsed_payload = await request.json()
        except Exception:
            parsed_payload = {}
        if parsed_payload is None:
            parsed_payload = {}
        if not isinstance(parsed_payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed_payload

        state_dir = Path(app.state.state_dir)
        scan_cfg = load_scan_config()
        definitions = ScoutLoader().list_scouts()
        definitions_by_name = {definition.name: definition for definition in definitions}

        requested_names: list[str] = []
        raw_scout_name = payload.get("scout_name")
        if raw_scout_name is not None:
            if not isinstance(raw_scout_name, str) or not raw_scout_name.strip():
                raise HTTPException(status_code=400, detail="scout_name must be a non-empty string")
            requested_names.append(raw_scout_name.strip())

        raw_scout_names = payload.get("scout_names")
        if raw_scout_names is not None:
            if not isinstance(raw_scout_names, list):
                raise HTTPException(status_code=400, detail="scout_names must be a list")
            for item in raw_scout_names:
                if not isinstance(item, str) or not item.strip():
                    raise HTTPException(status_code=400, detail="scout_names entries must be non-empty strings")
                requested_names.append(item.strip())

        if not requested_names:
            requested_names = (
                list(scan_cfg.scouts_enabled)
                if scan_cfg.scouts_enabled
                else [definition.name for definition in definitions]
            )

        scout_names: list[str] = []
        seen_names: set[str] = set()
        for name in requested_names:
            if name in seen_names:
                continue
            seen_names.add(name)
            scout_names.append(name)

        if not scout_names:
            raise HTTPException(status_code=400, detail="No scout names were provided")

        unknown = [name for name in scout_names if name not in definitions_by_name]
        if unknown:
            raise HTTPException(
                status_code=404,
                detail=f"Unknown scout(s): {', '.join(sorted(unknown))}",
            )

        top_n_raw = payload.get("top_n", scan_cfg.top_n)
        try:
            top_n = int(top_n_raw)
        except Exception as exc:
            raise HTTPException(status_code=400, detail="top_n must be an integer") from exc
        if top_n <= 0:
            raise HTTPException(status_code=400, detail="top_n must be greater than 0")

        raw_scan_cfg_params = scan_cfg.per_scout_params or {}
        if not isinstance(raw_scan_cfg_params, dict):
            raise HTTPException(status_code=500, detail="Invalid scan config: per_scout_params must be an object")
        scan_cfg_per_scout: dict[str, dict[str, Any]] = {}
        for scout_name, scout_params in raw_scan_cfg_params.items():
            if not isinstance(scout_name, str):
                continue
            if scout_params is None:
                scan_cfg_per_scout[scout_name] = {}
                continue
            if not isinstance(scout_params, dict):
                continue
            scan_cfg_per_scout[scout_name] = scout_params

        raw_request_params = payload.get("per_scout_params", {})
        if raw_request_params is None:
            raw_request_params = {}
        if not isinstance(raw_request_params, dict):
            raise HTTPException(status_code=400, detail="per_scout_params must be an object")
        per_scout_params: dict[str, dict[str, Any]] = {}
        for scout_name, scout_params in raw_request_params.items():
            if not isinstance(scout_name, str):
                raise HTTPException(
                    status_code=400,
                    detail="per_scout_params keys must be scout names",
                )
            if scout_params is None:
                per_scout_params[scout_name] = {}
                continue
            if not isinstance(scout_params, dict):
                raise HTTPException(
                    status_code=400,
                    detail=f"per_scout_params.{scout_name} must be an object",
                )
            per_scout_params[scout_name] = scout_params

        stored_overrides = ScoutConfigStore(state_dir).load_all()
        merged_per_scout_params: dict[str, dict[str, Any]] = {}
        for scout_name in scout_names:
            merged = dict(stored_overrides.get(scout_name, {}))
            merged.update(scan_cfg_per_scout.get(scout_name, {}))
            merged.update(per_scout_params.get(scout_name, {}))
            if merged:
                merged_per_scout_params[scout_name] = merged

        crm = str(payload.get("crm") or os.getenv("G_GREMLIN_CRM") or scan_cfg.crm or "salesforce").strip().lower()
        if not crm:
            crm = "salesforce"

        raw_org_alias = payload.get("org_alias")
        if raw_org_alias is not None and not isinstance(raw_org_alias, str):
            raise HTTPException(status_code=400, detail="org_alias must be a string")
        runtime_policy = apply_policy_overlays(app.state.policy, state_dir)
        org_alias = (
            raw_org_alias.strip()
            if isinstance(raw_org_alias, str) and raw_org_alias.strip()
            else runtime_policy.org_alias or scan_cfg.org_alias
        )

        raw_hubspot_profile = payload.get("hubspot_profile")
        if raw_hubspot_profile is not None and not isinstance(raw_hubspot_profile, str):
            raise HTTPException(status_code=400, detail="hubspot_profile must be a string")
        hubspot_profile = (
            raw_hubspot_profile.strip()
            if isinstance(raw_hubspot_profile, str) and raw_hubspot_profile.strip()
            else scan_cfg.hubspot_profile
        )

        raw_dynamics_profile = payload.get("dynamics_profile")
        if raw_dynamics_profile is not None and not isinstance(raw_dynamics_profile, str):
            raise HTTPException(status_code=400, detail="dynamics_profile must be a string")
        dynamics_profile = (
            raw_dynamics_profile.strip()
            if isinstance(raw_dynamics_profile, str) and raw_dynamics_profile.strip()
            else scan_cfg.dynamics_profile
        )

        engine = ScoutEngine(
            user_credentials=_configured_integrations(),
            state_dir=state_dir,
        )
        scan_result = engine.run_scan(
            scout_names,
            top_n=top_n,
            per_scout_params=merged_per_scout_params,
            org_alias=org_alias,
            crm=crm,
            hubspot_profile=hubspot_profile,
            dynamics_profile=dynamics_profile,
        )
        if not scan_result.ended_at:
            scan_result.ended_at = datetime.now(timezone.utc).isoformat()

        scan_payload = scan_result.to_dict(include_fields=True, include_reason=True)
        _write_scan_result_payload(state_dir, run_id=scan_result.run_id, payload=scan_payload)

        findings_total = sum(int(finding.findings_count) for finding in scan_result.findings)
        finding_summaries = [
            {
                "scout_name": finding.scout_name,
                "status": finding.status,
                "status_detail": finding.status_detail,
                "findings_count": int(finding.findings_count),
                "total_records_scanned": int(finding.total_records_scanned),
                "warnings": list(finding.warnings),
            }
            for finding in scan_result.findings
        ]

        return {
            "run_id": scan_result.run_id,
            "started_at": scan_result.started_at,
            "ended_at": scan_result.ended_at,
            "org_alias": scan_result.org_alias,
            "crm": crm,
            "scout_names": scout_names,
            "findings_total": findings_total,
            "errors": list(scan_result.errors),
            "total_duration_ms": int(scan_result.total_duration_ms or 0),
            "findings": finding_summaries,
        }

    @app.get("/api/scouts/{scout_name}/latest")
    async def latest_scout_finding(scout_name: str) -> dict[str, Any]:
        latest = _latest_scan_finding_by_scout(Path(app.state.state_dir)).get(scout_name)
        if not latest:
            return {"scout_name": scout_name, "latest": None}

        finding = dict(latest.get("finding", {}))
        evidence = finding.get("evidence")
        if isinstance(evidence, list):
            finding["evidence"] = evidence[:100]
        actions = finding.get("recommended_actions")
        if isinstance(actions, list):
            finding["recommended_actions"] = actions[:20]

        return {
            "scout_name": scout_name,
            "latest": {
                "run_id": latest.get("run_id"),
                "started_at": latest.get("started_at"),
                "ended_at": latest.get("ended_at"),
                "finding": finding,
            },
        }

    # --- Scout Config ---

    @app.get("/api/admin/scouts/config")
    async def get_all_scout_config() -> dict[str, Any]:
        state_dir = Path(app.state.state_dir)
        store = ScoutConfigStore(state_dir)
        overrides = store.load_all()
        definitions = ScoutLoader().list_scouts()

        return {
            "overrides": overrides,
            "definitions": [
                {
                    "name": definition.name,
                    "display_name": definition.display_name,
                    "parameters": [parameter.to_dict() for parameter in definition.parameters],
                }
                for definition in sorted(definitions, key=lambda item: item.display_name.lower())
            ],
        }

    @app.get("/api/admin/scouts/{scout_name}/config")
    async def get_scout_config(scout_name: str) -> dict[str, Any]:
        state_dir = Path(app.state.state_dir)
        loader = ScoutLoader()
        definition = loader.get_scout(scout_name)
        if definition is None:
            raise HTTPException(status_code=404, detail="Scout not found")

        store = ScoutConfigStore(state_dir)
        overrides = store.load_scout(scout_name)
        engine = ScoutEngine(user_credentials=_configured_integrations())
        effective = engine.resolve_params(definition, overrides)
        return {
            "scout_name": scout_name,
            "overrides": overrides,
            "parameters": [parameter.to_dict() for parameter in definition.parameters],
            "effective": effective,
        }

    @app.put("/api/admin/scouts/{scout_name}/config")
    async def put_scout_config(scout_name: str, request: Request) -> dict[str, Any]:
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        raw_params = payload.get("params", {})
        if raw_params is None:
            raw_params = {}
        if not isinstance(raw_params, dict):
            raise HTTPException(status_code=400, detail="params must be an object")

        loader = ScoutLoader()
        definition = loader.get_scout(scout_name)
        if definition is None:
            raise HTTPException(status_code=404, detail="Scout not found")

        store = ScoutConfigStore(Path(app.state.state_dir))
        warnings = store.validate_params(scout_name, raw_params, loader.list_scouts())
        store.save_scout(scout_name, raw_params, actor="api")

        overrides = store.load_scout(scout_name)
        engine = ScoutEngine(user_credentials=_configured_integrations())
        effective = engine.resolve_params(definition, overrides)
        return {
            "scout_name": scout_name,
            "overrides": overrides,
            "effective": effective,
            "warnings": warnings,
        }

    @app.delete("/api/admin/scouts/{scout_name}/config")
    async def delete_scout_config(scout_name: str) -> dict[str, Any]:
        store = ScoutConfigStore(Path(app.state.state_dir))
        deleted = store.delete_scout(scout_name, actor="api")
        return {"scout_name": scout_name, "deleted": deleted}

    # --- AI Provider ---

    @app.get("/api/admin/ai/status")
    async def ai_provider_status(request: Request) -> dict[str, Any]:
        state_dir = Path(app.state.state_dir)
        status = AIProviderStore(state_dir).load_status()
        payload = status.model_dump(exclude_none=False)
        payload["oauth"] = {
            "google": {
                "enabled": bool(_ai_oauth_client_id("google")),
                "callback_url": _ai_oauth_callback_url(request, "google"),
            }
        }
        return payload

    @app.put("/api/admin/ai/config")
    async def put_ai_provider_config(request: Request) -> dict[str, Any]:
        try:
            parsed_payload = await request.json()
        except Exception:
            parsed_payload = {}
        if not isinstance(parsed_payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed_payload

        provider = str(payload.get("provider") or "").strip().lower()
        if provider not in {"anthropic", "openai", "google"}:
            raise HTTPException(status_code=400, detail="provider must be one of: anthropic, openai, google")
        api_key = str(payload.get("api_key") or "").strip()
        model = str(payload.get("model") or "").strip()
        if not model:
            raise HTTPException(status_code=400, detail="model is required")
        enabled_raw = payload.get("enabled", True)
        if not isinstance(enabled_raw, bool):
            raise HTTPException(status_code=400, detail="enabled must be a boolean")

        state_dir = Path(app.state.state_dir)
        store = AIProviderStore(state_dir)
        if api_key:
            config = AIProviderConfig(
                provider=provider,  # type: ignore[arg-type]
                api_key=api_key,
                model=model,
                enabled=enabled_raw,
                auth_mode="api_key",
            )
            store.save_config(config)
        else:
            if provider != "google":
                raise HTTPException(
                    status_code=400,
                    detail="api_key is required for non-Google providers",
                )
            updated = store.update_oauth_settings(
                provider=provider,  # type: ignore[arg-type]
                model=model,
                enabled=enabled_raw,
            )
            if not updated:
                raise HTTPException(
                    status_code=400,
                    detail="api_key is required unless a Google OAuth connection is already configured",
                )

        actor = _module_actor(request)
        app.state.audit_log.log(
            "ai_config_change",
            actor,
            "AI provider config saved",
            detail={
                "provider": provider,
                "model": model,
                "enabled": enabled_raw,
                "auth_mode": "api_key" if api_key else "oauth",
            },
        )
        status_payload = store.load_status().model_dump(exclude_none=False)
        status_payload["oauth"] = {
            "google": {
                "enabled": bool(_ai_oauth_client_id("google")),
                "callback_url": _ai_oauth_callback_url(request, "google"),
            }
        }
        return status_payload

    @app.delete("/api/admin/ai/config")
    async def delete_ai_provider_config(request: Request) -> dict[str, Any]:
        state_dir = Path(app.state.state_dir)
        store = AIProviderStore(state_dir)
        deleted = store.delete_config()
        actor = _module_actor(request)
        app.state.audit_log.log(
            "ai_config_change",
            actor,
            "AI provider config deleted",
            detail={"deleted": deleted},
        )
        return {"deleted": deleted}

    @app.post("/api/admin/ai/oauth/{provider}/start")
    async def start_ai_provider_oauth(provider: str, request: Request) -> dict[str, Any]:
        payload: dict[str, Any]
        try:
            parsed = await request.json()
        except Exception:
            parsed = {}
        if parsed is None:
            parsed = {}
        if not isinstance(parsed, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed

        normalized_provider = str(provider or "").strip().lower()
        try:
            return _build_ai_oauth_start(
                state_dir=Path(app.state.state_dir),
                request=request,
                provider=normalized_provider,
                payload=payload,
            )
        except ValueError as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc

    @app.get("/api/admin/ai/oauth/{provider}/callback")
    async def ai_provider_oauth_callback(
        request: Request,
        provider: str,
        state: str | None = Query(None),
        code: str | None = Query(None),
        error: str | None = Query(None),
        error_description: str | None = Query(None),
    ) -> HTMLResponse:
        normalized_provider = str(provider or "").strip().lower()
        if normalized_provider != "google":
            return _oauth_callback_html(
                title="Connection failed",
                message=f"Unsupported AI OAuth provider: {normalized_provider or provider}",
                success=False,
            )

        if not state:
            return _oauth_callback_html(
                title="Connection failed",
                message="Missing OAuth state parameter.",
                success=False,
            )

        state_payload = _consume_oauth_state(
            Path(app.state.state_dir),
            state=state,
            expected_integration="ai_google",
        )
        if state_payload is None:
            return _oauth_callback_html(
                title="Connection failed",
                message="OAuth state is invalid or expired. Restart the connection flow from Config.",
                success=False,
            )

        if error:
            details = f"{error}: {error_description}" if error_description else error
            return _oauth_callback_html(
                title="Connection canceled",
                message=f"Provider returned an OAuth error ({details}).",
                success=False,
            )

        if not code:
            return _oauth_callback_html(
                title="Connection failed",
                message="Missing OAuth authorization code.",
                success=False,
            )

        try:
            token_payload = _exchange_ai_oauth_code(
                provider="google",
                request=request,
                code=code,
            )
            access_token = str(token_payload.get("access_token") or "").strip()
            refresh_token = str(token_payload.get("refresh_token") or "").strip()
            token_type = str(token_payload.get("token_type") or "").strip() or None
            expires_in = token_payload.get("expires_in")
            try:
                expires_seconds = int(expires_in) if expires_in is not None else 3600
            except Exception:
                expires_seconds = 3600
            expires_at = (datetime.now(timezone.utc) + timedelta(seconds=max(expires_seconds, 1))).isoformat()
            model = str(state_payload.get("model") or "").strip() or _default_ai_model("google")
            enabled = bool(state_payload.get("enabled", True))

            if not access_token:
                raise ValueError("Google OAuth token response did not include an access_token.")

            store = AIProviderStore(Path(app.state.state_dir))
            store.save_oauth_config(
                AIProviderConfig(
                    provider="google",
                    model=model,
                    enabled=enabled,
                    auth_mode="oauth",
                    access_token=access_token,
                    refresh_token=refresh_token or None,
                    token_type=token_type,
                    expires_at=expires_at,
                )
            )

            _audit_log(app).log(
                "ai_oauth_connect",
                _request_actor(request),
                "Google AI OAuth connected",
                detail={
                    "provider": "google",
                    "model": model,
                    "enabled": enabled,
                    "has_refresh_token": bool(refresh_token),
                },
            )
        except Exception as exc:
            return _oauth_callback_html(
                title="Connection failed",
                message=str(exc),
                success=False,
            )

        return _oauth_callback_html(
            title="Google AI connected",
            message="Google OAuth completed for AI provider. Return to Config and click Refresh.",
            success=True,
        )

    @app.post("/api/admin/ai/oauth/{provider}/disconnect")
    async def disconnect_ai_provider_oauth(provider: str, request: Request) -> dict[str, Any]:
        normalized_provider = str(provider or "").strip().lower()
        if normalized_provider != "google":
            raise HTTPException(status_code=404, detail=f"Unsupported AI OAuth provider: {normalized_provider}")

        state_dir = Path(app.state.state_dir)
        store = AIProviderStore(state_dir)
        status = store.load_status()
        if status.provider != "google" or status.auth_mode != "oauth":
            return {"deleted": False, "provider": normalized_provider}

        deleted = store.delete_config()
        _audit_log(app).log(
            "ai_oauth_disconnect",
            _request_actor(request),
            "Google AI OAuth disconnected",
            detail={"provider": "google", "deleted": deleted},
        )
        return {"deleted": deleted, "provider": normalized_provider}

    @app.post("/api/admin/ai/test")
    async def test_ai_provider_endpoint() -> dict[str, Any]:
        state_dir = Path(app.state.state_dir)
        store = AIProviderStore(state_dir)
        config = store.load_config()
        if config is None:
            return {"ok": False, "error": "No AI provider configured"}
        ok, error = test_ai_provider(config)
        store.save_test_result(ok, error)
        return {
            "ok": ok,
            "error": error,
            "provider": config.provider,
            "model": config.model,
        }

    # --- AI Control Plane ---

    def _control_store() -> Any:
        return control_plane_store(Path(app.state.state_dir))

    def _control_job_queue() -> ControlPlaneJobQueue:
        return app.state.control_plane_job_queue

    def _control_limits() -> ControlPlaneLimits:
        return ControlPlaneLimits.from_env()

    def _control_limit_check(payload: dict[str, Any]) -> ControlPlaneLimits:
        limits = _control_limits()
        if limits.kill_switch:
            raise HTTPException(status_code=503, detail="AI control-plane kill switch is enabled.")

        estimated_tokens = 0
        estimated_cost = 0.0
        if isinstance(payload, dict):
            try:
                estimated_tokens = int(payload.get("estimated_tokens") or 0)
            except Exception:
                estimated_tokens = 0
            try:
                estimated_cost = float(payload.get("estimated_cost_usd") or 0.0)
            except Exception:
                estimated_cost = 0.0

        if estimated_tokens > limits.max_tokens_per_run:
            raise HTTPException(
                status_code=400,
                detail=(
                    f"estimated_tokens exceeds per-run budget: "
                    f"{estimated_tokens} > {limits.max_tokens_per_run}"
                ),
            )
        if estimated_cost > limits.max_cost_usd_per_run:
            raise HTTPException(
                status_code=400,
                detail=(
                    f"estimated_cost_usd exceeds per-run budget: "
                    f"{estimated_cost} > {limits.max_cost_usd_per_run}"
                ),
            )
        return limits

    def _post_compile_pending_slack_notification(
        *,
        plan: PlanV1 | None,
        policy: Any,
        emit_pending: bool,
    ) -> None:
        if plan is None or not emit_pending:
            return

        bot_token = str(os.getenv("G_GREMLIN_SLACK_BOT_TOKEN") or "").strip()
        if not bot_token:
            return

        channel = resolve_slack_channel(getattr(policy, "defaults", None))
        channel = str(channel or "").strip()
        if not channel:
            return

        try:
            web_client = slack_create_client(bot_token)
        except Exception:
            logger.warning("Slack client initialization failed for control-plane approval request.", exc_info=True)
            return

        from .approvals import default_approval_ttl_seconds

        policy_hash = str(plan.policy_hash or "").strip() or compute_policy_hash(policy)
        token_store = ApprovalTokenStore(Path(app.state.state_dir) / "db" / "approval_tokens.sqlite")
        token = token_store.create_token(
            plan_hash=plan.plan_hash or "",
            policy_hash=policy_hash,
            run_id=plan.run_id,
            ttl_seconds=default_approval_ttl_seconds(),
        ).token
        try:
            post_approval_request(web_client, channel, plan, token)
        except Exception:
            logger.warning("Slack approval request post failed for control-plane compile.", exc_info=True)

    def _execute_control_plane_job(
        job_type: str,
        payload: dict[str, Any],
        *,
        actor: str,
    ) -> dict[str, Any]:
        state_dir = Path(app.state.state_dir)
        store = _control_store()
        limits = _control_limits()
        started = _time.time()
        if job_type == "generate":
            dedupe_window_seconds = payload.get("dedupe_window_seconds")
            if dedupe_window_seconds is None:
                dedupe_window_seconds = DEFAULT_DEDUPE_WINDOW_SECONDS
            result = generate_sequence_pause_proposals(
                store=store,
                state_dir=state_dir,
                dedupe_window_seconds=max(int(dedupe_window_seconds), 0),
                actor=actor,
            )
        elif job_type == "compile":
            proposal_id = str(payload.get("proposal_id") or "").strip()
            if not proposal_id:
                raise RuntimeError("proposal_id is required for compile jobs")
            emit_pending = bool(payload.get("emit_pending", True))
            policy = apply_policy_overlays(app.state.policy, state_dir)
            compile_result, plan = compile_proposal(
                store=store,
                state_dir=state_dir,
                policy=policy,
                proposal_id=proposal_id,
                emit_pending=emit_pending,
                actor=actor,
            )
            _post_compile_pending_slack_notification(
                plan=plan,
                policy=policy,
                emit_pending=emit_pending,
            )
            result = {
                "compile_result": compile_result.to_dict(),
                "pending_run_id": plan.run_id if plan is not None else None,
            }
        elif job_type == "reference_score":
            reference_set_id = str(payload.get("reference_set_id") or "").strip()
            if not reference_set_id:
                raise RuntimeError("reference_set_id is required for reference_score jobs")
            score = score_reference_set(
                store=store,
                state_dir=state_dir,
                reference_set_id=reference_set_id,
                threshold=payload.get("threshold", 0.7),
                max_candidates=payload.get("max_candidates", 250),
            )
            result = {"reference_score": score.to_dict()}
        else:
            raise RuntimeError(f"Unsupported control-plane job type: {job_type}")

        elapsed = _time.time() - started
        if elapsed > limits.timeout_seconds:
            raise RuntimeError(
                f"control-plane job exceeded timeout ({elapsed:.2f}s > {limits.timeout_seconds}s)"
            )
        return result if isinstance(result, dict) else {"result": result}

    def _process_control_plane_job(job: dict[str, Any]) -> dict[str, Any]:
        queue = _control_job_queue()
        store = _control_store()
        job_id = str(job.get("job_id") or "").strip()
        job_type = str(job.get("job_type") or "").strip().lower()
        actor = str(job.get("actor") or "").strip() or "system"
        payload = job.get("payload") if isinstance(job.get("payload"), dict) else {}
        if not job_id or not job_type:
            return {"status": "skipped", "reason": "invalid_job"}

        store.mark_job_running(job_id)
        try:
            result = _execute_control_plane_job(job_type, payload, actor=actor)
            store.mark_job_succeeded(job_id, result)
            queue.mark_succeeded(job_id=job_id)
            return {"job_id": job_id, "status": "succeeded", "result": result}
        except Exception as exc:
            next_status = queue.mark_failed(job_id=job_id, error=str(exc), retryable=True)
            if next_status == "dead_letter":
                store.mark_job_failed(job_id, str(exc))
            else:
                store.mark_job_pending(job_id, error=str(exc))
            return {"job_id": job_id, "status": next_status, "error": str(exc)}

    def _drain_control_plane_jobs(*, limit: int = 1) -> list[dict[str, Any]]:
        results: list[dict[str, Any]] = []
        queue = _control_job_queue()
        visibility_timeout = max(
            CONTROL_PLANE_QUEUE_VISIBILITY_TIMEOUT_SECONDS,
            int(_control_limits().timeout_seconds) + 15,
        )
        for _ in range(max(1, int(limit))):
            leased = queue.lease_next(visibility_timeout_seconds=visibility_timeout)
            if leased is None:
                break
            results.append(_process_control_plane_job(leased))
        return results

    async def _control_plane_job_worker_loop() -> None:
        stop_event: asyncio.Event = app.state.control_plane_job_worker_stop
        idle_sleep_seconds = 0.5
        max_idle_sleep_seconds = 5.0
        while not stop_event.is_set():
            try:
                drained = _drain_control_plane_jobs(limit=1)
                if drained:
                    idle_sleep_seconds = 0.5
                    continue
                await asyncio.sleep(idle_sleep_seconds)
                idle_sleep_seconds = min(max_idle_sleep_seconds, idle_sleep_seconds * 2.0)
            except Exception:
                logger.warning("Control-plane durable job worker cycle failed.", exc_info=True)
                await asyncio.sleep(min(max_idle_sleep_seconds, idle_sleep_seconds))
                idle_sleep_seconds = min(max_idle_sleep_seconds, max(1.0, idle_sleep_seconds * 2.0))

    def _ensure_control_plane_job_worker_started() -> None:
        stop_event: asyncio.Event = app.state.control_plane_job_worker_stop
        if stop_event.is_set():
            stop_event = asyncio.Event()
            app.state.control_plane_job_worker_stop = stop_event
        task = getattr(app.state, "control_plane_job_worker_task", None)
        if task is not None and not task.done():
            return
        try:
            loop = asyncio.get_running_loop()
        except RuntimeError:
            return
        app.state.control_plane_job_worker_task = loop.create_task(_control_plane_job_worker_loop())

    def _enqueue_control_plane_job(job_type: str, payload: dict[str, Any], *, actor: str) -> dict[str, Any]:
        store = _control_store()
        queue = _control_job_queue()
        job = store.create_job(job_type, payload, actor=actor)
        queue.enqueue_job(
            job_id=job.job_id,
            job_type=job.job_type,
            payload=job.request,
            actor=job.actor,
        )
        _ensure_control_plane_job_worker_started()
        return job.to_dict()

    @app.on_event("startup")
    async def _start_control_plane_job_worker() -> None:
        _ensure_control_plane_job_worker_started()

    @app.on_event("shutdown")
    async def _stop_control_plane_job_worker() -> None:
        stop_event: asyncio.Event = app.state.control_plane_job_worker_stop
        stop_event.set()
        task = getattr(app.state, "control_plane_job_worker_task", None)
        if task is None:
            return
        try:
            await asyncio.wait_for(task, timeout=2.0)
        except Exception:
            task.cancel()

    @app.get("/api/admin/control-plane/action-registry")
    async def control_plane_action_registry() -> dict[str, Any]:
        return {
            "version": ACTION_KEY_REGISTRY_VERSION,
            "reject_taxonomy_version": REJECT_TAXONOMY_VERSION,
            "reject_codes": list(REJECT_CODES_V1),
            "actions": ACTION_KEY_REGISTRY,
        }

    @app.get("/api/admin/control-plane/action-registry/health")
    async def control_plane_action_registry_health(request: Request) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="control_plane_action_registry_health",
        )
        module_store = ModuleStore(Path(app.state.state_dir))
        items: list[dict[str, Any]] = []
        for action_key in sorted(ACTION_KEY_REGISTRY.keys()):
            action_spec = ACTION_KEY_REGISTRY.get(action_key) or {}
            module_id = str(action_spec.get("module_id") or "").strip()
            supported_scouts = [
                str(value).strip()
                for value in (action_spec.get("supported_scouts") or [])
                if str(value).strip()
            ]
            try:
                definition = get_module_definition(module_id)
                envelope = module_store.load_module(module_id)
                reversibility, manifest = resolve_module_execution_contract(module_id, envelope.config)
                category = str(definition.category or "general").strip() or "general"
                module_enabled = bool(envelope.enabled)
                has_rollback = bool(reversibility.reversible and str(reversibility.undo_action_type or "").strip())
                has_safety_manifest = bool(str(manifest.idempotency_scope or "").strip())
            except Exception:
                category = "unknown"
                module_enabled = False
                has_rollback = False
                has_safety_manifest = False
            items.append(
                {
                    "action_key": action_key,
                    "module_id": module_id,
                    "module_enabled": module_enabled,
                    "has_rollback": has_rollback,
                    "has_safety_manifest": has_safety_manifest,
                    "supported_scouts": supported_scouts,
                    "category": category,
                }
            )
        return {"action_registry_version": ACTION_KEY_REGISTRY_VERSION, "items": items}

    @app.get("/api/admin/control-plane/non-goals")
    async def control_plane_non_goals() -> dict[str, Any]:
        return {
            "version": "v1",
            "non_goals": list(V1_NON_GOALS),
        }

    @app.post("/api/admin/control-plane/proposals/generate")
    async def control_plane_generate(request: Request) -> JSONResponse:
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        limits = _control_limit_check(payload)
        actor = _request_actor(request)
        job = _enqueue_control_plane_job("generate", payload, actor=actor)
        return JSONResponse(
            status_code=202,
            content={
                "status": "accepted",
                "job_id": job["job_id"],
                "limits": {
                    "max_tokens_per_run": limits.max_tokens_per_run,
                    "max_cost_usd_per_run": limits.max_cost_usd_per_run,
                    "timeout_seconds": limits.timeout_seconds,
                    "concurrency_cap": limits.concurrency_cap,
                },
            },
        )

    @app.get("/api/admin/control-plane/jobs/{job_id}")
    async def control_plane_job(job_id: str) -> dict[str, Any]:
        store = _control_store()
        job = store.get_job(job_id)
        if job is None:
            raise HTTPException(status_code=404, detail="Control-plane job not found")
        return job.to_dict()

    @app.get("/api/admin/jobs/queue")
    async def control_plane_queue_jobs(
        request: Request,
        status: str | None = Query(None),
        limit: int = Query(100, ge=1, le=500),
    ) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="jobs_queue",
        )
        normalized_status = str(status or "").strip().lower() or None
        if normalized_status and normalized_status not in {"pending", "processing", "succeeded", "dead_letter"}:
            raise HTTPException(status_code=400, detail="Invalid status filter")
        queue = _control_job_queue()
        return {
            "items": queue.list_jobs(status=normalized_status, limit=limit),
            "counts": queue.queue_counts(),
            "updated_at": datetime.now(timezone.utc).isoformat(),
        }

    @app.get("/api/admin/jobs/stats")
    async def control_plane_job_stats(request: Request) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="jobs_stats",
        )
        queue = _control_job_queue()
        dead_letter_items = queue.list_jobs(status="dead_letter", limit=10)
        recent_failures = [
            {
                "job_id": str(item.get("job_id") or ""),
                "job_type": str(item.get("job_type") or ""),
                "last_error": str(item.get("last_error") or ""),
                "updated_at": str(item.get("updated_at") or ""),
            }
            for item in dead_letter_items
        ]
        return {
            "queue_counts": queue.queue_counts(),
            "recent_failures": recent_failures,
            "action_registry_version": ACTION_KEY_REGISTRY_VERSION,
        }

    @app.get("/api/admin/readiness/autonomy-gate")
    async def autonomy_readiness_gate(request: Request) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="autonomy_readiness_gate",
        )
        return check_autonomy_readiness(Path(app.state.state_dir))

    @app.post("/api/admin/support-bundle/export")
    async def support_bundle_export(
        request: Request,
        lookback_days: int = Query(30, ge=1, le=3650),
        include_receipts: bool = Query(True),
    ) -> FileResponse:
        _require_roles(
            request,
            allowed={"admin"},
            reason_code="rbac_forbidden",
            required_for="support_bundle_export",
        )
        archive_path = build_support_bundle(
            Path(app.state.state_dir),
            include_receipts=bool(include_receipts),
            lookback_days=int(lookback_days),
        )
        return FileResponse(
            path=archive_path,
            media_type="application/gzip",
            filename=archive_path.name,
        )

    @app.post("/api/admin/jobs/retry")
    async def control_plane_retry_job(request: Request) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="jobs_retry",
        )
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        job_id = str(payload.get("job_id") or "").strip()
        if not job_id:
            raise HTTPException(status_code=400, detail="job_id is required")
        queue = _control_job_queue()
        retried = queue.retry_job(job_id=job_id)
        if retried is None:
            raise HTTPException(status_code=404, detail="Job not found")
        _control_store().mark_job_pending(job_id, error=None)
        return {"status": "retried", "job": retried, "counts": queue.queue_counts()}

    @app.post("/api/admin/control-plane/reference-sets")
    async def control_plane_create_reference_set(request: Request) -> dict[str, Any]:
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        name = str(payload.get("name") or "").strip()
        if not name:
            raise HTTPException(status_code=400, detail="name is required")
        agent_key = str(payload.get("agent_key") or "").strip()
        if not agent_key:
            raise HTTPException(status_code=400, detail="agent_key is required")
        raw_ids = payload.get("reference_record_ids")
        if not isinstance(raw_ids, list):
            raise HTTPException(status_code=400, detail="reference_record_ids must be an array")
        reference_record_ids = [str(value).strip() for value in raw_ids if str(value).strip()]
        if not reference_record_ids:
            raise HTTPException(status_code=400, detail="reference_record_ids must not be empty")

        store = _control_store()
        actor = _request_actor(request)
        existing_id = str(payload.get("reference_set_id") or "").strip()
        prior = store.get_reference_set(existing_id) if existing_id else None
        model_kwargs: dict[str, Any] = {
            "tenant_id": "default",
            "name": name,
            "agent_key": agent_key,
            "entity_type": str(payload.get("entity_type") or "opportunity").strip() or "opportunity",
            "description": str(payload.get("description") or "").strip(),
            "reference_record_ids": reference_record_ids,
            "status": str(payload.get("status") or (prior.status if prior else "active")).strip() or "active",
            "created_by": prior.created_by if prior else actor,
            "created_at": prior.created_at if prior else datetime.now(timezone.utc).isoformat(),
        }
        if prior is not None:
            model_kwargs["reference_set_id"] = prior.reference_set_id
        elif existing_id:
            model_kwargs["reference_set_id"] = existing_id
        try:
            reference_set = ReferenceSetV1(**model_kwargs)
        except Exception as exc:
            raise HTTPException(status_code=400, detail=f"Invalid reference set payload: {exc}") from exc
        saved = store.save_reference_set(reference_set)
        return saved.to_dict()

    @app.get("/api/admin/control-plane/reference-sets")
    async def control_plane_list_reference_sets(
        status: str | None = Query(None),
        agent_key: str | None = Query(None),
        cursor: str | None = Query(None),
        limit: int = Query(50, ge=1, le=200),
    ) -> dict[str, Any]:
        store = _control_store()
        items, next_cursor = store.list_reference_sets(
            status=status,
            agent_key=agent_key,
            cursor=cursor,
            limit=limit,
        )
        return {"items": [item.to_dict() for item in items], "next_cursor": next_cursor}

    @app.get("/api/admin/control-plane/reference-sets/{reference_set_id}")
    async def control_plane_get_reference_set(reference_set_id: str) -> dict[str, Any]:
        store = _control_store()
        reference_set = store.get_reference_set(reference_set_id)
        if reference_set is None:
            raise HTTPException(status_code=404, detail="Reference set not found")
        latest = store.latest_reference_score(reference_set_id)
        return {
            "reference_set": reference_set.to_dict(),
            "latest_score": latest.to_dict() if latest is not None else None,
        }

    @app.post("/api/admin/control-plane/reference-sets/{reference_set_id}/score")
    async def control_plane_score_reference_set(request: Request, reference_set_id: str) -> JSONResponse:
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        _control_limit_check(payload)
        store = _control_store()
        if store.get_reference_set(reference_set_id) is None:
            raise HTTPException(status_code=404, detail="Reference set not found")
        payload["reference_set_id"] = reference_set_id
        actor = _request_actor(request)
        job = _enqueue_control_plane_job("reference_score", payload, actor=actor)
        return JSONResponse(status_code=202, content={"status": "accepted", "job_id": job["job_id"]})

    @app.get("/api/admin/control-plane/reference-sets/{reference_set_id}/scores/latest")
    async def control_plane_reference_set_latest_score(reference_set_id: str) -> dict[str, Any]:
        store = _control_store()
        if store.get_reference_set(reference_set_id) is None:
            raise HTTPException(status_code=404, detail="Reference set not found")
        latest = store.latest_reference_score(reference_set_id)
        return {"reference_set_id": reference_set_id, "score": latest.to_dict() if latest is not None else None}

    @app.get("/api/admin/control-plane/proposals")
    async def control_plane_list_proposals(
        status: str | None = Query(None),
        risk_tier: str | None = Query(None),
        cursor: str | None = Query(None),
        limit: int = Query(50, ge=1, le=200),
    ) -> dict[str, Any]:
        store = _control_store()
        items, next_cursor = store.list_proposals(status=status, risk_tier=risk_tier, cursor=cursor, limit=limit)
        return {
            "items": [item.to_dict() for item in items],
            "next_cursor": next_cursor,
            "action_registry_version": ACTION_KEY_REGISTRY_VERSION,
            "reject_taxonomy_version": REJECT_TAXONOMY_VERSION,
        }

    @app.post("/api/admin/control-plane/proposals")
    async def control_plane_create_proposal(
        request: Request,
        async_mode: bool = Query(default=True, alias="async"),
    ) -> Any:
        actor_ctx = _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="control_plane_proposal_create",
        )
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        _control_limit_check(payload)

        proposal_payload = payload.get("proposal") if isinstance(payload.get("proposal"), dict) else payload
        if not isinstance(proposal_payload, dict):
            raise HTTPException(status_code=400, detail="proposal payload must be an object")
        title = str(proposal_payload.get("title") or "").strip()
        objective = str(proposal_payload.get("objective") or "").strip()
        raw_candidates = proposal_payload.get("action_candidates")
        if not title:
            raise HTTPException(status_code=400, detail="title is required")
        if not objective:
            raise HTTPException(status_code=400, detail="objective is required")
        if not isinstance(raw_candidates, list) or not raw_candidates:
            raise HTTPException(status_code=400, detail="action_candidates must be a non-empty array")
        action_candidates = [row for row in raw_candidates if isinstance(row, dict)]
        if not action_candidates:
            raise HTTPException(status_code=400, detail="action_candidates must include object entries")

        dedupe_window_seconds = _coerce_int(
            proposal_payload.get("dedupe_window_seconds", payload.get("dedupe_window_seconds")),
            DEFAULT_DEDUPE_WINDOW_SECONDS,
        )
        dedupe_window_seconds = max(dedupe_window_seconds, 0)
        evidence_ids = [
            str(value).strip()
            for value in (proposal_payload.get("evidence_ids") or [])
            if str(value).strip()
        ]
        if not evidence_ids:
            evidence_ids = [_stable_sha256({"action_candidates": action_candidates, "title": title})]

        signature = str(proposal_payload.get("proposal_signature") or "").strip()
        if not signature:
            action_family_values = sorted(
                {
                    str((candidate or {}).get("deterministic_action_key") or "").strip()
                    for candidate in action_candidates
                    if str((candidate or {}).get("deterministic_action_key") or "").strip()
                }
            )
            action_family = ",".join(action_family_values) if action_family_values else "custom"
            scope = str(proposal_payload.get("scope") or title).strip() or title
            signature = compute_proposal_signature(
                scope=scope,
                action_family=action_family,
                evidence_ids=evidence_ids,
                window_bucket=_window_bucket_timestamp(datetime.now(timezone.utc), dedupe_window_seconds),
            )

        proposal_kwargs: dict[str, Any] = {
            "tenant_id": str(proposal_payload.get("tenant_id") or "default").strip() or "default",
            "source_kind": str(proposal_payload.get("source_kind") or "scout").strip() or "scout",
            "source_refs": [
                str(value).strip()
                for value in (proposal_payload.get("source_refs") or [])
                if str(value).strip()
            ],
            "title": title,
            "objective": objective,
            "action_candidates": action_candidates,
            "confidence": proposal_payload.get("confidence") if isinstance(proposal_payload.get("confidence"), dict) else {},
            "risk": proposal_payload.get("risk") if isinstance(proposal_payload.get("risk"), dict) else {"tier": "low"},
            "evidence_ids": evidence_ids,
            "why_summary": str(proposal_payload.get("why_summary") or "").strip(),
            "proposal_signature": signature,
            "dedupe_window_seconds": dedupe_window_seconds,
            "created_by": actor_ctx.actor_id,
        }
        proposal_id = str(proposal_payload.get("proposal_id") or "").strip()
        if proposal_id:
            proposal_kwargs["proposal_id"] = proposal_id
        try:
            proposal = ProposalV1(**proposal_kwargs)
        except Exception as exc:
            raise HTTPException(status_code=400, detail=f"Invalid proposal payload: {exc}") from exc

        store = _control_store()
        saved, was_deduped = store.save_proposal(proposal)
        if not was_deduped:
            store.record_outcome(saved.proposal_id, "proposed", actor=actor_ctx.actor_id, detail={"source": "api"})

        emit_pending = bool(proposal_payload.get("emit_pending", payload.get("emit_pending", True)))
        if async_mode:
            job = _enqueue_control_plane_job(
                "compile",
                {"proposal_id": saved.proposal_id, "emit_pending": emit_pending},
                actor=actor_ctx.actor_id,
            )
            return JSONResponse(
                status_code=202,
                content={
                    "status": "accepted",
                    "proposal_id": saved.proposal_id,
                    "deduped": was_deduped,
                    "job_id": job["job_id"],
                    "async": True,
                },
            )

        policy = apply_policy_overlays(app.state.policy, Path(app.state.state_dir))
        compile_result, plan = compile_proposal(
            store=store,
            state_dir=Path(app.state.state_dir),
            policy=policy,
            proposal_id=saved.proposal_id,
            emit_pending=emit_pending,
            actor=actor_ctx.actor_id,
        )
        _post_compile_pending_slack_notification(
            plan=plan,
            policy=policy,
            emit_pending=emit_pending,
        )
        return {
            "status": "completed",
            "proposal_id": saved.proposal_id,
            "deduped": was_deduped,
            "compile_result": compile_result.to_dict(),
            "pending_run_id": plan.run_id if plan is not None else None,
            "async": False,
        }

    @app.get("/api/admin/control-plane/proposals/{proposal_id}")
    async def control_plane_get_proposal(proposal_id: str) -> dict[str, Any]:
        store = _control_store()
        payload = proposal_detail_payload(store, proposal_id)
        if payload is None:
            raise HTTPException(status_code=404, detail="Proposal not found")
        return payload

    @app.post("/api/admin/control-plane/proposals/{proposal_id}/compile")
    async def control_plane_compile(request: Request, proposal_id: str) -> JSONResponse:
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        _control_limit_check(payload)
        store = _control_store()
        if store.get_proposal(proposal_id) is None:
            raise HTTPException(status_code=404, detail="Proposal not found")
        payload["proposal_id"] = proposal_id
        actor = _request_actor(request)
        job = _enqueue_control_plane_job("compile", payload, actor=actor)
        return JSONResponse(status_code=202, content={"status": "accepted", "job_id": job["job_id"]})

    @app.post("/api/admin/control-plane/proposals/{proposal_id}/override")
    async def control_plane_override(request: Request, proposal_id: str) -> dict[str, Any]:
        actor_ctx = _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="control_plane_override",
        )
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        reason = str((payload or {}).get("reason") or "").strip()
        if len(reason) < 4:
            raise HTTPException(status_code=400, detail="reason is required for override and must be at least 4 characters")
        store = _control_store()
        proposal = store.get_proposal(proposal_id)
        if proposal is None:
            raise HTTPException(status_code=404, detail="Proposal not found")
        store.update_proposal_status(proposal_id, "overridden")
        store.record_outcome(
            proposal_id,
            "overridden",
            actor=actor_ctx.actor_id,
            detail={"reason": reason},
        )
        return {"status": "overridden", "proposal_id": proposal_id, "reason": reason}

    @app.post("/api/admin/control-plane/proposals/{proposal_id}/feedback")
    async def control_plane_feedback(request: Request, proposal_id: str) -> dict[str, Any]:
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        detail = payload if isinstance(payload, dict) else {"value": payload}
        store = _control_store()
        proposal = store.get_proposal(proposal_id)
        if proposal is None:
            raise HTTPException(status_code=404, detail="Proposal not found")
        store.record_outcome(
            proposal_id,
            "feedback",
            actor=_request_actor(request),
            detail={"feedback": detail},
        )
        return {"status": "recorded", "proposal_id": proposal_id}

    @app.get("/api/admin/control-plane/inbox")
    async def control_plane_list_inbox(
        status: str | None = Query(None),
        pattern_type: str | None = Query(None),
        cursor: str | None = Query(None),
        limit: int = Query(50, ge=1, le=200),
    ) -> dict[str, Any]:
        store = _control_store()
        items, next_cursor = store.list_inbox(status=status, pattern_type=pattern_type, cursor=cursor, limit=limit)
        return {"items": [item.to_dict() for item in items], "next_cursor": next_cursor}

    @app.get("/api/admin/control-plane/inbox/{inbox_item_id}")
    async def control_plane_get_inbox_item(inbox_item_id: str) -> dict[str, Any]:
        store = _control_store()
        item = store.get_inbox_item(inbox_item_id)
        if item is None:
            raise HTTPException(status_code=404, detail="Inbox item not found")
        return item.to_dict()

    @app.post("/api/admin/control-plane/inbox/{inbox_item_id}/triage-assist")
    async def control_plane_triage_assist(inbox_item_id: str) -> dict[str, Any]:
        store = _control_store()
        item = store.get_inbox_item(inbox_item_id)
        if item is None:
            raise HTTPException(status_code=404, detail="Inbox item not found")

        fallback_suggestions = [
            {
                "label": str(candidate).strip(),
                "confidence": 0.75,
                "reasoning": "Existing candidate semantics from deterministic ingestion.",
            }
            for candidate in (item.candidate_semantics or [])
            if str(candidate).strip()
        ]

        try:
            llm = resolve_provider()
        except RuntimeError:
            if fallback_suggestions:
                return {"suggestions": fallback_suggestions[:5]}
            return {
                "suggestions": [
                    {
                        "label": "manual_review",
                        "confidence": 0.3,
                        "reasoning": "No LLM provider configured. Set ANTHROPIC_API_KEY or OPENAI_API_KEY.",
                    }
                ]
            }

        prompt = (
            "Classify this inbox item and suggest likely mappings.\n"
            "Return ONLY JSON with shape:\n"
            "{\n"
            '  "suggestions": [{"label": "string", "confidence": 0.0-1.0, "reasoning": "string"}]\n'
            "}\n\n"
            f"pattern_type={item.pattern_type}\n"
            f"raw_value={', '.join(item.sample_values[:5])}\n"
            f"object_name={item.crm_object}\n"
            f"field_name={item.crm_field}\n"
            f"candidate_semantics={item.candidate_semantics}\n"
        )
        raw = llm.chat(
            [
                {"role": "system", "content": "You are a CRM schema triage assistant."},
                {"role": "user", "content": prompt},
            ],
            max_tokens=500,
        )

        parsed: dict[str, Any] | None = None
        raw_text = str(raw or "").strip()
        if raw_text:
            if "```" in raw_text:
                raw_text = _strip_markdown_fence(raw_text)
            try:
                parsed_payload = json.loads(raw_text)
            except Exception:
                parsed_payload = None
            if isinstance(parsed_payload, dict):
                parsed = parsed_payload

        suggestions_raw = parsed.get("suggestions") if isinstance(parsed, dict) else None
        suggestions: list[dict[str, Any]] = []
        if isinstance(suggestions_raw, list):
            for row in suggestions_raw[:8]:
                if not isinstance(row, dict):
                    continue
                label = str(row.get("label") or "").strip()
                reasoning = str(row.get("reasoning") or "").strip()
                try:
                    confidence = max(0.0, min(float(row.get("confidence")), 1.0))
                except Exception:
                    confidence = 0.0
                if not label:
                    continue
                suggestions.append(
                    {
                        "label": label,
                        "confidence": confidence,
                        "reasoning": reasoning,
                    }
                )

        if not suggestions:
            suggestions = fallback_suggestions
        if not suggestions:
            suggestions = [
                {
                    "label": "manual_review",
                    "confidence": 0.3,
                    "reasoning": "No high-confidence mapping inferred; manual review recommended.",
                }
            ]
        return {"suggestions": suggestions[:5]}

    @app.post("/api/admin/control-plane/inbox/{inbox_item_id}/resolve")
    async def control_plane_resolve_inbox(request: Request, inbox_item_id: str) -> dict[str, Any]:
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        resolution = payload.get("resolution") if isinstance(payload, dict) else None
        if resolution is None:
            resolution = payload if isinstance(payload, dict) else {}
        store = _control_store()
        item = store.update_inbox_status(
            inbox_item_id,
            status="resolved",
            resolution=resolution if isinstance(resolution, dict) else {},
        )
        if item is None:
            raise HTTPException(status_code=404, detail="Inbox item not found")
        return item.to_dict()

    @app.post("/api/admin/control-plane/inbox/{inbox_item_id}/ignore")
    async def control_plane_ignore_inbox(inbox_item_id: str) -> dict[str, Any]:
        store = _control_store()
        item = store.update_inbox_status(inbox_item_id, status="ignored")
        if item is None:
            raise HTTPException(status_code=404, detail="Inbox item not found")
        return item.to_dict()

    @app.get("/api/admin/control-plane/compile-results/{compile_id}")
    async def control_plane_compile_result(compile_id: str) -> dict[str, Any]:
        store = _control_store()
        result = store.get_compile_result(compile_id)
        if result is None:
            raise HTTPException(status_code=404, detail="Compile result not found")
        return result.to_dict()

    @app.get("/api/admin/control-plane/metrics/summary")
    async def control_plane_metrics_summary(
        window: str = Query("30d"),
    ) -> dict[str, Any]:
        store = _control_store()
        window_days = 30
        normalized = str(window or "").strip().lower()
        if normalized.endswith("d"):
            window_days = max(1, _coerce_int(normalized[:-1], 30))
        return store.metrics_summary(window_days=window_days)

    @app.get("/api/admin/autonomy/status")
    async def autonomy_operational_status(
        request: Request,
        window: str = Query("30d"),
        event_window: str = Query("24h"),
    ) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="autonomy_operational_status",
        )
        window_days = 30
        normalized_window = str(window or "").strip().lower()
        if normalized_window.endswith("d"):
            window_days = max(1, _coerce_int(normalized_window[:-1], 30))
        event_window_hours = 24
        normalized_event_window = str(event_window or "").strip().lower()
        if normalized_event_window.endswith("h"):
            event_window_hours = max(1, _coerce_int(normalized_event_window[:-1], 24))
        state_dir = Path(app.state.state_dir)
        operational = autonomy_operational_health(
            state_dir,
            lookback_days=window_days,
            event_window_hours=event_window_hours,
        )
        readiness = check_autonomy_readiness(state_dir)
        return {
            "generated_at": datetime.now(timezone.utc).isoformat(),
            "lookback_days": window_days,
            "event_window_hours": event_window_hours,
            "operational_health": operational,
            "readiness": readiness,
        }

    @app.get("/api/admin/control-plane/metrics/calibration")
    async def control_plane_metrics_calibration(
        window: str = Query("30d"),
    ) -> dict[str, Any]:
        store = _control_store()
        window_days = 30
        normalized = str(window or "").strip().lower()
        if normalized.endswith("d"):
            window_days = max(1, _coerce_int(normalized[:-1], 30))
        return store.calibration_metrics(window_days=window_days)

    # --- Discovery ---

    @app.post("/api/admin/discovery/run")
    async def run_discovery(request: Request) -> dict[str, Any]:
        try:
            parsed_payload = await request.json()
        except Exception:
            parsed_payload = {}
        if parsed_payload is None:
            parsed_payload = {}
        if not isinstance(parsed_payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed_payload

        raw_crm = payload.get("crm")
        if raw_crm is not None and not isinstance(raw_crm, str):
            raise HTTPException(status_code=400, detail="crm must be a string")
        crm = str(raw_crm or "salesforce").strip().lower()
        if not crm:
            crm = "salesforce"

        raw_org_alias = payload.get("org_alias")
        if raw_org_alias is not None and not isinstance(raw_org_alias, str):
            raise HTTPException(status_code=400, detail="org_alias must be a string or null")
        org_alias = raw_org_alias.strip() if isinstance(raw_org_alias, str) and raw_org_alias.strip() else None

        state_dir = Path(app.state.state_dir)
        store = DiscoveryStore(state_dir)
        overrides = ScoutConfigStore(state_dir).load_all()
        ai_store = AIProviderStore(state_dir)
        ai_config = ai_store.load_config()

        configured_integrations = _configured_integrations()
        loader = ScoutLoader(user_credentials=configured_integrations)
        engine = ScoutEngine(loader, user_credentials=configured_integrations)
        runner = DiscoveryRunner(
            store,
            engine,
            crm=crm,
            org_alias=org_alias,
            overrides=overrides,
            ai_config=ai_config if ai_config and ai_config.enabled else None,
        )
        try:
            report = runner.run()
        except Exception as exc:
            return JSONResponse(
                status_code=500,
                content={"status": "error", "detail": str(exc)},
            )

        return {
            "status": "completed",
            "run_id": report.run_id,
            "report": report.to_dict(),
        }

    @app.get("/api/admin/discovery/latest")
    async def latest_discovery_run() -> dict[str, Any]:
        store = DiscoveryStore(Path(app.state.state_dir))
        run_id = store.latest_run_id()
        if run_id is None:
            return {"status": "empty", "run": None, "report": None}

        run = store.load_run(run_id)
        report = store.load_report(run_id)
        if run is None:
            return {"status": "empty", "run": None, "report": None}

        return {
            "status": "ok",
            "run": run.to_dict(),
            "report": report.to_dict() if report else None,
        }

    @app.get("/api/admin/discovery/{run_id}")
    async def get_discovery_run(run_id: str) -> dict[str, Any]:
        store = DiscoveryStore(Path(app.state.state_dir))
        run = store.load_run(run_id)
        if run is None:
            raise HTTPException(status_code=404, detail="Discovery run not found")

        report = store.load_report(run_id)
        return {
            "status": "ok",
            "run": run.to_dict(),
            "report": report.to_dict() if report else None,
        }

    # --- Concierge Preview ---

    @app.post("/api/admin/concierge/preview")
    async def concierge_preview(request: Request) -> dict[str, Any]:
        try:
            parsed_payload = await request.json()
        except Exception:
            parsed_payload = {}
        if parsed_payload is None:
            parsed_payload = {}
        if not isinstance(parsed_payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed_payload

        raw_run_id = payload.get("run_id")
        if raw_run_id is not None and not isinstance(raw_run_id, str):
            raise HTTPException(status_code=400, detail="run_id must be a string")
        run_id = raw_run_id.strip() if isinstance(raw_run_id, str) and raw_run_id.strip() else None

        include_validation_scan = payload.get("include_validation_scan", True)
        if not isinstance(include_validation_scan, bool):
            raise HTTPException(status_code=400, detail="include_validation_scan must be a boolean")

        state_dir = Path(app.state.state_dir)
        discovery_store = DiscoveryStore(state_dir)
        report = None
        if run_id:
            report = discovery_store.load_report(run_id)
        else:
            latest_run_id = discovery_store.latest_run_id()
            if latest_run_id:
                report = discovery_store.load_report(latest_run_id)
        if report is None:
            return {"plan": None, "report_run_id": None}

        configured_integrations = _configured_integrations()
        scout_loader = ScoutLoader(user_credentials=configured_integrations)
        scout_engine = ScoutEngine(scout_loader, user_credentials=configured_integrations)
        planner = ConciergePlanner(
            report,
            ScoutConfigStore(state_dir),
            ModuleStore(state_dir),
            scout_loader,
            scout_engine,
        )
        plan = planner.plan(include_validation_scan=include_validation_scan)
        return {"plan": plan.model_dump(exclude_none=False), "report_run_id": report.run_id}

    @app.post("/api/admin/concierge/preview/select")
    async def concierge_preview_select(request: Request) -> dict[str, Any]:
        try:
            parsed_payload = await request.json()
        except Exception:
            parsed_payload = {}
        if parsed_payload is None:
            parsed_payload = {}
        if not isinstance(parsed_payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed_payload

        raw_run_id = payload.get("run_id")
        if not isinstance(raw_run_id, str) or not raw_run_id.strip():
            raise HTTPException(status_code=400, detail="run_id is required")
        run_id = raw_run_id.strip()

        raw_action_ids = payload.get("action_ids")
        if not isinstance(raw_action_ids, list):
            raise HTTPException(status_code=400, detail="action_ids must be a list")
        action_ids: list[str] = []
        for value in raw_action_ids:
            if not isinstance(value, str) or not value.strip():
                raise HTTPException(status_code=400, detail="action_ids entries must be non-empty strings")
            action_ids.append(value.strip())

        include_validation_scan = payload.get("include_validation_scan", True)
        if not isinstance(include_validation_scan, bool):
            raise HTTPException(status_code=400, detail="include_validation_scan must be a boolean")

        state_dir = Path(app.state.state_dir)
        discovery_store = DiscoveryStore(state_dir)
        report = discovery_store.load_report(run_id)
        if report is None:
            return {"plan": None, "report_run_id": None}

        configured_integrations = _configured_integrations()
        scout_loader = ScoutLoader(user_credentials=configured_integrations)
        scout_engine = ScoutEngine(scout_loader, user_credentials=configured_integrations)
        planner = ConciergePlanner(
            report,
            ScoutConfigStore(state_dir),
            ModuleStore(state_dir),
            scout_loader,
            scout_engine,
        )
        full_plan = planner.plan(include_validation_scan=include_validation_scan)

        selected_action_ids = set(action_ids)
        selected_actions = [
            action for action in full_plan.actions if action.action_id in selected_action_ids
        ]
        warnings = list(full_plan.warnings)

        selected_config_action = any(
            action.action_type in {"scout_config.upsert", "module_config.upsert"}
            for action in selected_actions
        )
        selected_policy_reload = any(
            action.action_type == "runtime_policy.reload" for action in selected_actions
        )
        if selected_config_action and not selected_policy_reload:
            warnings.append(
                "Selected configuration actions are missing runtime_policy.reload; "
                "reload is recommended before apply."
            )

        filtered_plan = full_plan.model_copy(
            update={
                "actions": selected_actions,
                "warnings": warnings,
                "stats": ConciergePlanner.compute_stats(selected_actions),
            }
        )
        return {"plan": filtered_plan.model_dump(exclude_none=False), "report_run_id": report.run_id}

    @app.post("/api/admin/concierge/apply")
    async def concierge_apply(request: Request) -> dict[str, Any]:
        try:
            parsed_payload = await request.json()
        except Exception:
            parsed_payload = {}
        if parsed_payload is None:
            parsed_payload = {}
        if not isinstance(parsed_payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed_payload

        raw_run_id = payload.get("run_id")
        if not isinstance(raw_run_id, str) or not raw_run_id.strip():
            raise HTTPException(status_code=400, detail="run_id is required")
        run_id = raw_run_id.strip()

        raw_action_ids = payload.get("action_ids")
        action_ids: list[str] | None = None
        if raw_action_ids is not None:
            if not isinstance(raw_action_ids, list):
                raise HTTPException(status_code=400, detail="action_ids must be a list")
            action_ids = []
            for value in raw_action_ids:
                if not isinstance(value, str) or not value.strip():
                    raise HTTPException(status_code=400, detail="action_ids entries must be non-empty strings")
                action_ids.append(value.strip())

        dry_run = payload.get("dry_run", True)
        if not isinstance(dry_run, bool):
            raise HTTPException(status_code=400, detail="dry_run must be a boolean")

        include_validation_scan = payload.get("include_validation_scan", True)
        if not isinstance(include_validation_scan, bool):
            raise HTTPException(status_code=400, detail="include_validation_scan must be a boolean")

        state_dir = Path(app.state.state_dir)
        discovery_store = DiscoveryStore(state_dir)
        report = discovery_store.load_report(run_id)
        if report is None:
            raise HTTPException(status_code=404, detail="Discovery run not found")

        configured_integrations = _configured_integrations()
        scout_loader = ScoutLoader(user_credentials=configured_integrations)
        scout_engine = ScoutEngine(scout_loader, user_credentials=configured_integrations)
        planner = ConciergePlanner(
            report,
            ScoutConfigStore(state_dir),
            ModuleStore(state_dir),
            scout_loader,
            scout_engine,
        )
        full_plan = planner.plan(include_validation_scan=include_validation_scan)

        plan_to_apply = full_plan
        if action_ids is not None:
            selected_action_ids = set(action_ids)
            selected_actions = [
                action for action in full_plan.actions if action.action_id in selected_action_ids
            ]
            warnings = list(full_plan.warnings)
            selected_config_action = any(
                action.action_type in {"scout_config.upsert", "module_config.upsert"}
                for action in selected_actions
            )
            selected_policy_reload = any(
                action.action_type == "runtime_policy.reload" for action in selected_actions
            )
            if selected_config_action and not selected_policy_reload:
                warnings.append(
                    "Selected configuration actions are missing runtime_policy.reload; "
                    "reload is recommended before apply."
                )
            plan_to_apply = full_plan.model_copy(
                update={
                    "actions": selected_actions,
                    "warnings": warnings,
                    "stats": ConciergePlanner.compute_stats(selected_actions),
                }
            )

        actor = _module_actor(request)
        audit_log = AuditLog(state_dir / "db" / "platform.sqlite")
        executor = ConciergeExecutor(
            ScoutConfigStore(state_dir),
            ModuleStore(state_dir),
            state_dir,
            audit_log=audit_log,
        )
        receipt = executor.execute(plan_to_apply, dry_run=dry_run, actor=actor)
        return {
            "receipt": receipt.model_dump(exclude_none=False),
            "report_run_id": report.run_id,
        }

    # --- Modules ---

    def _module_store() -> ModuleStore:
        return ModuleStore(Path(app.state.state_dir))

    def _module_policy() -> Any:
        return apply_policy_overlays(app.state.policy, Path(app.state.state_dir))

    def _module_schedule_keys() -> set[str]:
        policy = _module_policy()
        return set(policy.schedules.keys())

    def _module_routing_targets() -> set[str]:
        targets = {"#revops-exceptions"}
        slack_status = _resolve_slack_status(app, include_secret=False)
        channel = slack_status.get("channel")
        if isinstance(channel, str) and channel.strip():
            targets.add(channel.strip())
        return targets

    def _module_actor(request: Request) -> str:
        return _resolve_actor_context(request).actor_id

    def _module_field_meta(module_id: str) -> dict[str, Any]:
        definition = get_module_definition(module_id)
        return {
            field_name: meta.model_dump(exclude_none=True)
            for field_name, meta in definition.config_model.field_meta_map().items()
        }

    def _module_contracts(module_id: str, config: dict[str, Any] | None = None) -> dict[str, Any]:
        reversibility, safety_manifest = resolve_module_execution_contract(module_id, config)
        return {
            "reversibility_contract": reversibility.model_dump(exclude_none=False),
            "safety_manifest": safety_manifest.model_dump(exclude_none=False),
        }

    def _module_status(envelope: ModuleEnvelope) -> str:
        if not envelope.enabled:
            return "disabled"
        if envelope.policy_hash:
            return "active"
        return "draft"

    def _module_synthetic_samples(module_id: str) -> list[dict[str, Any]]:
        if module_id == "lead_to_account":
            return [
                {"lead_id": "L-100", "domain": "acme.com", "risk_score": 25},
                {"lead_id": "L-101", "domain": "example.org", "risk_score": 54},
                {"lead_id": "L-102", "domain": "highrisk.invalid", "risk_score": 90},
            ]
        if module_id == "deal_desk":
            return [
                {"opportunity_id": "006XX00000A1", "packet_id": "PKT-1", "risk_score": 18},
                {"opportunity_id": "006XX00000A2", "packet_id": "PKT-2", "risk_score": 62},
                {"opportunity_id": "006XX00000A3", "packet_id": "PKT-3", "risk_score": 88},
            ]
        if module_id == "sequence_pause":
            return [
                {"record_id": "00QXX0000001", "reason_code": "DNC_IN_ACTIVE_SEQUENCE", "risk_score": 22},
                {
                    "record_id": "003XX0000002",
                    "reason_code": "SUPPORT_ESCALATION_IN_SEQUENCE",
                    "risk_score": 58,
                },
                {"record_id": "00QXX0000003", "reason_code": "DNC_IN_ACTIVE_SEQUENCE", "risk_score": 85},
            ]
        if module_id == "offboarding_cleanup":
            return [
                {
                    "record_id": "00QXX0000101",
                    "sobject": "Lead",
                    "former_owner_id": "005INACTIVE1",
                    "new_owner_id": "005ACTIVE1",
                    "risk_score": 28,
                },
                {
                    "record_id": "001XX0000202",
                    "sobject": "Account",
                    "former_owner_id": "005INACTIVE2",
                    "new_owner_id": "005ACTIVE2",
                    "risk_score": 66,
                },
                {
                    "record_id": "006XX0000303",
                    "sobject": "Opportunity",
                    "former_owner_id": "005INACTIVE3",
                    "new_owner_id": "005ACTIVE3",
                    "risk_score": 89,
                },
            ]
        if module_id == "auto_enrich_missing_fields":
            return [
                {
                    "record_id": "001XX0000404",
                    "sobject": "Account",
                    "missing_fields": ["Website", "Industry"],
                    "provider": "zoominfo",
                    "confidence": 0.86,
                    "risk_score": 22,
                },
                {
                    "record_id": "001XX0000505",
                    "sobject": "Account",
                    "missing_fields": ["NumberOfEmployees"],
                    "provider": "apollo",
                    "confidence": 0.82,
                    "risk_score": 60,
                },
                {
                    "record_id": "001XX0000606",
                    "sobject": "Account",
                    "missing_fields": ["Website", "Industry", "NumberOfEmployees"],
                    "provider": "clearbit",
                    "confidence": 0.8,
                    "risk_score": 88,
                },
            ]
        if module_id == "follow_up_enforcement":
            return [
                {
                    "record_id": "00UXX0000001",
                    "sobject": "Event",
                    "owner_id": "005XX0000001",
                    "apply_stage_hold": False,
                    "notify_slack": False,
                    "risk_score": 26,
                },
                {
                    "record_id": "00UXX0000002",
                    "sobject": "Event",
                    "owner_id": "005XX0000002",
                    "apply_stage_hold": True,
                    "notify_slack": False,
                    "risk_score": 61,
                },
                {
                    "record_id": "00UXX0000003",
                    "sobject": "Event",
                    "owner_id": "005XX0000003",
                    "apply_stage_hold": True,
                    "notify_slack": True,
                    "risk_score": 86,
                },
            ]
        if module_id == "slack_routing":
            return [
                {
                    "record_id": "00QXX0000009",
                    "reason_code": "OPEN_TICKET_IN_SEQUENCE",
                    "channel": "#revops-alerts",
                    "risk_score": 24,
                },
                {
                    "record_id": "entity-42",
                    "reason_code": "CROSS_SYSTEM_FIELD_DRIFT",
                    "channel": "#revops-alerts",
                    "risk_score": 63,
                },
                {
                    "record_id": "entity-77",
                    "reason_code": "CROSS_SYSTEM_FIELD_DRIFT",
                    "channel": "#revops-alerts",
                    "risk_score": 90,
                },
            ]
        return []

    def _module_receipt_cached_samples(module_id: str, *, limit: int = 25) -> list[dict[str, Any]]:
        state_dir = Path(app.state.state_dir)
        receipts = sorted(
            _load_execution_receipts(state_dir),
            key=_execution_receipt_sort_key,
            reverse=True,
        )
        write_class_keys = set(get_module_definition(module_id).write_class_keys)

        seen: set[str] = set()
        samples: list[dict[str, Any]] = []
        for receipt in receipts:
            if not _receipt_matches_module(receipt, module_id=module_id, write_class_keys=write_class_keys):
                continue
            run_id = str(receipt.get("run_id") or "")
            outcome = str(receipt.get("outcome") or "")
            policy_hash = receipt.get("policy_hash")
            changes = receipt.get("changes")
            if not isinstance(changes, list):
                continue
            for change in changes:
                sample = _sample_from_receipt_change(change)
                if sample is None:
                    continue
                sample["_receipt_run_id"] = run_id
                sample["_receipt_outcome"] = outcome
                if isinstance(policy_hash, str) and policy_hash:
                    sample["_receipt_policy_hash"] = policy_hash
                cache_key = json.dumps(sample, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
                if cache_key in seen:
                    continue
                seen.add(cache_key)
                samples.append(sample)
                if len(samples) >= limit:
                    return samples
        return samples

    def _module_sample_sets(module_id: str) -> list[dict[str, Any]]:
        sample_sets: list[dict[str, Any]] = []
        cached_samples = _module_receipt_cached_samples(module_id)
        if cached_samples:
            sample_sets.append(
                {
                    "sample_set_id": "cache/receipts",
                    "source": "receipt_cache",
                    "label": "Recent execution receipts",
                    "samples": cached_samples,
                }
            )

        synthetic_samples = _module_synthetic_samples(module_id)
        if synthetic_samples:
            sample_sets.append(
                {
                    "sample_set_id": "synthetic/default",
                    "source": "synthetic",
                    "label": "Synthetic defaults",
                    "samples": synthetic_samples,
                }
            )

        sample_sets.append(
            {
                "sample_set_id": "user/manual",
                "source": "user",
                "label": "User-provided JSON",
                "samples": [],
            }
        )
        return sample_sets

    def _module_default_simulation_samples(module_id: str) -> list[dict[str, Any]]:
        for sample_set in _module_sample_sets(module_id):
            raw_samples = sample_set.get("samples")
            if not isinstance(raw_samples, list):
                continue
            rows = [item for item in raw_samples if isinstance(item, dict)]
            if rows:
                return rows
        return []

    def _module_rule_names(module_id: str) -> list[str]:
        definition = get_module_definition(module_id)
        rule_names: list[str] = []
        for write_class_key in sorted(definition.write_class_keys):
            rule_names.extend(
                [
                    f"module:{module_id}:{write_class_key}:green",
                    f"module:{module_id}:{write_class_key}:yellow",
                    f"module:{module_id}:{write_class_key}:red",
                ]
            )
        return rule_names

    def _candidate_envelope_from_payload(
        module_id: str,
        payload: dict[str, Any],
        *,
        current: ModuleEnvelope | None = None,
    ) -> ModuleEnvelope:
        required_keys = ("enabled", "schedule_ref", "exception_routing", "risk_tiers", "config")
        missing = [key for key in required_keys if key not in payload]
        if missing:
            raise ModuleValidationError(
                "proposed module payload must include full envelope fields: "
                + ", ".join(required_keys)
            )
        definition = get_module_definition(module_id)
        baseline = current or default_module_envelope(module_id)
        candidate = ModuleEnvelope(
            module_id=definition.module_id,
            display_name=definition.display_name,
            description=definition.description,
            icon=definition.icon,
            category=definition.category,
            schema_version=definition.schema_version,
            enabled=bool(payload["enabled"]),
            schedule_ref=payload.get("schedule_ref"),
            exception_routing=payload.get("exception_routing"),
            risk_tiers=payload.get("risk_tiers"),
            config=payload.get("config"),
            updated_by=baseline.updated_by,
            updated_at=baseline.updated_at,
            revision_hash=baseline.revision_hash,
            policy_hash=baseline.policy_hash,
        )
        return validate_module_envelope(
            candidate,
            schedule_keys=_module_schedule_keys(),
            routing_targets=_module_routing_targets(),
            require_full_config=True,
            allow_unknown_config=False,
        )

    @app.get("/api/modules/metadata")
    async def get_modules_metadata() -> dict[str, Any]:
        policy = _module_policy()
        schedules = [
            {
                "key": key,
                "cron": cfg.cron,
                "timezone": cfg.timezone,
            }
            for key, cfg in sorted(policy.schedules.items())
        ]
        templates = []
        for definition in list_module_definitions():
            default_env = default_module_envelope(definition.module_id)
            templates.append(
                {
                    "module_id": definition.module_id,
                    "display_name": definition.display_name,
                    "description": definition.description,
                    "icon": definition.icon,
                    "category": definition.category,
                    "default_envelope": default_env.model_dump(exclude_none=False),
                    "field_meta": _module_field_meta(definition.module_id),
                    "write_class_keys": list(definition.write_class_keys),
                    **_module_contracts(definition.module_id, default_env.config),
                }
            )

        return {
            "schedules": schedules,
            "routing_targets": sorted(_module_routing_targets()),
            "risk_presets": [
                {"name": "conservative", "green_max_score": 20, "yellow_max_score": 50, "red_min_score": 51},
                {"name": "balanced", "green_max_score": 30, "yellow_max_score": 70, "red_min_score": 71},
                {"name": "aggressive", "green_max_score": 40, "yellow_max_score": 80, "red_min_score": 81},
            ],
            "module_templates": templates,
            "precedence_matrix": MODULE_PRECEDENCE_MATRIX,
        }

    @app.get("/api/modules")
    async def list_modules() -> dict[str, Any]:
        store = _module_store()
        modules = []
        for envelope in store.list_modules():
            definition = get_module_definition(envelope.module_id)
            modules.append(
                {
                    **envelope.model_dump(exclude_none=False),
                    "status": _module_status(envelope),
                    "write_class_keys": list(definition.write_class_keys),
                    **_module_contracts(envelope.module_id, envelope.config),
                }
            )
        return {"modules": modules}

    @app.get("/api/modules/{module_id}")
    async def get_module(module_id: str) -> dict[str, Any]:
        store = _module_store()
        try:
            envelope = store.load_module(module_id)
            definition = get_module_definition(module_id)
        except ModuleValidationError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc
        return {
            "envelope": envelope.model_dump(exclude_none=False),
            "status": _module_status(envelope),
            "field_meta": _module_field_meta(module_id),
            "write_class_keys": list(definition.write_class_keys),
            **_module_contracts(module_id, envelope.config),
        }

    @app.get("/api/modules/{module_id}/effective")
    async def get_module_effective(module_id: str) -> dict[str, Any]:
        store = _module_store()
        try:
            envelope = store.load_module(module_id)
        except ModuleValidationError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc
        return {
            "module_id": module_id,
            "envelope": envelope.model_dump(exclude_none=False),
            "module_rules": _module_rule_names(module_id) if envelope.enabled else [],
            "precedence_matrix": MODULE_PRECEDENCE_MATRIX,
            **_module_contracts(module_id, envelope.config),
        }

    @app.put("/api/modules/{module_id}")
    async def save_module(module_id: str, request: Request) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="module_write",
        )
        store = _module_store()
        try:
            payload = await request.json()
            put_payload = ModulePutPayload.model_validate(payload)
        except Exception as exc:
            raise HTTPException(status_code=422, detail=f"Invalid full-replace module payload: {exc}") from exc

        try:
            previous = store.load_module(module_id)
            result = store.save_module(
                module_id,
                put_payload,
                actor=_module_actor(request),
                schedule_keys=_module_schedule_keys(),
                routing_targets=_module_routing_targets(),
                compile_policy_fn=lambda modules: compile_runtime_policy(
                    Path(app.state.state_dir),
                    app.state.policy,
                    modules,
                ).policy_hash,
            )
            runtime_revision = bump_runtime_policy_revision(Path(app.state.state_dir))
            write_runtime_reload_signal(Path(app.state.state_dir), revision=runtime_revision)
        except RevisionConflictError as exc:
            raise HTTPException(status_code=409, detail=str(exc)) from exc
        except (ModuleValidationError, ModuleCompileError) as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc

        semantic_diff = diff_module_revision(module_id, previous, result.envelope)
        config_receipt_id = _write_module_config_receipt(
            Path(app.state.state_dir),
            module_id=module_id,
            actor=_module_actor(request),
            old_envelope=previous,
            new_envelope=result.envelope,
            semantic_diff=semantic_diff.model_dump(exclude_none=False),
            action="save",
        )
        return {
            "envelope": result.envelope.model_dump(exclude_none=False),
            "revision_hash": result.envelope.revision_hash,
            "policy_hash": result.policy_hash,
            "config_receipt_id": config_receipt_id,
        }

    @app.post("/api/modules/{module_id}/validate")
    async def validate_module(module_id: str, request: Request) -> dict[str, Any]:
        payload = await request.json()
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        proposed = payload.get("proposed") if "proposed" in payload else payload
        if not isinstance(proposed, dict):
            raise HTTPException(status_code=400, detail="proposed must be an object")
        store = _module_store()
        current = store.load_module(module_id)
        try:
            normalized = _candidate_envelope_from_payload(module_id, proposed, current=current)
        except ModuleValidationError as exc:
            return {"valid": False, "errors": [str(exc)]}
        return {
            "valid": True,
            "errors": [],
            "normalized": normalized.model_dump(exclude_none=False),
        }

    @app.get("/api/modules/{module_id}/yaml")
    async def get_module_yaml(module_id: str) -> PlainTextResponse:
        try:
            store = _module_store()
            envelope = store.load_module(module_id)
        except ModuleValidationError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc
        try:
            import yaml

            text = yaml.safe_dump(
                envelope.model_dump(exclude_none=False),
                sort_keys=False,
                default_flow_style=False,
                allow_unicode=False,
            )
        except Exception:
            text = json.dumps(envelope.model_dump(exclude_none=False), indent=2, sort_keys=True)
        return PlainTextResponse(text)

    @app.get("/api/modules/{module_id}/samples")
    async def get_module_samples(module_id: str) -> dict[str, Any]:
        try:
            get_module_definition(module_id)
        except ModuleValidationError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc
        return {
            "module_id": module_id,
            "sample_sets": _module_sample_sets(module_id),
        }

    def _dealdesk_parse_record_ids(raw_ids: Any) -> list[str]:
        if isinstance(raw_ids, str):
            return [entry.strip() for entry in raw_ids.split(",") if entry.strip()]
        if isinstance(raw_ids, list):
            return [str(entry).strip() for entry in raw_ids if str(entry).strip()]
        return []

    def _dealdesk_history_bucket(
        values: list[str | None],
        *,
        limit: int = 5,
    ) -> list[dict[str, Any]]:
        counts: dict[str, int] = {}
        labels: dict[str, str] = {}
        for raw_value in values:
            cleaned = str(raw_value or "").strip()
            if not cleaned:
                cleaned = "unknown"
            key = cleaned.lower()
            counts[key] = counts.get(key, 0) + 1
            labels.setdefault(key, cleaned)
        ranked = sorted(counts.items(), key=lambda item: (-item[1], item[0]))
        return [
            {"key": key, "label": labels[key], "count": count}
            for key, count in ranked[: max(1, int(limit))]
        ]

    def _dealdesk_representative_history(rows: list[dict[str, Any]], *, limit: int = 5) -> list[dict[str, Any]]:
        scored: list[dict[str, Any]] = []
        for row in rows:
            try:
                normalized = normalize_deal_record(row)
            except Exception:
                continue
            completeness = 0
            if normalized.motion_id != "unknown":
                completeness += 2
            if normalized.segment:
                completeness += 1
            if normalized.product_family:
                completeness += 2
            if normalized.payment_terms:
                completeness += 1
            if normalized.term_months is not None:
                completeness += 1
            if normalized.discount_pct is not None:
                completeness += 1
            if row.get("Amount") not in (None, ""):
                completeness += 1
            scored.append(
                {
                    "opportunity_id": str(row.get("Id") or "").strip(),
                    "opportunity_name": str(row.get("Name") or "").strip() or "Unnamed opportunity",
                    "close_date": row.get("CloseDate"),
                    "amount": row.get("Amount"),
                    "motion_id": normalized.motion_id,
                    "segment": normalized.segment,
                    "product_family": normalized.product_family,
                    "payment_terms": normalized.payment_terms,
                    "completeness_score": completeness,
                }
            )
        scored.sort(
            key=lambda item: (
                int(item["completeness_score"]),
                str(item.get("close_date") or ""),
                str(item.get("opportunity_id") or ""),
            ),
            reverse=True,
        )
        return scored[: max(1, int(limit))]

    def _dealdesk_normalized_history(rows: list[dict[str, Any]], *, limit: int = 100) -> list[Any]:
        normalized_rows: list[Any] = []
        for row in rows[: max(1, int(limit))]:
            try:
                normalized_rows.append(normalize_deal_record(row))
            except Exception:
                continue
        return normalized_rows

    @app.post("/api/modules/{module_id}/quote-readiness-probe")
    async def module_quote_readiness_probe(module_id: str, request: Request) -> dict[str, Any]:
        if module_id != "deal_desk":
            raise HTTPException(status_code=404, detail=f"Quote readiness probe is not supported for module {module_id}")

        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        opportunity_id = str(
            payload.get("opportunity_id")
            or payload.get("record_id")
            or payload.get("readiness_opportunity_id")
            or ""
        ).strip()
        if not opportunity_id:
            raise HTTPException(status_code=400, detail="opportunity_id is required")

        org_alias = str(payload.get("org_alias") or "").strip() or None
        quote_request = payload.get("quote_request") if isinstance(payload.get("quote_request"), dict) else {}
        force_refresh = bool(payload.get("force_refresh"))

        registry = get_default_registry()
        tool = registry.get_tool("dealdesk.quote.readiness")
        handler = registry.get_handler("dealdesk.quote.readiness")
        if tool is None or handler is None:
            raise HTTPException(status_code=503, detail="Deal Desk quote readiness tool is not available in this runtime.")

        step = PlanStep(
            step_id=f"dealdesk_probe_{opportunity_id}",
            tool=tool.name,
            args={
                "opportunity_id": opportunity_id,
                "org_alias": org_alias,
                "quote_request": quote_request,
                "force_refresh": force_refresh,
                "state_dir": str(app.state.state_dir),
            },
            risk_level=0,
        )

        try:
            result = handler(step, tool)
        except HTTPException:
            raise
        except Exception as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc

        after = result.get("after") if isinstance(result, dict) else {}
        if not isinstance(after, dict):
            after = {}
        return {
            "module_id": module_id,
            "opportunity_id": opportunity_id,
            "org_alias": org_alias,
            **after,
        }

    @app.post("/api/modules/{module_id}/seller/deal-health")
    async def module_dealdesk_seller_deal_health(module_id: str, request: Request) -> dict[str, Any]:
        if module_id != "deal_desk":
            raise HTTPException(status_code=404, detail=f"Seller deal health is not supported for module {module_id}")

        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        opportunity_id = str(payload.get("opportunity_id") or payload.get("record_id") or "").strip()
        if not opportunity_id:
            raise HTTPException(status_code=400, detail="opportunity_id is required")

        org_alias = str(payload.get("org_alias") or "").strip() or None
        force_refresh = bool(payload.get("force_refresh"))

        try:
            summary = get_seller_deal_health_summary(
                opportunity_id=opportunity_id,
                org_alias=org_alias,
                state_dir=str(app.state.state_dir),
                force_refresh=force_refresh,
            )
        except HTTPException:
            raise
        except Exception as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc

        return {"module_id": module_id, **summary.to_dict()}

    @app.post("/api/modules/{module_id}/seller/lookup-deal")
    async def module_dealdesk_seller_lookup_deal(module_id: str, request: Request) -> dict[str, Any]:
        if module_id != "deal_desk":
            raise HTTPException(status_code=404, detail=f"Seller deal lookup is not supported for module {module_id}")

        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        query = str(payload.get("query") or payload.get("deal_query") or "").strip()
        if not query:
            raise HTTPException(status_code=400, detail="query is required")

        org_alias = str(payload.get("org_alias") or "").strip() or None
        try:
            limit = int(payload.get("limit") or 5)
        except Exception:
            limit = 5

        try:
            result = lookup_seller_active_deals(
                query=query,
                org_alias=org_alias,
                limit=limit,
            )
        except HTTPException:
            raise
        except Exception as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc

        return {"module_id": module_id, **result.to_dict()}

    @app.post("/api/modules/{module_id}/close-certification")
    async def module_dealdesk_close_certification(module_id: str, request: Request) -> dict[str, Any]:
        if module_id != "deal_desk":
            raise HTTPException(status_code=404, detail=f"Close certification is not supported for module {module_id}")

        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        opportunity_id = str(payload.get("opportunity_id") or payload.get("record_id") or "").strip()
        if not opportunity_id:
            raise HTTPException(status_code=400, detail="opportunity_id is required")

        org_alias = str(payload.get("org_alias") or "").strip() or None
        entrypoint = str(payload.get("entrypoint") or "submitted_for_close").strip() or "submitted_for_close"
        force_refresh = bool(payload.get("force_refresh"))
        actor_ctx = _resolve_actor_context(request)

        try:
            result = run_close_certification(
                opportunity_id=opportunity_id,
                entrypoint=entrypoint,
                policy=apply_policy_overlays(app.state.policy, Path(app.state.state_dir)),
                org_alias=org_alias,
                state_dir=str(app.state.state_dir),
                force_refresh=force_refresh,
            )
        except HTTPException:
            raise
        except Exception as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc

        response_payload = {"module_id": module_id, **result.to_dict()}
        if bool(getattr(result, "progression_approval_required", False)):
            routing = await asyncio.to_thread(
                _route_dealdesk_close_progression_for_approval,
                result=result,
                actor_ctx=actor_ctx,
            )
            response_payload["approval_routing"] = routing
        elif bool(getattr(result, "post_close_approval_required", False)):
            routing = await asyncio.to_thread(
                _route_dealdesk_post_close_trigger_for_approval,
                result=result,
                actor_ctx=actor_ctx,
            )
            response_payload["approval_routing"] = routing
        return response_payload

    @app.post("/api/modules/{module_id}/seller/submit-for-close")
    async def module_dealdesk_submit_for_close(module_id: str, request: Request) -> dict[str, Any]:
        if module_id != "deal_desk":
            raise HTTPException(status_code=404, detail=f"Close review intake is not supported for module {module_id}")

        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        opportunity_id = str(payload.get("opportunity_id") or payload.get("record_id") or "").strip()
        if not opportunity_id:
            raise HTTPException(status_code=400, detail="opportunity_id is required")

        org_alias = str(payload.get("org_alias") or "").strip() or None
        force_refresh = bool(payload.get("force_refresh"))
        actor_ctx = _resolve_actor_context(request)

        try:
            submission = submit_deal_for_close_review(
                opportunity_id=opportunity_id,
                policy=apply_policy_overlays(app.state.policy, Path(app.state.state_dir)),
                org_alias=org_alias,
                state_dir=str(app.state.state_dir),
                force_refresh=force_refresh,
            )
        except HTTPException:
            raise
        except Exception as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc

        response_payload = {"module_id": module_id, **submission.to_dict()}
        if bool(getattr(submission, "progression_approval_required", False)):
            routing = await asyncio.to_thread(
                _route_dealdesk_close_progression_for_approval,
                result=submission,
                actor_ctx=actor_ctx,
            )
            response_payload["approval_routing"] = routing
        elif bool(getattr(submission, "post_close_approval_required", False)):
            routing = await asyncio.to_thread(
                _route_dealdesk_post_close_trigger_for_approval,
                result=submission,
                actor_ctx=actor_ctx,
            )
            response_payload["approval_routing"] = routing
        return response_payload

    @app.post("/api/modules/{module_id}/seller/fix-missing-field")
    async def module_dealdesk_fix_missing_field(module_id: str, request: Request) -> dict[str, Any]:
        if module_id != "deal_desk":
            raise HTTPException(status_code=404, detail=f"Seller field fixes are not supported for module {module_id}")

        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        opportunity_id = str(payload.get("opportunity_id") or payload.get("record_id") or "").strip()
        field_name = str(payload.get("field_name") or "").strip()
        field_value = str(payload.get("field_value") or "").strip()
        if not opportunity_id:
            raise HTTPException(status_code=400, detail="opportunity_id is required")
        if not field_name:
            raise HTTPException(status_code=400, detail="field_name is required")
        if not field_value:
            raise HTTPException(status_code=400, detail="field_value is required")

        org_alias = str(payload.get("org_alias") or "").strip() or None
        actor_ctx = _resolve_actor_context(request)

        try:
            result = apply_seller_missing_field_fix(
                opportunity_id=opportunity_id,
                field_name=field_name,
                field_value=field_value,
                org_alias=org_alias,
                state_dir=str(app.state.state_dir),
                policy=apply_policy_overlays(app.state.policy, Path(app.state.state_dir)),
            )
        except HTTPException:
            raise
        except Exception as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc

        response_payload = {"module_id": module_id, **result.to_dict()}
        if bool(getattr(result, "approval_required", False)):
            routing = await asyncio.to_thread(
                _route_dealdesk_field_fix_for_approval,
                result=result,
                actor_ctx=actor_ctx,
            )
            response_payload["approval_routing"] = routing
        return response_payload

    @app.post("/api/modules/{module_id}/seller/add-contact-role")
    async def module_dealdesk_add_contact_role(module_id: str, request: Request) -> dict[str, Any]:
        if module_id != "deal_desk":
            raise HTTPException(status_code=404, detail=f"Seller contact adds are not supported for module {module_id}")

        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        opportunity_id = str(payload.get("opportunity_id") or payload.get("record_id") or "").strip()
        contact_reference = str(payload.get("contact_reference") or payload.get("contact") or "").strip()
        role_name = str(payload.get("role_name") or payload.get("role") or "").strip()
        if not opportunity_id:
            raise HTTPException(status_code=400, detail="opportunity_id is required")
        if not contact_reference:
            raise HTTPException(status_code=400, detail="contact_reference is required")
        if not role_name:
            raise HTTPException(status_code=400, detail="role_name is required")

        org_alias = str(payload.get("org_alias") or "").strip() or None
        actor_ctx = _resolve_actor_context(request)

        try:
            result = apply_seller_contact_role_add(
                opportunity_id=opportunity_id,
                contact_reference=contact_reference,
                role_name=role_name,
                org_alias=org_alias,
                state_dir=str(app.state.state_dir),
                policy=apply_policy_overlays(app.state.policy, Path(app.state.state_dir)),
            )
        except HTTPException:
            raise
        except Exception as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc

        response_payload = {"module_id": module_id, **result.to_dict()}
        if bool(getattr(result, "approval_required", False)):
            routing = await asyncio.to_thread(
                _route_dealdesk_contact_role_for_approval,
                result=result,
                actor_ctx=actor_ctx,
            )
            response_payload["approval_routing"] = routing
        return response_payload

    @app.post("/api/modules/{module_id}/closed-won-history")
    async def module_closed_won_history(module_id: str, request: Request) -> dict[str, Any]:
        if module_id != "deal_desk":
            raise HTTPException(status_code=404, detail=f"Closed-won history scan is not supported for module {module_id}")

        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        org_alias = str(payload.get("org_alias") or "").strip() or None
        if not org_alias:
            raise HTTPException(status_code=400, detail="org_alias is required")

        try:
            lookback_months = int(payload.get("lookback_months") or 12)
        except Exception:
            lookback_months = 12
        lookback_months = max(1, min(24, lookback_months))
        requested_gold_standard_ids = _dealdesk_parse_record_ids(
            payload.get("gold_standard_ids")
            or payload.get("reference_record_ids")
            or payload.get("record_ids")
        )
        lookback_days = lookback_months * 30

        try:
            historical_rows = fetch_historical_opportunities(
                org_alias=org_alias,
                lookback_days=lookback_days,
                limit=400,
            )
        except Exception as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc

        normalized_history = _dealdesk_normalized_history(historical_rows, limit=120)
        representative_deals = _dealdesk_representative_history(historical_rows, limit=5)
        suggested_gold_standard_ids = [
            str(item.get("opportunity_id") or "").strip()
            for item in representative_deals
            if str(item.get("opportunity_id") or "").strip()
        ]
        motion_summary = _dealdesk_history_bucket(
            [item.motion_id for item in normalized_history],
            limit=5,
        )
        product_family_summary = _dealdesk_history_bucket(
            [item.product_family for item in normalized_history],
            limit=5,
        )
        payment_terms_summary = _dealdesk_history_bucket(
            [item.payment_terms for item in normalized_history],
            limit=5,
        )

        historical_ids = {
            str(row.get("Id") or "").strip()
            for row in historical_rows
            if str(row.get("Id") or "").strip()
        }
        matched_gold_standard_ids = [
            record_id for record_id in requested_gold_standard_ids if record_id in historical_ids
        ]
        message = (
            f"Scanned {len(historical_rows)} closed opportunities from the last {lookback_months} month(s). "
            f"Suggested {len(suggested_gold_standard_ids)} gold-standard exemplars."
        )
        return {
            "module_id": module_id,
            "org_alias": org_alias,
            "lookback_months": lookback_months,
            "scanned_record_count": len(historical_rows),
            "requested_gold_standard_ids": requested_gold_standard_ids,
            "matched_gold_standard_ids": matched_gold_standard_ids,
            "suggested_config": {"reference_record_ids": suggested_gold_standard_ids} if suggested_gold_standard_ids else {},
            "motion_summary": motion_summary,
            "product_family_summary": product_family_summary,
            "payment_terms_summary": payment_terms_summary,
            "representative_deals": representative_deals,
            "message": message,
        }

    @app.post("/api/modules/{module_id}/reference-scan")
    async def module_reference_scan(module_id: str, request: Request) -> dict[str, Any]:
        try:
            definition = get_module_definition(module_id)
        except ModuleValidationError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc

        payload = await request.json()
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        raw_record_ids = payload.get("record_ids")
        if raw_record_ids is None:
            raw_record_ids = payload.get("reference_record_ids")

        requested_ids: list[str] = []
        if isinstance(raw_record_ids, str):
            requested_ids = [entry.strip() for entry in raw_record_ids.split(",") if entry.strip()]
        elif isinstance(raw_record_ids, list):
            requested_ids = [str(entry).strip() for entry in raw_record_ids if str(entry).strip()]
        elif raw_record_ids is None:
            requested_ids = []
        else:
            raise HTTPException(status_code=400, detail="record_ids must be a comma-separated string or array")

        normalized_lookup: dict[str, str] = {}
        for entry in requested_ids:
            normalized = _normalize_reference_record_id(entry)
            if not normalized:
                continue
            normalized_lookup.setdefault(normalized, entry)

        try:
            max_matches = int(payload.get("max_matches", 25))
        except Exception:
            max_matches = 25
        max_matches = max(1, min(100, max_matches))

        if not normalized_lookup:
            return {
                "module_id": module_id,
                "requested_record_ids": requested_ids,
                "matched_records": [],
                "matched_policy_hashes": [],
                "suggested_config": {},
                "suggestion_support": {},
                "message": "No reference IDs provided.",
            }

        matched_rows: list[dict[str, Any]] = []
        for sample_set in _module_sample_sets(module_id):
            sample_set_id = str(sample_set.get("sample_set_id") or "")
            source = str(sample_set.get("source") or "")
            label = str(sample_set.get("label") or "")
            raw_samples = sample_set.get("samples")
            if not isinstance(raw_samples, list):
                continue
            for sample in raw_samples:
                if not isinstance(sample, dict):
                    continue
                sample_ids = _extract_reference_ids_from_sample(sample)
                overlap = sorted(sample_ids.intersection(normalized_lookup.keys()))
                if not overlap:
                    continue
                matched_rows.append(
                    {
                        "sample_set_id": sample_set_id,
                        "source": source,
                        "label": label,
                        "matched_record_ids": [normalized_lookup[item] for item in overlap],
                        "run_id": str(sample.get("_receipt_run_id") or ""),
                        "outcome": str(sample.get("_receipt_outcome") or ""),
                        "policy_hash": str(sample.get("_receipt_policy_hash") or ""),
                        "sample": _redacted_reference_sample(sample),
                    }
                )
                if len(matched_rows) >= max_matches:
                    break
            if len(matched_rows) >= max_matches:
                break

        matched_policy_hashes = sorted(
            {
                str(row.get("policy_hash") or "").strip()
                for row in matched_rows
                if isinstance(row, dict) and str(row.get("policy_hash") or "").strip()
            }
        )

        suggestion_entries: list[ModuleEnvelope] = []
        if matched_policy_hashes:
            history_entries = _module_store().list_module_history(module_id)
            suggestion_entries = [
                entry for entry in history_entries if isinstance(entry.policy_hash, str) and entry.policy_hash in matched_policy_hashes
            ]

        known_config_fields = sorted(definition.config_model.model_fields.keys())
        suggested_config: dict[str, Any] = {}
        suggestion_support: dict[str, Any] = {}

        if suggestion_entries:
            for field_name in known_config_fields:
                if field_name == "reference_record_ids":
                    continue
                counts: dict[str, int] = {}
                value_by_signature: dict[str, Any] = {}
                for entry in suggestion_entries:
                    value = entry.config.get(field_name)
                    signature = _stable_json_value(value)
                    counts[signature] = counts.get(signature, 0) + 1
                    value_by_signature[signature] = value
                if not counts:
                    continue
                best_signature, best_count = sorted(counts.items(), key=lambda item: (-item[1], item[0]))[0]
                suggested_config[field_name] = value_by_signature[best_signature]
                suggestion_support[field_name] = {
                    "supporting_history_entries": best_count,
                    "total_history_entries": len(suggestion_entries),
                    "source": "module_history_by_policy_hash",
                }

        if not suggested_config and matched_rows:
            for field_name in known_config_fields:
                if field_name == "reference_record_ids":
                    continue
                counts: dict[str, int] = {}
                value_by_signature: dict[str, Any] = {}
                for row in matched_rows:
                    sample = row.get("sample")
                    if not isinstance(sample, dict) or field_name not in sample:
                        continue
                    value = sample.get(field_name)
                    signature = _stable_json_value(value)
                    counts[signature] = counts.get(signature, 0) + 1
                    value_by_signature[signature] = value
                if not counts:
                    continue
                best_signature, best_count = sorted(counts.items(), key=lambda item: (-item[1], item[0]))[0]
                suggested_config[field_name] = value_by_signature[best_signature]
                suggestion_support[field_name] = {
                    "supporting_samples": best_count,
                    "total_matched_samples": len(matched_rows),
                    "source": "matched_sample_fields",
                }

        message = "No matching historical records found for provided IDs."
        if matched_rows and suggested_config:
            message = "Reference matches found and config heuristics suggested."
        elif matched_rows:
            message = "Reference matches found, but no safe config suggestion could be derived."

        return {
            "module_id": module_id,
            "requested_record_ids": requested_ids,
            "matched_records": matched_rows,
            "matched_policy_hashes": matched_policy_hashes,
            "suggested_config": suggested_config,
            "suggestion_support": suggestion_support,
            "message": message,
        }

    @app.post("/api/modules/{module_id}/simulate")
    async def simulate_module(module_id: str, request: Request) -> dict[str, Any]:
        payload = await request.json()
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        samples = payload.get("samples")
        if samples is None:
            samples = _module_default_simulation_samples(module_id)
        proposed = payload.get("proposed")
        store = _module_store()
        current = store.load_module(module_id)
        try:
            envelope = (
                _candidate_envelope_from_payload(module_id, proposed, current=current)
                if isinstance(proposed, dict)
                else current
            )
            simulation = simulate_module_on_samples(module_id, envelope, samples, _module_policy())
        except ModuleValidationError as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc

        return {
            **simulation.model_dump(exclude_none=False),
            "precedence_matrix": MODULE_PRECEDENCE_MATRIX,
        }

    @app.post("/api/modules/{module_id}/diff")
    async def module_diff(module_id: str, request: Request) -> dict[str, Any]:
        payload = await request.json()
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        proposed = payload.get("proposed")
        if not isinstance(proposed, dict):
            raise HTTPException(status_code=422, detail="proposed must include a full envelope payload")
        store = _module_store()
        current = store.load_module(module_id)
        try:
            candidate = _candidate_envelope_from_payload(module_id, proposed, current=current)
            diff = diff_module_revision(module_id, current, candidate)
        except ModuleValidationError as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc
        return diff.model_dump(exclude_none=False)

    @app.get("/api/modules/{module_id}/history")
    async def module_history(module_id: str) -> dict[str, Any]:
        store = _module_store()
        try:
            history = store.list_module_history(module_id)
        except ModuleValidationError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc
        entries = []
        for envelope in reversed(history):
            entries.append(
                {
                    "module_id": envelope.module_id,
                    "revision_hash": envelope.revision_hash,
                    "policy_hash": envelope.policy_hash,
                    "updated_at": envelope.updated_at,
                    "updated_by": envelope.updated_by,
                }
            )
        return {"module_id": module_id, "history": entries}

    @app.post("/api/modules/{module_id}/rollback/{revision_hash}")
    async def rollback_module_revision(module_id: str, revision_hash: str, request: Request) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="module_rollback",
        )
        store = _module_store()
        try:
            current = store.load_module(module_id)
            history = store.list_module_history(module_id)
        except ModuleValidationError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc

        target = next((entry for entry in history if entry.revision_hash == revision_hash), None)
        if target is None:
            raise HTTPException(status_code=404, detail=f"Revision not found: {revision_hash}")

        payload = ModulePutPayload(
            revision_hash=current.revision_hash or "",
            enabled=target.enabled,
            schedule_ref=target.schedule_ref,
            exception_routing=target.exception_routing,
            risk_tiers=target.risk_tiers,
            config=target.config,
        )
        try:
            result = store.save_module(
                module_id,
                payload,
                actor=_module_actor(request),
                schedule_keys=_module_schedule_keys(),
                routing_targets=_module_routing_targets(),
                compile_policy_fn=lambda modules: compile_runtime_policy(
                    Path(app.state.state_dir),
                    app.state.policy,
                    modules,
                ).policy_hash,
            )
            runtime_revision = bump_runtime_policy_revision(Path(app.state.state_dir))
            write_runtime_reload_signal(Path(app.state.state_dir), revision=runtime_revision)
        except RevisionConflictError as exc:
            raise HTTPException(status_code=409, detail=str(exc)) from exc
        except (ModuleValidationError, ModuleCompileError) as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc

        semantic_diff = diff_module_revision(module_id, current, result.envelope)
        config_receipt_id = _write_module_config_receipt(
            Path(app.state.state_dir),
            module_id=module_id,
            actor=_module_actor(request),
            old_envelope=current,
            new_envelope=result.envelope,
            semantic_diff=semantic_diff.model_dump(exclude_none=False),
            action="rollback",
        )
        return {
            "module_id": module_id,
            "rolled_back_to_revision": revision_hash,
            "new_head_revision_hash": result.envelope.revision_hash,
            "policy_hash": result.policy_hash,
            "config_receipt_id": config_receipt_id,
        }

    @app.get("/api/runtime/policy")
    async def runtime_policy_status() -> dict[str, Any]:
        state_dir = Path(app.state.state_dir)
        compiled_hash = _read_runtime_compiled_hash(state_dir)
        daemon_runtime = read_runtime_status(state_dir)
        daemon_active_hash = daemon_runtime.get("daemon_active_policy_hash")
        marker_revision = read_runtime_policy_revision(state_dir)
        daemon_revision_raw = daemon_runtime.get("runtime_policy_revision")
        daemon_revision = int(daemon_revision_raw) if isinstance(daemon_revision_raw, int) else 0
        needs_reload = marker_revision != daemon_revision
        if compiled_hash and daemon_active_hash and compiled_hash != daemon_active_hash:
            needs_reload = True
        return {
            "compiled_policy_hash": compiled_hash,
            "daemon_active_policy_hash": daemon_active_hash,
            "runtime_policy_revision": marker_revision,
            "daemon_runtime_policy_revision": daemon_revision,
            "needs_reload": needs_reload,
        }

    @app.post("/api/runtime/policy/reload")
    async def runtime_policy_reload(request: Request) -> dict[str, Any]:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="policy_reload",
        )
        state_dir = Path(app.state.state_dir)
        revision = bump_runtime_policy_revision(state_dir)
        signal_path = write_runtime_reload_signal(state_dir, revision=revision)
        return {
            "ok": True,
            "runtime_policy_revision": revision,
            "reload_signal_path": str(signal_path),
        }

    @app.get("/api/modules/metrics/pilot")
    async def module_pilot_metrics() -> dict[str, Any]:
        return _compute_pilot_metrics(Path(app.state.state_dir))

    @app.get("/api/modules/metrics/gates")
    async def module_gate_metrics(
        lookback_days: int = Query(30, ge=1, le=3650),
    ) -> dict[str, Any]:
        from .gate_metrics import collect_gate_metrics

        return collect_gate_metrics(
            Path(app.state.state_dir),
            lookback_days=lookback_days,
        )

    @app.get("/api/modules/metrics/dealdesk")
    async def module_dealdesk_metrics(
        lookback_days: int = Query(30, ge=1, le=3650),
    ) -> dict[str, Any]:
        from g_gremlin.dealdesk.metrics import collect_dealdesk_metrics

        return collect_dealdesk_metrics(
            Path(app.state.state_dir),
            lookback_days=lookback_days,
        )

    @app.get("/api/admin/dealdesk/metrics")
    async def admin_dealdesk_metrics(
        lookback_days: int = Query(30, ge=1, le=3650),
        baseline_days: int = Query(7, ge=1, le=30),
        history_limit: int = Query(14, ge=1, le=90),
    ) -> dict[str, Any]:
        from g_gremlin.dealdesk.metrics import build_dealdesk_metrics_drilldown

        return build_dealdesk_metrics_drilldown(
            Path(app.state.state_dir),
            lookback_days=lookback_days,
            baseline_days=baseline_days,
            history_limit=history_limit,
        )

    @app.get("/api/admin/dealdesk/reviews")
    async def admin_dealdesk_reviews(
        limit: int = Query(25, ge=1, le=200),
        overall_disposition: str | None = Query(None),
        entrypoint: str | None = Query(None),
        q: str | None = Query(None),
        org_alias: str | None = Query(None),
    ) -> dict[str, Any]:
        from g_gremlin.dealdesk.review import build_close_review_index

        payload = build_close_review_index(
            Path(app.state.state_dir),
            limit=limit,
            overall_disposition=overall_disposition,
            entrypoint=entrypoint,
            query=q,
            org_alias=org_alias,
        )
        return payload.to_dict()

    @app.get("/api/admin/dealdesk/reviews/{run_id}")
    async def admin_dealdesk_review_detail(
        run_id: str,
        org_alias: str | None = Query(None),
    ) -> dict[str, Any]:
        from g_gremlin.dealdesk.review import load_close_review_detail

        try:
            payload = load_close_review_detail(
                Path(app.state.state_dir),
                run_id=run_id,
                org_alias=org_alias,
            )
        except KeyError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc
        return payload.to_dict()

    @app.get("/api/admin/dealdesk/triggers/registry")
    async def admin_dealdesk_trigger_registry(
        crm_org_id: str | None = Query(None),
        org_alias: str | None = Query(None),
    ) -> dict[str, Any]:
        from g_gremlin.dealdesk.review import load_post_close_trigger_registry

        payload = load_post_close_trigger_registry(
            Path(app.state.state_dir),
            crm_org_id=crm_org_id,
            org_alias=org_alias,
        )
        return payload.to_dict()

    @app.get("/api/admin/dealdesk/capabilities")
    async def admin_dealdesk_capabilities(
        crm_org_id: str | None = Query(None),
        org_alias: str | None = Query(None),
    ) -> dict[str, Any]:
        from g_gremlin.dealdesk.review import load_dealdesk_capability_summary

        payload = load_dealdesk_capability_summary(
            Path(app.state.state_dir),
            crm_org_id=crm_org_id,
            org_alias=org_alias,
        )
        return payload.to_dict()

    @app.get("/api/admin/dealdesk/quote-intelligence")
    async def admin_dealdesk_quote_intelligence(
        crm_org_id: str | None = Query(None),
        org_alias: str | None = Query(None),
        stage: str | None = Query(None),
        amount: float | None = Query(None),
    ) -> dict[str, Any]:
        from g_gremlin.dealdesk.quote_intelligence import load_dealdesk_quote_intelligence

        payload = load_dealdesk_quote_intelligence(
            Path(app.state.state_dir),
            crm_org_id=crm_org_id,
            org_alias=org_alias,
            stage=stage,
            amount=amount,
        )
        return payload.to_dict()

    @app.post("/api/admin/dealdesk/capabilities/refresh")
    async def admin_dealdesk_refresh_capabilities(request: Request) -> dict[str, Any]:
        from g_gremlin.dealdesk.capability_refresh import (
            RefreshDealDeskCapabilitiesRequestV1,
            refresh_dealdesk_capability_snapshot,
        )

        actor = _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="dealdesk_capability_refresh",
        )
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        try:
            body = RefreshDealDeskCapabilitiesRequestV1.model_validate(payload)
        except Exception as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        try:
            result = refresh_dealdesk_capability_snapshot(
                Path(app.state.state_dir),
                request=body,
                actor_id=actor.actor_id,
                actor_email=actor.mapped_email,
            )
        except RuntimeError as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        return result.to_dict()

    @app.post("/api/admin/dealdesk/capabilities/promote-draft")
    async def admin_dealdesk_promote_capability_draft(request: Request) -> dict[str, Any]:
        from g_gremlin.dealdesk.capability_promotion import (
            PromoteDealDeskCapabilityDraftRequestV1,
            promote_dealdesk_capability_draft,
        )

        actor = _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="dealdesk_capability_draft_promotion",
        )
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        try:
            body = PromoteDealDeskCapabilityDraftRequestV1.model_validate(payload)
        except Exception as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        try:
            result = promote_dealdesk_capability_draft(
                Path(app.state.state_dir),
                request=body,
                actor_id=actor.actor_id,
                actor_email=actor.mapped_email,
            )
        except KeyError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc
        except RuntimeError as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        return result.to_dict()

    @app.post("/api/admin/dealdesk/triggers/{trigger_key}/approve-preview")
    async def admin_dealdesk_approve_trigger_preview(
        trigger_key: str,
        request: Request,
    ) -> dict[str, Any]:
        from g_gremlin.dealdesk.mutations import (
            ApprovePostCloseTriggerPreviewRequestV1,
            approve_post_close_trigger_preview,
        )

        actor = _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="dealdesk_trigger_preview_approval",
        )
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        try:
            body = ApprovePostCloseTriggerPreviewRequestV1.model_validate(payload)
        except Exception as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        try:
            result = approve_post_close_trigger_preview(
                Path(app.state.state_dir),
                trigger_key=trigger_key,
                request=body,
                actor_id=actor.actor_id,
                actor_email=actor.mapped_email,
            )
        except KeyError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc
        except RuntimeError as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        return result.to_dict()

    @app.post("/api/admin/dealdesk/triggers/{trigger_key}/set-enabled")
    async def admin_dealdesk_set_trigger_enabled(
        trigger_key: str,
        request: Request,
    ) -> dict[str, Any]:
        from g_gremlin.dealdesk.mutations import (
            SetPostCloseTriggerEnabledRequestV1,
            set_post_close_trigger_enabled,
        )

        actor = _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="dealdesk_trigger_enabled_update",
        )
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        try:
            body = SetPostCloseTriggerEnabledRequestV1.model_validate(payload)
        except Exception as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        try:
            result = set_post_close_trigger_enabled(
                Path(app.state.state_dir),
                trigger_key=trigger_key,
                request=body,
                actor_id=actor.actor_id,
                actor_email=actor.mapped_email,
            )
        except KeyError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc
        except RuntimeError as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        return result.to_dict()

    @app.post("/api/admin/dealdesk/triggers/{trigger_key}/set-on-failure")
    async def admin_dealdesk_set_trigger_on_failure(
        trigger_key: str,
        request: Request,
    ) -> dict[str, Any]:
        from g_gremlin.dealdesk.mutations import (
            SetPostCloseTriggerOnFailureRequestV1,
            set_post_close_trigger_on_failure,
        )

        actor = _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="dealdesk_trigger_failure_mode_update",
        )
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        try:
            body = SetPostCloseTriggerOnFailureRequestV1.model_validate(payload)
        except Exception as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        try:
            result = set_post_close_trigger_on_failure(
                Path(app.state.state_dir),
                trigger_key=trigger_key,
                request=body,
                actor_id=actor.actor_id,
                actor_email=actor.mapped_email,
            )
        except KeyError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc
        except RuntimeError as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        return result.to_dict()

    @app.post("/api/admin/dealdesk/certification-steps/{step_key}/set-mode")
    async def admin_dealdesk_set_certification_step_mode(
        step_key: str,
        request: Request,
    ) -> dict[str, Any]:
        from g_gremlin.dealdesk.mutations import (
            SetCertificationStepModeRequestV1,
            set_certification_step_mode,
        )

        actor = _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="dealdesk_certification_step_mode_update",
        )
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        try:
            body = SetCertificationStepModeRequestV1.model_validate(payload)
        except Exception as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        try:
            result = set_certification_step_mode(
                Path(app.state.state_dir),
                step_key=step_key,
                request=body,
                actor_id=actor.actor_id,
                actor_email=actor.mapped_email,
            )
        except KeyError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc
        except RuntimeError as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        return result.to_dict()

    @app.get("/api/orchestration/metrics")
    async def orchestration_metrics(
        lookback_days: int = Query(30, ge=1, le=3650),
        promotion_limit: int = Query(25, ge=1, le=200),
    ) -> dict[str, Any]:
        from .orchestration_metrics import TrustPromotionStore, compute_orchestration_metrics

        state_path = Path(app.state.state_dir)
        payload = compute_orchestration_metrics(
            state_path,
            lookback_days=lookback_days,
        )
        trust_store = TrustPromotionStore(state_path / "db" / "trust_promotions.sqlite")
        payload["promotion_events"] = trust_store.list_events(limit=promotion_limit)
        return payload

    @app.post("/api/orchestration/graph/search")
    async def orchestration_graph_search(request: Request) -> dict[str, Any]:
        from .orchestration_views import EntityViewService

        payload: dict[str, Any]
        try:
            parsed = await request.json()
        except Exception:
            parsed = {}
        if parsed is None:
            parsed = {}
        if not isinstance(parsed, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed

        query = str(payload.get("query") or "").strip()
        source_system = str(payload.get("source_system") or "").strip().lower()
        source_record_id = str(payload.get("source_record_id") or "").strip()
        source_org_id = str(payload.get("source_org_id") or "").strip()
        email = str(payload.get("email") or "").strip()
        domain = str(payload.get("domain") or "").strip()
        try:
            limit = int(payload.get("limit") if payload.get("limit") is not None else 20)
        except Exception as exc:
            raise HTTPException(status_code=400, detail=f"Invalid limit: {exc}") from exc
        limit = max(1, min(limit, 200))

        has_source_lookup = bool(source_system or source_record_id)
        if has_source_lookup and not (source_system and source_record_id):
            raise HTTPException(
                status_code=400,
                detail="source_system and source_record_id are required together",
            )

        service = EntityViewService(Path(app.state.state_dir))
        if has_source_lookup or email or domain:
            entity = service.lookup_entity(
                source_system=source_system or None,
                source_record_id=source_record_id or None,
                source_org_id=source_org_id or None,
                email=email or None,
                domain=domain or None,
            )
            results = [entity] if entity else []
        else:
            results = service.search_entities(
                query=query or None,
                source_system=source_system or None,
                limit=limit,
            )
        return {
            "query": query,
            "source_system": source_system or None,
            "count": len(results),
            "results": results,
        }

    @app.get("/api/orchestration/graph/entity/{entity_id}")
    async def orchestration_graph_entity(
        entity_id: str,
        lookback_days: int = Query(30, ge=1, le=3650),
        activity_limit: int = Query(50, ge=1, le=200),
        provenance_limit: int = Query(500, ge=1, le=5000),
    ) -> dict[str, Any]:
        from .orchestration_views import EntityViewService

        service = EntityViewService(Path(app.state.state_dir))
        result = service.get_entity_view(
            entity_id,
            lookback_days=lookback_days,
            activity_limit=activity_limit,
            provenance_limit=provenance_limit,
        )
        if result is None:
            raise HTTPException(status_code=404, detail=f"Entity not found: {entity_id}")
        return result

    @app.get("/api/orchestration/modules/{module_id}/promotions")
    async def orchestration_module_promotions(
        module_id: str,
        limit: int = Query(25, ge=1, le=200),
    ) -> dict[str, Any]:
        from .orchestration_metrics import TrustPromotionStore

        store = TrustPromotionStore(Path(app.state.state_dir) / "db" / "trust_promotions.sqlite")
        return {
            "module_id": module_id,
            "events": store.list_events(module_id=module_id, limit=limit),
        }

    @app.post("/api/orchestration/modules/{module_id}/promote")
    async def orchestration_module_promote(module_id: str, request: Request) -> dict[str, Any]:
        from .orchestration_metrics import TrustPromotionStore, build_module_promotion_plan

        payload: dict[str, Any]
        try:
            parsed = await request.json()
        except Exception:
            parsed = {}
        if parsed is None:
            parsed = {}
        if not isinstance(parsed, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed

        action = str(payload.get("action") or "promote_to_auto").strip().lower()
        try:
            lookback_days = int(payload.get("lookback_days") if payload.get("lookback_days") is not None else 30)
        except Exception as exc:
            raise HTTPException(status_code=400, detail=f"Invalid lookback_days: {exc}") from exc
        lookback_days = max(1, min(lookback_days, 3650))
        dry_run = bool(payload.get("dry_run", True))

        state_path = Path(app.state.state_dir)
        try:
            plan = build_module_promotion_plan(
                state_path,
                module_id,
                action=action,
                lookback_days=lookback_days,
            )
        except ValueError as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc

        trust_store = TrustPromotionStore(state_path / "db" / "trust_promotions.sqlite")
        actor = _module_actor(request)

        applied = False
        policy_hash: str | None = None
        config_receipt_id: str | None = None
        runtime_revision: int | None = None
        result_envelope: dict[str, Any] | None = None
        previous_envelope: dict[str, Any] | None = None

        if not dry_run:
            store = _module_store()
            proposed = plan.get("proposed_envelope")
            current = plan.get("current_envelope")
            if not isinstance(proposed, dict) or not isinstance(current, dict):
                raise HTTPException(status_code=422, detail="Promotion plan is missing envelope payloads")
            try:
                put_payload = ModulePutPayload(
                    revision_hash=str(current.get("revision_hash") or ""),
                    enabled=bool(proposed.get("enabled")),
                    schedule_ref=proposed.get("schedule_ref"),
                    exception_routing=proposed.get("exception_routing"),
                    risk_tiers=proposed.get("risk_tiers") or {},
                    config=proposed.get("config") or {},
                )
            except Exception as exc:
                raise HTTPException(status_code=422, detail=f"Invalid promotion plan: {exc}") from exc

            try:
                previous = store.load_module(module_id)
                result = store.save_module(
                    module_id,
                    put_payload,
                    actor=actor,
                    schedule_keys=_module_schedule_keys(),
                    routing_targets=_module_routing_targets(),
                    compile_policy_fn=lambda modules: compile_runtime_policy(
                        Path(app.state.state_dir),
                        app.state.policy,
                        modules,
                    ).policy_hash,
                )
                runtime_revision = bump_runtime_policy_revision(Path(app.state.state_dir))
                write_runtime_reload_signal(Path(app.state.state_dir), revision=runtime_revision)
            except RevisionConflictError as exc:
                raise HTTPException(status_code=409, detail=str(exc)) from exc
            except (ModuleValidationError, ModuleCompileError) as exc:
                raise HTTPException(status_code=422, detail=str(exc)) from exc

            semantic_diff = diff_module_revision(module_id, previous, result.envelope)
            config_receipt_id = _write_module_config_receipt(
                Path(app.state.state_dir),
                module_id=module_id,
                actor=actor,
                old_envelope=previous,
                new_envelope=result.envelope,
                semantic_diff=semantic_diff.model_dump(exclude_none=False),
                action=f"auto_promote:{action}",
            )
            policy_hash = result.policy_hash
            applied = True
            previous_envelope = previous.model_dump(exclude_none=False)
            result_envelope = result.envelope.model_dump(exclude_none=False)

        event_payload = {
            "plan": plan,
            "applied": applied,
            "policy_hash": policy_hash,
            "config_receipt_id": config_receipt_id,
        }
        event_id = trust_store.record_event(
            module_id=module_id,
            action=action,
            actor=actor,
            applied=applied,
            dry_run=dry_run,
            lookback_days=lookback_days,
            payload=event_payload,
        )
        return {
            "module_id": module_id,
            "action": action,
            "dry_run": dry_run,
            "applied": applied,
            "event_id": event_id,
            "plan": plan,
            "previous_envelope": previous_envelope,
            "envelope": result_envelope,
            "policy_hash": policy_hash,
            "runtime_policy_revision": runtime_revision,
            "config_receipt_id": config_receipt_id,
        }

    # --- Admin (Credentials, Receipts, Pending, Policy) ---

    @app.get("/api/admin/credentials")
    async def list_credentials() -> list[dict[str, Any]]:
        """List integration credential statuses (config/file-only, cached)."""
        cache = getattr(app.state, "credentials_cache", None)
        now = _time.time()
        if cache and (now - cache[0]) < _CACHE_TTL:
            return cache[1]
        try:
            from g_gremlin.credentials.provider import CredentialProvider

            provider = CredentialProvider()
            result = [s.to_dict() for s in provider.list_integrations()]
        except Exception:
            result = []
        app.state.credentials_cache = (now, result)
        return result

    @app.get("/api/admin/config/summary")
    async def get_config_summary() -> dict[str, Any]:
        """Operational config summary for the Config UI."""
        sd = Path(app.state.state_dir)
        policy = apply_policy_overlays(app.state.policy, sd)
        heartbeat = _read_daemon_heartbeat(sd) or {}
        daemon_running = _daemon_running(sd)
        active_profile = heartbeat.get("profile")
        next_run_at = heartbeat.get("next_run_at")
        integrations = await list_credentials()
        slack = _resolve_slack_status(app, include_secret=False)

        return {
            "integrations": integrations,
            "slack": slack,
            "daemon": {
                "running": daemon_running,
                "active_profile": active_profile,
                "next_run_at": next_run_at,
                "available_profiles": sorted(policy.schedules.keys()),
            },
            "policy": {
                "org_alias": policy.org_alias,
                "version": policy.version,
                "hash": compute_policy_hash(policy),
                "path": str(app.state.policy_path) if app.state.policy_path else None,
            },
        }

    @app.get("/api/admin/feature-flags")
    async def get_feature_flags() -> dict[str, Any]:
        return {
            "flags": _feature_flags_payload(),
            "generated_at": datetime.now(timezone.utc).isoformat(),
        }

    @app.get("/api/admin/integrations")
    async def get_admin_integrations(request: Request) -> dict[str, Any]:
        return {"integrations": _list_admin_integrations(app, request)}

    @app.post("/api/admin/integrations/{integration}/manual")
    async def save_admin_integration_manual(integration: str, request: Request) -> dict[str, Any]:
        payload: dict[str, Any]
        try:
            parsed = await request.json()
        except Exception:
            parsed = {}
        if parsed is None:
            parsed = {}
        if not isinstance(parsed, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed

        if integration not in ADMIN_INTEGRATIONS:
            raise HTTPException(status_code=404, detail=f"Unsupported integration: {integration}")

        try:
            details = _save_manual_integration_config(
                integration,
                payload,
                state_dir=Path(app.state.state_dir),
            )
        except ValueError as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc
        except Exception as exc:
            raise HTTPException(status_code=500, detail=f"Failed to save integration config: {exc}") from exc

        _invalidate_credentials_cache(app)
        _invalidate_integration_status_cache(app)
        try:
            _audit_log(app).log(
                "credential_change",
                _request_actor(request),
                f"Manual credentials updated for {integration}",
                integration=integration,
                detail={"updated_fields": sorted(payload.keys())},
            )
        except Exception:
            logger.debug("Failed to write credential_change audit event", exc_info=True)
        return {
            "ok": True,
            "integration": _admin_integration_status(app, request, integration),
            "details": details,
        }

    @app.post("/api/admin/integrations/{integration}/oauth/start")
    async def start_admin_integration_oauth(integration: str, request: Request) -> dict[str, Any]:
        payload: dict[str, Any]
        try:
            parsed = await request.json()
        except Exception:
            parsed = {}
        if parsed is None:
            parsed = {}
        if not isinstance(parsed, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed

        if integration not in ADMIN_INTEGRATIONS:
            raise HTTPException(status_code=404, detail=f"Unsupported integration: {integration}")

        try:
            return _build_integration_oauth_start(
                state_dir=Path(app.state.state_dir),
                request=request,
                integration=integration,
                payload=payload,
            )
        except ValueError as exc:
            raise HTTPException(status_code=400, detail=str(exc)) from exc

    @app.get("/api/admin/integrations/status")
    async def get_admin_integrations_status(request: Request) -> dict[str, Any]:
        sync_result = _sync_outreach_bridge_session(app, state_dir=Path(app.state.state_dir))
        if sync_result.get("changed"):
            _invalidate_credentials_cache(app)
            _invalidate_integration_status_cache(app)

        cache = getattr(app.state, "integration_status_cache", None)
        now = _time.time()
        if cache and (now - cache[0]) < _CACHE_TTL:
            return cache[1]

        integrations = await _full_integration_status(app, request)
        payload = {
            "integrations": integrations,
            "timestamp": datetime.now(timezone.utc).isoformat(),
        }
        app.state.integration_status_cache = (now, payload)
        return payload

    @app.post("/api/admin/integrations/{integration}/test")
    async def test_admin_integration(integration: str, request: Request) -> dict[str, Any]:
        if integration not in ADMIN_INTEGRATIONS:
            raise HTTPException(status_code=404, detail=f"Unsupported integration: {integration}")

        state_dir = Path(app.state.state_dir)
        result = await _test_integration_connection(app, integration=integration, state_dir=state_dir)
        _record_integration_status_snapshot(
            state_dir=state_dir,
            integration=integration,
            ok=bool(result.get("ok")),
            details=result.get("details") if isinstance(result.get("details"), dict) else {},
            error=result.get("error"),
        )
        _invalidate_integration_status_cache(app)
        try:
            _audit_log(app).log(
                "oauth_test",
                _request_actor(request),
                f"Connection test for {integration}",
                integration=integration,
                detail={"ok": bool(result.get("ok")), "error": result.get("error")},
            )
        except Exception:
            logger.debug("Failed to write oauth_test audit event", exc_info=True)
        return result

    @app.post("/api/admin/integrations/{integration}/disconnect")
    async def disconnect_admin_integration(integration: str, request: Request) -> dict[str, Any]:
        if integration not in ADMIN_INTEGRATIONS:
            raise HTTPException(status_code=404, detail=f"Unsupported integration: {integration}")

        state_dir = Path(app.state.state_dir)
        disconnect_result = await _disconnect_integration(app, integration=integration, state_dir=state_dir)
        _invalidate_credentials_cache(app)
        _invalidate_integration_status_cache(app)
        try:
            _audit_log(app).log(
                "oauth_disconnect",
                _request_actor(request),
                f"Disconnected {integration}",
                integration=integration,
                detail={
                    "revoke_succeeded": disconnect_result.get("revoke_succeeded"),
                    "revoke_error": disconnect_result.get("revoke_error"),
                    "substrate": disconnect_result.get("substrate"),
                },
            )
        except Exception:
            logger.debug("Failed to write oauth_disconnect audit event", exc_info=True)
        return disconnect_result

    @app.get("/api/admin/audit")
    async def get_admin_audit(
        request: Request,
        event_type: str | None = Query(None),
        integration: str | None = Query(None),
        since: str | None = Query(None),
        limit: int = Query(100, ge=1, le=1000),
    ) -> dict[str, Any]:
        audit = _audit_log(app)
        retention_days = _audit_retention_days()
        purged = audit.purge(retention_days)
        events = audit.query(
            event_type=event_type,
            integration=integration,
            since=since,
            limit=limit,
        )
        return {
            "events": events,
            "count": len(events),
            "retention_days": retention_days,
            "purged": purged,
        }

    @app.get("/api/admin/substrate/stats")
    async def get_substrate_stats() -> dict[str, Any]:
        db_path = _platform_db_path(Path(app.state.state_dir))
        from .identity_graph import IdentityGraph
        from .field_provenance import FieldProvenanceStore

        identity = IdentityGraph(db_path)
        provenance = FieldProvenanceStore(db_path)
        return {
            "identity": identity.stats(),
            "provenance": {
                **provenance.stats(),
                "by_source_system": provenance.source_system_counts(),
            },
        }

    @app.get("/api/admin/substrate/identity/lookup")
    async def substrate_identity_lookup(
        source_system: str | None = Query(None),
        source_record_id: str | None = Query(None),
        source_org_id: str | None = Query(None),
        email: str | None = Query(None),
        domain: str | None = Query(None),
    ) -> dict[str, Any]:
        normalized_system = str(source_system or "").strip().lower()
        normalized_record_id = str(source_record_id or "").strip()
        normalized_org_id = str(source_org_id or "").strip() or None
        normalized_email = str(email or "").strip()
        normalized_domain = str(domain or "").strip()

        has_source_lookup = bool(normalized_system or normalized_record_id)
        if has_source_lookup and not (normalized_system and normalized_record_id):
            raise HTTPException(
                status_code=400,
                detail="source_system and source_record_id are required together",
            )
        if not has_source_lookup and not normalized_email and not normalized_domain:
            raise HTTPException(
                status_code=400,
                detail="Provide source lookup, email, or domain",
            )

        from .identity_graph import IdentityGraph

        identity = IdentityGraph(_platform_db_path(Path(app.state.state_dir)))
        entity: dict[str, Any] | None = None
        lookup_kind = "unknown"
        if normalized_system and normalized_record_id:
            lookup_kind = "source_record"
            entity = identity.find_entity_by_record(
                normalized_system,
                normalized_record_id,
                normalized_org_id,
            )
        elif normalized_email:
            lookup_kind = "email"
            entity = identity.find_entity_by_email(normalized_email)
        else:
            lookup_kind = "domain"
            entity = identity.find_entity_by_domain(normalized_domain)

        if not entity:
            return {"found": False, "lookup_kind": lookup_kind, "entity": None, "linked_records": []}

        entity_id = str(entity.get("entity_id") or "")
        return {
            "found": True,
            "lookup_kind": lookup_kind,
            "entity": entity,
            "linked_records": identity.get_linked_records(entity_id),
        }

    @app.get("/api/admin/substrate/provenance/entity/{entity_id}")
    async def substrate_provenance_by_entity(
        entity_id: str,
        field_name: str | None = Query(None),
        include_drift: bool = Query(True),
        limit: int = Query(200, ge=1, le=1000),
    ) -> dict[str, Any]:
        normalized_entity_id = str(entity_id or "").strip()
        if not normalized_entity_id:
            raise HTTPException(status_code=400, detail="entity_id is required")
        normalized_field_name = str(field_name or "").strip().lower() or None

        from .field_provenance import FieldProvenanceStore

        provenance = FieldProvenanceStore(_platform_db_path(Path(app.state.state_dir)))
        observations = provenance.list_entity_observations(
            normalized_entity_id,
            field_name=normalized_field_name,
            limit=limit,
        )
        if normalized_field_name:
            authoritative = provenance.get_authoritative_value(
                normalized_entity_id,
                normalized_field_name,
            )
            drift_rows = (
                provenance.detect_drift(normalized_entity_id, normalized_field_name)
                if include_drift
                else []
            )
            return {
                "entity_id": normalized_entity_id,
                "field_name": normalized_field_name,
                "observations": observations,
                "authoritative": authoritative,
                "drift": drift_rows,
            }

        field_names: list[str] = []
        seen_fields: set[str] = set()
        for observation in observations:
            name = str(observation.get("field_name") or "").strip().lower()
            if not name or name in seen_fields:
                continue
            seen_fields.add(name)
            field_names.append(name)

        authoritative_by_field: dict[str, dict[str, Any]] = {}
        drift_by_field: dict[str, list[dict[str, Any]]] = {}
        for name in field_names:
            authoritative = provenance.get_authoritative_value(normalized_entity_id, name)
            if authoritative is not None:
                authoritative_by_field[name] = authoritative
            if include_drift:
                drift_rows = provenance.detect_drift(normalized_entity_id, name)
                if drift_rows:
                    drift_by_field[name] = drift_rows

        return {
            "entity_id": normalized_entity_id,
            "observations": observations,
            "authoritative_by_field": authoritative_by_field,
            "drift_by_field": drift_by_field if include_drift else {},
        }

    @app.get("/api/admin/substrate/provenance/stale")
    async def substrate_provenance_stale(
        older_than_days: int = Query(90, ge=0, le=3650),
        limit: int = Query(200, ge=1, le=1000),
    ) -> dict[str, Any]:
        from .field_provenance import FieldProvenanceStore

        provenance = FieldProvenanceStore(_platform_db_path(Path(app.state.state_dir)))
        stale_rows = provenance.stale_fields(older_than_days=older_than_days)
        results = stale_rows[:limit]
        return {
            "older_than_days": older_than_days,
            "count": len(results),
            "truncated": len(stale_rows) > limit,
            "results": results,
        }

    @app.get("/api/admin/oauth/{integration}/callback")
    async def integration_oauth_callback(
        request: Request,
        integration: str,
        state: str | None = Query(None),
        code: str | None = Query(None),
        error: str | None = Query(None),
        error_description: str | None = Query(None),
    ) -> HTMLResponse:
        if integration not in ADMIN_INTEGRATIONS:
            raise HTTPException(status_code=404, detail=f"Unsupported integration: {integration}")

        if not state:
            return _oauth_callback_html(
                title="Connection failed",
                message="Missing OAuth state parameter.",
                success=False,
            )

        state_payload = _consume_oauth_state(
            Path(app.state.state_dir),
            state=state,
            expected_integration=integration,
        )
        if state_payload is None:
            return _oauth_callback_html(
                title="Connection failed",
                message="OAuth state is invalid or expired. Restart the connection flow from Config.",
                success=False,
            )

        if error:
            details = f"{error}: {error_description}" if error_description else error
            return _oauth_callback_html(
                title="Connection canceled",
                message=f"Provider returned an OAuth error ({details}).",
                success=False,
            )

        if not code:
            return _oauth_callback_html(
                title="Connection failed",
                message="Missing OAuth authorization code.",
                success=False,
            )

        try:
            state_dir = Path(app.state.state_dir)
            token_store = TokenStore(state_dir)
            if integration == "hubspot":
                token_payload = _exchange_hubspot_oauth_code(
                    request=request,
                    code=code,
                )
                access_token = str(token_payload.get("access_token") or "").strip()
                if not access_token:
                    raise ValueError("HubSpot token response did not include an access_token")
                import g_gremlin.hubspot as hubspot_helpers

                profile = str(state_payload.get("profile") or "default").strip() or "default"
                api_version = str(token_payload.get("api_version") or hubspot_helpers.DEFAULT_API_VERSION)
                hubspot_helpers.connect(
                    access_token=access_token,
                    api_version=api_version,
                    profile=profile,
                    set_current=True,
                    verified=True,
                )
                token_store.save_tokens(
                    "hubspot",
                    {
                        "access_token": access_token,
                        "refresh_token": str(token_payload.get("refresh_token") or "").strip() or None,
                        "token_type": str(token_payload.get("token_type") or "").strip() or None,
                        "scope": str(token_payload.get("scope") or "").strip() or None,
                        "expires_in": token_payload.get("expires_in"),
                    },
                )
                _invalidate_credentials_cache(app)
                _invalidate_integration_status_cache(app)
                _audit_log(app).log(
                    "oauth_connect",
                    _request_actor(request),
                    "HubSpot OAuth connected",
                    integration="hubspot",
                    detail={"profile": profile},
                )
                return _oauth_callback_html(
                    title="HubSpot connected",
                    message="HubSpot OAuth completed. Return to Config and refresh integration status.",
                    success=True,
                )

            if integration == "salesforce":
                token_payload = _exchange_salesforce_oauth_code(
                    request=request,
                    code=code,
                )
                access_token = str(token_payload.get("access_token") or "").strip()
                refresh_token = str(token_payload.get("refresh_token") or "").strip()
                instance_url = str(token_payload.get("instance_url") or "").strip()
                identity_url = str(token_payload.get("id") or "").strip()
                org_id = _extract_salesforce_org_id(identity_url)
                org_alias = str(state_payload.get("org_alias") or "").strip() or org_id or "salesforce-oauth"
                if not access_token:
                    raise ValueError("Salesforce token response did not include an access_token")
                token_store.save_tokens(
                    "salesforce",
                    {
                        "org_alias": org_alias,
                        "org_id": org_id,
                        "instance_url": instance_url or None,
                        "access_token": access_token,
                        "refresh_token": refresh_token or None,
                        "token_type": str(token_payload.get("token_type") or "").strip() or None,
                        "id": identity_url or None,
                        "scope": str(token_payload.get("scope") or "").strip() or None,
                        "expires_in": token_payload.get("expires_in"),
                    },
                )
                if _write_legacy_auth_files_enabled():
                    import g_gremlin.sfdc as sfdc_helpers

                    sfdc_helpers.set_configured_org(
                        org_alias,
                        instance_url=instance_url or None,
                        access_token=access_token,
                        refresh_token=None,
                    )
                _invalidate_credentials_cache(app)
                _invalidate_integration_status_cache(app)
                _audit_log(app).log(
                    "oauth_connect",
                    _request_actor(request),
                    "Salesforce OAuth connected",
                    integration="salesforce",
                    detail={"org_id": org_id or None, "org_alias": org_alias, "instance_url": instance_url or None},
                )
                return _oauth_callback_html(
                    title="Salesforce connected",
                    message="Salesforce OAuth completed. Return to Config and refresh integration status.",
                    success=True,
                )

            if integration == "dynamics":
                token_payload = _exchange_dynamics_oauth_code(
                    request=request,
                    code=code,
                )
                access_token = str(token_payload.get("access_token") or "").strip()
                refresh_token = str(token_payload.get("refresh_token") or "").strip()
                resource_url = _resolve_dynamics_resource_url(token_payload, state_payload=state_payload)
                if not access_token:
                    raise ValueError("Dynamics token response did not include an access_token")
                token_store.save_tokens(
                    "dynamics",
                    {
                        "environment_url": resource_url,
                        "resource_url": resource_url,
                        "access_token": access_token,
                        "refresh_token": refresh_token or None,
                        "token_type": str(token_payload.get("token_type") or "").strip() or None,
                        "scope": str(token_payload.get("scope") or "").strip() or None,
                        "expires_in": token_payload.get("expires_in"),
                    },
                )
                _invalidate_credentials_cache(app)
                _invalidate_integration_status_cache(app)
                _audit_log(app).log(
                    "oauth_connect",
                    _request_actor(request),
                    "Dynamics OAuth connected",
                    integration="dynamics",
                    detail={"resource_url": resource_url or None},
                )
                return _oauth_callback_html(
                    title="Dynamics connected",
                    message="Dynamics OAuth completed. Return to Config and refresh integration status.",
                    success=True,
                )

            if integration == "outreach":
                token_payload = _exchange_outreach_oauth_code(
                    request=request,
                    code=code,
                )
                access_token = str(token_payload.get("access_token") or "").strip()
                refresh_token = str(token_payload.get("refresh_token") or "").strip()
                if not access_token or not refresh_token:
                    raise ValueError("Outreach token response did not include access_token + refresh_token")

                cfg = _load_outreach_file_config()
                cfg["client_id"] = (
                    os.getenv("G_GREMLIN_OUTREACH_OAUTH_CLIENT_ID", "").strip()
                    or os.getenv("G_GREMLIN_OUTREACH_CLIENT_ID", "").strip()
                    or str(cfg.get("client_id") or "").strip()
                )
                cfg["client_secret"] = (
                    os.getenv("G_GREMLIN_OUTREACH_OAUTH_CLIENT_SECRET", "").strip()
                    or os.getenv("G_GREMLIN_OUTREACH_CLIENT_SECRET", "").strip()
                    or str(cfg.get("client_secret") or "").strip()
                )
                cfg["access_token"] = access_token
                cfg["refresh_token"] = refresh_token
                cfg["auth_mode"] = "local"
                expires_in = token_payload.get("expires_in")
                try:
                    expires_seconds = int(expires_in) if expires_in is not None else 7200
                except Exception:
                    expires_seconds = 7200
                cfg["token_expires_at"] = int(_time.time()) + max(expires_seconds, 1)
                scope_value = str(token_payload.get("scope") or "").strip()
                if scope_value:
                    cfg["requested_scopes"] = [scope for scope in scope_value.split(" ") if scope]
                scope_profile = str(state_payload.get("scope_profile") or "").strip()
                if scope_profile:
                    cfg["scope_profile"] = scope_profile
                _write_outreach_file_config(cfg)

                _invalidate_credentials_cache(app)
                _invalidate_integration_status_cache(app)
                _audit_log(app).log(
                    "oauth_connect",
                    _request_actor(request),
                    "Outreach OAuth connected",
                    integration="outreach",
                    detail={"scope_profile": scope_profile or None},
                )
                return _oauth_callback_html(
                    title="Outreach connected",
                    message="Outreach OAuth completed. Return to Config and refresh integration status.",
                    success=True,
                )

            raise ValueError(f"Unsupported integration callback: {integration}")
        except Exception as exc:
            return _oauth_callback_html(
                title="Connection failed",
                message=f"OAuth callback processing failed: {exc}",
                success=False,
            )

    @app.get("/api/admin/slack")
    async def get_slack_config() -> dict[str, Any]:
        """Show Slack wiring status (never returns raw token)."""
        return _resolve_slack_status(app, include_secret=False)

    @app.post("/api/admin/slack")
    async def save_slack_config(request: Request) -> dict[str, Any]:
        """Persist Slack token/channel into state volume config."""
        payload = await request.json()
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        channel_raw = payload.get("channel")
        bot_token_raw = payload.get("bot_token")
        clear_token = bool(payload.get("clear_token"))

        channel = str(channel_raw or "").strip()
        bot_token = str(bot_token_raw or "").strip()

        if not channel:
            raise HTTPException(status_code=400, detail="Slack channel is required")

        state_dir = Path(app.state.state_dir)
        cfg = _load_slack_file_config(state_dir)
        cfg["channel"] = channel
        if bot_token:
            cfg["bot_token"] = bot_token
        elif clear_token:
            cfg.pop("bot_token", None)
        cfg["updated_at"] = datetime.now(timezone.utc).isoformat()

        _write_slack_file_config(state_dir, cfg)
        return _resolve_slack_status(app, include_secret=False)

    @app.post("/api/admin/slack/test")
    async def test_slack_config(request: Request) -> dict[str, Any]:
        """Send a lightweight Slack test message."""
        payload = await request.json()
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        status = _resolve_slack_status(app, include_secret=True)
        token_override = str(payload.get("bot_token") or "").strip()
        token = token_override or str(status.get("token") or "").strip()
        channel_override = str(payload.get("channel") or "").strip()
        channel = channel_override or str(status.get("channel") or "").strip()
        if not token:
            raise HTTPException(status_code=400, detail="Slack bot token is not configured")
        if not channel:
            raise HTTPException(status_code=400, detail="Slack channel is not configured")

        message = str(payload.get("message") or "").strip()
        if not message:
            message = f"Gremlin platform test message ({datetime.now(timezone.utc).isoformat()})"

        try:
            from .slack_notify import create_client as slack_create_client

            client = slack_create_client(token)
            resp = client.chat_postMessage(channel=channel, text=message)
            ok = bool(getattr(resp, "get", lambda *_: True)("ok", True))
            if not ok:
                raise RuntimeError(str(getattr(resp, "get", lambda *_: "unknown_error")("error", "unknown_error")))
        except Exception as exc:
            raise HTTPException(status_code=400, detail=f"Slack test failed: {exc}") from exc

        return {"ok": True, "channel": channel, "message": "Slack test message sent"}

    @app.get("/api/admin/receipts")
    async def list_receipts(
        outcome: Optional[str] = Query(None, description="Filter by outcome"),
        since: Optional[str] = Query(None, description="Time window: 24h, 7d, 30d"),
        q: Optional[str] = Query(None, description="Search run_id or summary"),
    ) -> list[dict[str, Any]]:
        """List recent receipts with optional filtering."""
        receipts_dir = Path(app.state.state_dir) / "receipts"
        items: list[dict[str, Any]] = []

        # Compute cutoff time from 'since' param
        cutoff: datetime | None = None
        if since:
            since_map = {"24h": timedelta(hours=24), "7d": timedelta(days=7), "30d": timedelta(days=30)}
            delta = since_map.get(since)
            if delta:
                cutoff = datetime.now(timezone.utc) - delta

        if receipts_dir.exists():
            for run_dir in receipts_dir.iterdir():
                if not run_dir.is_dir():
                    continue
                receipt_path = run_dir / "receipt.json"
                if not receipt_path.exists():
                    continue
                try:
                    data = json.loads(receipt_path.read_text(encoding="utf-8"))
                except Exception:
                    continue

                run_id = data.get("run_id") or run_dir.name
                item_outcome = data.get("outcome")
                started_at = data.get("started_at")
                summary = data.get("summary", "")
                source = data.get("source", "")

                # Apply filters
                if outcome and item_outcome != outcome:
                    continue
                if cutoff:
                    ts = _parse_iso(started_at)
                    if not ts or ts < cutoff:
                        continue
                if q:
                    q_lower = q.lower()
                    if q_lower not in run_id.lower() and q_lower not in (summary or "").lower():
                        continue

                items.append(
                    {
                        "run_id": run_id,
                        "outcome": item_outcome,
                        "started_at": started_at,
                        "completed_at": data.get("completed_at"),
                        "change_count": len(data.get("changes") or []),
                        "source": source,
                        "summary": summary,
                    }
                )

        # Sort by timestamp desc, cap at 200
        items.sort(key=lambda x: _parse_iso(x.get("started_at")) or datetime.min, reverse=True)
        return items[:200]

    @app.get("/api/admin/receipts/{run_id}")
    async def get_receipt_detail(run_id: str) -> dict[str, Any]:
        run_dir = Path(app.state.state_dir) / "receipts" / run_id
        receipt_path = run_dir / "receipt.json"
        plan_path = run_dir / "plan.json"
        if not receipt_path.exists():
            raise HTTPException(status_code=404, detail="Receipt not found")
        receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
        plan = json.loads(plan_path.read_text(encoding="utf-8")) if plan_path.exists() else {}
        return {"receipt": receipt, "plan": plan}

    @app.get("/api/admin/pending")
    async def list_pending() -> list[dict[str, Any]]:
        return _load_pending_items(Path(app.state.state_dir))

    @app.get("/api/admin/pending/clusters")
    async def list_pending_clusters(
        window_seconds: int = Query(PENDING_CLUSTER_WINDOW_SECONDS, ge=60, le=7 * 24 * 60 * 60),
    ) -> dict[str, Any]:
        state_dir = Path(app.state.state_dir)
        pending_items = _load_pending_items(state_dir)
        clusters = _build_pending_clusters(state_dir, pending_items, window_seconds=window_seconds)
        return {
            "generated_at": datetime.now(timezone.utc).isoformat(),
            "window_seconds": window_seconds,
            "pending_count": len(pending_items),
            "cluster_contract": {
                "version": PENDING_CLUSTER_CONTRACT_VERSION,
                "cluster_key_fields": ["source_scout", "action_key", "target_sobject"],
                "cluster_signature_fields": ["action_family", "scope", "evidence_window", "policy_hash"],
                "dedupe_window_seconds": window_seconds,
                "split_when": [
                    "source_scout changes",
                    "action_key changes",
                    "target_sobject changes",
                    "policy_hash changes",
                    "created_at falls into a different evidence window",
                ],
                "merge_when": [
                    "source_scout matches",
                    "action_key matches",
                    "target_sobject matches",
                    "policy_hash matches",
                    "created_at bucket matches current evidence window",
                ],
            },
            "clusters": clusters,
        }

    @app.get("/api/admin/pending/{run_id}")
    async def get_pending_detail(run_id: str) -> dict[str, Any]:
        plan_path = Path(app.state.state_dir) / "pending" / run_id / "plan.json"
        if not plan_path.exists():
            raise HTTPException(status_code=404, detail="Pending plan not found")
        return json.loads(plan_path.read_text(encoding="utf-8"))

    @app.get("/api/admin/pending/{run_id}/decision-report")
    async def get_pending_decision_report(run_id: str) -> dict[str, Any]:
        return _build_decision_report(Path(app.state.state_dir), run_id)

    @app.post("/api/admin/pending/{run_id}/operator-intent")
    async def record_pending_operator_intent(run_id: str, request: Request) -> dict[str, Any]:
        payload: dict[str, Any]
        try:
            parsed = await request.json()
        except Exception:
            parsed = {}
        if parsed is None:
            parsed = {}
        if not isinstance(parsed, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed

        raw_intent = str(payload.get("intent") or "").strip().lower()
        if raw_intent not in OPERATOR_INTENT_LABELS:
            raise HTTPException(
                status_code=400,
                detail=f"intent must be one of: {', '.join(sorted(OPERATOR_INTENT_LABELS.keys()))}",
            )
        reason = str(payload.get("reason") or "").strip()
        if raw_intent in {"too_risky", "wrong_target", "pause_agent"} and len(reason) < 4:
            raise HTTPException(status_code=400, detail="reason is required and must be at least 4 characters")

        state_dir = Path(app.state.state_dir)
        decision_report = _build_decision_report(state_dir, run_id)
        cluster_signature = str(decision_report.get("cluster_signature") or "").strip() or None
        expected_cluster_signature = str(payload.get("expected_cluster_signature") or "").strip() or None
        if expected_cluster_signature and cluster_signature and expected_cluster_signature != cluster_signature:
            raise HTTPException(status_code=409, detail="Cluster signature mismatch")

        actor = _request_actor(request)
        detail_payload: dict[str, Any] = {
            "intent": raw_intent,
            "intent_label": OPERATOR_INTENT_LABELS[raw_intent],
            "reason": reason,
            "run_id": run_id,
            "cluster_signature": cluster_signature,
            "agent_module_id": decision_report.get("agent_module_id"),
            "proposal_id": decision_report.get("proposal_id"),
            "compile_id": decision_report.get("compile_id"),
        }

        proposal_id = str(decision_report.get("proposal_id") or "").strip()
        if proposal_id:
            cp_store = control_plane_store(state_dir)
            cp_store.record_outcome(
                proposal_id,
                "feedback",
                actor=actor,
                detail={"operator_intent": detail_payload},
            )

        next_step = None
        if raw_intent == "pause_agent":
            module_id = str(decision_report.get("agent_module_id") or "").strip()
            if module_id:
                next_step = f"Open Agents and move {module_id} to hold before approving additional runs."
            else:
                next_step = "Open Agents and move the implicated agent to hold before approving additional runs."

        audit_id: str | None
        try:
            audit_id = _audit_log(app).log(
                "pending_operator_intent",
                actor,
                f"Operator intent recorded: {raw_intent} for {run_id}",
                run_id=run_id,
                detail=detail_payload,
            )
        except Exception:
            audit_id = None

        return {
            "status": "recorded",
            "run_id": run_id,
            "intent": raw_intent,
            "intent_label": OPERATOR_INTENT_LABELS[raw_intent],
            "reason": reason or None,
            "cluster_signature": cluster_signature,
            "agent_module_id": decision_report.get("agent_module_id"),
            "proposal_id": decision_report.get("proposal_id"),
            "audit_id": audit_id,
            "next_step": next_step,
        }

    def _enforce_maker_checker(
        *,
        pending_action: Any | None,
        actor_ctx: ActorContext,
        run_id: str,
    ) -> None:
        if pending_action is None:
            return
        maker_actor = str(pending_action.created_by.actor_id or "").strip()
        maker_email = str(pending_action.created_by.mapped_email or "").strip().lower()
        approver_email = str(actor_ctx.mapped_email or "").strip().lower()
        if maker_actor and maker_actor == actor_ctx.actor_id:
            raise HTTPException(
                status_code=403,
                detail={
                    "reason": "maker_checker_violation",
                    "run_id": run_id,
                    "maker": maker_actor,
                    "approver": actor_ctx.actor_id,
                },
            )
        if maker_email and approver_email and maker_email == approver_email:
            raise HTTPException(
                status_code=403,
                detail={
                    "reason": "maker_checker_violation",
                    "run_id": run_id,
                    "maker_email": maker_email,
                    "approver_email": approver_email,
                },
            )

    def _approve_pending_with_actor(
        *,
        actor_ctx: ActorContext,
        run_id: str,
        approve_token: str | None = None,
        selected_step_ids: list[str] | None = None,
        metadata_action: str = "approve_http",
    ) -> tuple[dict[str, Any], ReceiptV1]:
        plan_path = Path(app.state.state_dir) / "pending" / run_id / "plan.json"
        if not plan_path.exists():
            raise HTTPException(status_code=404, detail="Pending plan not found")
        plan = PlanV1.from_dict(json.loads(plan_path.read_text(encoding="utf-8")))
        execution_plan = _plan_with_selected_steps(plan, selected_step_ids)
        pending_action = load_pending_action(Path(app.state.state_dir), run_id)
        _enforce_maker_checker(pending_action=pending_action, actor_ctx=actor_ctx, run_id=run_id)

        policy = apply_policy_overlays(app.state.policy, Path(app.state.state_dir))
        current_policy_hash = compute_policy_hash(policy)
        if plan.policy_hash and plan.policy_hash != current_policy_hash:
            raise HTTPException(status_code=409, detail="Policy hash mismatch")

        from .approvals import default_approval_ttl_seconds

        store = ApprovalTokenStore(Path(app.state.state_dir) / "db" / "approval_tokens.sqlite")
        token = approve_token
        if not token:
            token = store.create_token(
                execution_plan.plan_hash or "",
                current_policy_hash,
                execution_plan.run_id,
                ttl_seconds=default_approval_ttl_seconds(),
            ).token
        consumed = store.consume_token(
            token,
            execution_plan.plan_hash or "",
            current_policy_hash,
            execution_plan.run_id,
            metadata={
                "action": metadata_action,
                "actor": actor_ctx.actor_id,
                "roles": list(actor_ctx.roles),
            },
        )
        if not consumed:
            raise HTTPException(status_code=400, detail="Invalid or expired token")

        registry = get_default_registry()
        executor = PlanExecutor(policy=policy, registry=registry, state_dir=Path(app.state.state_dir))
        receipt = executor.execute(execution_plan, approve_token=token)
        existing_approved_by = getattr(receipt, "approved_by", None)
        if existing_approved_by is None:
            setattr(
                receipt,
                "approved_by",
                ReceiptApprovedBy(
                    channel=str(actor_ctx.auth_source or "api"),
                    actor=actor_ctx.actor_id,
                    mapped_email=actor_ctx.mapped_email,
                    principal_type=actor_ctx.principal_type,
                    verified_identity=bool(actor_ctx.verified_identity),
                ),
            )
        else:
            existing_actor = str(getattr(existing_approved_by, "actor", "")).strip()
            if existing_actor and existing_actor != actor_ctx.actor_id:
                raise HTTPException(
                    status_code=409,
                    detail={
                        "reason": "approved_by_immutable_violation",
                        "existing_actor": existing_actor,
                        "attempted_actor": actor_ctx.actor_id,
                    },
                )
            if not existing_actor:
                setattr(
                    receipt,
                    "approved_by",
                    ReceiptApprovedBy(
                        channel=str(actor_ctx.auth_source or "api"),
                        actor=actor_ctx.actor_id,
                        mapped_email=actor_ctx.mapped_email,
                        principal_type=actor_ctx.principal_type,
                        verified_identity=bool(actor_ctx.verified_identity),
                    ),
                )
        receipt.source_kind = execution_plan.source_kind
        receipt.proposal_id = execution_plan.proposal_id
        receipt.compile_id = execution_plan.compile_id
        receipt.compile_gate_snapshot = execution_plan.compile_gate_snapshot
        writer = ReceiptWriter(Path(app.state.state_dir))
        writer.write(receipt, execution_plan)
        if execution_plan.proposal_id:
            cp_store = control_plane_store(Path(app.state.state_dir))
            cp_store.record_outcome(
                execution_plan.proposal_id,
                "approved",
                actor=actor_ctx.actor_id,
                detail={"run_id": execution_plan.run_id, "compile_id": execution_plan.compile_id},
            )
            cp_store.record_outcome(
                execution_plan.proposal_id,
                "executed",
                actor=actor_ctx.actor_id,
                detail={"run_id": execution_plan.run_id, "outcome": receipt.outcome},
            )
            if receipt.outcome == "success":
                cp_store.update_proposal_status(execution_plan.proposal_id, "executed")
            else:
                cp_store.update_proposal_status(execution_plan.proposal_id, "approved")
        _archive_pending(Path(app.state.state_dir), run_id, status="approved")
        return receipt.to_dict(), receipt

    def _write_rejected_receipt(
        *,
        plan: PlanV1,
        actor_ctx: ActorContext,
    ) -> tuple[dict[str, Any], ReceiptV1]:
        receipt = ReceiptV1(
            run_id=plan.run_id,
            outcome="rejected",
            completed_at=datetime.now(timezone.utc).isoformat(),
            source_kind=plan.source_kind,
            proposal_id=plan.proposal_id,
            compile_id=plan.compile_id,
            compile_gate_snapshot=plan.compile_gate_snapshot,
        )
        writer = ReceiptWriter(Path(app.state.state_dir))
        receipt_dir = writer.write(receipt, plan)
        receipt_path = receipt_dir / "receipt.json"
        payload = json.loads(receipt_path.read_text(encoding="utf-8"))
        payload["rejected_by"] = {
            "channel": str(actor_ctx.auth_source or "api"),
            "actor": actor_ctx.actor_id,
            "mapped_email": actor_ctx.mapped_email,
            "principal_type": actor_ctx.principal_type,
            "verified_identity": bool(actor_ctx.verified_identity),
        }
        receipt_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
        return payload, receipt

    def _reject_pending_with_actor(
        *,
        actor_ctx: ActorContext,
        run_id: str,
        approve_token: str | None = None,
        reason: str | None = None,
        metadata_action: str = "reject_http",
        write_receipt: bool = False,
    ) -> tuple[dict[str, Any], ReceiptV1 | None]:
        plan_path = Path(app.state.state_dir) / "pending" / run_id / "plan.json"
        if not plan_path.exists():
            raise HTTPException(status_code=404, detail="Pending plan not found")
        plan = PlanV1.from_dict(json.loads(plan_path.read_text(encoding="utf-8")))

        policy = apply_policy_overlays(app.state.policy, Path(app.state.state_dir))
        current_policy_hash = compute_policy_hash(policy)

        from .approvals import default_approval_ttl_seconds

        store = ApprovalTokenStore(Path(app.state.state_dir) / "db" / "approval_tokens.sqlite")
        token = approve_token
        if not token:
            token = store.create_token(
                plan.plan_hash or "",
                current_policy_hash,
                plan.run_id,
                ttl_seconds=default_approval_ttl_seconds(),
            ).token
        consumed = store.consume_token(
            token,
            plan.plan_hash or "",
            current_policy_hash,
            plan.run_id,
            metadata={
                "action": metadata_action,
                "reason": reason or "",
                "actor": actor_ctx.actor_id,
                "roles": list(actor_ctx.roles),
            },
        )
        if not consumed:
            raise HTTPException(status_code=400, detail="Invalid or expired token")
        rejected_receipt_payload: dict[str, Any] | None = None
        rejected_receipt_obj: ReceiptV1 | None = None
        if write_receipt:
            rejected_receipt_payload, rejected_receipt_obj = _write_rejected_receipt(
                plan=plan,
                actor_ctx=actor_ctx,
            )
        if plan.proposal_id:
            cp_store = control_plane_store(Path(app.state.state_dir))
            cp_store.record_outcome(
                plan.proposal_id,
                "rejected",
                actor=actor_ctx.actor_id,
                detail={"run_id": plan.run_id, "compile_id": plan.compile_id, "reason": reason or ""},
            )
            cp_store.update_proposal_status(plan.proposal_id, "rejected")
        _archive_pending(Path(app.state.state_dir), plan.run_id, status="rejected")
        response = {"status": "rejected", "run_id": plan.run_id, "reason": reason or ""}
        if rejected_receipt_payload is not None:
            response["receipt"] = rejected_receipt_payload
        return response, rejected_receipt_obj

    async def _approve_pending_run(
        *,
        request: Request,
        run_id: str,
        approve_token: str | None = None,
        selected_step_ids: list[str] | None = None,
        metadata_action: str = "approve_http",
    ) -> dict[str, Any]:
        policy = apply_policy_overlays(app.state.policy, Path(app.state.state_dir))
        actor_ctx = _require_roles(
            request,
            allowed=_approval_allowed_roles(policy),
            reason_code="rbac_forbidden",
            required_for="pending_approve",
        )
        receipt_payload, _receipt = _approve_pending_with_actor(
            actor_ctx=actor_ctx,
            run_id=run_id,
            approve_token=approve_token,
            selected_step_ids=selected_step_ids,
            metadata_action=metadata_action,
        )
        return receipt_payload

    async def _reject_pending_run(
        *,
        request: Request,
        run_id: str,
        approve_token: str | None = None,
        reason: str | None = None,
        metadata_action: str = "reject_http",
    ) -> dict[str, Any]:
        policy = apply_policy_overlays(app.state.policy, Path(app.state.state_dir))
        actor_ctx = _require_roles(
            request,
            allowed=_approval_allowed_roles(policy),
            reason_code="rbac_forbidden",
            required_for="pending_reject",
        )
        reject_payload, _receipt_obj = _reject_pending_with_actor(
            actor_ctx=actor_ctx,
            run_id=run_id,
            approve_token=approve_token,
            reason=reason,
            metadata_action=metadata_action,
            write_receipt=False,
        )
        return reject_payload

    def _slack_actor_context(payload: dict[str, Any]) -> ActorContext:
        user_payload = payload.get("user") if isinstance(payload.get("user"), dict) else {}
        user_id = str(user_payload.get("id") or "").strip()
        username = str(user_payload.get("username") or user_payload.get("name") or "").strip()
        actor_id = user_id or username or "slack:unknown"
        mapped_email = username if "@" in username else None
        return ActorContext(
            actor_id=actor_id,
            roles=["approver"],
            auth_source="slack",
            mapped_email=mapped_email,
            principal_type="user",
            verified_identity=True,
        )

    async def _post_slack_interaction_response(response_url: str | None, payload: dict[str, Any]) -> None:
        url = str(response_url or "").strip()
        if not url:
            return
        try:
            await asyncio.to_thread(_post_json_no_response, url, payload, timeout_seconds=5.0)
        except Exception:
            logger.warning("Slack interaction callback post failed.", exc_info=True)

    async def _post_slack_ephemeral(response_url: str | None, message: str) -> None:
        await _post_slack_interaction_response(
            response_url,
            {"response_type": "ephemeral", "text": message},
        )

    async def _post_slack_status_update(response_url: str | None, status_line: str) -> None:
        await _post_slack_interaction_response(
            response_url,
            {
                "replace_original": True,
                "text": status_line,
                "blocks": [
                    {
                        "type": "section",
                        "text": {"type": "mrkdwn", "text": status_line},
                    }
                ],
            },
        )

    def _slack_action_error_message(exc: HTTPException) -> str:
        detail = exc.detail
        detail_text = str(detail if detail is not None else "").strip()
        if isinstance(detail, dict):
            reason = str(detail.get("reason") or "").strip().lower()
            if reason == "maker_checker_violation":
                return "Approval blocked by maker-checker policy."
            if reason:
                detail_text = reason.replace("_", " ")
        lowered = detail_text.lower()
        if exc.status_code == 404:
            return "Pending action not found."
        if "invalid or expired token" in lowered or "invalid" in lowered or "expired" in lowered:
            return "Invalid or expired approval token."
        if detail_text:
            return detail_text
        return "Unable to process Slack interaction."

    def _slack_dealdesk_target(value_payload: dict[str, Any]) -> tuple[str, str | None]:
        opportunity_id = str(value_payload.get("opportunity_id") or value_payload.get("record_id") or "").strip()
        org_alias = str(value_payload.get("org_alias") or "").strip() or None
        return opportunity_id, org_alias

    async def _post_slack_rendered_blocks(response_url: str | None, payload: dict[str, Any]) -> None:
        rendered = dict(payload)
        rendered["replace_original"] = True
        await _post_slack_interaction_response(response_url, rendered)

    def _slack_view_input_value(
        view_payload: dict[str, Any],
        *,
        block_id: str,
        action_id: str,
    ) -> str:
        state_payload = view_payload.get("state") if isinstance(view_payload.get("state"), dict) else {}
        values_payload = state_payload.get("values") if isinstance(state_payload.get("values"), dict) else {}
        block_payload = values_payload.get(block_id) if isinstance(values_payload.get(block_id), dict) else {}
        action_payload = block_payload.get(action_id) if isinstance(block_payload.get(action_id), dict) else {}
        return str(action_payload.get("value") or "").strip()

    def _slack_view_private_metadata(view_payload: dict[str, Any]) -> dict[str, Any]:
        private_metadata_raw = str(view_payload.get("private_metadata") or "").strip()
        if not private_metadata_raw:
            return {}
        try:
            parsed_private_metadata = json.loads(private_metadata_raw)
        except Exception:
            return {}
        return parsed_private_metadata if isinstance(parsed_private_metadata, dict) else {}

    def _route_dealdesk_close_review_to_revops(
        *,
        summary: Any,
        submission: Any,
        seller_actor_id: str | None = None,
        source_channel: str | None = None,
    ) -> dict[str, Any]:
        if str(submission.overall_disposition or "").strip() not in {"approval_required", "block_close"}:
            return {"posted": False, "reason": "not_required"}

        slack_status = _resolve_slack_status(app, include_secret=True)
        token = str(slack_status.get("token") or "").strip()
        channel = str(slack_status.get("channel") or "").strip()
        if not token or not channel:
            return {"posted": False, "reason": "slack_not_configured"}

        try:
            web_client = slack_create_client(token)
            payload = build_revops_close_review_alert_blocks(
                summary=summary,
                submission=submission,
                seller_actor_id=seller_actor_id,
                source_channel=source_channel,
            )
            web_client.chat_postMessage(channel=channel, text=payload["text"], blocks=payload["blocks"])
            return {"posted": True, "channel": channel}
        except Exception as exc:
            logger.warning("Failed to post Deal Desk close review exception to Slack.", exc_info=True)
            return {"posted": False, "reason": "post_failed", "channel": channel, "error": str(exc)}

    def _dealdesk_routing_note(result: dict[str, Any]) -> str | None:
        if bool(result.get("posted")):
            channel = str(result.get("channel") or "").strip()
            if channel:
                return f":warning: Routed to RevOps in `{channel}` for exception review."
            return ":warning: Routed to RevOps for exception review."
        reason = str(result.get("reason") or "").strip()
        if reason in {"slack_not_configured", "post_failed"}:
            return ":warning: RevOps routing could not be posted automatically."
        return None

    def _dealdesk_pending_approval_post_marker_path(run_id: str) -> Path:
        return Path(app.state.state_dir) / "pending" / run_id / "dealdesk_approval_request.json"

    def _load_dealdesk_pending_approval_post_marker(run_id: str) -> dict[str, Any] | None:
        marker_path = _dealdesk_pending_approval_post_marker_path(run_id)
        if not marker_path.exists():
            return None
        try:
            payload = json.loads(marker_path.read_text(encoding="utf-8"))
        except Exception:
            return None
        return payload if isinstance(payload, dict) else None

    def _write_dealdesk_pending_approval_post_marker(
        *,
        run_id: str,
        channel: str,
        operation_label: str,
    ) -> Path:
        marker_path = _dealdesk_pending_approval_post_marker_path(run_id)
        marker_path.parent.mkdir(parents=True, exist_ok=True)
        payload = {
            "run_id": run_id,
            "channel": channel,
            "operation_label": operation_label,
            "posted_at": datetime.now(timezone.utc).isoformat(),
        }
        temp_path = marker_path.with_suffix(".json.tmp")
        temp_path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
        temp_path.replace(marker_path)
        return marker_path

    def _route_dealdesk_pending_write_for_approval(
        *,
        result: Any,
        actor_ctx: ActorContext,
        operation_label: str,
        approval_post: Any | None = None,
    ) -> dict[str, Any]:
        if not bool(getattr(result, "approval_required", False)):
            return {"posted": False, "reason": "not_required"}

        pending_plan_path = str(getattr(result, "pending_plan_path", "") or "").strip()
        pending_run_id = str(getattr(result, "pending_run_id", "") or getattr(result, "receipt_run_id", "") or "").strip()
        if not pending_plan_path:
            return {"posted": False, "reason": "pending_plan_missing", "pending_run_id": pending_run_id or None}

        plan_path = Path(pending_plan_path)
        if not plan_path.exists():
            return {"posted": False, "reason": "pending_plan_missing", "pending_run_id": pending_run_id or None}

        plan = PlanV1.from_dict(json.loads(plan_path.read_text(encoding="utf-8")))
        existing_pending_action = load_pending_action(Path(app.state.state_dir), plan.run_id)
        existing_pending_action_path = pending_action_file_path(Path(app.state.state_dir), plan.run_id)
        existing_post_marker = _load_dealdesk_pending_approval_post_marker(plan.run_id)
        if existing_pending_action is not None and existing_post_marker is not None:
            return {
                "posted": False,
                "reason": "already_pending",
                "channel": str(existing_post_marker.get("channel") or "").strip() or None,
                "pending_run_id": plan.run_id,
                "pending_action_path": str(existing_pending_action_path),
            }

        pending_action_path = (
            write_pending_action(
                Path(app.state.state_dir),
                build_pending_action(plan=plan, actor=actor_ctx),
            )
            if existing_pending_action is None
            else existing_pending_action_path
        )

        slack_status = _resolve_slack_status(app, include_secret=True)
        token = str(slack_status.get("token") or "").strip()
        channel = str(slack_status.get("channel") or "").strip()
        if not token or not channel:
            return {
                "posted": False,
                "reason": "slack_not_configured",
                "pending_run_id": plan.run_id,
                "pending_action_path": str(pending_action_path),
            }

        try:
            web_client = slack_create_client(token)
        except Exception as exc:
            logger.warning(
                "Failed to initialize Slack client for Deal Desk %s approval.",
                operation_label,
                exc_info=True,
            )
            return {
                "posted": False,
                "reason": "client_init_failed",
                "pending_run_id": plan.run_id,
                "pending_action_path": str(pending_action_path),
                "error": str(exc),
            }

        from .approvals import default_approval_ttl_seconds

        policy = apply_policy_overlays(app.state.policy, Path(app.state.state_dir))
        policy_hash = str(plan.policy_hash or "").strip() or compute_policy_hash(policy)
        token_store = ApprovalTokenStore(Path(app.state.state_dir) / "db" / "approval_tokens.sqlite")
        approve_token = token_store.create_token(
            plan_hash=plan.plan_hash or "",
            policy_hash=policy_hash,
            run_id=plan.run_id,
            ttl_seconds=default_approval_ttl_seconds(),
        ).token
        try:
            if approval_post is None:
                post_approval_request(web_client, channel, plan, approve_token)
            else:
                approval_post(web_client, channel, plan, approve_token)
            _write_dealdesk_pending_approval_post_marker(
                run_id=plan.run_id,
                channel=channel,
                operation_label=operation_label,
            )
            return {
                "posted": True,
                "channel": channel,
                "pending_run_id": plan.run_id,
                "pending_action_path": str(pending_action_path),
            }
        except Exception as exc:
            logger.warning(
                "Failed to post Deal Desk %s approval request to Slack.",
                operation_label,
                exc_info=True,
            )
            return {
                "posted": False,
                "reason": "post_failed",
                "channel": channel,
                "pending_run_id": plan.run_id,
                "pending_action_path": str(pending_action_path),
                "error": str(exc),
            }

    def _route_dealdesk_field_fix_for_approval(
        *,
        result: Any,
        actor_ctx: ActorContext,
    ) -> dict[str, Any]:
        return _route_dealdesk_pending_write_for_approval(
            result=result,
            actor_ctx=actor_ctx,
            operation_label="field-fix",
        )

    def _route_dealdesk_contact_role_for_approval(
        *,
        result: Any,
        actor_ctx: ActorContext,
    ) -> dict[str, Any]:
        return _route_dealdesk_pending_write_for_approval(
            result=result,
            actor_ctx=actor_ctx,
            operation_label="contact-role",
        )

    def _route_dealdesk_quote_change_for_approval(
        *,
        result: Any,
        actor_ctx: ActorContext,
    ) -> dict[str, Any]:
        def _post_quote_change_approval(web_client: Any, channel: str, plan: PlanV1, approve_token: str) -> Any:
            approval_context = load_seller_quote_revision_approval_context(
                run_id=plan.run_id,
                state_dir=str(app.state.state_dir),
            )
            if approval_context is None:
                return post_approval_request(web_client, channel, plan, approve_token)
            rendered = build_seller_quote_revision_approval_request_blocks(
                approval_context,
                plan=plan,
                approve_token=approve_token,
            )
            return web_client.chat_postMessage(
                channel=channel,
                text=str(rendered.get("text") or f"Quote change approval required for run {plan.run_id}"),
                blocks=rendered.get("blocks") or [],
            )

        return _route_dealdesk_pending_write_for_approval(
            result=result,
            actor_ctx=actor_ctx,
            operation_label="quote-change",
            approval_post=_post_quote_change_approval,
        )

    def _route_dealdesk_close_progression_for_approval(
        *,
        result: Any,
        actor_ctx: ActorContext,
    ) -> dict[str, Any]:
        pending_progression = SimpleNamespace(
            approval_required=bool(getattr(result, "progression_approval_required", False)),
            pending_plan_path=getattr(result, "progression_pending_plan_path", None),
            pending_run_id=getattr(result, "progression_pending_run_id", None),
            receipt_run_id=getattr(result, "progression_receipt_run_id", None),
        )
        return _route_dealdesk_pending_write_for_approval(
            result=pending_progression,
            actor_ctx=actor_ctx,
            operation_label="close-progression",
        )

    def _route_dealdesk_post_close_trigger_for_approval(
        *,
        result: Any,
        actor_ctx: ActorContext,
    ) -> dict[str, Any]:
        pending_run = None
        for item in getattr(result, "post_close_trigger_runs", []) or []:
            status = str(getattr(item, "status", "") or "").strip()
            pending_path = getattr(item, "pending_plan_path", None)
            if status == "approval_required" and pending_path:
                pending_run = item
                break
            if isinstance(item, dict):
                status = str(item.get("status") or "").strip()
                pending_path = item.get("pending_plan_path")
                if status == "approval_required" and pending_path:
                    pending_run = item
                    break
        if pending_run is None:
            pending_trigger = SimpleNamespace(
                approval_required=bool(getattr(result, "post_close_approval_required", False)),
                pending_plan_path=getattr(result, "post_close_pending_plan_path", None),
                pending_run_id=getattr(result, "post_close_pending_run_id", None),
                receipt_run_id=None,
            )
        elif isinstance(pending_run, dict):
            pending_trigger = SimpleNamespace(
                approval_required=True,
                pending_plan_path=pending_run.get("pending_plan_path"),
                pending_run_id=pending_run.get("pending_run_id"),
                receipt_run_id=pending_run.get("receipt_run_id"),
            )
        else:
            pending_trigger = SimpleNamespace(
                approval_required=True,
                pending_plan_path=getattr(pending_run, "pending_plan_path", None),
                pending_run_id=getattr(pending_run, "pending_run_id", None),
                receipt_run_id=getattr(pending_run, "receipt_run_id", None),
            )
        return _route_dealdesk_pending_write_for_approval(
            result=pending_trigger,
            actor_ctx=actor_ctx,
            operation_label="post-close",
        )

    def _dealdesk_pending_write_routing_note(result: dict[str, Any]) -> str | None:
        if bool(result.get("posted")):
            channel = str(result.get("channel") or "").strip()
            if channel:
                return f":warning: Routed to RevOps in `{channel}` for approval."
            return ":warning: Routed to RevOps for approval."
        reason = str(result.get("reason") or "").strip()
        pending_run_id = str(result.get("pending_run_id") or "").strip()
        if reason == "already_pending":
            channel = str(result.get("channel") or "").strip()
            if channel and pending_run_id:
                return f":information_source: Approval is already pending in `{channel}` as run `{pending_run_id}`."
            if pending_run_id:
                return f":information_source: Approval is already pending as run `{pending_run_id}`."
            return ":information_source: Approval is already pending."
        if reason in {"slack_not_configured", "client_init_failed", "post_failed"}:
            if pending_run_id:
                return (
                    f":warning: Approval required. Pending run `{pending_run_id}` was created, "
                    "but automatic Slack routing is not available."
                )
            return ":warning: Approval required, but automatic Slack routing is not available."
        return None

    def _dealdesk_field_fix_routing_note(result: dict[str, Any]) -> str | None:
        return _dealdesk_pending_write_routing_note(result)

    def _dealdesk_contact_role_routing_note(result: dict[str, Any]) -> str | None:
        return _dealdesk_pending_write_routing_note(result)

    def _dealdesk_quote_change_routing_note(result: dict[str, Any]) -> str | None:
        return _dealdesk_pending_write_routing_note(result)

    def _dealdesk_close_progression_routing_note(result: dict[str, Any]) -> str | None:
        return _dealdesk_pending_write_routing_note(result)

    def _dealdesk_post_close_routing_note(result: dict[str, Any]) -> str | None:
        return _dealdesk_pending_write_routing_note(result)

    def _dealdesk_quote_change_result_from_context(
        *,
        approval_context: Any,
        receipt_outcome: str,
        receipt_run_id: str | None,
        receipt_dir: str | None,
    ) -> SellerQuoteRevisionApplyResultV1:
        updated_quote_status = get_seller_quote_status_summary(
            opportunity_id=str(approval_context.opportunity_id),
            org_alias=getattr(approval_context, "org_alias", None),
            state_dir=str(app.state.state_dir),
            force_refresh=True,
        )
        return SellerQuoteRevisionApplyResultV1(
            opportunity_id=str(approval_context.opportunity_id),
            crm_org_id=str(approval_context.crm_org_id),
            org_alias=getattr(approval_context, "org_alias", None),
            intent=approval_context.intent,
            field_preview=approval_context.field_preview,
            line_item_preview=getattr(approval_context, "line_item_preview", None),
            preview_policy_state=approval_context.preview_policy_state,
            receipt_run_id=receipt_run_id,
            receipt_dir=receipt_dir,
            receipt_outcome=receipt_outcome,
            applied=receipt_outcome == "success",
            already_set=receipt_outcome == "already_set",
            approval_required=False,
            updated_quote_status=updated_quote_status,
        )

    @app.post("/api/slack/commands")
    async def slack_commands(request: Request) -> JSONResponse:
        signing_secret = str(os.getenv("G_GREMLIN_SLACK_SIGNING_SECRET") or "").strip()
        if not signing_secret:
            raise HTTPException(status_code=403, detail="Slack signing secret is not configured")

        raw_body = await request.body()
        timestamp = str(request.headers.get("x-slack-request-timestamp") or "").strip()
        signature = str(request.headers.get("x-slack-signature") or "").strip()
        if not verify_slack_signature(signing_secret, timestamp, raw_body, signature):
            raise HTTPException(status_code=403, detail="Invalid Slack signature")

        parsed_form = parse_qs(raw_body.decode("utf-8", errors="replace"), keep_blank_values=True)
        command_text = str((parsed_form.get("text") or [""])[0] or "").strip()
        seller_actor_id = str((parsed_form.get("user_id") or [""])[0] or "").strip() or None
        seller_username = str((parsed_form.get("user_name") or [""])[0] or "").strip() or None
        source_channel = (
            str((parsed_form.get("channel_name") or [""])[0] or "").strip()
            or str((parsed_form.get("channel_id") or [""])[0] or "").strip()
            or None
        )

        try:
            seller_command = parse_seller_command(command_text)
        except ValueError as exc:
            return JSONResponse(
                {
                    "response_type": "ephemeral",
                    "text": str(exc) or seller_command_usage_text(),
                },
                status_code=200,
            )

        try:
            if seller_command.action == "fix_missing_field":
                seller_actor_ctx = _slack_actor_context(
                    {"user": {"id": seller_actor_id or "", "username": seller_username or ""}}
                )
                result = apply_seller_missing_field_fix(
                    opportunity_id=seller_command.opportunity_id,
                    field_name=str(seller_command.field_name or ""),
                    field_value=str(seller_command.field_value or ""),
                    org_alias=seller_command.org_alias,
                    state_dir=str(app.state.state_dir),
                    policy=apply_policy_overlays(app.state.policy, Path(app.state.state_dir)),
                )
                routing_note = None
                if bool(getattr(result, "approval_required", False)):
                    routing = await asyncio.to_thread(
                        _route_dealdesk_field_fix_for_approval,
                        result=result,
                        actor_ctx=seller_actor_ctx,
                    )
                    routing_note = _dealdesk_field_fix_routing_note(routing)
                payload = build_field_fix_result_blocks(result, routing_note=routing_note)
            elif seller_command.action == "add_contact_role":
                seller_actor_ctx = _slack_actor_context(
                    {"user": {"id": seller_actor_id or "", "username": seller_username or ""}}
                )
                result = apply_seller_contact_role_add(
                    opportunity_id=seller_command.opportunity_id,
                    contact_reference=str(seller_command.contact_reference or ""),
                    role_name=str(seller_command.role_name or ""),
                    org_alias=seller_command.org_alias,
                    state_dir=str(app.state.state_dir),
                    policy=apply_policy_overlays(app.state.policy, Path(app.state.state_dir)),
                )
                routing_note = None
                if bool(getattr(result, "approval_required", False)):
                    routing = await asyncio.to_thread(
                        _route_dealdesk_contact_role_for_approval,
                        result=result,
                        actor_ctx=seller_actor_ctx,
                    )
                    routing_note = _dealdesk_contact_role_routing_note(routing)
                payload = build_contact_role_result_blocks(result, routing_note=routing_note)
            elif seller_command.action == "submit_for_close":
                seller_actor_ctx = _slack_actor_context(
                    {"user": {"id": seller_actor_id or "", "username": seller_username or ""}}
                )
                submission = submit_deal_for_close_review(
                    opportunity_id=seller_command.opportunity_id,
                    policy=apply_policy_overlays(app.state.policy, Path(app.state.state_dir)),
                    org_alias=seller_command.org_alias,
                    state_dir=str(app.state.state_dir),
                    force_refresh=False,
                )
                routing_note = None
                if str(submission.overall_disposition or "").strip() in {"approval_required", "block_close"}:
                    summary = get_seller_deal_health_summary(
                        opportunity_id=seller_command.opportunity_id,
                        org_alias=seller_command.org_alias,
                        state_dir=str(app.state.state_dir),
                        force_refresh=False,
                    )
                    routing = await asyncio.to_thread(
                        _route_dealdesk_close_review_to_revops,
                        summary=summary,
                        submission=submission,
                        seller_actor_id=seller_actor_id,
                        source_channel=source_channel,
                    )
                    routing_note = _dealdesk_routing_note(routing)
                elif bool(getattr(submission, "progression_approval_required", False)):
                    routing = await asyncio.to_thread(
                        _route_dealdesk_close_progression_for_approval,
                        result=submission,
                        actor_ctx=seller_actor_ctx,
                    )
                    routing_note = _dealdesk_close_progression_routing_note(routing)
                elif bool(getattr(submission, "post_close_approval_required", False)):
                    routing = await asyncio.to_thread(
                        _route_dealdesk_post_close_trigger_for_approval,
                        result=submission,
                        actor_ctx=seller_actor_ctx,
                    )
                    routing_note = _dealdesk_post_close_routing_note(routing)
                payload = build_close_review_submission_blocks(submission, routing_note=routing_note)
            elif seller_command.action == "quote_status":
                opportunity_id = str(seller_command.opportunity_id or "").strip() or None
                if opportunity_id is None:
                    lookup = lookup_seller_active_deals(
                        query=str(seller_command.deal_query or ""),
                        org_alias=seller_command.org_alias,
                    )
                    if lookup.resolved_opportunity_id:
                        opportunity_id = lookup.resolved_opportunity_id
                    else:
                        payload = build_deal_lookup_blocks(
                            lookup,
                            action_id="dealdesk_seller_refresh_quote_status",
                            button_text="Open Quote Status",
                            context_note=(
                                "Use the explicit opportunity id if you want quote status, approval visibility, "
                                "or stale/conflict checks for a specific deal."
                            ),
                        )
                        payload["response_type"] = "ephemeral"
                        return JSONResponse(payload, status_code=200)
                quote_status = get_seller_quote_status_summary(
                    opportunity_id=opportunity_id,
                    org_alias=seller_command.org_alias,
                    state_dir=str(app.state.state_dir),
                    force_refresh=False,
                )
                payload = build_seller_quote_status_blocks(quote_status)
            elif seller_command.action == "quote_preview":
                opportunity_id = str(seller_command.opportunity_id or "").strip() or None
                if opportunity_id is None:
                    lookup = lookup_seller_active_deals(
                        query=str(seller_command.deal_query or ""),
                        org_alias=seller_command.org_alias,
                    )
                    if lookup.resolved_opportunity_id:
                        opportunity_id = lookup.resolved_opportunity_id
                    else:
                        payload = build_deal_lookup_blocks(
                            lookup,
                            action_id="dealdesk_seller_preview_quote_change",
                            button_text="Open Quote Preview",
                            context_note=(
                                "Select the active deal you want to preview before Gremlin shows the quote change diff."
                            ),
                            extra_payload={
                                "quote_operation": seller_command.quote_operation,
                                "quote_requested_value": seller_command.quote_requested_value,
                                "raw_text": seller_command.raw_text,
                            },
                        )
                        payload["response_type"] = "ephemeral"
                        return JSONResponse(payload, status_code=200)
                intent = build_quote_intent_v1(
                    opportunity_id=opportunity_id,
                    operation=str(seller_command.quote_operation or ""),
                    requested_value=str(seller_command.quote_requested_value or ""),
                    raw_request=seller_command.raw_text,
                    org_alias=seller_command.org_alias,
                )
                preview = preview_seller_quote_revision(
                    intent=intent,
                    state_dir=str(app.state.state_dir),
                    force_refresh=False,
                )
                payload = build_seller_quote_revision_preview_blocks(preview)
            else:
                opportunity_id = str(seller_command.opportunity_id or "").strip() or None
                if opportunity_id is None:
                    lookup = lookup_seller_active_deals(
                        query=str(seller_command.deal_query or ""),
                        org_alias=seller_command.org_alias,
                    )
                    if lookup.resolved_opportunity_id:
                        opportunity_id = lookup.resolved_opportunity_id
                    else:
                        payload = build_deal_lookup_blocks(lookup)
                        payload["response_type"] = "ephemeral"
                        return JSONResponse(payload, status_code=200)
                summary = get_seller_deal_health_summary(
                    opportunity_id=opportunity_id,
                    org_alias=seller_command.org_alias,
                    state_dir=str(app.state.state_dir),
                    force_refresh=False,
                )
                payload = build_seller_deal_health_blocks(summary)
        except Exception as exc:
            logger.warning("Deal Desk Slack command failed.", exc_info=True)
            return JSONResponse(
                {
                    "response_type": "ephemeral",
                    "text": f"Deal Desk request failed: {exc}",
                },
                status_code=200,
            )

        payload["response_type"] = "ephemeral"
        return JSONResponse(payload, status_code=200)

    @app.post("/api/slack/interactions")
    async def slack_interactions(request: Request) -> PlainTextResponse:
        signing_secret = str(os.getenv("G_GREMLIN_SLACK_SIGNING_SECRET") or "").strip()
        if not signing_secret:
            raise HTTPException(status_code=403, detail="Slack signing secret is not configured")

        raw_body = await request.body()
        timestamp = str(request.headers.get("x-slack-request-timestamp") or "").strip()
        signature = str(request.headers.get("x-slack-signature") or "").strip()
        if not verify_slack_signature(signing_secret, timestamp, raw_body, signature):
            raise HTTPException(status_code=403, detail="Invalid Slack signature")

        parsed_form = parse_qs(raw_body.decode("utf-8", errors="replace"), keep_blank_values=True)
        payload_raw = (parsed_form.get("payload") or [None])[0]
        if not isinstance(payload_raw, str) or not payload_raw.strip():
            return PlainTextResponse("", status_code=200)

        try:
            payload = json.loads(payload_raw)
        except Exception:
            return PlainTextResponse("", status_code=200)
        if not isinstance(payload, dict):
            return PlainTextResponse("", status_code=200)

        payload_type = str(payload.get("type") or "").strip()
        if payload_type == "view_submission":
            view_payload = payload.get("view") if isinstance(payload.get("view"), dict) else {}
            callback_id = str(view_payload.get("callback_id") or "").strip()
            if callback_id == "dealdesk_seller_fix_field_modal":
                private_metadata = _slack_view_private_metadata(view_payload)
                opportunity_id = str(private_metadata.get("opportunity_id") or "").strip()
                field_name = str(private_metadata.get("field_name") or "").strip()
                org_alias = str(private_metadata.get("org_alias") or "").strip() or None
                response_url = str(private_metadata.get("response_url") or "").strip()
                field_value = _slack_view_input_value(
                    view_payload,
                    block_id="field_value_input",
                    action_id="field_value",
                )
                if not opportunity_id or not field_name:
                    return JSONResponse(
                        {
                            "response_action": "errors",
                            "errors": {
                                "field_value_input": "Fix context is missing. Re-open the guided fix from the deal health card."
                            },
                        },
                        status_code=200,
                    )
                if not field_value:
                    return JSONResponse(
                        {
                            "response_action": "errors",
                            "errors": {"field_value_input": "Enter a value before applying the fix."},
                        },
                        status_code=200,
                    )

                actor_ctx = _slack_actor_context(payload)
                try:
                    result = apply_seller_missing_field_fix(
                        opportunity_id=opportunity_id,
                        field_name=field_name,
                        field_value=field_value,
                        org_alias=org_alias,
                        state_dir=str(app.state.state_dir),
                        policy=apply_policy_overlays(app.state.policy, Path(app.state.state_dir)),
                    )
                    routing_note = None
                    if bool(getattr(result, "approval_required", False)):
                        routing = await asyncio.to_thread(
                            _route_dealdesk_field_fix_for_approval,
                            result=result,
                            actor_ctx=actor_ctx,
                        )
                        routing_note = _dealdesk_field_fix_routing_note(routing)
                    rendered = build_field_fix_result_blocks(result, routing_note=routing_note)
                except Exception as exc:
                    logger.warning("Deal Desk seller fix modal submission failed.", exc_info=True)
                    return JSONResponse(
                        {
                            "response_action": "errors",
                            "errors": {"field_value_input": str(exc) or "Unable to apply this field fix."},
                        },
                        status_code=200,
                    )

                await _post_slack_rendered_blocks(response_url, rendered)
                return PlainTextResponse("", status_code=200)
            if callback_id == "dealdesk_seller_contact_role_modal":
                private_metadata = _slack_view_private_metadata(view_payload)
                opportunity_id = str(private_metadata.get("opportunity_id") or "").strip()
                org_alias = str(private_metadata.get("org_alias") or "").strip() or None
                response_url = str(private_metadata.get("response_url") or "").strip()
                contact_reference = _slack_view_input_value(
                    view_payload,
                    block_id="contact_reference_input",
                    action_id="contact_reference",
                )
                role_name = _slack_view_input_value(
                    view_payload,
                    block_id="role_name_input",
                    action_id="role_name",
                )
                if not opportunity_id:
                    return JSONResponse(
                        {
                            "response_action": "errors",
                            "errors": {
                                "contact_reference_input": "Contact context is missing. Re-open the guided contact flow from the deal health card."
                            },
                        },
                        status_code=200,
                    )
                errors: dict[str, str] = {}
                if not contact_reference:
                    errors["contact_reference_input"] = "Enter an exact contact email or Salesforce Contact Id."
                if not role_name:
                    errors["role_name_input"] = "Enter a role before attaching the contact."
                if errors:
                    return JSONResponse(
                        {"response_action": "errors", "errors": errors},
                        status_code=200,
                    )

                actor_ctx = _slack_actor_context(payload)
                try:
                    result = apply_seller_contact_role_add(
                        opportunity_id=opportunity_id,
                        contact_reference=contact_reference,
                        role_name=role_name,
                        org_alias=org_alias,
                        state_dir=str(app.state.state_dir),
                        policy=apply_policy_overlays(app.state.policy, Path(app.state.state_dir)),
                    )
                    routing_note = None
                    if bool(getattr(result, "approval_required", False)):
                        routing = await asyncio.to_thread(
                            _route_dealdesk_contact_role_for_approval,
                            result=result,
                            actor_ctx=actor_ctx,
                        )
                        routing_note = _dealdesk_contact_role_routing_note(routing)
                    rendered = build_contact_role_result_blocks(result, routing_note=routing_note)
                except Exception as exc:
                    logger.warning("Deal Desk seller contact modal submission failed.", exc_info=True)
                    error_message = str(exc) or "Unable to attach this contact."
                    error_block = "role_name_input" if "role" in error_message.lower() else "contact_reference_input"
                    return JSONResponse(
                        {
                            "response_action": "errors",
                            "errors": {error_block: error_message},
                        },
                        status_code=200,
                    )

                await _post_slack_rendered_blocks(response_url, rendered)
                return PlainTextResponse("", status_code=200)
            return PlainTextResponse("", status_code=200)

        actions = payload.get("actions") if isinstance(payload.get("actions"), list) else []
        action = actions[0] if actions and isinstance(actions[0], dict) else {}
        action_id = str(action.get("action_id") or "").strip()
        value_raw = action.get("value")
        value_payload: dict[str, Any] = {}
        if isinstance(value_raw, str) and value_raw.strip():
            try:
                parsed_value = json.loads(value_raw)
                if isinstance(parsed_value, dict):
                    value_payload = parsed_value
            except Exception:
                value_payload = {}

        run_id = str(value_payload.get("run_id") or "").strip()
        approve_token_raw = value_payload.get("approve_token")
        approve_token = str(approve_token_raw).strip() if approve_token_raw not in (None, "") else None
        response_url = str(payload.get("response_url") or "").strip()
        channel_payload = payload.get("channel") if isinstance(payload.get("channel"), dict) else {}
        interaction_channel = str(channel_payload.get("id") or channel_payload.get("name") or "").strip()

        if action_id in {
            "dealdesk_seller_refresh_health",
            "dealdesk_seller_refresh_quote_status",
            "dealdesk_seller_apply_quote_change",
            "dealdesk_seller_preview_quote_change",
            "dealdesk_seller_submit_for_close",
            "dealdesk_seller_open_contact_role_modal",
            "dealdesk_seller_open_fix_field_modal",
        }:
            opportunity_id, org_alias = _slack_dealdesk_target(value_payload)
            if not opportunity_id:
                await _post_slack_ephemeral(response_url, seller_command_usage_text())
                return PlainTextResponse("", status_code=200)

            actor_ctx = _slack_actor_context(payload)
            try:
                if action_id == "dealdesk_seller_open_fix_field_modal":
                    field_name = str(value_payload.get("field_name") or "").strip()
                    if not field_name:
                        await _post_slack_ephemeral(response_url, seller_command_usage_text())
                        return PlainTextResponse("", status_code=200)
                    trigger_id = str(payload.get("trigger_id") or "").strip()
                    slack_status = _resolve_slack_status(app, include_secret=True)
                    slack_token = str(slack_status.get("token") or "").strip()
                    if not trigger_id or not slack_token:
                        await _post_slack_ephemeral(
                            response_url,
                            seller_fix_modal_fallback_text(
                                opportunity_id=opportunity_id,
                                field_name=field_name,
                                org_alias=org_alias,
                            ),
                        )
                        return PlainTextResponse("", status_code=200)
                    try:
                        web_client = slack_create_client(slack_token)
                        await asyncio.to_thread(
                            web_client.views_open,
                            trigger_id=trigger_id,
                            view=build_seller_field_fix_modal(
                                opportunity_id=opportunity_id,
                                field_name=field_name,
                                org_alias=org_alias,
                                response_url=response_url,
                            ),
                        )
                    except Exception:
                        logger.warning("Deal Desk seller fix modal open failed.", exc_info=True)
                        await _post_slack_ephemeral(
                            response_url,
                            seller_fix_modal_fallback_text(
                                opportunity_id=opportunity_id,
                                field_name=field_name,
                                org_alias=org_alias,
                            ),
                        )
                    return PlainTextResponse("", status_code=200)
                if action_id == "dealdesk_seller_open_contact_role_modal":
                    trigger_id = str(payload.get("trigger_id") or "").strip()
                    slack_status = _resolve_slack_status(app, include_secret=True)
                    slack_token = str(slack_status.get("token") or "").strip()
                    if not trigger_id or not slack_token:
                        await _post_slack_ephemeral(
                            response_url,
                            seller_contact_modal_fallback_text(
                                opportunity_id=opportunity_id,
                                org_alias=org_alias,
                            ),
                        )
                        return PlainTextResponse("", status_code=200)
                    try:
                        web_client = slack_create_client(slack_token)
                        await asyncio.to_thread(
                            web_client.views_open,
                            trigger_id=trigger_id,
                            view=build_seller_contact_role_modal(
                                opportunity_id=opportunity_id,
                                org_alias=org_alias,
                                response_url=response_url,
                            ),
                        )
                    except Exception:
                        logger.warning("Deal Desk seller contact modal open failed.", exc_info=True)
                        await _post_slack_ephemeral(
                            response_url,
                            seller_contact_modal_fallback_text(
                                opportunity_id=opportunity_id,
                                org_alias=org_alias,
                            ),
                        )
                    return PlainTextResponse("", status_code=200)
                if action_id == "dealdesk_seller_submit_for_close":
                    submission = submit_deal_for_close_review(
                        opportunity_id=opportunity_id,
                        policy=apply_policy_overlays(app.state.policy, Path(app.state.state_dir)),
                        org_alias=org_alias,
                        state_dir=str(app.state.state_dir),
                        force_refresh=False,
                    )
                    routing_note = None
                    if str(submission.overall_disposition or "").strip() in {"approval_required", "block_close"}:
                        summary = get_seller_deal_health_summary(
                            opportunity_id=opportunity_id,
                            org_alias=org_alias,
                            state_dir=str(app.state.state_dir),
                            force_refresh=False,
                        )
                        routing = await asyncio.to_thread(
                            _route_dealdesk_close_review_to_revops,
                            summary=summary,
                            submission=submission,
                            seller_actor_id=actor_ctx.actor_id,
                            source_channel=interaction_channel,
                        )
                        routing_note = _dealdesk_routing_note(routing)
                    elif bool(getattr(submission, "progression_approval_required", False)):
                        routing = await asyncio.to_thread(
                            _route_dealdesk_close_progression_for_approval,
                            result=submission,
                            actor_ctx=actor_ctx,
                        )
                        routing_note = _dealdesk_close_progression_routing_note(routing)
                    elif bool(getattr(submission, "post_close_approval_required", False)):
                        routing = await asyncio.to_thread(
                            _route_dealdesk_post_close_trigger_for_approval,
                            result=submission,
                            actor_ctx=actor_ctx,
                    )
                    routing_note = _dealdesk_post_close_routing_note(routing)
                    rendered = build_close_review_submission_blocks(submission, routing_note=routing_note)
                elif action_id == "dealdesk_seller_refresh_quote_status":
                    quote_status = get_seller_quote_status_summary(
                        opportunity_id=opportunity_id,
                        org_alias=org_alias,
                        state_dir=str(app.state.state_dir),
                        force_refresh=False,
                    )
                    rendered = build_seller_quote_status_blocks(quote_status)
                elif action_id == "dealdesk_seller_preview_quote_change":
                    intent = build_quote_intent_v1(
                        opportunity_id=opportunity_id,
                        operation=str(value_payload.get("quote_operation") or ""),
                        requested_value=str(value_payload.get("quote_requested_value") or ""),
                        raw_request=str(value_payload.get("raw_text") or ""),
                        org_alias=org_alias,
                    )
                    preview = preview_seller_quote_revision(
                        intent=intent,
                        state_dir=str(app.state.state_dir),
                        force_refresh=False,
                    )
                    rendered = build_seller_quote_revision_preview_blocks(preview)
                elif action_id == "dealdesk_seller_apply_quote_change":
                    intent = build_quote_intent_v1(
                        opportunity_id=opportunity_id,
                        operation=str(value_payload.get("quote_operation") or ""),
                        requested_value=str(value_payload.get("quote_requested_value") or ""),
                        raw_request=str(value_payload.get("raw_text") or ""),
                        org_alias=org_alias,
                    )
                    result = apply_seller_quote_revision(
                        intent=intent,
                        policy=apply_policy_overlays(app.state.policy, Path(app.state.state_dir)),
                        state_dir=str(app.state.state_dir),
                    )
                    routing_note = None
                    if bool(getattr(result, "approval_required", False)):
                        routing = await asyncio.to_thread(
                            _route_dealdesk_quote_change_for_approval,
                            result=result,
                            actor_ctx=actor_ctx,
                        )
                        routing_note = _dealdesk_quote_change_routing_note(routing)
                    rendered = build_seller_quote_revision_apply_result_blocks(
                        result,
                        routing_note=routing_note,
                    )
                else:
                    summary = get_seller_deal_health_summary(
                        opportunity_id=opportunity_id,
                        org_alias=org_alias,
                        state_dir=str(app.state.state_dir),
                        force_refresh=False,
                    )
                    rendered = build_seller_deal_health_blocks(summary)
            except Exception:
                logger.warning("Deal Desk seller Slack interaction failed.", exc_info=True)
                await _post_slack_ephemeral(response_url, "Unexpected error while processing Deal Desk action.")
                return PlainTextResponse("", status_code=200)

            await _post_slack_rendered_blocks(response_url, rendered)
            return PlainTextResponse("", status_code=200)

        if not run_id:
            await _post_slack_ephemeral(response_url, "Missing run_id in Slack action payload.")
            return PlainTextResponse("", status_code=200)

        if load_pending_action(Path(app.state.state_dir), run_id) is None:
            await _post_slack_ephemeral(response_url, f"Pending run {run_id} was not found.")
            return PlainTextResponse("", status_code=200)

        actor_ctx = _slack_actor_context(payload)
        now_utc = datetime.now(timezone.utc).isoformat()
        quote_approval_context = load_seller_quote_revision_approval_context(
            run_id=run_id,
            state_dir=str(app.state.state_dir),
        )

        if action_id == "autopilot_approve_run":
            try:
                _receipt_payload, receipt_obj = _approve_pending_with_actor(
                    actor_ctx=actor_ctx,
                    run_id=run_id,
                    approve_token=approve_token,
                    selected_step_ids=None,
                    metadata_action="approve_slack_interaction",
                )
            except HTTPException as exc:
                await _post_slack_ephemeral(response_url, _slack_action_error_message(exc))
                return PlainTextResponse("", status_code=200)
            except Exception:
                logger.warning("Slack approval interaction failed.", exc_info=True)
                await _post_slack_ephemeral(response_url, "Unexpected error while processing approval.")
                return PlainTextResponse("", status_code=200)

            status_line = f":white_check_mark: Approved by `{actor_ctx.actor_id}` at `{now_utc}`"
            if quote_approval_context is not None:
                try:
                    result = _dealdesk_quote_change_result_from_context(
                        approval_context=quote_approval_context,
                        receipt_outcome=str(receipt_obj.outcome or "unknown"),
                        receipt_run_id=str(receipt_obj.run_id or run_id),
                        receipt_dir=None,
                    )
                    rendered = build_seller_quote_revision_apply_result_blocks(result, routing_note=status_line)
                    await _post_slack_rendered_blocks(response_url, rendered)
                    return PlainTextResponse("", status_code=200)
                except Exception:
                    logger.warning("Quote change approval result render failed.", exc_info=True)
            await _post_slack_status_update(response_url, status_line)

            slack_status = _resolve_slack_status(app, include_secret=True)
            summary_token = str(slack_status.get("token") or "").strip()
            summary_channel = interaction_channel or str(slack_status.get("channel") or "").strip()
            if summary_token and summary_channel:
                try:
                    web_client = slack_create_client(summary_token)
                    post_receipt_summary(web_client, summary_channel, receipt_obj)
                except Exception:
                    logger.warning("Slack receipt summary post failed after approve interaction.", exc_info=True)
            return PlainTextResponse("", status_code=200)

        if action_id == "autopilot_reject":
            try:
                _reject_payload, reject_receipt_obj = _reject_pending_with_actor(
                    actor_ctx=actor_ctx,
                    run_id=run_id,
                    approve_token=approve_token,
                    reason="Rejected via Slack interaction",
                    metadata_action="reject_slack_interaction",
                    write_receipt=True,
                )
            except HTTPException as exc:
                await _post_slack_ephemeral(response_url, _slack_action_error_message(exc))
                return PlainTextResponse("", status_code=200)
            except Exception:
                logger.warning("Slack reject interaction failed.", exc_info=True)
                await _post_slack_ephemeral(response_url, "Unexpected error while processing rejection.")
                return PlainTextResponse("", status_code=200)

            status_line = f":no_entry: Rejected by `{actor_ctx.actor_id}` at `{now_utc}`"
            if quote_approval_context is not None and reject_receipt_obj is not None:
                try:
                    result = _dealdesk_quote_change_result_from_context(
                        approval_context=quote_approval_context,
                        receipt_outcome=str(reject_receipt_obj.outcome or "rejected"),
                        receipt_run_id=str(reject_receipt_obj.run_id or run_id),
                        receipt_dir=None,
                    )
                    rendered = build_seller_quote_revision_apply_result_blocks(result, routing_note=status_line)
                    await _post_slack_rendered_blocks(response_url, rendered)
                    return PlainTextResponse("", status_code=200)
                except Exception:
                    logger.warning("Quote change rejection result render failed.", exc_info=True)
            await _post_slack_status_update(response_url, status_line)
            slack_status = _resolve_slack_status(app, include_secret=True)
            summary_token = str(slack_status.get("token") or "").strip()
            summary_channel = interaction_channel or str(slack_status.get("channel") or "").strip()
            if summary_token and summary_channel and reject_receipt_obj is not None:
                try:
                    web_client = slack_create_client(summary_token)
                    post_receipt_summary(web_client, summary_channel, reject_receipt_obj)
                except Exception:
                    logger.warning("Slack rejection summary post failed after interaction.", exc_info=True)
            return PlainTextResponse("", status_code=200)

        await _post_slack_ephemeral(
            response_url,
            f"Unsupported Slack action: {action_id or 'unknown'}.",
        )
        return PlainTextResponse("", status_code=200)

    @app.post("/api/admin/pending/batch/approve")
    async def approve_pending_batch(request: Request) -> dict[str, Any]:
        return await _approve_pending_batch_impl(request)

    @app.post("/api/admin/pending/batch/reject")
    async def reject_pending_batch(request: Request) -> dict[str, Any]:
        return await _reject_pending_batch_impl(request)

    @app.post("/api/admin/pending/{run_id}/approve")
    async def approve_pending(request: Request, run_id: str) -> dict[str, Any]:
        payload = await request.json()
        approve_token = (payload or {}).get("approve_token") if isinstance(payload, dict) else None
        selected_step_ids_raw = (
            (payload or {}).get("selected_step_ids") if isinstance(payload, dict) else None
        )
        selected_step_ids: list[str] | None = None
        if selected_step_ids_raw is not None:
            if not isinstance(selected_step_ids_raw, list):
                raise HTTPException(status_code=400, detail="selected_step_ids must be a list")
            selected_step_ids = []
            for value in selected_step_ids_raw:
                if value is None:
                    continue
                step_id = str(value).strip()
                if step_id:
                    selected_step_ids.append(step_id)
        return await _approve_pending_run(
            request=request,
            run_id=run_id,
            approve_token=approve_token,
            selected_step_ids=selected_step_ids,
            metadata_action="approve_http",
        )

    @app.post("/api/admin/pending/{run_id}/reject")
    async def reject_pending(request: Request, run_id: str) -> dict[str, Any]:
        payload = await request.json()
        approve_token = (payload or {}).get("approve_token") if isinstance(payload, dict) else None
        reason = str((payload or {}).get("reason") or "").strip() if isinstance(payload, dict) else ""
        return await _reject_pending_run(
            request=request,
            run_id=run_id,
            approve_token=approve_token,
            reason=reason or None,
            metadata_action="reject_http",
        )

    async def _approve_pending_batch_impl(request: Request) -> dict[str, Any]:
        payload: dict[str, Any]
        try:
            parsed = await request.json()
        except Exception:
            parsed = {}
        if parsed is None:
            parsed = {}
        if not isinstance(parsed, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed

        run_ids_raw = payload.get("run_ids")
        if not isinstance(run_ids_raw, list):
            raise HTTPException(status_code=400, detail="run_ids must be a list")
        run_ids = sorted({str(value).strip() for value in run_ids_raw if str(value).strip()})
        if not run_ids:
            raise HTTPException(status_code=400, detail="run_ids must not be empty")

        idempotency_key = str(payload.get("idempotency_key") or "").strip()
        if not idempotency_key:
            raise HTTPException(status_code=400, detail="idempotency_key is required")

        expected_cluster_signature = str(payload.get("expected_cluster_signature") or "").strip() or None
        selected_by_run_raw = payload.get("selected_step_ids_by_run")
        selected_by_run = selected_by_run_raw if isinstance(selected_by_run_raw, dict) else {}

        request_hash = _stable_sha256(
            {
                "run_ids": run_ids,
                "idempotency_key": idempotency_key,
                "expected_cluster_signature": expected_cluster_signature,
                "selected_step_ids_by_run": selected_by_run,
            }
        )
        actor = _request_actor(request)
        action_name = "approve"
        state_dir = Path(app.state.state_dir)
        idempotency_db = _batch_actions_db_path(state_dir)
        cached_entry = _load_idempotent_batch_entry(
            idempotency_db,
            actor=actor,
            action=action_name,
            idempotency_key=idempotency_key,
        )
        if cached_entry is not None:
            cached_hash = str(cached_entry.get("request_hash") or "").strip()
            if cached_hash and cached_hash != request_hash:
                raise HTTPException(
                    status_code=409,
                    detail="idempotency_key was already used with a different request payload",
                )
            cached = _load_idempotent_batch_response(
                idempotency_db,
                actor=actor,
                action=action_name,
                idempotency_key=idempotency_key,
            )
            if cached is not None:
                cached["idempotent_replay"] = True
                return cached

        pending_by_run = {str(item.get("run_id") or ""): item for item in _load_pending_items(state_dir)}
        critical_runs = [
            run_id
            for run_id in run_ids
            if _coerce_int((pending_by_run.get(run_id, {}).get("risk") or {}).get("max_risk_level"), 0) >= 3
        ]
        batch_id = secrets.token_hex(12)
        risk_matrix = [
            {
                "run_id": run_id,
                "risk_level": _coerce_int((pending_by_run.get(run_id, {}).get("risk") or {}).get("max_risk_level"), 0),
                "risk_tier": _risk_from_level(
                    _coerce_int((pending_by_run.get(run_id, {}).get("risk") or {}).get("max_risk_level"), 0)
                ).get("tier"),
            }
            for run_id in run_ids
        ]

        if critical_runs:
            blocked_results: list[dict[str, Any]] = []
            for run_id in run_ids:
                run_risk_level = _coerce_int((pending_by_run.get(run_id, {}).get("risk") or {}).get("max_risk_level"), 0)
                error_text = "Batch approve blocked: at least one run is critical risk and requires individual review."
                audit_id: str | None = None
                try:
                    audit_id = _audit_log(app).log(
                        "pending_batch_approve_run",
                        actor,
                        f"Batch approve blocked for {run_id}",
                        run_id=run_id,
                        detail={
                            "batch_id": batch_id,
                            "idempotency_key": idempotency_key,
                            "status": "blocked",
                            "risk_level": run_risk_level,
                            "risk_tier": _risk_from_level(run_risk_level).get("tier"),
                            "error": error_text,
                        },
                    )
                except Exception:
                    audit_id = None
                blocked_results.append(
                    {
                        "run_id": run_id,
                        "status": "blocked",
                        "audit_id": audit_id,
                        "risk_level": run_risk_level,
                        "risk_tier": _risk_from_level(run_risk_level).get("tier"),
                        "error": error_text,
                    }
                )
            response = {
                "batch_id": batch_id,
                "status": "blocked",
                "summary": {
                    "requested": len(run_ids),
                    "succeeded": 0,
                    "failed": 0,
                    "blocked_critical": len(critical_runs),
                },
                "critical_run_ids": critical_runs,
                "risk_matrix": risk_matrix,
                "results": blocked_results,
            }
            _persist_idempotent_batch_response(
                idempotency_db,
                batch_id=batch_id,
                actor=actor,
                action=action_name,
                idempotency_key=idempotency_key,
                request_hash=request_hash,
                response=response,
            )
            return response

        results: list[dict[str, Any]] = []
        succeeded = 0
        failed = 0
        for run_id in run_ids:
            error_text: str | None = None
            status = "approved"
            audit_id: str | None = None
            run_risk_level = _coerce_int((pending_by_run.get(run_id, {}).get("risk") or {}).get("max_risk_level"), 0)
            try:
                pending_item = pending_by_run.get(run_id)
                if pending_item is None:
                    raise HTTPException(status_code=404, detail=f"Pending plan not found: {run_id}")
                if expected_cluster_signature and str(pending_item.get("cluster_signature") or "") != expected_cluster_signature:
                    raise HTTPException(status_code=409, detail=f"Cluster signature mismatch for run {run_id}")
                selected_ids_raw = selected_by_run.get(run_id)
                selected_ids = (
                    [str(value).strip() for value in selected_ids_raw if str(value).strip()]
                    if isinstance(selected_ids_raw, list)
                    else None
                )
                await _approve_pending_run(
                    request=request,
                    run_id=run_id,
                    approve_token=None,
                    selected_step_ids=selected_ids,
                    metadata_action="approve_batch_http",
                )
                succeeded += 1
            except HTTPException as exc:
                status = "failed"
                failed += 1
                error_text = str(exc.detail)
            except Exception as exc:
                status = "failed"
                failed += 1
                error_text = str(exc)
            try:
                audit_id = _audit_log(app).log(
                    "pending_batch_approve_run",
                    actor,
                    f"Batch approve {status} for {run_id}",
                    run_id=run_id,
                    detail={
                        "batch_id": batch_id,
                        "idempotency_key": idempotency_key,
                        "status": status,
                        "error": error_text,
                    },
                )
            except Exception:
                audit_id = None
            results.append(
                {
                    "run_id": run_id,
                    "status": status,
                    "audit_id": audit_id,
                    "risk_level": run_risk_level,
                    "risk_tier": _risk_from_level(run_risk_level).get("tier"),
                    "error": error_text,
                }
            )

        response = {
            "batch_id": batch_id,
            "status": "ok" if failed == 0 else "partial_success",
            "summary": {
                "requested": len(run_ids),
                "succeeded": succeeded,
                "failed": failed,
                "blocked_critical": 0,
            },
            "critical_run_ids": [],
            "risk_matrix": risk_matrix,
            "results": results,
        }
        _persist_idempotent_batch_response(
            idempotency_db,
            batch_id=batch_id,
            actor=actor,
            action=action_name,
            idempotency_key=idempotency_key,
            request_hash=request_hash,
            response=response,
        )
        return response

    async def _reject_pending_batch_impl(request: Request) -> dict[str, Any]:
        payload: dict[str, Any]
        try:
            parsed = await request.json()
        except Exception:
            parsed = {}
        if parsed is None:
            parsed = {}
        if not isinstance(parsed, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")
        payload = parsed

        run_ids_raw = payload.get("run_ids")
        if not isinstance(run_ids_raw, list):
            raise HTTPException(status_code=400, detail="run_ids must be a list")
        run_ids = sorted({str(value).strip() for value in run_ids_raw if str(value).strip()})
        if not run_ids:
            raise HTTPException(status_code=400, detail="run_ids must not be empty")

        reason = str(payload.get("reason") or "").strip()
        if len(reason) < 8:
            raise HTTPException(status_code=400, detail="reason is required for batch dismiss and must be at least 8 characters")

        idempotency_key = str(payload.get("idempotency_key") or "").strip()
        if not idempotency_key:
            raise HTTPException(status_code=400, detail="idempotency_key is required")

        expected_cluster_signature = str(payload.get("expected_cluster_signature") or "").strip() or None
        request_hash = _stable_sha256(
            {
                "run_ids": run_ids,
                "idempotency_key": idempotency_key,
                "expected_cluster_signature": expected_cluster_signature,
                "reason": reason,
            }
        )
        actor = _request_actor(request)
        action_name = "reject"
        state_dir = Path(app.state.state_dir)
        idempotency_db = _batch_actions_db_path(state_dir)
        cached_entry = _load_idempotent_batch_entry(
            idempotency_db,
            actor=actor,
            action=action_name,
            idempotency_key=idempotency_key,
        )
        if cached_entry is not None:
            cached_hash = str(cached_entry.get("request_hash") or "").strip()
            if cached_hash and cached_hash != request_hash:
                raise HTTPException(
                    status_code=409,
                    detail="idempotency_key was already used with a different request payload",
                )
            cached = _load_idempotent_batch_response(
                idempotency_db,
                actor=actor,
                action=action_name,
                idempotency_key=idempotency_key,
            )
            if cached is not None:
                cached["idempotent_replay"] = True
                return cached

        pending_by_run = {str(item.get("run_id") or ""): item for item in _load_pending_items(state_dir)}
        batch_id = secrets.token_hex(12)
        risk_matrix = [
            {
                "run_id": run_id,
                "risk_level": _coerce_int((pending_by_run.get(run_id, {}).get("risk") or {}).get("max_risk_level"), 0),
                "risk_tier": _risk_from_level(
                    _coerce_int((pending_by_run.get(run_id, {}).get("risk") or {}).get("max_risk_level"), 0)
                ).get("tier"),
            }
            for run_id in run_ids
        ]
        results: list[dict[str, Any]] = []
        succeeded = 0
        failed = 0
        for run_id in run_ids:
            error_text: str | None = None
            status = "rejected"
            audit_id: str | None = None
            run_risk_level = _coerce_int((pending_by_run.get(run_id, {}).get("risk") or {}).get("max_risk_level"), 0)
            try:
                pending_item = pending_by_run.get(run_id)
                if pending_item is None:
                    raise HTTPException(status_code=404, detail=f"Pending plan not found: {run_id}")
                if expected_cluster_signature and str(pending_item.get("cluster_signature") or "") != expected_cluster_signature:
                    raise HTTPException(status_code=409, detail=f"Cluster signature mismatch for run {run_id}")
                await _reject_pending_run(
                    request=request,
                    run_id=run_id,
                    approve_token=None,
                    reason=reason,
                    metadata_action="reject_batch_http",
                )
                succeeded += 1
            except HTTPException as exc:
                status = "failed"
                failed += 1
                error_text = str(exc.detail)
            except Exception as exc:
                status = "failed"
                failed += 1
                error_text = str(exc)
            try:
                audit_id = _audit_log(app).log(
                    "pending_batch_reject_run",
                    actor,
                    f"Batch dismiss {status} for {run_id}",
                    run_id=run_id,
                    detail={
                        "batch_id": batch_id,
                        "idempotency_key": idempotency_key,
                        "status": status,
                        "reason": reason,
                        "error": error_text,
                    },
                )
            except Exception:
                audit_id = None
            results.append(
                {
                    "run_id": run_id,
                    "status": status,
                    "audit_id": audit_id,
                    "risk_level": run_risk_level,
                    "risk_tier": _risk_from_level(run_risk_level).get("tier"),
                    "error": error_text,
                }
            )

        response = {
            "batch_id": batch_id,
            "status": "ok" if failed == 0 else "partial_success",
            "summary": {
                "requested": len(run_ids),
                "succeeded": succeeded,
                "failed": failed,
                "blocked_critical": 0,
            },
            "risk_matrix": risk_matrix,
            "results": results,
        }
        _persist_idempotent_batch_response(
            idempotency_db,
            batch_id=batch_id,
            actor=actor,
            action=action_name,
            idempotency_key=idempotency_key,
            request_hash=request_hash,
            response=response,
        )
        return response

    @app.get("/api/admin/summary")
    async def get_admin_summary(
        request: Request,
        lookback_days: int = Query(30, ge=1, le=3650),
    ) -> dict[str, Any]:
        health_payload = (await health_check()).model_dump(exclude_none=False)
        return await _build_admin_summary_snapshot(
            app,
            request,
            lookback_days=lookback_days,
            health_payload=health_payload,
        )

    @app.get("/api/admin/summary/brief")
    async def get_admin_summary_brief(
        request: Request,
        lookback_days: int = Query(30, ge=1, le=3650),
    ) -> dict[str, Any]:
        health_payload = (await health_check()).model_dump(exclude_none=False)
        snapshot = await _build_admin_summary_snapshot(
            app,
            request,
            lookback_days=lookback_days,
            health_payload=health_payload,
        )
        deterministic = _build_deterministic_summary_brief(snapshot)
        state_dir = Path(app.state.state_dir)
        store = AIProviderStore(state_dir)
        status = store.load_status().model_dump(exclude_none=False)
        config = store.load_config()
        generated_at = datetime.now(timezone.utc).isoformat()

        if config is None or not config.enabled:
            return {
                "generated_at": generated_at,
                "snapshot_generated_at": snapshot.get("generated_at"),
                "status": "deterministic",
                "source": "deterministic",
                "ai_used": False,
                "provider": status.get("provider"),
                "model": status.get("model"),
                "error": "AI provider is not configured or not enabled.",
                "brief": deterministic,
            }

        try:
            ai_input = _summary_ai_input_payload(snapshot)
            prompt = _summary_brief_prompt(ai_input)
            raw = ai_generate_text(config, prompt, max_output_tokens=900)
            parsed = _parse_summary_brief_payload(raw)
            if parsed is None:
                raise ValueError("AI response was not valid JSON.")
            allowed_cards = set((snapshot.get("cards") or {}).keys())
            brief = _normalize_summary_brief(
                parsed,
                allowed_source_cards=allowed_cards,
                fallback=deterministic,
            )
            return {
                "generated_at": generated_at,
                "snapshot_generated_at": snapshot.get("generated_at"),
                "status": "ok",
                "source": "ai",
                "ai_used": True,
                "provider": config.provider,
                "model": config.model,
                "error": None,
                "brief": brief,
            }
        except Exception as exc:
            return {
                "generated_at": generated_at,
                "snapshot_generated_at": snapshot.get("generated_at"),
                "status": "fallback",
                "source": "deterministic",
                "ai_used": False,
                "provider": config.provider,
                "model": config.model,
                "error": str(exc),
                "brief": deterministic,
            }

    def _briefing_to_payload(briefing: Any) -> dict[str, Any]:
        sections_payload: list[dict[str, Any]] = []
        for section in getattr(briefing, "sections", []) or []:
            sections_payload.append(
                {
                    "title": str(getattr(section, "title", "") or "").strip(),
                    "body": str(getattr(section, "body", "") or "").strip(),
                    "priority": _coerce_int(getattr(section, "priority", 0), 0),
                }
            )
        return {
            "generated_at": str(getattr(briefing, "generated_at", "") or "").strip(),
            "summary": str(getattr(briefing, "summary", "") or "").strip(),
            "sections": sections_payload,
        }

    def _post_briefing_to_slack_if_configured(briefing: Any) -> dict[str, Any]:
        slack_status = _resolve_slack_status(app, include_secret=True)
        token = str(slack_status.get("token") or "").strip()
        channel = str(slack_status.get("channel") or "").strip()
        if not token or not channel:
            return {"posted": False, "reason": "slack_not_configured"}
        try:
            web_client = slack_create_client(token)
            post_briefing(web_client, channel, briefing)
            return {"posted": True, "channel": channel}
        except Exception as exc:
            logger.warning("Failed to post briefing to Slack.", exc_info=True)
            return {"posted": False, "channel": channel, "error": str(exc)}

    @app.get("/api/admin/briefing/latest")
    async def get_latest_briefing() -> dict[str, Any]:
        briefing = load_latest_briefing(Path(app.state.state_dir))
        if briefing is None:
            raise HTTPException(status_code=404, detail="No briefing found")
        return _briefing_to_payload(briefing)

    @app.post("/api/admin/briefing/generate")
    async def generate_briefing() -> dict[str, Any]:
        state_dir = Path(app.state.state_dir)
        try:
            llm = resolve_provider()
            llm_configured = True
        except RuntimeError:
            llm = None
            llm_configured = False
        briefing = compose_briefing(state_dir, llm=llm, lookback_hours=24)
        day_key = datetime.now(timezone.utc).date().isoformat()
        path = write_briefing(state_dir, briefing, day_key=day_key)
        slack_result = _post_briefing_to_slack_if_configured(briefing)
        return {
            "status": "ok",
            "llm_configured": llm_configured,
            "storage_path": str(path),
            "slack": slack_result,
            "briefing": _briefing_to_payload(briefing),
        }

    @app.get("/api/admin/recommendations")
    async def get_recommendations() -> dict[str, Any]:
        state_dir = Path(app.state.state_dir)
        recommendations: list[dict[str, Any]] = []

        try:
            discovery_summary = _build_discovery_summary(state_dir)
            findings_summary = _build_findings_summary(
                discovery_summary=discovery_summary,
                state_dir=state_dir,
                limit=20,
            )
            scout_actionability = _scout_actionability(state_dir)
            enabled_modules = {module.module_id for module in ModuleStore(state_dir).list_modules() if module.enabled}
            finding_items = findings_summary.get("items") if isinstance(findings_summary.get("items"), list) else []
            for item in finding_items:
                if not isinstance(item, dict):
                    continue
                scout_name = str(item.get("scout_name") or "").strip()
                findings_count = _coerce_int(item.get("findings_count"), 0)
                if not scout_name or findings_count < 5:
                    continue
                actionability = scout_actionability.get(scout_name) if isinstance(scout_actionability.get(scout_name), dict) else {}
                module_ids = actionability.get("module_ids") if isinstance(actionability.get("module_ids"), list) else []
                if not module_ids:
                    continue
                if any(str(module_id) in enabled_modules for module_id in module_ids):
                    continue
                module_id = str(module_ids[0]).strip()
                if not module_id:
                    continue
                recommendations.append(
                    {
                        "type": "module_enablement",
                        "message": (
                            f"Scout {scout_name} reported {findings_count} findings without an enabled module. "
                            f"Consider enabling module {module_id}."
                        ),
                        "confidence": 0.82,
                        "action_hint": f"Enable module {module_id} in Agents and keep initial risk tiers conservative.",
                    }
                )
        except Exception:
            logger.warning("Recommendation pass (module enablement) failed.", exc_info=True)

        try:
            tuning_rows = compute_tuning_suggestions(state_dir, min_sample_size=20, lookback_days=30)
            for row in tuning_rows:
                if row.parameter != "risk_tiers":
                    continue
                if str(row.module_id).startswith("scout:"):
                    continue
                evidence = row.evidence if isinstance(row.evidence, dict) else {}
                approval_rate = _coerce_float(evidence.get("approval_rate"), 0.0)
                sample_size = _coerce_int(evidence.get("sample_size"), 0)
                if approval_rate <= 0.95 or sample_size < 20:
                    continue
                recommendations.append(
                    {
                        "type": "tier_promotion",
                        "message": (
                            f"Module {row.module_id} has {round(approval_rate * 100, 1)}% approval rate "
                            f"over {sample_size} decisions in 30 days; consider promoting to a greener tier."
                        ),
                        "confidence": min(0.99, max(0.7, row.confidence)),
                        "action_hint": "Review tuning suggestions and update module risk tiers through module config endpoints.",
                    }
                )
        except Exception:
            logger.warning("Recommendation pass (tier promotion) failed.", exc_info=True)

        try:
            cp_store = control_plane_store(state_dir)
            open_items, _cursor_open = cp_store.list_inbox(status="open", limit=200)
            triaged_items, _cursor_triaged = cp_store.list_inbox(status="triaged", limit=200)
            unresolved = [*open_items, *triaged_items]
            high_confidence_count = 0
            for item in unresolved:
                candidate_semantics = getattr(item, "candidate_semantics", None)
                if isinstance(candidate_semantics, list) and any(str(value).strip() for value in candidate_semantics):
                    high_confidence_count += 1
            if high_confidence_count > 0:
                recommendations.append(
                    {
                        "type": "inbox_resolution",
                        "message": (
                            f"{high_confidence_count} inbox items have high-confidence candidate semantics "
                            "ready for operator review."
                        ),
                        "confidence": min(0.95, 0.75 + min(high_confidence_count, 20) * 0.01),
                        "action_hint": "Open the inbox and resolve candidate semantics to reduce compile blockers.",
                    }
                )
        except Exception:
            logger.warning("Recommendation pass (inbox) failed.", exc_info=True)

        return {"recommendations": recommendations}

    @app.get("/api/admin/tuning/suggestions")
    async def get_tuning_suggestions(
        min_sample_size: int = Query(20, ge=1, le=5000),
        lookback_days: int = Query(30, ge=1, le=3650),
    ) -> dict[str, Any]:
        suggestions = compute_tuning_suggestions(
            Path(app.state.state_dir),
            min_sample_size=min_sample_size,
            lookback_days=lookback_days,
        )
        payload = [
            {
                "module_id": row.module_id,
                "parameter": row.parameter,
                "current_value": row.current_value,
                "suggested_value": row.suggested_value,
                "reasoning": row.reasoning,
                "confidence": row.confidence,
                "evidence": row.evidence,
            }
            for row in suggestions
        ]
        return {"suggestions": payload}

    @app.get("/api/admin/policy")
    async def get_policy() -> dict[str, Any]:
        policy = apply_policy_overlays(app.state.policy, Path(app.state.state_dir))
        return policy.model_dump(by_alias=True, exclude_none=True)

    @app.get("/api/admin/policy/yaml")
    async def get_policy_yaml() -> PlainTextResponse:
        return PlainTextResponse(
            _policy_yaml_text(app),
            media_type="application/x-yaml",
        )

    @app.get("/api/admin/policy/download")
    async def download_policy_yaml() -> PlainTextResponse:
        return PlainTextResponse(
            _policy_yaml_text(app),
            media_type="application/x-yaml",
            headers={"Content-Disposition": 'attachment; filename="autopilot_policy.yaml"'},
        )

    # --- Chat ---

    @app.post("/api/revops/chat", response_model=RevOpsChatResponse)
    async def revops_chat(request: Request, body: RevOpsChatRequest) -> RevOpsChatResponse:
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="revops_chat",
        )
        state_dir = Path(app.state.state_dir)
        service = RevOpsChatService(
            state_dir,
            policy=apply_policy_overlays(app.state.policy, state_dir),
        )
        return service.answer(body)

    def _chat_recent_findings_context(
        state_dir: Path,
        *,
        lookback_hours: int = 24,
        limit: int = 25,
    ) -> list[dict[str, Any]]:
        cutoff = datetime.now(timezone.utc) - timedelta(hours=max(1, int(lookback_hours)))
        scans = sorted(_load_scan_results(state_dir), key=_scan_result_sort_key, reverse=True)
        findings_context: list[dict[str, Any]] = []
        for scan in scans:
            ended_at = _parse_iso(scan.get("ended_at")) or _parse_iso(scan.get("started_at"))
            if ended_at is None or ended_at < cutoff:
                continue
            run_id = str(scan.get("run_id") or "").strip()
            findings = scan.get("findings")
            if not isinstance(findings, list):
                continue
            for finding in findings:
                if not isinstance(finding, dict):
                    continue
                scout_name = str(finding.get("scout_name") or "").strip()
                if not scout_name:
                    continue
                finding_id = str(finding.get("finding_id") or "").strip() or f"{run_id}:{scout_name}"
                findings_context.append(
                    {
                        "finding_id": finding_id,
                        "run_id": run_id,
                        "scout_name": scout_name,
                        "status": str(finding.get("status") or "").strip(),
                        "severity": str(finding.get("severity") or "").strip().lower() or "medium",
                        "findings_count": _coerce_int(finding.get("findings_count"), 0),
                        "recommended_actions": finding.get("recommended_actions")
                        if isinstance(finding.get("recommended_actions"), list)
                        else [],
                    }
                )
                if len(findings_context) >= max(1, int(limit)):
                    return findings_context
        return findings_context

    def _chat_open_inbox_context(state_dir: Path, *, limit: int = 20) -> list[dict[str, Any]]:
        try:
            items, _cursor = control_plane_store(state_dir).list_inbox(status="open", limit=max(1, int(limit)))
        except Exception:
            return []
        return [
            {
                "inbox_item_id": item.inbox_item_id,
                "pattern_type": item.pattern_type,
                "crm_object": item.crm_object,
                "crm_field": item.crm_field,
                "sample_values": item.sample_values[:5],
                "candidate_semantics": item.candidate_semantics[:5],
                "status": item.status,
                "occurrence_count": item.occurrence_count,
                "last_seen_at": item.last_seen_at,
            }
            for item in items
        ]

    def _chat_latest_gate_metrics_context(state_dir: Path) -> dict[str, Any]:
        try:
            snapshot = GateMetricStore(state_dir / "db" / "gate_metrics.sqlite").latest_snapshot()
        except Exception:
            snapshot = None
        if not isinstance(snapshot, dict):
            return {}
        return {
            "generated_at": snapshot.get("generated_at"),
            "day_key": snapshot.get("day_key"),
            "lookback_days": snapshot.get("lookback_days"),
            "gate_passed": snapshot.get("gate_passed"),
            "payload": snapshot.get("payload") if isinstance(snapshot.get("payload"), dict) else {},
        }

    def _chat_recent_receipts_context(state_dir: Path, *, limit: int = 10) -> list[dict[str, Any]]:
        receipts = sorted(_load_execution_receipts(state_dir), key=_execution_receipt_sort_key, reverse=True)
        rows: list[dict[str, Any]] = []
        for payload in receipts:
            if not isinstance(payload, dict):
                continue
            rows.append(
                {
                    "run_id": payload.get("run_id"),
                    "outcome": payload.get("outcome"),
                    "summary": payload.get("summary"),
                    "source": payload.get("source"),
                    "started_at": payload.get("started_at"),
                    "completed_at": payload.get("completed_at"),
                    "change_count": len(payload.get("changes")) if isinstance(payload.get("changes"), list) else 0,
                }
            )
            if len(rows) >= max(1, int(limit)):
                break
        return rows

    def _parse_llm_json_payload(raw: str) -> dict[str, Any] | None:
        text = str(raw or "").strip()
        if not text:
            return None
        if "```" in text:
            text = _strip_markdown_fence(text)
        parsed: Any
        try:
            parsed = json.loads(text)
        except Exception:
            start = text.find("{")
            end = text.rfind("}")
            if start < 0 or end < start:
                return None
            try:
                parsed = json.loads(text[start : end + 1])
            except Exception:
                return None
        return parsed if isinstance(parsed, dict) else None

    def _findings_index(state_dir: Path) -> dict[str, dict[str, Any]]:
        indexed: dict[str, dict[str, Any]] = {}
        scans = sorted(_load_scan_results(state_dir), key=_scan_result_sort_key, reverse=True)
        for scan in scans[:50]:
            run_id = str(scan.get("run_id") or "").strip()
            captured_at = (
                str(scan.get("ended_at") or "").strip()
                or str(scan.get("completed_at") or "").strip()
                or str(scan.get("started_at") or "").strip()
            )
            findings = scan.get("findings")
            if not isinstance(findings, list):
                continue
            for finding in findings:
                if not isinstance(finding, dict):
                    continue
                scout_name = str(finding.get("scout_name") or "").strip()
                if not scout_name:
                    continue
                finding_id = str(finding.get("finding_id") or "").strip() or f"{run_id}:{scout_name}"
                indexed[finding_id] = {
                    "finding_id": finding_id,
                    "run_id": run_id,
                    "captured_at": captured_at,
                    "finding": finding,
                }
        return indexed

    def _normalize_chat_action_candidates(
        parsed_payload: dict[str, Any],
        *,
        fallback_action_key: str,
        fallback_target_selector: dict[str, Any],
        fallback_params: dict[str, Any],
    ) -> list[dict[str, Any]]:
        raw_candidates = parsed_payload.get("action_candidates") if isinstance(parsed_payload.get("action_candidates"), list) else []
        normalized: list[dict[str, Any]] = []
        for idx, raw_candidate in enumerate(raw_candidates):
            if not isinstance(raw_candidate, dict):
                continue
            params = raw_candidate.get("params") if isinstance(raw_candidate.get("params"), dict) else {}
            target_selector = (
                raw_candidate.get("target_selector")
                if isinstance(raw_candidate.get("target_selector"), dict)
                else {}
            )
            explicit_key = str(
                raw_candidate.get("deterministic_action_key")
                or raw_candidate.get("action_key")
                or fallback_action_key
                or ""
            ).strip()
            resolved_key = resolve_deterministic_action_key(
                explicit_action_key=explicit_key,
                tool_name=str(raw_candidate.get("tool") or "").strip(),
                action_type=str(raw_candidate.get("action_type") or "").strip(),
                scout_name=str(params.get("scout_name") or raw_candidate.get("scout_name") or "").strip(),
                action_details=raw_candidate,
            )
            normalized_candidate = dict(raw_candidate)
            normalized_candidate["candidate_id"] = (
                str(raw_candidate.get("candidate_id") or "").strip() or f"ai_draft_{idx + 1}"
            )
            normalized_candidate["params"] = params
            normalized_candidate["target_selector"] = target_selector
            if resolved_key:
                normalized_candidate["deterministic_action_key"] = resolved_key
            elif explicit_key:
                normalized_candidate["deterministic_action_key"] = explicit_key
            normalized.append(normalized_candidate)

        if normalized:
            return normalized

        resolved_key = resolve_deterministic_action_key(explicit_action_key=fallback_action_key)
        return [
            {
                "candidate_id": "ai_draft_1",
                "deterministic_action_key": resolved_key or fallback_action_key,
                "target_selector": fallback_target_selector,
                "params": fallback_params,
            }
        ]

    def _build_chat_draft_evidence_rows(
        proposal_id: str,
        *,
        selected_findings: list[dict[str, Any]],
        selected_inbox_items: list[dict[str, Any]],
    ) -> list[EvidenceV1]:
        evidence_rows: list[EvidenceV1] = []
        for finding_row in selected_findings:
            finding = finding_row.get("finding") if isinstance(finding_row.get("finding"), dict) else {}
            finding_id = str(finding_row.get("finding_id") or "").strip()
            run_id = str(finding_row.get("run_id") or "").strip()
            captured_at = str(finding_row.get("captured_at") or "").strip() or datetime.now(timezone.utc).isoformat()
            scout_name = str(finding.get("scout_name") or "").strip()
            severity = str(finding.get("severity") or "").strip().lower() or "medium"
            raw_evidence = finding.get("evidence") if isinstance(finding.get("evidence"), list) else []
            selector = {
                "finding_id": finding_id or None,
                "run_id": run_id or None,
                "scout_name": scout_name or None,
                "severity": severity,
            }
            excerpt = f"{scout_name or 'finding'} ({severity})"
            if raw_evidence and isinstance(raw_evidence[0], dict):
                evidence_sample = raw_evidence[0]
                selector["entity_id"] = str(
                    evidence_sample.get("record_id")
                    or evidence_sample.get("entity_id")
                    or evidence_sample.get("id")
                    or ""
                ).strip() or None
                selector["entity_type"] = str(
                    evidence_sample.get("sobject")
                    or evidence_sample.get("entity_type")
                    or evidence_sample.get("object")
                    or ""
                ).strip() or None
            evidence_rows.append(
                EvidenceV1(
                    proposal_id=proposal_id,
                    source_type="scout_finding",
                    source_ref=finding_id or run_id or _stable_sha256({"finding": finding}),
                    content_hash=_stable_sha256({"finding_id": finding_id, "run_id": run_id, "finding": finding}),
                    excerpt=excerpt[:280],
                    selector=selector,
                    captured_at=captured_at,
                    provenance_chain=[run_id] if run_id else [],
                    trust_score=0.85,
                    snapshot_id=run_id or finding_id or _stable_sha256({"proposal_id": proposal_id, "finding_id": finding_id}),
                    artifact_version="scan.json/v1",
                )
            )

        for inbox_item in selected_inbox_items:
            inbox_item_id = str(inbox_item.get("inbox_item_id") or "").strip()
            pattern_type = str(inbox_item.get("pattern_type") or "").strip()
            crm_object = str(inbox_item.get("crm_object") or "").strip()
            crm_field = str(inbox_item.get("crm_field") or "").strip()
            captured_at = (
                str(inbox_item.get("last_seen_at") or "").strip()
                or str(inbox_item.get("first_seen_at") or "").strip()
                or datetime.now(timezone.utc).isoformat()
            )
            sample_values = [
                str(value).strip()
                for value in (inbox_item.get("sample_values") or [])
                if str(value).strip()
            ]
            selector = {
                "inbox_item_id": inbox_item_id or None,
                "pattern_type": pattern_type or None,
                "crm_object": crm_object or None,
                "crm_field": crm_field or None,
            }
            excerpt_parts = [pattern_type or "inbox_item", crm_object, crm_field]
            excerpt = " ".join(part for part in excerpt_parts if part).strip()
            if sample_values:
                excerpt = f"{excerpt}: {', '.join(sample_values[:3])}" if excerpt else ", ".join(sample_values[:3])
            evidence_rows.append(
                EvidenceV1(
                    proposal_id=proposal_id,
                    source_type="inbox_item",
                    source_ref=inbox_item_id or _stable_sha256({"inbox_item": inbox_item}),
                    content_hash=_stable_sha256(
                        {
                            "inbox_item_id": inbox_item_id,
                            "pattern_type": pattern_type,
                            "crm_object": crm_object,
                            "crm_field": crm_field,
                            "sample_values": sample_values,
                        }
                    ),
                    excerpt=(excerpt or "Inbox triage context")[:280],
                    selector=selector,
                    captured_at=captured_at,
                    provenance_chain=[
                        str(value).strip()
                        for value in (inbox_item.get("evidence_ids") or [])
                        if str(value).strip()
                    ][:10],
                    trust_score=0.75,
                    snapshot_id=inbox_item_id or _stable_sha256({"proposal_id": proposal_id, "pattern_type": pattern_type}),
                    artifact_version="control-plane/inbox/v1",
                )
            )

        return evidence_rows

    def _chat_response_from_revops_result(result: RevOpsChatResponse) -> ChatResponse:
        return ChatResponse(
            status=JobStatus.COMPLETED,
            chat_mode=ChatMode.INVESTIGATION,
            message=result.answer,
            job_id=None,
            evidence_status=result.status,
            confidence=result.confidence,
            sources=[
                ChatSource(
                    source_id=source.source_id,
                    source_type=source.source_type,
                    title=source.title,
                    summary=source.summary,
                    captured_at=source.captured_at,
                    metadata=source.metadata,
                )
                for source in result.sources
            ],
            tools_used=list(result.tools_used),
            budget_used=ChatBudgetUsed(**result.budget_used.model_dump()),
            follow_up_suggestions=list(result.follow_up_suggestions),
            conversation_id=result.conversation_id,
        )

    def _sse_event(event: str, payload: dict[str, Any]) -> str:
        return f"event: {event}\ndata: {json.dumps(payload)}\n\n"

    def _stream_text_chunks(text: str, *, max_chars: int = 72) -> list[str]:
        normalized = str(text or "").strip()
        if not normalized:
            return []
        chunks: list[str] = []
        current: list[str] = []
        current_len = 0
        for word in normalized.split():
            next_len = current_len + len(word) + (1 if current else 0)
            if current and next_len > max_chars:
                chunks.append(" ".join(current) + " ")
                current = [word]
                current_len = len(word)
                continue
            current.append(word)
            current_len = next_len
        if current:
            chunks.append(" ".join(current))
        return chunks

    @app.post("/api/chat", response_model=ChatResponse)
    async def chat(request: Request, body: ChatRequest) -> ChatResponse:
        message = str(body.message or "").strip()
        if not message:
            return ChatResponse(status=JobStatus.FAILED, message="Message is required.", job_id=None)

        if body.mode == ChatMode.INVESTIGATION:
            _require_roles(
                request,
                allowed={"admin", "revops"},
                reason_code="rbac_forbidden",
                required_for="chat_investigation",
            )
            state_dir = Path(app.state.state_dir)
            result = RevOpsChatService(
                state_dir,
                policy=apply_policy_overlays(app.state.policy, state_dir),
            ).answer(
                RevOpsChatRequest(
                    message=message,
                    org_alias=body.org_alias,
                    conversation_id=body.conversation_id,
                )
            )
            return _chat_response_from_revops_result(result)

        try:
            llm = resolve_provider()
        except RuntimeError:
            return ChatResponse(
                status=JobStatus.FAILED,
                message="No LLM provider configured. Set ANTHROPIC_API_KEY or OPENAI_API_KEY.",
                job_id=None,
            )

        state_dir = Path(app.state.state_dir)
        chat_context = {
            "scout_findings_24h": _chat_recent_findings_context(state_dir, lookback_hours=24, limit=25),
            "open_inbox_items": _chat_open_inbox_context(state_dir, limit=20),
            "latest_gate_metrics": _chat_latest_gate_metrics_context(state_dir),
            "recent_receipts": _chat_recent_receipts_context(state_dir, limit=10),
        }
        system_prompt = (
            "You are the Gremlin Autopilot operations assistant.\n"
            "Capabilities include scout findings, inbox items, receipts, gate metrics, identity graph context, and field provenance.\n"
            "You are strictly read-only for CRM operations. Do not execute writes, approvals, or policy changes.\n"
            "No direct LLM-to-CRM writes are allowed. Provide analysis and recommendations only."
        )
        user_prompt = (
            f"User message:\n{message}\n\n"
            "Local control-plane context:\n"
            f"{json.dumps(chat_context, indent=2)}"
        )
        response_text = llm.chat(
            [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt},
            ],
            max_tokens=1200,
        )
        return ChatResponse(
            status=JobStatus.COMPLETED,
            chat_mode=ChatMode.PLANNING,
            message=str(response_text or "").strip(),
            job_id=None,
        )

    @app.post("/api/chat/stream")
    async def chat_stream(request: Request, body: ChatRequest) -> StreamingResponse:
        message = str(body.message or "").strip()
        if not message:
            raise HTTPException(status_code=400, detail="Message is required.")
        if body.mode != ChatMode.INVESTIGATION:
            raise HTTPException(status_code=400, detail="Streaming is currently supported for investigation mode only.")
        _require_roles(
            request,
            allowed={"admin", "revops"},
            reason_code="rbac_forbidden",
            required_for="chat_investigation",
        )

        state_dir = Path(app.state.state_dir)
        service = RevOpsChatService(
            state_dir,
            policy=apply_policy_overlays(app.state.policy, state_dir),
        )

        async def _event_stream() -> Any:
            yield _sse_event("status", {"phase": "grounding"})
            result = service.answer(
                RevOpsChatRequest(
                    message=message,
                    org_alias=body.org_alias,
                    conversation_id=body.conversation_id,
                )
            )
            payload = _chat_response_from_revops_result(result).model_dump(mode="json")
            for chunk in _stream_text_chunks(result.answer):
                yield _sse_event("delta", {"delta": chunk})
                await asyncio.sleep(0)
            yield _sse_event("result", payload)
            yield _sse_event("done", {"ok": True, "conversation_id": result.conversation_id})

        return StreamingResponse(
            _event_stream(),
            media_type="text/event-stream",
            headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
        )

    @app.post("/api/chat/gather-evidence")
    async def chat_gather_evidence(request: Request) -> dict[str, Any]:
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        message = str(payload.get("message") or "").strip()
        if not message:
            raise HTTPException(status_code=400, detail="message is required")

        context_payload = payload.get("context") if isinstance(payload.get("context"), dict) else {}
        finding_ids = [
            str(value).strip()
            for value in (context_payload.get("finding_ids") or [])
            if str(value).strip()
        ]
        inbox_item_ids = [
            str(value).strip()
            for value in (context_payload.get("inbox_item_ids") or [])
            if str(value).strip()
        ]

        state_dir = Path(app.state.state_dir)
        findings_index = _findings_index(state_dir)
        cp_store = _control_store()
        selected_findings = [findings_index[finding_id] for finding_id in finding_ids if finding_id in findings_index]
        selected_inbox_items = [
            item.to_dict()
            for inbox_item_id in inbox_item_ids
            for item in [cp_store.get_inbox_item(inbox_item_id)]
            if item is not None
        ]
        evidence_context = {
            "selected_findings": selected_findings if selected_findings else _chat_recent_findings_context(state_dir, lookback_hours=72, limit=20),
            "selected_inbox_items": selected_inbox_items if selected_inbox_items else _chat_open_inbox_context(state_dir, limit=20),
            "latest_gate_metrics": _chat_latest_gate_metrics_context(state_dir),
            "recent_receipts": _chat_recent_receipts_context(state_dir, limit=10),
        }

        summary = (
            f"Collected {len(evidence_context['selected_findings'])} findings, "
            f"{len(evidence_context['selected_inbox_items'])} inbox items, and "
            f"{len(evidence_context['recent_receipts'])} recent receipts."
        )
        try:
            llm = resolve_provider()
        except RuntimeError:
            return {"status": "completed", "summary": summary, "evidence": evidence_context}

        prompt = (
            "Summarize the operational evidence relevant to the user's request.\n"
            "You are strictly read-only. Do not propose writes, approvals, or policy changes.\n"
            "Call out stale evidence, gaps, or contradictory signals when present.\n\n"
            f"User request:\n{message}\n\n"
            f"Evidence context:\n{json.dumps(evidence_context, indent=2)}"
        )
        response_text = llm.chat(
            [
                {"role": "system", "content": "You are a constrained evidence-gathering assistant."},
                {"role": "user", "content": prompt},
            ],
            max_tokens=1000,
        )
        return {
            "status": "completed",
            "summary": str(response_text or "").strip() or summary,
            "evidence": evidence_context,
        }

    @app.post("/api/chat/draft-proposal")
    async def chat_draft_proposal(request: Request) -> dict[str, Any]:
        try:
            payload = await request.json()
        except Exception:
            payload = {}
        if payload is None:
            payload = {}
        if not isinstance(payload, dict):
            raise HTTPException(status_code=400, detail="Invalid payload")

        message = str(payload.get("message") or "").strip()
        if not message:
            raise HTTPException(status_code=400, detail="message is required")
        context_payload = payload.get("context") if isinstance(payload.get("context"), dict) else {}
        finding_ids = [
            str(value).strip()
            for value in (context_payload.get("finding_ids") or [])
            if str(value).strip()
        ]
        inbox_item_ids = [
            str(value).strip()
            for value in (context_payload.get("inbox_item_ids") or [])
            if str(value).strip()
        ]

        try:
            llm = resolve_provider()
        except RuntimeError:
            return {
                "status": "failed",
                "message": "No LLM provider configured. Set ANTHROPIC_API_KEY or OPENAI_API_KEY.",
            }

        state_dir = Path(app.state.state_dir)
        findings_index = _findings_index(state_dir)
        selected_findings = [findings_index[finding_id] for finding_id in finding_ids if finding_id in findings_index]
        cp_store = _control_store()
        selected_inbox_items = [
            item.to_dict()
            for inbox_item_id in inbox_item_ids
            for item in [cp_store.get_inbox_item(inbox_item_id)]
            if item is not None
        ]

        prompt = (
            "Draft a grounded proposal for operations planning. Return ONLY JSON with:\n"
            "{\n"
            '  "title": "string",\n'
            '  "objective": "string",\n'
            '  "action_key": "string",\n'
            '  "target_selector": {"entity_type": "string"},\n'
            '  "params": {"key": "value"},\n'
            '  "risk": {"tier": "low|medium|high|critical", "score": 0.0-1.0, "factors": ["string"]},\n'
            '  "confidence": {"overall": 0.0-1.0, "calibrated_score": 0.0-1.0},\n'
            '  "reasoning": "string"\n'
            "}\n"
            "This is draft-only. Do not execute anything.\n"
            "Every write draft must be justified by the referenced findings or inbox items. "
            "If evidence is insufficient, return action_key manual_review and explain the gap.\n\n"
            f"Operator message:\n{message}\n\n"
            f"Referenced findings:\n{json.dumps(selected_findings, indent=2)}\n\n"
            f"Referenced inbox items:\n{json.dumps(selected_inbox_items, indent=2)}\n"
        )
        llm_raw = llm.chat(
            [
                {"role": "system", "content": "You produce safe, reviewable proposal drafts."},
                {"role": "user", "content": prompt},
            ],
            max_tokens=1400,
        )
        parsed = _parse_llm_json_payload(llm_raw) or {}

        actor = _request_actor(request)
        title = str(parsed.get("title") or "").strip() or f"AI Draft: {message[:72]}"
        objective = str(parsed.get("objective") or "").strip() or message
        raw_action_key = str(parsed.get("action_key") or "").strip() or "manual_review"
        action_key = resolve_deterministic_action_key(explicit_action_key=raw_action_key) or raw_action_key
        target_selector = parsed.get("target_selector") if isinstance(parsed.get("target_selector"), dict) else {}
        params = parsed.get("params") if isinstance(parsed.get("params"), dict) else {}
        risk = parsed.get("risk") if isinstance(parsed.get("risk"), dict) else {"tier": "low", "score": 0.3}
        confidence = dict(parsed.get("confidence")) if isinstance(parsed.get("confidence"), dict) else {}
        confidence["overall"] = min(1.0, max(0.0, _coerce_float(confidence.get("overall"), 0.65)))
        confidence["calibrated_score"] = min(
            1.0,
            max(0.0, _coerce_float(confidence.get("calibrated_score"), confidence["overall"])),
        )
        reasoning = str(parsed.get("reasoning") or "").strip() or "Generated from chat context."

        action_candidates = _normalize_chat_action_candidates(
            parsed,
            fallback_action_key=action_key,
            fallback_target_selector=target_selector,
            fallback_params=params,
        )

        evidence_ids = [*finding_ids, *inbox_item_ids]
        evidence_ids = [value for value in evidence_ids if value]
        if not evidence_ids:
            evidence_ids = [_stable_sha256({"message": message, "title": title})]

        action_family_values = sorted(
            {
                str((candidate or {}).get("deterministic_action_key") or "").strip()
                for candidate in action_candidates
                if isinstance(candidate, dict)
                and str((candidate or {}).get("deterministic_action_key") or "").strip()
            }
        )
        action_family = ",".join(action_family_values) if action_family_values else "custom"
        signature = compute_proposal_signature(
            scope=title,
            action_family=action_family,
            evidence_ids=evidence_ids,
            window_bucket=_window_bucket_timestamp(datetime.now(timezone.utc), DEFAULT_DEDUPE_WINDOW_SECONDS),
        )

        proposal = ProposalV1(
            tenant_id="default",
            source_kind="ai_draft",
            source_refs=evidence_ids[:12],
            title=title,
            objective=objective,
            action_candidates=action_candidates,
            confidence=confidence,
            risk=risk,
            evidence_ids=evidence_ids,
            why_summary=reasoning,
            proposal_signature=signature,
            dedupe_window_seconds=DEFAULT_DEDUPE_WINDOW_SECONDS,
            created_by=actor,
        )
        saved, was_deduped = cp_store.save_proposal(proposal)
        if not was_deduped:
            cp_store.record_outcome(
                saved.proposal_id,
                "proposed",
                actor=actor,
                detail={"source": "chat_draft", "capability": "proposal_drafting"},
            )
        if not cp_store.list_evidence_for_proposal(saved.proposal_id):
            evidence_rows = _build_chat_draft_evidence_rows(
                saved.proposal_id,
                selected_findings=selected_findings,
                selected_inbox_items=selected_inbox_items,
            )
            if evidence_rows:
                cp_store.save_evidence(evidence_rows)

        compile_result = cp_store.latest_compile_result(saved.proposal_id)
        if compile_result is None:
            policy = apply_policy_overlays(app.state.policy, state_dir)
            compile_result, _plan = compile_proposal(
                store=cp_store,
                state_dir=state_dir,
                policy=policy,
                proposal_id=saved.proposal_id,
                emit_pending=False,
                actor=actor,
            )
        return {
            "status": "draft",
            "proposal": cp_store.get_proposal(saved.proposal_id).to_dict(),
            "deduped": was_deduped,
            "compile_result": compile_result.to_dict() if compile_result is not None else None,
            "pending_run_id": None,
        }

    # --- Jobs ---

    @app.post("/api/jobs/execute", response_model=ExecuteResponse)
    async def execute_job(body: ExecuteRequest) -> ExecuteResponse:
        """Execute an approved action plan — currently unavailable."""
        return ExecuteResponse(
            status=JobStatus.FAILED,
            job_id=body.job_id,
            results=[],
            message=(
                "Agent execution is not yet available. "
                "Use the CLI instead: g-gremlin autopilot play run <play_name>"
            ),
        )

    @app.get("/api/jobs/{job_id}")
    async def get_job_status(job_id: str) -> dict:
        """Get the status of a job."""
        return {
            "job_id": job_id,
            "status": "unavailable",
            "message": "Agent job tracking is not yet available.",
        }

    @app.get("/status")
    async def status_page() -> HTMLResponse:
        try:
            ref = importlib_resources.files("g_gremlin.autopilot.ui").joinpath("status.html")
            html = ref.read_text(encoding="utf-8")
            return HTMLResponse(html)
        except Exception as exc:
            logger.debug("Status page not available: %s", exc)
            raise HTTPException(status_code=404, detail="Status page not available") from exc

    # --- Static Files (React UI) ---
    # Serve static files from the ui/dist directory if it exists
    ui_dist_path = app.state.ui_dist_path
    if ui_dist_path.exists():
        app.mount("/", StaticFiles(directory=ui_dist_path, html=True), name="static")

    return app


def run_server(port: int = 7777, allow_remote: bool = False, policy_path: Path | None = None) -> None:
    """Run the Autopilot server.

    Args:
        port: Port to listen on (default 7777)
        allow_remote: If True, bind to all interfaces and show pairing code.
                     Default is False (localhost only).
        policy_path: Optional path to policy YAML.
    """
    import uvicorn

    app = create_app(allow_remote=allow_remote, port=port, policy_path=policy_path)
    host = "0.0.0.0" if allow_remote else "127.0.0.1"

    if allow_remote and app.state.api_key:
        logger.warning(
            "\n"
            "  ======================================================\n"
            "  SECURITY WARNING: Server bound to all interfaces.\n"
            "  API key authentication is enabled. Pairing is disabled.\n"
            "  ======================================================\n"
        )
    elif allow_remote:
        logger.warning(
            "\n"
            "  ======================================================\n"
            "  ⚠️  SECURITY WARNING: Server bound to all interfaces.\n"
            "     Only use --allow-remote on trusted networks.\n"
            "  ======================================================\n"
            f"\n  🔐 Pairing code: {app.state.pairing_code}\n"
            "     Enter this code in the browser to authenticate.\n"
            f"     Valid for 5 minutes (until {app.state.pairing_valid_until.strftime('%H:%M:%S')})\n"
        )
    else:
        logger.info(f"Starting Gremlin Autopilot on http://localhost:{port}")
        logger.info(f"Session token stored at: {app.state.session_key_path}")

    uvicorn.run(app, host=host, port=port, log_level="info")


def _parse_iso(value: str | None) -> datetime | None:
    if not value:
        return None
    try:
        return datetime.fromisoformat(value.replace("Z", "+00:00"))
    except Exception:
        return None


def _daemon_running(state_dir: Path) -> bool:
    locks_dir = Path(state_dir) / "locks"
    if not locks_dir.exists():
        return False
    for lock_path in locks_dir.glob("*.lock"):
        data = _read_lock_payload(lock_path)
        if not data:
            continue
        pid_raw = data.get("pid")
        try:
            pid = int(pid_raw)
        except (TypeError, ValueError):
            continue
        if _pid_alive(pid):
            return True
    return False


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

    latest: datetime | None = None
    latest_raw: str | None = None
    for run_dir in receipts_dir.iterdir():
        if not run_dir.is_dir():
            continue
        receipt_path = run_dir / "receipt.json"
        if not receipt_path.exists():
            continue
        try:
            payload = json.loads(receipt_path.read_text(encoding="utf-8"))
        except Exception:
            continue
        candidate_raw = payload.get("completed_at") or payload.get("started_at")
        candidate_dt = _parse_iso(candidate_raw) if isinstance(candidate_raw, str) else None
        if candidate_dt is None:
            continue
        if latest is None or candidate_dt > latest:
            latest = candidate_dt
            latest_raw = candidate_raw
    return latest_raw


def _read_daemon_heartbeat(state_dir: Path) -> dict[str, Any] | None:
    """Read daemon heartbeat file if it exists."""
    heartbeat_path = state_dir / "daemon_status.json"
    if not heartbeat_path.exists():
        return None
    try:
        return json.loads(heartbeat_path.read_text(encoding="utf-8"))
    except Exception:
        return None


def _heartbeat_stale_threshold(heartbeat: dict[str, Any]) -> timedelta:
    """Compute heartbeat staleness from schedule interval, with sane fallback."""
    fallback = timedelta(hours=4)
    last_scan = _parse_iso(heartbeat.get("last_scan_at"))
    next_run = _parse_iso(heartbeat.get("next_run_at"))
    if not last_scan or not next_run or next_run <= last_scan:
        return fallback
    interval = next_run - last_scan
    if interval < timedelta(minutes=1):
        return fallback
    threshold = interval * 2
    if threshold < fallback:
        return fallback
    return min(threshold, timedelta(days=7))


def _pluralize_word(word: str, count: int) -> str:
    base = (word or "record").strip()
    if count == 1:
        return base
    lowered = base.lower()
    if lowered.endswith("y") and len(base) > 1 and lowered[-2] not in "aeiou":
        return f"{base[:-1]}ies"
    if lowered.endswith(("s", "x", "z", "ch", "sh")):
        return f"{base}es"
    return f"{base}s"


def _tool_action_phrase(tool_name: str, *, singular: bool) -> str:
    tool = (tool_name or "").strip().lower()
    if tool == "tasks.create":
        return "follow-up task" if singular else "follow-up tasks"
    if tool == "sfdc.record.update":
        return "record update" if singular else "record updates"
    if tool == "sfdc.record.reassign_owner":
        return "owner reassignment" if singular else "owner reassignments"
    if tool == "sfdc.record.enrich_missing_fields":
        return "field enrichment" if singular else "field enrichments"
    if tool == "sfdc.followup.enforce":
        return "follow-up enforcement" if singular else "follow-up enforcements"
    if tool == "dealdesk.renewal.create_shell":
        return "renewal shell" if singular else "renewal shells"
    if tool == "dealdesk.approval.stamp":
        return "approval stamp" if singular else "approval stamps"
    if tool == "sfdc.user.deactivate":
        return "user deactivation" if singular else "user deactivations"
    if tool == "sfdc.user.assign_permission_sets":
        return "permission set assignment" if singular else "permission set assignments"
    if tool == "sfdc.user.assign_permission_set_groups":
        return "permission set group assignment" if singular else "permission set group assignments"
    if tool == "sfdc.user.add_to_queue_group":
        return "queue assignment" if singular else "queue assignments"
    if tool == "sfdc.forecast.assign_user_to_role":
        return "forecast role assignment" if singular else "forecast role assignments"
    human = tool.replace(".", " ").replace("_", " ").strip() or "action"
    return human if singular else _pluralize_word(human, 2)


def _summarize_pending_plan_data(plan_data: dict[str, Any]) -> dict[str, Any]:
    steps = plan_data.get("steps")
    if not isinstance(steps, list):
        steps = []

    scouts: set[str] = set()
    action_keys: set[str] = set()
    target_sobjects: set[str] = set()
    object_ids: dict[str, set[str]] = defaultdict(set)
    object_unknown_counts: dict[str, int] = defaultdict(int)
    distinct_record_ids: set[str] = set()
    primary_tool = ""

    for raw_step in steps:
        if not isinstance(raw_step, dict):
            continue
        if not primary_tool:
            primary_tool = str(raw_step.get("tool") or "").strip()
        args = raw_step.get("args")
        if not isinstance(args, dict):
            args = {}

        scout_name = args.get("scout_name")
        if scout_name:
            scouts.add(str(scout_name).strip())
        deterministic_action_key = str(args.get("deterministic_action_key") or "").strip()
        if deterministic_action_key:
            action_keys.add(deterministic_action_key)
        elif raw_step.get("tool"):
            action_keys.add(str(raw_step.get("tool")).strip())

        fallback_object = str(args.get("sobject") or "Record").strip() or "Record"
        target_sobjects.add(fallback_object)
        raw_record_ids = args.get("record_ids")
        record_ids = []
        if isinstance(raw_record_ids, list):
            for value in raw_record_ids:
                if value is None:
                    continue
                rid = str(value).strip()
                if rid:
                    record_ids.append(rid)

        used_record_payload = False
        raw_records = args.get("records")
        if isinstance(raw_records, list):
            for raw_record in raw_records:
                if not isinstance(raw_record, dict):
                    continue
                used_record_payload = True
                record_id = str(raw_record.get("record_id") or "").strip()
                obj = str(raw_record.get("sobject") or fallback_object or "Record").strip() or "Record"
                target_sobjects.add(obj)
                if record_id:
                    object_ids[obj].add(record_id)
                    distinct_record_ids.add(record_id)
                else:
                    object_unknown_counts[obj] += 1

        if not used_record_payload and record_ids:
            for record_id in record_ids:
                object_ids[fallback_object].add(record_id)
                distinct_record_ids.add(record_id)

    object_counts: dict[str, int] = {}
    for obj, ids in object_ids.items():
        object_counts[obj] = len(ids) + object_unknown_counts.get(obj, 0)
    for obj, count in object_unknown_counts.items():
        if obj not in object_counts:
            object_counts[obj] = count

    record_count = len(distinct_record_ids) + sum(object_unknown_counts.values())
    if not record_count:
        record_count = sum(object_counts.values())

    title = ""
    if record_count > 0:
        sorted_objects = sorted(object_counts.items(), key=lambda item: (-item[1], item[0]))
        primary_object = sorted_objects[0][0] if sorted_objects else "Record"
        if record_count == 1:
            title = f"1 {primary_object} needs {_tool_action_phrase(primary_tool, singular=True)}"
        else:
            title = (
                f"{record_count} {_pluralize_word(primary_object, record_count)} "
                f"need {_tool_action_phrase(primary_tool, singular=False)}"
            )
    elif steps:
        title = f"{len(steps)} planned step(s) awaiting decision"
    else:
        summary = str(plan_data.get("summary") or "").strip()
        title = summary or "Pending exception requires decision"

    return {
        "title": title,
        "scouts": sorted([name for name in scouts if name]),
        "action_keys": sorted([name for name in action_keys if name]),
        "target_sobjects": sorted([name for name in target_sobjects if name]),
        "record_count": record_count,
        "object_counts": object_counts,
    }


def _to_bucket_timestamp(ts: datetime, *, window_seconds: int) -> str:
    if window_seconds <= 0:
        return ts.replace(minute=0, second=0, microsecond=0).isoformat()
    epoch = int(ts.timestamp())
    bucket = epoch - (epoch % window_seconds)
    return datetime.fromtimestamp(bucket, tz=timezone.utc).isoformat()


def _pending_cluster_key(
    *,
    source_scout: str,
    action_key: str,
    target_sobject: str,
) -> str:
    return "|".join(
        [
            str(source_scout or "unknown").strip() or "unknown",
            str(action_key or "unknown").strip() or "unknown",
            str(target_sobject or "Record").strip() or "Record",
        ]
    )


def _pending_cluster_signature(
    *,
    source_scout: str,
    action_key: str,
    target_sobject: str,
    evidence_window: str,
    policy_hash: str,
) -> str:
    cluster_key = _pending_cluster_key(
        source_scout=source_scout,
        action_key=action_key,
        target_sobject=target_sobject,
    )
    payload = {
        "action_family": action_key,
        "scope": {
            "cluster_key": cluster_key,
            "source_scout": source_scout,
            "target_sobject": target_sobject,
        },
        "evidence_window": evidence_window,
        "policy_hash": policy_hash,
    }
    return _stable_sha256(payload)


def _cluster_title(
    *,
    scout_display_name: str,
    action_label: str,
    record_count: int,
    target_sobject: str,
    run_count: int,
) -> str:
    object_label = target_sobject or "Record"
    object_text = _pluralize_word(object_label, max(record_count, 1))
    return (
        f"{scout_display_name} {action_label} - "
        f"{record_count} {object_text.lower()} across {run_count} runs"
    )


def _tool_action_label(action_key: str) -> str:
    key = str(action_key or "").strip().lower()
    if key == "sequence.pause":
        return "Pause"
    if key == "tasks.create":
        return "Create Task"
    if key == "sfdc.record.update":
        return "Update"
    if key == "sfdc.record.reassign_owner":
        return "Reassign Owner"
    raw = str(action_key or "").replace(".", " ").replace("_", " ").strip()
    if not raw:
        return "Action"
    return " ".join(token.capitalize() for token in raw.split())


def _build_pending_clusters(
    state_dir: Path,
    pending_items: list[dict[str, Any]],
    *,
    window_seconds: int = PENDING_CLUSTER_WINDOW_SECONDS,
) -> list[dict[str, Any]]:
    scout_display_names: dict[str, str] = {}
    try:
        for definition in ScoutLoader().list_scouts():
            scout_display_names[definition.name] = definition.display_name or definition.name
    except Exception:
        scout_display_names = {}

    grouped: dict[str, dict[str, Any]] = {}
    for item in pending_items:
        source_scout = str(item.get("source_scout") or "unknown").strip() or "unknown"
        action_key = str(item.get("action_key") or "unknown").strip() or "unknown"
        target_sobject = str(item.get("target_sobject") or "Record").strip() or "Record"
        policy_hash = str(item.get("policy_hash") or "none").strip() or "none"
        created_at = _as_utc(_parse_iso(item.get("created_at"))) or datetime.now(timezone.utc)
        evidence_window = _to_bucket_timestamp(created_at, window_seconds=window_seconds)
        cluster_signature = _pending_cluster_signature(
            source_scout=source_scout,
            action_key=action_key,
            target_sobject=target_sobject,
            evidence_window=evidence_window,
            policy_hash=policy_hash,
        )

        cluster = grouped.setdefault(
            cluster_signature,
            {
                "cluster_signature": cluster_signature,
                "source_scout": source_scout,
                "source_scout_display_name": scout_display_names.get(source_scout, source_scout.replace("_", " ").title()),
                "action_key": action_key,
                "action_label": _tool_action_label(action_key),
                "target_sobject": target_sobject,
                "cluster_key": _pending_cluster_key(
                    source_scout=source_scout,
                    action_key=action_key,
                    target_sobject=target_sobject,
                ),
                "policy_hash": policy_hash,
                "evidence_window": evidence_window,
                "dedupe_window_seconds": window_seconds,
                "contract_version": PENDING_CLUSTER_CONTRACT_VERSION,
                "run_ids": [],
                "max_risk_level": 0,
                "record_count": 0,
                "step_count": 0,
                "runs": [],
            },
        )
        run_id = str(item.get("run_id") or "").strip()
        if run_id and run_id not in cluster["run_ids"]:
            cluster["run_ids"].append(run_id)
        cluster["record_count"] += _coerce_int(item.get("affected_records"), 0)
        cluster["step_count"] += _coerce_int(item.get("step_count"), 0)
        risk_level = _coerce_int((item.get("risk") or {}).get("max_risk_level"), 0) if isinstance(item.get("risk"), dict) else 0
        cluster["max_risk_level"] = max(_coerce_int(cluster.get("max_risk_level"), 0), risk_level)
        cluster["runs"].append(
            {
                "run_id": run_id,
                "title": item.get("human_title") or item.get("summary") or "Pending exception",
                "risk_level": risk_level,
                "created_at": item.get("created_at"),
                "step_count": _coerce_int(item.get("step_count"), 0),
                "affected_records": _coerce_int(item.get("affected_records"), 0),
            }
        )

    clusters: list[dict[str, Any]] = []
    for cluster in grouped.values():
        run_count = len(cluster.get("run_ids") or [])
        cluster["run_count"] = run_count
        cluster["title"] = _cluster_title(
            scout_display_name=str(cluster.get("source_scout_display_name") or "Unknown Signal"),
            action_label=str(cluster.get("action_label") or "Action"),
            record_count=_coerce_int(cluster.get("record_count"), 0),
            target_sobject=str(cluster.get("target_sobject") or "Record"),
            run_count=run_count,
        )
        cluster["runs"] = sorted(
            cluster.get("runs") or [],
            key=lambda row: (
                _coerce_int(row.get("risk_level"), 0),
                _as_utc(_parse_iso(row.get("created_at"))) or datetime.min.replace(tzinfo=timezone.utc),
            ),
            reverse=True,
        )
        clusters.append(cluster)

    clusters.sort(
        key=lambda row: (
            _coerce_int(row.get("max_risk_level"), 0),
            _coerce_int(row.get("record_count"), 0),
            _coerce_int(row.get("run_count"), 0),
            str(row.get("source_scout") or ""),
        ),
        reverse=True,
    )
    return clusters


def _estimate_step_record_count(step: Any) -> int:
    args = getattr(step, "args", {})
    if not isinstance(args, dict):
        return 0
    ids: set[str] = set()
    raw_ids = args.get("record_ids")
    if isinstance(raw_ids, list):
        for value in raw_ids:
            if value is None:
                continue
            text = str(value).strip()
            if text:
                ids.add(text)

    unknown_records = 0
    raw_records = args.get("records")
    if isinstance(raw_records, list):
        for raw_record in raw_records:
            if not isinstance(raw_record, dict):
                continue
            record_id = str(raw_record.get("record_id") or "").strip()
            if record_id:
                ids.add(record_id)
            else:
                unknown_records += 1

    if ids:
        return len(ids) + unknown_records
    if unknown_records:
        return unknown_records
    return 1


def _plan_with_selected_steps(plan: PlanV1, selected_step_ids: list[str] | None) -> PlanV1:
    if selected_step_ids is None:
        return plan

    selected = {str(step_id).strip() for step_id in selected_step_ids if str(step_id).strip()}
    if not selected:
        raise HTTPException(status_code=400, detail="No steps selected for approval")

    try:
        expanded_selected = expand_selected_step_ids(plan.steps, selected)
    except WorkflowCompositionError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc

    chosen_steps = [step for step in plan.steps if step.step_id in expanded_selected]
    if not chosen_steps:
        raise HTTPException(status_code=400, detail="Selected steps not found in plan")

    missing_ids = sorted(expanded_selected - {step.step_id for step in chosen_steps})
    if missing_ids:
        raise HTTPException(
            status_code=400,
            detail=f"Unknown step_id values: {', '.join(missing_ids)}",
        )

    if len(chosen_steps) == len(plan.steps):
        return plan

    risk = plan.risk.model_copy(deep=True)
    risk.max_risk_level = max((step.risk_level for step in chosen_steps), default=0)
    risk.estimated_count = sum(_estimate_step_record_count(step) for step in chosen_steps)

    verification = sorted(
        {
            str(step.args.get("scout_name")).strip()
            for step in chosen_steps
            if isinstance(step.args, dict) and step.args.get("scout_name")
        }
    )
    if len(chosen_steps) == len(selected):
        summary_suffix = f" (selected {len(chosen_steps)} of {len(plan.steps)} steps)"
    else:
        auto_added = len(chosen_steps) - len(selected)
        summary_suffix = (
            f" (selected {len(chosen_steps)} of {len(plan.steps)} steps; "
            f"auto-added {auto_added} dependency step{'s' if auto_added != 1 else ''})"
        )
    summary = (plan.summary or "Selected plan") + summary_suffix

    return PlanV1(
        version=plan.version,
        run_id=plan.run_id,
        source=plan.source.model_copy(deep=True),
        summary=summary,
        risk=risk,
        plan_hash=None,
        policy_hash=plan.policy_hash,
        steps=chosen_steps,
        verification=verification or list(plan.verification),
        approvals_required=plan.approvals_required,
    )


def _count_pending(state_dir: Path) -> int:
    """Count pending approval directories."""
    pending_dir = state_dir / "pending"
    if not pending_dir.exists():
        return 0
    count = 0
    for entry in pending_dir.iterdir():
        if entry.is_dir() and (entry / "plan.json").exists():
            count += 1
    return count


def _compute_recent_24h(state_dir: Path) -> dict[str, int]:
    """Tally receipt outcomes from the last 24 hours. Samples newest 500 max."""
    receipts_dir = state_dir / "receipts"
    result = {"success": 0, "failed": 0, "total": 0}
    if not receipts_dir.exists():
        return result

    cutoff = datetime.now(timezone.utc) - timedelta(hours=24)

    # Get receipt dirs sorted by mtime desc, sample newest 500
    dirs: list[tuple[float, Path]] = []
    for run_dir in receipts_dir.iterdir():
        if not run_dir.is_dir():
            continue
        receipt_path = run_dir / "receipt.json"
        if receipt_path.exists():
            try:
                dirs.append((receipt_path.stat().st_mtime, run_dir))
            except Exception:
                continue
    dirs.sort(key=lambda x: x[0], reverse=True)

    for _, run_dir in dirs[:500]:
        receipt_path = run_dir / "receipt.json"
        try:
            data = json.loads(receipt_path.read_text(encoding="utf-8"))
        except Exception:
            continue
        ts_raw = data.get("completed_at") or data.get("started_at")
        ts = _parse_iso(ts_raw) if isinstance(ts_raw, str) else None
        if not ts or ts < cutoff:
            continue
        result["total"] += 1
        outcome = data.get("outcome", "")
        if outcome == "success":
            result["success"] += 1
        elif outcome in ("failed", "partial_failure", "denied"):
            result["failed"] += 1
    return result


def _get_recent_24h_cached(app: FastAPI, state_dir: Path) -> dict[str, int]:
    """Return recent_24h with 30s caching on app.state."""
    cache = getattr(app.state, "recent_24h_cache", None)
    now = _time.time()
    if cache and (now - cache[0]) < _CACHE_TTL:
        return cache[1]
    result = _compute_recent_24h(state_dir)
    app.state.recent_24h_cache = (now, result)
    return result


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


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


def _as_utc(dt: datetime | None) -> datetime | None:
    if dt is None:
        return None
    if dt.tzinfo is None:
        return dt.replace(tzinfo=timezone.utc)
    return dt.astimezone(timezone.utc)


def _load_pending_items(state_dir: Path) -> list[dict[str, Any]]:
    pending_dir = Path(state_dir) / "pending"
    items: list[dict[str, Any]] = []
    if pending_dir.exists():
        for run_dir in pending_dir.iterdir():
            if not run_dir.is_dir():
                continue
            plan_path = run_dir / "plan.json"
            if not plan_path.exists():
                continue
            try:
                data = json.loads(plan_path.read_text(encoding="utf-8"))
            except Exception:
                continue
            pending_action = load_pending_action(state_dir, str(data.get("run_id") or run_dir.name))
            created_at_dt = datetime.fromtimestamp(plan_path.stat().st_mtime, timezone.utc)
            created_at = created_at_dt.isoformat()
            insights = _summarize_pending_plan_data(data)
            source_scout = insights["scouts"][0] if insights.get("scouts") else "unknown"
            action_key = insights["action_keys"][0] if insights.get("action_keys") else "unknown"
            target_sobject = insights["target_sobjects"][0] if insights.get("target_sobjects") else "Record"
            cluster_key = _pending_cluster_key(
                source_scout=source_scout,
                action_key=action_key,
                target_sobject=target_sobject,
            )
            policy_hash = str(data.get("policy_hash") or "none")
            evidence_window = _to_bucket_timestamp(created_at_dt, window_seconds=PENDING_CLUSTER_WINDOW_SECONDS)
            cluster_signature = _pending_cluster_signature(
                source_scout=source_scout,
                action_key=action_key,
                target_sobject=target_sobject,
                evidence_window=evidence_window,
                policy_hash=policy_hash,
            )
            items.append(
                {
                    "run_id": data.get("run_id") or run_dir.name,
                    "summary": data.get("summary", ""),
                    "risk": data.get("risk", {}),
                    "policy_hash": data.get("policy_hash"),
                    "step_count": len(data.get("steps") or []),
                    "created_at": created_at,
                    "human_title": insights["title"],
                    "source_scouts": insights["scouts"],
                    "affected_records": insights["record_count"],
                    "affected_objects": insights["object_counts"],
                    "source_scout": source_scout,
                    "action_key": action_key,
                    "target_sobject": target_sobject,
                    "cluster_key": cluster_key,
                    "cluster_signature": cluster_signature,
                    "cluster_window": evidence_window,
                    "cluster_contract_version": PENDING_CLUSTER_CONTRACT_VERSION,
                    "source_kind": data.get("source_kind"),
                    "proposal_id": data.get("proposal_id"),
                    "compile_id": data.get("compile_id"),
                    "proposal_confidence": data.get("proposal_confidence"),
                    "proposal_risk": data.get("proposal_risk"),
                    "why_summary": data.get("why_summary"),
                    "compile_gate_snapshot": data.get("compile_gate_snapshot"),
                    "evidence_ids": data.get("evidence_ids") if isinstance(data.get("evidence_ids"), list) else [],
                    "created_by": pending_action.created_by.to_dict() if pending_action is not None else None,
                }
            )
    items.sort(key=lambda item: _parse_iso(item.get("created_at")) or datetime.min, reverse=True)
    return items


def _load_pending_plan_payload(state_dir: Path, run_id: str) -> dict[str, Any] | None:
    plan_path = Path(state_dir) / "pending" / run_id / "plan.json"
    if not plan_path.exists():
        return None
    try:
        payload = json.loads(plan_path.read_text(encoding="utf-8"))
    except Exception:
        return None
    return payload if isinstance(payload, dict) else None


def _scan_evidence_row_from_source_ref(state_dir: Path, source_ref: str) -> dict[str, Any] | None:
    raw_source_ref = str(source_ref or "").strip()
    if not raw_source_ref or not raw_source_ref.startswith("receipts/"):
        return None
    if "#finding:" not in raw_source_ref:
        return None
    path_part, anchor = raw_source_ref.split("#", 1)
    scan_path = Path(state_dir) / path_part
    if not scan_path.exists():
        return None
    try:
        scan_payload = json.loads(scan_path.read_text(encoding="utf-8"))
    except Exception:
        return None
    if not isinstance(scan_payload, dict):
        return None
    findings = scan_payload.get("findings")
    if not isinstance(findings, list):
        return None
    try:
        anchor_parts = anchor.split(":")
        finding_idx = int(anchor_parts[1])
        evidence_idx = int(anchor_parts[3])
    except Exception:
        return None
    if finding_idx < 0 or finding_idx >= len(findings):
        return None
    finding_payload = findings[finding_idx]
    if not isinstance(finding_payload, dict):
        return None
    evidence_rows = finding_payload.get("evidence")
    if not isinstance(evidence_rows, list):
        return None
    if evidence_idx < 0 or evidence_idx >= len(evidence_rows):
        return None
    row = evidence_rows[evidence_idx]
    return row if isinstance(row, dict) else None


def _evidence_excerpts_for_decision_report(
    state_dir: Path,
    evidence_rows: list[dict[str, Any]],
    *,
    limit: int = 3,
) -> list[dict[str, Any]]:
    excerpts: list[dict[str, Any]] = []
    for evidence in evidence_rows:
        source_ref = str(evidence.get("source_ref") or "").strip()
        scan_row = _scan_evidence_row_from_source_ref(state_dir, source_ref)
        excerpts.append(
            {
                "record_id": (
                    str(scan_row.get("record_id") or "").strip()
                    if isinstance(scan_row, dict)
                    else str((evidence.get("selector") or {}).get("entity_id") or "").strip()
                )
                or None,
                "sobject": (str(scan_row.get("sobject") or "").strip() if isinstance(scan_row, dict) else "") or None,
                "reason": (
                    str(scan_row.get("reason") or scan_row.get("reason_code") or "").strip()
                    if isinstance(scan_row, dict)
                    else str(evidence.get("excerpt") or "").strip()
                )
                or None,
                "severity": (
                    str(scan_row.get("severity") or "").strip().lower()
                    if isinstance(scan_row, dict)
                    else "unknown"
                )
                or "unknown",
                "snapshot_id": str(evidence.get("snapshot_id") or "").strip() or None,
                "artifact_version": str(evidence.get("artifact_version") or "").strip() or None,
                "redaction_class": str(evidence.get("redaction_class") or "none").strip().lower() or "none",
                "evidence_id": str(evidence.get("evidence_id") or "").strip() or None,
            }
        )
    return excerpts[: max(1, min(int(limit), 10))]


def _risk_from_level(level: int) -> dict[str, Any]:
    if level >= 3:
        return {"tier": "critical", "score": 0.9, "factors": ["high_blast_radius"]}
    if level == 2:
        return {"tier": "high", "score": 0.7, "factors": ["elevated_risk"]}
    if level == 1:
        return {"tier": "medium", "score": 0.45, "factors": ["moderate_risk"]}
    return {"tier": "low", "score": 0.2, "factors": ["limited_scope"]}


def _as_gate_pass(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    if isinstance(value, str):
        normalized = value.strip().lower()
        if normalized in {"pass", "passed", "true", "1", "yes"}:
            return True
        if normalized in {"fail", "failed", "false", "0", "no"}:
            return False
    if isinstance(value, (int, float)):
        return bool(value)
    return False


def _reference_context_for_agent(state_dir: Path, agent_key: str | None) -> dict[str, Any] | None:
    normalized_agent = str(agent_key or "").strip()
    if not normalized_agent:
        return None
    store = control_plane_store(state_dir)
    sets, _next_cursor = store.list_reference_sets(agent_key=normalized_agent, limit=1)
    if not sets:
        return None
    reference_set = sets[0]
    latest = store.latest_reference_score(reference_set.reference_set_id)
    summary = latest.summary if latest and isinstance(latest.summary, dict) else {}
    return {
        "reference_set_id": reference_set.reference_set_id,
        "avg_conformance": summary.get("avg_conformance"),
        "below_threshold": summary.get("below_threshold_count"),
    }


def _build_decision_report(state_dir: Path, run_id: str) -> dict[str, Any]:
    plan_payload = _load_pending_plan_payload(state_dir, run_id)
    if not isinstance(plan_payload, dict):
        raise HTTPException(status_code=404, detail="Pending plan not found")

    insights = _summarize_pending_plan_data(plan_payload)
    pending_items = _load_pending_items(state_dir)
    pending_item = next((item for item in pending_items if str(item.get("run_id")) == run_id), None)
    cluster_signature = str(pending_item.get("cluster_signature") or "") if isinstance(pending_item, dict) else ""
    cluster_key = str(pending_item.get("cluster_key") or "") if isinstance(pending_item, dict) else ""
    clustered_items = (
        [item for item in pending_items if str(item.get("cluster_signature") or "") == cluster_signature]
        if cluster_signature
        else [pending_item] if isinstance(pending_item, dict) else []
    )
    cluster_run_ids = [str(item.get("run_id") or "").strip() for item in clustered_items if str(item.get("run_id") or "").strip()]
    blast_record_count = sum(_coerce_int(item.get("affected_records"), 0) for item in clustered_items) if clustered_items else _coerce_int(insights.get("record_count"), 0)
    sobject_types = sorted(set(insights.get("object_counts", {}).keys())) if isinstance(insights.get("object_counts"), dict) else []
    source_scout = str((pending_item or {}).get("source_scout") or "").strip()
    action_key = str((pending_item or {}).get("action_key") or "").strip()
    signal_display_name = source_scout.replace("_", " ").title() if source_scout else "Unknown Signal"
    try:
        loader = ScoutLoader()
        definition = loader.get_scout(source_scout) if source_scout else None
        if definition and definition.display_name:
            signal_display_name = definition.display_name
    except Exception:
        pass

    proposal_payload: dict[str, Any] = {}
    proposal: dict[str, Any] = {}
    compile_result: dict[str, Any] = {}
    evidence_rows: list[dict[str, Any]] = []
    proposal_id = str(plan_payload.get("proposal_id") or "").strip()
    if proposal_id:
        payload = proposal_detail_payload(control_plane_store(state_dir), proposal_id)
        if isinstance(payload, dict):
            proposal_payload = payload
            proposal = payload.get("proposal") if isinstance(payload.get("proposal"), dict) else {}
            compile_result = payload.get("latest_compile_result") if isinstance(payload.get("latest_compile_result"), dict) else {}
            evidence_rows = [row for row in (payload.get("evidence") or []) if isinstance(row, dict)]

    confidence = proposal.get("confidence") if isinstance(proposal.get("confidence"), dict) else {}
    proposal_risk = proposal.get("risk") if isinstance(proposal.get("risk"), dict) else {}
    plan_risk = plan_payload.get("risk") if isinstance(plan_payload.get("risk"), dict) else {}
    derived_risk = proposal_risk or _risk_from_level(_coerce_int(plan_risk.get("max_risk_level"), 0))
    compile_snapshot = plan_payload.get("compile_gate_snapshot") if isinstance(plan_payload.get("compile_gate_snapshot"), dict) else {}
    compile_gate_payload = (
        compile_result.get("gate_checks")
        if isinstance(compile_result.get("gate_checks"), dict)
        else compile_snapshot.get("gate_checks")
        if isinstance(compile_snapshot.get("gate_checks"), dict)
        else {}
    )

    agent_module_id = None
    action_candidates = proposal.get("action_candidates") if isinstance(proposal.get("action_candidates"), list) else []
    for candidate in 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_key else None
        if isinstance(action_spec, dict):
            module_id = str(action_spec.get("module_id") or "").strip()
            if module_id:
                agent_module_id = module_id
                break
    if not agent_module_id:
        for insight_action_key in insights.get("action_keys") or []:
            action_spec = ACTION_KEY_REGISTRY.get(str(insight_action_key or "").strip())
            if not isinstance(action_spec, dict):
                continue
            module_id = str(action_spec.get("module_id") or "").strip()
            if module_id:
                agent_module_id = module_id
                break

    def _optional_float(value: Any) -> float | None:
        if value is None:
            return None
        if isinstance(value, str) and not value.strip():
            return None
        try:
            return float(value)
        except Exception:
            return None

    objective = (
        str(proposal.get("objective") or "").strip()
        or str((pending_item or {}).get("human_title") or "").strip()
        or str(insights.get("title") or "").strip()
        or str(plan_payload.get("summary") or "").strip()
        or "Review pending exception"
    )
    why_summary = (
        str(proposal.get("why_summary") or "").strip()
        or str(plan_payload.get("why_summary") or "").strip()
    )
    if not why_summary:
        if proposal_id:
            why_summary = "No rationale summary is attached to this proposal."
        else:
            why_summary = "Auto-generated from deterministic scout findings. AI rationale is not available for this run."

    overall_confidence = _optional_float(confidence.get("overall")) if confidence else None
    calibrated_confidence = _optional_float(confidence.get("calibrated_score")) if confidence else None

    return {
        "objective": objective,
        "why_summary": why_summary,
        "blast_radius": {
            "record_count": blast_record_count,
            "sobject_types": sobject_types,
            "run_ids": cluster_run_ids or [run_id],
        },
        "confidence": {
            "overall": overall_confidence,
            "calibrated": calibrated_confidence,
        },
        "risk": {
            "tier": str(derived_risk.get("tier") or "low").strip().lower() or "low",
            "score": _coerce_float(derived_risk.get("score"), 0.0),
            "factors": [str(factor) for factor in (derived_risk.get("factors") or []) if str(factor).strip()],
        },
        "compile_gates": {
            str(name): _as_gate_pass(value)
            for name, value in compile_gate_payload.items()
            if isinstance(name, str)
        },
        "evidence_excerpts": _evidence_excerpts_for_decision_report(state_dir, evidence_rows, limit=3),
        "reference_context": _reference_context_for_agent(state_dir, agent_module_id),
        "steps": plan_payload.get("steps") if isinstance(plan_payload.get("steps"), list) else [],
        "agent_module_id": agent_module_id,
        "proposal_id": proposal_id or None,
        "compile_id": str(plan_payload.get("compile_id") or "").strip() or None,
        "cluster_key": cluster_key or None,
        "cluster_signature": cluster_signature or None,
        "source_kind": str(plan_payload.get("source_kind") or "").strip() or None,
        "signal_name": signal_display_name,
        "action_label": _tool_action_label(action_key),
        "plan_origin": "proposal" if proposal_id else "auto_generated",
    }


def _pending_rank_sort_key(item: dict[str, Any]) -> tuple[int, int, float]:
    risk = item.get("risk")
    risk_level = 0
    if isinstance(risk, dict):
        risk_level = _coerce_int(risk.get("max_risk_level"), 0)
    affected_records = _coerce_int(item.get("affected_records"), 0)
    created_at = _as_utc(_parse_iso(item.get("created_at")))
    created_ts = created_at.timestamp() if created_at else 0.0
    return (risk_level, affected_records, created_ts)


def _summarize_receipts_window(state_dir: Path, *, lookback_days: int) -> dict[str, Any]:
    bounded_lookback = max(1, min(int(lookback_days), 3650))
    now = datetime.now(timezone.utc)
    cutoff = now - timedelta(days=bounded_lookback)
    receipts = sorted(_load_execution_receipts(state_dir), key=_execution_receipt_sort_key, reverse=True)

    summary: dict[str, Any] = {
        "runs_total": 0,
        "success_runs": 0,
        "failed_runs": 0,
        "approval_required_runs": 0,
        "auto_executed_runs": 0,
        "changes_applied": 0,
        "outcomes": {},
        "recent_runs": [],
    }

    failure_outcomes = {
        "failed",
        "partial_failure",
        "denied",
        "budget_exceeded",
        "precheck_failed",
    }

    for payload in receipts:
        if not isinstance(payload, dict):
            continue
        ts = _as_utc(
            _parse_iso(str(payload.get("completed_at") or ""))
            or _parse_iso(str(payload.get("started_at") or ""))
        )
        if ts is None or ts < cutoff:
            continue

        summary["runs_total"] += 1
        outcome = str(payload.get("outcome") or "unknown").strip().lower() or "unknown"
        outcomes = summary["outcomes"]
        if isinstance(outcomes, dict):
            outcomes[outcome] = _coerce_int(outcomes.get(outcome), 0) + 1

        if outcome == "success":
            summary["success_runs"] += 1
        if outcome in failure_outcomes:
            summary["failed_runs"] += 1
        if outcome == "approval_required":
            summary["approval_required_runs"] += 1
        if not str(payload.get("approved_by") or "").strip():
            summary["auto_executed_runs"] += 1

        changes = payload.get("changes")
        summary["changes_applied"] += len(changes) if isinstance(changes, list) else 0

        recent_runs = summary["recent_runs"]
        if isinstance(recent_runs, list) and len(recent_runs) < 8:
            recent_runs.append(
                {
                    "run_id": payload.get("run_id"),
                    "outcome": outcome,
                    "completed_at": payload.get("completed_at") or payload.get("started_at"),
                    "change_count": len(changes) if isinstance(changes, list) else 0,
                    "summary": payload.get("summary"),
                    "source": payload.get("source"),
                }
            )

    return summary


def _build_discovery_summary(state_dir: Path) -> dict[str, Any]:
    store = DiscoveryStore(Path(state_dir))
    run_id = store.latest_run_id()
    if run_id is None:
        return {
            "status": "empty",
            "run_id": None,
            "completed_at": None,
            "crm_provider": None,
            "readiness_overall": None,
            "readiness_breakdown": {},
            "object_profiles_count": 0,
            "field_mapping_suggestions_count": 0,
            "needs_confirmation_count": 0,
            "scout_recommendations_count": 0,
            "total_findings": 0,
            "scouts_with_findings": 0,
            "scout_findings_summary": {},
        }

    run = store.load_run(run_id)
    report = store.load_report(run_id)
    if run is None:
        return {
            "status": "empty",
            "run_id": None,
            "completed_at": None,
            "crm_provider": None,
            "readiness_overall": None,
            "readiness_breakdown": {},
            "object_profiles_count": 0,
            "field_mapping_suggestions_count": 0,
            "needs_confirmation_count": 0,
            "scout_recommendations_count": 0,
            "total_findings": 0,
            "scouts_with_findings": 0,
            "scout_findings_summary": {},
        }

    report_payload = report.to_dict() if report is not None else {}
    scout_findings_summary = report_payload.get("scout_findings_summary")
    if not isinstance(scout_findings_summary, dict):
        scout_findings_summary = {}

    total_findings = 0
    scouts_with_findings = 0
    for summary in scout_findings_summary.values():
        if not isinstance(summary, dict):
            continue
        findings_count = _coerce_int(summary.get("findings_count"), 0)
        total_findings += max(findings_count, 0)
        if findings_count > 0:
            scouts_with_findings += 1

    readiness = report_payload.get("readiness")
    readiness_overall = None
    readiness_breakdown: dict[str, float] = {}
    if isinstance(readiness, dict):
        raw_overall = readiness.get("overall")
        if isinstance(raw_overall, (int, float)):
            readiness_overall = max(0.0, min(float(raw_overall), 1.0))
        raw_breakdown = readiness.get("breakdown")
        if isinstance(raw_breakdown, dict):
            for key, value in raw_breakdown.items():
                if isinstance(value, (int, float)):
                    readiness_breakdown[str(key)] = max(0.0, min(float(value), 1.0))

    mapping_suggestions = report_payload.get("field_mapping_suggestions")
    needs_confirmation_count = 0
    if isinstance(mapping_suggestions, list):
        for item in mapping_suggestions:
            if isinstance(item, dict) and bool(item.get("needs_confirmation")):
                needs_confirmation_count += 1

    return {
        "status": "ok",
        "run_id": run.run_id,
        "completed_at": run.completed_at,
        "crm_provider": run.crm_provider,
        "readiness_overall": readiness_overall,
        "readiness_breakdown": readiness_breakdown,
        "object_profiles_count": len(report_payload.get("object_profiles") or []),
        "field_mapping_suggestions_count": len(mapping_suggestions or []),
        "needs_confirmation_count": needs_confirmation_count,
        "scout_recommendations_count": len(report_payload.get("scout_recommendations") or []),
        "total_findings": total_findings,
        "scouts_with_findings": scouts_with_findings,
        "scout_findings_summary": scout_findings_summary,
    }


def _severity_rank(value: str | None) -> int:
    normalized = str(value or "").strip().lower()
    if normalized in {"critical", "sev1", "p0"}:
        return 4
    if normalized in {"high", "sev2", "p1"}:
        return 3
    if normalized in {"medium", "sev3", "p2"}:
        return 2
    if normalized in {"low", "sev4", "p3"}:
        return 1
    return 0


def _finding_severity_from_payload(payload: dict[str, Any]) -> str:
    severity = str(payload.get("severity") or "").strip().lower()
    if severity:
        return severity
    evidence = payload.get("evidence")
    if isinstance(evidence, list):
        for row in evidence:
            if not isinstance(row, dict):
                continue
            row_severity = str(row.get("severity") or "").strip().lower()
            if row_severity:
                return row_severity
    return "medium"


def _scout_actionability(state_dir: Path) -> dict[str, dict[str, Any]]:
    by_scout: dict[str, dict[str, Any]] = {}
    action_registry = ACTION_KEY_REGISTRY if isinstance(ACTION_KEY_REGISTRY, dict) else {}
    enabled_modules = {module.module_id for module in ModuleStore(state_dir).list_modules() if module.enabled}
    for action_key, action_spec in action_registry.items():
        if not isinstance(action_spec, dict):
            continue
        module_id = str(action_spec.get("module_id") or "").strip()
        supported = action_spec.get("supported_scouts")
        if not isinstance(supported, list):
            continue
        for raw_name in supported:
            scout_name = str(raw_name or "").strip()
            if not scout_name:
                continue
            current = by_scout.setdefault(
                scout_name,
                {
                    "action_keys": [],
                    "module_ids": [],
                },
            )
            if action_key not in current["action_keys"]:
                current["action_keys"].append(action_key)
            if module_id and module_id not in current["module_ids"]:
                current["module_ids"].append(module_id)
    for scout_name, payload in by_scout.items():
        module_ids = payload.get("module_ids") if isinstance(payload.get("module_ids"), list) else []
        if not module_ids:
            payload["actionability"] = "unsupported"
        elif any(module_id in enabled_modules for module_id in module_ids):
            payload["actionability"] = "actionable"
        else:
            payload["actionability"] = "manual"
    return by_scout


def _scout_metadata() -> dict[str, dict[str, Any]]:
    metadata: dict[str, dict[str, Any]] = {}
    try:
        for definition in ScoutLoader().list_scouts():
            metadata[definition.name] = {
                "display_name": definition.display_name or definition.name,
                "severity_default": definition.severity_default or "medium",
                "category": definition.category,
            }
    except Exception:
        metadata = {}
    return metadata


def _build_findings_summary(
    *,
    discovery_summary: dict[str, Any],
    state_dir: Path,
    limit: int = 8,
) -> dict[str, Any]:
    items: list[dict[str, Any]] = []
    source = "none"
    scout_meta = _scout_metadata()
    actionability_map = _scout_actionability(state_dir)
    latest_finding_by_scout = _latest_scan_finding_by_scout(state_dir)

    scout_findings_summary = discovery_summary.get("scout_findings_summary")
    if isinstance(scout_findings_summary, dict) and scout_findings_summary:
        source = "discovery"
        for scout_name, raw_summary in scout_findings_summary.items():
            if not isinstance(raw_summary, dict):
                continue
            normalized_scout = str(scout_name or "").strip() or "unknown"
            findings_count = _coerce_int(raw_summary.get("findings_count"), 0)
            status = str(raw_summary.get("status") or "unknown").strip().lower() or "unknown"
            needs_attention = findings_count > 0 or status in {"findings", "error", "unsupported"}
            if not needs_attention:
                continue
            severity = "medium"
            latest = latest_finding_by_scout.get(normalized_scout)
            if isinstance(latest, dict):
                finding_payload = latest.get("finding")
                if isinstance(finding_payload, dict):
                    severity = _finding_severity_from_payload(finding_payload)
            meta = scout_meta.get(normalized_scout, {})
            if not severity:
                severity = str(meta.get("severity_default") or "medium")
            actionability_info = actionability_map.get(normalized_scout, {})
            items.append(
                {
                    "scout_name": normalized_scout,
                    "display_name": str(meta.get("display_name") or normalized_scout.replace("_", " ").title()),
                    "status": status,
                    "severity": severity,
                    "findings_count": findings_count,
                    "records_scanned": _coerce_int(raw_summary.get("total_records_scanned"), 0),
                    "category": raw_summary.get("category") or meta.get("category"),
                    "actionability": str(actionability_info.get("actionability") or "unsupported"),
                    "action_keys": actionability_info.get("action_keys") if isinstance(actionability_info.get("action_keys"), list) else [],
                    "source_card_id": "findings",
                }
            )

    if not items:
        latest_scan = _latest_scan_result(state_dir)
        findings = latest_scan.get("findings") if isinstance(latest_scan, dict) else None
        if isinstance(findings, list) and findings:
            source = "scan"
            aggregates: dict[str, dict[str, Any]] = {}
            for raw_finding in findings:
                if not isinstance(raw_finding, dict):
                    continue
                scout_name = str(raw_finding.get("scout_name") or "").strip() or "unknown"
                severity = _finding_severity_from_payload(raw_finding)
                meta = scout_meta.get(scout_name, {})
                actionability_info = actionability_map.get(scout_name, {})
                current = aggregates.setdefault(
                    scout_name,
                    {
                        "scout_name": scout_name,
                        "display_name": str(meta.get("display_name") or scout_name.replace("_", " ").title()),
                        "status": "findings",
                        "severity": severity,
                        "findings_count": 0,
                        "records_scanned": 0,
                        "category": meta.get("category"),
                        "actionability": str(actionability_info.get("actionability") or "unsupported"),
                        "action_keys": actionability_info.get("action_keys") if isinstance(actionability_info.get("action_keys"), list) else [],
                        "source_card_id": "findings",
                    },
                )
                current["findings_count"] = _coerce_int(current.get("findings_count"), 0) + _coerce_int(raw_finding.get("findings_count"), 1)
                current["records_scanned"] = _coerce_int(current.get("records_scanned"), 0) + _coerce_int(raw_finding.get("total_records_scanned"), 0)
                if _severity_rank(severity) > _severity_rank(str(current.get("severity") or "")):
                    current["severity"] = severity
            items.extend(aggregates.values())

    items.sort(
        key=lambda item: (
            _severity_rank(str(item.get("severity") or "")),
            _coerce_int(item.get("findings_count"), 0),
            str(item.get("display_name") or item.get("scout_name") or ""),
        ),
        reverse=True,
    )
    trimmed = items[: max(1, min(int(limit), 50))]
    open_scouts = sum(1 for item in trimmed if _coerce_int(item.get("findings_count"), 0) > 0)
    total_findings = sum(_coerce_int(item.get("findings_count"), 0) for item in trimmed)
    return {
        "source": source,
        "open_scouts": open_scouts,
        "total_findings": total_findings,
        "items": trimmed,
    }


def _build_substrate_summary(state_dir: Path) -> dict[str, Any]:
    db_path = _platform_db_path(Path(state_dir))
    payload: dict[str, Any] = {
        "identity": {"entities": 0, "entity_links": 0},
        "provenance": {"observations": 0, "by_source_system": []},
    }
    try:
        from .identity_graph import IdentityGraph
        from .field_provenance import FieldProvenanceStore

        identity = IdentityGraph(db_path)
        provenance = FieldProvenanceStore(db_path)
        payload["identity"] = identity.stats()
        payload["provenance"] = {
            **provenance.stats(),
            "by_source_system": provenance.source_system_counts(),
        }
    except Exception as exc:
        payload["error"] = str(exc)
    return payload


def _orchestration_module_sort_key(item: dict[str, Any]) -> tuple[int, float, int]:
    recommendation = item.get("recommendation")
    action = ""
    if isinstance(recommendation, dict):
        action = str(recommendation.get("action") or "").strip().lower()
    action_rank = {"promote_to_auto": 3, "tighten_controls": 2, "hold": 1}.get(action, 0)
    metrics = item.get("metrics")
    trust_score = None
    total_runs = 0
    if isinstance(metrics, dict):
        raw_trust = metrics.get("trust_score")
        if isinstance(raw_trust, (int, float)):
            trust_score = float(raw_trust)
        total_runs = _coerce_int(metrics.get("total_runs"), 0)
    return (action_rank, trust_score if trust_score is not None else -1.0, total_runs)


def _dealdesk_saved_snapshot_rows(payload: dict[str, Any], *, limit: int = 7) -> list[dict[str, Any]]:
    rows = payload.get("saved_snapshots") if isinstance(payload.get("saved_snapshots"), list) else []
    summarized: list[dict[str, Any]] = []
    for item in rows[: max(1, min(int(limit), 30))]:
        if not isinstance(item, dict):
            continue
        summarized.append(
            {
                "snapshot_id": str(item.get("snapshot_id") or "").strip() or None,
                "day_key": str(item.get("day_key") or "").strip() or None,
                "generated_at": str(item.get("generated_at") or "").strip() or None,
                "data_source": str(item.get("data_source") or "").strip() or "legacy_inferred_state",
                "run_count": _coerce_int(item.get("run_count"), 0),
                "submitted_for_close_count": _coerce_int(item.get("submitted_for_close_count"), 0),
                "straight_through_progression_rate": item.get("straight_through_progression_rate"),
                "exception_rate": item.get("exception_rate"),
                "document_escalation_rate": item.get("document_escalation_rate"),
                "document_extraction_confidence_avg": item.get("document_extraction_confidence_avg"),
                "document_extraction_method_counts": item.get("document_extraction_method_counts")
                if isinstance(item.get("document_extraction_method_counts"), dict)
                else {},
            }
        )
    return summarized


def _build_dealdesk_summary_card(payload: dict[str, Any]) -> dict[str, Any]:
    latest = payload.get("latest_saved_snapshot") if isinstance(payload.get("latest_saved_snapshot"), dict) else {}
    run_count = _coerce_int(latest.get("run_count"), _coerce_int(payload.get("run_count"), 0))
    status = "ok" if run_count > 0 else "empty"
    return {
        "status": status,
        "generated_at": payload.get("generated_at"),
        "snapshot_id": payload.get("snapshot_id"),
        "day_key": payload.get("day_key"),
        "data_source": str(latest.get("data_source") or payload.get("data_source") or "legacy_inferred_state"),
        "submitted_for_close_count": _coerce_int(
            latest.get("submitted_for_close_count"),
            _coerce_int(payload.get("submitted_for_close_count"), 0),
        ),
        "retro_validation_count": _coerce_int(
            latest.get("retro_validation_count"),
            _coerce_int(payload.get("retro_validation_count"), 0),
        ),
        "run_count": run_count,
        "straight_through_progression_rate": latest.get(
            "straight_through_progression_rate",
            payload.get("straight_through_progression_rate"),
        ),
        "exception_rate": latest.get("exception_rate", payload.get("exception_rate")),
        "document_escalation_rate": latest.get(
            "document_escalation_rate",
            payload.get("document_escalation_rate"),
        ),
        "document_extraction_confidence_avg": latest.get(
            "document_extraction_confidence_avg",
            payload.get("document_extraction_confidence_avg"),
        ),
        "document_extraction_method_counts": latest.get("document_extraction_method_counts")
        if isinstance(latest.get("document_extraction_method_counts"), dict)
        else payload.get("document_extraction_method_counts")
        if isinstance(payload.get("document_extraction_method_counts"), dict)
        else {},
        "document_extraction_status_counts": latest.get("document_extraction_status_counts")
        if isinstance(latest.get("document_extraction_status_counts"), dict)
        else payload.get("document_extraction_status_counts")
        if isinstance(payload.get("document_extraction_status_counts"), dict)
        else {},
        "history_granularity": payload.get("history_granularity"),
        "drilldown_path": "/api/admin/dealdesk/metrics",
        "review_path": "/dealdesk/reviews",
        "recent_snapshots": _dealdesk_saved_snapshot_rows(payload, limit=7),
        "source_card_id": "dealdesk",
    }


async def _build_integrations_summary(app: FastAPI, request: Request) -> dict[str, Any]:
    sync_result = _sync_outreach_bridge_session(app, state_dir=Path(app.state.state_dir))
    if sync_result.get("changed"):
        _invalidate_credentials_cache(app)
        _invalidate_integration_status_cache(app)

    integrations = await _full_integration_status(app, request)
    total = len(integrations)
    connected = 0
    error_count = 0
    pending_count = 0
    for status in integrations.values():
        if not isinstance(status, dict):
            continue
        if bool(status.get("connected")):
            connected += 1
        normalized = str(status.get("status") or "").strip().lower()
        if normalized == "error":
            error_count += 1
        if normalized == "pending":
            pending_count += 1

    return {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "total": total,
        "connected": connected,
        "error": error_count,
        "pending": pending_count,
        "items": integrations,
    }


async def _build_admin_summary_snapshot(
    app: FastAPI,
    request: Request,
    *,
    lookback_days: int = 30,
    health_payload: dict[str, Any] | None = None,
) -> dict[str, Any]:
    bounded_lookback = max(1, min(int(lookback_days), 3650))
    state_dir = Path(app.state.state_dir)

    if health_payload is None:
        sd = Path(app.state.state_dir)
        current_policy = apply_policy_overlays(app.state.policy, sd)
        heartbeat = _read_daemon_heartbeat(sd)
        daemon_running = _daemon_running(sd)
        next_scheduled_run: str | None = None
        schedule_profile: str | None = None
        last_scan_at = _latest_scan_timestamp(sd)
        if heartbeat:
            schedule_profile = heartbeat.get("profile")
            updated_at = _parse_iso(heartbeat.get("updated_at"))
            stale_threshold = _heartbeat_stale_threshold(heartbeat)
            if updated_at and (datetime.now(timezone.utc) - updated_at) < stale_threshold:
                next_scheduled_run = heartbeat.get("next_run_at")
                if heartbeat.get("last_scan_at"):
                    last_scan_at = heartbeat["last_scan_at"]
            else:
                daemon_running = False
        health_payload = {
            "status": "ok",
            "version": "0.1.0",
            "daemon_running": daemon_running,
            "last_scan_at": last_scan_at,
            "policy_hash": compute_policy_hash(current_policy),
            "next_scheduled_run": next_scheduled_run,
            "schedule_profile": schedule_profile,
            "pending_count": _count_pending(sd),
            "recent_24h": _compute_recent_24h(sd),
        }

    pending_items = _load_pending_items(state_dir)
    ranked_pending = sorted(pending_items, key=_pending_rank_sort_key, reverse=True)
    pending_top = ranked_pending[:5]
    pending_clusters = _build_pending_clusters(state_dir, pending_items, window_seconds=PENDING_CLUSTER_WINDOW_SECONDS)
    pending_high_risk = sum(
        1
        for item in pending_items
        if isinstance(item.get("risk"), dict) and _coerce_int(item["risk"].get("max_risk_level"), 0) >= 2
    )
    needs_decision = {
        "pending_count": len(pending_items),
        "high_risk_count": pending_high_risk,
        "items": [
            {
                "run_id": item.get("run_id"),
                "title": item.get("human_title") or item.get("summary") or "Pending exception",
                "summary": item.get("summary"),
                "created_at": item.get("created_at"),
                "risk_level": _coerce_int((item.get("risk") or {}).get("max_risk_level"), 0)
                if isinstance(item.get("risk"), dict)
                else 0,
                "step_count": _coerce_int(item.get("step_count"), 0),
                "affected_records": _coerce_int(item.get("affected_records"), 0),
                "source_scouts": item.get("source_scouts") if isinstance(item.get("source_scouts"), list) else [],
                "cluster_key": item.get("cluster_key"),
                "cluster_signature": item.get("cluster_signature"),
                "source_card_id": "needs_decision",
            }
            for item in pending_top
        ],
        "clusters": pending_clusters[:8],
    }

    execution = _summarize_receipts_window(state_dir, lookback_days=bounded_lookback)
    discovery_summary = _build_discovery_summary(state_dir)
    findings_summary = _build_findings_summary(
        discovery_summary=discovery_summary,
        state_dir=state_dir,
        limit=8,
    )

    errors: dict[str, str] = {}
    gate_metrics: dict[str, Any] = {"status": "unavailable"}
    try:
        from .gate_metrics import collect_gate_metrics

        gate_metrics = collect_gate_metrics(state_dir, lookback_days=bounded_lookback)
    except Exception as exc:
        errors["gate_metrics"] = str(exc)
        gate_metrics = {"status": "error", "error": str(exc)}

    gate_health: dict[str, Any] = {"status": "unavailable"}
    gate_current = gate_metrics.get("current") if isinstance(gate_metrics.get("current"), dict) else {}
    gate_checks = gate_current.get("checks") if isinstance(gate_current.get("checks"), dict) else {}
    if gate_checks:
        total_checks = len(gate_checks)
        passed_checks = sum(
            1
            for payload in gate_checks.values()
            if isinstance(payload, dict) and payload.get("passed") is True
        )
        failed_checks = max(total_checks - passed_checks, 0)
        gate_health = {
            "status": "ok",
            "gate_passed": bool(gate_current.get("gate_passed")),
            "total_checks": total_checks,
            "passed_checks": passed_checks,
            "failed_checks": failed_checks,
            "source_card_id": "gate_metrics",
        }

    dealdesk_card: dict[str, Any] = {"status": "unavailable", "recent_snapshots": []}
    try:
        from g_gremlin.dealdesk.metrics import collect_dealdesk_metrics

        dealdesk_metrics = collect_dealdesk_metrics(state_dir, lookback_days=bounded_lookback)
        dealdesk_card = _build_dealdesk_summary_card(dealdesk_metrics)
    except Exception as exc:
        errors["dealdesk_metrics"] = str(exc)
        dealdesk_card = {"status": "error", "error": str(exc), "recent_snapshots": [], "source_card_id": "dealdesk"}

    orchestration_card: dict[str, Any] = {"status": "unavailable", "top_modules": []}
    try:
        from .orchestration_metrics import compute_orchestration_metrics

        orchestration_metrics = compute_orchestration_metrics(state_dir, lookback_days=bounded_lookback)
        modules = orchestration_metrics.get("modules")
        module_rows = [row for row in modules if isinstance(row, dict)] if isinstance(modules, list) else []
        module_rows = sorted(module_rows, key=_orchestration_module_sort_key, reverse=True)
        trust_deltas_7d: dict[str, float | None] = {}
        try:
            _persist_agent_trust_snapshots(state_dir, module_rows)
            for row in module_rows:
                module_id = str(row.get("module_id") or "").strip()
                metrics = row.get("metrics") if isinstance(row.get("metrics"), dict) else {}
                trust_score = (
                    float(metrics.get("trust_score"))
                    if isinstance(metrics.get("trust_score"), (int, float))
                    else None
                )
                trust_deltas_7d[module_id] = _agent_trust_delta_lookback(
                    state_dir,
                    module_id=module_id,
                    current_trust_score=trust_score,
                    lookback_days=AGENT_TRUST_DELTA_LOOKBACK_DAYS,
                )
        except Exception as exc:
            errors["agent_trust_snapshots"] = str(exc)

        top_modules: list[dict[str, Any]] = []
        for row in module_rows[:5]:
            module_id = str(row.get("module_id") or "").strip()
            metrics = row.get("metrics") if isinstance(row.get("metrics"), dict) else {}
            top_modules.append(
                {
                    "module_id": module_id,
                    "display_name": row.get("display_name"),
                    "stage": row.get("stage"),
                    "recommendation": (row.get("recommendation") or {}).get("action")
                    if isinstance(row.get("recommendation"), dict)
                    else None,
                    "trust_score": metrics.get("trust_score"),
                    "trust_delta_7d": trust_deltas_7d.get(module_id),
                    "override_rate": metrics.get("override_rate"),
                    "total_runs": metrics.get("total_runs"),
                    "source_card_id": "orchestration",
                }
            )
        orchestration_card = {
            "status": "ok",
            "generated_at": orchestration_metrics.get("generated_at"),
            "gate_passed": bool(orchestration_metrics.get("gate_passed")),
            "summary": orchestration_metrics.get("summary") if isinstance(orchestration_metrics.get("summary"), dict) else {},
            "top_modules": top_modules,
        }
    except Exception as exc:
        errors["orchestration_metrics"] = str(exc)
        orchestration_card = {"status": "error", "error": str(exc), "top_modules": []}

    control_plane_card: dict[str, Any] = {"status": "unavailable"}
    try:
        cp_metrics = control_plane_store(state_dir).metrics_summary(window_days=bounded_lookback)
        proposal_counts = cp_metrics.get("proposal_counts") if isinstance(cp_metrics.get("proposal_counts"), dict) else {}
        compile_counts = cp_metrics.get("compile_counts") if isinstance(cp_metrics.get("compile_counts"), dict) else {}
        autonomy_flow = cp_metrics.get("autonomy_flow") if isinstance(cp_metrics.get("autonomy_flow"), dict) else {}
        control_plane_card = {
            "status": "ok",
            "proposal_counts": proposal_counts,
            "compile_counts": compile_counts,
            "compile_pass_rate": cp_metrics.get("compile_pass_rate"),
            "autonomy_flow": autonomy_flow,
            "reject_distribution": cp_metrics.get("reject_distribution") if isinstance(cp_metrics.get("reject_distribution"), dict) else {},
            "unresolved_inbox_count": _coerce_int(cp_metrics.get("unresolved_inbox_count"), 0),
            "reference_sets_count": _coerce_int(cp_metrics.get("reference_sets_count"), 0),
            "latest_reference_score": cp_metrics.get("latest_reference_score")
            if isinstance(cp_metrics.get("latest_reference_score"), dict)
            else None,
            "action_registry_version": cp_metrics.get("action_registry_version"),
            "reject_taxonomy_version": cp_metrics.get("reject_taxonomy_version"),
            "source_card_id": "control_plane",
        }
    except Exception as exc:
        errors["control_plane_metrics"] = str(exc)
        control_plane_card = {"status": "error", "error": str(exc)}

    autonomy_card: dict[str, Any] = {"status": "unavailable"}
    try:
        operational = autonomy_operational_health(
            state_dir,
            lookback_days=bounded_lookback,
            event_window_hours=24,
        )
        crm_event_queue = operational.get("crm_event_queue") if isinstance(operational.get("crm_event_queue"), dict) else {}
        crm_queue_health = crm_event_queue.get("health") if isinstance(crm_event_queue.get("health"), dict) else {}
        crm_queue_counts = crm_queue_health.get("counts") if isinstance(crm_queue_health.get("counts"), dict) else {}
        crm_throughput = crm_event_queue.get("throughput") if isinstance(crm_event_queue.get("throughput"), dict) else {}
        control_plane_metrics = operational.get("control_plane") if isinstance(operational.get("control_plane"), dict) else {}
        control_plane_summary = (
            control_plane_metrics.get("summary") if isinstance(control_plane_metrics.get("summary"), dict) else {}
        )
        autonomy_flow = (
            control_plane_summary.get("autonomy_flow")
            if isinstance(control_plane_summary.get("autonomy_flow"), dict)
            else {}
        )
        verification = operational.get("verification") if isinstance(operational.get("verification"), dict) else {}
        rollback_coverage = (
            operational.get("rollback_coverage") if isinstance(operational.get("rollback_coverage"), dict) else {}
        )
        rollback_summary = (
            rollback_coverage.get("summary") if isinstance(rollback_coverage.get("summary"), dict) else {}
        )
        rollback_items = rollback_coverage.get("items") if isinstance(rollback_coverage.get("items"), list) else []
        manual_compensation_required = [
            {
                "write_class_id": str(item.get("write_class_id") or ""),
                "label": str(item.get("label") or ""),
                "reason": str(item.get("reason") or ""),
            }
            for item in rollback_items
            if isinstance(item, dict) and str(item.get("posture") or "") == "manual_compensation_required"
        ]
        intentionally_irreversible = [
            {
                "write_class_id": str(item.get("write_class_id") or ""),
                "label": str(item.get("label") or ""),
                "reason": str(item.get("reason") or ""),
            }
            for item in rollback_items
            if isinstance(item, dict) and str(item.get("posture") or "") == "intentionally_irreversible"
        ]
        autonomy_card = {
            "status": operational.get("status") or "ok",
            "healthy": bool(operational.get("healthy")),
            "issues": operational.get("issues") if isinstance(operational.get("issues"), list) else [],
            "event_queue": {
                "counts": crm_queue_counts,
                "oldest_pending_age_seconds": crm_queue_health.get("oldest_pending_age_seconds"),
                "oldest_dead_letter_age_seconds": crm_queue_health.get("oldest_dead_letter_age_seconds"),
                "throughput_events_per_hour": crm_throughput.get("throughput_events_per_hour"),
                "exception_rate": crm_throughput.get("exception_rate"),
            },
            "straight_through_rate": autonomy_flow.get("straight_through_rate"),
            "human_intervention_rate": autonomy_flow.get("human_intervention_rate"),
            "rejection_rate": autonomy_flow.get("rejection_rate"),
            "verification_failure_rate": verification.get("failure_rate"),
            "rollback_coverage": {
                "summary": rollback_summary,
                "manual_compensation_required": manual_compensation_required,
                "intentionally_irreversible": intentionally_irreversible,
            },
            "source_card_id": "autonomy",
        }
    except Exception as exc:
        errors["autonomy_operational_health"] = str(exc)
        autonomy_card = {"status": "error", "error": str(exc)}

    integrations_summary = await _build_integrations_summary(app, request)
    substrate_summary = _build_substrate_summary(state_dir)
    ai_status = AIProviderStore(state_dir).load_status().model_dump(exclude_none=False)

    cards = {
        "system_health": {
            "daemon_running": bool(health_payload.get("daemon_running")),
            "last_scan_at": health_payload.get("last_scan_at"),
            "next_scheduled_run": health_payload.get("next_scheduled_run"),
            "schedule_profile": health_payload.get("schedule_profile"),
            "policy_hash": health_payload.get("policy_hash"),
            "source_card_id": "system_health",
        },
        "activity_24h": health_payload.get("recent_24h") if isinstance(health_payload.get("recent_24h"), dict) else {},
        "needs_decision": needs_decision,
        "execution": execution,
        "discovery": discovery_summary,
        "findings": findings_summary,
        "gate_metrics": gate_metrics,
        "gate_health": gate_health,
        "dealdesk": dealdesk_card,
        "orchestration": orchestration_card,
        "control_plane": control_plane_card,
        "autonomy": autonomy_card,
        "substrate": substrate_summary,
        "integrations": integrations_summary,
    }

    payload: dict[str, Any] = {
        "generated_at": datetime.now(timezone.utc).isoformat(),
        "lookback_days": bounded_lookback,
        "cards": cards,
        "ai": ai_status,
    }
    if errors:
        payload["errors"] = errors
    return payload


def _summary_ai_input_payload(snapshot: dict[str, Any]) -> dict[str, Any]:
    cards = snapshot.get("cards") if isinstance(snapshot.get("cards"), dict) else {}
    integrations_card = cards.get("integrations") if isinstance(cards.get("integrations"), dict) else {}
    return {
        "generated_at": snapshot.get("generated_at"),
        "lookback_days": snapshot.get("lookback_days"),
        "cards": {
            "needs_decision": cards.get("needs_decision"),
            "findings": cards.get("findings"),
            "execution": cards.get("execution"),
            "system_health": cards.get("system_health"),
            "discovery": cards.get("discovery"),
            "gate_metrics": cards.get("gate_metrics"),
            "dealdesk": cards.get("dealdesk"),
            "orchestration": cards.get("orchestration"),
            "control_plane": cards.get("control_plane"),
            "autonomy": cards.get("autonomy"),
            "integrations": {
                "total": integrations_card.get("total"),
                "connected": integrations_card.get("connected"),
                "error": integrations_card.get("error"),
            },
        },
        "source_card_ids": list(cards.keys()),
    }


def _summary_brief_prompt(ai_input: dict[str, Any]) -> str:
    return (
        "You are producing an operations briefing for a CRM automation platform.\n"
        "Use only the deterministic snapshot data provided. Do not invent facts.\n"
        "Return ONLY valid JSON with this exact schema:\n"
        "{\n"
        '  "headline": "string",\n'
        '  "summary": "string",\n'
        '  "needs_decision": [{"title": "string", "rationale": "string", "source_card_id": "string"}],\n'
        '  "wins": [{"title": "string", "rationale": "string", "source_card_id": "string"}],\n'
        '  "risks": [{"title": "string", "rationale": "string", "source_card_id": "string"}],\n'
        '  "next_actions": [{"action": "string", "urgency": "now|today|this_week|monitor", "source_card_id": "string"}]\n'
        "}\n"
        "Rules:\n"
        "- source_card_id must reference one of source_card_ids.\n"
        "- Keep each list short (0 to 4 items).\n"
        "- Be concrete and concise.\n\n"
        f"Snapshot:\n{json.dumps(ai_input, indent=2)}\n"
    )


def _strip_markdown_fence(text: str) -> str:
    stripped = text.strip()
    if stripped.startswith("```"):
        first_newline = stripped.find("\n")
        if first_newline >= 0:
            stripped = stripped[first_newline + 1 :]
    if stripped.endswith("```"):
        stripped = stripped[:-3]
    return stripped.strip()


def _parse_summary_brief_payload(raw: str) -> dict[str, Any] | None:
    text = str(raw or "").strip()
    if not text:
        return None
    if "```" in text:
        text = _strip_markdown_fence(text)

    parsed: Any
    try:
        parsed = json.loads(text)
    except Exception:
        start = text.find("{")
        end = text.rfind("}")
        if start < 0 or end < start:
            return None
        try:
            parsed = json.loads(text[start : end + 1])
        except Exception:
            return None

    if not isinstance(parsed, dict):
        return None
    return parsed


def _normalize_source_card_id(
    value: Any,
    *,
    allowed_source_cards: set[str],
    default_card: str,
) -> str:
    candidate = str(value or "").strip()
    if candidate in allowed_source_cards:
        return candidate
    if default_card in allowed_source_cards:
        return default_card
    if allowed_source_cards:
        return sorted(allowed_source_cards)[0]
    return default_card


def _normalize_summary_brief_items(
    raw_items: Any,
    *,
    section: str,
    allowed_source_cards: set[str],
    fallback_source_card: str,
) -> list[dict[str, Any]]:
    if not isinstance(raw_items, list):
        return []
    normalized: list[dict[str, Any]] = []
    for raw_item in raw_items[:4]:
        if not isinstance(raw_item, dict):
            continue
        source_card_id = _normalize_source_card_id(
            raw_item.get("source_card_id"),
            allowed_source_cards=allowed_source_cards,
            default_card=fallback_source_card,
        )
        if section == "next_actions":
            action = str(raw_item.get("action") or "").strip()
            if not action:
                continue
            urgency = str(raw_item.get("urgency") or "").strip().lower() or "this_week"
            if urgency not in {"now", "today", "this_week", "monitor"}:
                urgency = "this_week"
            normalized.append(
                {
                    "action": action,
                    "urgency": urgency,
                    "source_card_id": source_card_id,
                }
            )
            continue

        title = str(raw_item.get("title") or "").strip()
        rationale = str(raw_item.get("rationale") or "").strip()
        if not title:
            continue
        normalized.append(
            {
                "title": title,
                "rationale": rationale,
                "source_card_id": source_card_id,
            }
        )
    return normalized


def _normalize_summary_brief(
    payload: dict[str, Any],
    *,
    allowed_source_cards: set[str],
    fallback: dict[str, Any],
) -> dict[str, Any]:
    headline = str(payload.get("headline") or "").strip() or str(fallback.get("headline") or "")
    summary = str(payload.get("summary") or "").strip() or str(fallback.get("summary") or "")

    needs_decision = _normalize_summary_brief_items(
        payload.get("needs_decision"),
        section="needs_decision",
        allowed_source_cards=allowed_source_cards,
        fallback_source_card="needs_decision",
    )
    wins = _normalize_summary_brief_items(
        payload.get("wins"),
        section="wins",
        allowed_source_cards=allowed_source_cards,
        fallback_source_card="execution",
    )
    risks = _normalize_summary_brief_items(
        payload.get("risks"),
        section="risks",
        allowed_source_cards=allowed_source_cards,
        fallback_source_card="findings",
    )
    next_actions = _normalize_summary_brief_items(
        payload.get("next_actions"),
        section="next_actions",
        allowed_source_cards=allowed_source_cards,
        fallback_source_card="needs_decision",
    )

    if not needs_decision:
        needs_decision = fallback.get("needs_decision") if isinstance(fallback.get("needs_decision"), list) else []
    if not wins:
        wins = fallback.get("wins") if isinstance(fallback.get("wins"), list) else []
    if not risks:
        risks = fallback.get("risks") if isinstance(fallback.get("risks"), list) else []
    if not next_actions:
        next_actions = fallback.get("next_actions") if isinstance(fallback.get("next_actions"), list) else []

    return {
        "headline": headline,
        "summary": summary,
        "needs_decision": needs_decision,
        "wins": wins,
        "risks": risks,
        "next_actions": next_actions,
    }


def _build_deterministic_summary_brief(snapshot: dict[str, Any]) -> dict[str, Any]:
    cards = snapshot.get("cards")
    if not isinstance(cards, dict):
        cards = {}
    needs_decision = cards.get("needs_decision") if isinstance(cards.get("needs_decision"), dict) else {}
    findings = cards.get("findings") if isinstance(cards.get("findings"), dict) else {}
    execution = cards.get("execution") if isinstance(cards.get("execution"), dict) else {}
    system = cards.get("system_health") if isinstance(cards.get("system_health"), dict) else {}
    gate_metrics = cards.get("gate_metrics") if isinstance(cards.get("gate_metrics"), dict) else {}
    gate_current = gate_metrics.get("current") if isinstance(gate_metrics.get("current"), dict) else {}
    dealdesk = cards.get("dealdesk") if isinstance(cards.get("dealdesk"), dict) else {}

    pending_count = _coerce_int(needs_decision.get("pending_count"), 0)
    open_scouts = _coerce_int(findings.get("open_scouts"), 0)
    total_findings = _coerce_int(findings.get("total_findings"), 0)
    runs_total = _coerce_int(execution.get("runs_total"), 0)
    success_runs = _coerce_int(execution.get("success_runs"), 0)
    daemon_running = bool(system.get("daemon_running"))
    dealdesk_submitted = _coerce_int(dealdesk.get("submitted_for_close_count"), 0)
    dealdesk_stp = dealdesk.get("straight_through_progression_rate")
    dealdesk_exception_rate = dealdesk.get("exception_rate")
    dealdesk_escalation_rate = dealdesk.get("document_escalation_rate")

    if pending_count > 0:
        headline = f"{pending_count} exceptions are waiting for review."
    elif total_findings > 0:
        headline = f"{total_findings} active signals need follow-up."
    elif dealdesk_submitted > 0 and isinstance(dealdesk_exception_rate, (int, float)) and float(dealdesk_exception_rate) > 0.0:
        headline = f"Deal Desk has {dealdesk_submitted} recent close submissions with certification exceptions."
    else:
        headline = "No pending exceptions or active signals right now."

    summary = (
        f"Lookback {snapshot.get('lookback_days')}d: "
        f"{runs_total} execution runs observed, {success_runs} successful, "
        f"{open_scouts} signal sources with open findings."
    )

    needs_decision_items: list[dict[str, Any]] = []
    raw_pending_items = needs_decision.get("items")
    if isinstance(raw_pending_items, list):
        for item in raw_pending_items[:3]:
            if not isinstance(item, dict):
                continue
            title = str(item.get("title") or item.get("summary") or "").strip()
            if not title:
                continue
            risk_level = _coerce_int(item.get("risk_level"), 0)
            affected = _coerce_int(item.get("affected_records"), 0)
            needs_decision_items.append(
                {
                    "title": title,
                    "rationale": f"Risk {risk_level}; affects about {affected} records.",
                    "source_card_id": "needs_decision",
                }
            )

    wins: list[dict[str, Any]] = []
    if daemon_running:
        wins.append(
            {
                "title": "Daemon is online",
                "rationale": "Scheduled scans and agent execution can continue without manual intervention.",
                "source_card_id": "system_health",
            }
        )
    if success_runs > 0:
        success_rate = (success_runs / runs_total) if runs_total > 0 else 0.0
        wins.append(
            {
                "title": f"{success_runs} successful execution runs",
                "rationale": f"Recent success rate is {round(success_rate * 100, 1)}%.",
                "source_card_id": "execution",
            }
        )
    if bool(gate_current.get("gate_passed")):
        wins.append(
            {
                "title": "Gate metrics currently pass",
                "rationale": "Promotion prerequisites are being met based on current telemetry snapshot.",
                "source_card_id": "gate_metrics",
            }
        )
    if (
        dealdesk_submitted > 0
        and isinstance(dealdesk_stp, (int, float))
        and float(dealdesk_stp) >= 0.5
    ):
        wins.append(
            {
                "title": "Deal Desk is progressing clean closes straight through",
                "rationale": (
                    f"Straight-through close progression is {round(float(dealdesk_stp) * 100, 1)}% "
                    f"across {dealdesk_submitted} recent submissions."
                ),
                "source_card_id": "dealdesk",
            }
        )

    risks: list[dict[str, Any]] = []
    if not daemon_running:
        risks.append(
            {
                "title": "Daemon is stopped",
                "rationale": "No background scans or agent actions will execute until restarted.",
                "source_card_id": "system_health",
            }
        )
    if total_findings > 0:
        risks.append(
            {
                "title": f"{total_findings} open findings across {open_scouts} scouts",
                "rationale": "These findings may indicate active operational or data quality issues.",
                "source_card_id": "findings",
            }
        )
    gate_checks = gate_current.get("checks") if isinstance(gate_current.get("checks"), dict) else {}
    failed_checks = [
        str(name).replace("_", " ")
        for name, value in gate_checks.items()
        if isinstance(value, dict) and value.get("passed") is False
    ]
    if failed_checks:
        risks.append(
            {
                "title": "Gate criteria not yet met",
                "rationale": f"Failing checks: {', '.join(failed_checks[:3])}.",
                "source_card_id": "gate_metrics",
            }
        )
    if (
        dealdesk_submitted > 0
        and isinstance(dealdesk_escalation_rate, (int, float))
        and float(dealdesk_escalation_rate) > 0.25
    ):
        risks.append(
            {
                "title": "Deal Desk document verification is escalating too many closes",
                "rationale": (
                    f"Document escalation rate is {round(float(dealdesk_escalation_rate) * 100, 1)}% "
                    f"across {dealdesk_submitted} recent submissions."
                ),
                "source_card_id": "dealdesk",
            }
        )
    if (
        dealdesk_submitted > 0
        and isinstance(dealdesk_exception_rate, (int, float))
        and float(dealdesk_exception_rate) > 0.0
    ):
        risks.append(
            {
                "title": "Deal Desk certification exceptions are present",
                "rationale": (
                    f"Exception rate is {round(float(dealdesk_exception_rate) * 100, 1)}% "
                    f"across {dealdesk_submitted} recent submissions."
                ),
                "source_card_id": "dealdesk",
            }
        )

    next_actions: list[dict[str, Any]] = []
    if pending_count > 0:
        next_actions.append(
            {
                "action": "Review and resolve pending exceptions in priority order.",
                "urgency": "today",
                "source_card_id": "needs_decision",
            }
        )
    if total_findings > 0:
        next_actions.append(
            {
                "action": "Investigate top signals and tune agent config where needed.",
                "urgency": "this_week",
                "source_card_id": "findings",
            }
        )
    if failed_checks:
        next_actions.append(
            {
                "action": "Keep trust promotions in monitor mode until gate checks pass.",
                "urgency": "this_week",
                "source_card_id": "gate_metrics",
            }
        )
    if (
        dealdesk_submitted > 0
        and isinstance(dealdesk_escalation_rate, (int, float))
        and float(dealdesk_escalation_rate) > 0.25
    ):
        next_actions.append(
            {
                "action": "Review recent Deal Desk document escalations and tighten the paper-vs-CRM path.",
                "urgency": "this_week",
                "source_card_id": "dealdesk",
            }
        )
    if not next_actions:
        next_actions.append(
            {
                "action": "Monitor Control Tower daily and keep current execution posture.",
                "urgency": "monitor",
                "source_card_id": "execution",
            }
        )

    return {
        "headline": headline,
        "summary": summary,
        "needs_decision": needs_decision_items,
        "wins": wins[:4],
        "risks": risks[:4],
        "next_actions": next_actions[:4],
    }


def _read_lock_payload(path: Path) -> dict[str, Any] | None:
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return None


def _pid_alive(pid: int) -> bool:
    if pid <= 0:
        return False
    if sys.platform == "win32":
        try:
            import ctypes
            from ctypes import wintypes

            kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
            kernel32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
            kernel32.OpenProcess.restype = wintypes.HANDLE
            kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
            kernel32.CloseHandle.restype = wintypes.BOOL

            PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
            handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
            if not handle:
                return False
            kernel32.CloseHandle(handle)
            return True
        except Exception:
            return False
    try:
        os.kill(pid, 0)
        return True
    except Exception:
        return False


def _resolve_policy_path(policy_path: Path | None) -> Path | None:
    if policy_path:
        return Path(policy_path)
    env_path = os.getenv("G_GREMLIN_POLICY_PATH")
    if env_path:
        return Path(env_path)
    default_path = Path("autopilot_policy.yaml")
    if default_path.exists():
        return default_path
    return None


def _archive_pending(state_dir: Path, run_id: str, *, status: str) -> None:
    pending_dir = Path(state_dir) / "pending" / run_id
    if not pending_dir.exists():
        return
    target_root = Path(state_dir) / "pending_archive" / status
    target_root.mkdir(parents=True, exist_ok=True)
    target_dir = target_root / run_id
    if target_dir.exists():
        shutil.rmtree(target_dir, ignore_errors=True)
    try:
        shutil.move(str(pending_dir), str(target_dir))
    except Exception:
        shutil.rmtree(pending_dir, ignore_errors=True)


def _invalidate_credentials_cache(app: FastAPI) -> None:
    app.state.credentials_cache = None


def _invalidate_integration_status_cache(app: FastAPI) -> None:
    app.state.integration_status_cache = None


def _platform_db_path(state_dir: Path) -> Path:
    return Path(state_dir) / "db" / "platform.sqlite"


def _source_systems_for_integration(integration: str) -> tuple[str, ...]:
    normalized = str(integration or "").strip().lower()
    if normalized == "salesforce":
        return ("salesforce", "sfdc")
    if normalized in {"hubspot", "dynamics", "outreach"}:
        return (normalized,)
    return ()


def _purge_substrate_for_integration(state_dir: Path, integration: str) -> dict[str, Any]:
    source_systems = _source_systems_for_integration(integration)
    result: dict[str, Any] = {
        "source_systems": list(source_systems),
        "identity_links_deleted": 0,
        "identity_entities_deleted": 0,
        "provenance_deleted": 0,
    }
    if not source_systems:
        return result

    db_path = _platform_db_path(state_dir)
    if not db_path.exists():
        return result

    try:
        from .identity_graph import IdentityGraph

        graph = IdentityGraph(db_path)
        for source_system in source_systems:
            purged = graph.purge_source_system(source_system)
            result["identity_links_deleted"] += int(purged.get("links_deleted") or 0)
            result["identity_entities_deleted"] += int(purged.get("entities_deleted") or 0)
    except Exception as exc:
        result["identity_error"] = str(exc)

    try:
        from .field_provenance import FieldProvenanceStore

        provenance_store = FieldProvenanceStore(db_path)
        for source_system in source_systems:
            result["provenance_deleted"] += int(provenance_store.purge_source_system(source_system) or 0)
    except Exception as exc:
        result["provenance_error"] = str(exc)

    return result


def _audit_log(app: FastAPI) -> AuditLog:
    log = getattr(app.state, "audit_log", None)
    if isinstance(log, AuditLog):
        return log
    resolved = AuditLog(_platform_db_path(Path(app.state.state_dir)))
    app.state.audit_log = resolved
    return resolved


def _json_header_dict(request: Request, header_names: tuple[str, ...]) -> dict[str, Any]:
    for header in header_names:
        raw = str(request.headers.get(header, "")).strip()
        if not raw:
            continue
        try:
            payload = json.loads(raw)
        except Exception:
            continue
        if isinstance(payload, dict):
            return payload
    return {}


def _normalize_actor_roles(raw_roles: Any) -> list[str]:
    if raw_roles is None:
        return []
    values: list[str]
    if isinstance(raw_roles, list):
        values = [str(value).strip().lower() for value in raw_roles]
    else:
        values = [part.strip().lower() for part in str(raw_roles).replace(";", ",").split(",")]
    normalized: list[str] = []
    seen: set[str] = set()
    for role in values:
        if role not in ACTOR_ROLES:
            continue
        for implied in sorted(_ROLE_IMPLIED_BY.get(role, {role})):
            if implied in seen:
                continue
            seen.add(implied)
            normalized.append(implied)
    return normalized


def _resolve_actor_context(request: Request) -> ActorContext:
    trust_identity_headers = _trust_identity_headers()
    bound_actor_ctx = getattr(request.state, "actor_context", None)
    if isinstance(bound_actor_ctx, ActorContext) and not trust_identity_headers:
        return bound_actor_ctx

    auth_header = request.headers.get("authorization", "")
    session_header = request.headers.get("x-autopilot-token", "").strip()
    auth_source = "anonymous"
    if auth_header.startswith("Bearer "):
        auth_source = "api_key"
    elif session_header:
        auth_source = "session_token"

    # Header-derived actor claims are only safe behind a trusted identity proxy.
    api_key_meta = (
        _json_header_dict(
            request,
            (
                "x-autopilot-api-key-metadata",
                "x-api-key-metadata",
            ),
        )
        if trust_identity_headers
        else {}
    )
    session_claims = (
        _json_header_dict(
            request,
            (
                "x-autopilot-session-claims",
                "x-autopilot-claims",
                "x-session-claims",
            ),
        )
        if trust_identity_headers
        else {}
    )

    actor_candidates = [
        api_key_meta.get("actor"),
        api_key_meta.get("actor_id"),
        api_key_meta.get("subject"),
        api_key_meta.get("key_id"),
        session_claims.get("actor"),
        session_claims.get("sub"),
        session_claims.get("user_id"),
        session_claims.get("email"),
        request.headers.get("x-autopilot-actor", "") if trust_identity_headers else "",
        request.headers.get("x-user-email", "") if trust_identity_headers else "",
        request.headers.get("x-user-id", "") if trust_identity_headers else "",
    ]
    actor_id = ""
    for candidate in actor_candidates:
        normalized = str(candidate or "").strip()
        if normalized:
            actor_id = normalized
            break
    if not actor_id and isinstance(bound_actor_ctx, ActorContext):
        return bound_actor_ctx
    if not actor_id:
        if auth_source == "api_key":
            actor_id = "api_key"
        elif auth_source == "session_token":
            actor_id = "session_token"
        else:
            client = getattr(request, "client", None)
            host = getattr(client, "host", None)
            actor_id = f"ip:{host}" if host else "unknown"

    mapped_email = str(
        session_claims.get("email")
        or api_key_meta.get("email")
        or (request.headers.get("x-user-email", "") if trust_identity_headers else "")
        or ""
    ).strip()
    mapped_email = mapped_email or None

    roles = _normalize_actor_roles(
        api_key_meta.get("roles")
        or session_claims.get("roles")
        or (request.headers.get("x-autopilot-roles") if trust_identity_headers else None)
        or (request.headers.get("x-autopilot-role") if trust_identity_headers else None)
        or (request.headers.get("x-user-roles") if trust_identity_headers else None)
        or (request.headers.get("x-user-role") if trust_identity_headers else None)
    )
    if not roles and isinstance(bound_actor_ctx, ActorContext):
        roles = list(bound_actor_ctx.roles)
    if not roles:
        if auth_source in {"api_key", "session_token"}:
            roles = _normalize_actor_roles([_default_rbac_role()])
            if not roles:
                roles = ["revops"]
        else:
            roles = ["viewer"]

    return ActorContext(
        actor_id=actor_id,
        roles=roles,
        auth_source=auth_source,
        mapped_email=mapped_email,
        principal_type=None,
        verified_identity=False,
    )


def _require_roles(
    request: Request,
    *,
    allowed: set[str],
    reason_code: str,
    required_for: str,
) -> ActorContext:
    actor = _resolve_actor_context(request)
    normalized_allowed = {str(role).strip().lower() for role in allowed if str(role).strip()}
    actor_roles = {str(role).strip().lower() for role in actor.roles if str(role).strip()}
    if actor_roles.intersection(normalized_allowed):
        return actor
    raise HTTPException(
        status_code=403,
        detail={
            "reason": reason_code,
            "required_for": required_for,
            "required_roles": sorted(normalized_allowed),
            "actor_id": actor.actor_id,
            "actor_roles": sorted(actor_roles),
        },
    )


def _request_actor(request: Request) -> str:
    return _resolve_actor_context(request).actor_id


def _audit_retention_days(default: int = 365) -> int:
    raw = os.getenv("G_GREMLIN_AUDIT_RETENTION_DAYS", "").strip()
    if not raw:
        return default
    try:
        value = int(raw)
    except Exception:
        return default
    return value if value > 0 else default


def _write_legacy_auth_files_enabled() -> bool:
    value = os.getenv("G_GREMLIN_WRITE_LEGACY_AUTH_FILES", "").strip().lower()
    return value in {"1", "true", "yes", "on"}


def _integration_status_path(state_dir: Path) -> Path:
    return Path(state_dir) / "config" / INTEGRATION_STATUS_FILE


def _load_integration_status_snapshot(state_dir: Path) -> dict[str, Any]:
    data = _load_json_file(_integration_status_path(state_dir))
    return data if isinstance(data, dict) else {}


def _save_integration_status_snapshot(state_dir: Path, payload: dict[str, Any]) -> None:
    _write_json_file(_integration_status_path(state_dir), payload)


def _record_integration_status_snapshot(
    *,
    state_dir: Path,
    integration: str,
    ok: bool,
    details: dict[str, Any] | None = None,
    error: str | None = None,
) -> None:
    status_map = _load_integration_status_snapshot(state_dir)
    status_map[integration] = {
        "last_verified": datetime.now(timezone.utc).isoformat(),
        "status": "ok" if ok else "error",
        "details": details or {},
        "error": error,
    }
    _save_integration_status_snapshot(state_dir, status_map)


def _clear_integration_status_snapshot(state_dir: Path, integration: str) -> None:
    status_map = _load_integration_status_snapshot(state_dir)
    if integration in status_map:
        status_map.pop(integration, None)
        _save_integration_status_snapshot(state_dir, status_map)


def _extract_salesforce_org_id(identity_url: str) -> str:
    parsed = urlparse(identity_url)
    parts = [part for part in parsed.path.split("/") if part]
    # expected format: /id/{org_id}/{user_id}
    if len(parts) >= 3 and parts[0] == "id":
        return parts[1]
    return ""


def _resolve_dynamics_resource_url(token_payload: dict[str, Any], *, state_payload: dict[str, Any]) -> str:
    candidates = [
        str(token_payload.get("resource") or "").strip(),
        str(state_payload.get("environment_url") or "").strip(),
        str(os.getenv("G_GREMLIN_DYNAMICS_RESOURCE_URL", "")).strip(),
        str(os.getenv("G_GREMLIN_DYNAMICS_ENVIRONMENT_URL", "")).strip(),
    ]
    for item in candidates:
        if item:
            return item.rstrip("/")
    return ""


def _outreach_config_path() -> Path:
    return gremlin_path("outreach.json")


def _load_outreach_file_config() -> dict[str, Any]:
    return _load_json_file(_outreach_config_path())


def _write_outreach_file_config(payload: dict[str, Any]) -> None:
    cleaned = {key: value for key, value in payload.items() if value not in (None, "")}
    _write_json_file(_outreach_config_path(), cleaned)


def _resolve_outreach_bridge_api_base(explicit: str | None = None) -> str:
    candidate = (
        (explicit or "").strip()
        or os.getenv("G_GREMLIN_OUTREACH_BRIDGE_API_BASE", "").strip()
        or os.getenv("FOUNDRYOPS_API_URL", "").strip()
        or os.getenv("FOUNDRYMATCH_API_URL", "").strip()
        or "https://api.foundryops.io"
    )
    return candidate.rstrip("/")


def _resolve_outreach_bridge_auth_headers() -> dict[str, str]:
    auth_token = os.getenv("FOUNDRYMATCH_AUTH_TOKEN", "").strip()
    if auth_token:
        return {"Authorization": f"Bearer {auth_token}"}

    api_key = os.getenv("FOUNDRYMATCH_API_KEY", "").strip()
    if api_key:
        return {"X-API-Key": api_key}

    try:
        from g_gremlin.credentials import license as license_mod

        license_token = license_mod.get_license_token()
    except Exception:
        license_token = None
    if license_token:
        return {"Authorization": f"Bearer {license_token}"}

    if os.getenv("FM_TEST_MODE", "").strip() == "1":
        return {"Authorization": "Bearer dev-token"}

    return {}


def _outreach_bridge_callback_url(api_base: str | None = None) -> str:
    configured = os.getenv("OUTREACH_OAUTH_REDIRECT_URI", "").strip()
    if configured:
        return configured
    base = _resolve_outreach_bridge_api_base(api_base)
    return f"{base}/api/v2/integrations/outreach/callback"


def _outreach_oauth_session_path(state_dir: Path) -> Path:
    return Path(state_dir) / "config" / OUTREACH_OAUTH_SESSION_FILE


def _load_outreach_oauth_session(state_dir: Path) -> dict[str, Any]:
    return _load_json_file(_outreach_oauth_session_path(state_dir))


def _save_outreach_oauth_session(state_dir: Path, payload: dict[str, Any]) -> None:
    _write_json_file(_outreach_oauth_session_path(state_dir), payload)


def _clear_outreach_oauth_session(state_dir: Path) -> None:
    path = _outreach_oauth_session_path(state_dir)
    try:
        if path.exists():
            path.unlink()
    except Exception:
        logger.debug("Failed to clear outreach oauth session file", exc_info=True)


def _resolve_outreach_token_expiry_unix(token_payload: dict[str, Any]) -> int:
    expires_at_raw = str(token_payload.get("expires_at") or "").strip()
    expires_at = _parse_iso(expires_at_raw)
    if expires_at is not None:
        return int(expires_at.timestamp())

    expires_in = _coerce_expires_in(token_payload.get("expires_in"))
    if expires_in is None:
        expires_in = 7200
    return int(_time.time()) + max(expires_in, 1)


def _persist_outreach_bridge_tokens(
    token_payload: dict[str, Any],
    *,
    api_base: str,
    workspace_id: str,
    scope_profile: str,
) -> None:
    access_token = str(token_payload.get("access_token") or "").strip()
    if not access_token:
        raise ValueError("Outreach OAuth token response missing access_token.")

    resolved_scope_profile = str(token_payload.get("scope_profile") or scope_profile or "default").strip() or "default"
    cfg = dict(_load_outreach_file_config())
    cfg["access_token"] = access_token
    cfg["token_expires_at"] = _resolve_outreach_token_expiry_unix(token_payload)
    cfg["scope_profile"] = resolved_scope_profile
    cfg["auth_mode"] = "server"
    cfg["foundryops_api_base"] = api_base

    org_id = token_payload.get("org_id")
    org_name = token_payload.get("org_name")
    if org_id:
        cfg["org_id"] = str(org_id)
    if org_name:
        cfg["org_name"] = str(org_name)
    if workspace_id:
        cfg["workspace_id"] = workspace_id

    try:
        import g_gremlin.outreach as outreach_helpers

        scope_profiles = getattr(outreach_helpers, "SCOPE_PROFILES", {}) or {}
        default_scopes = getattr(outreach_helpers, "OUTREACH_SCOPES", []) or []
        cfg["requested_scopes"] = list(scope_profiles.get(resolved_scope_profile, default_scopes))
    except Exception:
        pass

    cfg.pop("refresh_token", None)
    cfg.pop("client_secret", None)
    cfg.pop("client_id", None)
    _write_outreach_file_config(cfg)


def _sync_outreach_bridge_session(app: FastAPI, *, state_dir: Path) -> dict[str, Any]:
    session = _load_outreach_oauth_session(state_dir)
    session_id = str(session.get("session_id") or "").strip()
    if not session_id:
        return {"status": "none", "changed": False}

    created_at = _parse_iso(str(session.get("created_at") or ""))
    now = datetime.now(timezone.utc)
    if created_at is None or (now - created_at).total_seconds() > OUTREACH_OAUTH_SESSION_TTL_SECONDS:
        _clear_outreach_oauth_session(state_dir)
        return {"status": "expired", "changed": True}

    api_base = _resolve_outreach_bridge_api_base(str(session.get("api_base") or "").strip() or None)
    poll_url = f"{api_base}/api/v2/integrations/outreach/cli/poll?{urlencode({'session_id': session_id})}"
    try:
        poll_status, poll_payload, poll_text = _http_request_json(
            "GET",
            poll_url,
            headers={"Accept": "application/json"},
            timeout=15.0,
        )
    except Exception as exc:
        message = f"Outreach OAuth poll failed: {exc}"
        session["status"] = "error"
        session["last_error"] = message
        session["last_checked_at"] = now.isoformat()
        _save_outreach_oauth_session(state_dir, session)
        return {"status": "error", "error": message, "changed": False}
    if poll_status >= 400:
        message = _response_error_message(
            poll_payload,
            poll_text,
            default=f"Outreach OAuth poll failed with HTTP {poll_status}",
        )
        session["status"] = "error"
        session["last_error"] = message
        session["last_checked_at"] = now.isoformat()
        _save_outreach_oauth_session(state_dir, session)
        return {"status": "error", "error": message, "changed": True}

    poll_data = poll_payload if isinstance(poll_payload, dict) else {}
    flow_status = str(poll_data.get("status") or "pending").strip().lower() or "pending"
    workspace_id = str(poll_data.get("workspace_id") or session.get("workspace_id") or "").strip()
    session["status"] = flow_status
    session["workspace_id"] = workspace_id
    session["last_checked_at"] = now.isoformat()
    session.pop("last_error", None)

    if flow_status == "pending":
        _save_outreach_oauth_session(state_dir, session)
        return {"status": "pending", "changed": False}

    if flow_status == "error":
        message = str(poll_data.get("message") or "Outreach authorization failed.").strip()
        session["last_error"] = message
        _save_outreach_oauth_session(state_dir, session)
        return {"status": "error", "error": message, "changed": False}

    if flow_status != "complete":
        _save_outreach_oauth_session(state_dir, session)
        return {"status": flow_status, "changed": False}

    auth_headers = _resolve_outreach_bridge_auth_headers()
    if not auth_headers:
        message = (
            "Outreach authorization completed, but server-side FoundryOps auth is missing. "
            "Set FOUNDRYMATCH_AUTH_TOKEN or FOUNDRYMATCH_API_KEY and refresh."
        )
        session["status"] = "error"
        session["last_error"] = message
        _save_outreach_oauth_session(state_dir, session)
        return {"status": "error", "error": message, "changed": False}

    token_params: dict[str, str] = {"session_id": session_id}
    if workspace_id:
        token_params["workspace_id"] = workspace_id
    token_url = f"{api_base}/api/v2/integrations/outreach/cli/token?{urlencode(token_params)}"
    try:
        token_status, token_payload, token_text = _http_request_json(
            "GET",
            token_url,
            headers={**auth_headers, "Accept": "application/json"},
            timeout=20.0,
        )
    except Exception as exc:
        message = f"Outreach OAuth token retrieval failed: {exc}"
        session["status"] = "error"
        session["last_error"] = message
        _save_outreach_oauth_session(state_dir, session)
        return {"status": "error", "error": message, "changed": False}
    if token_status >= 400:
        message = _response_error_message(
            token_payload,
            token_text,
            default=f"Outreach OAuth token retrieval failed with HTTP {token_status}",
        )
        session["status"] = "error"
        session["last_error"] = message
        _save_outreach_oauth_session(state_dir, session)
        return {"status": "error", "error": message, "changed": False}

    if not isinstance(token_payload, dict):
        message = "Outreach OAuth token retrieval returned invalid payload."
        session["status"] = "error"
        session["last_error"] = message
        _save_outreach_oauth_session(state_dir, session)
        return {"status": "error", "error": message, "changed": False}

    try:
        _persist_outreach_bridge_tokens(
            token_payload,
            api_base=api_base,
            workspace_id=workspace_id,
            scope_profile=str(session.get("scope_profile") or "default"),
        )
    except Exception as exc:
        message = str(exc)
        session["status"] = "error"
        session["last_error"] = message
        _save_outreach_oauth_session(state_dir, session)
        return {"status": "error", "error": message, "changed": False}

    _clear_outreach_oauth_session(state_dir)
    _invalidate_credentials_cache(app)
    _invalidate_integration_status_cache(app)
    return {"status": "complete", "changed": True}


def _start_outreach_bridge_oauth(
    *,
    state_dir: Path,
    payload: dict[str, Any],
) -> dict[str, Any]:
    auth_headers = _resolve_outreach_bridge_auth_headers()
    if not auth_headers:
        raise ValueError(
            "Outreach OAuth requires FoundryOps auth on this server. "
            "Set FOUNDRYMATCH_AUTH_TOKEN or FOUNDRYMATCH_API_KEY."
        )

    api_base = _resolve_outreach_bridge_api_base(str(payload.get("api_base") or "").strip() or None)
    scope_profile = str(payload.get("scope_profile") or "default").strip() or "default"
    workspace_id = str(payload.get("workspace_id") or "").strip()
    session_id = secrets.token_urlsafe(24)
    params: dict[str, str] = {
        "session_id": session_id,
        "scope_profile": scope_profile,
    }
    if workspace_id:
        params["workspace_id"] = workspace_id

    authorize_url = f"{api_base}/api/v2/integrations/outreach/cli/authorize?{urlencode(params)}"
    try:
        status, response_payload, response_text = _http_request_json(
            "GET",
            authorize_url,
            headers={**auth_headers, "Accept": "application/json"},
            timeout=20.0,
        )
    except Exception as exc:
        raise ValueError(f"Outreach OAuth start request failed: {exc}") from exc
    if status >= 400:
        message = _response_error_message(
            response_payload,
            response_text,
            default=f"Outreach OAuth start failed with HTTP {status}",
        )
        raise ValueError(message)
    if not isinstance(response_payload, dict):
        raise ValueError("Outreach OAuth start returned invalid response.")

    auth_url = str(response_payload.get("auth_url") or "").strip()
    if not auth_url:
        raise ValueError("Outreach OAuth start did not return auth_url.")

    resolved_workspace = str(response_payload.get("workspace_id") or workspace_id).strip()
    resolved_scope_profile = str(response_payload.get("scope_profile") or scope_profile).strip() or "default"
    callback_url = os.getenv("OUTREACH_OAUTH_REDIRECT_URI", "").strip()
    if not callback_url:
        callback_url = f"{api_base}/api/v2/integrations/outreach/callback"
    _save_outreach_oauth_session(
        state_dir,
        {
            "session_id": session_id,
            "status": "pending",
            "api_base": api_base,
            "workspace_id": resolved_workspace,
            "scope_profile": resolved_scope_profile,
            "created_at": datetime.now(timezone.utc).isoformat(),
            "last_checked_at": None,
            "last_error": None,
        },
    )

    return {
        "integration": "outreach",
        "auth_url": auth_url,
        "callback_url": callback_url,
        "session_id": session_id,
        "scope_profile": resolved_scope_profile,
        "workspace_id": resolved_workspace or None,
        "bridge_api_base": api_base,
    }


def _manual_field_schema(integration: str) -> list[dict[str, Any]]:
    if integration == "salesforce":
        return [
            {"name": "org_alias", "label": "Org Alias", "required": True},
            {"name": "instance_url", "label": "Instance URL", "required": False},
            {"name": "access_token", "label": "Access Token", "required": False},
            {"name": "refresh_token", "label": "Refresh Token", "required": False},
            {"name": "client_id", "label": "Client ID", "required": False},
            {"name": "client_secret", "label": "Client Secret", "required": False},
            {"name": "username", "label": "Username", "required": False},
            {"name": "password", "label": "Password", "required": False},
            {"name": "security_token", "label": "Security Token", "required": False},
        ]
    if integration == "hubspot":
        return [
            {"name": "access_token", "label": "PAT / Access Token", "required": True},
            {"name": "api_version", "label": "API Version", "required": False},
            {"name": "profile", "label": "Profile", "required": False},
        ]
    if integration == "dynamics":
        return [
            {"name": "environment_url", "label": "Environment URL", "required": True},
            {"name": "tenant_id", "label": "Tenant ID", "required": False},
            {"name": "client_id", "label": "Client ID", "required": False},
            {"name": "client_secret", "label": "Client Secret", "required": False},
            {"name": "default_environment", "label": "Default Environment", "required": False},
            {"name": "profile", "label": "Profile", "required": False},
        ]
    if integration == "outreach":
        return [
            {"name": "client_id", "label": "Client ID", "required": True},
            {"name": "client_secret", "label": "Client Secret", "required": True},
            {"name": "refresh_token", "label": "Refresh Token", "required": True},
            {"name": "scope_profile", "label": "Scope Profile", "required": False},
        ]
    return []


def _oauth_client_id(integration: str) -> str:
    if integration == "hubspot":
        return (
            os.getenv("G_GREMLIN_HUBSPOT_OAUTH_CLIENT_ID", "").strip()
            or os.getenv("G_GREMLIN_HUBSPOT_CLIENT_ID", "").strip()
        )
    if integration == "salesforce":
        return (
            os.getenv("G_GREMLIN_SALESFORCE_OAUTH_CLIENT_ID", "").strip()
            or os.getenv("G_GREMLIN_SALESFORCE_CLIENT_ID", "").strip()
        )
    if integration == "dynamics":
        return (
            os.getenv("G_GREMLIN_DYNAMICS_OAUTH_CLIENT_ID", "").strip()
            or os.getenv("G_GREMLIN_DYNAMICS_CLIENT_ID", "").strip()
        )
    if integration == "outreach":
        return (
            os.getenv("G_GREMLIN_OUTREACH_OAUTH_CLIENT_ID", "").strip()
            or os.getenv("G_GREMLIN_OUTREACH_CLIENT_ID", "").strip()
            or str(_load_outreach_file_config().get("client_id") or "").strip()
        )
    return ""


def _credential_status_map() -> dict[str, dict[str, Any]]:
    try:
        from g_gremlin.credentials.provider import CredentialProvider

        provider = CredentialProvider()
        return {status.integration: status.to_dict() for status in provider.list_integrations()}
    except Exception:
        return {}


def _list_admin_integrations(app: FastAPI, request: Request) -> list[dict[str, Any]]:
    return [_admin_integration_status(app, request, integration) for integration in ADMIN_INTEGRATIONS]


def _admin_integration_status(app: FastAPI, request: Request, integration: str) -> dict[str, Any]:
    credential_status = _credential_status_map().get(integration) or {}
    configured = bool(credential_status.get("configured"))
    status = str(credential_status.get("status") or "not_configured")
    source = credential_status.get("source")
    keys_present = list(credential_status.get("keys_present") or [])
    keys_missing = list(credential_status.get("keys_missing") or [])

    details: dict[str, Any] = {}
    if integration == "salesforce":
        try:
            import g_gremlin.sfdc as sfdc_helpers

            org_alias = sfdc_helpers.get_configured_org()
            details["org_alias"] = org_alias
            if org_alias and "org_alias" not in keys_present:
                keys_present.append("org_alias")
                keys_missing = [key for key in keys_missing if key != "org_alias"]
                configured = True
                if status == "not_configured":
                    status = "ok"
                if not source:
                    source = "legacy_file"
        except Exception:
            pass
    elif integration == "hubspot":
        try:
            import g_gremlin.hubspot as hubspot_helpers

            cfg = hubspot_helpers.HubSpotConfig.load()
            details["profile"] = cfg.profile
            details["portal_id"] = cfg.portal_id or None
            details["hub_domain"] = cfg.hub_domain or None
        except Exception:
            details["profile"] = None
    elif integration == "dynamics":
        try:
            from g_gremlin.profile_manager import ProfileStore

            store = ProfileStore.load(gremlin_path("dynamics.json"), default_profile="default")
            current_profile = store.current_profile_name()
            details["profile"] = current_profile
            try:
                current = store.get_profile(current_profile)
            except Exception:
                current = {}
            details["environment_url"] = (current or {}).get("environment_url")
        except Exception:
            details["profile"] = None
    elif integration == "outreach":
        sync_result = _sync_outreach_bridge_session(app, state_dir=Path(app.state.state_dir))
        if sync_result.get("changed"):
            _invalidate_credentials_cache(app)
            _invalidate_integration_status_cache(app)
        session_state = _load_outreach_oauth_session(Path(app.state.state_dir))
        outreach_cfg = _load_outreach_file_config()
        details["scope_profile"] = outreach_cfg.get("scope_profile")
        details["org_id"] = outreach_cfg.get("org_id")
        details["org_name"] = outreach_cfg.get("org_name")
        details["auth_mode"] = outreach_cfg.get("auth_mode")
        details["oauth_session_status"] = sync_result.get("status") or session_state.get("status")
        details["oauth_session_error"] = session_state.get("last_error")
        if (
            outreach_cfg.get("client_id")
            and outreach_cfg.get("client_secret")
            and outreach_cfg.get("refresh_token")
        ):
            configured = True
            if status == "not_configured":
                status = "ok"
            if not source:
                source = "legacy_file"
            for key in ("client_id", "client_secret", "refresh_token"):
                if key not in keys_present:
                    keys_present.append(key)
            keys_missing = [key for key in keys_missing if key not in {"client_id", "client_secret", "refresh_token"}]
        elif (
            str(outreach_cfg.get("auth_mode") or "").strip().lower() == "server"
            and str(outreach_cfg.get("access_token") or "").strip()
        ):
            configured = True
            if status == "not_configured":
                status = "ok"
            if not source:
                source = "state_file"
            if "access_token" not in keys_present:
                keys_present.append("access_token")
            keys_missing = [key for key in keys_missing if key != "access_token"]

    return {
        "integration": integration,
        "display_name": {
            "salesforce": "Salesforce",
            "hubspot": "HubSpot",
            "dynamics": "Dynamics 365",
            "outreach": "Outreach",
        }.get(integration, integration),
        "status": status,
        "configured": configured,
        "source": source,
        "keys_present": sorted(set(keys_present)),
        "keys_missing": sorted(set(keys_missing)),
        "details": details,
        "manual_fields": _manual_field_schema(integration),
        "oauth": {
            "enabled": bool(_resolve_outreach_bridge_auth_headers())
            if integration == "outreach"
            else bool(_oauth_client_id(integration)),
            "callback_url": _oauth_callback_url(request, integration),
        },
    }


async def _full_integration_status(app: FastAPI, request: Request) -> dict[str, dict[str, Any]]:
    state_dir = Path(app.state.state_dir)
    snapshots = _load_integration_status_snapshot(state_dir)
    token_store = TokenStore(state_dir)
    statuses: dict[str, dict[str, Any]] = {}

    for integration in ADMIN_INTEGRATIONS:
        if integration in CRM_INTEGRATIONS:
            try:
                await _maybe_refresh_token(app, integration, state_dir)
            except Exception:
                logger.debug("Failed proactive token refresh for %s", integration, exc_info=True)

        admin_status = _admin_integration_status(app, request, integration)
        token_payload = token_store.load_tokens(integration) or {}
        snapshot = snapshots.get(integration) if isinstance(snapshots.get(integration), dict) else {}
        snapshot_details = snapshot.get("details") if isinstance(snapshot.get("details"), dict) else {}

        connected = bool(admin_status.get("configured")) or bool(token_payload.get("access_token"))
        source = admin_status.get("source")
        auth_method = "oauth" if token_payload else ("manual" if connected else "none")
        last_verified = snapshot.get("last_verified")
        error = snapshot.get("error")
        status = "ok" if connected else "not_configured"
        if error:
            status = "error"

        details: dict[str, Any] = {}
        if integration == "salesforce":
            details = {
                "org_alias": token_payload.get("org_alias")
                or (admin_status.get("details") or {}).get("org_alias"),
                "org_id": token_payload.get("org_id") or snapshot_details.get("org_id"),
                "instance_url": token_payload.get("instance_url") or snapshot_details.get("instance_url"),
                "username": token_payload.get("username") or snapshot_details.get("username"),
            }
        elif integration == "hubspot":
            details = {
                "portal_id": snapshot_details.get("portal_id")
                or (admin_status.get("details") or {}).get("portal_id"),
                "hub_domain": snapshot_details.get("hub_domain")
                or (admin_status.get("details") or {}).get("hub_domain"),
                "profile": (admin_status.get("details") or {}).get("profile"),
            }
        elif integration == "dynamics":
            details = {
                "environment_url": token_payload.get("environment_url")
                or token_payload.get("resource_url")
                or (admin_status.get("details") or {}).get("environment_url")
                or snapshot_details.get("environment_url"),
                "org_id": snapshot_details.get("org_id"),
                "user_id": snapshot_details.get("user_id"),
            }
        elif integration == "outreach":
            outreach_details = admin_status.get("details") or {}
            details = {
                "scope_profile": outreach_details.get("scope_profile"),
                "org_id": outreach_details.get("org_id") or snapshot_details.get("org_id"),
                "org_name": outreach_details.get("org_name") or snapshot_details.get("org_name"),
                "auth_mode": outreach_details.get("auth_mode"),
                "oauth_session_status": outreach_details.get("oauth_session_status"),
                "oauth_session_error": outreach_details.get("oauth_session_error"),
            }
            if connected:
                auth_method = "oauth"
            elif details.get("oauth_session_status") == "pending":
                status = "pending"
            elif details.get("oauth_session_status") == "error":
                status = "error"
                error = str(details.get("oauth_session_error") or "Outreach OAuth failed.")

        statuses[integration] = {
            "connected": connected,
            "status": status,
            "auth_method": auth_method,
            "source": source,
            "last_verified": last_verified,
            "error": error,
            **details,
        }

    return statuses


def _integration_refresh_lock(app: FastAPI, integration: str) -> asyncio.Lock:
    locks = getattr(app.state, "refresh_locks", None)
    if not isinstance(locks, dict):
        locks = {}
        app.state.refresh_locks = locks
    lock = locks.get(integration)
    if not isinstance(lock, asyncio.Lock):
        lock = asyncio.Lock()
        locks[integration] = lock
    return lock


async def _maybe_refresh_token(app: FastAPI, integration: str, state_dir: Path) -> None:
    token_store = TokenStore(state_dir)
    if not token_store.needs_refresh(integration):
        return
    await _refresh_integration_token(app, integration, state_dir)


def _coerce_expires_in(raw: Any) -> int | None:
    if raw in (None, ""):
        return None
    try:
        value = int(str(raw))
    except Exception:
        return None
    return value if value > 0 else None


def _refresh_token_exchange(
    integration: str,
    *,
    refresh_token: str,
) -> dict[str, Any]:
    if integration == "salesforce":
        client_id = _oauth_client_id("salesforce")
        client_secret = (
            os.getenv("G_GREMLIN_SALESFORCE_OAUTH_CLIENT_SECRET", "").strip()
            or os.getenv("G_GREMLIN_SALESFORCE_CLIENT_SECRET", "").strip()
        )
        if not client_id or not client_secret:
            raise ValueError("Salesforce OAuth refresh is not configured.")
        token_url = (
            os.getenv("G_GREMLIN_SALESFORCE_OAUTH_TOKEN_URL", "").strip()
            or "https://login.salesforce.com/services/oauth2/token"
        )
        return _post_form_json(
            token_url,
            {
                "grant_type": "refresh_token",
                "client_id": client_id,
                "client_secret": client_secret,
                "refresh_token": refresh_token,
            },
        )

    if integration == "hubspot":
        client_id = _oauth_client_id("hubspot")
        client_secret = (
            os.getenv("G_GREMLIN_HUBSPOT_OAUTH_CLIENT_SECRET", "").strip()
            or os.getenv("G_GREMLIN_HUBSPOT_CLIENT_SECRET", "").strip()
        )
        if not client_id or not client_secret:
            raise ValueError("HubSpot OAuth refresh is not configured.")
        token_url = os.getenv("G_GREMLIN_HUBSPOT_OAUTH_TOKEN_URL", "").strip() or "https://api.hubapi.com/oauth/v1/token"
        return _post_form_json(
            token_url,
            {
                "grant_type": "refresh_token",
                "client_id": client_id,
                "client_secret": client_secret,
                "refresh_token": refresh_token,
            },
        )

    if integration == "dynamics":
        client_id = _oauth_client_id("dynamics")
        client_secret = (
            os.getenv("G_GREMLIN_DYNAMICS_OAUTH_CLIENT_SECRET", "").strip()
            or os.getenv("G_GREMLIN_DYNAMICS_CLIENT_SECRET", "").strip()
        )
        if not client_id or not client_secret:
            raise ValueError("Dynamics OAuth refresh is not configured.")
        tenant_id = (
            os.getenv("G_GREMLIN_DYNAMICS_OAUTH_TENANT_ID", "").strip()
            or os.getenv("G_GREMLIN_DYNAMICS_TENANT_ID", "").strip()
            or "common"
        )
        token_url = (
            os.getenv("G_GREMLIN_DYNAMICS_OAUTH_TOKEN_URL", "").strip()
            or f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
        )
        scope = os.getenv("G_GREMLIN_DYNAMICS_OAUTH_SCOPES", "").strip() or "offline_access openid profile"
        return _post_form_json(
            token_url,
            {
                "grant_type": "refresh_token",
                "client_id": client_id,
                "client_secret": client_secret,
                "refresh_token": refresh_token,
                "scope": scope,
            },
        )

    raise ValueError(f"Unsupported integration refresh: {integration}")


def _write_salesforce_legacy_from_tokens(tokens: dict[str, Any]) -> None:
    org_alias = str(tokens.get("org_alias") or "").strip()
    if not org_alias:
        return
    instance_url = str(tokens.get("instance_url") or "").strip()
    access_token = str(tokens.get("access_token") or "").strip()
    if not access_token:
        return
    import g_gremlin.sfdc as sfdc_helpers

    sfdc_helpers.set_configured_org(
        org_alias,
        instance_url=instance_url or None,
        access_token=access_token,
        refresh_token=None,
    )


async def _refresh_integration_token(
    app: FastAPI,
    integration: str,
    state_dir: Path,
    *,
    force: bool = False,
) -> dict[str, Any]:
    token_store = TokenStore(state_dir)
    lock = _integration_refresh_lock(app, integration)
    acquired = False
    try:
        await asyncio.wait_for(lock.acquire(), timeout=5)
        acquired = True
    except asyncio.TimeoutError:
        return {"ok": False, "refreshed": False, "error": "Refresh lock timeout"}

    try:
        if not force and not token_store.needs_refresh(integration):
            return {"ok": True, "refreshed": False, "rotated_refresh": False}

        tokens = token_store.load_tokens(integration) or {}
        refresh_token = str(tokens.get("refresh_token") or "").strip()
        if not refresh_token:
            return {"ok": False, "refreshed": False, "error": "No refresh_token available"}

        response = _refresh_token_exchange(integration, refresh_token=refresh_token)
        access_token = str(response.get("access_token") or "").strip()
        if not access_token:
            raise ValueError("Refresh response did not include an access_token")

        expires_in = _coerce_expires_in(response.get("expires_in"))
        refreshed_at = datetime.now(timezone.utc)
        new_refresh_token = str(response.get("refresh_token") or "").strip()
        rotated_refresh = bool(new_refresh_token and new_refresh_token != refresh_token)

        if rotated_refresh:
            updated = dict(tokens)
            updated["access_token"] = access_token
            updated["refresh_token"] = new_refresh_token
            updated["token_type"] = str(response.get("token_type") or updated.get("token_type") or "").strip() or None
            if response.get("instance_url"):
                updated["instance_url"] = str(response.get("instance_url") or "").strip()
            if response.get("scope"):
                updated["scope"] = str(response.get("scope") or "").strip()
            if expires_in is not None:
                updated["expires_in"] = expires_in
            updated["refreshed_at"] = refreshed_at.isoformat()
            token_store.save_tokens(integration, updated)
        else:
            token_store.update_access_token(
                integration,
                access_token,
                refreshed_at,
                expires_in=expires_in,
            )

        latest = token_store.load_tokens(integration) or {}
        if integration == "salesforce" and _write_legacy_auth_files_enabled():
            _write_salesforce_legacy_from_tokens(latest)

        _invalidate_credentials_cache(app)
        _invalidate_integration_status_cache(app)

        try:
            _audit_log(app).log(
                "oauth_refresh",
                "system",
                f"Refreshed OAuth token for {integration}",
                integration=integration,
                detail={"rotated_refresh": rotated_refresh},
            )
        except Exception:
            logger.debug("Failed to write oauth_refresh audit event", exc_info=True)

        return {"ok": True, "refreshed": True, "rotated_refresh": rotated_refresh}
    finally:
        if acquired:
            lock.release()


def _http_request(
    method: str,
    url: str,
    *,
    headers: dict[str, str] | None = None,
    body: bytes | None = None,
    timeout: float = 20.0,
) -> tuple[int, str]:
    req = UrlRequest(url, data=body, headers=headers or {}, method=method)
    try:
        with urlopen(req, timeout=timeout) as response:
            text = response.read().decode("utf-8", errors="replace")
            return int(getattr(response, "status", 200)), text
    except HTTPError as exc:
        text = exc.read().decode("utf-8", errors="replace")
        return int(exc.code), text
    except URLError as exc:
        raise ValueError(f"HTTP request failed: {exc.reason}") from exc


def _http_request_json(
    method: str,
    url: str,
    *,
    headers: dict[str, str] | None = None,
    body: bytes | None = None,
    timeout: float = 20.0,
) -> tuple[int, dict[str, Any] | list[Any] | None, str]:
    status, text = _http_request(method, url, headers=headers, body=body, timeout=timeout)
    if not text.strip():
        return status, None, text
    try:
        payload = json.loads(text)
    except Exception:
        payload = None
    return status, payload, text


def _response_error_message(payload: Any, text: str, *, default: str) -> str:
    if isinstance(payload, dict):
        for key in ("error_description", "error", "message", "detail"):
            value = payload.get(key)
            if isinstance(value, str) and value.strip():
                return value.strip()
    if isinstance(payload, list) and payload:
        first = payload[0]
        if isinstance(first, dict):
            for key in ("message", "errorCode", "error"):
                value = first.get(key)
                if isinstance(value, str) and value.strip():
                    return value.strip()
    text_trimmed = text.strip()
    if text_trimmed:
        return text_trimmed
    return default


async def _test_salesforce_connection(app: FastAPI, state_dir: Path) -> dict[str, Any]:
    token_store = TokenStore(state_dir)
    tokens = token_store.load_tokens("salesforce") or {}
    access_token = str(tokens.get("access_token") or "").strip()
    instance_url = str(tokens.get("instance_url") or "").strip()
    if not access_token or not instance_url:
        return {"ok": False, "error": "Salesforce is not fully configured (access_token + instance_url required)."}

    def _call(token: str) -> tuple[int, Any, str]:
        return _http_request_json(
            "GET",
            f"{instance_url.rstrip('/')}/services/oauth2/userinfo",
            headers={
                "Accept": "application/json",
                "Authorization": f"Bearer {token}",
            },
            timeout=20.0,
        )

    status, payload, text = _call(access_token)
    if status == 401:
        refresh_result = await _refresh_integration_token(app, "salesforce", state_dir, force=True)
        if refresh_result.get("ok"):
            refreshed_tokens = token_store.load_tokens("salesforce") or {}
            refreshed_access = str(refreshed_tokens.get("access_token") or "").strip()
            if refreshed_access:
                status, payload, text = _call(refreshed_access)

    if status >= 400:
        return {
            "ok": False,
            "error": _response_error_message(payload, text, default=f"Salesforce test failed with HTTP {status}"),
        }

    if not isinstance(payload, dict):
        payload = {}
    details = {
        "org_id": payload.get("organization_id") or tokens.get("org_id"),
        "username": payload.get("preferred_username") or payload.get("email"),
        "instance_url": instance_url,
        "user_id": payload.get("user_id"),
    }
    return {"ok": True, "details": details}


async def _test_hubspot_connection(app: FastAPI, state_dir: Path) -> dict[str, Any]:
    token_store = TokenStore(state_dir)
    tokens = token_store.load_tokens("hubspot") or {}
    access_token = str(tokens.get("access_token") or "").strip()
    if not access_token:
        try:
            import g_gremlin.hubspot as hubspot_helpers

            access_token = str(hubspot_helpers.HubSpotConfig.load().access_token or "").strip()
        except Exception:
            access_token = ""
    if not access_token:
        return {"ok": False, "error": "HubSpot access token not configured."}

    def _call(token: str) -> tuple[int, Any, str]:
        return _http_request_json(
            "GET",
            "https://api.hubapi.com/account-info/v3/details",
            headers={
                "Accept": "application/json",
                "Authorization": f"Bearer {token}",
            },
            timeout=20.0,
        )

    status, payload, text = _call(access_token)
    if status == 401:
        refresh_result = await _refresh_integration_token(app, "hubspot", state_dir, force=True)
        if refresh_result.get("ok"):
            refreshed_tokens = token_store.load_tokens("hubspot") or {}
            refreshed_access = str(refreshed_tokens.get("access_token") or "").strip()
            if refreshed_access:
                status, payload, text = _call(refreshed_access)

    if status >= 400:
        return {
            "ok": False,
            "error": _response_error_message(payload, text, default=f"HubSpot test failed with HTTP {status}"),
        }

    if not isinstance(payload, dict):
        payload = {}
    details = {
        "portal_id": str(payload.get("portalId") or ""),
        "hub_domain": payload.get("hubDomain"),
        "time_zone": payload.get("timeZone"),
    }
    return {"ok": True, "details": details}


async def _test_dynamics_connection(app: FastAPI, state_dir: Path) -> dict[str, Any]:
    token_store = TokenStore(state_dir)
    tokens = token_store.load_tokens("dynamics") or {}
    access_token = str(tokens.get("access_token") or "").strip()
    resource_url = str(tokens.get("resource_url") or tokens.get("environment_url") or "").strip()
    if not resource_url:
        try:
            from g_gremlin.profile_manager import ProfileStore

            store = ProfileStore.load(gremlin_path("dynamics.json"), default_profile="default")
            current_profile = store.current_profile_name()
            current = store.get_profile(current_profile)
            resource_url = str((current or {}).get("environment_url") or "").strip()
        except Exception:
            resource_url = ""
    if not access_token or not resource_url:
        return {"ok": False, "error": "Dynamics is not fully configured (access_token + environment_url required)."}

    whoami_url = f"{resource_url.rstrip('/')}/api/data/v9.2/WhoAmI"

    def _call(token: str) -> tuple[int, Any, str]:
        return _http_request_json(
            "GET",
            whoami_url,
            headers={
                "Accept": "application/json",
                "Authorization": f"Bearer {token}",
            },
            timeout=20.0,
        )

    status, payload, text = _call(access_token)
    if status == 401:
        refresh_result = await _refresh_integration_token(app, "dynamics", state_dir, force=True)
        if refresh_result.get("ok"):
            refreshed_tokens = token_store.load_tokens("dynamics") or {}
            refreshed_access = str(refreshed_tokens.get("access_token") or "").strip()
            if refreshed_access:
                status, payload, text = _call(refreshed_access)

    if status >= 400:
        return {
            "ok": False,
            "error": _response_error_message(payload, text, default=f"Dynamics test failed with HTTP {status}"),
        }

    if not isinstance(payload, dict):
        payload = {}
    details = {
        "org_id": payload.get("OrganizationId"),
        "user_id": payload.get("UserId"),
        "business_unit_id": payload.get("BusinessUnitId"),
        "environment_url": resource_url,
    }
    return {"ok": True, "details": details}


async def _test_integration_connection(app: FastAPI, *, integration: str, state_dir: Path) -> dict[str, Any]:
    if integration == "salesforce":
        return await _test_salesforce_connection(app, state_dir)
    if integration == "hubspot":
        return await _test_hubspot_connection(app, state_dir)
    if integration == "dynamics":
        return await _test_dynamics_connection(app, state_dir)
    if integration == "outreach":
        try:
            _sync_outreach_bridge_session(app, state_dir=state_dir)
            from g_gremlin.outreach import OutreachClient

            client = OutreachClient()
            return {"ok": True, "details": client.test_connection()}
        except Exception as exc:
            return {"ok": False, "error": str(exc)}
    return {"ok": False, "error": f"Unsupported integration: {integration}"}


def _delete_file_if_exists(path: Path) -> None:
    try:
        if path.exists():
            path.unlink()
    except Exception:
        logger.debug("Failed to remove file %s", path, exc_info=True)


def _clear_legacy_integration_files(integration: str) -> None:
    if integration == "salesforce":
        try:
            import g_gremlin.sfdc as sfdc_helpers

            _delete_file_if_exists(Path(sfdc_helpers.SFDC_CONFIG_PATH))
        except Exception:
            pass
    elif integration == "hubspot":
        try:
            import g_gremlin.hubspot as hubspot_helpers

            _delete_file_if_exists(Path(hubspot_helpers.CONFIG_PATH))
        except Exception:
            pass
    elif integration == "dynamics":
        try:
            import g_gremlin.dynamics as dynamics_helpers

            _delete_file_if_exists(Path(dynamics_helpers.CONFIG_PATH))
        except Exception:
            pass
    elif integration == "outreach":
        _delete_file_if_exists(_outreach_config_path())


def _clear_writable_credential_sources(integration: str) -> None:
    keys_by_integration = {
        "salesforce": [
            "org_alias",
            "instance_url",
            "org_id",
            "token_type",
            "access_token",
            "refresh_token",
            "client_id",
            "client_secret",
            "username",
            "password",
            "security_token",
        ],
        "hubspot": ["access_token", "refresh_token", "api_version"],
        "dynamics": [
            "environment_url",
            "resource_url",
            "access_token",
            "refresh_token",
            "tenant_id",
            "client_id",
            "client_secret",
            "default_environment",
        ],
        "outreach": [
            "client_id",
            "client_secret",
            "refresh_token",
            "access_token",
            "token_expires_at",
        ],
    }
    keys = keys_by_integration.get(integration, [])
    if not keys:
        return
    try:
        from g_gremlin.credentials.provider import CredentialProvider

        provider = CredentialProvider()
        for key in keys:
            try:
                provider.delete_secret(integration, key)
            except Exception:
                continue
    except Exception:
        pass


def _try_revoke_integration_token(integration: str, tokens: dict[str, Any]) -> tuple[bool, str | None]:
    access_token = str(tokens.get("access_token") or "").strip()
    refresh_token = str(tokens.get("refresh_token") or "").strip()
    if integration == "salesforce":
        instance_url = str(tokens.get("instance_url") or "").strip()
        if not instance_url or not access_token:
            return False, "Salesforce revoke skipped: missing instance_url or access_token"
        revoke_url = f"{instance_url.rstrip('/')}/services/oauth2/revoke?{urlencode({'token': access_token})}"
        status, text = _http_request("POST", revoke_url, timeout=5.0)
        if status >= 400:
            return False, text.strip() or f"HTTP {status}"
        return True, None
    if integration == "hubspot":
        token = refresh_token or access_token
        if not token:
            return False, "HubSpot revoke skipped: missing token"
        revoke_url = f"https://api.hubapi.com/oauth/v1/refresh-tokens/{quote(token, safe='')}"
        status, text = _http_request("DELETE", revoke_url, timeout=5.0)
        if status >= 400:
            return False, text.strip() or f"HTTP {status}"
        return True, None
    if integration == "dynamics":
        return False, "Dynamics does not expose a standard revoke endpoint"
    if integration == "outreach":
        return False, "Outreach revoke is not supported via this endpoint"
    return False, "Unsupported integration"


def _disconnect_outreach_bridge_remote() -> tuple[bool, str | None]:
    cfg = _load_outreach_file_config()
    if str(cfg.get("auth_mode") or "").strip().lower() != "server":
        return False, "Outreach revoke skipped: local auth mode"

    auth_headers = _resolve_outreach_bridge_auth_headers()
    if not auth_headers:
        return False, "Outreach revoke skipped: missing FoundryOps auth"

    api_base = _resolve_outreach_bridge_api_base(str(cfg.get("foundryops_api_base") or "").strip() or None)
    status, payload, text = _http_request_json(
        "DELETE",
        f"{api_base}/api/v2/integrations/outreach/disconnect",
        headers={**auth_headers, "Accept": "application/json"},
        timeout=15.0,
    )
    if status >= 400:
        message = _response_error_message(
            payload,
            text,
            default=f"Outreach disconnect failed with HTTP {status}",
        )
        return False, message
    return True, None


async def _disconnect_integration(app: FastAPI, *, integration: str, state_dir: Path) -> dict[str, Any]:
    token_store = TokenStore(state_dir)
    tokens = token_store.load_tokens(integration) or {}
    revoke_succeeded = False
    revoke_error: str | None = None
    try:
        if integration == "outreach":
            revoke_succeeded, revoke_error = _disconnect_outreach_bridge_remote()
        else:
            revoke_succeeded, revoke_error = _try_revoke_integration_token(integration, tokens)
    except Exception as exc:
        revoke_succeeded = False
        revoke_error = str(exc)

    token_store.delete_tokens(integration)
    _clear_integration_status_snapshot(state_dir, integration)
    if integration == "outreach":
        _clear_outreach_oauth_session(state_dir)
    _clear_legacy_integration_files(integration)
    _clear_writable_credential_sources(integration)
    substrate = _purge_substrate_for_integration(state_dir, integration)

    return {
        "ok": True,
        "revoke_succeeded": revoke_succeeded,
        "revoke_error": revoke_error,
        "substrate": substrate,
    }


def _load_json_file(path: Path) -> dict[str, Any]:
    if not path.exists():
        return {}
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}
    return payload if isinstance(payload, dict) else {}


def _write_json_file(path: Path, payload: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp_path = path.parent / f"{path.name}.tmp"
    tmp_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    try:
        tmp_path.chmod(0o600)
    except Exception:
        pass
    os.replace(tmp_path, path)


def _save_manual_integration_config(
    integration: str,
    payload: dict[str, Any],
    *,
    state_dir: Path,
) -> dict[str, Any]:
    token_store = TokenStore(state_dir)
    if integration == "hubspot":
        access_token = str(payload.get("access_token") or payload.get("token") or "").strip()
        if not access_token:
            raise ValueError("HubSpot access_token is required")
        import g_gremlin.hubspot as hubspot_helpers

        profile = str(payload.get("profile") or "default").strip() or "default"
        api_version = str(payload.get("api_version") or hubspot_helpers.DEFAULT_API_VERSION).strip()
        cfg = hubspot_helpers.connect(
            access_token=access_token,
            api_version=api_version or hubspot_helpers.DEFAULT_API_VERSION,
            profile=profile,
            set_current=True,
            verified=False,
        )
        token_store.save_tokens(
            "hubspot",
            {
                "access_token": access_token,
                "api_version": api_version or hubspot_helpers.DEFAULT_API_VERSION,
            },
        )
        return {
            "profile": cfg.profile,
            "api_version": cfg.api_version,
        }

    if integration == "dynamics":
        from g_gremlin.profile_manager import ProfileStore

        profile = str(payload.get("profile") or "default").strip() or "default"
        environment_url = str(payload.get("environment_url") or "").strip()
        if not environment_url:
            raise ValueError("Dynamics environment_url is required")

        config_path = gremlin_path("dynamics.json")
        store = ProfileStore.load(config_path, default_profile="default")
        try:
            current = dict(store.get_profile(profile))
        except Exception:
            current = {}

        current["environment_url"] = environment_url
        for key in ("tenant_id", "client_id", "client_secret", "default_environment"):
            value = str(payload.get(key) or "").strip()
            if value:
                current[key] = value
        store.set_profile(profile, current, set_current=True)
        store.save()
        token_store.save_tokens(
            "dynamics",
            {
                "environment_url": environment_url,
                "resource_url": environment_url,
                "tenant_id": str(payload.get("tenant_id") or "").strip() or None,
                "client_id": str(payload.get("client_id") or "").strip() or None,
                "client_secret": str(payload.get("client_secret") or "").strip() or None,
                "default_environment": str(payload.get("default_environment") or "").strip() or None,
            },
        )
        return {
            "profile": profile,
            "environment_url": environment_url,
        }

    if integration == "salesforce":
        import g_gremlin.sfdc as sfdc_helpers

        org_alias = str(payload.get("org_alias") or "").strip()
        if not org_alias:
            raise ValueError("Salesforce org_alias is required")
        instance_url = str(payload.get("instance_url") or "").strip()
        access_token = str(payload.get("access_token") or "").strip()
        refresh_token = str(payload.get("refresh_token") or "").strip()

        token_store.save_tokens(
            "salesforce",
            {
                "org_alias": org_alias,
                "instance_url": instance_url or None,
                "access_token": access_token or None,
                "refresh_token": refresh_token or None,
                "client_id": str(payload.get("client_id") or "").strip() or None,
                "client_secret": str(payload.get("client_secret") or "").strip() or None,
                "username": str(payload.get("username") or "").strip() or None,
                "password": str(payload.get("password") or "").strip() or None,
                "security_token": str(payload.get("security_token") or "").strip() or None,
            },
        )

        if _write_legacy_auth_files_enabled():
            sfdc_helpers.set_configured_org(
                org_alias,
                instance_url=instance_url or None,
                access_token=access_token or None,
                refresh_token=None,
            )

            cfg_path = Path(sfdc_helpers.SFDC_CONFIG_PATH)
            current = _load_json_file(cfg_path)
            for key in ("client_id", "client_secret", "username", "password", "security_token"):
                value = str(payload.get(key) or "").strip()
                if value:
                    current[key] = value
            if current:
                current["org_alias"] = org_alias
                if instance_url:
                    current["instance_url"] = instance_url
                if access_token:
                    current["access_token"] = access_token
                _write_json_file(cfg_path, current)

        return {
            "org_alias": org_alias,
            "instance_url": instance_url or None,
        }

    if integration == "outreach":
        existing = _load_outreach_file_config()
        client_id = str(payload.get("client_id") or existing.get("client_id") or "").strip()
        client_secret = str(payload.get("client_secret") or existing.get("client_secret") or "").strip()
        refresh_token = str(payload.get("refresh_token") or existing.get("refresh_token") or "").strip()
        access_token = str(payload.get("access_token") or existing.get("access_token") or "").strip()
        scope_profile = str(payload.get("scope_profile") or existing.get("scope_profile") or "").strip()

        if not client_id or not client_secret:
            raise ValueError("Outreach client_id and client_secret are required")
        if not refresh_token:
            raise ValueError("Outreach refresh_token is required (or use OAuth Connect)")

        config_payload = dict(existing)
        config_payload["client_id"] = client_id
        config_payload["client_secret"] = client_secret
        config_payload["refresh_token"] = refresh_token
        config_payload["access_token"] = access_token or None
        config_payload["auth_mode"] = "local"
        if scope_profile:
            config_payload["scope_profile"] = scope_profile
        if not access_token:
            config_payload["token_expires_at"] = 0
        _write_outreach_file_config(config_payload)

        return {
            "client_id": client_id,
            "scope_profile": scope_profile or None,
            "refresh_token_present": True,
        }

    raise ValueError(f"Unsupported integration: {integration}")


def _resolve_platform_base_url(request: Request) -> str:
    configured = os.getenv("PLATFORM_BASE_URL", "").strip()
    if configured:
        return configured.rstrip("/")

    forwarded_proto = request.headers.get("x-forwarded-proto", "").split(",")[0].strip()
    forwarded_host = request.headers.get("x-forwarded-host", "").split(",")[0].strip()
    host = forwarded_host or request.headers.get("host", "").strip() or request.url.netloc
    scheme = forwarded_proto or request.url.scheme or "https"
    return f"{scheme}://{host}".rstrip("/")


def _oauth_callback_url(request: Request, integration: str) -> str:
    if integration == "outreach":
        return _outreach_bridge_callback_url(_resolve_outreach_bridge_api_base())
    return f"{_resolve_platform_base_url(request)}/api/admin/oauth/{integration}/callback"


def _ai_oauth_callback_url(request: Request, provider: str) -> str:
    return f"{_resolve_platform_base_url(request)}/api/admin/ai/oauth/{provider}/callback"


def _ai_oauth_client_id(provider: str) -> str:
    normalized = str(provider or "").strip().lower()
    if normalized == "google":
        return (
            os.getenv("G_GREMLIN_AI_GOOGLE_OAUTH_CLIENT_ID", "").strip()
            or os.getenv("G_GREMLIN_GOOGLE_OAUTH_CLIENT_ID", "").strip()
        )
    return ""


def _ai_oauth_client_secret(provider: str) -> str:
    normalized = str(provider or "").strip().lower()
    if normalized == "google":
        return (
            os.getenv("G_GREMLIN_AI_GOOGLE_OAUTH_CLIENT_SECRET", "").strip()
            or os.getenv("G_GREMLIN_GOOGLE_OAUTH_CLIENT_SECRET", "").strip()
        )
    return ""


def _default_ai_model(provider: str) -> str:
    normalized = str(provider or "").strip().lower()
    if normalized == "google":
        return os.getenv("G_GREMLIN_AI_GOOGLE_DEFAULT_MODEL", "").strip() or "gemini-2.0-flash"
    if normalized == "openai":
        return "gpt-4o"
    if normalized == "anthropic":
        return "claude-sonnet-4-5-20250514"
    return "gemini-2.0-flash"


def _oauth_state_path(state_dir: Path) -> Path:
    return Path(state_dir) / "config" / OAUTH_STATE_FILE


def _load_oauth_state_map(state_dir: Path) -> dict[str, Any]:
    return _load_json_file(_oauth_state_path(state_dir))


def _save_oauth_state_map(state_dir: Path, payload: dict[str, Any]) -> None:
    _write_json_file(_oauth_state_path(state_dir), payload)


def _create_oauth_state(
    state_dir: Path,
    *,
    integration: str,
    payload: dict[str, Any] | None = None,
) -> str:
    now = datetime.now(timezone.utc)
    state_map = _load_oauth_state_map(state_dir)
    filtered: dict[str, Any] = {}
    for state_key, value in state_map.items():
        if not isinstance(state_key, str) or not isinstance(value, dict):
            continue
        created_at = _parse_iso(value.get("created_at"))
        if created_at is None:
            continue
        age_seconds = (now - created_at).total_seconds()
        if age_seconds <= OAUTH_STATE_TTL_SECONDS:
            filtered[state_key] = value

    state = secrets.token_urlsafe(24)
    filtered[state] = {
        "integration": integration,
        "created_at": now.isoformat(),
        "payload": payload or {},
    }
    _save_oauth_state_map(state_dir, filtered)
    return state


def _consume_oauth_state(
    state_dir: Path,
    *,
    state: str,
    expected_integration: str,
) -> dict[str, Any] | None:
    state_map = _load_oauth_state_map(state_dir)
    raw = state_map.pop(state, None)
    _save_oauth_state_map(state_dir, state_map)
    if not isinstance(raw, dict):
        return None
    if str(raw.get("integration") or "") != expected_integration:
        return None
    created_at = _parse_iso(raw.get("created_at"))
    if created_at is None:
        return None
    age_seconds = (datetime.now(timezone.utc) - created_at).total_seconds()
    if age_seconds > OAUTH_STATE_TTL_SECONDS:
        return None
    payload = raw.get("payload")
    return payload if isinstance(payload, dict) else {}


def _build_integration_oauth_start(
    *,
    state_dir: Path,
    request: Request,
    integration: str,
    payload: dict[str, Any],
) -> dict[str, Any]:
    if integration == "outreach":
        return _start_outreach_bridge_oauth(state_dir=state_dir, payload=payload)

    callback_url = _oauth_callback_url(request, integration)
    state_payload: dict[str, Any] = {}
    for key in ("profile", "org_alias", "environment_url", "scope_profile"):
        value = str(payload.get(key) or "").strip()
        if value:
            state_payload[key] = value

    state = _create_oauth_state(
        state_dir,
        integration=integration,
        payload=state_payload,
    )
    auth_url = _build_oauth_authorize_url(
        integration=integration,
        callback_url=callback_url,
        state=state,
    )

    return {
        "integration": integration,
        "auth_url": auth_url,
        "callback_url": callback_url,
        "state": state,
    }


def _build_ai_oauth_start(
    *,
    state_dir: Path,
    request: Request,
    provider: str,
    payload: dict[str, Any],
) -> dict[str, Any]:
    normalized_provider = str(provider or "").strip().lower()
    if normalized_provider != "google":
        raise ValueError(f"Unsupported AI OAuth provider: {normalized_provider or provider}")
    client_id = _ai_oauth_client_id(normalized_provider)
    client_secret = _ai_oauth_client_secret(normalized_provider)
    if not client_id or not client_secret:
        raise ValueError(
            "Google AI OAuth is not configured. Set "
            "G_GREMLIN_AI_GOOGLE_OAUTH_CLIENT_ID and G_GREMLIN_AI_GOOGLE_OAUTH_CLIENT_SECRET."
        )

    callback_url = _ai_oauth_callback_url(request, normalized_provider)
    model = str(payload.get("model") or "").strip() or _default_ai_model(normalized_provider)
    enabled = bool(payload.get("enabled", True))
    state = _create_oauth_state(
        state_dir,
        integration="ai_google",
        payload={"provider": normalized_provider, "model": model, "enabled": enabled},
    )
    auth_url = _build_ai_oauth_authorize_url(
        provider=normalized_provider,
        callback_url=callback_url,
        state=state,
    )
    return {
        "provider": normalized_provider,
        "auth_url": auth_url,
        "callback_url": callback_url,
        "state": state,
        "model": model,
    }


def _build_oauth_authorize_url(*, integration: str, callback_url: str, state: str) -> str:
    if integration == "outreach":
        client_id = _oauth_client_id("outreach")
        if not client_id:
            raise ValueError(
                "Outreach OAuth is not configured. Provide client_id in Config or set "
                "G_GREMLIN_OUTREACH_OAUTH_CLIENT_ID (or G_GREMLIN_OUTREACH_CLIENT_ID)."
            )
        auth_base = os.getenv("G_GREMLIN_OUTREACH_OAUTH_AUTHORIZE_URL", "").strip() or "https://api.outreach.io/oauth/authorize"
        default_scopes = "prospects.read prospects.write sequences.read sequenceStates.read sequenceStates.write users.read mailboxes.read"
        scopes = os.getenv("G_GREMLIN_OUTREACH_OAUTH_SCOPES", "").strip() or default_scopes
        params = {
            "response_type": "code",
            "client_id": client_id,
            "redirect_uri": callback_url,
            "scope": scopes,
            "state": state,
        }
        return f"{auth_base}?{urlencode(params)}"

    if integration == "hubspot":
        client_id = _oauth_client_id("hubspot")
        if not client_id:
            raise ValueError(
                "HubSpot OAuth is not configured. Set G_GREMLIN_HUBSPOT_OAUTH_CLIENT_ID (or G_GREMLIN_HUBSPOT_CLIENT_ID)."
            )
        auth_base = os.getenv("G_GREMLIN_HUBSPOT_OAUTH_AUTHORIZE_URL", "").strip() or "https://app.hubspot.com/oauth/authorize"
        scopes = os.getenv("G_GREMLIN_HUBSPOT_OAUTH_SCOPES", "").strip() or "crm.objects.contacts.read crm.objects.companies.read crm.objects.deals.read"
        params = {
            "client_id": client_id,
            "redirect_uri": callback_url,
            "scope": scopes,
            "state": state,
        }
        return f"{auth_base}?{urlencode(params)}"

    if integration == "salesforce":
        client_id = _oauth_client_id("salesforce")
        if not client_id:
            raise ValueError(
                "Salesforce OAuth is not configured. Set G_GREMLIN_SALESFORCE_OAUTH_CLIENT_ID (or G_GREMLIN_SALESFORCE_CLIENT_ID)."
            )
        auth_base = (
            os.getenv("G_GREMLIN_SALESFORCE_OAUTH_AUTHORIZE_URL", "").strip()
            or "https://login.salesforce.com/services/oauth2/authorize"
        )
        scopes = os.getenv("G_GREMLIN_SALESFORCE_OAUTH_SCOPES", "").strip() or "api refresh_token"
        params = {
            "response_type": "code",
            "client_id": client_id,
            "redirect_uri": callback_url,
            "scope": scopes,
            "state": state,
        }
        return f"{auth_base}?{urlencode(params)}"

    if integration == "dynamics":
        client_id = _oauth_client_id("dynamics")
        if not client_id:
            raise ValueError(
                "Dynamics OAuth is not configured. Set G_GREMLIN_DYNAMICS_OAUTH_CLIENT_ID (or G_GREMLIN_DYNAMICS_CLIENT_ID)."
            )
        tenant_id = (
            os.getenv("G_GREMLIN_DYNAMICS_OAUTH_TENANT_ID", "").strip()
            or os.getenv("G_GREMLIN_DYNAMICS_TENANT_ID", "").strip()
            or "common"
        )
        auth_base = (
            os.getenv("G_GREMLIN_DYNAMICS_OAUTH_AUTHORIZE_URL", "").strip()
            or f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/authorize"
        )
        scopes = os.getenv("G_GREMLIN_DYNAMICS_OAUTH_SCOPES", "").strip() or "offline_access openid profile"
        params = {
            "client_id": client_id,
            "response_type": "code",
            "redirect_uri": callback_url,
            "response_mode": "query",
            "scope": scopes,
            "state": state,
        }
        return f"{auth_base}?{urlencode(params)}"

    raise ValueError(f"Unsupported integration: {integration}")


def _build_ai_oauth_authorize_url(*, provider: str, callback_url: str, state: str) -> str:
    normalized_provider = str(provider or "").strip().lower()
    if normalized_provider != "google":
        raise ValueError(f"Unsupported AI OAuth provider: {normalized_provider or provider}")
    client_id = _ai_oauth_client_id(normalized_provider)
    if not client_id:
        raise ValueError(
            "Google AI OAuth is not configured. Set G_GREMLIN_AI_GOOGLE_OAUTH_CLIENT_ID."
        )
    auth_base = os.getenv("G_GREMLIN_AI_GOOGLE_OAUTH_AUTHORIZE_URL", "").strip() or "https://accounts.google.com/o/oauth2/v2/auth"
    scopes = (
        os.getenv("G_GREMLIN_AI_GOOGLE_OAUTH_SCOPES", "").strip()
        or "https://www.googleapis.com/auth/generative-language"
    )
    params = {
        "response_type": "code",
        "client_id": client_id,
        "redirect_uri": callback_url,
        "scope": scopes,
        "state": state,
        "access_type": "offline",
        "prompt": "consent",
    }
    return f"{auth_base}?{urlencode(params)}"


def _post_form_json(url: str, form_payload: dict[str, str]) -> dict[str, Any]:
    encoded = urlencode(form_payload).encode("utf-8")
    req = UrlRequest(
        url,
        data=encoded,
        headers={
            "Content-Type": "application/x-www-form-urlencoded",
            "Accept": "application/json",
        },
        method="POST",
    )
    try:
        with urlopen(req, timeout=20) as response:
            body = response.read().decode("utf-8", errors="replace")
    except HTTPError as exc:
        details = exc.read().decode("utf-8", errors="replace")
        message = details.strip() or str(exc.reason)
        raise ValueError(f"OAuth token exchange failed: {message}") from exc
    except URLError as exc:
        raise ValueError(f"OAuth token exchange failed: {exc.reason}") from exc

    try:
        payload = json.loads(body) if body.strip() else {}
    except Exception as exc:
        raise ValueError(f"OAuth token exchange returned invalid JSON: {exc}") from exc
    if not isinstance(payload, dict):
        raise ValueError("OAuth token exchange returned unexpected payload type")
    if payload.get("error"):
        raise ValueError(str(payload.get("error_description") or payload.get("error")))
    return payload


def _post_json(url: str, payload: dict[str, Any]) -> dict[str, Any]:
    encoded = json.dumps(payload).encode("utf-8")
    req = UrlRequest(
        url,
        data=encoded,
        headers={
            "Content-Type": "application/json",
            "Accept": "application/json",
        },
        method="POST",
    )
    try:
        with urlopen(req, timeout=20) as response:
            body = response.read().decode("utf-8", errors="replace")
    except HTTPError as exc:
        details = exc.read().decode("utf-8", errors="replace")
        message = details.strip() or str(exc.reason)
        raise ValueError(f"OAuth token exchange failed: {message}") from exc
    except URLError as exc:
        raise ValueError(f"OAuth token exchange failed: {exc.reason}") from exc

    try:
        response_payload = json.loads(body) if body.strip() else {}
    except Exception as exc:
        raise ValueError(f"OAuth token exchange returned invalid JSON: {exc}") from exc
    if not isinstance(response_payload, dict):
        raise ValueError("OAuth token exchange returned unexpected payload type")
    if response_payload.get("error"):
        raise ValueError(str(response_payload.get("error_description") or response_payload.get("error")))
    return response_payload


def _post_json_no_response(url: str, payload: dict[str, Any], *, timeout_seconds: float = 5.0) -> None:
    encoded = json.dumps(payload).encode("utf-8")
    req = UrlRequest(
        url,
        data=encoded,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urlopen(req, timeout=timeout_seconds):
        return None


def _exchange_hubspot_oauth_code(*, request: Request, code: str) -> dict[str, Any]:
    client_id = _oauth_client_id("hubspot")
    client_secret = (
        os.getenv("G_GREMLIN_HUBSPOT_OAUTH_CLIENT_SECRET", "").strip()
        or os.getenv("G_GREMLIN_HUBSPOT_CLIENT_SECRET", "").strip()
    )
    if not client_id or not client_secret:
        raise ValueError(
            "HubSpot OAuth token exchange is not configured. Set client_id/client_secret environment variables."
        )
    token_url = os.getenv("G_GREMLIN_HUBSPOT_OAUTH_TOKEN_URL", "").strip() or "https://api.hubapi.com/oauth/v1/token"
    callback_url = _oauth_callback_url(request, "hubspot")
    return _post_form_json(
        token_url,
        {
            "grant_type": "authorization_code",
            "client_id": client_id,
            "client_secret": client_secret,
            "redirect_uri": callback_url,
            "code": code,
        },
    )


def _exchange_salesforce_oauth_code(*, request: Request, code: str) -> dict[str, Any]:
    client_id = _oauth_client_id("salesforce")
    client_secret = (
        os.getenv("G_GREMLIN_SALESFORCE_OAUTH_CLIENT_SECRET", "").strip()
        or os.getenv("G_GREMLIN_SALESFORCE_CLIENT_SECRET", "").strip()
    )
    if not client_id or not client_secret:
        raise ValueError(
            "Salesforce OAuth token exchange is not configured. Set client_id/client_secret environment variables."
        )
    token_url = (
        os.getenv("G_GREMLIN_SALESFORCE_OAUTH_TOKEN_URL", "").strip()
        or "https://login.salesforce.com/services/oauth2/token"
    )
    callback_url = _oauth_callback_url(request, "salesforce")
    return _post_form_json(
        token_url,
        {
            "grant_type": "authorization_code",
            "client_id": client_id,
            "client_secret": client_secret,
            "redirect_uri": callback_url,
            "code": code,
        },
    )


def _exchange_dynamics_oauth_code(*, request: Request, code: str) -> dict[str, Any]:
    client_id = _oauth_client_id("dynamics")
    client_secret = (
        os.getenv("G_GREMLIN_DYNAMICS_OAUTH_CLIENT_SECRET", "").strip()
        or os.getenv("G_GREMLIN_DYNAMICS_CLIENT_SECRET", "").strip()
    )
    if not client_id or not client_secret:
        raise ValueError(
            "Dynamics OAuth token exchange is not configured. Set client_id/client_secret environment variables."
        )
    tenant_id = (
        os.getenv("G_GREMLIN_DYNAMICS_OAUTH_TENANT_ID", "").strip()
        or os.getenv("G_GREMLIN_DYNAMICS_TENANT_ID", "").strip()
        or "common"
    )
    token_url = (
        os.getenv("G_GREMLIN_DYNAMICS_OAUTH_TOKEN_URL", "").strip()
        or f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
    )
    callback_url = _oauth_callback_url(request, "dynamics")
    scope = os.getenv("G_GREMLIN_DYNAMICS_OAUTH_SCOPES", "").strip() or "offline_access openid profile"
    return _post_form_json(
        token_url,
        {
            "grant_type": "authorization_code",
            "client_id": client_id,
            "client_secret": client_secret,
            "redirect_uri": callback_url,
            "code": code,
            "scope": scope,
        },
    )


def _exchange_outreach_oauth_code(*, request: Request, code: str) -> dict[str, Any]:
    cfg = _load_outreach_file_config()
    client_id = (
        os.getenv("G_GREMLIN_OUTREACH_OAUTH_CLIENT_ID", "").strip()
        or os.getenv("G_GREMLIN_OUTREACH_CLIENT_ID", "").strip()
        or str(cfg.get("client_id") or "").strip()
    )
    client_secret = (
        os.getenv("G_GREMLIN_OUTREACH_OAUTH_CLIENT_SECRET", "").strip()
        or os.getenv("G_GREMLIN_OUTREACH_CLIENT_SECRET", "").strip()
        or str(cfg.get("client_secret") or "").strip()
    )
    if not client_id or not client_secret:
        raise ValueError(
            "Outreach OAuth token exchange is not configured. Provide client_id/client_secret in Config "
            "or set environment variables."
        )
    token_url = os.getenv("G_GREMLIN_OUTREACH_OAUTH_TOKEN_URL", "").strip() or "https://api.outreach.io/oauth/token"
    callback_url = _oauth_callback_url(request, "outreach")
    return _post_json(
        token_url,
        {
            "grant_type": "authorization_code",
            "client_id": client_id,
            "client_secret": client_secret,
            "redirect_uri": callback_url,
            "code": code,
        },
    )


def _exchange_ai_oauth_code(*, provider: str, request: Request, code: str) -> dict[str, Any]:
    normalized_provider = str(provider or "").strip().lower()
    if normalized_provider != "google":
        raise ValueError(f"Unsupported AI OAuth provider: {normalized_provider or provider}")

    client_id = _ai_oauth_client_id(normalized_provider)
    client_secret = _ai_oauth_client_secret(normalized_provider)
    if not client_id or not client_secret:
        raise ValueError(
            "Google AI OAuth token exchange is not configured. Set client_id/client_secret environment variables."
        )
    token_url = os.getenv("G_GREMLIN_AI_GOOGLE_OAUTH_TOKEN_URL", "").strip() or "https://oauth2.googleapis.com/token"
    callback_url = _ai_oauth_callback_url(request, normalized_provider)
    return _post_form_json(
        token_url,
        {
            "grant_type": "authorization_code",
            "client_id": client_id,
            "client_secret": client_secret,
            "redirect_uri": callback_url,
            "code": code,
        },
    )

def _oauth_callback_html(*, title: str, message: str, success: bool) -> HTMLResponse:
    tone = "#16a34a" if success else "#dc2626"
    html = (
        "<!doctype html><html><head><meta charset='utf-8'>"
        "<meta name='viewport' content='width=device-width, initial-scale=1'>"
        f"<title>{title}</title>"
        "<style>"
        "body{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:#0b0f14;color:#e5e7eb;margin:0;padding:24px;}"
        ".card{max-width:680px;margin:0 auto;background:#111827;border:1px solid #1f2937;border-radius:12px;padding:20px;}"
        f"h1{{font-size:20px;margin:0 0 10px 0;color:{tone};}}"
        "p{line-height:1.45;margin:0 0 14px 0;}"
        "button{background:#1f2937;color:#e5e7eb;border:1px solid #374151;border-radius:8px;padding:8px 12px;cursor:pointer;}"
        "</style></head><body>"
        f"<div class='card'><h1>{title}</h1><p>{message}</p>"
        "<button onclick='window.close()'>Close</button></div></body></html>"
    )
    return HTMLResponse(html)


def _slack_config_path(state_dir: Path) -> Path:
    return Path(state_dir) / "config" / "slack.json"


def _load_slack_file_config(state_dir: Path) -> dict[str, Any]:
    path = _slack_config_path(state_dir)
    if not path.exists():
        return {}
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        if isinstance(data, dict):
            return data
    except Exception:
        return {}
    return {}


def _write_slack_file_config(state_dir: Path, payload: dict[str, Any]) -> None:
    path = _slack_config_path(state_dir)
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp_path = path.with_suffix(".json.tmp")
    tmp_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    try:
        tmp_path.chmod(0o600)
    except Exception:
        # Best effort on non-POSIX filesystems.
        pass
    os.replace(tmp_path, path)


def _mask_secret(value: str | None) -> str | None:
    if not value:
        return None
    if len(value) <= 8:
        return "*" * len(value)
    return f"{value[:4]}{'*' * (len(value) - 8)}{value[-4:]}"


def _resolve_slack_status(app: FastAPI, *, include_secret: bool = False) -> dict[str, Any]:
    state_dir = Path(app.state.state_dir)
    file_cfg = _load_slack_file_config(state_dir)

    env_token = os.getenv("G_GREMLIN_SLACK_BOT_TOKEN", "").strip()
    env_channel = os.getenv("G_GREMLIN_SLACK_CHANNEL", "").strip()

    file_token = str(file_cfg.get("bot_token") or "").strip()
    file_channel = str(file_cfg.get("channel") or "").strip()
    policy_channel_raw = getattr(getattr(app.state.policy, "defaults", None), "slack_channel", None)
    policy_channel = policy_channel_raw.strip() if isinstance(policy_channel_raw, str) else ""

    token = env_token or file_token
    channel = env_channel or file_channel or policy_channel

    if token and channel:
        status = "connected"
    elif token or channel:
        status = "incomplete"
    else:
        status = "not_configured"

    result = {
        "status": status,
        "configured": bool(token and channel),
        "token_present": bool(token),
        "token_masked": _mask_secret(token),
        "token_source": "env" if env_token else ("state_file" if file_token else "none"),
        "channel": channel or None,
        "channel_source": (
            "env"
            if env_channel
            else ("state_file" if file_channel else ("policy" if policy_channel else "none"))
        ),
        "updated_at": file_cfg.get("updated_at"),
    }
    if include_secret:
        result["token"] = token or None
    return result


def _policy_yaml_text(app: FastAPI) -> str:
    policy_path = getattr(app.state, "policy_path", None)
    if policy_path:
        try:
            return Path(policy_path).read_text(encoding="utf-8")
        except Exception:
            # Fall through to rendered policy if file read fails.
            pass

    policy = apply_policy_overlays(app.state.policy, Path(app.state.state_dir))
    payload = policy.model_dump(by_alias=True, exclude_none=True)
    try:
        import yaml

        return yaml.safe_dump(payload, sort_keys=False, default_flow_style=False, allow_unicode=False)
    except Exception:
        return json.dumps(payload, indent=2, sort_keys=True)


def _read_runtime_compiled_hash(state_dir: Path) -> str | None:
    meta_path = Path(state_dir) / RUNTIME_POLICY_META_PATH
    if not meta_path.exists():
        return None
    try:
        payload = json.loads(meta_path.read_text(encoding="utf-8"))
    except Exception:
        return None
    if not isinstance(payload, dict):
        return None
    raw = payload.get("policy_hash")
    if isinstance(raw, str) and raw.strip():
        return raw.strip()
    return None


def _infer_module_write_class_from_tool(tool_name: str) -> tuple[str | None, str | None]:
    return infer_module_write_class_from_tool(tool_name)


def _execution_receipt_sort_key(payload: dict[str, Any]) -> float:
    if not isinstance(payload, dict):
        return 0.0
    completed = _parse_iso(payload.get("completed_at"))
    if completed is not None:
        return completed.timestamp()
    started = _parse_iso(payload.get("started_at"))
    if started is not None:
        return started.timestamp()
    return 0.0


def _receipt_matches_module(
    payload: dict[str, Any],
    *,
    module_id: str,
    write_class_keys: set[str],
) -> bool:
    module_tags = payload.get("module_tags")
    if isinstance(module_tags, list) and any(tag == module_id for tag in module_tags if isinstance(tag, str)):
        return True

    write_tags = payload.get("write_class_tags")
    if isinstance(write_tags, list):
        for tag in write_tags:
            if isinstance(tag, str) and tag in write_class_keys:
                return True

    changes = payload.get("changes")
    if not isinstance(changes, list):
        return False
    for change in changes:
        if not isinstance(change, dict):
            continue
        tool_name = str(change.get("tool") or "")
        mapped_module, mapped_write_class = _infer_module_write_class_from_tool(tool_name)
        if mapped_module == module_id:
            return True
        if mapped_write_class and mapped_write_class in write_class_keys:
            return True
    return False


def _sample_from_receipt_change(change: Any) -> dict[str, Any] | None:
    if not isinstance(change, dict):
        return None
    after = change.get("after")
    before = change.get("before")

    sample_payload: dict[str, Any] | None = None
    if isinstance(after, dict) and after and set(after.keys()) != {"status"}:
        sample_payload = dict(after)
    elif isinstance(before, dict) and before:
        sample_payload = dict(before)
    elif isinstance(after, dict) and after:
        sample_payload = dict(after)
    else:
        sample_payload = None
    if sample_payload is None:
        return None

    tool_name = str(change.get("tool") or "")
    if tool_name:
        sample_payload["_receipt_tool"] = tool_name
    targets = change.get("targets")
    if isinstance(targets, list):
        sample_payload["_receipt_targets"] = [str(target) for target in targets]
    return sample_payload


def _normalize_reference_record_id(value: Any) -> str | None:
    if value is None:
        return None
    cleaned = str(value).strip()
    if not cleaned:
        return None
    return cleaned.lower()


def _extract_reference_ids_from_sample(sample: dict[str, Any]) -> set[str]:
    candidates: set[str] = set()
    for key, value in sample.items():
        lowered_key = str(key).strip().lower()
        if not lowered_key:
            continue
        if lowered_key == "_receipt_targets":
            if isinstance(value, list):
                for item in value:
                    normalized = _normalize_reference_record_id(item)
                    if normalized:
                        candidates.add(normalized)
            continue
        if not isinstance(value, str):
            continue
        if lowered_key in {"id", "record_id"} or lowered_key.endswith("_id"):
            normalized = _normalize_reference_record_id(value)
            if normalized:
                candidates.add(normalized)
    return candidates


def _stable_json_value(value: Any) -> str:
    try:
        return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
    except Exception:
        return json.dumps(str(value), ensure_ascii=True)


def _redacted_reference_sample(sample: dict[str, Any]) -> dict[str, Any]:
    """Return a minimally useful sample shape without leaking full CRM payloads.

    TODO: tighten this redaction policy further if reference-scan is exposed in
    multi-tenant or non-local contexts.
    """

    allowed_exact = {
        "id",
        "record_id",
        "lead_id",
        "opportunity_id",
        "packet_id",
        "risk_score",
        "risk_tier",
        "reason_code",
        "_receipt_run_id",
        "_receipt_outcome",
        "_receipt_policy_hash",
        "_receipt_tool",
        "_receipt_targets",
    }
    redacted: dict[str, Any] = {}
    for key, value in sample.items():
        normalized_key = str(key).strip().lower()
        if normalized_key in allowed_exact or normalized_key.endswith("_id"):
            redacted[str(key)] = value
            continue
        redacted[str(key)] = "[redacted]"
    return redacted


def _load_execution_receipts(state_dir: Path) -> list[dict[str, Any]]:
    receipts_dir = Path(state_dir) / "receipts"
    if not receipts_dir.exists():
        return []
    receipts: list[dict[str, Any]] = []
    for run_dir in receipts_dir.iterdir():
        if not run_dir.is_dir():
            continue
        path = run_dir / "receipt.json"
        if not path.exists():
            continue
        try:
            payload = json.loads(path.read_text(encoding="utf-8"))
        except Exception:
            continue
        if isinstance(payload, dict):
            receipts.append(payload)
    return receipts


def _configured_integrations() -> set[str]:
    try:
        from g_gremlin.credentials.provider import CredentialProvider

        provider = CredentialProvider()
        return set(provider.list_configured_integrations())
    except Exception:
        return set()


def _scan_result_sort_key(payload: dict[str, Any]) -> float:
    if not isinstance(payload, dict):
        return 0.0
    ended_at = _parse_iso(payload.get("ended_at"))
    if ended_at is not None:
        return ended_at.timestamp()
    started_at = _parse_iso(payload.get("started_at"))
    if started_at is not None:
        return started_at.timestamp()
    return 0.0


def _write_scan_result_payload(state_dir: Path, *, run_id: str, payload: dict[str, Any]) -> Path:
    run_dir = Path(state_dir) / "receipts" / run_id
    run_dir.mkdir(parents=True, exist_ok=True)
    scan_path = run_dir / "scan.json"
    tmp_path = run_dir / f"scan.{os.getpid()}.tmp"
    tmp_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    os.replace(tmp_path, scan_path)
    return scan_path


def _load_scan_results(state_dir: Path) -> list[dict[str, Any]]:
    receipts_dir = Path(state_dir) / "receipts"
    if not receipts_dir.exists():
        return []
    results: list[dict[str, Any]] = []
    for run_dir in receipts_dir.iterdir():
        if not run_dir.is_dir():
            continue
        path = run_dir / "scan.json"
        if not path.exists():
            continue
        try:
            payload = json.loads(path.read_text(encoding="utf-8"))
        except Exception:
            continue
        if not isinstance(payload, dict):
            continue
        if not payload.get("run_id"):
            payload["run_id"] = run_dir.name
        results.append(payload)
    return results


def _latest_scan_result(state_dir: Path) -> dict[str, Any] | None:
    scans = sorted(_load_scan_results(state_dir), key=_scan_result_sort_key, reverse=True)
    if not scans:
        return None
    return scans[0]


def _latest_scan_finding_by_scout(state_dir: Path) -> dict[str, dict[str, Any]]:
    scans = sorted(_load_scan_results(state_dir), key=_scan_result_sort_key, reverse=True)
    latest: dict[str, dict[str, Any]] = {}
    for scan in scans:
        findings = scan.get("findings")
        if not isinstance(findings, list):
            continue
        for finding in findings:
            if not isinstance(finding, dict):
                continue
            scout_name = finding.get("scout_name")
            if not isinstance(scout_name, str) or not scout_name or scout_name in latest:
                continue
            latest[scout_name] = {
                "run_id": scan.get("run_id"),
                "started_at": scan.get("started_at"),
                "ended_at": scan.get("ended_at"),
                "finding": finding,
            }
    return latest


def _load_config_receipts(state_dir: Path) -> list[dict[str, Any]]:
    receipts_dir = Path(state_dir) / MODULE_CONFIG_RECEIPTS_DIR
    if not receipts_dir.exists():
        return []
    receipts: list[dict[str, Any]] = []
    for path in sorted(receipts_dir.glob("*.json")):
        try:
            payload = json.loads(path.read_text(encoding="utf-8"))
        except Exception:
            continue
        if isinstance(payload, dict):
            receipts.append(payload)
    return receipts


def _load_incident_counters(state_dir: Path) -> tuple[int, int]:
    path = Path(state_dir) / "runtime" / "module_incidents.json"
    if not path.exists():
        return 0, 0
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return 0, 0
    if not isinstance(payload, dict):
        return 0, 0
    false_positive = payload.get("false_positive")
    false_negative = payload.get("false_negative")
    fp = int(false_positive) if isinstance(false_positive, int) and false_positive >= 0 else 0
    fn = int(false_negative) if isinstance(false_negative, int) and false_negative >= 0 else 0
    return fp, fn


def _compute_pilot_metrics(state_dir: Path) -> dict[str, Any]:
    execution_receipts = _load_execution_receipts(state_dir)
    config_receipts = _load_config_receipts(state_dir)

    module_write_totals: dict[tuple[str, str], int] = {}
    module_write_exceptions: dict[tuple[str, str], int] = {}
    total_module_runs = 0
    total_stp = 0

    for receipt in execution_receipts:
        changes = receipt.get("changes")
        if not isinstance(changes, list):
            continue

        module_write_pairs: set[tuple[str, str]] = set()
        for change in changes:
            if not isinstance(change, dict):
                continue
            tool_name = str(change.get("tool") or "")
            module_id, write_class = _infer_module_write_class_from_tool(tool_name)
            if module_id and write_class:
                module_write_pairs.add((module_id, write_class))

        if not module_write_pairs:
            continue

        total_module_runs += 1
        outcome = str(receipt.get("outcome") or "")
        approved_by = receipt.get("approved_by")
        if outcome == "success" and not approved_by:
            total_stp += 1

        is_exception = outcome in {
            "approval_required",
            "denied",
            "failed",
            "partial_failure",
            "budget_exceeded",
            "precheck_failed",
        }
        for pair in module_write_pairs:
            module_write_totals[pair] = module_write_totals.get(pair, 0) + 1
            if is_exception:
                module_write_exceptions[pair] = module_write_exceptions.get(pair, 0) + 1

    exception_rate_by_module_write_class = []
    for (module_id, write_class), total in sorted(module_write_totals.items()):
        exceptions = module_write_exceptions.get((module_id, write_class), 0)
        rate = (exceptions / total) if total else 0.0
        exception_rate_by_module_write_class.append(
            {
                "module_id": module_id,
                "write_class": write_class,
                "total": total,
                "exceptions": exceptions,
                "rate": round(rate, 6),
            }
        )

    remediation_deltas: list[float] = []
    by_module: dict[str, list[datetime]] = {}
    for receipt in config_receipts:
        module_id = receipt.get("module_id")
        timestamp = _parse_iso(receipt.get("timestamp"))
        if isinstance(module_id, str) and module_id and timestamp:
            by_module[module_id] = by_module.get(module_id, []) + [timestamp]
    for timestamps in by_module.values():
        ordered = sorted(timestamps)
        for index in range(1, len(ordered)):
            remediation_deltas.append((ordered[index] - ordered[index - 1]).total_seconds())

    rollback_count = 0
    rollback_success_count = 0
    for receipt in config_receipts:
        action = str(receipt.get("action") or "save")
        if action == "rollback":
            rollback_count += 1
            rollback_success_count += 1

    total_config_changes = len(config_receipts)
    false_positive, false_negative = _load_incident_counters(state_dir)
    stp_rate = (total_stp / total_module_runs) if total_module_runs else 0.0
    rollback_rate = (rollback_count / total_config_changes) if total_config_changes else 0.0
    rollback_success_rate = (rollback_success_count / rollback_count) if rollback_count else 0.0

    return {
        "straight_through_processing_rate": round(stp_rate, 6),
        "exception_rate_by_module_write_class": exception_rate_by_module_write_class,
        "median_time_to_remediation_seconds": round(median(remediation_deltas), 3) if remediation_deltas else None,
        "rollback_rate": round(rollback_rate, 6),
        "rollback_success_rate": round(rollback_success_rate, 6),
        "false_positive_incidents": false_positive,
        "false_negative_incidents": false_negative,
    }


def _module_config_receipts_dir(state_dir: Path) -> Path:
    return Path(state_dir) / MODULE_CONFIG_RECEIPTS_DIR


def _write_module_config_receipt(
    state_dir: Path,
    *,
    module_id: str,
    actor: str,
    old_envelope: ModuleEnvelope,
    new_envelope: ModuleEnvelope,
    semantic_diff: dict[str, Any],
    action: str,
) -> str:
    writer = ConfigReceiptWriter(state_dir)
    receipt_id, _ = writer.write(
        module_id=module_id,
        actor=actor,
        old_envelope=old_envelope.model_dump(exclude_none=False),
        new_envelope=new_envelope.model_dump(exclude_none=False),
        semantic_diff=semantic_diff,
        action=action,
    )
    return receipt_id


if __name__ == "__main__":
    # For development: run directly
    logging.basicConfig(level=logging.INFO)
    run_server()

