# 跨 Run 通过率聚合：故障计零、用户取消排除

ADR-0002 定义了单次 Run 内的通过率口径（用例级、含执行失败），但没有覆盖**跨 Run 聚合**（仪表盘总览、场景排行、趋势、活动周期报告）。实际代码里长出了两个矛盾口径：仪表盘对"仅已完成的 Run"求均值（故障 Run 被排除出分母），活动报告把失败 Run 计 0.0（除以全部）。决定统一为一个聚合规则，由单一函数实现，所有跨 Run 读者共用：

- **执行故障的 Run 计 0.0**——延续 ADR-0002 的服务视角：整个 Run 挂了，等价于该 Run 的所有用例都不达标；
- **用户手动取消的 Run 排除出分母**（含通过率与可用性）——取消是用户操作，不是被评对象的质量或可用性信号，计 0.0 会诬陷对象，计 1.0 会粉饰，排除是唯一诚实的处理；
- 取消与故障通过 `RunSummary.error.code == "cancelled_by_user"` 区分（取消 Run 的持久化状态仍是 failed）。

## Considered Options

- **仅对已完成 Run 求均值（仪表盘现状）** — 被否：故障期间仪表盘显示"质量正常"，与 ADR-0002 的动机直接冲突；同一个 pass_rate 键在两处得出不同数字，无从解释。
- **非 completed 一律计 0.0（活动现状，单一规则最简）** — 被否：用户自己停掉的 Run 拉低质量分，指标不诚实；长期会让人不敢取消 Run。
- **保留两种聚合、显式命名（service_pass_rate / completed_pass_rate）** — 被否：两个函数就是两个口径，仪表盘与活动报告的数字依旧不可互相印证，分歧只是换了名字。

## Consequences

- 仪表盘 / 场景排行 / 趋势的数字会变：故障 Run 并入分母（数字下降），取消 Run 排除（略回升）。这是修正，不是回归。
- 活动周期报告的口径同步细化：用户取消的子 Run 不再计 0.0（此前极少发生，现有报告几乎不变）。
- 聚合逻辑必须收敛在一个函数里；任何新读者（未来的对比视图、导出）禁止自行读 `summary["pass_rate"]` 重新聚合。
- 取消的判别依赖统一的 `error.code`——错误形状（此前字符串/对象混用）必须随之统一。
