lili 9a6feddfee feat(studio): A11 切片三 M5 三结构断言机器门(改对了没/有没有误伤/血缘可查)
九门只验"仍能玩",验不出"改对了没、有没有误伤";三断言补这层(plan① 线 154)。

cheap_assert.py:
- ① change_applied:改动真生效非 no-op(deterministic=manifest found 且 old≠new / regenerate=changed)
- ② nontarget_stable:base→new 真比对算内容差,非目标文件变=误伤——不信执行器自报标记,
  防执行器 bug 自证(兑现 Opus 评审 P1-2)
- ③ lineage_queryable:baseVersionId 在(worker 侧验必要条件,DB 真可查由 M1 回填 + 后端 e2e)
- three_assertions 合并 verdict

集成进 execute_*:两档加 base_version_id 参 + 附 assertions verdict + 收紧 status
(succeeded ⟺ 九门 ∧ ① ∧ ②;regenerate 原漏判 ①changed 已补);worker 透传 modify.baseVersionId。

测试:test_a11_m5_assertions 12/12 + 全 85 测试独立复跑全绿(含 M3/M4 桩接 kwarg)。
集成端到端 smoke(真九门):ROUND_MS 30000→19000 + baseVersionId=4096
→ status=succeeded、a1/a2/a3 全 true、allPass=true、落盘 19000。

受计费真后端 e2e(经 studio→aigc→worker→回调→新版本+D12计费+血缘)待 mini-desktop 全栈。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 02:34:06 -07:00

77 lines
3.9 KiB
Python

"""cheap_assert.py — A11 切片三 M5 三结构断言机器门。
plan① 线 154:A11 完成判据 = 九门 + 三层校验 + 三结构断言。九门只验"仍能玩",验不出"改对了没、有没有
误伤";三断言补这一层:
① change_applied —— 请求的结构化改动真实生效(非 no-op 重构);
② nontarget_stable —— 除目标文件外其余字节未变(没顺手改坏别的模块);
③ lineage_queryable —— 新版本带 baseVersionId(可回溯改自哪一版)。
设计要点:
· 断言②由 base→new files **真比对**得出(算内容差),**不信执行器自报的 untouchedStable 标记**——防执行器
自身 bug 自证(评审 Opus P1-2:目标文件内顺手改坏、或执行器误报,真比对才抓得到)。
· 跨两档统一:deterministic manifest=list[{file,kind,key,old,new,found}] / regenerate manifest=dict
{file,kind,intent,changed,untouchedStable};assert_change_applied 两形态都认。
· 纯函数无网络无 LLM、可单测;断言③的"可查"在 worker 侧只验 baseVersionId 在(必要条件),DB 真可查由
受计费后端 e2e 验(M1 已把 base_version_id 回填 game_source_project)。
"""
def assert_change_applied(manifest) -> dict:
"""断言① 改动真生效(非 no-op)。
deterministic(manifest=list):至少一条 found 且 old≠new(字符串化比对,容数值/字符串);
regenerate(manifest=dict):changed 为真。返 {pass, reason}。
"""
if isinstance(manifest, list):
applied = [m for m in manifest if m.get("found") and str(m.get("old")) != str(m.get("new"))]
return {"pass": bool(applied), "applied": len(applied),
"reason": "" if applied else "无任一条 found 且值真变化(no-op)"}
if isinstance(manifest, dict):
ok = bool(manifest.get("changed"))
return {"pass": ok, "reason": "" if ok else "regenerate changed=false(目标文件未变,no-op)"}
return {"pass": False, "reason": f"manifest 形态未知:{type(manifest).__name__}"}
def _changed_files(base_files: dict, new_files: dict) -> set:
"""base→new 之间内容真变了的文件集(含新增/删除);base/new 形如 路径→文本。"""
base = base_files or {}
new = new_files or {}
keys = set(base) | set(new)
return {k for k in keys if base.get(k) != new.get(k)}
def assert_nontarget_stable(base_files: dict, new_files: dict, target_files) -> dict:
"""断言② 非目标稳定:base→new 真变了的文件必须 ⊆ 目标文件集;有非目标变动 = 误伤。
target_files:允许改动的目标文件相对路径集合(deterministic=manifest 涉及的文件;regenerate={game-logic.js})。
{pass, changedFiles, nontargetChanged}。
"""
targets = set(target_files or ())
changed = _changed_files(base_files, new_files)
nontarget = sorted(changed - targets)
return {"pass": not nontarget, "changedFiles": sorted(changed), "nontargetChanged": nontarget}
def assert_lineage(base_version_id) -> dict:
"""断言③ 血缘可查:新版本带 baseVersionId(worker 侧验必要条件;DB 真可查由后端 e2e 验)。"""
ok = base_version_id is not None
return {"pass": ok, "baseVersionId": base_version_id,
"reason": "" if ok else "缺 baseVersionId(血缘断链)"}
def three_assertions(manifest, base_files: dict, new_files: dict, target_files, base_version_id) -> dict:
"""合并三断言 verdict:三条全过才算"改成"(配合九门 + 三层校验)。
Returns:
{a1_changeApplied, a2_nontargetStable, a3_lineageQueryable, allPass}。
"""
a1 = assert_change_applied(manifest)
a2 = assert_nontarget_stable(base_files, new_files, target_files)
a3 = assert_lineage(base_version_id)
return {
"a1_changeApplied": a1,
"a2_nontargetStable": a2,
"a3_lineageQueryable": a3,
"allPass": bool(a1["pass"] and a2["pass"] and a3["pass"]),
}