## Codex Pool

- `codex-pool plan|sync|validate|runtime` 同时覆盖 k3s target 和 PK01 host-Docker target：
  - 查询 runtime 参数先用 `runtime <action> --help`：
    - 该入口只返回 action 适用的紧凑本地帮助；
    - 不读取远端状态、不创建 target job，也不展开平台顶层帮助；
  - PK01 host-Docker 的 `sync --confirm` 通过 Sub2API admin API 对齐 group、YAML-managed accounts、统一消费 API key、capacity/loadFactor、WebSocket v2 标记和内置 `temp_unschedulable` 规则；
  - 统一消费 key 写入 YAML 声明的 `targets[PK01].hostDocker.envPath`；
  - PK01 路径不创建 k8s Secret、不部署 sentinel 资源，也不触发 `sub2api apply`、Docker compose、Caddy reload 或容器重启；
  - `runtime list|get` 查询 pool group 内全部账号或指定账号的实际状态；`get` 同时披露脱敏 `modelMapping` 和完整临时不可调度规则；
  - 新增手工账号的模板接入使用最短路径：
    - 先执行 `runtime list --recent <duration>`，按原生 `created_at` 倒序查看新近账号、完整名称、ID、调度状态和现有模板；
    - 原生 ISO-8601 时间的小数秒先归一到 Python 可解析精度，避免非 3/6 位小数秒漏掉新账号；对首次详情投影中 `created_at` 缺失、但 `updated_at` 落窗的账号，命令自动精确重读一次；只有重读后的原生 `created_at` 落窗才纳入，`updated_at` 不作为新账号替代依据；默认摘要披露 rechecked、recovered 和 updatedUnknownExcluded 计数，并有界列出刚更新但创建时间仍无法解析的账号；
    - `created_at` 缺失的账号只计入 `unknownCreatedAt`，不猜测为新账号；
    - 选定后仍以精确 `--account <id-or-exact-name>` 依次执行 dry-run 和 confirm，禁止把“最新账号”隐式变成写入 selector；
  - 当前正式只读入口为 `runtime get --account` 和 `runtime errors --account --full`；前者读取账号配置与调度状态，后者读取原生 Ops 已观测模型证据，不把历史请求模型误报为实时上游能力；
  - `runtime infrastructure --account` 只读关联账号 proxy 绑定与基础设施状态：
    - 同时披露账号 proxy 记录和容器代理环境，不能因容器没有 `HTTP_PROXY` 就把账号误判为直连；
    - 采集容器网络模式、DNS、默认路由、接口计数器、proxy socket、监听进程、服务状态和近期错误日志；
    - 不发送上游账号测试或网络探针，不修改账号、proxy、调度或 YAML；
    - socket、接口计数器和服务状态是查询时快照，不能单独证明历史故障窗口内网络正常；
  - `runtime errors` 优先使用 Sub2API 原生 admin/ops 能力：
    - `--all-groups` 按稳定 group ID 游标分页返回所有分组概要；
    - `--group <name-or-id>` 返回指定分组的账号根因、策略效果和 request 索引；
    - `--platform <name>` 可筛选平台；省略分组参数时继续分析 YAML 当前 Codex pool；
    - 默认输出使用 Kubernetes 风格紧凑表格；机器读取必须显式使用 `--json`、`--full` 或 `--raw`；
    - dashboard 请求量和客户错误率按分组精确统计；
    - 指定分组下钻同时返回原生 `openai-token-stats` 模型时延与 `request-errors` 客户错误画像：
      - 模型时延只提供模型维度的平均 TTFT、平均总耗时和首 token 覆盖率；
      - 客户错误画像提供模型、账号、状态码、同步/流式、阶段、入口和精确请求路径分布；
      - 原生接口没有账号维度 TTFT 时禁止从错误账号分布推导账号 TTFT；
      - 默认同时给出两类有界 request ID 索引：
        - 客户错误索引区分可评分上游错误与排除的非上游错误；
        - 策略事件索引区分切号结果、选号失败和流式转发失败；
        - 使用 `trace` 对样本 request ID 做精确下钻，只有完整有限证据确有需要时才使用 `--full`；
    - 指定分组和 `--account` 下钻提供上游账号质量评分：
      - 成功请求分母和账号级 TTFT 来自原生 admin usage；
      - 同一张账号表必须聚合请求数、Token、标准 API 美元计费基数、账号名称末尾的人民币采购倍率和人民币上游成本；
      - 常规评分与成本评估只运行一次 `runtime errors --group ... --since ...`，不再分别调用 usage、profit 和逐账号查询拼表；
      - 默认文本使用账号评分与用量聚合单表：
        - 同表披露请求、Token、标准 API 美元计费基数、人民币采购倍率与上游成本、可归因失败率、TTFT、切号恢复和可用性；
        - 客户错误只保留有界桶摘要，完整账号证据使用 `--account`，完整有限索引使用 `--full`；
      - 客户错误来自原生 request-errors；
      - 切号、同账号重试、临时不可调度和 `forward_failed` 来自原生 system-log 索引；
      - 默认权重为可靠性 60、TTFT 25、当前可用性 15，并同时输出评分组成、等级、置信度和扣分原因；
      - TTFT 少于 5 个样本时不进入分母，不能把缺失时延当成零时延；
      - 至少 10 次账号尝试且至少 5 个 TTFT 样本时，才允许把高分映射为 A-D 可比较等级；样本不足的偶然高分标记为 `insufficient`；
      - 至少 10 次账号尝试且评分低于 60 时，即使 TTFT 缺失也保留 E 级，避免明确的高失败率被“证据不足”掩盖；
      - 请求证据不足时使用低置信度，不能据此自动调整 priority、capacity 或 schedulable；
      - 账号名称是人类可读运维标识，应与账号 ID 一起完整输出；API Key、token 和 credentials 仍必须脱敏；
      - 只有带 `group_id` 的 failover 和 `forward_failed` request ID 才进入分组账号失败率；账号级临时不可调度和同账号重试可以作为共享运维信号展示，但不加入分组失败分子；
      - 客户错误只有满足以下任一条件时才计入可靠性扣分：
        - 原生阶段或错误分类明确标记为 upstream；
        - 已选择账号且消息命中稳定的上游网关故障语义；
      - 以下错误必须显示在原始客户错误和排除原因中，但不扣账号分：
        - 上下文窗口超限和客户输入校验；
        - 模型路由或分组模型配置错误；
        - internal、client 或 business 阶段错误；
        - 没有选中账号或无法确认上游归属的客户错误；
      - 排除项必须按 request ID 去重，并输出 `scoreableUpstreamErrorRequests`、`excludedNonUpstreamErrorRequests` 和原因桶；禁止把未知归因默认算作上游失败；
    - 账号被多个分组共享时，账号级 upstream-errors 和缺少 `group_id` 的 runtime 事件属于共享证据，禁止跨分组相加；
    - dashboard overview 提供请求分母和错误率；
    - dashboard overview 的 `ttft` 提供成功流式请求的首 token 分位数，`duration` 提供成功请求总耗时分位数；二者不能互相替代；
    - `runtime errors` 只读披露运行实例实际生效的 OpenAI response-header timeout，并按 Sub2API 的 config file、process env、内置默认值优先级标注来源；通用 response-header timeout 不得解释为 OpenAI timeout；
    - timeout 候选表同时展示固定对照值和“TTFT P99 加 15 秒后向上取整到 15 秒”的观测下限；观测下限只用于排除明显过低值，不是可直接下发的安全推荐；
    - `/admin/ops/requests` 的总耗时长尾只按原生 `duration_ms` 统计；错误记录没有 `duration_ms` 覆盖时显示不可用，禁止把零记录解释为没有慢失败；
    - account availability 提供可用、限流、过载和错误状态；
    - concurrency 提供容量与排队；
    - upstream errors 提供账号根因；
  - 原生 system-log 索引提供事件时直接使用；索引未保留临时不可调度、failover 或 `forward_failed` 时，才从 target runtime 日志补这些事件，并显式披露 fallback 原因和数量；
  - `policyEffectiveness` 从同一日志窗口汇总临时不可调度、切号、无候选和网关最终响应，并且只通过 `request_id` 关联切号结果；
  - `requestDrilldown` 给出切号后成功、失败、结果缺失和选号失败的有限 request id，可继续交给 `trace` 下钻；
  - 只有 `account_temp_unschedulable` 事件才算规则命中，不能仅凭错误状态码推断模板生效；
  - `requestIdCoverage` 披露各类事件缺少 request id 的数量；缺失时不能把零关联解释为没有发生摘号后切号；
  - `forwardFailureWithHttpSuccessRequestCount` 单列 HTTP 200 与 `forward_failed` 并存的请求，避免把流式响应已提交后的错误误判为完整成功；
  - 网关最终错误占比只使用窗口内目标 API 路径的 `http request completed` 日志事件，必须同时披露窗口与 tail 边界，不能解释为完整业务错误率；
  - `--full` 额外输出模型、小时、流式分布和有限详情样本；
  - 当前并发是查询时快照，不代表错误发生瞬间并发；没有历史并发证据时，命令必须明确输出不可用，不能据此建议降低并发；
  - 错误列表没有账号全部路由尝试数时，命令只报告错误数量，不伪造错误率；
  - Ops 记录与 runtime 日志是独立证据，命令明确披露采样数量，不按日志位置强行关联；
  - `runtime apply|delete` 支持临时不可调度与模型映射；`runtime apply` 还支持账号优先级、名称和容量的精准 runtime 更新；
  - 单账号必须显式使用 `--account`，已知精准批量必须显式使用 `--accounts <id-or-exact-name,...>`，不提供隐式 `all`；
  - 更新已配置同类 runtime 策略的手工账号时，可显式使用 `--select-configured-policy --account-type apikey` 自动选集，并用 `--exclude-accounts <id-or-exact-name,...>` 排除例外：
    - 该入口只选择当前 credentials 已存在临时不可调度字段的账号，不把未配置账号自动纳入；
    - 不以 `matchingTemplate` 作为更新选集，因为模板内容变化后旧 runtime 不再与新 YAML 精确相等；
    - dry-run 必须披露候选数、已配置数、类型匹配数、排除账号、最终名称/ID 和选中来源；
    - confirm 继续复用批量预检、顺序写入和自动回读对账；
  - 批量操作先校验全部 selector 存在且账号类型可修改，再顺序调用 Sub2API admin API：
    - 选择错误不会产生部分写入；
    - 上游 admin API 在批次中途失败时仍需逐账号 `get` 对账；
  - runtime 模板必须按可复用错误语义设计，不得包含账号名称、账号 ID、provider URL 或分组特例；非 auth 错误使用精确状态码和稳定短语，auth 与账号状态错误保留专门处理路径；
  - 写操作不带 `--confirm` 时只返回 dry-run，带 `--confirm` 才通过 Sub2API admin API 写入；
  - 批量默认输出账号 ID、名称、change、mode、mutation 和汇总计数；runtime 命令不输出 API key 或完整 credentials，使用 `get`、`--full` 或 `--raw` 下钻；
  - 文档中的 runtime action 或参数被当前分支 CLI 拒绝时，按 CLI 集成漂移处理：
    - 不退回手写 Sub2API 管理 API。
    - 先确认当前工作分支包含对应受控实现，或完成语义合入后再操作运行面。
  - `sentinel-report`、`sentinel-probe`、`sentinel-image` 和部分 `trace` 能力仍以 k3s target 为主，需要这些能力时显式选择对应 k3s target。

