import { readFileSync } from "node:fs"; import { rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { rootPath } from "./src/config"; import { defaultCodexPoolSentinelConfig, readCodexPoolSentinelConfig, renderCodexPoolSentinelManifest, sentinelRunnerPython, } from "./src/platform-infra-sub2api-codex-sentinel"; function assertCondition(condition: unknown, message: string, detail: unknown = {}): void { if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`); } const configPath = rootPath("config", "platform-infra", "sub2api-codex-pool.yaml"); const parsed = Bun.YAML.parse(readFileSync(configPath, "utf8")) as { sentinel?: unknown }; const sentinel = readCodexPoolSentinelConfig(parsed.sentinel, defaultCodexPoolSentinelConfig(), configPath); assertCondition(sentinel.monitor.enabled === true, "sentinel monitor must be enabled for observation-first rollout", sentinel); assertCondition(sentinel.actions.enabled === false, "sentinel actions must default off until monitoring quality is reviewed", sentinel); assertCondition(sentinel.actions.freezeOnMarkerMismatch === true, "marker mismatch must be configured as freeze-worthy when actions are enabled", sentinel.actions); assertCondition(sentinel.actions.freezeOnTransportError === false, "transport errors must not freeze by default", sentinel.actions); assertCondition(sentinel.endpoint === "responses", "v1 sentinel must target OpenAI Responses only", sentinel); assertCondition(sentinel.model === "gpt-5.5", "v1 sentinel must use GPT-5.5", sentinel); assertCondition(sentinel.probe.maxOutputTokens > 0 && sentinel.probe.maxOutputTokens <= 16, "sentinel maxOutputTokens must be tightly capped", sentinel.probe); assertCondition(!("concurrency" in sentinel.probe), "sentinel must not cap probe concurrency; all due accounts are probed concurrently", sentinel.probe); assertCondition(!("maxAccountsPerRun" in sentinel.probe), "sentinel must not cap accounts per run; all due accounts are eligible", sentinel.probe); assertCondition(sentinel.cadence.successInitialIntervalMinutes === 1, "success trust backoff must start at 1 minute", sentinel.cadence); assertCondition(sentinel.cadence.successMaxIntervalMinutes === 20, "success trust backoff must cap at 20 minutes", sentinel.cadence); assertCondition(sentinel.freeze.initialTtlMinutes === 10, "freeze backoff must start at 10 minutes", sentinel.freeze); assertCondition(sentinel.freeze.maxTtlMinutes === 120, "freeze backoff must cap at 2 hours", sentinel.freeze); assertCondition(!("budget" in sentinel), "sentinel must not use token budgets as a probe gate; usage is recorded only", sentinel); const manifest = renderCodexPoolSentinelManifest(sentinel, [ { accountName: "unidesk-codex-example", profile: "example", baseUrl: "https://example.invalid/v1", apiKey: "sk-test-secret", upstreamUserAgent: null, }, ], { namespace: "platform-infra", serviceName: "sub2api", serviceDns: "sub2api.platform-infra.svc.cluster.local:8080", appSecretName: "sub2api-secrets", }); assertCondition(manifest.includes("kind: CronJob"), "sentinel manifest must render a CronJob", manifest.slice(0, 1000)); assertCondition(manifest.includes("concurrencyPolicy: Forbid"), "sentinel CronJob must forbid overlapping runs", manifest); assertCondition(manifest.includes("suspend: false"), "monitor.enabled=true must unsuspend the CronJob", manifest); assertCondition(manifest.includes("kind: ServiceAccount") && manifest.includes("kind: Role") && manifest.includes("kind: RoleBinding"), "sentinel manifest must include minimal RBAC", manifest); assertCondition(manifest.includes("sub2api-account-sentinel-state"), "sentinel manifest must reference the state ConfigMap", manifest); assertCondition(manifest.includes("\"enabled\": false"), "sentinel manifest must preserve actions.enabled=false in config.json", manifest); assertCondition(!manifest.includes("sk-test-secret"), "sentinel manifest must not expose upstream credentials as plaintext", manifest); assertCondition(manifest.includes("profiles.json:"), "sentinel credentials Secret must include the profiles payload as Secret data", manifest); assertCondition(manifest.includes("\"budgetMode\": \"record-only\""), "sentinel runner must expose record-only budget/accounting mode", manifest); assertCondition(manifest.includes("max_workers=max(1, len(due))"), "sentinel runner must probe all due accounts concurrently", manifest); const disabledMonitor = { ...sentinel, monitor: { enabled: false }, actions: { ...sentinel.actions, enabled: false }, }; const suspendedManifest = renderCodexPoolSentinelManifest(disabledMonitor, [], { namespace: "platform-infra", serviceName: "sub2api", serviceDns: "sub2api.platform-infra.svc.cluster.local:8080", appSecretName: "sub2api-secrets", }); assertCondition(suspendedManifest.includes("suspend: true"), "monitor.enabled=false must suspend the CronJob", suspendedManifest); const pythonPath = join(tmpdir(), `sub2api-account-sentinel-${process.pid}.py`); writeFileSync(pythonPath, sentinelRunnerPython(), "utf8"); try { const pyCompile = Bun.spawnSync(["python3", "-m", "py_compile", pythonPath], { stdout: "pipe", stderr: "pipe", }); assertCondition(pyCompile.exitCode === 0, "sentinel runner python must compile", { exitCode: pyCompile.exitCode, stdout: pyCompile.stdout.toString(), stderr: pyCompile.stderr.toString(), }); } finally { rmSync(pythonPath, { force: true }); } console.log(JSON.stringify({ ok: true, checks: [ "sentinel has independent monitor/actions YAML switches", "observation-first rollout keeps actions disabled", "v1 scope is OpenAI Responses + GPT-5.5", "probe max_output_tokens is tightly capped", "probe concurrency is not artificially capped", "budget is record-only and does not gate probes", "success trust backoff is 1m to 20m", "freeze backoff is 10m to 120m", "CronJob is k8s-native with Forbid concurrency and minimal RBAC", "monitor switch controls CronJob suspend state", "rendered Secret avoids plaintext upstream credentials", "embedded Python runner compiles", ], }));