Skip to main content

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

第 32 章 —— 变化归因、Maven 与发布决策

本章承诺: 固定一个变化轴,运行与问题相称的最小 Maven 阶梯,并判断现有 evidence 只支持本地诊断、团队门禁、治理审批,还是发布决策。

学习目标

  1. 分开 DSL graph 变更、BLOGE toolchain 升级和业务代码变更。
  2. 区分“观察到差异”和“BLOGE 能归因差异”。
  3. 按需要支持的 claim 选择 Maven 命令,而不是按控制台有多绿来选择。
  4. 保存发布决策所需的 receipt 和 external attestation。

升级改变了结果,但原因是什么?

贷款团队升级到 BLOGE 0.9.8-RC1 后,边界申请从自动批准变为人工复核。同一分支里还有人修改了 risk Operator。前后测试能显示差异,却不能把差异归给其中一项变更。

图 31:五个责任人把一次冻结比较传递到发布决策

归因需要受控实验:只改变一个声明轴,冻结所有重要的非目标事实。Operator 和 runtime 同时变化时,先停在诊断层,把实验拆成两次 comparison。

把一次升级拆成两张 receipt

“升级分支”不是有效 change axis,它混合 source edit、graph edit、dependency resolution 和 runtime bytes。贷款团队从同一 common baseline 建立两个实验:

图 32.2:把被污染的升级拆成一张 DSL-axis receipt 和一张 toolchain-axis receipt

实验唯一变化事实必须冻结的事实诚实结果
A — DSL_GRAPHbaseline graph → candidate graphBLOGE runtime、Scenario、Policy、Fixture、bootstrap、registry、profilegraph 差异可归因,或 COMPARISON_INPUT_DRIFT
B — BLOGE_TOOLCHAINbaseline BLOGE class inventory → RC1 inventorybusiness classpath、graph、声明输入、Java launcher/protocol只有 BLOGE_VERIFIED observation 才能归因

实验 A 对应 attributesAnObservedBusinessRegressionToTheDslGraphAxis:只改变 candidate graph 的 decision input。两侧各执行一次;candidate report 为 FAIL,change code 包含 OUTPUT_DIGEST_CHANGED:approve-low-risk/decision,comparison 可归因但不 equivalent。

控制实验 attributesOnlyDslGraphChangeWhenEveryFrozenExecutionFactMatches 改变 graph bytes,却保持 observed contract 不变;结果同时 attributable 和 equivalent。因此 attributableequivalent 是两个独立问题。

实验 B 对应 blogeOwnedChildExecutesTheFrozenScenarioBeforeAttributingTheToolchainAxis:固定 business classes 与声明输入,改变 runtime inventory,由两个终止式 child JVM 返回 BLOGE_VERIFIED observations。结果 attributable、observationally equivalent、equivalent,并发布为 SOURCE_BOUND。把 artifact 复制到 bound project location 之外,会得到 COMPARISON_SOURCE_BINDING_DRIFT

再攻击一个冻结事实:businessClasspathMutationIsReportedAsInputDrift 在 toolchain 实验中改变 business classpath marker。结果变成 COMPARISON_INPUT_DRIFTattributable=false。runtime upgrade 可能仍值得研究,但这次运行已经不能声称它导致了 observation。

只比较一个 DSL graph 变化轴

只变 graph 时,两次执行复用同一个 canonical Scenario 和 verifier 的 source-bound 环境:

ComparisonPlan plan = ComparisonPlan.dslGraph(
"loan-risk-threshold-v3",
scenarioPath,
baselineGraphPath,
candidateGraphPath);

VerificationAttributedComparisonEvidence evidence =
verifier.compareEvidence(plan);

compareEvidence(...) 只在 DSL_GRAPH 可归因时发布。它会冻结 Scenario、Policy、Fixture、selected Cases、execution profile、bootstrap、Operator registry、service identity、governance inputs 和 Git source binding。非目标事实漂移会产生 COMPARISON_INPUT_DRIFT,只保留内存诊断。