k3s 账号池操作示例：

```bash
bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool sync --target D601 --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool validate --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool runtime list --target PK01
bun scripts/cli.ts platform-infra sub2api codex-pool runtime list --target PK01 --recent 24h
bun scripts/cli.ts platform-infra sub2api codex-pool runtime get --target PK01 --account <name-or-id>
bun scripts/cli.ts platform-infra sub2api codex-pool runtime get --target PK01 --account <name-or-id> --json
bun scripts/cli.ts platform-infra sub2api codex-pool runtime errors --target PK01 --account <name-or-id> --full
bun scripts/cli.ts platform-infra sub2api codex-pool runtime infrastructure --target PK01 --account <name-or-id> --since 30m
bun scripts/cli.ts platform-infra sub2api codex-pool runtime errors --target PK01 --since 24h
bun scripts/cli.ts platform-infra sub2api codex-pool runtime errors --target PK01 --since 24h --all-groups
bun scripts/cli.ts platform-infra sub2api codex-pool runtime errors --target PK01 --since 24h --group <name-or-id>
bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --target PK01 --account <name-or-id> --template <template-id>
bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --target PK01 --account <name-or-id> --template <template-id> --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --target PK01 --accounts <id-or-exact-name,...> --template <template-id>
bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --target PK01 --accounts <id-or-exact-name,...> --template <template-id> --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --target PK01 --template <template-id> --select-configured-policy --account-type apikey --exclude-accounts <id-or-exact-name,...>
bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --target PK01 --accounts <id-or-exact-name,...> --kind priority --priority <0-1000> --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --target PK01 --accounts <id-or-exact-name,...> --kind capacity --capacity <1-100000> --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --target PK01 --account <id-or-exact-name> --kind account-name --name <new-name> --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool runtime delete --target PK01 --account <name-or-id> --kind temp-unschedulable
bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --target PK01 --account <name-or-id> --kind model-mapping --model <client-model> --upstream-model <upstream-model>
bun scripts/cli.ts platform-infra sub2api codex-pool runtime delete --target PK01 --account <name-or-id> --kind model-mapping --model <client-model>
bun scripts/cli.ts platform-infra sub2api codex-pool error-passthrough list --target PK01
bun scripts/cli.ts platform-infra sub2api codex-pool error-passthrough apply --target PK01 --template <template-id>
bun scripts/cli.ts platform-infra sub2api codex-pool error-passthrough apply --target PK01 --template <template-id> --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool error-passthrough delete --target PK01 --template <template-id> --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --since 24h
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --since 24h --json
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --since 24h --accounts
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --since 24h --missing-costs
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --month 2026-07
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --month 2026-07 --forecast
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --month <YYYY-MM> --forecast --sale-rate <CNY/API_USD> --scenario-without-account '<exact-account-name>'
bun scripts/cli.ts platform-infra sub2api codex-pool credits grant --target PK01 --active-since 24h --reason <text>
bun scripts/cli.ts platform-infra sub2api codex-pool credits grant --target PK01 --active-since 24h --amount-usd 20 --reason <text>
bun scripts/cli.ts platform-infra sub2api codex-pool credits grant --target PK01 --active-start 2026-07-14T17:00:00 --active-end 2026-07-14T18:00:00 --amount-usd 10 --reason <text>
bun scripts/cli.ts platform-infra sub2api codex-pool credits grant --target PK01 --active-since 24h --rank-tiers '<count>:<amount>,*:<amount>' --reason <text>
bun scripts/cli.ts platform-infra sub2api codex-pool credits grant --target PK01 --allocations-json '<selector-to-amount-object>' --reason <text> --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool announcements list --target PK01
bun scripts/cli.ts platform-infra sub2api codex-pool announcements get --target PK01 --id <announcement-id>
bun scripts/cli.ts platform-infra sub2api codex-pool announcements create --target PK01 --title <text> --content <markdown> --targeting-json '{"any_of":[]}'
bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --target D601 --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --target D601 --account unidesk-codex-hy --confirm
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D601 --confirm
```

