# Auto-oncall 单 Ticket Optimizer

> Status: Active design and operating contract
> Updated: 2026-09-01
> Scope: 由持久化 `/ao wrong|revise`、`human_better`、运行时降级信号或人工 `/ticket` 明确指出的单个 Auto-oncall bad case

本文涉及的对客/对内消息身份、replay、版本和调查输入统一遵守
`docs/DUAL_MESSAGE_CONTRACT.md`。

## 1. 解决的问题

Recent Focus 解决的是至少两个 case 共同证明的可复用问题。它不适合处理已经由一个
ticket 独立证明、可以用确定性测试验收的有界问题，例如：稳定且有官方来源的产品知识
缺失、确定性的 Provider query/validator/materialization 失败、已有 evidence 在 handoff
丢失，或者安全 fallback 掩盖了当前可复现的局部 orchestration timeout。

单 Ticket Optimizer 提供第二条独立链路：人先通过反馈或命令指定一个 bad case，Agent 自主查看该
ticket 的完整当前上下文，判断它是确定性工程缺陷、Focus 候选、已经修复/没有问题，
还是证据不足。它可以同时记录多个独立 finding，但每个 job 最多选择一个满足确定性准入
的 finding 继续自动修改、合并、部署和验证；同票的诊断策略问题仍保留为 Focus 信号。

它不是 replay 的子功能，也不把所有单 case 诊断问题都伪装成工程 bug。Replay 是它
读取的一种事实来源；入口和状态归属于 bad-case optimization。

## 2. 与 Recent Focus 的职责边界

| 维度 | 单 Ticket Optimizer | Recent Focus Optimizer |
| --- | --- | --- |
| 触发 | `/ao wrong|revise`、`human_better`、查询/runner 降级自动触发；`/ticket` 手工补跑 | 日报、反馈和 runtime signal 聚类 |
| 样本 | 恰好一个 ticket | 至少两个同根因 target |
| 目标 | 单 case 已证明的稳定知识、确定性查询或局部编排契约违例 | 可复用的诊断、语义、策略或跨 case 问题 |
| 验收 | 一个确定性 regression test/运行时 criterion | baseline、challenger、controls、candidate gate |
| 状态 | optimizer 自己的 `single_ticket` job | GitHub Focus Issue/checkpoints |
| 输出 | 无修改结论，或自动 merge、deploy、replay、Jira refresh 和 verified record | Focus lifecycle 与通过 gate 的 Draft PR |

两条链路互不消费状态。单 ticket job 不创建、更新、阻塞或关闭 Focus；Focus 也不能把
一个 ticket 的所有问题整体标记为已解决。若一张票同时存在确定性 knowledge/query/fallback
问题和诊断语义差异，前者可被选中修复，后者仍保留；如果只得到诊断语义差异、通用时长
趋势或必须借助其他 case 验证的结论，则返回 `focus_candidate`，等待正常 problem-signal 聚类。

## 3. 触发契约

自动入口是 `auto-oncall-services` 持久化的 problem signal。Jira `/ao wrong|revise`、
LLM Judge `human_better`、成功 run 中 `evidenceAcquisition=degraded|failed`，以及
`scenario_router_timeout|final_response_timeout` runner notice 都写入同一耐久候选表；同一个
`auto-oncall-optimizer` Deployment 每分钟从内部只读接口按递增 candidate ID 拉取：

```text
GET /v1/auto-analysis/optimizer-candidates?afterId=<cursor>&limit=<batch>
```

首次启用只读取 `mode=latest` 并持久化 activation cursor，不补跑已有历史候选；之后每个
new candidate ID 恰好映射到一个 `single_ticket` job，入队成功后才推进游标。候选使用稳定
`signalKey` 去重，游标与 job 一起落在原 optimizer PVC，重启后继续；
`messageId=auto-candidate:<candidateId>` 提供第二层幂等。feedback collector 原有采集周期
不变；其他信号在 run 或 evaluation 落库时同步产生。

