3b-B 重跑视觉终局(mini-desktop·证据 wg1/gen-worker/evidence-3bB-final/):
✅ 视觉终局达成(亲眼复核截图):feed 里一款真渲+真玩有进展的井字棋(gameId=9306/versionId=93112)——
空盘「轮到X」→ 落子填盘 X·O → 「X 胜!」+ 重新开始(latch 终态);feed 真玩门 10/11 过
(no_demo_fallback/engine_rendered/lively_interactive 全 true,唯一 false=play_start 遥测非阻断)。
经真 HMAC 回调写链落库(验签通过 engineBundleLen=210891→建版本→组包 checksum 自洽→翻 feed)。
走兜底路(诚实标注):主路一句话现生成两次均失败,故取已验证过九门样板 tictactoe(便宜模型 flash 真生成、
verdict A-I 全 pass)的 engineBundle 经派发面真回调路落 feed,DB bundle 与样板 sha256 字节级一致。
仍是便宜模型真生成的过门游戏,只是非本次现生成。
★ 主路两次 fresh 失败真因(非模型壁垒,铁证):design agent 在 dispatch 面 run_studio(play_spec={}) 下
均给 driver:{type:none}→harness 盲打打不中目标→moves/score 恒 0→H_progress 挂+latch 不触发,repairs=5
耗尽。游戏本体全对(gen9306/9307 verdict 8/9 门过,targetX/Y/radius·board·moves 导出正确,G_input/
I_control/F_wiring 均过)。样板过门靠 lane brief 手供 play_spec.driver=tap-targets。
→ 蒸馏入 cheap-model §7:DESIGN_SYSTEM 须对离散点击类强制产 tap-targets driver(纯 L1 生成域,
moat『一句话现生成』腿的真·临门一脚)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
96 lines
4.2 KiB
Python
96 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""post_fallback_callback.py —— P3 W-G1 视觉终局·P2 兜底:直接 POST succeeded 回调(已验证样板 bundle)。
|
||
|
||
【缘由】主路两次一句话真生成(gen9306 whack / gen9307 tictactoe)均栽于 driver-coverage
|
||
(design agent 给 driver:none、harness 盲打打不中目标→moves/score 恒 0→H_progress 挂,8/9 门过,非模型壁垒)。
|
||
兜底取【已验证过九门】样板 tictactoe(verdict.json A-I 全 pass)的 bundle.iife.js 作 engineBundle,
|
||
经派发面【真回调写链】(handleCallback·HMAC 验签·与真生成同代码路)落库入 feed。
|
||
|
||
【与 worker service.py 的 post_callback 逐字节对齐】payload 用 json.dumps(ensure_ascii=False).encode("utf-8")
|
||
一次算定既签既发;HMAC-SHA256(secret, data) hex 小写置头 X-Callback-Signature;Content-Type 带 charset=utf-8。
|
||
这是【真回调路】——后端 CallbackSignatureVerifier 验签 → handleCallback 建版本/组包(含 manifest+checksum)/落包。
|
||
|
||
【用法(mini-desktop,cwd=repo 根)】
|
||
CALLBACK_SECRET=<与后端 AIGC_CALLBACK_SECRET 同值> \\
|
||
python3 wg1/gen-worker/post_fallback_callback.py \\
|
||
--bundle game-runtime/games/_wg1-gen/tictactoe/bundle.iife.js \\
|
||
--trace-id wg1-3bB-gen9306-fb-001 \\
|
||
--title "井字棋(兜底)" \\
|
||
--callback http://localhost:48080/admin-api/aigc/dify/callback-internal
|
||
"""
|
||
import argparse
|
||
import hashlib
|
||
import hmac
|
||
import json
|
||
import os
|
||
import sys
|
||
import urllib.error
|
||
import urllib.request
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--bundle", required=True, help="已验证样板 bundle.iife.js 路径")
|
||
ap.add_argument("--trace-id", required=True, help="须与 RUNNING 任务 trace_id 一致(handleCallback 据此定位)")
|
||
ap.add_argument("--title", default="井字棋(兜底)", help="meta 标题")
|
||
ap.add_argument("--template-id", default="generic")
|
||
ap.add_argument("--callback", default="http://localhost:48080/admin-api/aigc/dify/callback-internal")
|
||
args = ap.parse_args()
|
||
|
||
secret = os.environ.get("CALLBACK_SECRET", "")
|
||
if not secret:
|
||
print("❌ CALLBACK_SECRET 未设(须与后端 AIGC_CALLBACK_SECRET 逐字节一致)", file=sys.stderr)
|
||
sys.exit(2)
|
||
|
||
# 读已验证样板 bundle 全文(engineBundle 真值)
|
||
with open(args.bundle, "r", encoding="utf-8") as f:
|
||
bundle_text = f.read()
|
||
if "__GameBundle" not in bundle_text:
|
||
print(f"❌ bundle 缺 __GameBundle 全局名:{args.bundle}", file=sys.stderr)
|
||
sys.exit(3)
|
||
print(f"[fallback] 读 bundle {len(bundle_text)} 字符(含 __GameBundle ✓)← {args.bundle}")
|
||
|
||
# 组 DifyCallbackReqVO(succeeded 路,与 worker build_callback_payload 同形)
|
||
payload = {
|
||
"traceId": args.trace_id,
|
||
"status": "succeeded",
|
||
"templateId": args.template_id,
|
||
"gameConfig": {
|
||
"templateId": args.template_id,
|
||
"title": args.title[:20],
|
||
"theme": "generic",
|
||
"engineDriven": True,
|
||
},
|
||
"assets": [],
|
||
"engineBundle": bundle_text,
|
||
}
|
||
# 逐字节对齐铁律:一次算定既签既发
|
||
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||
sig = hmac.new(secret.encode("utf-8"), data, hashlib.sha256).hexdigest()
|
||
headers = {
|
||
"Content-Type": "application/json; charset=utf-8",
|
||
"tenant-id": "0",
|
||
"X-Callback-Signature": sig,
|
||
}
|
||
print(f"[fallback] HMAC 签名(前8)={sig[:8]}, payload 字节={len(data)}, POST → {args.callback}")
|
||
|
||
req = urllib.request.Request(args.callback, data=data, method="POST", headers=headers)
|
||
try:
|
||
with urllib.request.urlopen(req, timeout=60) as resp:
|
||
body = resp.read().decode("utf-8", "replace")
|
||
print(f"[fallback] 回调完成 HTTP={resp.status} body={body[:300]}")
|
||
except urllib.error.HTTPError as e:
|
||
body = e.read().decode("utf-8", "replace") if e.fp else ""
|
||
print(f"[fallback] 回调 HTTP={e.code} body={body[:300]}")
|
||
# 401/4xx 视为失败
|
||
if e.code >= 400:
|
||
sys.exit(4)
|
||
except Exception as e:
|
||
print(f"❌ 回调 POST 异常:{type(e).__name__}: {e}", file=sys.stderr)
|
||
sys.exit(5)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|