- `error-passthrough` 只管理 `runtime.errorPassthroughRules` 声明的官方全局错误透传规则：
  - 使用 Sub2API 原生 `/api/v1/admin/error-passthrough-rules` CRUD，不修改 Sub2API 源码；
  - 使用 YAML `id` 选择模板，使用唯一 `name` 与运行面规则对账；
  - `apply` / `delete` 默认 dry-run，只有 `--confirm` 才写入，写后必须自动回读并显示 `synced` / `absent`；
  - 错误码与关键词需要同时约束时使用 `matchMode: all`，避免仅凭通用 400/502 扩大匹配；
  - `passthroughBody: true` 可保留上游具体客户错误，`skipMonitoring: false` 保持原生运维事件可见；
  - 规则是 Sub2API 全局事实，不按账号或分组复制，也不得用于把应当 failover 的真实非 auth 上游故障伪装成客户错误。

- `profit` 只读聚合 Sub2API 原生 `/api/v1/admin/usage`：
  - 默认窗口、盈利分组、自用分组、排除分组、人民币售价和账号采购成本来源由 `config/platform-infra/sub2api-codex-pool.yaml#profit` 声明；
  - 原生 `actual_cost` 是标准 API 美元计费基数；`saleRateCnyPerApiUsd` 和账号名称末尾成本都表示每 1 美元标准 API 计费对应的人民币单价，不做汇率换算；
  - `periodCosts` 声明按 `YYYY-MM` 入账的一次性人民币固定成本；`--month YYYY-MM` 使用原生 `/api/v1/admin/usage/stats` 按分组和账号聚合该月流水并一次扣除匹配期间成本，任意 `--since` 窗口继续读取逐条 usage 以保持滚动窗口精度且不分摊固定成本；
  - 账号调度经济性使用边际采购倍率，不把已经发生的一次性期间成本摊回请求成本或用于压低账号优先级；月度净利润必须单列期间成本后再计算；
  - `--month YYYY-MM --forecast` 以月初至当前的实际秒数计算线性 run-rate：收入、盈利池流量成本和自用流量成本按完整月份秒数外推，一次性 `periodCosts` 只扣一次；同时保留 MTD 实际汇总和两种整月预计利润，不能把预测值表述为已实现收益；
  - 候选售价或账号退役评估必须在同一次利润查询中使用情景参数：
    - `--sale-rate <CNY/API_USD>` 只覆盖本次只读核算售价，不修改 YAML；
    - `--scenario-without-account <id-or-exact-name>` 可重复指定多个精确账号；CLI 自动移除绑定这些账号的期间成本，并把其盈利池和自用池流量分别按各自剩余账号的实际加权采购倍率重分配；
    - 情景汇总直接披露理论利润上限、重分配后的两种利润、日均营业额与成本、账号流量占比、替代成本和剩余账号负载增幅；不要再使用 `--accounts` 分页后手工拼算；
    - 账号 selector 不存在时必须失败；剩余账号存在未知成本、没有可用计费基数或仍有未分类流量时必须标记 `partial`，禁止输出伪精确利润；
    - 负载增幅只表示容量需求，不等价于剩余账号能够稳定承接；退役建议还需结合账号质量、并发和客户可见错误评估；
  - 默认 Kubernetes 风格输出优先给出总收入、已确认成本、扣除已知成本后的待定利润基数、未知成本对应的 API USD 基数、完整性和参数化利润公式；
  - 显式 `--json` 返回同一份稳定紧凑汇总，不携带完整账号数组，避免大窗口触发全局输出保护；
  - `--accounts` 固定返回一页完整账号名称、账号 ID、请求数、Token、标准 API 美元计费基数、人民币采购单价、人民币收入、人民币上游成本和人民币利润贡献；存在后续页时返回由分类、分组、账号 ID 和名称生成的 opaque `--page-token` 命令，不提供手工 `limit`；
  - `--missing-costs` 单独返回缺失成本账号的完整名称、账号 ID、分组、分类和未知成本 API USD 基数，避免与账号分页叠加后再次触发全局输出保护；
  - 汇总同时给出“不扣自用成本”和“扣除自用成本”两种口径，自用分组不计收入；缺失成本时 `profitBeforeUnknownCostsCny` 只表示扣除已知成本后的待定利润基数，最终利润必须按输出公式继续扣除未知账号成本，禁止把未知成本当作 0；完整状态下该基数才等于最终利润；
  - 账号名称末尾缺少采购成本系数，或存在未分类流量时，相关口径必须显示 `partial`，禁止把缺失值当作零；
  - `--json` 保留精确十进制字符串、原生来源、时间窗、分页数和完整性明细；命令不修改 runtime。

