Files
pikasTech-unidesk/scripts/src/platform-infra-sub2api-codex/remote-python-sync.ts
T

3220 lines
143 KiB
TypeScript

// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. remote-python-sync module for scripts/src/platform-infra-sub2api-codex.ts.
// Moved mechanically from scripts/src/platform-infra-sub2api-codex.ts:4265-5400 for #903.
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { UniDeskConfig } from "../config";
import { rootPath } from "../config";
import type { RenderedCliResult } from "../output";
import { applyPk01CaddyBlock, prepareFrpcSecret, renderFrpcManifest, type PublicServiceExposure, type PublicServiceTarget } from "../platform-infra-public-service";
import { shortSha256Fingerprint } from "../platform-infra-ops-library";
import {
codexPoolSentinelSummary,
codexPoolSentinelRuntimeImage,
readCodexPoolSentinelConfig,
renderCodexPoolSentinelManifest,
type CodexPoolSentinelConfig,
type CodexPoolSentinelProfileSecret,
} from "../platform-infra-sub2api-codex-sentinel";
import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets";
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";
import type { CodexPoolConfig, CodexPoolRuntimeTarget } from "./types";
import { desiredAccountCapacityMap, desiredAccountLoadFactorMap, desiredAccountTempUnschedulableMap, desiredAccountWebSocketsV2ModeMap } from "./accounts";
import { resolvedManualAccountProtections } from "./public-exposure";
import { fieldManager } from "./types";
function pyJson(value: unknown): string {
return `json.loads(${JSON.stringify(JSON.stringify(value))})`;
}
export function remotePythonScript(mode: "sync" | "validate" | "trace" | "cleanup-probes" | "sentinel-probe", encodedPayload: string, pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): string {
const hostDockerEnvPath = target.runtimeMode === "host-docker" ? target.hostDockerEnvPath : null;
return `
set -u
python3 - <<'PY'
import base64
import hashlib
import json
import os
import re
import secrets
import string
import subprocess
import sys
import time
from datetime import datetime, timezone, timedelta
from urllib.parse import quote
TARGET_ID = ${pyJson(target.id)}
RUNTIME_MODE = ${pyJson(target.runtimeMode)}
NAMESPACE = ${pyJson(target.namespace)}
SERVICE_NAME = ${pyJson(target.serviceName)}
SERVICE_DNS = ${pyJson(target.serviceDns)}
HOST_DOCKER_APP_PORT = ${pyJson(target.hostDockerAppPort)}
HOST_DOCKER_ENV_PATH = ${pyJson(hostDockerEnvPath)}
HOST_DOCKER_APP_CONTAINER = "sub2api-app"
FIELD_MANAGER = "${fieldManager}"
APP_SECRET_NAME = ${pyJson(target.appSecretName)}
POOL_GROUP_NAME = "${pool.groupName}"
POOL_GROUP_DESCRIPTION = ${pyJson(pool.groupDescription)}
POOL_API_KEY_NAME = "${pool.apiKeyName}"
POOL_API_KEY_SECRET_NAME = "${pool.apiKeySecretName}"
POOL_API_KEY_SECRET_KEY = "${pool.apiKeySecretKey}"
POOL_ADMIN_EMAIL_DEFAULT = ${pyJson(pool.adminEmailDefault)}
MIN_OWNER_BALANCE_USD = ${pyJson(pool.minOwnerBalanceUsd)}
MIN_OWNER_CONCURRENCY = ${pyJson(pool.minOwnerConcurrency)}
MIN_OWNER_CONCURRENCY_SOURCE = ${pyJson(pool.minOwnerConcurrencySource)}
POOL_DEFAULT_ACCOUNT_PRIORITY = ${pyJson(pool.defaultAccountPriority)}
POOL_DEFAULT_ACCOUNT_CAPACITY = ${pyJson(pool.defaultAccountCapacity)}
POOL_DEFAULT_ACCOUNT_LOAD_FACTOR = ${pyJson(pool.defaultAccountLoadFactor)}
RESPONSES_SMOKE_MODEL = ${pyJson(pool.localCodex.responsesSmokeModel)}
EXPECTED_ACCOUNT_CAPACITIES = ${pyJson(desiredAccountCapacityMap(pool))}
EXPECTED_ACCOUNT_LOAD_FACTORS = ${pyJson(desiredAccountLoadFactorMap(pool))}
EXPECTED_ACCOUNT_WS_MODES = json.loads(${JSON.stringify(JSON.stringify(desiredAccountWebSocketsV2ModeMap(pool)))})
EXPECTED_ACCOUNT_TEMP_UNSCHEDULABLE = json.loads(${JSON.stringify(JSON.stringify(desiredAccountTempUnschedulableMap(pool)))})
MANUAL_ACCOUNT_PROTECTIONS = json.loads(${JSON.stringify(JSON.stringify(resolvedManualAccountProtections(pool, target)))})
SENTINEL_CONFIG = json.loads(${JSON.stringify(JSON.stringify(pool.sentinel))})
TARGET_EGRESS_PROXY = json.loads(${JSON.stringify(JSON.stringify(target.egressProxy))})
TARGET_SENTINEL_ENABLED = ${target.sentinelEnabled ? "True" : "False"}
MODE = "${mode}"
PAYLOAD_B64 = "${encodedPayload}"
def run(cmd, input_bytes=None):
return subprocess.run(cmd, input=input_bytes, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def text(data, limit=4000):
if isinstance(data, bytes):
data = data.decode("utf-8", errors="replace")
return data[-limit:]
def read_host_env():
if RUNTIME_MODE != "host-docker":
return {}
if not isinstance(HOST_DOCKER_ENV_PATH, str) or not HOST_DOCKER_ENV_PATH:
raise RuntimeError("host-docker env source path missing")
values = {}
lines = read_host_env_lines()
for line in lines:
stripped = line.strip()
if not stripped or stripped.startswith("#") or "=" not in stripped:
continue
key, value = stripped.split("=", 1)
key = key.strip()
value = value.strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in ("'", '"'):
value = value[1:-1]
if key:
values[key] = value
return values
def read_host_env_lines():
try:
with open(HOST_DOCKER_ENV_PATH, "r", encoding="utf-8") as handle:
return handle.read().splitlines()
except FileNotFoundError:
raise RuntimeError(f"host-docker env source missing: {HOST_DOCKER_ENV_PATH}")
except PermissionError:
proc = run(["sudo", "-n", "cat", HOST_DOCKER_ENV_PATH])
if proc.returncode != 0:
raise RuntimeError("read host-docker env source failed: " + text(proc.stderr, 1000))
return proc.stdout.decode("utf-8", errors="replace").splitlines()
def write_host_env_value(key, value):
if RUNTIME_MODE != "host-docker":
raise RuntimeError("write_host_env_value is only valid for host-docker")
if not isinstance(HOST_DOCKER_ENV_PATH, str) or not HOST_DOCKER_ENV_PATH:
raise RuntimeError("host-docker env source path missing")
if not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", key):
raise RuntimeError(f"unsupported env key: {key}")
os.makedirs(os.path.dirname(HOST_DOCKER_ENV_PATH), exist_ok=True)
try:
lines = read_host_env_lines()
except RuntimeError as exc:
if "missing" not in str(exc):
raise
lines = []
next_lines = []
replaced = False
for line in lines:
stripped = line.strip()
if stripped.startswith("#") or "=" not in stripped:
next_lines.append(line)
continue
current_key = stripped.split("=", 1)[0].strip()
if current_key == key:
next_lines.append(f"{key}={value}")
replaced = True
else:
next_lines.append(line)
if not replaced:
next_lines.append(f"{key}={value}")
content = "\\n".join(next_lines).rstrip() + "\\n"
tmp_path = HOST_DOCKER_ENV_PATH + ".tmp"
try:
with open(tmp_path, "w", encoding="utf-8") as handle:
handle.write(content)
os.chmod(tmp_path, 0o600)
os.replace(tmp_path, HOST_DOCKER_ENV_PATH)
except PermissionError:
try:
os.unlink(tmp_path)
except Exception:
pass
script = r'''
set -eu
path="$1"
dir="$(dirname "$path")"
mkdir -p "$dir"
tmp="$path.tmp.$$"
umask 077
cat > "$tmp"
mv "$tmp" "$path"
chmod 600 "$path"
'''
proc = run(["sudo", "-n", "sh", "-c", script, "sh", HOST_DOCKER_ENV_PATH], content.encode("utf-8"))
if proc.returncode != 0:
raise RuntimeError("write host-docker env source failed: " + text(proc.stderr, 1000))
return "updated" if replaced else "created"
def docker(args):
proc = run(["docker", *args])
if proc.returncode == 0:
return proc
sudo_proc = run(["sudo", "-n", "docker", *args])
return sudo_proc if sudo_proc.returncode == 0 else proc
def runtime_logs(since, tail):
if RUNTIME_MODE == "host-docker":
return docker(["logs", f"--since={since}", f"--tail={tail}", HOST_DOCKER_APP_CONTAINER])
return kubectl(["-n", NAMESPACE, "logs", "deployment/sub2api", f"--since={since}", f"--tail={tail}"])
def kubectl(args, input_obj=None):
if isinstance(input_obj, str):
input_bytes = input_obj.encode("utf-8")
else:
input_bytes = input_obj
return run(["kubectl", *args], input_bytes)
def require_kubectl(args, input_obj=None, label="kubectl"):
proc = kubectl(args, input_obj)
if proc.returncode != 0:
raise RuntimeError(f"{label} failed: {text(proc.stderr, 1000)}")
return proc.stdout
def kube_json(args, label):
raw = require_kubectl([*args, "-o", "json"], label=label)
return json.loads(raw.decode("utf-8"))
def decode_secret_value(name, key):
if RUNTIME_MODE == "host-docker":
return read_host_env().get(key)
data = kube_json(["-n", NAMESPACE, "get", "secret", name], f"secret/{name}").get("data") or {}
if key not in data:
return None
return base64.b64decode(data[key]).decode("utf-8")
def get_config_value(name, key):
if RUNTIME_MODE == "host-docker":
return read_host_env().get(key)
data = kube_json(["-n", NAMESPACE, "get", "configmap", name], f"configmap/{name}").get("data") or {}
value = data.get(key)
return value if isinstance(value, str) and value else None
def select_app_pod():
if RUNTIME_MODE == "host-docker":
return HOST_DOCKER_APP_CONTAINER
pods = kube_json(["-n", NAMESPACE, "get", "pods", "-l", "app.kubernetes.io/name=sub2api"], "sub2api pods").get("items") or []
for pod in pods:
status = pod.get("status") or {}
if status.get("phase") != "Running":
continue
statuses = status.get("containerStatuses") or []
if statuses and all(item.get("ready") is True for item in statuses):
return pod["metadata"]["name"]
if pods:
return pods[0]["metadata"]["name"]
raise RuntimeError("sub2api app pod not found")
APP_POD = select_app_pod()
def parse_curl_output(proc):
stdout = proc.stdout.decode("utf-8", errors="replace")
marker = "\\n__HTTP_CODE__:"
pos = stdout.rfind(marker)
if pos < 0:
return {
"ok": False,
"httpStatus": 0,
"json": None,
"body": stdout,
"stderr": text(proc.stderr, 1000),
"transportExitCode": proc.returncode,
}
body = stdout[:pos]
status_text = stdout[pos + len(marker):].strip()
try:
http_status = int(status_text[-3:])
except ValueError:
http_status = 0
try:
parsed = json.loads(body) if body.strip() else None
except json.JSONDecodeError:
parsed = None
return {
"ok": proc.returncode == 0 and 200 <= http_status < 300,
"httpStatus": http_status,
"json": parsed,
"body": body,
"stderr": text(proc.stderr, 1000),
"transportExitCode": proc.returncode,
}
def utc_iso(offset_seconds=0):
return (datetime.now(timezone.utc) + timedelta(seconds=offset_seconds)).strftime("%Y-%m-%dT%H:%M:%SZ")
def normalize_runtime_base_url(value):
if not isinstance(value, str):
return None
value = value.strip().rstrip("/")
return value or None
def empty_to_none(value):
return value if isinstance(value, str) and value else None
def sentinel_quality_gate_enabled():
return TARGET_SENTINEL_ENABLED and (SENTINEL_CONFIG.get("monitor") or {}).get("enabled") is True and (SENTINEL_CONFIG.get("actions") or {}).get("enabled") is True
def account_notes_fingerprint(account):
notes = account.get("notes") if isinstance(account, dict) else None
if not isinstance(notes, str):
return None
match = re.search(r"fingerprint=([A-Za-z0-9_-]+)", notes)
return match.group(1) if match else None
def runtime_account_credentials(account):
credentials = account.get("credentials") if isinstance(account, dict) and isinstance(account.get("credentials"), dict) else {}
return credentials
def runtime_account_extra(account):
extra = account.get("extra") if isinstance(account, dict) and isinstance(account.get("extra"), dict) else {}
return extra
def sentinel_probe_change_reasons(current, profile):
if not isinstance(current, dict) or current.get("id") is None:
return ["created"]
credentials = runtime_account_credentials(current)
extra = runtime_account_extra(current)
expected_base_url = normalize_runtime_base_url(profile.get("baseUrl"))
runtime_base_url = normalize_runtime_base_url(credentials.get("base_url"))
expected_user_agent = empty_to_none(profile.get("upstreamUserAgent"))
runtime_user_agent = empty_to_none(credentials.get("user_agent"))
expected_ws_mode = empty_to_none(profile.get("openaiResponsesWebSocketsV2Mode"))
runtime_ws_mode = empty_to_none(extra.get("openai_apikey_responses_websockets_v2_mode"))
expected_trust_upstream = profile.get("trustUpstream") is True
runtime_trust_upstream = extra.get("unidesk_trust_upstream") is True
expected_protect = profile.get("sentinelProtect") if isinstance(profile.get("sentinelProtect"), dict) else {"enabled": False}
runtime_protect = extra.get("unidesk_sentinel_protect") if isinstance(extra.get("unidesk_sentinel_protect"), dict) else {"enabled": False}
reasons = []
if empty_to_none(extra.get("unidesk_codex_profile")) != profile.get("profile"):
reasons.append("profile")
if runtime_base_url != expected_base_url:
reasons.append("base-url")
if account_notes_fingerprint(current) != profile.get("apiKeyFingerprint"):
reasons.append("api-key-fingerprint")
if runtime_user_agent != expected_user_agent:
reasons.append("upstream-user-agent")
if runtime_ws_mode != expected_ws_mode:
reasons.append("responses-websockets-v2-mode")
if runtime_trust_upstream != expected_trust_upstream:
reasons.append("trust-upstream")
if runtime_protect != expected_protect:
reasons.append("sentinel-protect")
return reasons
def curl_api(method, path, bearer=None, payload=None):
body = b"" if payload is None else json.dumps(payload, separators=(",", ":")).encode("utf-8")
script = r'''
set -eu
method="$1"
url="$2"
token="\${3:-}"
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
cat > "$tmp"
if [ -n "$token" ]; then
if [ "$method" = "GET" ] && [ ! -s "$tmp" ]; then
curl -sS -w '\\n__HTTP_CODE__:%{http_code}' -X "$method" -H "Authorization: Bearer $token" "$url"
else
curl -sS -w '\\n__HTTP_CODE__:%{http_code}' -X "$method" -H 'Content-Type: application/json' -H "Authorization: Bearer $token" --data-binary @"$tmp" "$url"
fi
else
if [ "$method" = "GET" ] && [ ! -s "$tmp" ]; then
curl -sS -w '\\n__HTTP_CODE__:%{http_code}' -X "$method" "$url"
else
curl -sS -w '\\n__HTTP_CODE__:%{http_code}' -X "$method" -H 'Content-Type: application/json' --data-binary @"$tmp" "$url"
fi
fi
'''
if RUNTIME_MODE == "host-docker":
if not isinstance(HOST_DOCKER_APP_PORT, int):
raise RuntimeError("host-docker app port missing")
proc = run(["sh", "-c", script, "sh", method, f"http://127.0.0.1:{HOST_DOCKER_APP_PORT}{path}", bearer or ""], body)
else:
proc = run([
"kubectl", "-n", NAMESPACE, "exec", "-i", APP_POD,
"--", "sh", "-c", script, "sh", method, f"http://127.0.0.1:8080{path}", bearer or "",
], body)
return parse_curl_output(proc)
def envelope_data(parsed):
if isinstance(parsed, dict) and "data" in parsed:
return parsed.get("data")
return parsed
def ensure_success(resp, label):
parsed = resp.get("json")
code = parsed.get("code") if isinstance(parsed, dict) else None
if not resp.get("ok") or (code is not None and code != 0):
message = parsed.get("message") if isinstance(parsed, dict) else text(resp.get("body", ""), 500)
raise RuntimeError(f"{label} failed: http={resp.get('httpStatus')} message={message}")
return envelope_data(parsed)
def ensure_admin_compliance(token):
status_resp = curl_api("GET", "/api/v1/admin/compliance", bearer=token)
if status_resp.get("httpStatus") == 404:
return {
"ok": True,
"action": "not-supported-by-runtime",
"valuesPrinted": False,
}
status = ensure_success(status_resp, "get admin compliance")
if not isinstance(status, dict):
return {
"ok": True,
"action": "status-unstructured",
"valuesPrinted": False,
}
version = status.get("version")
required = status.get("required") is True
if not required:
return {
"ok": True,
"action": "already-acknowledged",
"version": version,
"requiredBefore": False,
"requiredAfter": False,
"phrasePrinted": False,
"valuesPrinted": False,
}
phrase = status.get("ack_phrase_zh") if isinstance(status.get("ack_phrase_zh"), str) and status.get("ack_phrase_zh") else status.get("ack_phrase_en")
language = "zh" if isinstance(status.get("ack_phrase_zh"), str) and status.get("ack_phrase_zh") else "en"
if not isinstance(phrase, str) or not phrase:
raise RuntimeError("admin compliance acknowledgement phrase missing")
accepted = ensure_success(
curl_api("POST", "/api/v1/admin/compliance/accept", bearer=token, payload={"phrase": phrase, "language": language}),
"accept admin compliance",
)
required_after = accepted.get("required") if isinstance(accepted, dict) else None
return {
"ok": required_after is False,
"action": "accepted",
"version": version,
"requiredBefore": True,
"requiredAfter": required_after,
"phrasePrinted": False,
"valuesPrinted": False,
}
def extract_items(data):
if isinstance(data, list):
return data
if isinstance(data, dict):
if isinstance(data.get("items"), list):
return data["items"]
for key in ("groups", "accounts", "api_keys", "keys"):
if isinstance(data.get(key), list):
return data[key]
return []
def find_access_token(data):
if isinstance(data, dict):
for key in ("access_token", "token"):
if isinstance(data.get(key), str) and data[key]:
return data[key]
for value in data.values():
token = find_access_token(value)
if token:
return token
return None
def login():
admin_email = get_config_value("sub2api-config", "ADMIN_EMAIL") or POOL_ADMIN_EMAIL_DEFAULT
admin_password = decode_secret_value(APP_SECRET_NAME, "ADMIN_PASSWORD")
if not admin_password:
raise RuntimeError("ADMIN_PASSWORD missing from sub2api-secrets")
data = ensure_success(curl_api("POST", "/api/v1/auth/login", payload={"email": admin_email, "password": admin_password}), "admin login")
token = find_access_token(data)
if not token:
raise RuntimeError("admin login response did not contain access_token")
compliance = ensure_admin_compliance(token)
if compliance.get("ok") is not True:
raise RuntimeError("admin compliance acknowledgement failed")
return admin_email, token, compliance
def group_payload():
return {
"name": POOL_GROUP_NAME,
"description": POOL_GROUP_DESCRIPTION,
"platform": "openai",
"rate_multiplier": 1,
"is_exclusive": False,
"subscription_type": "standard",
"allow_messages_dispatch": True,
"require_oauth_only": False,
"require_privacy_set": False,
"rpm_limit": 0,
}
def list_groups(token):
data = ensure_success(curl_api("GET", "/api/v1/admin/groups/all?platform=openai", bearer=token), "list groups")
return extract_items(data)
def ensure_group(token):
existing = next((item for item in list_groups(token) if item.get("name") == POOL_GROUP_NAME), None)
payload = group_payload()
if existing is None:
created = ensure_success(curl_api("POST", "/api/v1/admin/groups", bearer=token, payload=payload), "create group")
return created, "created"
group_id = existing.get("id")
if group_id is None:
raise RuntimeError("existing group has no id")
payload["status"] = "active"
updated = ensure_success(curl_api("PUT", f"/api/v1/admin/groups/{group_id}", bearer=token, payload=payload), "update group")
return updated if isinstance(updated, dict) else existing, "updated"
def list_accounts_for_group(token, group_id):
path = f"/api/v1/admin/accounts?group_id={group_id}&page=1&page_size=500&platform=openai"
data = ensure_success(curl_api("GET", path, bearer=token), f"list accounts for group {group_id}")
return extract_items(data)
def account_group_ids(token, account):
if not isinstance(account, dict) or account.get("id") is None:
return []
account_id = account.get("id")
account_name = account.get("name")
ids = []
for group in list_groups(token):
group_id = group.get("id") if isinstance(group, dict) else None
if group_id is None:
continue
members = list_accounts_for_group(token, group_id)
if any(item.get("id") == account_id or item.get("name") == account_name for item in members if isinstance(item, dict)):
ids.append(group_id)
return sorted(set(ids))
def list_accounts(token):
path = "/api/v1/admin/accounts?page=1&page_size=200&platform=openai&type=apikey&search=" + quote("unidesk-codex-")
data = ensure_success(curl_api("GET", path, bearer=token), "list accounts")
return extract_items(data)
def find_account_by_name(token, name):
path = "/api/v1/admin/accounts?page=1&page_size=50&platform=openai&search=" + quote(str(name))
data = ensure_success(curl_api("GET", path, bearer=token), "find account " + str(name))
for item in extract_items(data):
if isinstance(item, dict) and item.get("name") == name:
return item
return None
def list_proxy_candidates(token, search=""):
path = "/api/v1/admin/proxies?page=1&page_size=200"
if search:
path += "&search=" + quote(str(search))
data = ensure_success(curl_api("GET", path, bearer=token), "list proxies")
return extract_items(data)
def find_proxy_by_name(token, name):
for item in list_proxy_candidates(token, name):
if isinstance(item, dict) and item.get("name") == name:
return item
return None
def manual_binding_plan(binding, expected_kind):
if not isinstance(binding, dict):
return None
plan = binding.get("sourcePlan")
if not isinstance(plan, dict):
raise RuntimeError("manual account binding is missing resolved sourcePlan")
if plan.get("enabled") is not True:
raise RuntimeError(f"manual account binding source {plan.get('id')} is disabled")
if plan.get("kind") != expected_kind:
raise RuntimeError(f"manual account binding source {plan.get('id')} kind must be {expected_kind}")
return plan
def desired_manual_proxy_payload(protection):
binding = protection.get("proxyBinding") if isinstance(protection, dict) else None
if not isinstance(binding, dict) or binding.get("enabled") is not True:
return None
plan = manual_binding_plan(binding, "proxy")
if plan.get("provider") not in ("target-egress-proxy", "fixed-http-proxy"):
raise RuntimeError(f"manual account proxyBinding source {plan.get('id')} uses unsupported provider {plan.get('provider')}")
if plan.get("provider") == "fixed-http-proxy":
fixed_proxy = plan.get("fixedProxy")
if not isinstance(fixed_proxy, dict):
raise RuntimeError(f"manual account proxyBinding source {plan.get('id')} requires fixedProxy")
proxy_name = binding.get("proxyName")
protocol = fixed_proxy.get("protocol")
host = fixed_proxy.get("host")
port = fixed_proxy.get("port")
if not isinstance(proxy_name, str) or not proxy_name:
raise RuntimeError("manual account proxyBinding proxyName is missing")
if protocol != "http":
raise RuntimeError("fixed proxy protocol must be http")
if not isinstance(host, str) or not host:
raise RuntimeError("fixed proxy host is missing")
if not isinstance(port, int) or port <= 0:
raise RuntimeError("fixed proxy port is missing")
return {
"name": proxy_name,
"protocol": protocol,
"host": host,
"port": port,
"fallback_mode": "none",
"expiry_warn_days": 0,
}
target_proxy = plan.get("targetEgressProxy")
if not isinstance(target_proxy, dict):
raise RuntimeError(f"manual account proxyBinding source {plan.get('id')} requires an enabled target egressProxy")
service_name = target_proxy.get("serviceName")
listen_port = target_proxy.get("listenPort")
namespace = target_proxy.get("namespace") if isinstance(target_proxy.get("namespace"), str) and target_proxy.get("namespace") else NAMESPACE
proxy_name = binding.get("proxyName")
if not isinstance(service_name, str) or not service_name:
raise RuntimeError("target egressProxy serviceName is missing")
if not isinstance(listen_port, int) or listen_port <= 0:
raise RuntimeError("target egressProxy listenPort is missing")
if not isinstance(proxy_name, str) or not proxy_name:
raise RuntimeError("manual account proxyBinding proxyName is missing")
return {
"name": proxy_name,
"protocol": "http",
"host": f"{service_name}.{namespace}.svc.cluster.local",
"port": listen_port,
"fallback_mode": "none",
"expiry_warn_days": 0,
}
def proxy_needs_update(proxy, payload):
if not isinstance(proxy, dict):
return True
for key in ("name", "protocol", "host", "port", "fallback_mode", "expiry_warn_days"):
if proxy.get(key) != payload.get(key):
return True
return proxy.get("status") != "active"
def ensure_manual_proxy(token, payload):
current = find_proxy_by_name(token, payload["name"])
if current is None:
created = ensure_success(curl_api("POST", "/api/v1/admin/proxies", bearer=token, payload=payload), f"create proxy {payload['name']}")
return created, "created"
if proxy_needs_update(current, payload):
update_payload = dict(payload)
update_payload["status"] = "active"
updated = ensure_success(curl_api("PUT", f"/api/v1/admin/proxies/{current['id']}", bearer=token, payload=update_payload), f"update proxy {payload['name']}")
return updated if isinstance(updated, dict) else current, "updated"
return current, "unchanged"
def manual_proxy_status(token, account, protection):
payload = desired_manual_proxy_payload(protection)
if payload is None:
return {
"enabled": False,
"ok": True,
"action": "not-configured",
"valuesPrinted": False,
}
binding = protection.get("proxyBinding") if isinstance(protection, dict) else None
plan = manual_binding_plan(binding, "proxy")
proxy = find_proxy_by_name(token, payload["name"])
runtime_proxy = account.get("proxy") if isinstance(account, dict) and isinstance(account.get("proxy"), dict) else None
binding_aligned = (
isinstance(account, dict)
and isinstance(proxy, dict)
and account.get("proxy_id") == proxy.get("id")
)
return {
"enabled": True,
"ok": binding_aligned,
"action": "validate",
"source": plan.get("id"),
"sourceProvider": plan.get("provider"),
"expectedProxyName": payload["name"],
"expectedProtocol": payload["protocol"],
"expectedHost": payload["host"],
"expectedPort": payload["port"],
"proxyRecordExists": isinstance(proxy, dict),
"proxyId": proxy.get("id") if isinstance(proxy, dict) else None,
"runtimeProxyId": account.get("proxy_id") if isinstance(account, dict) else None,
"runtimeProxyName": runtime_proxy.get("name") if isinstance(runtime_proxy, dict) else None,
"bindingAligned": binding_aligned,
"valuesPrinted": False,
}
def ensure_manual_account_proxy_bindings(token):
items = []
for protection in MANUAL_ACCOUNT_PROTECTIONS:
if not isinstance(protection, dict):
continue
name = protection.get("accountName")
if not isinstance(name, str) or not name:
continue
account = find_account_by_name(token, name)
payload = desired_manual_proxy_payload(protection)
if payload is None:
items.append({
"accountName": name,
"enabled": False,
"action": "not-configured",
"ok": True,
"valuesPrinted": False,
})
continue
if not isinstance(account, dict):
items.append({
"accountName": name,
"enabled": True,
"action": "account-missing",
"ok": False,
"expectedProxyName": payload["name"],
"valuesPrinted": False,
})
continue
proxy, proxy_action = ensure_manual_proxy(token, payload)
binding = protection.get("proxyBinding") if isinstance(protection, dict) else None
plan = manual_binding_plan(binding, "proxy")
proxy_id = proxy.get("id") if isinstance(proxy, dict) else None
if proxy_id is None:
raise RuntimeError(f"proxy {payload['name']} has no id")
action = "unchanged"
if account.get("proxy_id") != proxy_id:
updated = ensure_success(curl_api("PUT", f"/api/v1/admin/accounts/{account['id']}", bearer=token, payload={"proxy_id": proxy_id}), f"bind manual account proxy {name}")
account = updated if isinstance(updated, dict) else account
action = "bound"
runtime_proxy = account.get("proxy") if isinstance(account.get("proxy"), dict) else None
items.append({
"accountName": name,
"accountId": account.get("id"),
"enabled": True,
"action": action,
"proxyAction": proxy_action,
"source": plan.get("id"),
"sourceProvider": plan.get("provider"),
"ok": account.get("proxy_id") == proxy_id,
"expectedProxyName": payload["name"],
"expectedProtocol": payload["protocol"],
"expectedHost": payload["host"],
"expectedPort": payload["port"],
"proxyId": proxy_id,
"runtimeProxyId": account.get("proxy_id"),
"runtimeProxyName": runtime_proxy.get("name") if isinstance(runtime_proxy, dict) else None,
"bindingAligned": account.get("proxy_id") == proxy_id,
"controlPolicy": "manual-protected: only proxy_id binding is YAML-controlled; credentials/status/schedulable are untouched",
"valuesPrinted": False,
})
return {
"ok": all(item.get("ok") is True for item in items),
"itemCount": len(items),
"items": items,
"valuesPrinted": False,
}
def manual_group_binding_plan(protection):
binding = protection.get("groupBinding") if isinstance(protection, dict) else None
if not isinstance(binding, dict) or binding.get("enabled") is not True:
return None
plan = manual_binding_plan(binding, "group")
if plan.get("provider") != "pool-group":
raise RuntimeError(f"manual account groupBinding source {plan.get('id')} uses unsupported provider {plan.get('provider')}")
return plan
def manual_group_binding_enabled(protection):
return manual_group_binding_plan(protection) is not None
def manual_group_status(token, account, protection, group_id):
plan = manual_group_binding_plan(protection)
if plan is None:
return {
"enabled": False,
"ok": True,
"action": "not-configured",
"valuesPrinted": False,
}
group_accounts = list_accounts_for_group(token, group_id)
account_id = account.get("id") if isinstance(account, dict) else None
account_name = account.get("name") if isinstance(account, dict) else None
binding_aligned = any(
item.get("id") == account_id or item.get("name") == account_name
for item in group_accounts
if isinstance(item, dict)
)
return {
"enabled": True,
"ok": binding_aligned,
"action": "validate",
"source": plan.get("id"),
"sourceProvider": plan.get("provider"),
"poolGroupName": POOL_GROUP_NAME,
"poolGroupId": group_id,
"bindingAligned": binding_aligned,
"valuesPrinted": False,
}
def ensure_manual_account_group_bindings(token, group_id):
items = []
for protection in MANUAL_ACCOUNT_PROTECTIONS:
if not isinstance(protection, dict):
continue
name = protection.get("accountName")
if not isinstance(name, str) or not name:
continue
if not manual_group_binding_enabled(protection):
items.append({
"accountName": name,
"enabled": False,
"action": "not-configured",
"ok": True,
"valuesPrinted": False,
})
continue
plan = manual_group_binding_plan(protection)
account = find_account_by_name(token, name)
if not isinstance(account, dict):
items.append({
"accountName": name,
"enabled": True,
"action": "account-missing",
"ok": False,
"poolGroupName": POOL_GROUP_NAME,
"poolGroupId": group_id,
"valuesPrinted": False,
})
continue
existing_group_ids = account_group_ids(token, account)
desired_group_ids = sorted(set(existing_group_ids + [group_id]))
action = "unchanged"
if group_id not in existing_group_ids:
updated = ensure_success(curl_api("PUT", f"/api/v1/admin/accounts/{account['id']}", bearer=token, payload={"group_ids": desired_group_ids}), f"bind manual account group {name}")
account = updated if isinstance(updated, dict) else account
action = "bound"
binding_aligned = any(
item.get("id") == account.get("id") or item.get("name") == name
for item in list_accounts_for_group(token, group_id)
if isinstance(item, dict)
)
items.append({
"accountName": name,
"accountId": account.get("id"),
"enabled": True,
"ok": binding_aligned,
"action": action,
"source": plan.get("id") if isinstance(plan, dict) else None,
"sourceProvider": plan.get("provider") if isinstance(plan, dict) else None,
"poolGroupName": POOL_GROUP_NAME,
"poolGroupId": group_id,
"previousGroupIds": existing_group_ids,
"desiredGroupIds": desired_group_ids,
"bindingAligned": binding_aligned,
"controlPolicy": "manual-protected: only pool group membership is YAML-controlled; credentials/status/schedulable are untouched and sentinel does not probe it",
"valuesPrinted": False,
})
return {
"ok": all(item.get("ok") is True for item in items),
"itemCount": len(items),
"items": items,
"valuesPrinted": False,
}
def manual_account_protection_status(token, group_id=None):
items = []
desired_names = set(EXPECTED_ACCOUNT_CAPACITIES.keys())
for protection in MANUAL_ACCOUNT_PROTECTIONS:
if not isinstance(protection, dict):
continue
name = protection.get("accountName")
if not isinstance(name, str) or not name:
continue
account = find_account_by_name(token, name)
extra = account.get("extra") if isinstance(account, dict) and isinstance(account.get("extra"), dict) else {}
proxy_status = manual_proxy_status(token, account, protection)
group_status = manual_group_status(token, account, protection, group_id) if group_id is not None else {"enabled": False, "ok": True, "action": "not-checked", "valuesPrinted": False}
items.append({
"accountName": name,
"reason": protection.get("reason") if isinstance(protection.get("reason"), str) else None,
"exists": isinstance(account, dict),
"accountId": account.get("id") if isinstance(account, dict) else None,
"status": account.get("status") if isinstance(account, dict) else None,
"schedulable": account.get("schedulable") if isinstance(account, dict) else None,
"inYamlProfiles": name in desired_names,
"runtimeMarkedUnideskManaged": extra.get("unidesk_managed") is True,
"proxyBinding": proxy_status,
"groupBinding": group_status,
"ok": proxy_status.get("ok") is True and group_status.get("ok") is True,
"controlPolicy": "manual-protected: no create/update/prune/probe/freeze; optional proxy_id and pool group membership binding only when configured",
"valuesPrinted": False,
})
return {
"ok": all(item.get("ok") is True for item in items),
"protectedCount": len(items),
"items": items,
"valuesPrinted": False,
}
def list_probe_accounts(token):
path = "/api/v1/admin/accounts?page=1&page_size=200&platform=openai&type=apikey&search=" + quote("unidesk-probe-")
data = ensure_success(curl_api("GET", path, bearer=token), "list probe accounts")
return [item for item in extract_items(data) if isinstance(item.get("name"), str) and item.get("name").startswith("unidesk-probe-")]
def list_probe_groups(token):
data = ensure_success(curl_api("GET", "/api/v1/admin/groups/all?platform=openai", bearer=token), "list groups")
return [item for item in extract_items(data) if isinstance(item.get("name"), str) and item.get("name").startswith("unidesk-probe-")]
def cleanup_probe_resources(token):
api_key_results = []
account_results = []
group_results = []
for item in list_user_keys(token):
name = item.get("name")
key_id = item.get("id")
if not isinstance(name, str) or not name.startswith("unidesk-probe-") or key_id is None:
continue
api_key_results.append(delete_probe_resource(token, "DELETE", f"/api/v1/keys/{key_id}", "api-key"))
for item in list_probe_accounts(token):
account_id = item.get("id")
if account_id is None:
continue
account_results.append(delete_probe_resource(token, "DELETE", f"/api/v1/admin/accounts/{account_id}", "account"))
for item in list_probe_groups(token):
group_id = item.get("id")
if group_id is None:
continue
group_results.append(delete_probe_resource(token, "DELETE", f"/api/v1/admin/groups/{group_id}", "group"))
return {
"ok": all(item.get("ok") is True for item in api_key_results + account_results + group_results),
"apiKeysDeleted": len(api_key_results),
"accountsDeleted": len(account_results),
"groupsDeleted": len(group_results),
"items": {
"apiKeys": api_key_results,
"accounts": account_results,
"groups": group_results,
},
"valuesPrinted": False,
}
def temp_unschedulable_credentials(profile):
credentials = profile.get("tempUnschedulableCredentials")
if not isinstance(credentials, dict):
credentials = {}
return normalize_temp_unschedulable_credentials(credentials)
def account_payload(profile, group_id):
extra = {
"openai_responses_mode": "force_responses",
"unidesk_codex_profile": profile["profile"],
"unidesk_managed": True,
"unidesk_trust_upstream": profile.get("trustUpstream") is True,
"unidesk_sentinel_protect": profile.get("sentinelProtect") if isinstance(profile.get("sentinelProtect"), dict) else {"enabled": False},
}
ws_mode = profile.get("openaiResponsesWebSocketsV2Mode")
if ws_mode:
extra["openai_apikey_responses_websockets_v2_mode"] = ws_mode
extra["openai_apikey_responses_websockets_v2_enabled"] = ws_mode != "off"
credentials = {
"api_key": profile["apiKey"],
"base_url": profile["baseUrl"],
}
upstream_user_agent = profile.get("upstreamUserAgent")
if upstream_user_agent:
credentials["user_agent"] = upstream_user_agent
temp_unschedulable = temp_unschedulable_credentials(profile)
if temp_unschedulable["enabled"]:
credentials["temp_unschedulable_enabled"] = True
credentials["temp_unschedulable_rules"] = temp_unschedulable["rules"]
return {
"name": profile["accountName"],
"notes": f"UniDesk-managed Codex profile {profile['profile']} from {profile['configFile']} and {profile['authFile']}; secret source={profile['apiKeySource']}; fingerprint={profile['apiKeyFingerprint']}.",
"platform": "openai",
"type": "apikey",
"credentials": credentials,
"extra": extra,
"concurrency": int(profile.get("capacity", 5) or 5),
"priority": int(profile.get("priority", POOL_DEFAULT_ACCOUNT_PRIORITY) or POOL_DEFAULT_ACCOUNT_PRIORITY),
"rate_multiplier": 1,
"load_factor": int(profile.get("loadFactor", POOL_DEFAULT_ACCOUNT_LOAD_FACTOR) or POOL_DEFAULT_ACCOUNT_LOAD_FACTOR),
"group_ids": [group_id],
"confirm_mixed_channel_risk": True,
**({"proxy_id": int(profile["proxyId"])} if profile.get("proxyId") is not None else {}),
}
def planned_sentinel_account_results(profiles, existing_accounts):
existing = {item.get("name"): item for item in existing_accounts if isinstance(item, dict)}
results = []
for profile in profiles:
change_reasons = sentinel_probe_change_reasons(existing.get(profile["accountName"]), profile)
quality_gate_required = sentinel_quality_gate_enabled() and len(change_reasons) > 0
results.append({
"profile": profile["profile"],
"accountName": profile["accountName"],
"profileConfig": {
"accountName": profile["accountName"],
"trustUpstream": profile.get("trustUpstream") is True,
"sentinelProtect": profile.get("sentinelProtect") if isinstance(profile.get("sentinelProtect"), dict) else {"enabled": False},
},
"sentinelProbeConfigFingerprint": profile.get("sentinelProbeConfigFingerprint"),
"sentinelProbeRequired": quality_gate_required,
"sentinelChangeReasons": change_reasons if quality_gate_required else [],
"sentinelProbePending": quality_gate_required,
"sentinelDefaultFrozen": False,
"valuesPrinted": False,
})
return results
def ensure_accounts(token, profiles, group_id, prune_removed=False, protected_frozen_names=None, existing_accounts=None):
if not isinstance(protected_frozen_names, set):
protected_frozen_names = set()
if not isinstance(existing_accounts, list):
existing_accounts = list_accounts(token)
existing = {item.get("name"): item for item in existing_accounts}
desired_names = {profile["accountName"] for profile in profiles}
results = []
for profile in profiles:
payload = account_payload(profile, group_id)
current = existing.get(profile["accountName"])
change_reasons = sentinel_probe_change_reasons(current, profile)
quality_gate_required = sentinel_quality_gate_enabled() and len(change_reasons) > 0
keep_frozen = profile["accountName"] in protected_frozen_names
if current and current.get("id") is not None:
account_id = current["id"]
update_payload = dict(payload)
update_payload.pop("platform", None)
update_payload["status"] = "active"
data = ensure_success(curl_api("PUT", f"/api/v1/admin/accounts/{account_id}", bearer=token, payload=update_payload), f"update account {profile['accountName']}")
action = "updated"
else:
data = ensure_success(curl_api("POST", "/api/v1/admin/accounts", bearer=token, payload=payload), f"create account {profile['accountName']}")
action = "created"
results.append({
"profile": profile["profile"],
"accountName": profile["accountName"],
"profileConfig": {
"accountName": profile["accountName"],
"trustUpstream": profile.get("trustUpstream") is True,
"sentinelProtect": profile.get("sentinelProtect") if isinstance(profile.get("sentinelProtect"), dict) else {"enabled": False},
},
"accountId": data.get("id") if isinstance(data, dict) else None,
"action": action,
"baseUrl": profile["baseUrl"],
"apiKeySource": profile["apiKeySource"],
"apiKeyFingerprint": profile["apiKeyFingerprint"],
"sentinelProbeConfigFingerprint": profile.get("sentinelProbeConfigFingerprint"),
"sentinelProbeRequired": quality_gate_required,
"sentinelChangeReasons": change_reasons if quality_gate_required else [],
"sentinelProbePending": quality_gate_required,
"sentinelDefaultFrozen": False,
"sentinelFreezeProtected": keep_frozen,
"schedulableControl": "sentinel-marker-restore",
"openaiResponsesWebSocketsV2Mode": profile.get("openaiResponsesWebSocketsV2Mode"),
"trustUpstream": profile.get("trustUpstream") is True,
"priority": int(profile.get("priority", POOL_DEFAULT_ACCOUNT_PRIORITY) or POOL_DEFAULT_ACCOUNT_PRIORITY),
"capacity": int(profile.get("capacity", 5) or 5),
"loadFactor": int(profile.get("loadFactor", POOL_DEFAULT_ACCOUNT_LOAD_FACTOR) or POOL_DEFAULT_ACCOUNT_LOAD_FACTOR),
"runtimeConcurrency": data.get("concurrency") if isinstance(data, dict) else None,
"runtimeLoadFactor": data.get("load_factor") if isinstance(data, dict) else None,
"runtimeSchedulable": data.get("schedulable") if isinstance(data, dict) else None,
"tempUnschedulableConfigured": bool(payload["credentials"].get("temp_unschedulable_enabled")),
"tempUnschedulableRuleCount": len(payload["credentials"].get("temp_unschedulable_rules") or []),
"upstreamUserAgentConfigured": bool(profile.get("upstreamUserAgent")),
"valuesPrinted": False,
})
prune_results = prune_removed_accounts(token, existing_accounts, desired_names) if prune_removed else []
return results, prune_results
def prune_removed_accounts(token, existing_accounts, desired_names):
results = []
for account in existing_accounts:
name = account.get("name")
account_id = account.get("id")
extra = account.get("extra") if isinstance(account.get("extra"), dict) else {}
if not isinstance(name, str) or not name.startswith("unidesk-codex-"):
continue
if extra.get("unidesk_managed") is not True:
continue
if name in desired_names:
continue
if account_id is None:
raise RuntimeError(f"removed account {name} has no id")
ensure_success(curl_api("DELETE", f"/api/v1/admin/accounts/{account_id}", bearer=token), f"delete removed account {name}")
results.append({
"accountName": name,
"accountId": account_id,
"profile": extra.get("unidesk_codex_profile"),
"action": "deleted",
"reason": "removed-from-yaml",
"valuesPrinted": False,
})
return results
def generate_api_key():
alphabet = string.ascii_letters + string.digits
return "sk-unidesk-codex-" + "".join(secrets.choice(alphabet) for _ in range(48))
def existing_sub2api_pool_api_key(token):
try:
existing = next((item for item in list_user_keys(token) if item.get("name") == POOL_API_KEY_NAME), None)
except Exception:
return None
if not isinstance(existing, dict):
return None
key = existing.get("key")
return key if isinstance(key, str) and key else None
def ensure_api_key_secret(group_id, token):
existing = None
try:
existing = decode_secret_value(POOL_API_KEY_SECRET_NAME, POOL_API_KEY_SECRET_KEY)
except Exception:
existing = None
reused = None if existing else existing_sub2api_pool_api_key(token)
api_key = existing or reused or generate_api_key()
if existing:
secret_action = "kept-existing"
elif reused:
secret_action = "reused-existing-sub2api-key"
else:
secret_action = "created"
if RUNTIME_MODE == "host-docker":
env_action = "kept-existing" if existing else write_host_env_value(POOL_API_KEY_SECRET_KEY, api_key)
return api_key, secret_action, f"host-docker-env:{env_action};source={HOST_DOCKER_ENV_PATH};key={POOL_API_KEY_SECRET_KEY};valuesPrinted=false"
manifest = {
"apiVersion": "v1",
"kind": "Secret",
"metadata": {
"name": POOL_API_KEY_SECRET_NAME,
"namespace": NAMESPACE,
"labels": {
"app.kubernetes.io/name": "sub2api",
"app.kubernetes.io/part-of": "platform-infra",
"app.kubernetes.io/managed-by": "unidesk",
"unidesk.ai/secret-purpose": "sub2api-codex-pool-api-key",
},
},
"type": "Opaque",
"stringData": {
POOL_API_KEY_SECRET_KEY: api_key,
"GROUP_ID": str(group_id),
"GROUP_NAME": POOL_GROUP_NAME,
"SERVICE_DNS": SERVICE_DNS,
},
}
proc = kubectl(["apply", "--server-side", "--force-conflicts", f"--field-manager={FIELD_MANAGER}", "-f", "-"], json.dumps(manifest))
if proc.returncode != 0:
raise RuntimeError(f"apply API key secret failed: {text(proc.stderr, 1000)}")
return api_key, secret_action, text(proc.stdout, 1000)
def pool_api_key_secret_location():
if RUNTIME_MODE == "host-docker":
return f"{HOST_DOCKER_ENV_PATH}.{POOL_API_KEY_SECRET_KEY}"
return f"{NAMESPACE}/{POOL_API_KEY_SECRET_NAME}.{POOL_API_KEY_SECRET_KEY}"
def apply_sentinel_manifest(manifest):
if not TARGET_SENTINEL_ENABLED:
return {
"ok": True,
"action": "skipped-target-disabled",
"valuesPrinted": False,
}
if not isinstance(manifest, str) or not manifest.strip():
return {
"ok": False,
"action": "missing-manifest",
"valuesPrinted": False,
}
proc = kubectl(["apply", "--server-side", "--force-conflicts", f"--field-manager={FIELD_MANAGER}", "-f", "-"], manifest)
if proc.returncode != 0:
return {
"ok": False,
"action": "apply-failed",
"stdoutTail": text(proc.stdout, 2000),
"stderrTail": text(proc.stderr, 4000),
"valuesPrinted": False,
}
status = sentinel_runtime_status()
return {
"ok": status.get("ok") is True,
"action": "applied",
"stdoutTail": text(proc.stdout, 2000),
"runtime": status,
"valuesPrinted": False,
}
def safe_kube_json(args, label):
proc = kubectl([*args, "-o", "json"])
if proc.returncode != 0:
return None, {"label": label, "exitCode": proc.returncode, "stderrTail": text(proc.stderr, 1000)}
try:
return json.loads(proc.stdout.decode("utf-8")), None
except Exception as exc:
return None, {"label": label, "exitCode": proc.returncode, "error": str(exc), "stdoutTail": text(proc.stdout, 1000)}
def sentinel_runtime_status():
if not TARGET_SENTINEL_ENABLED:
return {
"ok": True,
"action": "skipped-target-disabled",
"desired": {
"monitorEnabled": SENTINEL_CONFIG.get("monitor", {}).get("enabled"),
"actionsEnabled": SENTINEL_CONFIG.get("actions", {}).get("enabled"),
},
"valuesPrinted": False,
}
cfg = SENTINEL_CONFIG
cronjob_name = cfg.get("cronJobName")
secret_name = cfg.get("credentialsSecretName")
configmap_name = cfg.get("configMapName")
state_name = cfg.get("stateConfigMapName")
cronjob, cronjob_error = safe_kube_json(["-n", NAMESPACE, "get", "cronjob", cronjob_name], f"cronjob/{cronjob_name}")
secret, secret_error = safe_kube_json(["-n", NAMESPACE, "get", "secret", secret_name], f"secret/{secret_name}")
configmap, configmap_error = safe_kube_json(["-n", NAMESPACE, "get", "configmap", configmap_name], f"configmap/{configmap_name}")
state_cm, state_error = safe_kube_json(["-n", NAMESPACE, "get", "configmap", state_name], f"configmap/{state_name}")
state = None
if isinstance(state_cm, dict):
raw_state = (state_cm.get("data") or {}).get("state.json")
if isinstance(raw_state, str) and raw_state:
try:
state = json.loads(raw_state)
except Exception as exc:
state = {"parseError": str(exc)}
accounts = (state.get("accounts") or {}) if isinstance(state, dict) else {}
quarantined = []
recent_accounts = []
for name, account_state in accounts.items():
if not isinstance(account_state, dict):
continue
quarantine = account_state.get("quarantine")
if isinstance(quarantine, dict) and quarantine.get("active") is True:
quarantined.append({
"accountName": name,
"until": quarantine.get("until"),
"applied": quarantine.get("applied"),
"reason": quarantine.get("reason"),
"failureKind": quarantine.get("failureKind"),
"errorDetails": quarantine.get("errorDetails"),
"intervalMinutes": quarantine.get("intervalMinutes"),
"sentinelProtect": quarantine.get("sentinelProtect"),
})
last_probe = account_state.get("lastProbe")
if isinstance(last_probe, dict):
recent_accounts.append({
"accountName": name,
"lastProbeAt": account_state.get("lastProbeAt"),
"lastStatus": account_state.get("lastStatus"),
"nextProbeAfter": account_state.get("nextProbeAfter"),
"ok": last_probe.get("ok"),
"purpose": last_probe.get("purpose"),
"httpStatus": last_probe.get("httpStatus"),
"durationMs": last_probe.get("durationMs"),
"markerMatched": last_probe.get("markerMatched"),
"sentinelProtect": last_probe.get("sentinelProtect") or account_state.get("sentinelProtectConfig"),
"outputHash": last_probe.get("outputHash"),
"outputPreview": last_probe.get("outputPreview"),
"responseBodyHash": last_probe.get("responseBodyHash"),
"responseBodyPreview": last_probe.get("responseBodyPreview"),
"error": last_probe.get("error"),
"errorDetails": last_probe.get("errorDetails"),
"usage": last_probe.get("usage"),
"failureKind": last_probe.get("failureKind"),
"requestShape": last_probe.get("requestShape"),
"action": last_probe.get("action"),
})
recent_accounts.sort(key=lambda item: item.get("lastProbeAt") or "")
last_run = state.get("lastRun") if isinstance(state, dict) else None
cronjob_spec = cronjob.get("spec") if isinstance(cronjob, dict) else {}
secret_data = secret.get("data") if isinstance(secret, dict) else {}
configmap_data = configmap.get("data") if isinstance(configmap, dict) else {}
ok = cronjob is not None and secret is not None and configmap is not None
return {
"ok": ok,
"desired": {
"monitorEnabled": cfg.get("monitor", {}).get("enabled"),
"actionsEnabled": cfg.get("actions", {}).get("enabled"),
"schedule": cfg.get("schedule"),
"cronJobName": cronjob_name,
"configMapName": configmap_name,
"credentialsSecretName": secret_name,
"stateConfigMapName": state_name,
},
"cronJob": {
"exists": cronjob is not None,
"schedule": cronjob_spec.get("schedule") if isinstance(cronjob_spec, dict) else None,
"suspend": cronjob_spec.get("suspend") if isinstance(cronjob_spec, dict) else None,
"lastScheduleTime": (cronjob.get("status") or {}).get("lastScheduleTime") if isinstance(cronjob, dict) else None,
"active": len((cronjob.get("status") or {}).get("active") or []) if isinstance(cronjob, dict) else None,
"error": cronjob_error,
},
"secret": {
"exists": secret is not None,
"profileSecretPresent": isinstance(secret_data, dict) and "profiles.json" in secret_data,
"valuesPrinted": False,
"error": secret_error,
},
"configMap": {
"exists": configmap is not None,
"configPresent": isinstance(configmap_data, dict) and "config.json" in configmap_data,
"runnerPresent": isinstance(configmap_data, dict) and "sentinel.py" in configmap_data,
"error": configmap_error,
},
"state": {
"exists": state_cm is not None,
"accountCount": len(accounts),
"quarantinedCount": len(quarantined),
"quarantined": quarantined[-10:],
"recentAccounts": recent_accounts[-12:],
"lastRun": last_run,
"error": state_error,
},
"valuesPrinted": False,
}
def parse_epoch_z(value):
if not isinstance(value, str) or not value:
return None
try:
return datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp()
except Exception:
return None
def sentinel_state_object():
if not TARGET_SENTINEL_ENABLED:
return None, None
state_name = SENTINEL_CONFIG.get("stateConfigMapName")
if not state_name:
return None, None
obj, err = safe_kube_json(["-n", NAMESPACE, "get", "configmap", state_name], f"configmap/{state_name}")
if not isinstance(obj, dict):
return None, None
raw_state = (obj.get("data") or {}).get("state.json")
if not isinstance(raw_state, str) or not raw_state:
return obj, None
try:
return obj, json.loads(raw_state)
except Exception:
return obj, None
def active_sentinel_quarantine_names():
if not TARGET_SENTINEL_ENABLED:
return set()
_, state = sentinel_state_object()
if not isinstance(state, dict):
return set()
accounts_state = state.get("accounts") if isinstance(state.get("accounts"), dict) else {}
names = set()
for name, account_state in accounts_state.items():
if not isinstance(name, str) or not isinstance(account_state, dict):
continue
quarantine = account_state.get("quarantine")
if isinstance(quarantine, dict) and quarantine.get("active") is True and quarantine.get("applied") is True:
names.add(name)
return names
def default_sentinel_state():
return {"version": 1, "accounts": {}, "ledger": {}, "history": []}
def clamp_sentinel_freezes_for_config(state, now):
freeze_config = SENTINEL_CONFIG.get("freeze") if isinstance(SENTINEL_CONFIG.get("freeze"), dict) else {}
try:
max_interval = int(freeze_config.get("maxTtlMinutes") or 10)
except Exception:
max_interval = 10
accounts_state = state.get("accounts") if isinstance(state.get("accounts"), dict) else {}
now_epoch = time.time()
items = []
for name, account_state in accounts_state.items():
if not isinstance(name, str) or not isinstance(account_state, dict):
continue
quarantine = account_state.get("quarantine")
if not isinstance(quarantine, dict) or quarantine.get("active") is not True or quarantine.get("applied") is not True:
continue
try:
interval = int(quarantine.get("intervalMinutes") or 0)
except Exception:
interval = 0
until_epoch = parse_epoch_z(quarantine.get("until"))
old_until = quarantine.get("until")
if interval <= max_interval and (until_epoch is None or until_epoch <= now_epoch + max_interval * 60):
continue
quarantine["previousIntervalMinutes"] = interval
quarantine["intervalMinutes"] = max_interval
quarantine["until"] = now
quarantine["clampedAt"] = now
quarantine["clampedBy"] = "sync-freeze-max-ttl"
account_state["nextProbeAfter"] = now
items.append({
"accountName": name,
"previousIntervalMinutes": interval,
"maxIntervalMinutes": max_interval,
"previousUntil": old_until,
"nextProbeAfter": now,
})
return items
def parse_iso_epoch(value):
if not isinstance(value, str) or not value:
return None
try:
return datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp()
except Exception:
return None
def profile_success_max_interval(profile):
cadence = SENTINEL_CONFIG.get("cadence") if isinstance(SENTINEL_CONFIG.get("cadence"), dict) else {}
legacy = cadence.get("successMaxIntervalMinutes")
if legacy is None:
legacy = cadence.get("trustedSuccessMaxIntervalMinutes") or cadence.get("untrustedSuccessMaxIntervalMinutes") or 1
if profile.get("trustUpstream") is True:
value = cadence.get("trustedSuccessMaxIntervalMinutes") or legacy
else:
value = cadence.get("untrustedSuccessMaxIntervalMinutes") or legacy
try:
return int(value)
except Exception:
return int(legacy)
def clamp_sentinel_success_cadence_for_config(state, profiles, now):
accounts_state = state.get("accounts") if isinstance(state.get("accounts"), dict) else {}
profile_map = {item.get("accountName"): item for item in profiles if isinstance(item, dict) and isinstance(item.get("accountName"), str)}
now_epoch = time.time()
items = []
for name, profile in profile_map.items():
account_state = accounts_state.get(name)
if not isinstance(account_state, dict):
continue
quarantine = account_state.get("quarantine")
if isinstance(quarantine, dict) and quarantine.get("active") is True:
account_state["trustUpstream"] = profile.get("trustUpstream") is True
account_state["sentinelProtectConfig"] = profile.get("sentinelProtect") if isinstance(profile.get("sentinelProtect"), dict) else {"enabled": False}
account_state["successMaxIntervalMinutes"] = profile_success_max_interval(profile)
continue
try:
interval = int(account_state.get("successIntervalMinutes") or 0)
except Exception:
interval = 0
next_epoch = parse_iso_epoch(account_state.get("nextProbeAfter"))
max_interval = profile_success_max_interval(profile)
account_state["trustUpstream"] = profile.get("trustUpstream") is True
account_state["sentinelProtectConfig"] = profile.get("sentinelProtect") if isinstance(profile.get("sentinelProtect"), dict) else {"enabled": False}
account_state["successMaxIntervalMinutes"] = max_interval
if interval <= max_interval and (next_epoch is None or next_epoch <= now_epoch + max_interval * 60):
continue
old_next = account_state.get("nextProbeAfter")
account_state["previousSuccessIntervalMinutes"] = interval
account_state["successIntervalMinutes"] = min(interval, max_interval) if interval > 0 else interval
account_state["nextProbeAfter"] = now
account_state["cadenceClampedAt"] = now
account_state["cadenceClampedBy"] = "sync-success-max-interval"
items.append({
"accountName": name,
"trustUpstream": profile.get("trustUpstream") is True,
"previousSuccessIntervalMinutes": interval,
"maxIntervalMinutes": max_interval,
"previousNextProbeAfter": old_next,
"nextProbeAfter": now,
})
return items
def update_sentinel_state_configmap(obj, state):
state_name = SENTINEL_CONFIG.get("stateConfigMapName")
if not state_name:
return {"ok": False, "reason": "state-configmap-missing"}
state_json = json.dumps(state, ensure_ascii=False, indent=2)
manifest = {
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {
"name": state_name,
"namespace": NAMESPACE,
"labels": {
"app.kubernetes.io/name": SERVICE_NAME,
"app.kubernetes.io/component": "account-sentinel",
"app.kubernetes.io/managed-by": "unidesk-platform-infra",
},
},
"data": {"state.json": state_json},
}
proc = kubectl(["apply", "--server-side", "--force-conflicts", f"--field-manager={FIELD_MANAGER}", "-f", "-"], json.dumps(manifest))
action = "applied" if isinstance(obj, dict) else "created"
if proc.returncode != 0:
return {"ok": False, "reason": f"{action}-failed", "stderrTail": text(proc.stderr, 2000)}
return {"ok": True, "action": action}
def ensure_sentinel_state_for_sync(account_results, pending_only=False):
if not sentinel_quality_gate_enabled():
return {"ok": True, "skipped": True, "reason": "sentinel-quality-gate-disabled", "changedCount": 0, "items": [], "valuesPrinted": False}
state_obj, state = sentinel_state_object()
if not isinstance(state, dict):
state = default_sentinel_state()
state.setdefault("version", 1)
accounts_state = state.setdefault("accounts", {})
if not isinstance(accounts_state, dict):
accounts_state = {}
state["accounts"] = accounts_state
now = utc_iso()
items = []
clamped_items = [] if pending_only else clamp_sentinel_freezes_for_config(state, now)
cadence_clamped_items = [] if pending_only else clamp_sentinel_success_cadence_for_config(state, [item.get("profileConfig") for item in account_results if isinstance(item.get("profileConfig"), dict)], now)
changed_count = 0
fingerprint_only_count = 0
for item in account_results:
name = item.get("accountName")
if not isinstance(name, str) or not name:
continue
account_state = accounts_state.setdefault(name, {})
if not isinstance(account_state, dict):
account_state = {}
accounts_state[name] = account_state
fingerprint_value = item.get("sentinelProbeConfigFingerprint")
if isinstance(fingerprint_value, str) and fingerprint_value:
account_state["probeConfigFingerprint"] = fingerprint_value
if item.get("sentinelProbeRequired") is not True:
fingerprint_only_count += 1
continue
changed_count += 1
reasons = item.get("sentinelChangeReasons") if isinstance(item.get("sentinelChangeReasons"), list) else []
quarantine = account_state.get("quarantine") if isinstance(account_state.get("quarantine"), dict) else None
if not (isinstance(quarantine, dict) and quarantine.get("active") is True):
account_state["quarantine"] = {
"active": False,
"reason": "yaml-account-change-pending-sentinel-probe",
"lastPendingAt": now,
"changeReasons": reasons,
}
account_state["nextProbeAfter"] = now
account_state["successStreak"] = 0
account_state["successIntervalMinutes"] = 0
profile_config = item.get("profileConfig") if isinstance(item.get("profileConfig"), dict) else {}
account_state["trustUpstream"] = profile_config.get("trustUpstream") is True
account_state["sentinelProtectConfig"] = profile_config.get("sentinelProtect") if isinstance(profile_config.get("sentinelProtect"), dict) else {"enabled": False}
account_state["successMaxIntervalMinutes"] = profile_success_max_interval(profile_config)
account_state["lastStatus"] = "pending-sentinel-quality-gate"
account_state["qualityGate"] = {
"pending": True,
"reason": "yaml-account-change",
"changeReasons": reasons,
"markedAt": now,
"pendingOnly": pending_only,
"defaultFrozen": False,
}
items.append({"accountName": name, "changeReasons": reasons, "nextProbeAfter": now, "defaultFrozen": False, "defaultSchedulable": True, "pendingOnly": pending_only})
if changed_count <= 0 and len(clamped_items) <= 0 and len(cadence_clamped_items) <= 0:
return {"ok": True, "skipped": False, "reason": "no-new-or-changed-accounts", "changedCount": 0, "fingerprintOnlyCount": fingerprint_only_count, "clampedCount": 0, "cadenceClampedCount": 0, "items": [], "valuesPrinted": False}
update = update_sentinel_state_configmap(state_obj, state)
if pending_only and changed_count > 0:
reason = "new-or-changed-accounts-pending-probe-prepared-default-schedulable"
elif changed_count > 0 and (len(clamped_items) > 0 or len(cadence_clamped_items) > 0):
reason = "new-or-changed-accounts-default-schedulable-and-sentinel-cadence-clamped"
elif changed_count > 0:
reason = "new-or-changed-accounts-default-schedulable"
elif len(cadence_clamped_items) > 0:
reason = "success-cadence-clamped-to-current-config"
else:
reason = "freeze-backoff-clamped-to-current-config"
return {
"ok": update.get("ok") is True,
"skipped": False,
"reason": reason,
"changedCount": changed_count,
"fingerprintOnlyCount": fingerprint_only_count,
"clampedCount": len(clamped_items),
"cadenceClampedCount": len(cadence_clamped_items),
"pendingOnly": pending_only,
"items": items,
"clampedItems": clamped_items,
"cadenceClampedItems": cadence_clamped_items,
"update": update,
"valuesPrinted": False,
}
def sentinel_state_summary():
_, state = sentinel_state_object()
if not isinstance(state, dict):
return {"exists": False, "valuesPrinted": False}
accounts = state.get("accounts") if isinstance(state.get("accounts"), dict) else {}
quarantined = []
recent_accounts = []
for name, account_state in accounts.items():
if not isinstance(account_state, dict):
continue
quarantine = account_state.get("quarantine")
if isinstance(quarantine, dict) and quarantine.get("active") is True:
quarantined.append({
"accountName": name,
"until": quarantine.get("until"),
"applied": quarantine.get("applied"),
"reason": quarantine.get("reason"),
"failureKind": quarantine.get("failureKind"),
"errorDetails": quarantine.get("errorDetails"),
"intervalMinutes": quarantine.get("intervalMinutes"),
"sentinelProtect": quarantine.get("sentinelProtect"),
})
last_probe = account_state.get("lastProbe")
if isinstance(last_probe, dict):
recent_accounts.append({
"accountName": name,
"lastProbeAt": account_state.get("lastProbeAt"),
"lastStatus": account_state.get("lastStatus"),
"nextProbeAfter": account_state.get("nextProbeAfter"),
"ok": last_probe.get("ok"),
"purpose": last_probe.get("purpose"),
"httpStatus": last_probe.get("httpStatus"),
"durationMs": last_probe.get("durationMs"),
"markerMatched": last_probe.get("markerMatched"),
"sentinelProtect": last_probe.get("sentinelProtect") or account_state.get("sentinelProtectConfig"),
"usage": last_probe.get("usage"),
"responseBodyHash": last_probe.get("responseBodyHash"),
"responseBodyPreview": last_probe.get("responseBodyPreview"),
"error": last_probe.get("error"),
"errorDetails": last_probe.get("errorDetails"),
"failureKind": last_probe.get("failureKind"),
"requestShape": last_probe.get("requestShape"),
"action": last_probe.get("action"),
})
recent_accounts.sort(key=lambda item: item.get("lastProbeAt") or "")
return {
"exists": True,
"accountCount": len(accounts),
"quarantinedCount": len(quarantined),
"quarantined": quarantined[-10:],
"recentAccounts": recent_accounts[-12:],
"lastRun": state.get("lastRun"),
"valuesPrinted": False,
}
def reassert_sentinel_freezes_after_sync(token):
if not TARGET_SENTINEL_ENABLED:
return {"ok": True, "skipped": True, "reason": "target-disabled", "items": [], "valuesPrinted": False}
if (SENTINEL_CONFIG.get("actions") or {}).get("enabled") is not True:
return {"ok": True, "skipped": True, "reason": "actions-disabled", "items": [], "valuesPrinted": False}
_, state = sentinel_state_object()
if not isinstance(state, dict):
return {"ok": True, "skipped": True, "reason": "state-missing", "items": [], "valuesPrinted": False}
accounts_state = state.get("accounts") if isinstance(state.get("accounts"), dict) else {}
accounts = list_accounts(token)
by_name = {item.get("name"): item for item in accounts if isinstance(item.get("name"), str)}
items = []
for name, account_state in accounts_state.items():
if not isinstance(account_state, dict):
continue
quarantine = account_state.get("quarantine")
if not isinstance(quarantine, dict) or quarantine.get("active") is not True or quarantine.get("applied") is not True:
continue
account = by_name.get(name)
if not account or account.get("id") is None:
items.append({"accountName": name, "ok": False, "reason": "account-not-found"})
continue
try:
ensure_success(
curl_api("POST", f"/api/v1/admin/accounts/{account['id']}/schedulable", bearer=token, payload={"schedulable": False}),
f"reassert sentinel freeze for {name}",
)
items.append({"accountName": name, "accountId": account.get("id"), "ok": True, "until": quarantine.get("until")})
except Exception as exc:
items.append({"accountName": name, "accountId": account.get("id"), "ok": False, "error": str(exc)})
return {
"ok": all(item.get("ok") is True for item in items),
"skipped": False,
"items": items,
"valuesPrinted": False,
}
def list_user_keys(token):
data = ensure_success(curl_api("GET", "/api/v1/keys?page=1&page_size=200", bearer=token), "list user keys")
return extract_items(data)
def ensure_sub2api_api_key(token, api_key, group_id):
keys = list_user_keys(token)
existing = next((item for item in keys if item.get("key") == api_key), None)
action = "kept-existing"
if existing is None:
existing = next((item for item in keys if item.get("name") == POOL_API_KEY_NAME), None)
if existing is None or existing.get("key") != api_key:
payload = {
"name": POOL_API_KEY_NAME,
"group_id": group_id,
"custom_key": api_key,
"quota": 0,
"rate_limit_5h": 0,
"rate_limit_1d": 0,
"rate_limit_7d": 0,
}
created = ensure_success(curl_api("POST", "/api/v1/keys", bearer=token, payload=payload), "create pool API key")
existing = created if isinstance(created, dict) else existing
action = "created"
elif existing.get("id") is not None and existing.get("group_id") != group_id:
updated = ensure_success(curl_api("PUT", f"/api/v1/keys/{existing['id']}", bearer=token, payload={"name": POOL_API_KEY_NAME, "group_id": group_id}), "update pool API key group")
existing = updated if isinstance(updated, dict) else existing
action = "updated-group"
return {
"action": action,
"id": existing.get("id") if isinstance(existing, dict) else None,
"name": existing.get("name") if isinstance(existing, dict) else POOL_API_KEY_NAME,
"groupId": existing.get("group_id") if isinstance(existing, dict) else group_id,
"userId": existing.get("user_id") if isinstance(existing, dict) else None,
}
def get_admin_user(token, user_id):
data = ensure_success(curl_api("GET", f"/api/v1/admin/users/{user_id}", bearer=token), "get API key owner")
if not isinstance(data, dict):
raise RuntimeError("API key owner response is not an object")
return data
def ensure_pool_owner_balance(token, user_id):
user = get_admin_user(token, user_id)
current = float(user.get("balance") or 0)
if current >= MIN_OWNER_BALANCE_USD:
return {
"action": "kept-existing",
"userId": user_id,
"balanceBefore": current,
"balanceAfter": current,
"minimumBalanceUsd": MIN_OWNER_BALANCE_USD,
}
updated = ensure_success(curl_api("POST", f"/api/v1/admin/users/{user_id}/balance", bearer=token, payload={
"balance": MIN_OWNER_BALANCE_USD,
"operation": "set",
"notes": "UniDesk Sub2API Codex pool internal API key bootstrap balance.",
}), "set API key owner balance")
after = float(updated.get("balance") or MIN_OWNER_BALANCE_USD) if isinstance(updated, dict) else MIN_OWNER_BALANCE_USD
return {
"action": "set",
"userId": user_id,
"balanceBefore": current,
"balanceAfter": after,
"minimumBalanceUsd": MIN_OWNER_BALANCE_USD,
}
def ensure_pool_owner_concurrency(token, user_id):
user = get_admin_user(token, user_id)
try:
current = int(user.get("concurrency") or 0)
except Exception:
current = 0
if current >= MIN_OWNER_CONCURRENCY:
return {
"ok": True,
"action": "kept-existing",
"userId": user_id,
"concurrencyBefore": current,
"concurrencyAfter": current,
"minimumConcurrency": MIN_OWNER_CONCURRENCY,
"minimumConcurrencySource": MIN_OWNER_CONCURRENCY_SOURCE,
}
updated = ensure_success(curl_api("PUT", f"/api/v1/admin/users/{user_id}", bearer=token, payload={
"concurrency": MIN_OWNER_CONCURRENCY,
}), "set API key owner concurrency")
try:
after = int(updated.get("concurrency") or MIN_OWNER_CONCURRENCY) if isinstance(updated, dict) else MIN_OWNER_CONCURRENCY
except Exception:
after = MIN_OWNER_CONCURRENCY
return {
"ok": after >= MIN_OWNER_CONCURRENCY,
"action": "set",
"userId": user_id,
"concurrencyBefore": current,
"concurrencyAfter": after,
"minimumConcurrency": MIN_OWNER_CONCURRENCY,
"minimumConcurrencySource": MIN_OWNER_CONCURRENCY_SOURCE,
}
def validate_gateway(api_key):
resp = curl_api("GET", "/v1/models", bearer=api_key)
parsed = resp.get("json")
model_count = None
if isinstance(parsed, dict) and isinstance(parsed.get("data"), list):
model_count = len(parsed["data"])
return {
"ok": resp.get("ok"),
"httpStatus": resp.get("httpStatus"),
"transportExitCode": resp.get("transportExitCode"),
"modelCount": model_count,
"bodyPreview": text(resp.get("body", ""), 500) if not resp.get("ok") else "",
"stderr": resp.get("stderr", ""),
"method": "GET /v1/models",
"serviceDns": SERVICE_DNS,
"valuesPrinted": False,
}
def response_output_preview(parsed):
if not isinstance(parsed, dict):
return ""
if isinstance(parsed.get("output_text"), str):
return parsed["output_text"][:240]
output = parsed.get("output")
if not isinstance(output, list):
return ""
parts = []
for item in output:
if not isinstance(item, dict):
continue
content = item.get("content")
if not isinstance(content, list):
continue
for block in content:
if not isinstance(block, dict):
continue
text_value = block.get("text")
if isinstance(text_value, str) and text_value:
parts.append(text_value)
return "\\n".join(parts)[:240]
def request_log_evidence(request_id):
proc = runtime_logs("5m", 800)
stdout = proc.stdout.decode("utf-8", errors="replace")
lines = [line for line in stdout.splitlines() if request_id in line]
failovers = []
final = None
for line in lines:
json_start = line.find("{")
if json_start < 0:
continue
try:
item = json.loads(line[json_start:])
except Exception:
continue
if "upstream_failover_switching" in line:
failovers.append({
"accountId": item.get("account_id"),
"upstreamStatus": item.get("upstream_status"),
"switchCount": item.get("switch_count"),
"maxSwitches": item.get("max_switches"),
})
if "http request completed" in line:
final = {
"accountId": item.get("account_id"),
"statusCode": item.get("status_code"),
"latencyMs": item.get("latency_ms"),
"path": item.get("path"),
}
return {
"requestId": request_id,
"matchedLogLineCount": len(lines),
"failovers": failovers,
"final": final,
"logsExitCode": proc.returncode,
"logsStderr": text(proc.stderr, 1000),
}
def recent_compact_gateway_evidence():
proc = runtime_logs("6h", 2500)
stdout = proc.stdout.decode("utf-8", errors="replace")
failures = []
successes = []
failovers = []
final_errors = []
context_canceled = []
for line in stdout.splitlines():
if "/responses/compact" not in line and "remote_compact" not in line:
continue
json_start = line.find("{")
if json_start < 0:
continue
try:
item = json.loads(line[json_start:])
except Exception:
continue
path = item.get("path")
entry = {
"requestId": item.get("request_id"),
"clientRequestId": item.get("client_request_id"),
"accountId": item.get("account_id"),
"statusCode": item.get("status_code"),
"upstreamStatus": item.get("upstream_status"),
"latencyMs": item.get("latency_ms"),
"path": path,
}
if "codex.remote_compact.failed" in line:
failures.append(entry)
elif "codex.remote_compact.succeeded" in line:
successes.append(entry)
elif "upstream_failover_switching" in line and path == "/responses/compact":
failovers.append({
**entry,
"switchCount": item.get("switch_count"),
"maxSwitches": item.get("max_switches"),
})
elif "http request completed" in line and path == "/responses/compact" and isinstance(item.get("status_code"), int) and item.get("status_code") >= 400:
final_errors.append(entry)
if "context canceled" in line and path == "/responses/compact":
context_canceled.append(entry)
return {
"ok": True,
"degraded": len(failures) > 0 or len(final_errors) > 0 or len(context_canceled) > 0,
"window": "6h",
"tailLines": 2500,
"failureCount": len(failures),
"successCount": len(successes),
"failoverCount": len(failovers),
"finalErrorCount": len(final_errors),
"contextCanceledCount": len(context_canceled),
"recentFailures": failures[-5:],
"recentSuccesses": successes[-5:],
"recentFailovers": failovers[-8:],
"recentFinalErrors": final_errors[-5:],
"recentContextCanceled": context_canceled[-5:],
"logsExitCode": proc.returncode,
"logsStderr": text(proc.stderr, 1000),
"valuesPrinted": False,
}
def is_ignored_probe_noise(entry):
for value in (entry.get("requestId"), entry.get("clientRequestId")):
if isinstance(value, str) and value.startswith("unidesk-400-model-probe-"):
return True
return False
def filter_ignored_probe_noise(items):
return [item for item in items if not is_ignored_probe_noise(item)]
def ignored_probe_noise(items, section):
result = []
for item in items:
if is_ignored_probe_noise(item):
probe = dict(item)
probe["section"] = section
result.append(probe)
return result
def group_by_request_id(items):
grouped = {}
for item in items:
request_id = item.get("requestId")
if not isinstance(request_id, str) or not request_id:
continue
grouped.setdefault(request_id, []).append(item)
return grouped
def failover_budget_exhausted_evidence(failovers, final_errors):
final_by_request = {}
for item in final_errors:
request_id = item.get("requestId")
if isinstance(request_id, str) and request_id:
final_by_request[request_id] = item
exhausted = []
for request_id, request_failovers in group_by_request_id(failovers).items():
final = final_by_request.get(request_id)
if not final:
continue
last = request_failovers[-1]
switch_count = last.get("switchCount")
max_switches = last.get("maxSwitches")
final_status = final.get("statusCode")
if (
isinstance(switch_count, int)
and isinstance(max_switches, int)
and max_switches > 0
and switch_count >= max_switches
and isinstance(final_status, int)
and final_status >= 500
):
exhausted.append({
"requestId": request_id,
"clientRequestId": final.get("clientRequestId") or last.get("clientRequestId"),
"path": final.get("path") or last.get("path"),
"finalAccountId": final.get("accountId"),
"finalStatusCode": final_status,
"switchCount": switch_count,
"maxSwitches": max_switches,
"lastFailoverAccountId": last.get("accountId"),
"lastUpstreamStatus": last.get("upstreamStatus"),
})
return exhausted
def recent_responses_gateway_evidence():
proc = runtime_logs("6h", 2500)
stdout = proc.stdout.decode("utf-8", errors="replace")
failovers = []
forward_failures = []
final_errors = []
context_canceled = []
slow_final_errors = []
for line in stdout.splitlines():
if '"/responses"' not in line and '"/v1/responses"' not in line:
continue
json_start = line.find("{")
if json_start < 0:
continue
try:
item = json.loads(line[json_start:])
except Exception:
continue
path = item.get("path")
if path not in ("/responses", "/v1/responses"):
continue
entry = {
"requestId": item.get("request_id"),
"clientRequestId": item.get("client_request_id"),
"accountId": item.get("account_id"),
"statusCode": item.get("status_code"),
"upstreamStatus": item.get("upstream_status"),
"latencyMs": item.get("latency_ms"),
"path": path,
}
if "upstream_failover_switching" in line:
failovers.append({
**entry,
"switchCount": item.get("switch_count"),
"maxSwitches": item.get("max_switches"),
})
elif "openai.forward_failed" in line:
forward_failures.append({
**entry,
"errorPreview": text(str(item.get("error") or ""), 500),
"fallbackErrorResponseWritten": item.get("fallback_error_response_written"),
"upstreamErrorResponseAlreadyWritten": item.get("upstream_error_response_already_written"),
})
elif "http request completed" in line and isinstance(item.get("status_code"), int) and item.get("status_code") >= 400:
final_errors.append(entry)
latency_ms = item.get("latency_ms")
if isinstance(latency_ms, int) and latency_ms >= 30000:
slow_final_errors.append(entry)
if "context canceled" in line:
context_canceled.append(entry)
visible_failovers = filter_ignored_probe_noise(failovers)
visible_forward_failures = filter_ignored_probe_noise(forward_failures)
visible_final_errors = filter_ignored_probe_noise(final_errors)
visible_slow_final_errors = filter_ignored_probe_noise(slow_final_errors)
visible_context_canceled = filter_ignored_probe_noise(context_canceled)
failover_budget_exhausted = failover_budget_exhausted_evidence(visible_failovers, visible_final_errors)
probe_noise = (
ignored_probe_noise(failovers, "failovers")
+ ignored_probe_noise(forward_failures, "forwardFailures")
+ ignored_probe_noise(final_errors, "finalErrors")
+ ignored_probe_noise(slow_final_errors, "slowFinalErrors")
+ ignored_probe_noise(context_canceled, "contextCanceled")
)
return {
"ok": True,
"degraded": len(visible_forward_failures) > 0 or len(visible_final_errors) > 0 or len(visible_context_canceled) > 0,
"window": "6h",
"tailLines": 2500,
"failoverCount": len(visible_failovers),
"forwardFailureCount": len(visible_forward_failures),
"finalErrorCount": len(visible_final_errors),
"slowFinalErrorCount": len(visible_slow_final_errors),
"contextCanceledCount": len(visible_context_canceled),
"ignoredProbeNoiseCount": len(probe_noise),
"failoverBudgetExhausted": failover_budget_exhausted[-8:],
"rawCounts": {
"failoverCount": len(failovers),
"forwardFailureCount": len(forward_failures),
"finalErrorCount": len(final_errors),
"slowFinalErrorCount": len(slow_final_errors),
"contextCanceledCount": len(context_canceled),
},
"recentFailovers": visible_failovers[-8:],
"recentForwardFailures": visible_forward_failures[-8:],
"recentFinalErrors": visible_final_errors[-8:],
"recentSlowFinalErrors": visible_slow_final_errors[-5:],
"recentContextCanceled": visible_context_canceled[-5:],
"recentProbeNoise": probe_noise[-5:],
"logsExitCode": proc.returncode,
"logsStderr": text(proc.stderr, 1000),
"valuesPrinted": False,
}
def validate_gateway_responses(api_key):
request_id = "unidesk-codex-pool-validate-" + str(int(time.time() * 1000))
payload = {
"model": RESPONSES_SMOKE_MODEL,
"input": "Reply exactly: unidesk-sub2api-validate-ok",
"stream": False,
"store": False,
"max_output_tokens": 32,
}
body = json.dumps(payload, separators=(",", ":")).encode("utf-8")
script = r'''
set -eu
token="$1"
request_id="$2"
url="$3"
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
cat > "$tmp"
curl -sS -w '\\n__HTTP_CODE__:%{http_code}' -X POST \
-H "Authorization: Bearer $token" \
-H 'Content-Type: application/json' \
-H "X-Request-ID: $request_id" \
-H "OpenAI-Client-Request-ID: $request_id" \
--data-binary @"$tmp" \
"$url"
'''
started = time.time()
if RUNTIME_MODE == "host-docker":
if not isinstance(HOST_DOCKER_APP_PORT, int):
raise RuntimeError("host-docker app port missing")
proc = run(["sh", "-c", script, "sh", api_key, request_id, f"http://127.0.0.1:{HOST_DOCKER_APP_PORT}/v1/responses"], body)
else:
proc = run([
"kubectl", "-n", NAMESPACE, "exec", "-i", APP_POD,
"--", "sh", "-c", script, "sh", api_key, request_id, "http://127.0.0.1:8080/v1/responses",
], body)
resp = parse_curl_output(proc)
evidence = request_log_evidence(request_id)
parsed = resp.get("json")
failover_count = len(evidence.get("failovers") or [])
return {
"ok": resp.get("ok"),
"degraded": failover_count > 0,
"outcome": "succeeded-with-failover" if resp.get("ok") and failover_count > 0 else ("succeeded" if resp.get("ok") else "failed"),
"httpStatus": resp.get("httpStatus"),
"transportExitCode": resp.get("transportExitCode"),
"method": "POST /v1/responses",
"model": RESPONSES_SMOKE_MODEL,
"requestId": request_id,
"durationMs": int((time.time() - started) * 1000),
"outputTextPreview": response_output_preview(parsed),
"bodyPreview": "" if resp.get("ok") else text(resp.get("body", ""), 800),
"stderr": resp.get("stderr", ""),
"evidence": evidence,
"valuesPrinted": False,
}
def bool_value(value):
if isinstance(value, bool):
return value
if isinstance(value, str):
if value.lower() == "true":
return True
if value.lower() == "false":
return False
return False
def normalize_temp_unschedulable_credentials(credentials):
if not isinstance(credentials, dict):
credentials = {}
enabled = bool_value(credentials.get("temp_unschedulable_enabled"))
raw_rules = credentials.get("temp_unschedulable_rules")
if isinstance(raw_rules, str):
try:
raw_rules = json.loads(raw_rules)
except json.JSONDecodeError:
raw_rules = []
rules = []
if isinstance(raw_rules, list):
for rule in raw_rules:
if not isinstance(rule, dict):
continue
error_code = rule.get("error_code", rule.get("statusCode"))
duration_minutes = rule.get("duration_minutes", rule.get("durationMinutes"))
keywords = rule.get("keywords")
if not isinstance(error_code, int) or not isinstance(duration_minutes, int) or not isinstance(keywords, list):
continue
clean_keywords = [item for item in keywords if isinstance(item, str) and item.strip()]
if not clean_keywords:
continue
description = rule.get("description") if isinstance(rule.get("description"), str) else ""
rules.append({
"error_code": error_code,
"keywords": clean_keywords,
"duration_minutes": duration_minutes,
"description": description,
})
return {
"enabled": enabled,
"rules": rules,
}
def summarize_temp_unschedulable_rules(rules):
return [{
"errorCode": rule.get("error_code"),
"durationMinutes": rule.get("duration_minutes"),
"keywordCount": len(rule.get("keywords") or []),
"keywords": rule.get("keywords") or [],
"hasDescription": bool(rule.get("description")),
} for rule in rules]
def success_body_reclassification_requirement():
for name in sorted(EXPECTED_ACCOUNT_TEMP_UNSCHEDULABLE):
expected = normalize_temp_unschedulable_credentials(EXPECTED_ACCOUNT_TEMP_UNSCHEDULABLE[name])
if expected["enabled"] is not True:
continue
for rule in expected["rules"]:
error_code = rule.get("error_code")
keywords = rule.get("keywords") or []
if isinstance(error_code, int) and 200 <= error_code < 300 and keywords:
return {
"required": True,
"sourceAccountName": name,
"statusCode": error_code,
"keywords": keywords,
"representativeKeyword": keywords[0],
"durationMinutes": rule.get("duration_minutes"),
}
return {
"required": False,
"sourceAccountName": None,
"statusCode": None,
"keywords": [],
"representativeKeyword": None,
"durationMinutes": None,
}
def model_routing_400_failover_requirement():
preferred = ["invalid_encrypted_content", "encrypted content", "could not be verified", "could not be decrypted", "暂不支持", "可用模型", "unsupported model", "model not supported", "does not support", "not supported", "model_not_found", "no available channel for model"]
for name in sorted(EXPECTED_ACCOUNT_TEMP_UNSCHEDULABLE):
expected = normalize_temp_unschedulable_credentials(EXPECTED_ACCOUNT_TEMP_UNSCHEDULABLE[name])
if expected["enabled"] is not True:
continue
for rule in expected["rules"]:
error_code = rule.get("error_code")
keywords = rule.get("keywords") or []
if error_code != 400 or not keywords:
continue
representative_keyword = next((item for item in preferred if item in keywords), keywords[0])
return {
"required": True,
"sourceAccountName": name,
"statusCode": error_code,
"keywords": keywords,
"representativeKeyword": representative_keyword,
"durationMinutes": rule.get("duration_minutes"),
}
return {
"required": False,
"sourceAccountName": None,
"statusCode": None,
"keywords": [],
"representativeKeyword": None,
"durationMinutes": None,
}
def delete_probe_resource(token, method, path, label):
if not path:
return {"label": label, "ok": True, "skipped": True}
resp = curl_api(method, path, bearer=token)
ok = resp.get("ok") is True or resp.get("httpStatus") in (404, 410)
return {
"label": label,
"ok": ok,
"method": method,
"path": path,
"httpStatus": resp.get("httpStatus"),
"transportExitCode": resp.get("transportExitCode"),
"bodyPreview": "" if ok else text(resp.get("body", ""), 500),
"valuesPrinted": False,
}
def validate_runtime_capabilities(token):
success_body = success_body_reclassification_requirement()
model_routing_400 = model_routing_400_failover_requirement()
return {
"ok": success_body.get("required") is not True and model_routing_400.get("required") is True,
"runtimeImage": app_pod_runtime_image(),
"successBodyReclassification": {
"ok": success_body.get("required") is not True,
"required": success_body.get("required"),
"outcome": "not-required-by-yaml" if success_body.get("required") is not True else "requires-source-review-and-real-traffic-evidence",
"requirement": success_body,
"valuesPrinted": False,
},
"modelRouting400Failover": {
"ok": model_routing_400.get("required") is True,
"required": model_routing_400.get("required"),
"outcome": "yaml-rules-present-runtime-observed-by-real-traffic",
"requirement": model_routing_400,
"evidence": "Use Sub2API source review plus validation.gatewayResponsesRecent and Artificer/real request ids for runtime proof; default validate does not create mock upstreams or temporary failover accounts.",
"valuesPrinted": False,
},
"valuesPrinted": False,
}
def app_pod_runtime_image():
if RUNTIME_MODE == "host-docker":
proc = docker(["inspect", HOST_DOCKER_APP_CONTAINER])
if proc.returncode != 0:
return {
"container": HOST_DOCKER_APP_CONTAINER,
"error": text(proc.stderr, 1000) or text(proc.stdout, 1000),
}
try:
data = json.loads(proc.stdout.decode("utf-8"))
item = data[0] if isinstance(data, list) and data else {}
except Exception as exc:
return {"container": HOST_DOCKER_APP_CONTAINER, "error": str(exc)}
state = item.get("State") if isinstance(item, dict) and isinstance(item.get("State"), dict) else {}
health = state.get("Health") if isinstance(state.get("Health"), dict) else {}
config = item.get("Config") if isinstance(item, dict) and isinstance(item.get("Config"), dict) else {}
return {
"container": HOST_DOCKER_APP_CONTAINER,
"id": (item.get("Id") or "")[:12] if isinstance(item.get("Id"), str) else None,
"image": config.get("Image"),
"imageID": item.get("Image"),
"ready": state.get("Running") is True and (not health or health.get("Status") in (None, "healthy")),
"restartCount": item.get("RestartCount"),
"startedAt": state.get("StartedAt"),
"health": health.get("Status"),
}
try:
pod = kube_json(["-n", NAMESPACE, "get", "pod", APP_POD], f"pod/{APP_POD}")
spec_containers = ((pod.get("spec") or {}).get("containers") or []) if isinstance(pod, dict) else []
status_containers = ((pod.get("status") or {}).get("containerStatuses") or []) if isinstance(pod, dict) else []
spec = next((item for item in spec_containers if item.get("name") == "sub2api"), spec_containers[0] if spec_containers else {})
status = next((item for item in status_containers if item.get("name") == "sub2api"), status_containers[0] if status_containers else {})
return {
"pod": APP_POD,
"image": spec.get("image"),
"imageID": status.get("imageID"),
"ready": status.get("ready"),
"restartCount": status.get("restartCount"),
}
except Exception as exc:
return {"pod": APP_POD, "error": str(exc)}
def get_account_detail(token, account):
account_id = account.get("id") if isinstance(account, dict) else None
if account_id is None:
return account
data = ensure_success(curl_api("GET", f"/api/v1/admin/accounts/{account_id}", bearer=token), f"get account {account.get('name')}")
return data if isinstance(data, dict) else account
def account_temp_unschedulable_status(token):
accounts = list_accounts(token)
by_name = {item.get("name"): item for item in accounts if isinstance(item.get("name"), str)}
items = []
missing = []
mismatched = []
enabled_names = []
for name in sorted(EXPECTED_ACCOUNT_TEMP_UNSCHEDULABLE):
expected = normalize_temp_unschedulable_credentials(EXPECTED_ACCOUNT_TEMP_UNSCHEDULABLE[name])
account = by_name.get(name)
if account is None:
missing.append(name)
items.append({
"accountName": name,
"accountId": None,
"expectedEnabled": expected["enabled"],
"runtimeEnabled": None,
"expectedRuleCount": len(expected["rules"]),
"runtimeRuleCount": None,
"ok": False,
})
continue
detail = get_account_detail(token, account)
credentials = detail.get("credentials") if isinstance(detail.get("credentials"), dict) else {}
runtime = normalize_temp_unschedulable_credentials(credentials)
temp_until = detail.get("temp_unschedulable_until") or detail.get("tempUnschedulableUntil")
temp_reason = detail.get("temp_unschedulable_reason") or detail.get("tempUnschedulableReason") or ""
ok = runtime == expected
if expected["enabled"]:
enabled_names.append(name)
if not ok:
mismatched.append(name)
items.append({
"accountName": name,
"accountId": account.get("id"),
"expectedEnabled": expected["enabled"],
"runtimeEnabled": runtime["enabled"],
"expectedRuleCount": len(expected["rules"]),
"runtimeRuleCount": len(runtime["rules"]),
"expectedRules": summarize_temp_unschedulable_rules(expected["rules"]),
"runtimeRules": summarize_temp_unschedulable_rules(runtime["rules"]),
"status": account.get("status"),
"schedulable": account.get("schedulable"),
"tempUnschedulableUntil": temp_until,
"tempUnschedulableReasonPreview": text(str(temp_reason), 500) if temp_reason else "",
"tempUnschedulableSet": temp_until is not None or bool(temp_reason),
"ok": ok,
})
return {
"ok": len(missing) == 0 and len(mismatched) == 0,
"desired": len(EXPECTED_ACCOUNT_TEMP_UNSCHEDULABLE),
"enabled": enabled_names,
"missing": missing,
"mismatched": mismatched,
"items": items,
"valuesPrinted": False,
}
def account_capacity_status(token):
accounts = list_accounts(token)
by_name = {item.get("name"): item for item in accounts if isinstance(item.get("name"), str)}
items = []
missing = []
mismatched = []
expected_capacity_total = 0
runtime_concurrency_total = 0
schedulable_runtime_concurrency_total = 0
available_runtime_concurrency_total = 0
temp_unschedulable_runtime_concurrency_total = 0
for name in sorted(EXPECTED_ACCOUNT_CAPACITIES):
expected = int(EXPECTED_ACCOUNT_CAPACITIES[name])
expected_capacity_total += expected
account = by_name.get(name)
if account is None:
missing.append(name)
items.append({
"accountName": name,
"accountId": None,
"expectedCapacity": expected,
"runtimeConcurrency": None,
"ok": False,
})
continue
runtime = account.get("concurrency")
runtime_int = runtime if isinstance(runtime, int) else 0
runtime_concurrency_total += runtime_int
if account.get("schedulable") is True:
runtime_status = account.get("status")
if runtime_status is None or runtime_status == "active":
runtime_status_ok = True
else:
runtime_status_ok = False
if runtime_status_ok:
schedulable_runtime_concurrency_total += runtime_int
temp_until = account.get("temp_unschedulable_until") or account.get("tempUnschedulableUntil")
temp_reason = account.get("temp_unschedulable_reason") or account.get("tempUnschedulableReason")
if temp_until is None and not bool(temp_reason):
available_runtime_concurrency_total += runtime_int
else:
temp_unschedulable_runtime_concurrency_total += runtime_int
ok = runtime == expected
if not ok:
mismatched.append(name)
items.append({
"accountName": name,
"accountId": account.get("id"),
"expectedCapacity": expected,
"runtimeConcurrency": runtime,
"priority": account.get("priority"),
"status": account.get("status"),
"schedulable": account.get("schedulable"),
"ok": ok,
})
return {
"ok": len(missing) == 0 and len(mismatched) == 0,
"defaultAccountCapacity": POOL_DEFAULT_ACCOUNT_CAPACITY,
"desired": len(EXPECTED_ACCOUNT_CAPACITIES),
"totals": {
"expectedCapacityTotal": expected_capacity_total,
"runtimeConcurrencyTotal": runtime_concurrency_total,
"schedulableRuntimeConcurrencyTotal": schedulable_runtime_concurrency_total,
"availableRuntimeConcurrencyTotal": available_runtime_concurrency_total,
"tempUnschedulableRuntimeConcurrencyTotal": temp_unschedulable_runtime_concurrency_total,
"minOwnerConcurrency": MIN_OWNER_CONCURRENCY,
"minOwnerConcurrencySource": MIN_OWNER_CONCURRENCY_SOURCE,
},
"missing": missing,
"mismatched": mismatched,
"items": items,
"valuesPrinted": False,
}
def account_load_factor_status(token):
accounts = list_accounts(token)
by_name = {item.get("name"): item for item in accounts if isinstance(item.get("name"), str)}
items = []
missing = []
mismatched = []
for name in sorted(EXPECTED_ACCOUNT_LOAD_FACTORS):
expected = int(EXPECTED_ACCOUNT_LOAD_FACTORS[name])
account = by_name.get(name)
if account is None:
missing.append(name)
items.append({
"accountName": name,
"accountId": None,
"expectedLoadFactor": expected,
"runtimeLoadFactor": None,
"ok": False,
})
continue
runtime_raw = account.get("load_factor")
if runtime_raw is None:
detail = get_account_detail(token, account)
runtime_raw = detail.get("load_factor") if isinstance(detail, dict) else None
try:
runtime = int(runtime_raw)
except Exception:
runtime = runtime_raw
ok = runtime == expected
if not ok:
mismatched.append(name)
items.append({
"accountName": name,
"accountId": account.get("id"),
"expectedLoadFactor": expected,
"runtimeLoadFactor": runtime,
"priority": account.get("priority"),
"status": account.get("status"),
"schedulable": account.get("schedulable"),
"ok": ok,
})
return {
"ok": len(missing) == 0 and len(mismatched) == 0,
"defaultAccountLoadFactor": POOL_DEFAULT_ACCOUNT_LOAD_FACTOR,
"desired": len(EXPECTED_ACCOUNT_LOAD_FACTORS),
"missing": missing,
"mismatched": mismatched,
"items": items,
"valuesPrinted": False,
}
def account_ws_v2_status(token):
accounts = list_accounts(token)
by_name = {item.get("name"): item for item in accounts if isinstance(item.get("name"), str)}
items = []
missing = []
mismatched = []
enabled_names = []
unschedulable_enabled = []
schedulable_enabled = []
for name in sorted(EXPECTED_ACCOUNT_WS_MODES):
expected_mode = EXPECTED_ACCOUNT_WS_MODES[name]
expected_enabled = expected_mode not in (None, "", "off")
account = by_name.get(name)
if account is None:
missing.append(name)
items.append({
"accountName": name,
"accountId": None,
"expectedMode": expected_mode,
"expectedEnabled": expected_enabled,
"runtimeMode": None,
"runtimeEnabled": None,
"ok": False,
})
continue
extra = account.get("extra") if isinstance(account.get("extra"), dict) else {}
runtime_mode = extra.get("openai_apikey_responses_websockets_v2_mode")
runtime_enabled = extra.get("openai_apikey_responses_websockets_v2_enabled")
schedulable = account.get("schedulable")
if expected_mode == "off":
ok = runtime_mode == "off" and runtime_enabled is False
elif expected_mode is None:
ok = runtime_mode in (None, "", "off") and runtime_enabled in (None, False)
else:
ok = runtime_mode == expected_mode and runtime_enabled is True
if not ok:
mismatched.append(name)
if expected_enabled:
enabled_names.append(name)
if schedulable is True:
schedulable_enabled.append(name)
else:
unschedulable_enabled.append(name)
items.append({
"accountName": name,
"accountId": account.get("id"),
"expectedMode": expected_mode,
"expectedEnabled": expected_enabled,
"runtimeMode": runtime_mode,
"runtimeEnabled": runtime_enabled,
"status": account.get("status"),
"schedulable": schedulable,
"ok": ok and ((not expected_enabled) or schedulable is True),
})
availability_ok = len(enabled_names) == 0 or len(schedulable_enabled) > 0
return {
"ok": len(missing) == 0 and len(mismatched) == 0 and availability_ok,
"desired": len(EXPECTED_ACCOUNT_WS_MODES),
"enabled": enabled_names,
"schedulableEnabled": schedulable_enabled,
"unschedulableEnabled": unschedulable_enabled,
"missing": missing,
"mismatched": mismatched,
"items": items,
"valuesPrinted": False,
}
def api_key_preview(api_key):
if len(api_key) <= 14:
return "***"
return api_key[:10] + "..." + api_key[-4:]
def secret_fingerprint(value):
return "sha256:" + hashlib.sha256(value.encode("utf-8")).hexdigest()[:16]
def run_sync():
global MANUAL_ACCOUNT_PROTECTIONS
payload = json.loads(base64.b64decode(PAYLOAD_B64).decode("utf-8"))
manual_accounts_payload = payload.get("manualAccounts") if isinstance(payload.get("manualAccounts"), dict) else {}
resolved_manual_protections = manual_accounts_payload.get("protected")
if not isinstance(resolved_manual_protections, list):
raise RuntimeError("sync payload has no manualAccounts.protected binding plan")
MANUAL_ACCOUNT_PROTECTIONS = resolved_manual_protections
profiles = payload.get("profiles") or []
prune_removed = bool(payload.get("pruneRemoved"))
sentinel_payload = payload.get("sentinel") if isinstance(payload.get("sentinel"), dict) else {}
if not profiles and not MANUAL_ACCOUNT_PROTECTIONS:
raise RuntimeError("sync payload has no profiles and no manualAccounts.protected binding plan")
admin_email, token, admin_compliance = login()
group, group_action = ensure_group(token)
group_id = group.get("id") if isinstance(group, dict) else None
if group_id is None:
raise RuntimeError("pool group id missing after ensure")
existing_accounts = list_accounts(token)
planned_account_results = planned_sentinel_account_results(profiles, existing_accounts)
sentinel_quality_prepare = ensure_sentinel_state_for_sync(planned_account_results, True)
if sentinel_quality_prepare.get("ok") is not True:
raise RuntimeError("prepare sentinel pending probe failed: " + json.dumps(sentinel_quality_prepare, ensure_ascii=False))
protected_frozen_names = active_sentinel_quarantine_names()
account_results, pruned_account_results = ensure_accounts(token, profiles, group_id, prune_removed, protected_frozen_names, existing_accounts)
manual_account_proxy_bindings = ensure_manual_account_proxy_bindings(token)
manual_account_group_bindings = ensure_manual_account_group_bindings(token, group_id)
manual_account_protections = manual_account_protection_status(token, group_id)
capacity_status = account_capacity_status(token)
load_factor_status = account_load_factor_status(token)
ws_v2_status = account_ws_v2_status(token)
temp_unschedulable_status = account_temp_unschedulable_status(token)
api_key, secret_action, secret_apply_stdout = ensure_api_key_secret(group_id, token)
api_key_result = ensure_sub2api_api_key(token, api_key, group_id)
owner_balance = ensure_pool_owner_balance(token, api_key_result["userId"])
owner_concurrency = ensure_pool_owner_concurrency(token, api_key_result["userId"])
gateway = validate_gateway(api_key)
responses_smoke = validate_gateway_responses(api_key)
compact_evidence = recent_compact_gateway_evidence()
responses_evidence = recent_responses_gateway_evidence()
runtime_capabilities = validate_runtime_capabilities(token)
sentinel = apply_sentinel_manifest(sentinel_payload.get("manifest"))
sentinel_quality = ensure_sentinel_state_for_sync(account_results)
sentinel_reassert = reassert_sentinel_freezes_after_sync(token)
return {
"ok": gateway["ok"] is True and responses_smoke["ok"] is True and owner_concurrency["ok"] is True and capacity_status["ok"] is True and load_factor_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True and manual_account_proxy_bindings.get("ok") is True and manual_account_group_bindings.get("ok") is True and manual_account_protections.get("ok") is True and sentinel.get("ok") is True and sentinel_quality_prepare.get("ok") is True and sentinel_quality.get("ok") is True and sentinel_reassert.get("ok") is True and runtime_capabilities.get("ok") is True,
"degraded": bool(responses_smoke.get("degraded")) or bool(compact_evidence.get("degraded")) or bool(responses_evidence.get("degraded")) or runtime_capabilities.get("ok") is not True,
"mode": "sync",
"namespace": NAMESPACE,
"serviceDns": SERVICE_DNS,
"appPod": APP_POD,
"admin": {"email": admin_email, "tokenPrinted": False, "compliance": admin_compliance},
"pool": {"name": POOL_GROUP_NAME, "id": group_id, "action": group_action, "platform": group.get("platform") if isinstance(group, dict) else "openai"},
"accounts": {
"desired": len(profiles),
"created": sum(1 for item in account_results if item["action"] == "created"),
"updated": sum(1 for item in account_results if item["action"] == "updated"),
"pruned": len(pruned_account_results),
"pruneMode": "explicit" if prune_removed else "disabled-by-default",
"items": account_results,
"prunedItems": pruned_account_results,
"processControl": {"schedulableRestore": "sentinel marker probe only; sync does not restore schedulable for existing accounts", "durableConfig": False},
"valuesPrinted": False,
},
"manualAccounts": {**manual_account_protections, "proxySync": manual_account_proxy_bindings, "groupSync": manual_account_group_bindings},
"capacity": capacity_status,
"loadFactor": load_factor_status,
"webSocketsV2": ws_v2_status,
"tempUnschedulable": temp_unschedulable_status,
"apiKey": {
"name": POOL_API_KEY_NAME,
"secret": pool_api_key_secret_location(),
"secretAction": secret_action,
"secretApply": secret_apply_stdout,
"sub2apiAction": api_key_result["action"],
"sub2apiId": api_key_result["id"],
"groupId": api_key_result["groupId"],
"userId": api_key_result["userId"],
"apiKeyFingerprint": secret_fingerprint(api_key),
"valuesPrinted": False,
},
"ownerBalance": owner_balance,
"ownerConcurrency": owner_concurrency,
"sentinel": {**sentinel, "qualityGatePrepare": sentinel_quality_prepare, "qualityGate": sentinel_quality, "freezeReassert": sentinel_reassert},
"runtimeCapabilities": runtime_capabilities,
"validation": {"gatewayModels": gateway, "gatewayResponses": responses_smoke, "gatewayResponsesRecent": responses_evidence, "gatewayCompactRecent": compact_evidence},
}
def run_validate():
api_key = decode_secret_value(POOL_API_KEY_SECRET_NAME, POOL_API_KEY_SECRET_KEY)
if not api_key:
raise RuntimeError(f"{POOL_API_KEY_SECRET_NAME}.{POOL_API_KEY_SECRET_KEY} missing")
admin_email, token, admin_compliance = login()
key_item = next((item for item in list_user_keys(token) if item.get("key") == api_key), None)
owner_balance = None
owner_concurrency = None
if key_item is not None and key_item.get("user_id") is not None:
owner_balance = ensure_pool_owner_balance(token, key_item["user_id"])
owner_concurrency = ensure_pool_owner_concurrency(token, key_item["user_id"])
capacity_status = account_capacity_status(token)
load_factor_status = account_load_factor_status(token)
ws_v2_status = account_ws_v2_status(token)
temp_unschedulable_status = account_temp_unschedulable_status(token)
pool_group_id = key_item.get("group_id") if isinstance(key_item, dict) else None
manual_account_protections = manual_account_protection_status(token, pool_group_id)
gateway = validate_gateway(api_key)
responses_smoke = validate_gateway_responses(api_key)
compact_evidence = recent_compact_gateway_evidence()
responses_evidence = recent_responses_gateway_evidence()
runtime_capabilities = validate_runtime_capabilities(token)
sentinel = sentinel_runtime_status()
return {
"ok": gateway["ok"] is True and responses_smoke["ok"] is True and (owner_concurrency is None or owner_concurrency["ok"] is True) and capacity_status["ok"] is True and load_factor_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True and manual_account_protections.get("ok") is True and sentinel.get("ok") is True and runtime_capabilities.get("ok") is True,
"degraded": bool(responses_smoke.get("degraded")) or bool(compact_evidence.get("degraded")) or bool(responses_evidence.get("degraded")) or runtime_capabilities.get("ok") is not True,
"mode": "validate",
"namespace": NAMESPACE,
"serviceDns": SERVICE_DNS,
"appPod": APP_POD,
"admin": {"email": admin_email, "tokenPrinted": False, "compliance": admin_compliance},
"apiKey": {
"secret": pool_api_key_secret_location(),
"sub2apiId": key_item.get("id") if isinstance(key_item, dict) else None,
"userId": key_item.get("user_id") if isinstance(key_item, dict) else None,
"groupId": key_item.get("group_id") if isinstance(key_item, dict) else None,
"apiKeyFingerprint": secret_fingerprint(api_key),
"valuesPrinted": False,
},
"ownerBalance": owner_balance,
"ownerConcurrency": owner_concurrency,
"capacity": capacity_status,
"loadFactor": load_factor_status,
"webSocketsV2": ws_v2_status,
"tempUnschedulable": temp_unschedulable_status,
"manualAccounts": manual_account_protections,
"sentinel": sentinel,
"runtimeCapabilities": runtime_capabilities,
"validation": {"gatewayModels": gateway, "gatewayResponses": responses_smoke, "gatewayResponsesRecent": responses_evidence, "gatewayCompactRecent": compact_evidence},
}
def parse_log_line(line):
json_start = line.find("{")
if json_start < 0:
return None
prefix = line[:json_start].rstrip()
try:
item = json.loads(line[json_start:])
except Exception:
return None
if not isinstance(item, dict):
return None
at = None
parts = prefix.split()
if parts:
at = parts[0]
message = ""
if len(parts) >= 4:
message = " ".join(parts[3:])
elif len(parts) >= 3:
message = parts[2]
elif len(parts) >= 1:
message = parts[-1]
item["_at"] = at
item["_message"] = message
item["_line"] = line
return item
def log_time_epoch(item):
at = item.get("_at") if isinstance(item, dict) else None
if not isinstance(at, str) or not at:
return None
try:
return datetime.strptime(at, "%Y-%m-%dT%H:%M:%S.%f%z").timestamp()
except Exception:
try:
return datetime.fromisoformat(at.replace("Z", "+00:00")).timestamp()
except Exception:
return None
def event_base(item, account_names_by_id):
account_id = item.get("account_id")
if isinstance(account_id, str) and account_id.isdigit():
account_id = int(account_id)
account_name = account_names_by_id.get(account_id)
return {
"at": item.get("_at"),
"message": item.get("_message"),
"requestId": item.get("request_id"),
"clientRequestId": item.get("client_request_id"),
"path": item.get("path"),
"method": item.get("method"),
"model": item.get("model"),
"accountId": account_id,
"accountName": account_name,
"statusCode": item.get("status_code"),
"upstreamStatus": item.get("upstream_status"),
"latencyMs": item.get("latency_ms"),
}
def classify_trace_event(item, account_names_by_id):
message = str(item.get("_message") or "")
event = event_base(item, account_names_by_id)
if "content_moderation.gateway_check_start" in message:
event.update({
"type": "request-start",
"stream": item.get("stream"),
"bodyBytes": item.get("body_bytes"),
"groupId": item.get("group_id"),
"groupName": item.get("group_name"),
"apiKeyName": item.get("api_key_name"),
})
elif "content_moderation.gateway_check_done" in message:
event.update({
"type": "gateway-check",
"allowed": item.get("allowed"),
"blocked": item.get("blocked"),
"action": item.get("action"),
})
elif "openai.upstream_failover_switching" in message:
event.update({
"type": "failover",
"switchCount": item.get("switch_count"),
"maxSwitches": item.get("max_switches"),
})
elif "openai.account_select_failed" in message:
event.update({
"type": "select-failed",
"error": item.get("error"),
"excludedAccountCount": item.get("excluded_account_count"),
})
elif "account_upstream_error" in message:
event.update({
"type": "upstream-error",
"error": item.get("error"),
})
elif "account_temp_unschedulable" in message:
event.update({
"type": "temp-unschedulable",
"until": item.get("until") or item.get("temp_unschedulable_until"),
"ruleIndex": item.get("rule_index"),
"matchedKeyword": item.get("matched_keyword"),
"reason": item.get("reason") or item.get("error"),
})
elif "http request completed" in message:
event.update({
"type": "final",
"clientIp": item.get("client_ip"),
"protocol": item.get("protocol"),
"platform": item.get("platform"),
"completedAt": item.get("completed_at"),
})
elif "admin account schedulable updated" in message or "account schedulable updated" in message or "/schedulable" in str(item.get("path") or ""):
event.update({
"type": "admin-schedulable",
"schedulable": item.get("schedulable"),
})
else:
event.update({"type": "other"})
return event
def with_trace_phase(event, first_epoch, last_epoch):
epoch = None
at = event.get("at") if isinstance(event, dict) else None
if isinstance(at, str) and at:
epoch = log_time_epoch({"_at": at})
if epoch is None or first_epoch is None:
phase = "unknown"
elif epoch < first_epoch:
phase = "before-request"
elif last_epoch is not None and epoch > last_epoch:
phase = "after-request"
else:
phase = "during-request"
event["phase"] = phase
return event
def account_snapshot_from_runtime(token):
try:
accounts = list_accounts(token)
except Exception as exc:
return [], {"error": str(exc)}
rows = []
for item in accounts:
if not isinstance(item, dict):
continue
rows.append({
"accountId": item.get("id"),
"accountName": item.get("name"),
"schedulable": item.get("schedulable"),
"status": item.get("status"),
"concurrency": item.get("concurrency"),
"priority": item.get("priority"),
"tempUnschedulableUntil": item.get("temp_unschedulable_until") or item.get("tempUnschedulableUntil"),
"tempUnschedulableSet": (item.get("temp_unschedulable_until") or item.get("tempUnschedulableUntil")) is not None or bool(item.get("temp_unschedulable_reason") or item.get("tempUnschedulableReason")),
})
rows.sort(key=lambda row: (str(row.get("accountName") or ""), int(row.get("accountId") or 0)))
return rows, None
def trace_reason(events, final_event):
failovers = [item for item in events if item.get("type") == "failover"]
select_failures = [item for item in events if item.get("type") == "select-failed"]
upstream_errors = [item for item in events if item.get("type") == "upstream-error"]
if failover_budget_exhausted(failovers, final_event):
return "failover-budget-exhausted"
if failovers and select_failures:
return "failover-attempted-no-candidate"
if failovers:
return "failover-attempted"
if select_failures:
return "account-select-failed"
if upstream_errors:
return "upstream-error"
if isinstance(final_event, dict) and isinstance(final_event.get("statusCode"), int) and final_event.get("statusCode") >= 400:
return "final-http-error"
if isinstance(final_event, dict) and isinstance(final_event.get("statusCode"), int):
return "completed"
return "unknown"
def failover_budget_exhausted(failovers, final_event):
if not failovers or not isinstance(final_event, dict):
return False
last = failovers[-1]
switch_count = last.get("switchCount")
max_switches = last.get("maxSwitches")
final_status = final_event.get("statusCode")
return (
isinstance(switch_count, int)
and isinstance(max_switches, int)
and max_switches > 0
and switch_count >= max_switches
and isinstance(final_status, int)
and final_status >= 500
)
def trace_untried_schedulable_accounts(failovers, final_event, account_snapshot):
if not failover_budget_exhausted(failovers, final_event):
return []
tried = set()
for item in failovers:
account_id = item.get("accountId")
if isinstance(account_id, int):
tried.add(account_id)
final_account = final_event.get("accountId") if isinstance(final_event, dict) else None
if isinstance(final_account, int):
tried.add(final_account)
result = []
for item in account_snapshot:
account_id = item.get("accountId")
if not isinstance(account_id, int) or account_id in tried:
continue
if item.get("schedulable") is True and item.get("status") == "active" and item.get("tempUnschedulableSet") is not True:
result.append({
"accountId": account_id,
"accountName": item.get("accountName"),
"priority": item.get("priority"),
"concurrency": item.get("concurrency"),
})
return result
def run_trace():
payload = json.loads(base64.b64decode(PAYLOAD_B64).decode("utf-8")) if PAYLOAD_B64 else {}
request_id = payload.get("requestId")
since = payload.get("since") or "24h"
tail = int(payload.get("tail") or 20000)
context_seconds = int(payload.get("contextSeconds") or 300)
show_lines = bool(payload.get("showLines"))
if not isinstance(request_id, str) or not request_id:
raise RuntimeError("trace payload missing requestId")
admin_email, token, admin_compliance = login()
account_snapshot, account_snapshot_error = account_snapshot_from_runtime(token)
account_names_by_id = {}
for row in account_snapshot:
account_id = row.get("accountId")
if isinstance(account_id, str) and account_id.isdigit():
account_id = int(account_id)
if isinstance(account_id, int) and isinstance(row.get("accountName"), str):
account_names_by_id[account_id] = row.get("accountName")
proc = kubectl(["-n", NAMESPACE, "logs", "deployment/sub2api", f"--since={since}", f"--tail={tail}"])
stdout = proc.stdout.decode("utf-8", errors="replace")
parsed_lines = []
matched = []
for line in stdout.splitlines():
parsed = parse_log_line(line)
if parsed is None:
continue
parsed_lines.append(parsed)
if request_id in line:
matched.append(parsed)
first_epoch = None
last_epoch = None
for item in matched:
epoch = log_time_epoch(item)
if epoch is None:
continue
first_epoch = epoch if first_epoch is None else min(first_epoch, epoch)
last_epoch = epoch if last_epoch is None else max(last_epoch, epoch)
window_lines = []
if first_epoch is not None:
start_epoch = first_epoch - context_seconds
end_epoch = (last_epoch if last_epoch is not None else first_epoch) + context_seconds
for item in parsed_lines:
epoch = log_time_epoch(item)
if epoch is not None and start_epoch <= epoch <= end_epoch:
window_lines.append(item)
else:
window_lines = matched
events = [classify_trace_event(item, account_names_by_id) for item in matched]
request_start = next((item for item in events if item.get("type") == "request-start"), None)
final_event = next((item for item in reversed(events) if item.get("type") == "final"), None)
failovers = [item for item in events if item.get("type") == "failover"]
select_failures = [item for item in events if item.get("type") == "select-failed"]
upstream_errors = [item for item in events if item.get("type") == "upstream-error"]
temp_unsched = [with_trace_phase(classify_trace_event(item, account_names_by_id), first_epoch, last_epoch) for item in window_lines if "account_temp_unschedulable" in str(item.get("_message") or "")]
admin_sched = [with_trace_phase(classify_trace_event(item, account_names_by_id), first_epoch, last_epoch) for item in window_lines if ("schedulable" in str(item.get("_message") or "") or "/schedulable" in str(item.get("path") or ""))]
window_events = [classify_trace_event(item, account_names_by_id) for item in window_lines]
final_errors = [item for item in window_events if item.get("type") == "final" and isinstance(item.get("statusCode"), int) and item.get("statusCode") >= 400]
window_failovers = [item for item in window_events if item.get("type") == "failover"]
window_select_failures = [item for item in window_events if item.get("type") == "select-failed"]
untried_schedulable_accounts = trace_untried_schedulable_accounts(failovers, final_event or {}, account_snapshot)
reason = trace_reason(events, final_event)
if not matched:
outcome = "not-found"
elif isinstance(final_event, dict) and isinstance(final_event.get("statusCode"), int) and final_event.get("statusCode") < 400:
outcome = "succeeded"
elif isinstance(final_event, dict):
outcome = "failed"
else:
outcome = "incomplete"
return {
"ok": proc.returncode == 0 and len(matched) > 0,
"mode": "trace",
"namespace": NAMESPACE,
"serviceDns": SERVICE_DNS,
"appPod": APP_POD,
"admin": {"email": admin_email, "tokenPrinted": False, "compliance": admin_compliance},
"requestId": request_id,
"summary": {
"outcome": outcome,
"reason": reason,
"eventCount": len(events),
"matchedLineCount": len(matched),
"firstAt": events[0].get("at") if events else None,
"lastAt": events[-1].get("at") if events else None,
},
"window": {
"since": since,
"tail": tail,
"beforeSeconds": context_seconds,
"afterSeconds": context_seconds,
"lineCount": len(window_lines),
},
"request": request_start or {},
"final": final_event or {},
"events": events,
"failovers": failovers,
"selectFailures": select_failures,
"upstreamErrors": upstream_errors,
"tempUnschedulable": temp_unsched,
"adminSchedulable": admin_sched[-20:],
"windowStats": {
"matchedLines": len(matched),
"eventCount": len(window_events),
"finalErrorCount": len(final_errors),
"failoverCount": len(window_failovers),
"selectFailedCount": len(window_select_failures),
"tempUnschedulableCount": len(temp_unsched),
"adminSchedulableCount": len(admin_sched),
},
"diagnostics": {
"failoverBudgetExhausted": failover_budget_exhausted(failovers, final_event or {}),
"untriedSchedulableAccounts": untried_schedulable_accounts,
},
"accountSnapshot": account_snapshot,
"accountSnapshotError": account_snapshot_error,
"rawLines": [{"line": item.get("_line")} for item in matched[-30:]] if show_lines else [],
"showLines": show_lines,
"logs": {
"exitCode": proc.returncode,
"stderrTail": text(proc.stderr, 1000),
"stdoutLineCount": len(stdout.splitlines()),
},
"valuesPrinted": False,
}
def parse_embedded_json(stdout):
if not isinstance(stdout, str) or not stdout.strip():
return None
start = stdout.find("{")
end = stdout.rfind("}")
if start < 0 or end <= start:
return None
try:
return json.loads(stdout[start:end + 1])
except Exception:
return None
def inject_env(template, name, value):
spec = template.setdefault("spec", {})
containers = spec.setdefault("containers", [])
if not containers:
raise RuntimeError("sentinel job template has no containers")
container = containers[0]
env = container.setdefault("env", [])
env[:] = [item for item in env if not (isinstance(item, dict) and item.get("name") == name)]
env.append({"name": name, "value": value})
def sentinel_probe_job_manifest(accounts):
cronjob_name = SENTINEL_CONFIG.get("cronJobName")
if not isinstance(cronjob_name, str) or not cronjob_name:
raise RuntimeError("sentinel cronJobName missing from config")
cronjob = kube_json(["-n", NAMESPACE, "get", "cronjob", cronjob_name], f"cronjob/{cronjob_name}")
job_template = ((cronjob.get("spec") or {}).get("jobTemplate") or {}) if isinstance(cronjob, dict) else {}
job_spec = copy_json(job_template.get("spec") or {})
if not isinstance(job_spec, dict) or not job_spec:
raise RuntimeError("sentinel CronJob jobTemplate.spec missing")
template = job_spec.get("template")
if not isinstance(template, dict):
raise RuntimeError("sentinel CronJob jobTemplate.spec.template missing")
inject_env(template, "SENTINEL_ACCOUNT_NAMES", ",".join(accounts))
job_spec["ttlSecondsAfterFinished"] = int(job_spec.get("ttlSecondsAfterFinished") or 3600)
suffix = str(int(time.time()))[-8:] + "-" + "".join(secrets.choice(string.ascii_lowercase + string.digits) for _ in range(5))
job_name = ("sub2api-sentinel-probe-" + suffix)[:63]
return job_name, {
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"name": job_name,
"namespace": NAMESPACE,
"labels": {
"app.kubernetes.io/name": cronjob_name,
"app.kubernetes.io/part-of": "platform-infra",
"app.kubernetes.io/managed-by": "unidesk",
"unidesk.ai/job-purpose": "sub2api-account-sentinel-manual-probe",
},
},
"spec": job_spec,
}
def copy_json(value):
return json.loads(json.dumps(value))
def job_condition(job, cond_type):
for item in ((job.get("status") or {}).get("conditions") or []):
if item.get("type") == cond_type and item.get("status") == "True":
return item
return None
def wait_sentinel_probe_job(job_name, timeout_seconds):
deadline = time.time() + timeout_seconds
latest = None
while time.time() < deadline:
latest, err = safe_kube_json(["-n", NAMESPACE, "get", "job", job_name], f"job/{job_name}")
if isinstance(latest, dict):
complete = job_condition(latest, "Complete")
failed = job_condition(latest, "Failed")
if complete is not None:
return "succeeded", latest, complete
if failed is not None:
return "failed", latest, failed
time.sleep(2)
return "timeout", latest, None
def job_logs(job_name):
proc = kubectl(["-n", NAMESPACE, "logs", f"job/{job_name}", "--tail=4000"])
return {
"exitCode": proc.returncode,
"stdout": proc.stdout.decode("utf-8", errors="replace"),
"stderr": text(proc.stderr, 4000),
}
def run_sentinel_probe():
payload = json.loads(base64.b64decode(PAYLOAD_B64).decode("utf-8")) if PAYLOAD_B64 else {}
accounts = payload.get("accounts") if isinstance(payload, dict) else None
if not isinstance(accounts, list) or not accounts or not all(isinstance(item, str) and item for item in accounts):
raise RuntimeError("sentinel-probe payload requires non-empty accounts")
job_name, manifest = sentinel_probe_job_manifest(accounts)
proc = kubectl(["apply", "--server-side", "--force-conflicts", f"--field-manager={FIELD_MANAGER}", "-f", "-"], json.dumps(manifest))
if proc.returncode != 0:
raise RuntimeError(f"apply sentinel probe job failed: {text(proc.stderr, 2000)}")
timeout_seconds = max(300, int(SENTINEL_CONFIG.get("probe", {}).get("timeoutSeconds") or 30) + 240)
status, job, condition = wait_sentinel_probe_job(job_name, timeout_seconds)
logs = job_logs(job_name)
parsed = parse_embedded_json(logs.get("stdout") or "")
results = parsed.get("results") if isinstance(parsed, dict) and isinstance(parsed.get("results"), list) else []
state_summary = sentinel_state_summary()
last_run = state_summary.get("lastRun") if isinstance(state_summary, dict) and isinstance(state_summary.get("lastRun"), dict) else {}
if not results and isinstance(last_run.get("results"), list):
results = last_run.get("results")
requested = set(accounts)
measured = {item.get("accountName") for item in results if isinstance(item, dict)}
missing = sorted(name for name in requested if name not in measured)
marker_ok = len(missing) == 0 and all(isinstance(item, dict) and item.get("accountName") in requested and item.get("markerMatched") is True for item in results if isinstance(item, dict) and item.get("accountName") in requested)
if not results and isinstance(last_run, dict):
selected = int(last_run.get("selected") or 0)
ok_count = int(last_run.get("okCount") or 0)
marker_mismatch_count = int(last_run.get("markerMismatchCount") or 0)
transport_failure_count = int(last_run.get("transportFailureCount") or 0)
if selected >= len(requested) and ok_count >= len(requested) and marker_mismatch_count == 0 and transport_failure_count == 0:
missing = []
marker_ok = True
job_ok = status == "succeeded" and logs.get("exitCode") == 0
return {
"ok": job_ok and marker_ok,
"jobExecutionOk": job_ok,
"markerOk": marker_ok,
"mode": "sentinel-probe",
"namespace": NAMESPACE,
"requestedAccounts": accounts,
"missingAccounts": missing,
"job": {
"name": job_name,
"status": status,
"condition": condition,
"timeoutSeconds": timeout_seconds,
"applyStdoutTail": text(proc.stdout, 1200),
"logsExitCode": logs.get("exitCode"),
"logsStderrTail": logs.get("stderr"),
},
"probe": parsed,
"summary": {
"at": last_run.get("at"),
"selected": last_run.get("selected"),
"okCount": last_run.get("okCount"),
"markerMismatchCount": last_run.get("markerMismatchCount"),
"transportFailureCount": last_run.get("transportFailureCount"),
"actionsTaken": last_run.get("actionsTaken"),
"selection": last_run.get("selection"),
},
"results": results,
"sentinelState": state_summary,
"valuesPrinted": False,
}
def run_cleanup_probes():
admin_email, token, admin_compliance = login()
cleanup = cleanup_probe_resources(token)
return {
"ok": cleanup.get("ok") is True,
"mode": "cleanup-probes",
"namespace": NAMESPACE,
"serviceDns": SERVICE_DNS,
"appPod": APP_POD,
"admin": {"email": admin_email, "tokenPrinted": False, "compliance": admin_compliance},
"cleanup": cleanup,
"valuesPrinted": False,
}
try:
if MODE == "sync":
result = run_sync()
elif MODE == "trace":
result = run_trace()
elif MODE == "cleanup-probes":
result = run_cleanup_probes()
elif MODE == "sentinel-probe":
result = run_sentinel_probe()
else:
result = run_validate()
except Exception as exc:
result = {
"ok": False,
"mode": MODE,
"namespace": NAMESPACE,
"serviceDns": SERVICE_DNS,
"appPod": globals().get("APP_POD"),
"error": str(exc),
"valuesPrinted": False,
}
print(json.dumps(result, ensure_ascii=False, indent=2))
sys.exit(0 if result.get("ok") else 1)
PY
`;
}