# Harness 证据安全链:从引用真实到结论可发布
**更新日期**:2026-07-29
**主题**:EvidenceGuard、EvidenceRepair、SemanticGuard 与 Diagnosis Release
**术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md)
**前置阅读**:[Harness-Tool双视图-从原始结果到可验证证据.md](Harness-Tool双视图-从原始结果到可验证证据.md)
## 1. 证据存在,不代表结论成立
诊断报告至少可能出现三类不同错误:
1. **伪造引用**:Draft 引用了不存在、失败或属于其他 Run 的 Tool Call。
2. **引用断裂**:analysis 有证据,但 conclusion、action plan 或 recommendation 没有绑定对应 analysis。
3. **牵强推论**:引用和结构都真实,但证据并不足以支持所写根因。
例如,系统确实查到了一条支付超时日志,但报告把根因写成“数据库连接池耗尽”。此时 Tool Call 真实、日志真实、引用格式也正确,结论仍然不成立。
因此“报告是否安全”不能由一个笼统的 Verifier 回答。它至少包含两个不同命题:
```text
P1:引用的证据是否真实、属于当前 Run,并形成结构闭包?
P2:这些真实证据是否足以支持报告中的结论?
```
P1 可以由代码确定性证明;P2 是语义判断。把两者都交给模型,会让本可机械验证的身份和状态也变成概率结果;全部交给规则,则规则代码会逐渐变成另一套业务诊断引擎。
## 2. 设计目标:验证链不能产生新事实
证据安全链遵守四个约束:
- Diagnosis Agent 是唯一报告作者;
- EvidenceGuard 只做确定性验真;
- SemanticGuard 只做受限语义审查;
- Release 是唯一公开决策点。
任何 Guard 都不能调用业务 Tool、探索新证据或重写最终报告。否则验证链会变成第二条隐式 Agent 链,重新引入多 Agent 架构的问题。
```mermaid
flowchart LR
D["DiagnosisDraft
唯一业务 Draft"] --> R["DiagnosisReleaseUseCase
发布编排所有者"]
R --> E["EvidenceGuard
确定性引用验真"]
E -->|"invalid"| X["EvidenceRepair
只修结构/引用"]
X --> E2["EvidenceGuard Recheck"]
E -->|"valid"| V["VerifiedEvidenceSnapshot"]
E2 -->|"valid"| V
V --> S["SemanticGuard
SUPPORTED / UNSUPPORTED"]
S -->|"SUPPORTED"| OK["发布原始或等义修复 Draft"]
E -->|"repair 失败"| F["SafeFallback"]
E2 -->|"invalid"| F
S -->|"UNSUPPORTED / unavailable"| F
```
## 3. 第一层:EvidenceGuard 证明引用真实性
`EvidenceGuard` 不调用模型。它从当前 `RunContext` 出发,对 Draft 进行两阶段检查。
### 3.1 Draft 结构与引用闭包
首先检查报告自身结构:
- Draft、analysis、analysis ID、kind 和正文是否存在;
- analysis ID 是否重复;
- 每条有结论分析是否绑定 Tool Call;
- conclusion、action plan 和 recommendation 是否绑定已存在的 analysis ID;
- limitations 是否存在并声明报告范围。
引用链的目标不是让 JSON 看起来完整,而是形成下面的闭包:
```text
Conclusion / Action / Recommendation
|
v
Analysis Item
|
v
framework tool_call_id
|
v
Canonical READY Invocation
```
只要其中一跳缺失,公开报告就不能证明其来源。
### 3.2 Canonical Invocation 验真
对每个 `tool_call_id`,Guard 使用当前 `runId` 重新生成 canonical key,然后检查:
1. ID 格式是否合法;
2. canonical record 是否存在;
3. record 内 ID 是否与引用一致;
4. record 是否属于当前 Run;
5. invocation 是否 READY;
6. analysis kind 是否接受该 evidence status;
7. Tool 类型是否受支持;
8. `agent_result` 是否能严格解析为对应 typed result;
9. typed result 中的 ID、状态、count 和集合是否自洽。
因此,即使模型猜中了另一个 Run 的 Tool Call ID,也无法通过当前 Run key 和 ownership 检查。
### 3.3 正向证据和负向观察不能混用
`AnalysisKind` 与 `EvidenceStatus` 的匹配是关键约束。
一条 READY + NO_EVIDENCE 日志结果,只能支持如下陈述:
> 在企业 A、时间窗 T、服务 S、查询条件 Q 下没有匹配事件。
它不能支持:
> 系统没有发生故障。
EvidenceGuard 会把空结果投影为带 source 和 scope 的负向观察,而不是把它提升为支持任意根因的正向证据。
## 4. VerifiedEvidenceSnapshot 为什么是必要中间产物
EvidenceGuard 验证通过后不会把 canonical raw 直接交给 SemanticGuard,而是生成 `VerifiedEvidenceSnapshot`。它保存:
- analysis ID、正文和 kind;
- 已验证证据的 source type、source、scope、timestamp、excerpt;
- Tool-specific 的有限结构化 values;
- 去重后的 verified sources。
Snapshot 的作用是形成一条新的最小信任边界:SemanticGuard 不需要访问 Redis,也不能看到 request、raw response 或内部错误。它只判断“这组已经验真的事实是否支持 Draft”。
这也避免了语义审查阶段重新解释 backend 私有格式。
## 5. 第二层:EvidenceRepair 只允许修引用
### 5.1 为什么需要 Repair
模型可能生成业务语义正确、但引用结构存在局部问题的 Draft,例如:
- conclusion 漏写 `based_on_analysis_ids`;
- analysis 引用了错误的 Tool Call ID;
- 输出字段结构不符合严格 Schema。
如果所有结构错误都直接 Fallback,会丢掉部分可修复结果。但让 Repair 自由重写又会产生更严重的问题:最终结论不再来自 Diagnosis Agent,Repair 事实上成为第二个报告作者。
### 5.2 Repair 的不变量
`EvidenceRepair` 只获得:
- 原始 query;
- 原始 Draft;
- EvidenceGuard 给出的 violations。
它没有 Tool,也不能获取新证据。修复输出必须再次严格解析,并满足:
```text
SemanticDraftView(original)
==
SemanticDraftView(repaired)
```
即用户可见的 analysis、conclusion、action plan、recommendations 和 limitations 语义不变,只允许修复引用和结构。之后必须重新执行 EvidenceGuard,不能因为“已经 Repair”就跳过验真。
当前策略只给 EvidenceRepair 一次 attempt。Repair 失败、改变语义或复检仍不通过,直接进入 `EVIDENCE_VALIDATION_FAILED` Fallback。
## 6. 第三层:SemanticGuard 判断结论支持度
EvidenceGuard 证明的是“证据是真的”,SemanticGuard 判断的是“推论是否成立”。为了不让它变成第二个 Agent,系统主动削减了它的能力:
| 约束 | 目的 |
|---|---|
| 无 Tool | 不能自行寻找新事实 |
| 无会话记忆 | 不能引入当前输入之外的信息 |
| 单轮调用 | 不形成新的 ReAct loop |
| 输入固定 | 只接收 query、Draft 和 VerifiedEvidenceSnapshot |
| 输出严格 | 只能返回 `verdict + reason` |
| 二值 verdict | 只允许 `SUPPORTED / UNSUPPORTED` |
| 无发布权限 | 不能改写或直接返回用户报告 |
技术超时、传输失败、解析失败和 Schema 非法可以按完全相同输入进行一次显式重试。仍失败时不假设“可能支持”,而是 fail closed,发布 `SEMANTIC_UNAVAILABLE` Fallback。
这里需要准确描述“确定性边界”:SemanticGuard 的语义判断仍然是概率性的;确定的是它的权限、输入、输出、成本、重试次数和失败后的发布行为。
## 7. Release 为什么必须是唯一发布入口
如果 Application、EvidenceGuard 和 SemanticGuard 都能构造公开结果,同一种失败会出现多个含义,甚至可能绕过前置校验。`DiagnosisReleaseUseCase` 集中处理以下分支:
| 输入情况 | 验证路径 | 发布结果 |
|---|---|---|
| Draft 有 conclusion | EvidenceGuard -> 可选 Repair/Recheck -> SemanticGuard | 原 Draft/等义修复 Draft,或安全 Fallback |
| Draft 无 conclusion,有已验真进展 | 只验证其中已有 Tool 引用 | `INSUFFICIENT_EVIDENCE` |
| Draft 无 conclusion,无进展但声明缺失信息 | 检查引用边界 | `MISSING_REQUIRED_CONTEXT` |
| Harness 受控停止,有已验真进展 | ProgressSnapshot | `INSUFFICIENT_EVIDENCE` |
| Draft 非法,但有已验真进展 | 丢弃非法 Draft,使用 ProgressSnapshot | `INSUFFICIENT_EVIDENCE` |
| Draft 非法且没有安全进展 | 无可发布事实 | FAILED,保持 fail closed |
有结论时,只有 `SUPPORTED` 可以发布 Draft。发布的是 Diagnosis Agent 原始 Draft,或者经证明用户可见语义相同的 Repair Draft,不让 SemanticGuard生成“更好的答案”。
## 8. `conclusion=null` 为什么不进入完整语义审查
当模型明确表示无法确认根因时,不存在需要验证的根因结论。继续调用 EvidenceRepair 或 SemanticGuard,不仅浪费 Token,还可能让验证模型反向补出一个原本不存在的结论。
因此无结论路径只做必要的引用真实性检查,然后根据安全进展决定:
```text
有 verified progress
-> INSUFFICIENT_EVIDENCE
没有 progress,但有 limitations.missing_info
-> MISSING_REQUIRED_CONTEXT
两者都没有
-> 不是合法业务结果,fail closed
```
这项设计把“没有找到根因”从模型失败中分离出来,但没有降低证据要求。
## 9. SafeFallback 不是通用错误文案
SafeFallback 是结构化、确定性的发布结果。它可以包含:
- `verified_sources`:通过当前 Run 验真的来源;
- `observed_facts`:有界、去重后的事实或负向观察;
- `limitations`:为什么不能确认根因;
- `next_steps`:下一步需要补充的信息或检查;
- `validation_issues`:稳定违规码和目标字段;
- `failure_stage`:失败发生在收集、证据还是语义阶段。
它不能包含 Prompt、原始 Draft、raw Tool payload、内部异常或模型 reasoning。Fallback 不是“把错误吞掉”,而是只发布系统已经能够证明的部分。
## 10. 为什么不采用其他方案
### 10.1 单个 Verifier Agent
优点是实现表面简单,缺点是把 ID、Run ownership、Schema 和业务支持度混在同一次模型判断中。本来可以 100% 用代码拒绝的跨 Run 引用,也会变成概率审查。
### 10.2 Guard 自动改写报告
可以提高表面成功率,但破坏唯一作者原则。Guard 为了“修好”结论,往往会引入新解释;此时必须重新验证新内容,最终形成循环。
### 10.3 只做引用存在检查
能阻止伪造 ID,却无法阻止“真实证据 + 错误根因”。这正是 SemanticGuard 存在的原因。
### 10.4 语义失败直接技术报错
丢失了已经验真的事实,也把“结论不受支持”错误表达为基础设施故障。SafeFallback 可以保留过程价值,同时拒绝发布根因。
## 11. 真实问题如何改变设计
| 问题 | 暴露的设计缺陷 | 修正 |
|---|---|---|
| 旧 Verifier 同时验引用、判断语义和改写答案 | 验证职责没有边界 | EvidenceGuard、SemanticGuard、Release 三段拆分 |
| Tool 结果面向开发者,含 raw 和重复正文 | Guard 与 Agent 没有独立事实面 | 先建立 canonical truth 和 verified snapshot |
| `NO_EVIDENCE` 被当成一般失败或正向证据 | 负向观察没有范围约束 | AnalysisKind 与 EvidenceStatus 匹配校验 |
| Repair 可能改变结论 | 修复者变成新的报告作者 | SemanticDraftView 前后等义检查 |
| 非法 Draft 使已完成 Tool 检查全部丢失 | Draft 是唯一可发布价值来源 | 只在 ProgressSnapshot 已验真时允许过程型 Fallback |
| SemanticGuard 技术失败时行为不一致 | 各层自行决定降级 | Release 统一生成 `SEMANTIC_UNAVAILABLE` |
## 12. 代价与剩余风险
1. SemanticGuard 增加一次模型调用和延迟;这是语义安全与成本之间的明确取舍。
2. Semantic verdict 不是形式化证明,仍可能误判;当前设计限制的是权限和失败影响,而不是宣称模型绝对正确。
3. EvidenceGuard 需要理解每种 Tool 的 typed result;新增 Tool 必须同步增加验证规则。
4. Repair 的语义等价由结构化视图定义,无法证明两个自然语言文本在所有解释下完全等价,因此 Repair 能力被刻意限制。
5. Canonical record TTL 到期后无法重新完成完整验真,所以公开决策必须在当前 Run 内完成。
## 13. 如何验证
| 需要证明 | 代表性验证 |
|---|---|
| 缺失、重复、未知 analysis 引用被拒绝 | `EvidenceGuardTest` |
| cross-run、非 READY、ID/status 不一致被拒绝 | `EvidenceGuardTest` + canonical fixtures |
| NO_EVIDENCE 只能形成限定负向观察 | Evidence kind focused cases |
| Repair 不得改变用户可见语义 | `DiagnosisReleaseUseCaseTest`、Repair tests |
| Semantic 非法输出只有限重试并安全降级 | `SemanticGuardTest` |
| Guard 不改写报告,Release 只发布原 Draft 或 Fallback | `DiagnosisReleaseUseCaseTest` |
| 无结论、非法 Draft、受控停止正确映射 | `DiagnosisChatExecutorTest`、Release focused cases |
| exact-run Trace 能解释每一道门禁 | live E2E Timeline |
## 14. 与前后链路的关系
证据安全链依赖 [Tool 双视图](Harness-Tool双视图-从原始结果到可验证证据.md)提供独立 canonical truth;它解决的是“哪些内容能够发布”,不负责判断 Agent 是否还应该继续取证。正常收敛由[Harness信息增益停止-让无证据诊断正常收敛.md](Harness信息增益停止-让无证据诊断正常收敛.md)负责。