"""
Feature Flag HTTP Service · Phase 2a+ scaffold
A-owned · session_a · FastAPI

Wraps evaluator.py with an HTTP boundary. Reads registry.json once at
startup. Dev-mode context via X-PTT-* headers or query params. No JWT,
no Redis cache, no override store in this phase.

Endpoints:
  GET  /api/flags/eval          · single evaluation
  POST /api/flags/eval/batch    · multiple evaluations, shared context
  GET  /api/flags/registry      · read-only registry snapshot
  GET  /api/flags/health        · liveness + readiness

Contract: ./http_contract.json
Parity:   ./verify_parity.py (must be 19/19 before serving)

Run: bash run.sh    (or:  uvicorn app:app --port 8080 --reload)
"""
from __future__ import annotations

import json
import logging
import os
import time
import uuid
from typing import Any

from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse

from evaluator import VERSION as EVAL_VERSION, evaluate, evaluate_batch

SERVICE_VERSION = "2a_plus-py-scaffold-0.1"

HERE = os.path.dirname(os.path.abspath(__file__))
REGISTRY_PATH = os.environ.get(
    "FF_REGISTRY_PATH",
    os.path.abspath(os.path.join(HERE, "..", "feature-flags", "registry.json")),
)

logging.basicConfig(
    level=os.environ.get("FF_LOG_LEVEL", "INFO"),
    format='{"ts":"%(asctime)s","level":"%(levelname)s","msg":%(message)s}',
)
log = logging.getLogger("ff-service")


def _load_registry() -> tuple[dict | None, str | None]:
    try:
        with open(REGISTRY_PATH, encoding="utf-8") as f:
            reg = json.load(f)
        if not isinstance(reg.get("flags"), list):
            return None, "registry.flags is not a list"
        return reg, None
    except FileNotFoundError:
        return None, f"registry not found at {REGISTRY_PATH}"
    except json.JSONDecodeError as e:
        return None, f"registry JSON parse failed: {e}"
    except Exception as e:  # noqa: BLE001
        return None, f"registry load error: {e}"


REGISTRY, REGISTRY_ERR = _load_registry()
STARTED_AT = time.time()

app = FastAPI(
    title="PTT Feature Flag Service",
    version=SERVICE_VERSION,
    description="Phase 2a+ HTTP boundary · wraps evaluator.py (parity with evaluator.js 2a-js-module)",
)

# Dev CORS · localhost anywhere. Restrict in prod config (not this phase).
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=False,
    allow_methods=["GET", "POST", "OPTIONS"],
    allow_headers=["*"],
)


def _envelope(ok: bool, data: Any = None, error: dict | None = None, request_id: str | None = None) -> dict:
    return {
        "ok": ok,
        "data": data,
        "error": error,
        "service": {
            "service_version": SERVICE_VERSION,
            "evaluator_version": EVAL_VERSION,
            "request_id": request_id or str(uuid.uuid4()),
        },
    }


def _err(code: str, message: str, hint: str | None = None) -> dict:
    e = {"code": code, "message": message}
    if hint:
        e["hint"] = hint
    return e


def _req_id(request: Request) -> str:
    return request.headers.get("x-request-id") or str(uuid.uuid4())


def _resolve_context(request: Request, q: dict[str, Any]) -> dict[str, Any]:
    """Headers > query. Mirrors http_contract.auth_strategy."""
    h = request.headers
    ctx = {
        "tenant_id": h.get("x-ptt-tenant-id") or q.get("tenant"),
        "user_id": h.get("x-ptt-user-id") or q.get("user"),
        "env": h.get("x-ptt-env") or q.get("env") or "dev",
        "tier": h.get("x-ptt-tier") or q.get("tier") or "anonymous",
        "role_key": h.get("x-ptt-role-key") or q.get("role_key"),
        "now_iso": q.get("now_iso"),
    }
    return {k: v for k, v in ctx.items() if v is not None}


def _require_registry(request_id: str):
    if REGISTRY is None:
        raise HTTPException(
            status_code=503,
            detail=_envelope(
                False,
                error=_err("registry_unavailable", REGISTRY_ERR or "registry not loaded",
                           hint=f"check FF_REGISTRY_PATH={REGISTRY_PATH}"),
                request_id=request_id,
            ),
        )


