## Sentinel

- `sentinel.monitor.enabled`: 账号级 marker 哨兵监控开关；开启后 `codex-pool sync --confirm` 会在 `platform-infra` 创建/更新 k8s CronJob、ConfigMap、Secret、ServiceAccount、Role 和 RoleBinding。CronJob 直打 YAML-managed 上游账号的 OpenAI Responses `gpt-5.5`，用确定 marker 作为唯一健康标准，并在独立 state ConfigMap 中记录 token/cost 账本。
- `sentinel.actions.enabled`: 账号级哨兵冻结/恢复动作开关；当前 marker-only guard 要求开启。动作关闭时只记录 `would-freeze`，不会调用 Sub2API admin API 改 `schedulable`。动作开启后，只要不满足 marker match，不论是 HTTP 200 私货、4xx/5xx、非 JSON、连接错误还是空输出，都进入同一个冻结/恢复状态机。
- `sentinel.sdk.openaiPythonVersion`: 哨兵容器使用的 OpenAI Python SDK 固定版本；模型请求必须通过标准 SDK `responses.create`，不要手工拼 `/v1/responses` 请求体或手写响应解析。后续升级 SDK 只改 YAML 并 `sync --confirm`。
- `sentinel.probe.maxOutputTokens`: 哨兵本地流式 delta 收集上限，必须保持小值；它不作为上游 `max_output_tokens` 字段发送，以保持与 Sub2API WebUI 默认账号连接测试的 Responses SSE 请求形态一致。哨兵不限制并发和每轮账号数，所有到期账号会在同一轮并发探测。
- `sentinel.probe.userAgent`: 哨兵 direct upstream probe 的默认 User-Agent，通过 OpenAI SDK `extra_headers` 传递；默认贴近 Sub2API `net/http` 账号连接测试形态，个别账号仍可用 `profiles.entries[].upstreamUserAgent` 覆盖。
- `sentinel.cadence`: 成功信任指数退避配置。当前口径是从 1 分钟开始，连续成功后按账号 `trustUpstream` 选择可信/不可信最大退避；任意非 marker match 清零成功信任并进入冻结退避。可信/不可信最大退避数值只写 YAML。
- `sentinel.freeze`: 失败冻结 TTL 指数退避配置。当前口径是初始 1 分钟，失败后 `1m -> 2m -> 4m -> 8m -> 10m`，最大 10 分钟；失败 probe 基本不消耗有效输出 token，因此冻结窗口保持短周期。冻结到期后只做恢复 probe，通过才自动恢复，不能仅靠 TTL 到期解封。
- `sentinel.pricing`: 直打上游时哨兵自己的 token/cost 估算价格。因为 direct upstream probe 不经过 Sub2API 普通用量账本，哨兵必须自己记录全局与 per-account token/cost；这些账本只用于观察，不作为跳过探测的预算门禁。

`codex-pool sync --confirm` 会登录 Sub2API admin、创建/更新 group、创建/更新 YAML 中的 `unidesk-codex-*` accounts，并创建/复用统一 API key。k3s target 还会写入统一 API key Secret 并部署/更新哨兵资源；PK01 host-Docker target 只对齐 Sub2API runtime 和 YAML 声明的 host env key source，不创建 k8s Secret/CronJob，也不要求 sentinel runtime 存在。`sync` 不把既有 managed account 直接恢复为 `schedulable=true`。恢复只由哨兵在读取 Sub2API runtime `schedulable=false` 后触发 recovery probe，并在 marker 命中时执行。`sync` 默认不删除 YAML 中缺席的 managed account。只有明确退役上游时才使用 `sync --confirm --prune-removed` 删除缺席且 `extra.unidesk_managed=true` 的 `unidesk-codex-*` account。对 `manualAccounts.protected`，`sync` 只执行 YAML 显式允许的窄同步；当前允许项是从目标 `egressProxy` 创建/更新 Sub2API internal proxy 记录并绑定 `proxy_id`，以及把受保护手动账号加入当前 `pool.groupName`。它仍不接管该账号凭据、status、schedulable、priority/capacity/loadFactor 或哨兵状态。