- `credits grant` 管理客户余额补偿：
  - `--active-since <duration>` 从原生 usage 选取相对窗口内有请求的非管理员活跃用户；
  - `--active-start <ISO> --active-end <ISO>` 使用精确半开区间 `[start,end)`；未携带 UTC 偏移的输入按 `config/platform-infra/sub2api-codex-pool.yaml#credits.timezone` 解释，并同时披露本地与 UTC 边界；
  - `--users <id-or-email,...>` 使用精确 selector；相对窗口、固定窗口和精确 selector 三者必须且只能选择一个；
  - `config/platform-infra/sub2api-codex-pool.yaml#credits.excludedUsers` 声明不得获得客户补偿的精确用户 ID 或邮箱；活跃窗口和显式用户模式都必须排除，并在默认表格中披露匹配 selector 与原因；
  - 默认 dry-run，逐用户披露完整邮箱、用户 ID、请求数、原生 `input_tokens` / `output_tokens` / 总 Token、Token 字段覆盖情况、当前余额、增加额和目标余额；Token 字段缺失时标记 `partial`，不把缺失值伪装成完整零消耗；
  - 活跃窗口只允许生成计划；实际写入必须把 dry-run 返回的用户 ID 固定为精确 `--users` selectors，再显式 `--confirm`，防止移动窗口在确认时扩大或缩小名单；
  - 按排名分档发放使用固定计划入口：
    - 活跃窗口未显式提供 `--amount-usd`、`--rank-tiers` 或 `--allocations-json` 时，读取 `config/platform-infra/sub2api-codex-pool.yaml#credits.defaultRankTiers` 生成默认排名计划；
    - YAML 使用若干正整数 `count` 档和一个最终 `remaining: true` 档声明默认阶梯，具体人数与金额只以 YAML 为准；
    - 显式 `--amount-usd` 或 `--rank-tiers` 覆盖 YAML 默认，输出必须披露 `rankTiersSource`；
    - dry-run 在活跃窗口增加 `--rank-tiers '<人数>:<金额>,...,*:<金额>'`；
    - CLI 按总 Token 降序、请求数降序、用户 ID 升序稳定排名；
    - 最后一档必须使用 `*:<金额>` 覆盖剩余合格用户；
    - 输出同时披露排名、Token、逐人金额、总额和可直接确认的 `--allocations-json`；
    - 排名窗口禁止 `--confirm`，避免移动窗口改变名单；
    - 用户明确确认固定窗口、完整用户 ID、逐人金额、总额和原因后，只需把原样 `--allocations-json` 加 `--confirm` 执行一次；
    - `--allocations-json` 支持不同用户使用不同正数金额，并在一个批次内逐用户写入和回读余额；
    - 计划身份由规范化后的用户 ID、逐人金额和原因共同决定，重试使用同一幂等键；
    - 统一金额的 `--users ... --amount-usd ...` 入口及其既有幂等身份保持不变；
  - 排名分档发放的最短调用链：
    - 第一次 credits 调用生成排名计划，必须为 dry-run；
    - 人工审核并明确授权计划输出，不额外重新抓取移动窗口；
    - 第二次 credits 调用使用固定 `--allocations-json --confirm` 完成全部异额发放和回读；
    - 公告继续使用独立的 create dry-run 与 confirm，正文从已确认计划生成；
    - 禁止把充值与公告伪装为原子事务；任一侧失败都必须分别披露实际状态和可重试入口；
  - 确认写入为每个用户生成由精确名单、金额和原因确定的 `Idempotency-Key`，调用原生 `POST /api/v1/admin/users/:id/balance` 的 `add` 操作，随后逐用户回读余额并对账；
  - 实际批量充值属于外部财务影响操作，执行前必须取得用户对本次具体名单、金额和原因的明确授权；仅提出设计目标或要求 dry-run 不构成授权。