@app.exception_handler(HTTPException)
async def http_ex_handler(request: Request, exc: HTTPException):
    if isinstance(exc.detail, dict) and "ok" in exc.detail:
        return JSONResponse(status_code=exc.status_code, content=exc.detail)
    return JSONResponse(
        status_code=exc.status_code,
        content=_envelope(False, error=_err("internal_error", str(exc.detail)),
                          request_id=_req_id(request)),
    )


@app.exception_handler(Exception)
async def any_ex_handler(request: Request, exc: Exception):
    log.exception("unhandled")
    return JSONResponse(
        status_code=500,
        content=_envelope(False, error=_err("internal_error", str(exc)),
                          request_id=_req_id(request)),
    )


@app.get("/api/flags/health")
def health(request: Request):
    rid = _req_id(request)
    ok = REGISTRY is not None
    data = {
        "status": "ready" if ok else "degraded",
        "registry_loaded": ok,
        "registry_path": REGISTRY_PATH,
        "registry_error": REGISTRY_ERR,
        "flag_count": len(REGISTRY["flags"]) if ok else 0,
        "uptime_seconds": int(time.time() - STARTED_AT),
        "service_version": SERVICE_VERSION,
        "evaluator_version": EVAL_VERSION,
    }
    status = 200 if ok else 503
    return JSONResponse(status_code=status, content=_envelope(ok, data=data, request_id=rid))


@app.get("/api/flags/eval")
def eval_one(request: Request):
    rid = _req_id(request)
    _require_registry(rid)
    q = dict(request.query_params)

    key = q.get("key")
    if not key:
        raise HTTPException(
            status_code=400,
            detail=_envelope(
                False,
                error=_err("invalid_request", "missing required query param: key",
                           hint="?key=<flag_key>"),
                request_id=rid,
            ),
        )

    ctx = _resolve_context(request, q)
    if not ctx.get("user_id"):
        raise HTTPException(
            status_code=400,
            detail=_envelope(
                False,
                error=_err("invalid_request", "missing user_id",
                           hint="pass ?user=<id> or X-PTT-User-Id header"),
                request_id=rid,
            ),
        )

    req = {"flag_key": key, **ctx}
    t0 = time.perf_counter()
    result = evaluate(REGISTRY, req)
    dur_ms = round((time.perf_counter() - t0) * 1000, 3)

    log.info(json.dumps({
        "request_id": rid, "event": "flag_eval",
        "flag_key": key, "source": result["source"],
        "tenant_id": ctx.get("tenant_id"), "tier": ctx.get("tier"),
        "duration_ms": dur_ms,
    }))

    return JSONResponse(
        status_code=200,
        content=_envelope(True, data=result, request_id=rid),
        headers={"X-Request-Id": rid},
    )


@app.post("/api/flags/eval/batch")
async def eval_batch(request: Request):
    rid = _req_id(request)
    _require_registry(rid)

    try:
        body = await request.json()
    except Exception:
        raise HTTPException(
            status_code=400,
            detail=_envelope(
                False,
                error=_err("invalid_request", "body is not valid JSON",
                           hint='expect {"context":{...},"flags":[...]}'),
                request_id=rid,
            ),
        )

    shared = body.get("context") or {}
    # allow X-PTT-* headers to fill gaps
    hdr_ctx = _resolve_context(request, {})
    merged_ctx = {**hdr_ctx, **{k: v for k, v in shared.items() if v is not None}}

    if not merged_ctx.get("user_id"):
        raise HTTPException(
            status_code=400,
            detail=_envelope(
                False,
                error=_err("invalid_request",
                           "context.user_id missing · also not in X-PTT-User-Id"),
                request_id=rid,
            ),
        )

    flags = body.get("flags")
    if not isinstance(flags, list) or len(flags) == 0:
        raise HTTPException(
            status_code=400,
            detail=_envelope(
                False,
                error=_err("invalid_request", "flags[] missing or empty"),
                request_id=rid,
            ),
        )

    requests: list[dict] = []
    for item in flags:
        if isinstance(item, str):
            requests.append({"flag_key": item, **merged_ctx})
        elif isinstance(item, dict) and isinstance(item.get("flag_key"), str):
            requests.append({**merged_ctx, **item})
        else:
            requests.append({"flag_key": None, **merged_ctx})

    t0 = time.perf_counter()
    results = evaluate_batch(REGISTRY, requests)
    dur_ms = round((time.perf_counter() - t0) * 1000, 3)

    log.info(json.dumps({
        "request_id": rid, "event": "flag_batch",
        "count": len(results), "duration_ms": dur_ms,
        "tier": merged_ctx.get("tier"),
    }))

    return JSONResponse(
        status_code=200,
        content=_envelope(True, data=results, request_id=rid),
        headers={"X-Request-Id": rid},
    )