Feedback collector 在 lookback 内读取 Jira source comment 的当前正文。标准命令使用
`/ao revise: 原因`，同时接受 `.`、`。` 和旧评论使用的纯空格作为自然语言分隔符，并将
命令行后的多行正文保留为完整 reason。兼容命令仍必须占据一个完整的 trim 后行，并以
精确 `/ao` 和 action 开头。同一 source comment 被编辑后只更新已有 feedback 的 action/reason，
不得改绑到编辑期间出现的其他答案版本；如果此前因格式无法识别而尚未落库，编辑后的
有效 `wrong|revise` 首次落库仍是一个新的 feedback ID，并按本节既有规则触发一次任务。

旧 case、activation cursor 之前的反馈和人工指定观察重点，仍可用现有 Feishu bot 手工
补跑：

```text
@bot /ticket APID-29232 [可选的问题描述]
```

系统不会把每个正常输出都当作 bad case；只有上述显式 feedback、`human_better` 或确定性
运行时降级信号触发。命令必须包含恰好一个合法 Jira issue key；可选描述、Judge gap 和
signal reason 只限定观察重点，不是预设根因。

该命令可能自动合并并触发 staging replay，因此沿用原 optimizer 的写任务授权：允许群中的精确 bot
mention 可以触发；私聊必须属于 PR sender allowlist。重复 Feishu `message_id` 继续由
原 job store 幂等去重。命令入口 allowlist 只控制入站消息，不控制自动结果发往哪里；
状态通知群不会因为接收自动结果而成为新的 `@bot` 命令入口。

## 4. 输入上下文

Orchestrator 在把任务交给 Agent 前，从 `auto-oncall-services` 的显式 HTTP API 收集：

1. 自动候选使用 `GET /v1/auto-analysis/jobs/:jobId` 读取 signal 绑定的 job/run，并要求
   issue key 和 answer fingerprint 都与候选一致；手工 `/ticket` 使用
   `GET /v1/auto-analysis/issues/:issueKey/latest`；
2. `POST /v1/tickets/analyze` with `mode=read`：在当前已部署运行时上重新读取 Jira、保持
   canonical cutoff/filter 语义并执行一轮 fresh replay/judge；该 response 必须保留
   `customerReplyMarkdown` 对客消息和 `internalHandoffMarkdown` 对内消息，并通过
   顶层 `answerFingerprint` 返回这对新消息的规范版本，同时通过
   `runtimeTrace.calls[]` 返回本次运行实际使用的 Agent thread 索引。Evaluation 还必须以
   `humanAnswerMarkdown` 返回本轮实际交给 Judge 的完整同阶段人工答案；无可比较人工答案时
   该字段为 `null`，不能只留下 comment digest 和 Judge 摘要。Fresh execution 中
   `failureCodes` 只保留最终去重结果用于 execution status 和候选触发；
   `providerQueryAttempts` 必须保留每个 Managed Provider query 的完整安全尝试顺序，供
   investigator 判断中间失败、请求纠正、supplemental query 和最终结果；
3. 显式强制更新 `refs/remotes/origin/main` 后，从该 ref 创建并校验 HEAD 的独立 Git
   worktree；context 和 job 同时保存实际 `repositoryBaseSha`，不得使用缓存的本地 `main`；
4. 人工提名文本。

这些内容写入任务 worktree 根目录的临时只读
`.auto-oncall-single-ticket-context.json`。Agent 不直接获得 Jira、GitHub 或 Feishu
orchestrator secret，也不负责调用写接口。为验证稳定 product knowledge，调查 Agent 只可读取
公开的 PingCAP 官方文档，并把精确 HTTPS URL 写入 `authorityRefs`；不得以 roadmap、价格、
商业承诺、非公开例外或反馈文案自身作为确定性知识来源。上下文文件在 diff 检查前删除，不能进入提交。
Fresh replay 返回结构化非 2xx 时，HTTP status 和错误 body 仍作为 terminal-chain evidence
交给 investigator；只有网络失败、非 JSON 响应、latest-answer 读取失败或 ticket identity
不一致才在调查前终止任务。

