02 · LOOPS/L2 · Integration

L2 · Integration Loop

中间层。当所有 Unit passed 后开闸:跑集成测试 → 若失败则归因 → 改 SDD → 回 L1。L2 是「跨 Unit 契约」一致性的守门员。

状态机

run-integration failstderr + diff ship triageBugTriager patch SDDArchitect reset units→ L1

L2 闸门触发条件

不是「所有 Unit 都跑过一次」,而是「所有 Unit 的当前 contract_hash 与依赖关系下,状态都是 passed」。这避免 SDD 改过之后某个旧版 Unit 还停留在 passed 而事实上契约已变。

function l2GateOpen(project) {
  return project.units.every(u =>
    u.status === "passed" &&
    u.contract_hash === currentSDD(project).contractHashFor(u.id)
  );
}

失败归因 — BugTriager 输出契约

BugTriager 必须输出一个机读 JSON,LoopController 据此自动决定回滚范围。Triager 不直接改文件,只发「建议」。

{
  "run_id": "run-int-20260512-1031",
  "root_cause": "auth-be returns 500 on invalid pwd, expected 401; auth-fe error handler relies on 401",
  "category": "contract_mismatch",
  "affected_units": ["auth-be", "auth-fe"],
  "proposed_sdd_patch": {
    "target_section": "#unit-auth-be",
    "op": "update",
    "text": "明确:登录失败必须 401 with {error:'invalid_credentials'}"
  },
  "confidence": 0.82
}

归因 → 回写 → 重置 三步

① 归因

BugTriager 读 stderr + diff + units.json + sdd.html,产 triage.json。confidence < 0.5 时上报 L3 让人类裁决。

② 回写

Architect 接收 proposed_sdd_patch,用 section 级 DOM 操作改 sdd.html,归档旧版到 history/sdd-rev-N.html。

③ 重置

UnitRegistry 把 affected_units 的 status 改回 pending,并自动重算 contract_hash,触发 L1 新一轮。

L2 修复预算

同一项目每个 PRD rev 内最多触发 3 次 L2 修复轮回。超出后整个项目挂起,发 SSE 事件 project.l2_exhausted,等待人类(L3)发新需求或手动干预。

为什么是 3:实测 Claude Code Agent 三轮内还修不好的,要么是契约描述本身有歧义(PRD 缺陷),要么是技术选型有根本问题。再加轮次只会浪费 Token。

集成测试套件从哪来?

这是一个设计决策(见 风险与待决 · R3)。初版选 方案 A:

方案谁写集成测试优点缺点
A · 选中Architect 在生成 SDD 时同步生成 integration/spec.json少一个 Agent,集成测试与 SDD 同步演化Architect 职责变重
B独立 IntegrationAuthor Agent关注点分离多一个 Agent,多一次握手
CIntegrator 自己写读取代码后再写最贴合容易写一个「永远会通过」的 trivial test