Skip to main content

BLOGE 0.9.8-RC1 在线版 · 事实校验 2026-09-15 · English

第 27 章 —— 合同敏感性:Suite 能拒绝错误答案吗?

本章承诺: 把一个绿色贷款 Case 变成受控负向实验,分清 KILLED 见证与 SURVIVED 盲区,同时不把两者误写成穷尽式 mutation coverage。

学习目标

  1. 解释“candidate 等于 expected”为何还不能证明合同会拒绝相邻错误答案。
  2. 使用 verifyContractSensitivity(Path) 检查一个受治理的 BUSINESS_CONTRACT Expectation。
  3. 分别读取 KILLEDSURVIVED、report status 和 source-bound evidence。
  4. 设计业务 Owner 能审阅的单变量 witness。

同一个绿色 Case 可能藏着两种完全不同的合同

低风险贷款 Case 期望 APPROVE,graph 也返回 APPROVE。现在设想两个 matcher:

  • 精确合同:actual 必须等于 APPROVE
  • 宽松合同:actual 只要不等于 DECLINE

两者今天都会通过,但只有前者会拒绝 REVIEW。正向例子只能说明一个点匹配;sensitivity 问的是:选定错误答案是否越过合同边界。

图 27.1:同一个受控错误答案会被精确合同杀死,却能从宽松合同中存活

改 expected,不改运行中的系统

sidecar 精确指向受治理的 Expectation,只替换它的 expected value:

schemaVersion: 1
sensitivityId: loan-decision-witnesses
scenario: loan-approval.scenario.yaml
mutants:
- mutantId: low-risk-must-not-review
requirementRef: LOAN-DECISION-001
caseId: auto-approve
expectationId: decision-approved
kind: EXPECTATION_VALUE_REPLACED
replacementExpected: REVIEW
VerificationContractSensitivityReport result =
verifier.verifyContractSensitivity(sensitivityPath);

BLOGE 只执行一次 baseline Case,再把真实 observation 投影到替代 expected value 上。它不修改生产代码、不换一个 Operator 重跑,也不发明业务规则。这个隔离很重要:如果实现和 Oracle 同时改变,失败就无法归因给合同敏感性。

观察状态转移,不只看最后徽章

RC1 测试 ContractSensitivityTest.passesOnlyWhenEveryRequiredWitnessIsKilled 使用精确 EQUALS Expectation。graph 仍观察到 APPROVE;把 expected 换成 REVIEW 后会被拒绝,因此 witness 为 KILLED、report 为 PASSreasonCodes 为空。

配对测试 sealsASurvivingWitnessAsARecheckableSensitivityFailure 从更宽的“not review”式合同开始,再把 expected 换成 DECLINE。同一个 observation 仍被接纳;witness 变成 SURVIVED,report 变成 FAIL,发布后的 artifact 仍可作为 SOURCE_BOUND evidence 被独立复核。

ObservationWitness outcomeReport含义
替代 expected 被拒绝KILLED只有全部必要 witness 都被杀死才 PASS合同能识别这个选定错误答案
替代 expected 仍被接纳SURVIVEDFAILmatcher 或 Oracle 边界对这个 witness 过宽
plan 指向缺失或不支持的目标NOT_EVALUATEDINVALID实验本身无效

这里故意不对称:存活 witness 是有价值的失败 evidence,不能因为原业务 Case 是绿色就被隐藏。

从业务错误设计 witness

不要枚举随机字符串,先找业务能命名的错误:

  1. 选择一个受保护决策,例如“低风险申请不得进入人工复核”。
  2. 固定 Scenario、Policy、Fixture、graph、execution profile 和 source commit。
  3. 只把一个 expected value 换成一个可信的错误答案。
  4. 请业务 Owner 判断该 witness 是否代表真实决策边界。
  5. 通过评审后再把 witness 纳入版本控制。

贷款审批中,auto-approve Case 的 REVIEW、blacklist Case 的 APPROVE 都是有意义的 witness。胡乱填写的值可能只测到序列化,教不了读者业务合同。

实验:让一个 witness 存活

  1. 运行低风险 Case,记录 baseline PASS 和 actual APPROVE
  2. 加入上面的 REVIEW replacement,确认精确 matcher 得到 KILLED
  3. 只把 matcher 改成也能接受 REVIEW 的宽松条件。
  4. 再运行同一 sidecar,记录 SURVIVEDCONTRACT_MUTANT_SURVIVED:<mutantId>
  5. 恢复精确 matcher并重跑,witness 必须回到 KILLED
  6. 把 artifact receipt 保存到运行目录之外,再交给另一个 reader 复验。

这是两次运行、一个变量的实验:witness 固定,只改变合同严格程度,因此结果可解释。

现实迁移:折扣规则敏感性

在零售定价中,保持购物车和实现不变,只把期望的会员折扣从 10% 替换为 30%。 精确金额 matcher 应该杀死这个 witness;只检查 discount > 0 的宽松 matcher 会让它存活。这个结果只说明合同能否拒绝所选定价错误,不代表覆盖了所有促销组合。

本章边界

  • Sensitivity 只覆盖作者声明的 mutation,不是全部规则和输出的 mutation coverage。
  • KILLED 只证明拒绝一个选定错误答案,不证明输入域上的正确性。
  • SURVIVED 定位歧义,但不替业务决定应该改 matcher 还是 Oracle。
  • Source-bound 发布让结果可复核,不会让工具成为业务 Owner。

实验验收卡

  • 预期与观察: 同一 observation 在精确与宽松合同下得到 KILLED 与 SURVIVED。
  • 失败与恢复: 放宽一条 expectation 让 witness 存活;恢复精确答案。
  • 证明边界: 证明已声明 witness 的敏感性,不证明发现全部盲点。
  • 练习合同: 绿色 Case 和一个 witness;只改一条 expectation;交付状态转移;错误 witness 被 KILLED 即停止。

小结

绿色例子只说“这个答案匹配”;被杀死的 witness 增加“这个选定错误答案不会匹配”;存活 witness 暴露合同过宽的位置。第 28 章会再次换问题:不再只试一个人工挑选的错误答案,而是探索有限输入域并缩小真实失败。

下一章:第 28 章——有限属性验证与有用反例

Coding Agent: Open the versioned task guide.