@app.get("/api/flags/registry")
def get_registry(request: Request):
    rid = _req_id(request)
    _require_registry(rid)
    q = dict(request.query_params)
    summary = str(q.get("summary", "")).lower() in ("1", "true", "yes")

    if summary:
        data = {
            "schema_version": REGISTRY.get("schema_version"),
            "count": len(REGISTRY["flags"]),
            "flags": [
                {
                    "key": f.get("key"),
                    "id": f.get("id"),
                    "rollout_stage": f.get("rollout_stage"),
                    "sensitive_flag": f.get("sensitive_flag", False),
                    "requires_approval": f.get("requires_approval", False),
                    "default_value": f.get("default_value"),
                }
                for f in REGISTRY["flags"]
            ],
        }
    else:
        data = REGISTRY

    log.info(json.dumps({
        "request_id": rid, "event": "registry_read",
        "summary": summary, "count": len(REGISTRY["flags"]),
    }))

    return JSONResponse(
        status_code=200,
        content=_envelope(True, data=data, request_id=rid),
        headers={"X-Request-Id": rid},
    )


@app.get("/")
def root(request: Request):
    return _envelope(True, data={
        "service": "PTT Feature Flag Service",
        "phase": "2a+ (base) + 2b (additive)",
        "endpoints": [
            "GET  /api/flags/eval?key=…&user=…&tier=…",
            "POST /api/flags/eval/batch",
            "GET  /api/flags/registry",
            "GET  /api/flags/health",
            "GET  /api/flags/overrides/by-flag/{flag_key}   (Phase 2b)",
            "POST /api/flags/sensitive-eval                (Phase 2b)",
        ],
        "contract": "http_contract.json (Phase 2a+) · phase_2b_contract.json (Phase 2b additive)",
        "parity": "verify_parity.py (19/19 · unchanged by Phase 2b)",
    }, request_id=_req_id(request))


# ============================================================================
# PHASE 2B · additive only · does NOT alter Phase 2a+ endpoints above
# ============================================================================
#  * JWT Bearer resolver (decode-only unless verification preconditions met)
#  * File-backed override store (loaded at startup)
#  * NEW endpoints: GET /api/flags/overrides/by-flag/{key}, POST /api/flags/sensitive-eval
# Parity gate (19/19) is unaffected — evaluator.py is untouched.
# Contract: phase_2b_contract.json

PHASE_2B_VERSION = "2b-py-additive-0.1"

OVERRIDES_PATH = os.environ.get(
    "FF_OVERRIDES_PATH",
    os.path.abspath(os.path.join(HERE, "override_examples.json")),
)
JWT_VERIFY_FLAG = os.environ.get("POLICY_JWT_VERIFY", "").lower() in ("1", "true", "yes")
JWT_PUBLIC_KEY = os.environ.get("POLICY_JWT_PUBLIC_KEY")
JWT_JWKS_URL   = os.environ.get("POLICY_JWKS_URL")

try:
    import jwt as _pyjwt   # PyJWT is OPTIONAL · not in requirements as hard dep
    PYJWT_AVAILABLE = True
except Exception:          # noqa: BLE001
    _pyjwt = None          # type: ignore
    PYJWT_AVAILABLE = False


def _load_override_store() -> tuple[list[dict], dict[str, int], str | None]:
    """Load override store JSON. Returns (rows, index_by_flag, error_msg)."""
    try:
        with open(OVERRIDES_PATH, encoding="utf-8") as f:
            data = json.load(f)
        rows = data.get("overrides") or []
        if not isinstance(rows, list):
            return [], {}, "override_examples.overrides is not a list"
        # Soft validation · drop unusable rows but keep warnings in log
        valid: list[dict] = []
        for r in rows:
            if not isinstance(r, dict):
                continue
            if not r.get("id") or not r.get("flag_key") or r.get("scope") not in ("tenant", "user"):
                continue
            if "value" not in r:
                continue
            if r["scope"] == "user" and not r.get("user_id"):
                continue
            valid.append(r)
        # index by flag_key for O(1) lookup
        idx: dict[str, list[int]] = {}
        for i, r in enumerate(valid):
            idx.setdefault(r["flag_key"], []).append(i)
        return valid, idx, None
    except FileNotFoundError:
        return [], {}, f"override file not found at {OVERRIDES_PATH}"
    except json.JSONDecodeError as e:
        return [], {}, f"override JSON parse failed: {e}"
    except Exception as e:  # noqa: BLE001
        return [], {}, f"override load error: {e}"