因此 `/ao revise` 和 `/ticket` 从 fresh replay 起使用同一个调查、repair gate 和收尾状态机；
必要差异只在历史对象选择：`/ao revise` 固定评论发生时的回答版本，`/ticket` 默认选择当前
latest。这样可以同时回答两个问题：signal 指向的旧 bad answer 具体是什么；当前 runtime/main 是否已经修复
或仍能复现。只看旧日志会把已修复问题重复修一遍，只看 fresh replay 又可能丢失原答案
版本的反馈对象。

`runtimeTrace` 只负责运行时关联：`runId` 绑定 Auto-oncall run，`backend` 标识实际执行后端，
每条 call 只包含角色调用名、时间、结果和 thread ID。Optimizer 可以据此读取对应的 Agent9
Session/Operation；它不得把 backend、Session、Operation 或失败状态注入 Provider evidence，
也不得从这些运行元数据重做 Evidence Agent 或 Scenario Router 的诊断。历史 response 或单线程
direct path 可以回退到 `result.codexThreadId`，新 diagnostic replay 不再假设一个顶层 thread ID
能够代表 Router、Evidence、Diagnostic 和 Final Response 的全部独立线程。

## 5. 调查模型

调查 Agent 在 disposable、network-enabled worktree 中自主读取上下文、当前源码、测试和
只读 runtime evidence。Codex sandbox 允许 workspace write 以保留网络能力，但调查阶段结束
后 orchestrator 要求 worktree 除冻结 context 外没有任何变更；否则任务失败且不能进入
repair。外部 Jira、Clinic 和 Kubernetes 身份保持只读。调用方不给具体根因，也不先用
历史修复暗示答案。Agent 必须先完成以下回答审阅：

```text
原 AI 最终答案 + 同阶段人工答案 + 版本绑定的人工反馈 + fresh Judge
  -> 独立回答审阅和版本适用性判断
  -> 全链路首个断点调查
```

回答审阅作为结构化 `answerReview` 输出。调查 Agent 分别说明原 AI 回答做了什么、人工答案
带来了什么实质信息、人工反馈具体指向什么问题，以及该历史问题是否在新答案复现。
Orchestrator 用原答案和 fresh replay 的 fingerprint 确定 `judgeAnswerScope`，并把 fresh
Judge 的 status/verdict 固定为当前答案评价后注入持久化结果；调查 Agent 不输出或改写这些字段，不能把它改成跨版本
`conflicted`。版本不同时，旧 `/ao wrong|revise` 只决定旧答案的历史评价，不能决定当前答案
或支持当前回答质量 finding；当前答案以 fresh Judge verdict 独立展示。Judge 仍不是诊断或
chain classification 的权威。调查 Agent 必须按原回答当时可见事实做阶段公平比较：人工后来
取得的新事实或执行的生产操作不能反向证明 AI 更差，但原回答遗漏当时可见事实、给出无
依据结论、选错 owner 或留下不可执行 handoff 仍是回答问题。

完成上述审阅后，Agent 再按顺序检查：

```text
input/cutoff
  -> context materialization
  -> route/target
  -> provider execution
  -> evidence handoff
  -> parent judgment/refinement
  -> final synthesis
  -> evaluation
  -> runtime completion
```

`final synthesis` 必须作为双消息边界检查：两份消息来自同一个 Router 决定，但受众不同。
调查 Agent 分别判断对客消息是否给出正确的最终回答、进展或精准澄清，以及对内消息是否
保留内部取数失败、缺口 owner、下一动作和升级条件。只要最终需要向客户澄清，必须按 L1
保底标记 `human_required`；内部关键数据自动取不到且必须由 Support 继续，或存在独立
风险/操作边界时，同样需要 human takeover。分类时的缺口若已被内部证据消解且最终没有
反问，则不单独触发升级。

