feat(planB-p2): U9 M0 core 联合门 turnkey(003-U3)——shell U8 + 三契约结构核

M0 core = 一句话真生成可玩游戏 + 三契约对齐:
- u9_m0_core_gate.py:shell U8 e2e + 运行时读真契约做结构核(API 信封 code==0 / dispatcher=source-project.schema 4键 / manifest=game-package.schema 8键);selftest 5/5
- 读契约 required 防硬编码漂移;缺 artifact 如实记 gap(不伪报)
- README 补「gen-done 接线时须补」4 项(play 路 / 遥测查询 / U8→U9 artifacts / 抽样源)
turnkey 三件齐(U7 rubric + U8 e2e + U9 M0 门),gen-done 落地即跑。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-20 01:38:39 -07:00
parent 4261a638a0
commit 7c2a35888d
2 changed files with 174 additions and 1 deletions

View File

@ -10,7 +10,7 @@
|---|---|---|---|
| **U7 rubric 评分R9** | `rubric_score.py` + `rubric-protocol.md` | ✅ 建好selftest 7/7 | `--template --batch <批manifest> -o sheet.json` → 人工填分 → `--score sheet.json` 判门 |
| **U8 feed真玩 e2eR4+R11** | `u8_feed_play_e2e.py`(接 backend_gw 全链 + play.cdp.cjs/前端 player_cdp + 资产对位断言) | ✅ 建好selftest 8/8 纯逻辑断言;编排步 gated on gen-done | `--run --prompt "…" --cdp <端点>`一句话→SAA 生成→发布→feed→试玩→断言published/in-feed/资产对位/九门/终局遥测。play 路二选一见文件注(产品前端 :4173 / 本地 bundle 九门) |
| **U9 M0 core 联合门003-U3** | `u9_m0_core_gate.*`(组合 U8 + 三契约核) | ⏳ 待建 | 跑 U8 e2e + 核 API/dispatcher/manifest 三契约对齐 |
| **U9 M0 core 联合门003-U3** | `u9_m0_core_gate.py`shell U8 + 运行时读契约结构核) | ✅ 建好selftest 5/5读真契约 dispatcher=4键/manifest=8键 | `--run`:跑 U8 e2e + 三契约核API 信封 code==0 / dispatcher=source-project.schema / manifest=game-package.schema |
## 门禁判据plan 003 对齐)
- **U7**N=8 评分表 + 总均分 ≥ 1.4 + 资产对位项无 0一票否决
@ -22,3 +22,10 @@ harness 的**逻辑/断言/抽样/聚合**与具体生成产物无关——只
## 串行约束
U8/U9 的 mini-desktop CDP 真跑与 Plan A 的 e2e **串行排队**(同机端口 4320/9222、flock——见 `.agents/skills/game-e2e-cdp-harness.md`
## gen-done 接线时须补(诚实标注·非现编)
本目录 harness 的**逻辑/断言/抽样/三契约核**已建 + selftest下列须**待 gen-done 产出形态明确后**接线(现无法验,故标 deferred 不伪报):
1. **U8 play 路二选一**Plan A 产「staging 发布游戏」→ 走产品前端 :4173 GamePlayerplayer_cdp同 u5u2_walk产「本地 gameDefinition」→ 走 play.cdp.cjs 本地 bundle 九门。二者断言口径同。
2. **U8 遥测查询接口**`backend_gw` 现无 `get_telemetry`,终局断言暂记 gap——gen-done 接线时补遥测查询(或经 DB/日志取 `game_end`)。
3. **U8→U9 artifacts 传递**U9 三契约核需 U8 evidence 带 `api_envelope/source_project/package` 三件 artifact现 U8 未输出(形态待 gen-done 实物定U9 已对此记 gap。接线时在 `run_e2e``ev["artifacts"]`
4. **抽样源**U7 `--batch` 须喂 Plan A ≥80% 批 manifest形态list[gameId|对象])。

View File

@ -0,0 +1,166 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
U9 M0 core 联合验收门Plan B Phase-2 / origin 003-U3 turnkey
M0 core = 一句话真生成可玩游戏 + 三契约对齐
1. U8 e2ecreate生成发布feed真玩u8_feed_play_e2e.run= 一句话端到端可玩
2. **三契约结构核**运行时读契约 required防漂移
- APICommonResult 信封code==0 + data 非空 runtime/package · feed · aigc 关键响应
- dispatcherSAA 产出 = source-project.schema.jsonschemaVersion/sourceHash/profile/gameDefinition
- manifest游戏包 = game-package.schema.jsonschemaVersion/gameId/versionId/templateId/gameConfig/assets/manifest/meta
U8 PASS 三契约全对齐
**gated on gen-done + U8**编排步须 Plan A 产可玩游戏后跑mini-desktop本文件先建 + 三契约结构核 selftest 现验
"""
import argparse
import json
import os
import subprocess
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.abspath(os.path.join(HERE, "..", "..", "..")) # 仓根harness 在 docs/agent-specs/<dir>/
MANIFEST_SCHEMA = os.path.join(REPO, "contracts", "game-package.schema.json")
DISPATCHER_SCHEMA = os.path.join(REPO, "contracts", "agent-loop", "source-project.schema.json")
U8 = os.path.join(HERE, "u8_feed_play_e2e.py")
def _required(schema_path):
"""运行时读契约 required防硬编码漂移缺文件抛错。"""
with open(schema_path, encoding="utf-8") as f:
return json.load(f).get("required") or []
def check_required(obj, required):
"""结构核required 键齐则 ([], True),否则 (缺键列表, False)。"""
obj = obj or {}
missing = [k for k in required if k not in obj]
return missing, len(missing) == 0
# ============== 三契约结构核gen-done 前可 selftest==============
def assert_api(envelope):
"""API 契约CommonResult 信封 code==0 + data 非空。"""
e = envelope or {}
ok = (e.get("code") == 0) and (e.get("data") is not None)
return ok, {"code": e.get("code"), "has_data": e.get("data") is not None}
def assert_dispatcher(source_project):
"""dispatcher 契约SAA 产出对齐 source-project.schema required。"""
missing, ok = check_required(source_project, _required(DISPATCHER_SCHEMA))
return ok, {"missing": missing}
def assert_manifest(package):
"""manifest 契约:游戏包对齐 game-package.schema required。"""
missing, ok = check_required(package, _required(MANIFEST_SCHEMA))
return ok, {"missing": missing}
def three_contracts(api_envelope, source_project, package):
"""三契约联合核。返回 (passed, detail)。"""
a_ok, a = assert_api(api_envelope)
d_ok, d = assert_dispatcher(source_project)
m_ok, m = assert_manifest(package)
return (a_ok and d_ok and m_ok), {"api": {"ok": a_ok, **a},
"dispatcher": {"ok": d_ok, **d},
"manifest": {"ok": m_ok, **m}}
# ============== 联合门gated on gen-done + U8==============
def run_m0_core(prompt, cdp_endpoint, out_dir=None):
"""跑 U8 e2e + 三契约核。返回 verdict dict。"""
# 1. U8 e2e一句话端到端可玩—— shell u8读其 JSON 输出
cmd = [sys.executable, U8, "--run", "--prompt", prompt, "--cdp", cdp_endpoint]
if out_dir:
cmd += ["--out", out_dir]
p = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
try:
# u8 末行打 U8_VERDICT前面是 JSON取首个 { 到末个对应 }
txt = p.stdout
u8_ev = json.loads(txt[txt.index("{"):txt.rindex("}") + 1])
except Exception:
u8_ev = {"passed": False, "_parse_error": (p.stdout or p.stderr)[-400:]}
u8_pass = bool(u8_ev.get("passed"))
# 2. 三契约核 —— 从 U8 evidence 取 artifactsapi 信封 / source-project / manifest
# u8 在 steps 里留有 manifest/gameDef此处按其 evidence 形态取,缺则记 gap不伪报
api_env = (u8_ev.get("artifacts") or {}).get("api_envelope")
src_proj = (u8_ev.get("artifacts") or {}).get("source_project")
pkg = (u8_ev.get("artifacts") or {}).get("package")
tc_pass, tc = three_contracts(api_env, src_proj, pkg)
artifacts_present = all(x is not None for x in (api_env, src_proj, pkg))
passed = u8_pass and tc_pass and artifacts_present
return {
"passed": passed,
"u8_passed": u8_pass,
"three_contracts_passed": tc_pass,
"artifacts_present": artifacts_present,
"three_contracts": tc,
"u8": u8_ev,
"gap": None if artifacts_present else "U8 evidence 未带 api_envelope/source_project/package 三件 artifact——gen-done 接线时补 u8 artifacts 输出",
}
# ============== selftest三契约结构核==============
def _selftest():
# 用真契约文件读 required构造 好/缺键 fixture
req_disp = _required(DISPATCHER_SCHEMA)
req_mani = _required(MANIFEST_SCHEMA)
assert req_disp and req_mani, "契约 required 读取为空"
good_disp = {k: "x" for k in req_disp}
good_mani = {k: "x" for k in req_mani}
good_api = {"code": 0, "data": {"x": 1}}
# 全好 → 过
ok, tc = three_contracts(good_api, good_disp, good_mani); assert ok, tc
# API code!=0 → 不过
ok, tc = three_contracts({"code": 500, "data": None}, good_disp, good_mani)
assert (not ok) and (not tc["api"]["ok"]), tc
# dispatcher 缺键 → 不过 + 列缺
bad_disp = {k: "x" for k in req_disp[1:]} # 缺第一个
ok, tc = three_contracts(good_api, bad_disp, good_mani)
assert (not ok) and tc["dispatcher"]["missing"] == [req_disp[0]], tc
# manifest 缺键 → 不过
bad_mani = {k: "x" for k in req_mani[:-1]} # 缺最后一个
ok, tc = three_contracts(good_api, good_disp, bad_mani)
assert (not ok) and tc["manifest"]["missing"] == [req_mani[-1]], tc
# 空对象 → 全缺
ok, tc = three_contracts({}, {}, {})
assert (not ok) and len(tc["dispatcher"]["missing"]) == len(req_disp), tc
print(f"u9_m0_core_gate selftest: 5/5 PASS三契约结构核读真契约 dispatcher={len(req_disp)}键 manifest={len(req_mani)}键)")
def _print(v):
print("=== U9 M0 core 联合门 ===")
print(f" U8 e2e一句话端到端可玩: {'' if v['u8_passed'] else ''}")
print(f" artifacts 齐(api/dispatcher/manifest 三件): {'' if v['artifacts_present'] else '' + (v.get('gap') or '')}")
tc = v["three_contracts"]
print(f" 三契约: API {'' if tc['api']['ok'] else ''} | dispatcher {'' if tc['dispatcher']['ok'] else '✗ 缺' + str(tc['dispatcher'].get('missing'))} | manifest {'' if tc['manifest']['ok'] else '✗ 缺' + str(tc['manifest'].get('missing'))}")
print(f" M0_CORE_VERDICT={'PASS' if v['passed'] else 'FAIL'}")
def main():
ap = argparse.ArgumentParser(description="U9 M0 core 联合门gated on gen-done + U8")
ap.add_argument("--selftest", action="store_true", help="三契约结构核自测读真契约gen-done 前可跑)")
ap.add_argument("--run", action="store_true", help="真跑 M0 core须 gen-done + mini-desktop CDP")
ap.add_argument("--prompt", default="一个躲避陨石的小游戏")
ap.add_argument("--cdp", default="http://localhost:9222")
ap.add_argument("--out")
args = ap.parse_args()
if args.selftest:
_selftest(); return 0
if args.run:
v = run_m0_core(args.prompt, args.cdp, args.out)
_print(v)
return 0 if v["passed"] else 1
ap.print_help(); return 2
if __name__ == "__main__":
sys.exit(main())