OVERRIDE_ROWS, OVERRIDE_INDEX, OVERRIDE_ERR = _load_override_store()


def _iso_in_past(s: str | None) -> bool:
    if not s:
        return False
    try:
        from datetime import datetime, timezone
        if s.endswith("Z"):
            s2 = s[:-1] + "+00:00"
        else:
            s2 = s
        d = datetime.fromisoformat(s2)
        return d < datetime.now(timezone.utc)
    except Exception:
        return False


def _parse_bearer(request: Request) -> tuple[dict, str, list[str]]:
    """Return (claims, auth_source, warnings).

    auth_source: 'jwt' | 'jwt_unverified' | 'none'
    Verification requires ALL of: PyJWT installed, POLICY_JWT_VERIFY=true, and a public key/JWKS.
    """
    warnings: list[str] = []
    hdr = request.headers.get("authorization", "")
    if not hdr.lower().startswith("bearer "):
        return {}, "none", warnings
    token = hdr[7:].strip()
    if not token:
        return {}, "none", warnings
    can_verify = PYJWT_AVAILABLE and JWT_VERIFY_FLAG and (JWT_PUBLIC_KEY or JWT_JWKS_URL)
    if can_verify and _pyjwt is not None:
        try:
            claims = _pyjwt.decode(token, JWT_PUBLIC_KEY, algorithms=["RS256", "HS256"])
            return claims, "jwt", warnings
        except Exception as e:  # noqa: BLE001
            warnings.append(f"jwt_verify_failed: {e}")
            # fall through to unverified decode
    # Unverified decode — dev/demo only. PyJWT path first; manual fallback otherwise.
    try:
        if _pyjwt is not None:
            claims = _pyjwt.decode(token, options={"verify_signature": False, "verify_exp": False})
        else:
            # Minimal base64 payload decode (no signature check)
            import base64
            parts = token.split(".")
            if len(parts) < 2:
                return {}, "none", warnings + ["bearer_malformed"]
            pad = "=" * (-len(parts[1]) % 4)
            claims = json.loads(base64.urlsafe_b64decode(parts[1] + pad))
        warnings.append("auth_not_verified")
        return claims, "jwt_unverified", warnings
    except Exception as e:  # noqa: BLE001
        return {}, "none", warnings + [f"bearer_decode_failed: {e}"]


def _ctx_from_claims(claims: dict) -> dict:
    """Map JWT claims onto the evaluator's request context vocabulary."""
    out: dict[str, Any] = {}
    if claims.get("sub"):       out["user_id"] = claims["sub"]
    if claims.get("tenant_id"): out["tenant_id"] = claims["tenant_id"]
    if claims.get("tier"):      out["tier"] = claims["tier"]
    if claims.get("role"):      out["role_key"] = claims["role"]
    return out


def _build_ctx_2b(request: Request, q: dict[str, Any]) -> tuple[dict[str, Any], str, list[str]]:
    """Phase 2b context resolver: Bearer > X-PTT-* > query > defaults.

    Returns (context_dict, auth_source, warnings).
    The returned context shape matches what the evaluator expects, with the
    addition that `overrides` may be populated from the file-backed store.
    """
    claims, auth_src, warnings = _parse_bearer(request)
    hdr = _resolve_context(request, {})   # reuse 2a+ header reader
    from_claims = _ctx_from_claims(claims)
    merged: dict[str, Any] = {}
    for src in (q, hdr, from_claims):  # lowest-priority first · later overrides earlier
        if not src:
            continue
        for k, v in src.items():
            if v is None:
                continue
            merged[k] = v
    # Defaults
    merged.setdefault("env", "dev")
    merged.setdefault("tier", "anonymous")
    # auth_source enum
    if auth_src == "none":
        if hdr and q:
            auth_src = "mixed"
        elif hdr:
            auth_src = "dev_headers"
        elif q:
            auth_src = "query"
    elif auth_src.startswith("jwt") and (hdr or q):
        auth_src = "mixed"
    return merged, auth_src, warnings