- `announcements list|get|create` 管理 Sub2API 原生公告：
  - `list` 和 `get` 只读调用原生 `/api/v1/admin/announcements`，支持分页、状态、搜索、排序和按 ID 下钻；
  - `create` 支持标题、Markdown 内容、`draft|active|archived`、`silent|popup`、原生 `any_of/all_of` targeting 以及可选 Unix 秒或 RFC3339 开始/结束时间；targeting 必须通过 `--targeting-json` 的 JSON 解析器输入，不拼接临时结构；
  - 每次 create 的用户可见正文都必须明确业务时间范围和接收范围；`targeting` 只控制投递对象，不能替代公告正文中的业务适用范围；
  - 补偿公告必须在正文逐项列出：
    - 故障影响时间窗或补偿资格统计时间窗；
    - 全部实际接收用户的稳定用户 ID 和邮箱；邮箱可以脱敏，但禁止只写“符合条件的用户”或其他无法对账的泛化描述；
    - 每名用户的补偿金额和本批总额；候选名单与实际发放名单不一致时，两者及排除原因都要明确；
  - create dry-run 和 confirm 输出都显示人工范围检查；CLI 不根据标题关键词猜测公告类型，也不把内容出现某些词误判为范围已经完整，操作者必须在 confirm 前核对正文；
  - `create` 默认只输出完整预览，固定 `mutation=false`、`writeAttempted=0`，不登录运行面也不调用创建 API；
  - 只有显式 `--confirm` 才调用原生创建 API，并按返回的正整数 ID 回读；对账必须逐字段核对 `title`、`content`、`status`、`notify_mode`、`targeting`、`starts_at`、`ends_at`，输出 `mismatchedFields`，任一字段不一致都不得报告成功；发布公告属于面向客户的外部影响操作，必须取得用户对本次具体标题、内容、状态、通知方式、targeting 和时间窗的明确授权；
  - 默认输出 Kubernetes 风格紧凑文本，显式 `--json` 输出同一份机器结构；命令不打印管理凭据或 Secret。