双消息从 Final Response 到持久化、replay、Jira 和本 Optimizer context 的完整 producer /
consumer 不变量见 `docs/DUAL_MESSAGE_CONTRACT.md`；本流程不得为 latest 或 fresh replay
单独重建、替换或丢弃其中一份消息。

在第一个相关失败点，分别判断：

- `produced`：上游是否实际产出；
- `consumable`：下游拿到的数据是否可直接使用；
- `consumed`：下游是否实际使用；
- `completed`：该阶段及整条链是否正确完成。

Provider 调查必须读取完整的 `providerQueryAttempts`，不能从候选触发用的去重
`failureCodes` 反推请求数量、错误原因、修正过程或诊断影响。相同 `queryId` 的中间失败在
本次运行内已修正成功、失败仅属于 supplemental evidence，或历史失败没有在 fresh replay
复现时，不构成 current finding；只有 fresh replay 中仍未恢复且对 materialized handoff
或诊断覆盖有实质影响的失败，才能标记 current broken boundary。

Provider execution finding 还必须绑定 fresh replay 的实际 backend。`backend=agent9` 且
Provider 为 Clinic 时，Provider Executor owner 是
`tools/agent9-auto-oncall/managed-clinic/`；`src/clinic-evidence-batch.ts` 是仍在使用的本地
Codex SDK 兼容 Executor，不能作为该线上失败的 implementation target。若断点在 runner
materialization 或 handoff，仍修改对应的精确下游 owner，不能为了保持两套实现表面对称而
同时修改。

这四个 chain 状态描述 current main 加 fresh replay 的整体执行状态。当前问题分别进入
`findings[]`，每个 finding 自己拥有 `firstBrokenBoundary`、`diagnosticImpact`、
`authorityRefs` 和 `repairDecision`；`selectedRepairFindingId` 最多指向其中一个。历史被提名
答案独有的断点使用 `historical_only` finding，不能被选中。回答的诊断或语义有误本身属于
`diagnostic_strategy`；runner notice 与 runtime trace 只能证明 orchestration degradation，
不能作为 Provider evidence 或重做诊断。

调查必须覆盖真实的诊断和时长问题，不能因为它们较难就忽略。但“发现问题”与“允许
自动修复”是两个不同决定。历史 `docs/opts`、提交和修复可以在独立定位断点之后用于判断
当前 main 是否已经解决，不能作为预填根因。

## 6. 分类和修复准入

顶层仍有四种整票分类，但不会再压掉独立 finding：

- `engineering_defect`：恰好一个 finding 被选中进入确定性修复；
- `focus_candidate`：没有 finding 被选中，但存在需要跨 case 或人工标准的诊断策略问题；
- `no_problem`：当前上下文证明提名已修复、反馈不适用于该答案版本，或没有诊断影响；
- `inconclusive`：现有上下文不足以证明或排除首个断点。

Fresh replay 的非 2xx 响应本身是 current runtime artifact，不得笼统归为“证据不足”。
如果 replay 是传输中断，调查者必须先把 replay 时间窗与 context 中已有的 AOS 进程或 Pod
lifecycle evidence 对齐；AOS 在该窗口重启或 rollout 替换 caller，可以中断长 HTTP 请求，
不能据此认定 LLM 或 Agent9 失败。Context 没有这类 lifecycle evidence 时不得猜测，必须把
AOS restart/rollout 标为缺失的判别信息，并保留一个未选中的 current finding。
调查者必须先沿错误码检查当前源码和测试；若一个遗漏的输入边界可确定性解释失败，并能给出
唯一 owner、复现和验收条件，应选择 `orchestration_fallback_contract` 进入工程修复。相邻的
简化场景测试通过不能证明失败边界未知。只有响应和当前源码仍不能确定唯一 owner 时，才保留
未选中的 current finding，并明确还缺哪个判别信息。