`sentinel-image status|build` 管理哨兵 Python 运行环境镜像。镜像由 YAML 的 `sentinel.image` 基础镜像和 `sentinel.sdk.openaiPythonVersion` 派生，发布到目标 runtime 的本地 registry；`build --confirm` 会先检查 registry tag，存在则快速复用，不存在才在目标 host 构建并 push。k3s target 的 CronJob 必须使用这个派生 runtime image，不要退回基础 Python 镜像再在容器启动时 `pip install openai`。需要外网拉官方基础镜像或 Python wheel 时，构建脚本应读取目标 YAML/host-proxy 提供的 proxy env（例如 JD01 `/etc/unidesk/proxy.env`）并用 host network 让 `127.0.0.1` hostproxy 生效；不要为了这条路径新增镜像源。CronJob 启动脚本只做 `OPENAI_PYTHON_VERSION` 校验，版本不符才兜底安装。目标是否启用哨兵以 `config/platform-infra/sub2api.yaml` 的 `sentinel.enabledOnTargets` 为准；未启用的 target 在 `sync`/`validate` 中应显示 `skipped-target-disabled`，不得要求镜像构建、CronJob、Secret 或 state ConfigMap 存在。

`sync --confirm` 同时会按 YAML 渲染账号级哨兵资源，并在 monitor 开启时先确保可复用哨兵镜像存在。当前目标是 `sentinel.monitor.enabled=true` + `sentinel.actions.enabled=true` 的 marker-only 自动冻结/恢复；不要手工 patch CronJob、Secret 或 Sub2API account。若 YAML 新增账号或修改 profile/base URL/API key fingerprint/upstream User-Agent/Responses WebSocket mode，sync 会从变更前 runtime state 写入 pending probe 记录并立即安排 sentinel probe，但不会把既有账号直接恢复为可调度；只有 sentinel 读取到 Sub2API runtime `schedulable=false` 后执行 recovery probe，且 marker 命中，才恢复 `schedulable=true`。sentinel 冻结/恢复只改 `schedulable=false|true`，不得顺手调用 Sub2API `recover-state` 清除请求路径临时不可调度或其他 runtime backoff。无关账号的既有成功/失败退避不能被重置。若 YAML 下调失败冻结最大窗口，sync 会把仍 active 的旧冻结状态迁移到当前最大窗口内并立即安排 recovery probe，但不会直接解冻。若怀疑某个账号被误判，先用 `codex-pool sentinel-probe --account <accountName> --confirm` 立即触发该账号测量；该命令从现有 CronJob 模板派生一次性 Job，复用同一份 Secret、ConfigMap、OpenAI SDK probe、token/cost 账本和冻结/恢复状态机。默认输出应是短文本摘要；逐账号完整 state 只走 `--full`/`--raw`。

`trace --request-id <requestId>` 是只读 request 追溯报表，不触发 probe、不修改账号。默认输出请求开始/最终状态、failover、`account_select_failed`、窗口内 `account_temp_unschedulable`、admin schedulable 写入计数和当前账号快照；`reason=failover-attempted-no-candidate` 表示 Sub2API 已进入自动切号，但排除当前失败账号后没有可用候选。需要机器处理时使用 `--raw`，需要原始匹配行时加 `--show-lines`。

`sentinel-report` 是只读低噪声报表，不触发 probe、不修改账号。默认输出类似 `ps` 的文本表，展示每个账号的探测次数、Sub2API runtime `schedulable`、最近 marker/HTTP/动作、冻结 TTL、成功退避、下一次 probe 和最近 run 事件；`SCH` 展示 Sub2API runtime schedulable，`PROT` 展示账号级保护阈值，`P_FAIL` 展示最近一次保护确认中的失败次数/阈值；需要机器处理时使用 `sentinel-report --raw`。

对已支持的 k3s target，`sync --confirm` 和 `validate` 可能超过单次 SSH/runtime 短连接窗口。必须继续使用 `bun scripts/cli.ts platform-infra sub2api codex-pool ... --target <k3s-target>`，由 CLI 在目标远端提交作业并短轮询状态；不要改用裸 `trans <target>:k3s sh` 等一个长连接等待完整结果。若看到 `UNIDESK_SSH_RUNTIME_TIMEOUT`，先按 `docs/reference/platform-infra.md` 的规则处理为控制面可见性问题，修 CLI/job/poll 或重跑受控命令，不要手工 patch Sub2API credentials 或源码。