`config/platform-infra/sub2api-codex-pool.yaml` 控制：

- `pool.groupName`: Sub2API group 名称。
- `pool.apiKeySecretName` / `pool.apiKeySecretKey`: 统一消费 API key 的 key 名。k3s target 写入对应 namespace 下的 k3s Secret；PK01 host-Docker target 写入 `config/platform-infra/sub2api.yaml` 中 `targets[PK01].hostDocker.envPath` 声明的 env 文件。
- `pool.minOwnerBalanceUsd`: pool key owner 最低余额，sync/validate 会补齐。
- `pool.minOwnerConcurrency`: 可选统一消费 API key owner 最低并发；省略时 CLI 自动使用所有已解析账号 capacity 的总和，sync/validate 会补齐。显式 YAML 值只作为 override，仍必须不小于账号 capacity 总和；未显式写 `profiles.entries[].capacity` 的账号会使用 `pool.defaultAccountCapacity` 参与求和，不要用提高某个 provider capacity 来掩盖用户并发层 WS 1013。
- 普通测试用户 fixture 使用 `codex-pool users ensure`：
  - 默认只输出用户、Key 和凭据 sourceRef 计划；
  - `--confirm` 才在目标侧复用原生用户 API 创建或更新普通用户，并确保一个用户 API Key；
  - 密码由目标侧随机生成，只写入本机 owner-only `.state/secrets/sub2api-test-users/`，CLI 仅显示 sourceRef、presence 和 fingerprint；
  - 禁止把该入口用于现有客户账号或管理员账号。
- `runtime.templates`: 可复用 runtime 规则模板；模板存在不表示账号已绑定，也不自动接管手工账号。
- `pool.defaultTempUnschedulableTemplate`: YAML-managed 账号默认引用的临时不可调度模板。`sync --confirm` 只把模板渲染到 YAML-managed 账号。
- `profiles.entries[].tempUnschedulableTemplate`: 可选账号级模板引用，只用于明确偏离默认模板。
- YAML 未声明的账号标记为 `runtime-manual`；它可以显示 `matchingTemplate`，但模板差异不是 drift，也不能由 `sync` 自动改写。
- YAML 只保存可复用规则模板和 managed account 的模板引用。未在 YAML 预配置的手工账号 runtime 配置必须使用 CRUD，不得为了批量下发而把账号纳入 YAML managed profile。
- 手工账号需要采用模板时：
  - 写入使用显式 `runtime apply --account ...` 或 `--accounts ... --template ... --confirm`。
  - 删除使用相同显式 selector 的 `runtime delete --kind temp-unschedulable --confirm`。
- 账号全局 `priority` 使用 `runtime apply --kind priority --priority <0-1000>`：
  - 数值越小优先级越高，不存在分组级 priority；
  - 单账号和精准批量都必须先 dry-run，确认写入后逐账号回读并显示 before、desired、actual 与 reconciled；
  - 只更新账号 priority，不携带 credentials、分组、容量、代理、状态、可调度或临时不可调度字段。
- 运行时账号并发保护使用 `runtime apply --kind capacity --capacity <1-100000>`：
  - 单账号和精准批量账号都必须显式选择，默认 dry-run，确认后顺序写入并逐账号回读；
  - 只更新 Sub2API 原生账号 `concurrency` 字段，不携带 priority、credentials、分组、代理、状态或可调度字段；
  - 只有错误时段的并发重建或同等级原生证据表明上游实际并发上限低于本地 capacity 时才收敛，不能用查询时快照代替错误时证据；
  - YAML-managed 账号的长期容量仍由 owning YAML 管理；未在 YAML 中预配置的手工账号使用该 runtime CRUD，不纳入 YAML profile。
- 账号人类可读名称使用 `runtime apply --kind account-name --name <new-name>`：
  - 只接受单个精确账号 selector，先 dry-run，确认后自动回读名称；
  - 只更新名称，不携带 credentials、priority、分组、容量、代理、状态或可调度字段。
