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>
This commit is contained in:
parent
14c79dbfdd
commit
9a6feddfee
76
cheap-worker/cheap_assert.py
Normal file
76
cheap-worker/cheap_assert.py
Normal file
@ -0,0 +1,76 @@
|
||||
"""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"]),
|
||||
}
|
||||
@ -27,6 +27,7 @@ import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import cheap_assert
|
||||
import cheap_run
|
||||
|
||||
# A11 M4 模块重生成:behavior 在便宜档映射到玩法文件(KTD4)——只重写它,其余文件保持不变。
|
||||
@ -272,7 +273,7 @@ def materialize_source_project(game_id: str, files) -> Path:
|
||||
# ───────────────────────── ③ execute_deterministic_modify(集成) ─────────────────────────
|
||||
|
||||
def execute_deterministic_modify(game_id: str, source_project, modify_patch,
|
||||
*, port: int = 4320, cdp_port: int = 9222) -> dict:
|
||||
*, base_version_id=None, port: int = 4320, cdp_port: int = 9222) -> dict:
|
||||
"""确定性执行集成:apply 改一处 → 落盘已知良源 → 重建 → 九门。复用 cheap_run 九门,不另造。
|
||||
|
||||
流程:apply_deterministic_modify → 若 ok:(缺 plumbing 才 scaffold 已知良 entry-bundle/index.html)→
|
||||
@ -326,11 +327,21 @@ def execute_deterministic_modify(game_id: str, source_project, modify_patch,
|
||||
pr = cheap_run.play(game_id, port=port, cdp_port=cdp_port)
|
||||
verdict = pr.get("verdict")
|
||||
gates_pass = isinstance(verdict, dict) and verdict.get("pass") is True
|
||||
status = "succeeded" if gates_pass else "failed"
|
||||
log(f"确定性改重建+九门完成 game_id={game_id} status={status} smokeOk={sm.get('ok')} playOk={pr.get('ok')}")
|
||||
return {"status": status, "manifest": manifest, "verdict": verdict,
|
||||
"stage": "play", "error": None if status == "succeeded" else "九门未通过",
|
||||
"smokeOk": sm.get("ok"), "playOk": pr.get("ok")}
|
||||
|
||||
# M5 三结构断言:base→改后 真比对(断言①改动真生效 / 断言②非目标稳)+ 血缘(③ baseVersionId)。
|
||||
# 断言②对 deterministic 是兜 apply bug(理应只改目标文件,真比对抓"是否顺手碰了非目标")。
|
||||
base_files = _as_dict(source_project).get("files") if isinstance(_as_dict(source_project).get("files"), dict) else {}
|
||||
target_files = {m["file"] for m in manifest if m.get("file")}
|
||||
assertions = cheap_assert.three_assertions(manifest, base_files, applied["files"], target_files, base_version_id)
|
||||
a12_pass = assertions["a1_changeApplied"]["pass"] and assertions["a2_nontargetStable"]["pass"]
|
||||
status = "succeeded" if (gates_pass and a12_pass) else "failed"
|
||||
err = None
|
||||
if status != "succeeded":
|
||||
err = "九门未通过" if not gates_pass else "三断言未通过:" + json.dumps(
|
||||
{k: v for k, v in assertions.items() if isinstance(v, dict) and not v.get("pass")}, ensure_ascii=False)
|
||||
log(f"确定性改重建+九门+三断言完成 game_id={game_id} status={status} gates={gates_pass} a12={a12_pass}")
|
||||
return {"status": status, "manifest": manifest, "verdict": verdict, "assertions": assertions,
|
||||
"stage": "play", "error": err, "smokeOk": sm.get("ok"), "playOk": pr.get("ok")}
|
||||
|
||||
|
||||
# ───────────────────────── ④ execute_regenerate_modify(模块重生成 · 改玩法)─────────────────────────
|
||||
@ -368,10 +379,12 @@ def _regen_manifest(intent: str, *, changed, stable) -> dict:
|
||||
"changed": changed, "untouchedStable": stable}
|
||||
|
||||
|
||||
def _regen_why(gates_pass: bool, untouched_stable: bool) -> str:
|
||||
"""失败诊断文案:断言②泄漏优先(误伤非目标比九门不过更严重)。"""
|
||||
def _regen_why(gates_pass: bool, untouched_stable: bool, changed: bool = True) -> str:
|
||||
"""失败诊断文案:断言②泄漏优先(误伤非目标比九门不过更严重)→ 断言①no-op → 九门。"""
|
||||
if not untouched_stable:
|
||||
return "断言②失败:模块重生成误伤了非目标文件(core/render/assets/host-config 等应保持不变)"
|
||||
if not changed:
|
||||
return "断言①失败:模块重生成未改变 game-logic.js(no-op,目标文件没动)"
|
||||
if not gates_pass:
|
||||
return "九门未通过"
|
||||
return "模块重生成失败"
|
||||
@ -408,7 +421,7 @@ def _default_rewrite(game_id: str, intent: str, *, port: int = 4320, cdp_port: i
|
||||
|
||||
|
||||
def execute_regenerate_modify(game_id: str, source_project, modify_patch,
|
||||
*, rewrite_fn=None, port: int = 4320, cdp_port: int = 9222) -> dict:
|
||||
*, base_version_id=None, rewrite_fn=None, port: int = 4320, cdp_port: int = 9222) -> dict:
|
||||
"""模块重生成执行(改玩法):取 base 源 + intent → 有界单文件重写 game-logic.js → 九门 → 断言②非目标稳定 + 改动清单。
|
||||
|
||||
流程:取 intent(空→failed 不重写)→ 取 base 源 files(无源→failed)→ scaffold-if-needed + materialize
|
||||
@ -487,14 +500,25 @@ def execute_regenerate_modify(game_id: str, source_project, modify_patch,
|
||||
"verdict": None, "summary": None,
|
||||
"error": f"模块重生成异常:{type(e).__name__}: {e}"}
|
||||
|
||||
# 7. 据九门 verdict + 断言②判 status:九门过 ∧ 非目标稳 才算成功(误伤非目标=边界泄漏,即便九门过也判失败)。
|
||||
# 7. 据九门 verdict + 三断言判 status:九门过 ∧ 改动真生效(①) ∧ 非目标稳(②) 才算成功
|
||||
# (误伤非目标=边界泄漏、目标没变=no-op,即便九门过也判失败)。
|
||||
verdict_full = run_summary.get("verdictFull") if isinstance(run_summary.get("verdictFull"), dict) else None
|
||||
vb = run_summary.get("verdict") if isinstance(run_summary.get("verdict"), dict) else None
|
||||
gates_pass = bool(vb and vb.get("pass") is True)
|
||||
status = "succeeded" if (gates_pass and untouched_stable) else "failed"
|
||||
# 三断言 verdict(统一形态,与 deterministic 路一致):①changed ②非目标稳(含误伤清单)③血缘。
|
||||
nontarget_changed = sorted(k for k in set(nontarget_before) | set(nontarget_after)
|
||||
if nontarget_before.get(k) != nontarget_after.get(k))
|
||||
assertions = {
|
||||
"a1_changeApplied": {"pass": changed, "reason": "" if changed else "目标文件未变(no-op)"},
|
||||
"a2_nontargetStable": {"pass": untouched_stable, "nontargetChanged": nontarget_changed},
|
||||
"a3_lineageQueryable": cheap_assert.assert_lineage(base_version_id),
|
||||
"allPass": bool(changed and untouched_stable and base_version_id is not None),
|
||||
}
|
||||
a12_pass = changed and untouched_stable
|
||||
status = "succeeded" if (gates_pass and a12_pass) else "failed"
|
||||
stage = run_summary.get("stage") or "code"
|
||||
log(f"模块重生成完成 game_id={game_id} status={status} changed={changed} "
|
||||
f"untouchedStable={untouched_stable} gatesPass={gates_pass} stage={stage}")
|
||||
return {"status": status, "stage": stage, "manifest": manifest,
|
||||
"verdict": verdict_full, "summary": run_summary,
|
||||
"error": None if status == "succeeded" else _regen_why(gates_pass, untouched_stable)}
|
||||
"verdict": verdict_full, "summary": run_summary, "assertions": assertions,
|
||||
"error": None if status == "succeeded" else _regen_why(gates_pass, untouched_stable, changed)}
|
||||
|
||||
@ -209,7 +209,7 @@ def test_worker_deterministic_route_attaches_manifest_on_failed():
|
||||
fake = {"status": "failed",
|
||||
"manifest": [{"file": "src/core.js", "kind": "config", "key": "X", "old": None, "new": "5", "found": False}],
|
||||
"verdict": None, "error": "no-op:常量未找到", "stage": "apply"}
|
||||
state = W.WorkerState(modify_fn=lambda gid, sp, patch: fake,
|
||||
state = W.WorkerState(modify_fn=lambda gid, sp, patch, **kw: fake,
|
||||
send_fn=lambda u, p, s: captured.update(payload=p) or (200, ""),
|
||||
profile_fn=lambda j, g: None)
|
||||
job = {"job_id": "m1", "traceId": "m1", "modifyMode": "deterministic", "gameId": "nope-x",
|
||||
@ -234,7 +234,7 @@ def test_worker_deterministic_route_success_carries_bundle_and_source():
|
||||
"old": "30000", "new": "20000", "found": True}],
|
||||
"verdict": {"pass": True, "guards": {}}, "error": None, "stage": "play"}
|
||||
profile = {"tickModel": "realtime", "inputModel": "discrete-choice", "progressModel": "metric"}
|
||||
state = W.WorkerState(modify_fn=lambda *a: fake,
|
||||
state = W.WorkerState(modify_fn=lambda *a, **kw: fake,
|
||||
send_fn=lambda u, p, s: captured.update(payload=p) or (200, ""),
|
||||
profile_fn=lambda j, g: profile)
|
||||
job = {"job_id": "m2", "traceId": "m2", "modifyMode": "deterministic", "gameId": gid,
|
||||
|
||||
@ -188,7 +188,7 @@ def test_worker_regenerate_route_success_carries_manifest_bundle_source():
|
||||
"attempts": 4, "stage": "play", "costRmb": 0.7, "models": {"code": "MiniMax-M3"}, "gameId": gid},
|
||||
"error": None}
|
||||
profile = {"tickModel": "realtime", "inputModel": "discrete-choice", "progressModel": "metric"}
|
||||
state = W.WorkerState(regen_fn=lambda *a: fake,
|
||||
state = W.WorkerState(regen_fn=lambda *a, **kw: fake,
|
||||
send_fn=lambda u, p, s: captured.update(payload=p) or (200, ""),
|
||||
profile_fn=lambda j, g: profile)
|
||||
job = {"job_id": "rmok", "traceId": "rmok", "modifyMode": "regenerate-module", "gameId": gid,
|
||||
@ -214,7 +214,7 @@ def test_worker_regenerate_route_failed_attaches_error_and_manifest():
|
||||
"changed": True, "untouchedStable": False},
|
||||
"verdict": {"pass": True, "guards": {}}, "summary": None,
|
||||
"error": "断言②失败:模块重生成误伤了非目标文件"}
|
||||
state = W.WorkerState(regen_fn=lambda *a: fake,
|
||||
state = W.WorkerState(regen_fn=lambda *a, **kw: fake,
|
||||
send_fn=lambda u, p, s: captured.update(payload=p) or (200, ""),
|
||||
profile_fn=lambda j, g: None)
|
||||
job = {"job_id": "rmf", "traceId": "rmf", "modifyMode": "regenerate-module", "gameId": gid,
|
||||
@ -226,7 +226,7 @@ def test_worker_regenerate_route_failed_attaches_error_and_manifest():
|
||||
assert "sourceProject" not in captured["payload"], "失败路不带新版源"
|
||||
|
||||
|
||||
def _boom_regen(*a):
|
||||
def _boom_regen(*a, **kw):
|
||||
raise AssertionError("deterministic/create 路不应调用 regen_fn")
|
||||
|
||||
|
||||
@ -240,7 +240,7 @@ def test_worker_deterministic_unaffected_by_regen_wiring():
|
||||
det = {"status": "succeeded",
|
||||
"manifest": [{"file": "src/core.js", "kind": "config", "key": "SPEED", "old": "5", "new": "8", "found": True}],
|
||||
"verdict": {"pass": True, "guards": {}}, "error": None, "stage": "play"}
|
||||
state = W.WorkerState(modify_fn=lambda *a: det, regen_fn=_boom_regen,
|
||||
state = W.WorkerState(modify_fn=lambda *a, **kw: det, regen_fn=_boom_regen,
|
||||
send_fn=lambda u, p, s: captured.update(payload=p) or (200, ""),
|
||||
profile_fn=lambda j, g: None)
|
||||
job = {"job_id": "det1", "traceId": "det1", "modifyMode": "deterministic", "gameId": gid,
|
||||
|
||||
117
cheap-worker/tests/test_a11_m5_assertions.py
Normal file
117
cheap-worker/tests/test_a11_m5_assertions.py
Normal file
@ -0,0 +1,117 @@
|
||||
"""test_a11_m5_assertions.py — A11 切片三 M5 三结构断言机器门单测。
|
||||
|
||||
plan① 线 154 的 A11 完成判据(九门之外补三条,验"改对了没、有没有误伤"):
|
||||
① 改动真生效(非 no-op):请求的结构化改动可断言真实落地;
|
||||
② 非目标稳定:除目标文件外,其余文件字节未变(没顺手改坏别的模块);
|
||||
③ 血缘可查:新版本带 baseVersionId(可回溯改自哪一版)。
|
||||
三断言跨两档统一(deterministic manifest=list / regenerate manifest=dict),由 base→new files 真比对得出,
|
||||
不只信执行器自报的标记(防执行器 bug 自证)。
|
||||
|
||||
跑:cheap-worker/.venv/bin/python cheap-worker/tests/test_a11_m5_assertions.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1])) # → cheap-worker/
|
||||
import cheap_assert as A # noqa: E402
|
||||
|
||||
_BASE = {
|
||||
"src/core.js": "export const ROUND_MS = 30000;\n",
|
||||
"src/game-logic.js": "// 玩法 v1\n",
|
||||
"src/render.js": "// 渲染\n",
|
||||
"src/assets.js": "export const IMAGE_FILES = {};\n",
|
||||
}
|
||||
|
||||
|
||||
# ── 断言①:改动真生效(非 no-op)──
|
||||
|
||||
def test_a1_deterministic_real_change():
|
||||
manifest = [{"file": "src/core.js", "kind": "config", "key": "ROUND_MS", "old": "30000", "new": "20000", "found": True}]
|
||||
assert A.assert_change_applied(manifest)["pass"] is True
|
||||
|
||||
|
||||
def test_a1_deterministic_noop_found_false():
|
||||
manifest = [{"file": "src/core.js", "kind": "config", "key": "NOPE", "old": None, "new": None, "found": False}]
|
||||
assert A.assert_change_applied(manifest)["pass"] is False
|
||||
|
||||
|
||||
def test_a1_deterministic_noop_same_value():
|
||||
manifest = [{"file": "src/core.js", "kind": "config", "key": "ROUND_MS", "old": "30000", "new": "30000", "found": True}]
|
||||
assert A.assert_change_applied(manifest)["pass"] is False, "found 但值没变=no-op"
|
||||
|
||||
|
||||
def test_a1_regenerate_changed_true():
|
||||
assert A.assert_change_applied({"file": "src/game-logic.js", "kind": "behavior", "changed": True})["pass"] is True
|
||||
|
||||
|
||||
def test_a1_regenerate_changed_false():
|
||||
assert A.assert_change_applied({"file": "src/game-logic.js", "kind": "behavior", "changed": False})["pass"] is False
|
||||
|
||||
|
||||
# ── 断言②:非目标稳定(base→new 真比对,不信自报)──
|
||||
|
||||
def test_a2_only_target_changed():
|
||||
new = dict(_BASE)
|
||||
new["src/core.js"] = "export const ROUND_MS = 20000;\n" # 只改目标
|
||||
out = A.assert_nontarget_stable(_BASE, new, target_files={"src/core.js"})
|
||||
assert out["pass"] is True
|
||||
assert out["nontargetChanged"] == []
|
||||
|
||||
|
||||
def test_a2_collateral_damage_caught():
|
||||
new = dict(_BASE)
|
||||
new["src/core.js"] = "export const ROUND_MS = 20000;\n"
|
||||
new["src/render.js"] = "// 渲染 被顺手改坏\n" # 误伤非目标
|
||||
out = A.assert_nontarget_stable(_BASE, new, target_files={"src/core.js"})
|
||||
assert out["pass"] is False, "误伤非目标必须被抓"
|
||||
assert "src/render.js" in out["nontargetChanged"]
|
||||
|
||||
|
||||
def test_a2_regenerate_target_gamelogic():
|
||||
new = dict(_BASE)
|
||||
new["src/game-logic.js"] = "// 玩法 v2 连击\n"
|
||||
out = A.assert_nontarget_stable(_BASE, new, target_files={"src/game-logic.js"})
|
||||
assert out["pass"] is True
|
||||
|
||||
|
||||
# ── 断言③:血缘可查(baseVersionId 在)──
|
||||
|
||||
def test_a3_lineage_present():
|
||||
assert A.assert_lineage(4096)["pass"] is True
|
||||
|
||||
|
||||
def test_a3_lineage_missing():
|
||||
assert A.assert_lineage(None)["pass"] is False
|
||||
|
||||
|
||||
# ── 合并三断言 verdict ──
|
||||
|
||||
def test_three_assertions_all_pass():
|
||||
new = dict(_BASE)
|
||||
new["src/core.js"] = "export const ROUND_MS = 20000;\n"
|
||||
manifest = [{"file": "src/core.js", "kind": "config", "key": "ROUND_MS", "old": "30000", "new": "20000", "found": True}]
|
||||
v = A.three_assertions(manifest, _BASE, new, target_files={"src/core.js"}, base_version_id=4096)
|
||||
assert v["allPass"] is True
|
||||
assert v["a1_changeApplied"]["pass"] and v["a2_nontargetStable"]["pass"] and v["a3_lineageQueryable"]["pass"]
|
||||
|
||||
|
||||
def test_three_assertions_fails_on_collateral():
|
||||
new = dict(_BASE)
|
||||
new["src/core.js"] = "export const ROUND_MS = 20000;\n"
|
||||
new["src/render.js"] = "// 误伤\n"
|
||||
manifest = [{"file": "src/core.js", "kind": "config", "key": "ROUND_MS", "old": "30000", "new": "20000", "found": True}]
|
||||
v = A.three_assertions(manifest, _BASE, new, target_files={"src/core.js"}, base_version_id=4096)
|
||||
assert v["allPass"] is False, "误伤非目标 → 整体不过"
|
||||
|
||||
|
||||
def _run_all():
|
||||
fns = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
|
||||
for fn in fns:
|
||||
fn()
|
||||
print(f" ✓ {fn.__name__}")
|
||||
print(f"\n[test_a11_m5_assertions] {len(fns)}/{len(fns)} passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_run_all()
|
||||
@ -277,7 +277,8 @@ def _process_modify_job(state: WorkerState, job: dict, modify: dict) -> dict:
|
||||
import cheap_modify
|
||||
modify_fn = cheap_modify.execute_deterministic_modify
|
||||
|
||||
result = modify_fn(game_id, modify.get("sourceProject"), modify.get("modifyPatch"))
|
||||
result = modify_fn(game_id, modify.get("sourceProject"), modify.get("modifyPatch"),
|
||||
base_version_id=modify.get("baseVersionId")) # M5 三断言③血缘透传
|
||||
summary = _modify_summary(result, game_id)
|
||||
game_dir = cheap_run.game_dir(game_id)
|
||||
|
||||
@ -353,7 +354,8 @@ def _process_regenerate_job(state: WorkerState, job: dict, modify: dict) -> dict
|
||||
import cheap_modify
|
||||
regen_fn = cheap_modify.execute_regenerate_modify
|
||||
|
||||
result = regen_fn(game_id, modify.get("sourceProject"), modify.get("modifyPatch"))
|
||||
result = regen_fn(game_id, modify.get("sourceProject"), modify.get("modifyPatch"),
|
||||
base_version_id=modify.get("baseVersionId")) # M5 三断言③血缘透传
|
||||
summary = _regen_summary(result, game_id)
|
||||
game_dir = cheap_run.game_dir(game_id)
|
||||
|
||||
|
||||
@ -306,6 +306,13 @@ stateDiagram-v2
|
||||
- **一次真 LLM smoke(子代理单跑,本机 Chrome + new-api M3)**:base=`amgen-bake-click-score-0`、intent="把命中加分从固定改成连击递增" → **status=succeeded、untouchedStable=True、九门 verdict.pass=True、attempts=1、¥0.31、137s、无熔断**;独立 diff 佐证 5 个非目标文件字节相同、仅 game-logic.js 变且真实现连击递增。**M4 plumbing 通**(重写发生 + 写边界守住 + 非目标稳 + 九门跑到 verdict);此次质量也恰好达标(质量归 agent 层、不在 M4 判据)。
|
||||
- **M4 收口**:两档执行(deterministic + regenerate-module)全落地;断言②地基(非目标 hash 稳)就位。
|
||||
|
||||
**M5 验收闭环 · 三断言机器门(完成、绿,2026-06-29)· 受计费 e2e 评估中**
|
||||
|
||||
- **`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 真可查由后端 e2e + M1 回填)。`three_assertions` 合并 verdict。`test_a11_m5_assertions` 12/12。
|
||||
- **集成进 execute_***:两档执行加 `base_version_id` 参 + 计算并附 `assertions` verdict + 收紧 status 判据(succeeded ⟺ 九门 ∧ ① ∧ ②;regenerate 原漏判 ①changed 已补,no-op 重写不再算成功);worker `_process_*_job` 透传 `modify.baseVersionId`。全 **85 测试**独立复跑全绿(含 M3/M4 注入桩接 kwarg 修)。
|
||||
- **集成端到端 smoke(主会话独立、真九门)**:ROUND_MS 30000→19000 + base_version_id=4096 → **status=succeeded、a1/a2/a3 全 true、allPass=true、落盘 core.js=19000**。三断言真接进执行器、清洁改动全过。
|
||||
- **受计费真后端 e2e(mini-desktop)**:A11 全链路代码已完成 + 本机端到端逐段验证(判意图链路 / 两档执行 + 真九门 / 三断言 / 血缘回填均已一手坐实)。剩最终集成证 = 经真后端(studio `/modify/plan`+`/modify` → aigc → worker → 回调 → 新预览版 + D12 计费 + base_version_id 可查)跑通一次受计费调整任务,需 mini-desktop 全栈起。
|
||||
|
||||
## 状态
|
||||
|
||||
双评审已过(发现全处置)→ 待创始人批 → 执行(M1→M5 顺序,M1 为 plan① 所称"首个里程碑")。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user