承接目录治理:上轮只压缩内容未减文件数,闭线工作记录仍平铺致目录看着仍一堆(创始人指出)。本次执行 B2 归档。 64 个 ≤06-15 闭线档(25 压缩桩 + 闭线 review/report/纪要/edit-plan)git mv 入 docs/agent-specs/_archive/(文件名不变、仍 git 跟踪可查)。热目录 ≤06-15 仅留 13 活档(决策/纲领/SoT/活spike)+13 个 06-16 在飞。 活资产 20 处旧路径引用(.agents/docs/memory/_index)同步改 _archive/,引用断裂复测=0;_index 活地图 + 治理档状态收口。约束:0 个 06-16 被移、orchestrator 等未跟踪在飞档零误纳。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
15 KiB
设计 v2:最小数据闭环(clicker-plus 通用 runtime)+ 小卖部皮肤
文档号:HJ-DESIGN-006 | 日期:2026-06-09 | 分支:dev/2.0.0 上游:
docs/agent-specs/_archive/2026-06-09-下一阶段路线-plan.md(autoplan 三轨定稿)的 B 轨黄金闭环。 评审:/autoplan CEO 双声音收敛"收缩重构",用户前提门 D1 确认。进度底账docs/mvp/MVP进度总账.md。 下一步:转 writing-plans。
1. 背景与动机
B 轨 = 把 1 条完整垂直闭环做真,核心是最小数据回路(遥测→quality_score→feed 排序)。
v1→v2 反转(autoplan CEO 双声音一致):v1 为「经营」模板专门扩 Runtime,被两个独立模型判为"把可演示游戏误当平台闭环"。v2 改为:通用 clicker-plus 数据驱动 runtime + 确定性 PackageFactory,经营/小卖部只是配置皮肤 + 内容样例,不主导架构。这样同样产出"小卖部"体验,又沉淀可复用的平台生产能力,避免每品类加 Runtime 分支。
可行性勘察实证(只读代码):前端 Runtime 已动态读 gameConfig.target/assets,但玩法逻辑硬编码、manifest.entry 从不执行;后端 aigc/runtime/telemetry 全桩,feed-api 无 FeedApi、publish 不写 feed、game_runtime_package 无列存整包 JSON。
2. 目标 / 非目标
目标:证明平台「生产→分发→采数→排序」的复用能力(非"做一个游戏"):
- 通用 clicker-plus 数据驱动 runtime + PackageFactory:同一套数据化配置(点击/目标/时长/可选库存商品)产出不同皮肤的游戏。
- 最小数据回路真实跑通:play 事件真落库、quality_score 真算、feed 排序真读它(A/B 可证),clicker-plus 即可验,不依赖经营皮肤。
- 小卖部皮肤 + ≥10 个参数化种子内容,让 feed 不是"两个游戏赛马"。
非目标(YAGNI): 不为单品类扩 runtime(经营=配置皮肤);不做完整经营状态机;不接真实 LLM/Dify、MQ、OSS、runtime 真实编译;不做真实鉴权/多租户。
口径收敛(对外措辞,CEO 双声音):本闭环达成 = "数据飞轮埋点骨架 + 推荐反馈链路已跑通",不是"护城河已建"(护城河要等真实创作者/玩家/反作弊/留存)。
3. 架构总览
[创作] studio.createDraft → project.createProject ✅已有
↓ 选 clicker-plus 模板 + 参数(点击/目标/时长/可选商品·皮肤)
[生成] aigc.generate → PackageFactory 确定性填充 → 合法 GamePackage(无LLM) 🔨桩→真
↓
[落包] runtime.persistPackage → game_runtime_package(READY) via PackageStore 🔨新建(V10加列)
↓ manifestUrl=后端端点(PackageStore.read)
[发布] project.submitPublish 门禁 → 状态机 ✅门禁已真
[可见] 进入"可见态"(批次1定义:终态可见 + package READY 双条件) ──钩子──→ 写 feed rank 🔨FeedApi.upsertRank
↓
[feed可见] feed.stream 读 rank + **回填本体(标题/封面/作者/packageUrl)** 🔨读端在,本体回填待补
↓
[试玩] 前端 getRuntimePackage→manifestUrl→通用 clicker-plus runtime 🔨数据驱动小扩
↓
[遥测] game_play_start/end(duration_ms,completed)/like/share + eventId 🔨桩→真(同步落库,经聚合服务接口)
→ /events/batch 同步写 event → upsert game_telemetry_game_stat(按天)
→ quality_score → **当日 stat 覆盖** game_feed_rank.quality_score/sort_score
↓
[排序] feed.stream 重查 → 排序反映新 quality_score ✅读端在
3 个关键决策(已修正):
- feed 写入挂"可见态":v1 误写
APPROVE→PUBLISHED,实际审核通过到APPROVED、PUBLISHED 是另一状态(Codex critical)。→ 批次1 先读ProjectStatusEnum+ 状态机定义 feed-eligible 态(终态可见 AND runtime package=READY 双条件),feed 写入钩子挂这里,不把不可玩游戏推进 feed。 - manifest=后端端点 +
PackageStore抽象:DB 是 MVP 实现,V10 迁移加game_runtime_package.package_json LONGTEXT(确定项,非开放项)。退场契约:manifest-from-DB 是脚手架,M3 接 OSS/真实编译时整体下线,不在其上叠功能。 - quality_score:
clamp(60×完玩率 + 20×like率 + 20×share率, 0, 100)(完玩率=completed/play_start;like/share 率截断 1.0);回灌口径=取该 game 当日 stat 覆盖feed_rank.quality_score(跨天加权留 M5)(修 stat 按天 / rank 按游戏 的语义裂,Codex critical);sort_score=quality_score+boost。
3.5 三段批次(数据回路 AC 与经营皮肤解耦,CEO 双声音)
| 批次 | 内容 | 验收 |
|---|---|---|
| ① 契约/状态机校正 | 定义 feed-eligible 发布态 / V10 manifest 存储 / FeedApi 契约(含本体回填字段)/ telemetry 幂等键(eventId) / defaultZone / IP 原创名 |
契约入库、V10 迁移就绪 |
| ② 最小数据闭环(clicker-plus) | PackageFactory 产 clicker-plus 包→落包→发布后入 feed→试玩→事件落库→quality_score 回灌→A/B 排序。不依赖经营皮肤/runtime 扩展 | 数据回路 A/B AC 在此锁定 |
| ③ 经营皮肤 + 种子 | clicker-plus 配置出"小卖部"皮(点击售卖+极简库存)+ ≥10 参数化种子内容 | feed 有内容池;经营皮肤可降级(撞 <15KB 先退纯 clicker,数据回路 AC 不动) |
4. 组件设计(职责 / 接口 / 依赖)
| # | 单元 | 职责 | 接口 | 依赖 |
|---|---|---|---|---|
| 1 | clicker-plus GameConfig schema(契约) | 约束通用 clicker-plus 配置(点击/目标/时长/可选商品列表/皮肤元信息) | contracts/templates/clicker-plus.schema.json;经营=该 schema 的一组参数+皮肤 |
game-package.schema.json |
| 2 | PackageFactory(aigc 桩→真) | 给 templateId+参数 → 确定性产合法 GamePackage,无 LLM;多参数批量产种子 | getTemplateList() 返真模板;generate DONE 产物=骨架代入参数→校验 clicker-plus+package schema |
#1、project |
| 3 | PackageStore + runtime 落包(新建) | 抽象包存储(DB=MVP impl);跳过真编译落 game_runtime_package(READY) |
PackageStore.persist/read;V10 加 package_json LONGTEXT;GET /app-api/runtime/manifest/{versionId} |
game_runtime_package(+V10) |
| 4 | FeedApi.upsertRank + 本体回填(新建·填结构缺口) | feed-api 加 FeedApi;upsert rank;feed.stream 回填标题/封面/作者/packageUrl(否则空卡,Codex high) |
upsertRank(gameId,versionId,zoneId,qualityScore,boost);stream join project 本体 |
game_feed_rank、project |
| 5 | project 可见态钩子(改) | 进入 feed-eligible 态(批次1定义)后调 FeedApi.upsertRank 写基线分;不动门禁/状态机(D2锚点) |
钩子挂"可见态 + package READY"双条件后 | #4 |
| 6 | telemetry 同步落库+算分(桩→真) | /events/batch 同步:写 event→upsert stat→算 quality_score→调 #4。经聚合服务接口(未来换 MQ 不改业务语义) |
替换 dispatchToMq;幂等键含 eventId/clientEventId(防 ts 变化重复刷分) |
event/stat 表、#4 |
| 7 | 通用 clicker-plus runtime(前端中度扩展) | 数据驱动读 gameConfig:点击/目标/时长/可选库存商品;皮肤化渲染。先量当前 build 字节余量(防撞 <15KB) | 扩 startRuntime;上报 game_play_start/game_play_end(duration_ms,completed)/like/share+有效 traceId+eventId |
gameConfig(#1)、SDK |
| 8 | 6 开工前置项 + beacon/tenant(前端) | VITE_API_BASE / mock 门控 / token=test1+tenant-id / sendBeacon 走 baseURL(否则绕过) / 种子 tenant_id=1 / vite dev | — | — |
upsertRank 列级语义(消歧,Codex high):project 钩子写 quality_score=COALESCE(existing, baseline)(不下调);telemetry 写 quality_score=新值, sort_score=新值+boost(必须能覆盖)。因"可见先于试玩"正常无竞争,此为安全网。
5. 数据流 + A/B 验收
主链路:创作→PackageFactory 产 clicker-plus 包→PackageStore 落包(READY)→submitPublish 门禁→进可见态→钩子写 feed rank→feed.stream(回填本体)可见→前端 clicker-plus runtime 试玩→上报事件(eventId+traceId)→同步写 event→upsert 当日 stat→算 quality_score→当日覆盖 feed_rank→重查 feed 排序变化。
A/B 验收(数据回路 AC,批次②,clicker-plus 即可,Codex critical 协议):备同 zone 同 boost 的 A/B 两个已"可见"clicker-plus 游戏 → 记初始 feed 顺序 → 只向 B 上报有效 game_play_start+game_play_end(completed=true)+like+share(带 eventId)→ 断言 game_telemetry_event 有原始行、当日 stat.quality_score 变大、feed_rank.sort_score 变大 → 重查 feed B 排到 A 前 → 同 eventId 重发不重复计数 → 关 mock 仍通过 → feed 卡片本体非空(标题/封面真回填)。
6. 错误处理
| 失败点 | 处理 |
|---|---|
| PackageFactory 产物 schema 不合法 | task FAILED,不落包,studio 链态 FAILED,前端提示 |
| 包不存在(NOT_READY) | 前端走 demo 兜底——但黄金闭环要真包,落包失败=闭环断,登记缺口(不靠兜底掩盖) |
| 未到可见态/package 未 READY | 不写 feed(双条件);防不可玩游戏进流 |
| feed 卡片本体回填失败 | 缺本体的 rank 行不展示或降级占位;不允许空卡冒充可见 |
| feed rank 并发(可见钩子 vs telemetry) | 列级语义分离(§4):钩子不下调、telemetry 权威覆盖 |
| telemetry eventId 缺失/重复 | 缺 eventId 拒;同 eventId 幂等去重(防 ts 变化重复刷分) |
| traceId 空 | 后端拒(已有);前端必生成 |
| mock token 401/加密/跨域 | autoplan 已覆盖 |
7. 测试与验收
- 批次② A/B 验收(§5)=数据回路 AC,clicker-plus 即可,与经营皮肤解耦。
- 单元测试:① PackageFactory(合法/非法参数验产物 schema + 批量产种子)② PackageStore(存/读整包 JSON)③ FeedApi.upsertRank(给分验 sort_score+幂等+本体回填)④ telemetry(灌 play_start/end 验 stat 累加+quality_score 公式+eventId 幂等+调 feed)⑤ project 可见态钩子(到可见态→调 FeedApi;未到→不调)⑥ clicker-plus runtime(给 config 验点击/目标/时长/库存)。
- 主 agent 独立复跑(铁律)。
- 完成判据:批次② A/B 全绿 + 单元绿 + feed 本体非空 + 浏览器肉眼演示。
8. 边界 / 回滚 / 风险
- 改动边界:不动 project 门禁/状态机(D2锚点)、不动 feed 读端排序逻辑、不接 MQ/OSS/真编译/真 LLM;telemetry 走聚合服务接口、包走 PackageStore(换 MQ/OSS 不改业务语义)。
- 回滚单元(按 3 批次,各自 commit):①契约+V10 ②PackageFactory/PackageStore/FeedApi/可见钩子/telemetry 同步 ③clicker-plus runtime+经营皮肤+种子。后端非零改动,回滚=按单元 revert + cleanup SQL。
- 数据污染清理:固定 traceId/eventId 前缀 + 固定 gameId 清单 + cleanup SQL + 表快照。
- 风险:① clicker-plus runtime 撞 <15KB(缓解:先量字节余量;经营皮肤可降级退纯 clicker,数据回路 AC 不动)② manifest-from-DB 沉淀成债(缓解:PackageStore 抽象+退场契约)③ quality_score 低样本易刷(缓解:MVP 接受,置信度/去重/反作弊留 M5,对外口径不夸大)。
9. 开放项 / 依赖(批次①解决)
- 发布态定义:读
ProjectStatusEnum+ 状态机,确定 feed-eligible 态(APPROVED 即可见?还是 APPROVED 后再 publish 到 PUBLISHED?)+ package READY 双条件。 - V10 迁移(确定项):
game_runtime_package.package_json LONGTEXT(注明大小上限与超限处理)。 - defaultZone:feed 默认专区 zoneId(读 project game_zone 或约定常量)。
- IP:种子内容用原创换皮(如"蓝莓妹的小卖部"/自造),不用真实"王蓝莓"IP(撞自家 ip/compliance 风门,对外材料有侵权尾险);"王蓝莓"仅内部代号。
- 种子供给:≥10 参数化种子(PackageFactory 批量产),feed 内容池;与上游 plan §2.D 对齐。
Decision Audit Trail(autoplan · design review)
| # | 阶段 | 决策 | 分类 | 原则 | 理由 | 否决项 |
|---|---|---|---|---|---|---|
| 1 | CEO | 运行 Claude 独立 CEO 子代理 + Codex CEO 声音 | Mechanical | P6 | 双声音对抗评审(codex 已就绪) | 仅 subagent |
| 2 | CEO | 设计形态:收缩重构 | USER-GATED(前提门 D1) | P1+P5 | 两模型一致;用户确认通用 clicker-plus+皮肤 | 保留经营-specific runtime |
| 3 | CEO | 通用 clicker-plus 数据驱动 runtime + PackageFactory(经营=皮肤) | Taste→采纳 | P5 | 避免每品类扩 runtime 分支爆炸,沉淀复用能力 | 为 business 单独扩 runtime |
| 4 | CEO | feed 写入挂"可见态+package READY"双条件 | Mechanical | P5 | 修 APPROVE≠PUBLISHED 状态机错(Codex critical) | 挂 REVIEWING/错态 |
| 5 | CEO | V10 加 package_json LONGTEXT(确定项) | Mechanical | P5 | 现表无列存整包 JSON,包存 DB 必需(Claude/Codex critical) | 标"实现时确认" |
| 6 | CEO | quality_score 回灌=当日 stat 覆盖 rank | Mechanical | P5 | 修 stat 按天/rank 按游戏语义裂(Claude/Codex) | 一次性 upsert 不定口径 |
| 7 | CEO | telemetry 幂等键含 eventId/clientEventId | Mechanical | P1 | 防 ts 变化重复刷分(Codex high) | 仅 traceId |
| 8 | CEO | feed.stream 回填本体(标题/封面/作者/packageUrl) | Mechanical | P1 | 防空卡 demo 露馅(Codex high) | 只读 rank |
| 9 | CEO | 抽 PackageStore + telemetry 聚合服务接口 + 退场契约 | Taste→采纳 | P5 | 换 MQ/OSS 不改业务语义;防 DB-JSON 永久债 | DB/同步写死长期架构 |
| 10 | CEO | IP 原创换皮(非真王蓝莓) | Mechanical | P4 | 撞自家 ip 风门+对外侵权尾险 | 真实王蓝莓做招牌 |
| 11 | CEO | 拆 3 段批次(数据回路 AC 与经营皮肤解耦) | Taste→采纳 | P3 | clicker-plus 即可验 A/B;经营皮肤可降级 | 单批次绑死 |
| 12 | CEO | 对外口径=埋点骨架/反馈链路(非护城河) | Mechanical | P5 | 2 游戏少量遥测不证护城河 | 夸大为护城河已建 |
| 13 | — | Design/DX 阶段跳过 | Mechanical | P3/P6 | UI 项 0 命中;内部模块 API 非开发者产品 | 跑 Design/DX |