finding 类型为 `knowledge_contract`、`provider_query_contract`、
`orchestration_fallback_contract`、`diagnostic_strategy`、`external_transient` 或
`historical_only`。只有同时满足以下全部条件，一个 finding 的 `repairability` 才能是
`deterministic_engineering` 并被 `selectedRepairFindingId` 选中：

1. finding 是当前、material、可复现的 knowledge/query/fallback 契约违例，并有非空 broken boundary；
2. 一个清晰的 expected/observed contract violation；
3. 一个 owner boundary，而不是多个层级的重构；
4. 仅凭该 ticket 即可重复的 reproduction；
5. 一个确定性的 pass/fail acceptance criterion；
6. 当前源码中明确、限定在 `auto-oncall-services/` 的 implementation targets。

implementation target 通常必须是 current main 中已存在的普通文件。唯一新增文件例外是
`auto-oncall-services/product-knowledge/topics/*.md` 的精确直属路径，且 topics 目录必须已存在；
index、测试和 owner 源文件仍须指向当前文件。其他缺失路径、子目录或新执行代码不准入。

稳定知识还必须有精确的 PingCAP 官方公开 `authorityRefs` 和明确适用范围；行为测试验证知识
候选可被召回并交给语义 owner，不绑定某段 LLM 原文。query 类只接受本仓库可修复的确定性
window/parameter/schema/mapping/filter/parser/handoff 失败。fallback 类必须由 fresh replay 或
当前 runtime artifact 复现，且 owner 是局部 orchestration contract。

诊断结论是否正确、prompt 偏好、政策取舍、宽泛性能趋势、全局 timeout/model/reasoning/retry
调整都不能通过该 gate。一次性 429/502/503/504、认证/readiness、目标歧义记为
`external_transient`，允许暴露和重试但不自动补丁；roadmap、价格和商业承诺也不能准入。
这些问题分别返回 `requires_human_judgment`、`requires_cross_case_evaluation` 或
`not_applicable`。解析器检查 finding 类型、来源、断点、影响、repair decision 和选中 ID
之间的交叉一致性，不能靠 Agent 文案绕过。

`focus_candidate` 必须没有 selected finding，且至少有一个 `diagnostic_strategy` finding 需要
人工或跨 case 评估。`no_problem` 只表达当前状态：chain 全部 `established`、`findings` 为空、
selected ID 为 null。一个 job 即使另有 Focus finding，只要存在一个合法 selected finding，
顶层可为 `engineering_defect` 并只修选中的那一个。

如果首次结构化结果通过 JSON shape 但违反上述分类组合，controller 复用同一个只读调查
thread，提供精确 validator 错误和原结果，只允许一次不重新调查、不增加证据或 target 的
结构纠正。纠正后重新执行完整 parser/gate；第二次仍不合法才将 job 标为 `failed`。该纠正
不会启动写 Agent，也不能放宽确定性修复准入。

## 7. 修复执行

通过准入后，第二个 workspace-write Agent 在同一个任务 worktree 中读取冻结的 context
和 investigation，只允许：

- 只修复 `selectedRepairFindingId` 指向的 finding；
- knowledge finding 只写入 `product-knowledge/` 下稳定且有适用边界的知识，不写 ticket-specific 数据；
- 添加直接覆盖 acceptance criterion 的最小 regression test；
- 按 `AGENTS.md` 维护该质量变化需要的单一 `docs/opts` record；
- 运行相关测试。

