feat(cheap-worker): Node 退役授权三条齐判定 retire_authorization(M2 U3)

读三份既有落盘报告聚合:① M1 达标(bake-off-M1-final.json overallMeets)
② 002 对照等价(compare-multi-002-final.json summary.equivalentGenres ⊇ 三品类)
③ M2 auto-vs-golden(meets)。三条全绿→authorized;任一 False/报告缺失/字段类型
不符→未授权并列明欠项(不静默当通过)。纯函数零 LLM 零真跑。单测 17/17。
真切路由/D12 扣退在 M3。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-27 08:43:58 -07:00
parent 423b0b6df3
commit dd0d1cc224
2 changed files with 148 additions and 0 deletions

View File

@ -12,6 +12,11 @@ tap-targets 不成立(M1 已把自动 spec 断言加厚到与金标逐字段一
U1 逐门 delta 判据(本段,纯逻辑可单测);U2 同游戏双驱动 play 真跑;U3 三条齐退役授权判定
"""
import json
from pathlib import Path
_DEFAULT_GENRES = ["click-score", "whack-mole", "shop-serve"]
# 九门里唯一对驱动器敏感的三门(未驱动时 E_live/H_progress 降 advisory、G_input 无输入 skip):
# 「这游戏到底能不能玩」的判据,自动低于金标即驱动更差,容差 0。
_KEY_GATES = frozenset({"E_live", "H_progress", "G_input"})
@ -91,3 +96,75 @@ def aggregate_delta(genre_runs: dict, required_genres: list) -> dict:
"note": "auto-vs-golden delta 门(同款双驱动 · 关键门容差 0 · 零 LLM);"
"覆盖面内鉴别力有限、定性为不退化确认,退役强证据=M1 达标+002 等价(见 U3)",
}
# ───────────────────────── U3 Node 退役授权三条齐判定(纯逻辑 · 零真跑 · 只读既有报告)─────────────────────────
def _read_json(path):
"""容错读 JSON 报告:文件缺失 / 解析失败 → None(由调用方判「前置证据缺失」、不静默当通过)。"""
p = Path(path)
if not p.exists():
return None
try:
return json.loads(p.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return None
def retire_authorization(m1_path, compare_path, avg_path, required_genres=None) -> dict:
"""Node 退役授权:三个硬前置全绿才出 authorized。
M1 80% 达标 = M1 收口报告 overallMeets is True(bake-off-M1-final.json)
002 对照等价 = 002 对照报告 summary.equivalentGenres required_genres(compare-multi-002-final.json)
M2 auto-vs-golden = U2 报告 meets is True(auto-vs-golden-*.json)
任一 False / 报告缺失 / 字段类型不符 authorized=Falsemissing 列明欠项(不静默当通过)
纯函数 LLM零真跑(只读既有落盘报告)
"""
req = list(required_genres) if required_genres else list(_DEFAULT_GENRES)
missing = []
# ① M1 达标。
m1 = _read_json(m1_path)
if m1 is None:
m1_met = False
missing.append("M1 达标:前置证据缺失(报告文件缺失或不可解析)")
elif m1.get("overallMeets") is not True:
m1_met = False
missing.append("M1 达标:overallMeets 非 True(≥80% 达标门未绿)")
else:
m1_met = True
# ② 002 对照等价。
cmp = _read_json(compare_path)
equiv = ((cmp or {}).get("summary") or {}).get("equivalentGenres")
if cmp is None:
parity_ok = False
missing.append("对照等价:前置证据缺失(报告文件缺失或不可解析)")
elif not isinstance(equiv, list) or not set(req).issubset(set(equiv)):
parity_ok = False
lack = [g for g in req if g not in (equiv or [])]
missing.append(f"对照等价:002 equivalentGenres 未覆盖全部品类(缺 {lack})")
else:
parity_ok = True
# ③ M2 auto-vs-golden。
avg = _read_json(avg_path)
if avg is None:
avg_ok = False
missing.append("auto-vs-golden:前置证据缺失(报告文件缺失或不可解析)")
elif avg.get("meets") is not True:
avg_ok = False
missing.append("auto-vs-golden:meets 非 True(逐门 delta 门未绿)")
else:
avg_ok = True
authorized = m1_met and parity_ok and avg_ok
return {
"authorized": authorized,
"conditions": {"m1Met": m1_met, "parityEquivalent": parity_ok, "autoVsGoldenAligned": avg_ok},
"requiredGenres": req,
"missing": missing,
"note": "Node 退役授权三条齐(M1 达标 ∧ 002 对照等价 ∧ auto-vs-golden 不退化);"
"authorized=授权切默认路由的条件齐备,真切路由/D12 扣退在 M3。本机口径,生产复验在 M3。",
}

View File

@ -12,7 +12,9 @@ test_auto_vs_golden.py — M2 auto-vs-golden 门单测(U1 逐门 delta 判据 +
:cheap-worker/.venv/bin/python cheap-worker/tests/test_auto_vs_golden.py
"""
import json
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1])) # → cheap-worker/
@ -118,6 +120,75 @@ def test_aggregate_missing_genre_fails():
assert r["meets"] is False and "shop-serve" in r["missingGenres"]
# ───────────────────────── U3 Node 退役授权三条齐判定 ─────────────────────────
_REQ = ["click-score", "whack-mole", "shop-serve"]
def _write(d: Path, name: str, obj) -> Path:
p = d / name
p.write_text(json.dumps(obj, ensure_ascii=False), encoding="utf-8")
return p
def _reports(d: Path, m1=True, equiv=None, avg=True):
"""造三份报告:M1 达标 / 002 等价品类集 / M2 auto-vs-golden meets。"""
m1p = _write(d, "m1.json", {"overallMeets": m1})
equiv = _REQ if equiv is None else equiv
cmp = _write(d, "cmp.json", {"summary": {"equivalentGenres": equiv}})
avgp = _write(d, "avg.json", {"meets": avg})
return m1p, cmp, avgp
def test_retire_all_three_green_authorized():
with tempfile.TemporaryDirectory() as t:
m1p, cmp, avgp = _reports(Path(t))
r = A.retire_authorization(m1p, cmp, avgp, _REQ)
assert r["authorized"] is True
assert r["conditions"] == {"m1Met": True, "parityEquivalent": True, "autoVsGoldenAligned": True}
assert not r["missing"]
def test_retire_m1_not_met_unauthorized():
with tempfile.TemporaryDirectory() as t:
m1p, cmp, avgp = _reports(Path(t), m1=False)
r = A.retire_authorization(m1p, cmp, avgp, _REQ)
assert r["authorized"] is False and r["conditions"]["m1Met"] is False
assert any("M1" in m or "达标" in m for m in r["missing"])
def test_retire_parity_missing_genre_unauthorized():
"""002 等价品类集缺一品类(某品类 regressed)→ 对照等价未达成。"""
with tempfile.TemporaryDirectory() as t:
m1p, cmp, avgp = _reports(Path(t), equiv=["click-score", "whack-mole"])
r = A.retire_authorization(m1p, cmp, avgp, _REQ)
assert r["authorized"] is False and r["conditions"]["parityEquivalent"] is False
assert any("对照" in m or "equivalent" in m.lower() for m in r["missing"])
def test_retire_avg_not_met_unauthorized():
with tempfile.TemporaryDirectory() as t:
m1p, cmp, avgp = _reports(Path(t), avg=False)
r = A.retire_authorization(m1p, cmp, avgp, _REQ)
assert r["authorized"] is False and r["conditions"]["autoVsGoldenAligned"] is False
assert any("auto-vs-golden" in m or "auto" in m.lower() for m in r["missing"])
def test_retire_missing_report_file_unauthorized():
"""任一报告文件缺失 → 前置证据缺失、不静默当通过。"""
with tempfile.TemporaryDirectory() as t:
m1p, cmp, avgp = _reports(Path(t))
r = A.retire_authorization(m1p, cmp, Path(t) / "nonexistent.json", _REQ)
assert r["authorized"] is False and r["conditions"]["autoVsGoldenAligned"] is False
assert any("缺失" in m or "证据" in m for m in r["missing"])
def test_retire_deterministic_zero_llm():
with tempfile.TemporaryDirectory() as t:
m1p, cmp, avgp = _reports(Path(t))
assert A.retire_authorization(m1p, cmp, avgp, _REQ) == A.retire_authorization(m1p, cmp, avgp, _REQ)
if __name__ == "__main__":
_fns = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
_failed = 0