attributable() 回答观察差异是否属于声明轴。equivalent() 更严格:必须可以归因、两侧都通过,且 payload-free observations 一致。输出相同不证明 candidate 更正确,只说明当前合同范围内没有观察到回归。

用冻结的业务代码比较两套 BLOGE toolchain

runtime 升级需要两个执行后终止的 child JVM,并共享一份 business runtime:

ToolchainBusinessRuntime business = ToolchainBusinessRuntime.of(
businessClasspath,
"com.acme.loan.LoanApprovalVerificationBootstrap");

ToolchainComparisonPlan plan = ToolchainComparisonPlan.blogeVerification(
"bloge-0.9.7-to-0.9.8-rc1",
inputs,
business,
ToolchainEndpoint.blogeVerifier("baseline", baselineToolchain),
ToolchainEndpoint.blogeVerifier("candidate", candidateToolchain));

VerificationToolchainComparisonEvidence evidence =
PairedExecutionCoordinator.builder()
.projectRoot(projectRoot)
.build()
.compareEvidence(plan, outputRoot);

inputs 必须包含 scenariopolicy 和所有被引用的 DSL、Fixture、Oracle 文件。两个 child 使用 coordinator 的 Java launcher、同一 ordered business classpath,以及不同的 BLOGE class inventory。固定 BLOGE child 按 project-relative path 重建文件,再运行真实 ScenarioVerifier

自定义 javaMain(...) adapter 只有 PROVIDER_ATTESTED 权威。它可以报告 observationallyEquivalent(),但不能让 attributable()equivalent() 为 true。只有 blogeVerifier(...) 能提供足以归因的 BLOGE_VERIFIED observation。

运行与问题匹配的 Maven 阶梯

  1. 聚焦故事检查——快速反馈。 运行贷款 starter 的三个测试。11 个 passing tests 覆盖选中的故事,不代表整个 Reactor。

    mvn -pl bloge-starter-loan-approval -am \
    -Dtest='LoanApprovalApplicationTest,LoanApprovalBusinessScenarioTest,LoanApprovalOperatorFlowTest' \
    -Dsurefire.failIfNoSpecifiedTests=false test
  2. 源码检出时先安装 Preview plugin。 如果 0.9.8-RC1 尚未发布到你使用的 Maven 仓库,先从同一源码快照安装。invoker.skip 只跳过插件自己的集成 fixture;它不能替代后面的完整 Reactor 检查。

    mvn -pl bloge-maven-plugin -am install \
    -DskipTests -Dinvoker.skip=true
  3. 发布 customer module verification artifacts——Preview。 在客户 module 中,把编译与 goal 放在同一次 Maven 调用里。goal 读取 .blogeverify.yaml,写入配置的 report directory。

    mvn test-compile \
    com.leanowtech.bloge:bloge-maven-plugin:0.9.8-RC1:verify

    这里的 PASS 仍受 config、source binding、receipt custody 和 evidence capability 限制。receipt 应保存在可变 artifact directory 之外。

    RC1 已知边界: 本书贷款 starter 的 bootstrap 包名是 com.leanowtech.bloge.starter.*0.9.8-RC1 的 plugin classloader 会把该前缀误判为 plugin-owned class,因此对这个 starter 执行 goal 会 fail closed:TEST_CLASSPATH_UNAVAILABLE。这不等于 Scenario 失败;当前可运行入口是第 1 步的 JUnit 测试。通用客户包名的 Plain Java 和 Spring bootstrap 由 plugin Invoker fixture 覆盖。修复类加载边界前,不把 starter 写成 artifact 发布成功案例。

  4. 检查完整 Java build。

    mvn clean install

    这会检查 Reactor build,不会创建缺失的业务 Requirement,也不会批准 Oracle。verify-reactor 是另一条 Preview 路径,需要 schema-v2 per-module governance 配置,以及同一 Git worktree 内的 direct modules。本书的贷款 starter 配置不声称已有这份 Reactor evidence。