它不得顺手修复同 ticket 的其他问题、查看或优化其他 ticket、创建/修改 Focus、扩大
业务 gate、写 Jira、merge 或部署。Orchestrator 删除临时输入后验证所有 changed path 都
位于 `auto-oncall-services/`，在缺少依赖时执行 `npm ci`，并强制运行完整 `npm test`。
Managed Tool source 发生变化时，完整测试还要求 AOS-owned bundle manifest 中当前版本的
每个文件 pin 与仓库源文件 SHA-256 完全相同；只构建 AOS image、未发布并 pin 新 Tool
package 的候选不得进入 PR 或声称已经部署。
Validation 子进程只收到 PATH/locale/证书目录和独立 HOME，不继承 GitHub、Feishu、Codex
或其他 orchestrator credential。
如果 controller 的第一次完整 `npm test` 失败，它把当前命令 stdout/stderr 的有界尾部和
精确失败返回同一 repair thread，只允许一次 validation continuation。该 continuation 只能
补齐 selected diff 直接影响的 test、snapshot、expected inventory 或 generated contract；
不得改变 finding、owner、产品行为范围或实现策略。若失败枚举、物化、统计或 snapshot 了
本次新增/删除的 identifier，默认视为本次 diff 导致，除非在 untouched `origin/main` 上证明
同一个精确失败。continuation 必须保留首次 repair 的全部 changed path；新增 changed path 只
允许位于 `auto-oncall-services/test/`。第二次完整测试仍失败则 fail closed 并保留 worktree，
不再重试。
无 diff 表示当前 main 已满足契约，任务正常结束而不创建 PR；有合法 diff 且完整测试通过
时，才由原 optimizer Git owner 统一 commit、push 并创建 ready PR。Controller 等待 CI，
不要求人工 review，也不使用 admin bypass；CI 或仓库规则阻止合并时按大问题终止并飞书通知。
GitHub Actions 可能在 PR 创建后的数秒内尚未注册任何 check；Controller 在有界两分钟窗口内
重试这一精确的“尚无 checks”状态，再进入 CI watch。其他 GitHub 错误和已注册 CI 的真实
失败仍立即 fail closed。

PR 合并后 controller 触发 staging release，并同时等待 release workflow 成功和运行中
`AUTO_ONCALL_BUILD_SHA` 等于包含修复的 main SHA。部署前 fresh replay 只证明问题存在，
不能替代部署后 replay。部署后先运行一次 `mode=read` criterion verification；通过后调用
`POST /v1/tickets/publish-read`，按该 read 的 `runId` 直接更新或创建稳定的 `public=false`
Jira internal comment，并持久化同一份 result/evaluation。这个发布步骤不再执行第二轮
analysis、Provider query 或 Judge；read workspace 已丢失时必须失败，不能回退成
`mode=write`。同 fingerprint 的明确 `accept|revise|wrong` 仍由人工结论拥有质量投影。
若 read replay 只剩同 finding、同 earliest owner、同 implementation targets 的局部缺口，
允许复用 repair thread 续修一次、重新 CI/merge/deploy/replay；换 stage/owner、需要新 finding、
范围扩张、外部不确定性或第二次不通过都转为 human handoff 并发送飞书。

成功发布 read replay 后，controller 只修改原 repair 自己的一个 `docs/opts` record，写入 Jira
comment/run evidence并晋级 `optimized_verified`，再通过独立 status PR 自动合并和部署。
Jira refresh 和 record verification 缺一不可，不能只凭 PR、merge 或 Ready 声称成功。

## 8. 同一 Deployment

单 Ticket Optimizer 不是新服务或新 CronJob。它作为 `single_ticket` job mode 运行在现有
`services/auto-oncall-optimizer` Deployment 中，共享：

- 同一个服务镜像与发布 workflow；
- 已持久化的独立 `CODEX_HOME`；
- repository clone、任务 worktree 和 job-state PVC；
- Feishu/GitHub 授权和 admin job API；
- 原有单 worker 顺序执行、重启后 requeue 和消息幂等。

