BLOGE
0.9.8-RC1在线版 · 事实校验 2026-09-15 · English
第 25 章 —— Policy、Fixture 与 Assurance
本章承诺: 你会把「必须验证什么」「外部行为怎样受控」「证据最低达到什么程度」拆成三份声明。删除受保护 Case、Fixture 消费越界或放行真实 effect 时,验证在执行边界内停止,而不是制造一个假绿结果。
学习目标
- 区分 Scenario、Policy、Fixture 和
VerificationExecutionProfile的责任。 - 看懂
FAIL、INVALID、INCOMPLETE对应的下一步动作。 - 用
verifyGoverned(Path)检查 Requirement,用verifyFixtureFidelity(Path)检查 Fixture 合同。 - 复现三个单因素失败,并从最早 reason code 开始排查。
三份声明守住一个执行入口
团队为了让贷款测试变绿,可能删除 manual-review,把 Fixture 改成「任何请求都返回成功」,或者让未声明的 effect 访问真实账务系统。三种修改都没有改变断言,却缩小或污染了证明范围。
图中的橙色入口是 preflight。Scenario、Policy 和 Fixture 先完成一致性检查,验证器才创建客户 bootstrap 并进入真实 BLOGE parser、compiler 和 engine。被拒绝的配置不会先执行客户代码再补一个警告。
Scenario 声明一个 Case 的输入、动作和 Expectation。它回答「这次要观察什么」。
Policy 声明不能静默删除的 Suite、Case、Requirement 和最低 assurance。它回答「最少必须保留什么」。
Fixture 声明哪一个 node call 或 effect port 被接管、返回什么以及允许消费几次。它回答「外部行为怎样受控」。
VerificationExecutionProfile 固定逻辑时钟、随机种子、ID 种子、harness timeout 和 persistence mode。host capability 只是被绑定的输入,不是隔离证明。
先保护人工复核 Case
starter Policy 已把三个决策 Case 写进 requiredCases:
requiredSuites:
- suiteId: loan-approval-decisions
requiredCases: [auto-approve, auto-reject, manual-review]
minimumDepth: L2
minimumBusinessExpectations: 6
minimumReplayAssurance: CONTRACT_CONTROLLED
maximumUnverifiedEffects: 0
运行治理入口:
VerificationGovernedReport report = ScenarioVerifier.builder()
.bootstrap(caseContext -> loanVerificationEnvironment(caseContext))
.projectRoot(projectRoot)
.policy(projectRoot.resolve("src/test/bloge/verification-policy.yaml"))
.build()
.verifyGoverned(projectRoot.resolve(
"src/test/bloge/scenarios/loan-approval.scenario.yaml"));
requiredCases 只防止 Case 消失。Requirement 是否满足还取决于 requirementObligations 和 requirementBindings:只有通过的 BUSINESS_CONTRACT Expectation 能贡献 Requirement evidence。Case 仅引用 requirementRefs 不等于 Requirement 已满足。
Fixture 不是全局 mock
替换整个 Operator 调用时使用 NODE_CALL;保留 Operator 计算、只接管外部访问时使用 EFFECT_PORT。两类 target 不能混用。
schemaVersion: 1
fixtureSetId: funding-effect
rules:
- ruleId: book-once
target:
type: EFFECT_PORT
portId: ledger.book
parentNodeId: bookDisbursement
inputEquals: {applicationId: LA-FUND-1}
behavior:
type: RETURN
value: {status: booked}
consumption: {minimum: 1, maximum: 1}
规则零命中、歧义、欠消费、超额消费和未绑定 effect 都 fail closed。CI_SAFE 是当前唯一 profile,并明确拒绝 ALLOW_REAL。Effect DELAY 使用 bootstrap 提供的 TimeSource;Fixture 本身不会用 Thread.sleep 等待墙钟。
Assurance 与 fidelity 不混写
一个 Case 可以业务 PASS,但 Fixture fidelity 仍然只达到 DECLARED_SYNTHETIC。实现 VerificationFixtureFidelityProvider 后,provider 提供按 fixtureSetId/ruleId 定位的合同;验证器自行检查 Schema 和可选 adapter observation,provider 无权直接返回 PASS 或指定等级。
VerificationFidelityReport fidelity = verifier.verifyFixtureFidelity(scenarioPath);
本地 provider 的首版上限是 PROVIDER_ATTESTED_ADAPTER。只有合同来源、adapter 实现与配置、治理主体都进入 source-bound evidence,才可能得到 ADAPTER_CONTRACT_VERIFIED。未配置 provider 时返回较低的 DECLARED_SYNTHETIC,不能把「没有 fidelity 证据」写成「验证失败」。
做三次单因素攻击
三次都使用同一个 manual-review Case,每次攻击后先恢复输入。否则第二次结果无法归因到一个变化。
| 只改一个事实 | 最早可靠观察 | 客户代码是否运行 | 恢复动作 |
|---|---|---|---|
| 删除 Policy 保护的 Case | 候选 Policy 变弱:POLICY_REGRESSION;Scenario 缺 Case:REQUIRED_CASE_MISSING | 否 | 恢复 Case,不降低 baseline。 |
Fixture rule 调用超过 maximum | 下一次 resolve fail closed:CONTROL_RULE_OVER_CONSUMED;verifier diagnostic 映射为 FIXTURE_OVER_CONSUMED:<set>/<rule> | 已开始执行,但额外的真实调用不会放行 | 修调用次数,或由业务确认新上限。 |
在 CI_SAFE 下把 behavior 改成 ALLOW_REAL | Fixture 编译阶段得到 REAL_EXECUTION_FORBIDDEN | 不调用 real port | 保留 double;只有明确隔离 profile 存在时才迁移测试。 |
这里的时机区别很重要。Case 是否存在、ALLOW_REAL 是否出现,都能在执行前知道;超额消费只有看到多出来的那次 invocation 才能判断。把三者都称为“preflight”会掩盖真正的安全边界。
2026-09-14 在 BLOGE commit cc38fbe5 上运行 PolicyContinuityValidatorTest(11)、FixtureBehaviorCompilerTest(2)和 VerificationExecutionProfileTest(2),共 15 tests 全部通过。BehaviorActionTest 另有 7 个 execution-control tests,其中两次返回序列的第三次调用抛出 CONTROL_RULE_OVER_CONSUMED。ScenarioVerifier.controlReasons 再把结束诊断翻译为 FIXTURE_OVER_CONSUMED:<ruleReference>。
状态决定下一步动作
FAIL——业务答案错了。 合同有效且执行完成,但业务或 temporal
Expectation 不满足。先检查业务实现、输入或批准的标准答案,不要先改 Policy。
INVALID——验证声明错了。 Scenario、Policy、Fixture、DSL 或能力组合无效。
修复声明;用同一份输入直接重试没有意义。
INCOMPLETE——运行没有完成。 超时、资源、adapter 或 evidence capture
中断了运行。保留诊断,修复环境,再从一次干净运行开始重试。
从最早 reason code 开始,而不是从最后一条异常文本猜原因。代表性边界如下:
REQUIRED_CASE_MISSING:Policy 要求的 Case 已被删除。FIXTURE_UNDER_CONSUMED:<fixtureSetId>/<ruleId>:声明至少消费一次,但实际没有命中。FIXTURE_OVER_CONSUMED:<fixtureSetId>/<ruleId>:实际调用次数超过maximum。REAL_EXECUTION_FORBIDDEN:CI_SAFE遇到ALLOW_REAL。FIXTURE_TARGET_ZERO_MATCH/FIXTURE_TARGET_AMBIGUOUS:target 不能唯一绑定受控调用点。
实验:依次制造三个失败
每轮只 改变一个因素,并在下一轮前恢复该因素。
- 从
loan-approval.scenario.yaml删除manual-review。运行治理入口,预期在客户 bootstrap 前得到INVALID和REQUIRED_CASE_MISSING。停止并恢复 Case,不要降低 Policy。 - 把一个会调用两次的 Fixture rule 的
maximum改为1。预期得到FIXTURE_OVER_CONSUMED:<fixtureSetId>/<ruleId>。检查 attempt 和 rule target,不要扩大为无界消费。 - 把 effect behavior 改为
ALLOW_REAL。在CI_SAFE下预期得到INVALID和REAL_EXECUTION_FORBIDDEN。停止运行;若真实 adapter 必须参与,应在隔离环境另建明确能力边界,不能在本门禁内放行。
记录表只保留可复核事实:改动、状态、第一条 reason code、bootstrap 是否被调用、恢复动作。不要只保存终端截图。
现实迁移:高额退款控制
在电商退款流程中,让 Policy 保护“高额退款必须人工复核”的 Case,让 Fixture
只接管 payment.refund,并让 CI_SAFE 禁止真实支付 adapter。分三次运行删除
Case、重复消费 Fixture、放行真实 port。真正有用的结果是每次停在哪一层,而不是
控制台最终能否变绿。
本章边界
BUSINESS_CONTRACT回答业务结果;STRUCTURAL_CONTRACT回答 node、invocation、Fixture 消费和补偿事实。INVOCATION与EFFECT_INPUT是观察选择器,不是生产 adapter 认证。- Fixture 通过不证明生产 HTTP、数据库或消息协议兼容。
VerificationEffectResolverFingerprintProvider绑定 resolver 配置;它不把运行时自然变化的状态写进配置摘要。
实验验收卡
- 预期与观察: Policy、Fixture、Assurance 分别约束动作、模拟行为和证据强度。
- 失败与恢复: 删除 Case、超额消费或切到 ALLOW_REAL;按 reason 修复。
- 证明边界: 证明入口受控,不证明模拟服务等同生产。
- 练习合同: 人工复核 Case;每次攻击一个控制;交付三份失败 receipt;每个 reason 对应唯一动作即停止。
本章小结
Policy 防止验证范围被静默缩小,Fixture 控制外部行为,execution profile 固定可重复运行的基础输入。只有 policy/profile preflight 成功且 Fixture 消费检查完成后,业务 PASS 或 FAIL 才值得解释。下一章把同样的纪律扩展到暂停、信号、恢复和逻辑时间。
下一章:第 26 章 —— Durable 与 Temporal 验证
Coding Agent: Open the versioned task guide.