把 evidence 转成决策

读取现有 evidence 支持的最高 ClaimCapability,同时保留它的边界:

  • 本地诊断——AUTHORING comparison 没有 source binding、没有固定单轴,或缺少 continuity evidence。先修实验,再讨论因果。
  • 团队门禁——TEAM_GATE 具备 source binding、Policy continuity、replay assurance 和 CI-safe execution。它支持团队集成,不代表业务治理审批。
  • 治理审批——GOVERNED_GATE 可信 Project evidence 还满足 versioned Requirements 和所需 Oracle provenance。
  • 发布决策——RELEASE_QUALIFIED governed Project evidence、exact attributable comparison receipt 和可信 external release attestation 绑定同一 change axis 与 Oracle closure。

external signature 只能证明 signer 认可 exact digests,不能证明未声明行为、生产兼容性,也不能证明业务政策本身合理。

实验:拆开一次被污染的升级

  1. 保持同一 Scenario、Policy、Fixture 集、bootstrap 和 source-bound verifier,只比较 baseline-risk.blogecandidate-risk.bloge
  2. 记录 changeAxis()attributable()equivalent()、reason codes 和 external receipt digest。
  3. 恢复 DSL。冻结 business classpath,再用 blogeVerification(...) 比较两套不同的 BLOGE toolchain inventory。
  4. 同时修改 risk Operator 和 BLOGE runtime。确认这不是合法的 single-axis plan,不要把观察差异称为 runtime regression。
  5. 把被污染的变更拆成 business-code experiment 和 toolchain experiment,请业务负责人判断变化后的业务 observation。
  6. assess 当前 governed 和 comparison evidence。在发布负责人签署前,写出最高支持能力和全部剩余 gap。

最后交付两张独立 receipt 和一张 gap card,而不是一片绿色 console:

DSL receipt axis · baseline digest · candidate digest · attributable · equivalent
Toolchain receipt axis · runtime digests · business-classpath digest · authority · attributable
Release gap card current capability · missing evidence · owner · next action

现实迁移:支付网关升级归因

同一发布分支如果同时升级支付 SDK 并修改 retry policy,成功率上升不能归因给其中 任何一项。先冻结业务代码,只比较 SDK inventory;再恢复 SDK,只比较 retry policy。保留两张独立 receipt,由发布负责人根据两个有边界的结果判断,不读取一张 被多变量污染的 dashboard。

本章边界

  • semantic difference 不会自动成为 attributable difference。
  • PROVIDER_ATTESTED protocol correctness 不是 BLOGE-owned execution authority。
  • mvn clean install 是 build evidence,不是 Requirement satisfaction。
  • attributable comparison 单独仍为 AUTHORING;release qualification 还需要 governed Project evidence 和可信 external attestation。
  • 本章任何 comparison 都不能证明声明的 Scenario、Oracle、inputs 和 runtime 边界以外的正确性。

实验验收卡

  • 预期与观察: 升级拆成 DSL_GRAPH 与 BLOGE_TOOLCHAIN 两张单轴 receipt。
  • 失败与恢复: 同时改 classpath 和 toolchain;冻结一轴后分别比较。
  • 证明边界: 证明固定输入的变化归因,不证明新版本可发布。
  • 练习合同: 前后两个版本;每次只变一轴;交付两张 receipt;每个差异只有一个候选原因即停止。

小结

冻结除一个声明轴以外的全部事实。DSL graph 使用 ScenarioVerifier.compareEvidence(...),BLOGE-owned toolchain comparison 使用 PairedExecutionCoordinator.compareEvidence(...)。再用 Maven 阶梯和 VerificationClaims 只报告当前 evidence 真正支持的决策能力。第六阶段在负责任的发布判断开始处结束:留下可归因、有边界、可独立复核的 evidence。

下一步:第六阶段回顾——从业务承诺到发布证据

Coding Agent: Open the versioned task guide.