普通 `analyze`/`pr` job 保持 45 分钟 timeout。`single_ticket` 需要先完成 fresh replay，
使用独立 3 小时 timeout，覆盖 CI、两次 staging rollout 和 replay，避免改变原命令行为。Deployment 新增内部 analysis base URL、
该 mode timeout、feedback poll 配置和一个独立的 outbound notification chat ID；
不新增 secret、PVC、replica、CronJob 或独立服务。

## 9. 状态、失败和可观察性

持久化 job 增加：

- `mode=single_ticket`；
- `issueKey`；
- `trigger=lark|feedback|candidate`、candidate source/signal、可选 feedback ID/action、source job/run 和 answer fingerprint；
- 结构化 `investigation`；
- `notificationStatus=pending|sent`、最近发送错误和发送时间；
- 原有 `threadId`、`worktreePath`、`result`、`prUrl` 和 `error`，以及 merge SHA、replay run、verification PR/SHA。
- 每次任务显式 fetch 后实际使用的 `repositoryBaseSha`。

终态行为：

| 结果 | 修改 | 自动收尾 | Focus 动作 |
| --- | --- | --- | --- |
| `no_problem` | 否 | 否 | 否 |
| `focus_candidate` | 否 | 否 | 否；保留为后续信号 |
| `inconclusive` | 否 | 否 | 否 |
| 未选中 finding | 否 | 否 | 否 |
| 选中 finding、repair 无 diff | 否 | 否 | 否；其他 finding 仍保留 |
| 选中 finding、repair 有合法 diff | 是 | CI 后 merge、deploy、replay、Jira、record | 否；其他 finding 仍保留 |

Context fetch、replay、Codex、第二次 schema/classification 校验、Git、测试或 PR 失败时 job
为 `failed`；已经创建的失败 worktree 按原服务契约保留供人工检查。首次分类组合冲突会先
执行上一节的一次受限纠正，不单独形成失败终态。服务重启会把 `running` job requeue。

每个 `single_ticket` job 进入 `succeeded` 或 `failed` 时都必须产生一条飞书终态消息；
`no_problem`、`focus_candidate`、`inconclusive` 和无 diff 也是成功终态，不能静默。自动反馈
任务向 `AUTO_ONCALL_OPTIMIZER_NOTIFY_CHAT_ID` 当前指定的 notification chat 主动发消息，
staging 目标是 `Auto Oncall 通知`；手工 `/ticket` 回复原消息线程。出站目标以运行时配置为
唯一 owner，不复制到自动 feedback job，因此已经排队或运行中的任务也会使用部署后的当前
目标。终态消息使用面向人的中文“结果、工单、反馈、结论、处理、任务 ID”结构，不直接
暴露 classification/repairability/impact 枚举、完整 Agent 原文或原始技术错误；完整结构化
结果和错误仍保存在 job 状态中。调查 Agent 为通知生成的 `classificationReason`
必须使用不依赖上下文的通俗中文：直接说本次回答做了什么、这是否真有问题，以及为什么修改或不修改代码；
不得只用“新鲜回放”“证据边界”“修复门”“跨案例评估”或“下一步的可执行性”等压缩表达。
终态先持久化为
`notificationStatus=pending`，发送成功后改为 `sent`；发送失败只记录错误并由同一 worker
定期重试，且使用稳定 job notification UUID 避免“已发送但状态落盘失败”造成重复消息；
不能把 optimizer 结果改成失败，也不能重跑调查、修复或 PR side effect。