def _active_store_overrides(flag_key: str, tenant_id: str | None, user_id: str | None) -> dict:
    """Return inline-overrides dict to pass into evaluator: {user:{}, tenant:{}}.

    Picks the highest-priority active (non-expired) row per scope.
    """
    out: dict[str, dict] = {"user": {}, "tenant": {}}
    if OVERRIDE_ERR:
        return out
    for i in OVERRIDE_INDEX.get(flag_key, []):
        r = OVERRIDE_ROWS[i]
        if _iso_in_past(r.get("expires_at")):
            continue
        if r["scope"] == "user" and r.get("user_id") == user_id and r.get("tenant_id") == tenant_id:
            out["user"][flag_key] = r["value"]
        elif r["scope"] == "tenant" and r.get("tenant_id") == tenant_id:
            # only set tenant if user didn't already win
            out["tenant"].setdefault(flag_key, r["value"])
    return out


@app.get("/api/flags/overrides/by-flag/{flag_key}")
def ep_overrides_by_flag(flag_key: str, request: Request):
    """Phase 2b · READ-ONLY override inspection.

    Returns every row for `flag_key` including expired ones. Each row carries
    `active: bool` computed at response time. No writes, no state mutation.
    """
    rid = _req_id(request)
    _require_registry(rid)
    rows_out = []
    for i in OVERRIDE_INDEX.get(flag_key, []):
        r = OVERRIDE_ROWS[i]
        rows_out.append({**r, "active": not _iso_in_past(r.get("expires_at"))})
    log.info(json.dumps({
        "request_id": rid, "event": "overrides_read",
        "flag_key": flag_key, "row_count": len(rows_out),
    }))
    return JSONResponse(
        status_code=200,
        content=_envelope(True, data={
            "flag_key": flag_key,
            "row_count": len(rows_out),
            "rows": rows_out,
            "phase": "2b",
            "store_path": OVERRIDES_PATH,
            "store_error": OVERRIDE_ERR,
        }, request_id=rid),
        headers={"X-Request-Id": rid},
    )


