From 7c2a35888defe5f8e7c650cceb59ea4aa1200599 Mon Sep 17 00:00:00 2001 From: lili Date: Sat, 20 Jun 2026 01:38:39 -0700 Subject: [PATCH] =?UTF-8?q?feat(planB-p2):=20U9=20M0=20core=20=E8=81=94?= =?UTF-8?q?=E5=90=88=E9=97=A8=20turnkey=EF=BC=88003-U3=EF=BC=89=E2=80=94?= =?UTF-8?q?=E2=80=94shell=20U8=20+=20=E4=B8=89=E5=A5=91=E7=BA=A6=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E6=A0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../2026-06-20-planB-phase2-harness/README.md | 9 +- .../u9_m0_core_gate.py | 166 ++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 docs/agent-specs/2026-06-20-planB-phase2-harness/u9_m0_core_gate.py diff --git a/docs/agent-specs/2026-06-20-planB-phase2-harness/README.md b/docs/agent-specs/2026-06-20-planB-phase2-harness/README.md index ebdd947b..b5bf69c6 100644 --- a/docs/agent-specs/2026-06-20-planB-phase2-harness/README.md +++ b/docs/agent-specs/2026-06-20-planB-phase2-harness/README.md @@ -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真玩 e2e(R4+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 GamePlayer(player_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|对象])。 diff --git a/docs/agent-specs/2026-06-20-planB-phase2-harness/u9_m0_core_gate.py b/docs/agent-specs/2026-06-20-planB-phase2-harness/u9_m0_core_gate.py new file mode 100644 index 00000000..908569c1 --- /dev/null +++ b/docs/agent-specs/2026-06-20-planB-phase2-harness/u9_m0_core_gate.py @@ -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 e2e(create→生成→发布→feed→真玩,u8_feed_play_e2e.run)= 一句话端到端可玩; + 2. **三契约结构核**(运行时读契约 required,防漂移): + - API:CommonResult 信封(code==0 + data 非空)—— runtime/package · feed · aigc 关键响应; + - dispatcher:SAA 产出 = source-project.schema.json(schemaVersion/sourceHash/profile/gameDefinition); + - manifest:游戏包 = game-package.schema.json(schemaVersion/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//) +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 取 artifacts(api 信封 / 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())