`inconclusive` 不得统一显示为“证据不足”：当前 finding 若是 Provider terminal failure，
标题使用“🚨 单工单优化问题：证据查询失败”；若在输入选择、上下文、路由、评测或 runtime
completion 阶段 terminal failure，标题使用“🚨 单工单优化问题：回放失败”。这些是优化过程遇到的
阻塞，不代表已确认待优化的目标问题；其他正常处理结论继续使用“单工单优化结果”，无法定位的情况显示
“单工单优化结果：问题未定位”。
optimizer 控制器自身在 context fetch、Codex、Git、CI、部署或重启恢复阶段失败时继续使用
带 🚨 的“单工单优化问题” `failed` 终态，并按失败阶段和稳定错误签名独立聚合，不把它包装成 ticket 质量结论，也不由
同一个失败 job 递归修改控制器自身。确定性 AOS replay/query 契约仍可由单工单修复门处理；
optimizer 服务自身的实现缺陷交由独立工程修复后，再重跑受影响 ticket。
若修复已合并后 post-change replay 或收尾验证失败，标题必须使用
“🚨 单工单优化问题：部署后验证失败”，并显式写明修复已部署但 replay/收尾验证失败、当前问题
仍未确认修复，便于通知接收人快速识别需要人工介入的高优先级过程故障。

## 10. 验收标准

实现至少满足：

1. 新落库的 `wrong|revise`、`human_better`、Provider 降级/失败和 runner timeout 自动、顺序、幂等入队；首次 activation 不补跑历史；
2. `/ticket` 只能接受一个 issue key，并按原写权限授权，且可补跑旧 case；
3. latest answer 与 fresh read-only replay 都被收集且 issue key 一致；fresh Evaluation
   保留实际 Judge 使用的完整同阶段人工答案，而不只有 comment digest；
4. 调查先独立审阅原 AI、人工答案、版本绑定反馈和 fresh Judge，明确 Judge 是否评价同一
   答案版本并解决冲突；fresh replay 同时保留对客与对内消息；调查能识别两份消息、typed decision、
   clarification 和实际 publication outcome 之间的 producer/consumer 断链；
5. 调查可拆分多个 finding；稳定知识、确定性 query 和当前可复现的局部 fallback contract 可单票准入；
6. 每个 job 最多选一个 finding；诊断策略、外部瞬态、roadmap/商业内容和全局预算调整不能准入；
7. semantic gap 保留为 Focus finding，不能压掉同票可修 finding；`no_problem` 必须没有当前 finding；
8. 分类字段首次冲突时只允许同一调查 thread 纠正一次，第二次仍不合法则 fail closed；
9. 未通过 gate 时不运行写 Agent、不创建 branch/PR side effect；
10. 通过 gate 后只修改 `auto-oncall-services/`，临时 context/investigation 不进入 commit；
11. controller 首次完整测试失败只允许同一 repair thread 修一次直接受影响的验证契约；第二次失败即 fail closed；
12. 仅有合法 diff 且 controller 的完整 `npm test` 通过时创建 PR；CI 后无需人工 review 自动 merge；
13. 成功和失败都向独立通知群发送人类可读的中文结果；失败发送持久重试且不重跑 optimizer；
14. 新 mode 使用同一个 optimizer Deployment/PVC/CODEX_HOME/Git owner；
15. 原 `analyze` 和 `/pr` 行为及 timeout 不变；
16. merge 后等待精确 build SHA 再 replay；read criterion 通过后才 write Jira，并验证 run/comment/evaluation；
17. 同 owner 小缺口只续修一次，范围变化或第二次失败发送飞书 human handoff；
18. Jira refresh 后原 record 以独立 PR 更新为 `optimized_verified` 并部署；
19. Go tests、race、vet、build 和 contract diff 检查通过。
20. 每个修复 worktree 的 HEAD 等于本次显式 fetch 的 `origin/main` SHA，Agent9 Provider
    修复目标与实际 Managed Tool owner 一致，且当前 Managed Tool 源码与 bundle pin
    byte-for-byte 一致。

修改触发、分类、repair gate、部署归属或完成语义时，必须同步更新本文、
`docs/AUTO_ONCALL_EVALUATION.md`、两个适用的 `AGENTS.md`、OpenAPI contract 和测试。
涉及对客/对内消息输入时，还必须同步更新并遵守
`docs/DUAL_MESSAGE_CONTRACT.md`。