@app.post("/api/flags/sensitive-eval")
async def ep_sensitive_eval(request: Request):
    """Phase 2b · sensitive-flag precheck + optional evaluation.

    Returns:
      - sensitivity_profile from registry (sensitive_flag · requires_approval · rollout_stage)
      - policy_hint (LOCAL computation · does NOT call admin service)
      - can_proceed_to_eval (NOT policy_precheck_required OR approvals_present)
      - flag_result (only when can_proceed_to_eval=true)

    A real policy-first orchestration lives at /runtime/cross-runtime-integration/.
    This endpoint stays in-process for fast sensitive-flag inspection.
    """
    rid = _req_id(request)
    _require_registry(rid)
    try:
        body = await request.json()
    except Exception:
        raise HTTPException(status_code=400, detail=_envelope(
            False, error=_err("invalid_request", "body is not valid JSON",
                              hint='expect {"context":{...},"flag_key":"...", "approval_refs":[...]}'),
            request_id=rid,
        ))

    flag_key = body.get("flag_key")
    shared = body.get("context") or {}
    approval_refs = body.get("approval_refs") or []
    if not isinstance(approval_refs, list):
        approval_refs = []

    q = {}  # no query support here
    ctx, auth_source, warnings = _build_ctx_2b(request, q)
    for k, v in shared.items():
        if v is not None:
            ctx[k] = v

    if not flag_key or not isinstance(flag_key, str):
        raise HTTPException(status_code=400, detail=_envelope(
            False, error=_err("invalid_request", "flag_key required"), request_id=rid,
        ))
    if not ctx.get("user_id"):
        raise HTTPException(status_code=400, detail=_envelope(
            False, error=_err("invalid_request", "user_id missing · set sub claim, X-PTT-User-Id header, or context.user_id"),
            request_id=rid,
        ))

    # Look up flag in registry
    flag = None
    for f in REGISTRY["flags"]:
        if f.get("key") == flag_key:
            flag = f; break
    if flag is None:
        sensitivity = None
        policy_hint = {
            "decision": "skipped",
            "required_approvers": [],
            "approval_matrix_row": None,
            "approvals_present": bool(approval_refs),
            "honest_note": "POLICY_HINT_ONLY · this does NOT call admin policy · use /runtime/cross-runtime-integration/ for a real policy-first chain",
        }
        can_proceed = False
        flag_result = {"flag_key": flag_key, "value": False, "source": "unknown_flag",
                       "trace": ["[1] flag_exists_check: UNKNOWN_FLAG key=" + flag_key]}
        warnings.append("unknown_flag")
    else:
        sensitive = bool(flag.get("sensitive_flag"))
        requires = bool(flag.get("requires_approval"))
        precheck_required = sensitive or requires
        sensitivity = {
            "sensitive_flag": sensitive,
            "requires_approval": requires,
            "rollout_stage": flag.get("rollout_stage"),
            "policy_precheck_required": precheck_required,
        }
        approvals_present = bool(approval_refs)
        if sensitive:
            required_approvers = ["tenant_dpo", "platform_governance"]
            approval_matrix_row = flag.get("approval_matrix_row") or "row-sensitive-override"
        elif requires:
            required_approvers = ["tenant_admin"]
            approval_matrix_row = flag.get("approval_matrix_row")
        else:
            required_approvers = []
            approval_matrix_row = None
        if precheck_required:
            decision = "allow" if approvals_present else "deny-needs-approval"
        else:
            decision = "skipped"
        policy_hint = {
            "decision": decision,
            "required_approvers": required_approvers,
            "approval_matrix_row": approval_matrix_row,
            "approvals_present": approvals_present,
            "honest_note": "POLICY_HINT_ONLY · this does NOT call admin policy · use /runtime/cross-runtime-integration/ for a real policy-first chain",
        }
        can_proceed = (not precheck_required) or approvals_present
        if can_proceed:
            # Layer store-backed overrides UNDER any inline overrides (per precedence note)
            store_ov = _active_store_overrides(flag_key, ctx.get("tenant_id"), ctx.get("user_id"))
            inline = ctx.get("overrides") or {}
            merged_ov = {
                "user":   {**store_ov["user"],   **(inline.get("user") or {})},
                "tenant": {**store_ov["tenant"], **(inline.get("tenant") or {})},
            }
            eval_ctx = {**ctx, "flag_key": flag_key, "overrides": merged_ov}
            t0 = time.perf_counter()
            flag_result = evaluate(REGISTRY, eval_ctx)
            _ = round((time.perf_counter() - t0) * 1000, 3)
            # Annotate override hit, if any
            hit_user = bool(store_ov["user"])
            hit_tenant = bool(store_ov["tenant"])
            flag_result.setdefault("trace", []).append(
                f"[2b] override_store: user_hit={hit_user} tenant_hit={hit_tenant}"
            )
        else:
            flag_result = None

    data = {
        "flag_key": flag_key,
        "phase": "2b",
        "sensitivity_profile": sensitivity,
        "policy_hint": policy_hint,
        "can_proceed_to_eval": bool(can_proceed),
        "flag_result": flag_result,
        "warnings": warnings,
        "auth_source": auth_source,
    }
    log.info(json.dumps({
        "request_id": rid, "event": "sensitive_eval",
        "flag_key": flag_key, "decision": policy_hint["decision"],
        "can_proceed": bool(can_proceed), "auth_source": auth_source,
    }))
    return JSONResponse(
        status_code=200,
        content=_envelope(True, data=data, request_id=rid),
        headers={"X-Request-Id": rid},
    )


@app.get("/phase-2b/health")
def health_2b(request: Request):
    """Phase 2b health extension · does NOT replace /api/flags/health."""
    rid = _req_id(request)
    base = {
        "phase": "2b",
        "override_store_loaded": OVERRIDE_ERR is None,
        "override_store_path": OVERRIDES_PATH,
        "override_store_error": OVERRIDE_ERR,
        "override_count": len(OVERRIDE_ROWS),
        "pyjwt_installed": PYJWT_AVAILABLE,
        "jwt_verify_live": PYJWT_AVAILABLE and JWT_VERIFY_FLAG and bool(JWT_PUBLIC_KEY or JWT_JWKS_URL),
        "jwt_verify_flag_env": JWT_VERIFY_FLAG,
        "jwt_public_key_present": bool(JWT_PUBLIC_KEY),
        "jwks_url_present": bool(JWT_JWKS_URL),
        "phase_2b_endpoints": [
            "GET  /api/flags/overrides/by-flag/{flag_key}",
            "POST /api/flags/sensitive-eval",
            "GET  /phase-2b/health",
        ],
        "contract": "phase_2b_contract.json",
        "honest": "Additive only · Phase 2a+ endpoints unchanged · 19/19 parity still holds",
    }
    return JSONResponse(status_code=200, content=_envelope(True, data=base, request_id=rid))