- 单账号成本标注与优先级调优使用以下最短路径：
  - 已知账号 selector 时，首个诊断入口固定为：
    `runtime errors --target <target> --since <window> --group <group> --account <account>`；
  - 同一次账号级查询直接读取评分、可归因上游失败率、TTFT、用量、Token、标准 API 计费基数、当前名称成本标注和 priority；
  - 只有需要确认 capacity、schedulable、proxy、临时不可调度规则或上游 5h/7d 周窗口保护时，才补一次 `runtime get --account <account>`；
  - 只有需要跨账号比较时才使用全组 `runtime errors --group <group>`；单账号任务不得先跑全组大报表，再重复账号级查询；
  - priority 数值越小越优先，`0` 合法且最高；这是稳定合同，除非真实调度结果冲突，不得重复检索源码确认方向。成本只参与人工经济性判断，必须同时考虑可靠性、TTFT、容量、可调度状态和样本置信度，不得按成本自动排序；
  - 名称成本标注和 priority 是两个独立 runtime 字段：分别先 dry-run，再顺序 confirm；禁止并发更新同一账号，避免整对象更新互相覆盖；
  - 两项 confirm 已各自回读一致后，最后只执行一次 `runtime get` 验收完整状态；不重复 confirm，不用全组查询代替账号回读。
- 模型路由先用 `runtime get --account ...` 获取账号配置，再用 `runtime errors --account ... --full` 获取原生 Ops 已观测模型证据：
  - 只有实时模型列表或真实模型请求证明支持时才确认写入。
  - 禁止把名称相近的模型猜测为别名。
  - 禁止用错误映射掩盖 `model_not_found` 或无候选。
- runtime 模板与外部 `sentinel.*` 分开配置、互不驱动。内置规则负责请求路径即时冷却和 failover；哨兵负责 marker health、账号级隔离、恢复和 probe 退避。
- 外部 sentinel 的写入面只允许通过 Sub2API admin `schedulable` 接口冻结/恢复账号；不能写入、清理或间接清理 `temp_unschedulable_until` / `temp_unschedulable_reason`、rate-limit、overload、model-rate-limit 等 Sub2API 请求路径 runtime 状态，也不能调用 `recover-state` 作为恢复动作。看到 UI 里的“触发时间/解除时间/规则序号/匹配关键词”临时不可调度状态时，默认先归因到 Sub2API 内置 request-path temp-unschedulable，而不是 sentinel。
- YAML 只选择和配置 Codex 上游，不声明 `schedulable` 长期字段；`codex-pool sync --confirm` 不负责把既有账号恢复为 `schedulable=true`。既有账号的 `schedulable=false` 必须由哨兵先同步 Sub2API runtime 状态，再在 marker probe 命中后恢复。
- `profiles.entries`: 从 master `~/.codex/` 选择上游 profile 并映射到 Sub2API account。
- `profiles.entries[].capacity`: 可选 per-account concurrency override；不写则使用 `pool.defaultAccountCapacity`。具体数值只以 `config/platform-infra/sub2api-codex-pool.yaml` 为准，skill 和长期参考只描述规则，不重复写当前值。
- `profiles.entries[].loadFactor`: 可选 per-account Sub2API `load_factor` override；不写则使用 `pool.defaultAccountLoadFactor`。具体数值只以 `config/platform-infra/sub2api-codex-pool.yaml` 为准，修改后必须 `codex-pool sync --confirm` 和 `codex-pool validate`。
- `profiles.entries[].trustUpstream`: 可选账号级哨兵信任标记；默认 `false`。可信账号使用 `sentinel.cadence.trustedSuccessMaxIntervalMinutes` 作为连续成功后的最大探测退避，不可信账号使用 `sentinel.cadence.untrustedSuccessMaxIntervalMinutes`。它只影响哨兵探测频率和状态可见性，不改变 Sub2API account priority/capacity/loadFactor。
- `pool.defaultSentinelProtect`: 账号级哨兵保护默认策略；是否启用、连续确认次数、初始延迟、最大延迟和退避倍率都只以 YAML 为准。marker probe 或 gateway failure 触发冻结前会先按该策略做连续 marker 确认，只有全部失败才进入冻结状态机。
- `profiles.entries[].sentinelProtect`: 可选账号级哨兵保护覆盖；只用于明确偏离 pool 默认策略。它只影响哨兵冻结判定和 `sentinel-report` 可见性，不改变 Sub2API account priority/capacity/loadFactor。
- 除非用户明确要求修改配置，不要仅凭推断改账号 membership、priority、capacity、loadFactor、WebSocket mode 或其他调度策略；先保留 YAML，完成 provenance/runtime evidence 溯源，并把结论写回相关 issue 或 runbook 后再提出变更。
- Sub2API 是 UniDesk 可读源码和可观测运行面的受控组件；排查 Sub2API 调度、failover、错误传播、临时不可调度或 account selection 时，默认先读当前 Sub2API 源码实现，再用真实 request id、Sub2API 日志和原入口流量验证。不要用 mock upstream、临时 probe account 或测试桩作为默认结论来源；这类探针最多是显式 debug 辅助，不能替代源码链路和真实运行证据。
- `profiles.entries[].openaiResponsesWebSocketsV2Mode`: 需要 Responses WebSocket v2 的上游才设置，值为 `off`、`ctx_pool` 或 `passthrough`。
- `profiles.entries[].upstreamUserAgent`: 少数要求 Codex CLI User-Agent 的上游才设置，不能含换行。
- `manualAccounts.protected`: 已在 Sub2API 手动创建/维护、且必须排除在 UniDesk-managed Codex pool credentials 和 sentinel 控制之外的账号。默认不得改 credentials/status/schedulable/priority/capacity/loadFactor；只有显式声明 `proxyBinding` 时，`sync --confirm` 才允许把该账号的 `proxy_id` 对齐到 YAML 目标的 egress proxy；只有显式声明 `groupBinding.source: pool-group` 时，才允许把该账号加入统一消费 API key 使用的 pool group。`targetIds` 可选；省略表示所有 target 都保护该账号，设置后只在匹配 target 上纳入 proxy/group 窄同步和 sentinel-probe 拒绝列表，避免 PK01-only 手动账号漂移卡住 JD01 pool。
- Sentinel 配置、marker-only 判定、镜像、report/probe 和远端 job/poll 边界见 [sentinel.md](sentinel.md)。

`sync --confirm` 会登录 Sub2API admin、创建/更新 group、创建/更新 YAML 中的 `unidesk-codex-*` accounts，并创建/复用统一 API key。k3s target 还会写入统一 API key Secret 并部署/更新哨兵资源；PK01 host-Docker target 只写 Sub2API runtime 和 host-Docker env 文件。host-Docker `validate` 也先使用 env 中的 Sub2API admin 凭据登录管理 API；如果 env 的统一 `API_KEY` 缺失或不匹配，它只通过管理 API 恢复或创建名为 YAML `pool.apiKeyName` 的统一消费 key 并写回 host-Docker env，不读取本地 profile 文件、不创建/更新账号、不 prune、不改变调度策略。`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 或哨兵状态。若受保护手动账号在运行面缺失，`sync`/`validate` 会报告 manual account drift；不要自动创建、删除、接管或从 YAML 移除该账号。

`trace --request-id <requestId>` 是只读 request 追溯报表，不触发 probe、不修改账号。命令优先使用原生 `/admin/ops/requests` 获取请求结果，并使用 `/admin/ops/system-logs` 获取 request-id 索引事件；target runtime 日志只补原生索引未保留的邻近事件。报表显式输出 target、runtime mode、主数据源、上下文数据源和读取状态。默认输出请求开始/最终状态、failover、`account_select_failed`、`forward_failed`、窗口内 `account_temp_unschedulable`、admin schedulable 写入计数和当前账号快照。`forward_failed` 与 HTTP 200 并存且没有 failover 时，结果标记为 `degraded`，不能解释为完整流式成功；`reason=failover-attempted-no-candidate` 表示 Sub2API 已进入自动切号，但排除当前失败账号后没有可用候选。需要机器处理时使用 `--raw`，需要原始匹配行时加 `--show-lines`。

近期故障分析默认使用短窗口，并优先接受原生 Ops 证据。`trace` 在原生 requests 详情缺失时可在同一 `--since` 窗口内查询原生 request-errors；只有原生 system-log 索引缺失时才使用 runtime 日志补事件。历史日志已过保留期不构成运行面缺陷，也不应阻塞近期根因判断。

`trace` 的时间线以原生索引事件时间戳为主。原生 request 记录只有完成时间、但最终日志带 `latency_ms` 时，命令用“完成时间减总耗时”重建参考起点，并披露首次 failover、选号失败和最终响应相对耗时。Sub2API Ops 没有客户 deadline 时，客户剩余时间必须显示不可用，不能用 `latency_ms` 推断。

对已支持的 k3s target，`sync --confirm`、`validate` 和 sentinel 操作可能超过单次 SSH/runtime 短连接窗口；远端 job/poll 边界见 [sentinel.md](sentinel.md)。

不要给 UniDesk-managed Codex accounts 开 Sub2API `pool_mode`。UniDesk 期望的 failover 是把失败账号临时标记为 unschedulable，让同组其他账号接手；`pool_mode` 会重试同一个 account path。

WebSocket v2 是账号能力集合，不是调度 pin。`openaiResponsesWebSocketsV2Mode` 只声明该账号可承担 Codex Responses WSv2 链路；只有 `localCodex.supportsWebSockets=true` / `localCodex.responsesWebSocketsV2=true` 时，`codex-pool validate` 才必须看到至少一个 `webSocketsV2.schedulableEnabled` 账号。真实可用性仍以 direct Codex WSv2 probe、Sub2API 日志和原入口 Codex smoke 为准。

Codex 启动时反复出现 WebSocket reconnect、HTTPS fallback、`websocket closed by server before response.completed`，或 Sub2API 日志出现 `openai.websocket_proxy_failed` / `openai.websocket_account_select_failed` / 上游 WS handshake 4xx/5xx 时，先按运行证据定位具体 account 和 transport。若账号的 WSv2 握手失败，优先只在 YAML 中把该账号的 `openaiResponsesWebSocketsV2Mode` 收敛为 `off`；若没有任何 direct Codex WSv2 probe 通过，则同时把 `localCodex.supportsWebSockets` 与 `localCodex.responsesWebSocketsV2` 收敛为 `false`，再 `codex-pool sync --confirm`。不要顺手改 membership、priority、capacity、Secret 或代码 fallback。
