docs(design): 王蓝莓小卖部黄金垂直闭环实现设计(brainstorming 产出)

- B 轨黄金闭环具体化:创作→确定性模板填充→落包→发布→审核→feed→试玩→遥测→quality_score→排序
- 经只读代码可行性勘察:前端 Runtime 只会 clicker、后端数据链全桩+结构缺口(无FeedApi/包不落库/不算分)
- 方案=后端数据链全建+前端轻经营(点击售卖,主题王蓝莓);8 组件/数据流/错误/测试/A-B验收
- 转 writing-plans 前的设计层

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zizi 2026-06-09 08:31:01 +00:00
parent 3e488ba1c5
commit 6add6c771a

View File

@ -0,0 +1,143 @@
# 设计:王蓝莓的小卖部 · 黄金垂直闭环B 轨实现设计)
> 文档号HJ-DESIGN-006 日期2026-06-09 分支dev/2.0.0
> 上游:`docs/agent-specs/2026-06-09-下一阶段路线-plan.md`autoplan 三轨定稿)的 **B 轨黄金闭环**实现设计。
> 进度底账:`docs/mvp/MVP进度总账.md`。本文经 /superpowers:brainstorming 协作打磨 + 只读代码可行性勘察。
> 下一步:转 writing-plans 出实现计划。
---
## 1. 背景与动机
autoplan 双模型评审定稿"三轨并行",其中 **B 轨 = 把 1 条完整垂直闭环做到真实可演示、可采数据**,核心是 **最小数据回路遥测→quality_score→feed 排序)= 护城河种子**。本设计把 B 轨具体化为一条端到端真链路,主题 **「王蓝莓的小卖部」(经营模板,轻经营玩法)**。
**为什么是"轻经营 + 后端数据链全建"(方案 1只读代码勘察实证**
- 前端 Canvas Runtime **只会跑写死的 clicker**`game-studio/src/host/runtime/index.ts`:状态仅 score/running/target`manifest.entry` 代码加载链路未接)。跑完整经营要扩 Runtime中→大<15KB 红线)。→ 故前端做**轻经营**clicker 小扩成"点击售卖+极简库存"不做完整经营状态机
- 后端整条数据生产链是桩 + 结构缺口aigc 不产 GamePackage、runtime 包从不 insert、telemetry 只 `dispatchToMq`debug TODO、**feed-api 连 `FeedApi` 都没有、publish 不写 feed**。→ 这条链是**大而不可避免**的工作,也正是护城河,本设计全建。
---
## 2. 目标 / 非目标
**目标:**
- 一条真实垂直闭环创作→确定性模板填充生成→落包→发布→审核→feed 可见→试玩→遥测→quality_score→feed 排序,全程真实(非 mock、非 demo 兜底)。
- 最小数据回路真实产出play 事件真落库、quality_score 真计算、feed 排序真读它A/B 可证)。
- 「王蓝莓的小卖部」在玩法上真有"卖货赚钱"手感(点击售卖 + 极简库存),不只换文案。
**非目标YAGNI本阶段明确不做**
- 不接真实 LLM/Dify生成走确定性模板填充真实生成是 C 轨 Spike
- 不做完整经营状态机(顾客 AI/进货循环/价格博弈 留后续)。
- 不接 MinIO/OSSmanifestUrl 走后端端点GamePackage 存 DB
- 不接 MQtelemetry 同步落库MQ 只留契约 + TODO
- 不做 runtime 真实编译引擎(最小直接落包)。
- 不做真实鉴权/多租户mock token账号/权限是 P0留下一步
- 其余 4 条链路只烟测登记(本设计只详化黄金闭环这一条)。
---
## 3. 架构总览
```
[创作] studio.createDraft → project.createProject ✅已有真实
↓ 用户选「经营」模板 + 填王蓝莓商品/价格
[生成] aigc.generate(经营模板,参数) → 确定性模板填充 → 合法 GamePackage 🔨桩→真(无LLM)
[落包] runtime 最小直接落包 → game_runtime_package(READY) 🔨新建(跳过真编译)
↓ manifestUrl = 后端端点(不接OSS)
[发布] project.submitPublish 门禁 → REVIEWING ✅门禁已真
[审核] admin reviewProject(APPROVE) → PUBLISHED ──钩子──→ 写 feed rank 🔨新建 FeedApi.upsertRank
[feed可见] feed.stream 读 game_feed_rank(按 sort_score 降序) ✅已有真实
[试玩] 前端 getRuntimePackage→manifestUrl→Canvas Runtime「轻经营」 🔨前端小扩(点击售卖+极简库存)
[遥测] game_play_start/end(duration_ms,completed)/like/share 🔨桩→真(同步落库,不接MQ)
→ /events/batch 同步写 event → upsert game_telemetry_game_stat
→ 算极简 quality_score → 窄seam 写 game_feed_rank.quality_score/sort_score
[排序] feed.stream 重查 → 排序反映新 quality_score ✅已有(读端真实)
```
**新建/改动单元8 个)**:见 §4。
**3 个关键架构决策(已确认)**
1. **feed 写入时机 = 审核通过PUBLISHED那一刻**(非提交发布 REVIEWING 时)——游戏 PUBLISHED 才该可见A/B 验收需 2 个 PUBLISHED 游戏。
2. **manifestUrl = 后端端点**`/app-api/runtime/manifest/{versionId}`**绕开 MinIO/OSS**GamePackage JSON 暂存 DB。
3. **quality_score 极简公式(给定具体默认,实现可调权重)**`quality_score = clamp(60×完玩率 + 20×like率 + 20×share率, 0, 100)`(完玩率=completed/play_startlike率/share率=like 或 share 数 / play_start各截断至 1.0`sort_score = quality_score + boost`
---
## 4. 组件设计(职责 / 接口 / 依赖)
| # | 单元 | 职责 | 接口 | 依赖 |
|---|---|---|---|---|
| 1 | **经营 GameConfig schema**(契约新建) | 约束 templateId=`business` 的 gameConfig 结构 | `contracts/templates/business.schema.json``{shopName, goods:[{id,name,cost,price,stock}], durationSec, goalMoney, customerIntervalMs}` | game-package.schema.json |
| 2 | **aigc 模板填充器**(桩→真) | 给 templateId+参数 → 确定性产合法 GamePackage无 LLM | `getTemplateList()` 返真模板;`generate` task DONE 时产物=经营骨架代入参数→校验 business+package schema | #1、project(gameId/versionId) |
| 3 | **runtime 最小落包**(新建) | 跳过真编译,把 GamePackage 写 `game_runtime_package(READY)` 存 DB | `persistPackage(versionId, pkg)``GET /app-api/runtime/manifest/{versionId}` 返 manifest JSON | game_runtime_package 表(已有) |
| 4 | **FeedApi.upsertRank**(新建·填结构缺口) | feed-api 新增 `FeedApi`upsert `game_feed_rank``sort_score=quality_score+boost` | `upsertRank(gameId, versionId, zoneId, qualityScore, boost)`。**两个调用方语义明确**project 钩子=建行写基线分(**不下调已有 quality_score**telemetry=更新 quality_score。两者均重算 sort_score。因"发布先于试玩",正常无竞争;该规则是再发布/并发的安全网 | game_feed_rank 表(已有) |
| 5 | **project 发布钩子**(改) | `reviewProject(APPROVE)`→PUBLISHED 后调 `FeedApi.upsertRank` 写 feed初始基线分 | 现有 APPROVE 分支后加 seam 调用,**不动门禁/状态机D2 锚点)** | #4 |
| 6 | **telemetry 同步落库+算分**(桩→真) | `/events/batch` 同步:写 event→upsert stat→算 quality_score→调 #4 更 rank | 替换 `dispatchToMq` 桩;同 traceId 批次幂等去重MQ 只留契约+TODO | event/stat 表(已有)、#4 |
| 7 | **Canvas Runtime 轻经营**(前端小扩) | clicker→点击售卖读 gameConfig 商品/价/库存/时长;顾客按 interval 到;点击=卖money+=pricestock--);时长尽/达 goal→结束。主题王蓝莓 | 扩 `startRuntime`;上报 `game_play_start`/`game_play_end`(duration_ms,completed)/`like`/`share`,带有效 traceId保持 <15KB | gameConfig(#1)、SDK postMessage已有 |
| 8 | **6 开工前置项**前端autoplan 已定) | VITE_API_BASE 真名 / mock 门控 / token=test1+tenant-id / sendBeacon 走 baseURL / 种子 tenant_id=1 / vite dev 连 staging | — | — |
**契约新增**`contracts/templates/business.schema.json``feed.yaml``FeedApi.upsertRank`、runtime manifest 端点GAP-2 manifestUrl 给真实现)。
**单元边界自检**:每个单元独立可测;改动严守——不动 project 门禁/状态机、不动 feed 读端排序逻辑、不接 MQ/OSS。
---
## 5. 数据流
**主链路(一次完整跑通):**
1. **创作**studio.createDraft(creatorId, "王蓝莓的小卖部", templateId=`business`) → project.createProject 返 gameId/versionId✅真
2. **生成**studio.generate → aigc.generate(templateId, params={goods:[辣条/汽水/冰棍…], 价/成本/库存, durationSec, goalMoney}) → 填充器取经营骨架代入参数 → 校验 business+package schema → 产合法 GamePackagetask DONE
3. **落包**runtime.persistPackage(versionId, pkg) → 写 `game_runtime_package(READY)`package JSON 存 DBmanifestUrl=`/app-api/runtime/manifest/{versionId}`
4. **发布→审核**project.submitPublish 门禁✅真→REVIEWINGadmin reviewProject(APPROVE)✅真→PUBLISHED →**钩子** FeedApi.upsertRank(gameId, versionId, defaultZone, qualityScore=基线 0, boost=0) → 写 `game_feed_rank`
5. **feed 可见**GET /app-api/feed/stream → 读 rank 按 sort_score 降序(✅真),含本游戏。
6. **试玩**:前端 getRuntimePackage→manifestUrl→fetch+sha256 校验→Canvas Runtime 轻经营跑王蓝莓小卖部(点击售卖)。
7. **遥测**:开局 `game_play_start`、结束 `game_play_end`(props.duration_ms, completed)、点 like/share → /events/batch带 traceId→ 同步写 event → upsert stat → 算 quality_score → FeedApi.upsertRank 更新 quality_score/sort_score。
8. **排序**:重查 feed/stream → 顺序反映新分。
**A/B 验收(证 quality_score 真驱动排序Codex critical**:备同 zone 同 boost 的 A/B 两个 PUBLISHED 王蓝莓游戏 → 记初始 feed 顺序 → 只向 B 上报有效 `game_play_start+game_play_end(completed=true)+like+share` → 断言 `game_telemetry_event` 有原始行、`game_telemetry_game_stat.quality_score` 变大、`game_feed_rank.sort_score` 变大 → 重查 feed **B 排到 A 前** → 同 traceId 重发不重复计数 → 关 mock 仍通过。
---
## 6. 错误处理(失败路径)
| 失败点 | 处理 |
|---|---|
| aigc 填充产物 schema 不合法 | task FAILED**不落包**studio 链态 FAILED前端提示不污染下游 |
| runtime 包不存在NOT_READY | 前端现有逻辑走 demo 兜底——但**黄金闭环要真包**,故落包失败=闭环断,**登记缺口**(不靠兜底掩盖) |
| 审核 REJECT | **只 APPROVE 写 feed**REJECT 不写,游戏不可见 |
| **feed rank 并发写**(审核钩子 vs telemetry 算分抢同一行 `uk_game_zone` | **职责分离**project 钩子只在行不存在时写基线(**不覆盖 quality_score**telemetry 权威更新 quality_score/sort_score。避免互相覆盖 |
| telemetry traceId 空/无效 | 后端拒(✅已有校验);**前端必须生成有效 traceId**(前置项) |
| telemetry 同 traceId 批次重发 | 幂等去重,不重复计数 |
| 前端 manifest fetch/sha256 失败 | 现有 fetchAndVerifyManifest 失败走兜底;黄金闭环须保证 manifest 端点返合法包(否则登记) |
| mock token 401 / 加密 / 跨域 | autoplan 已覆盖token=test1+tenant-id、admin 关加密、CORS 已放行) |
---
## 7. 测试与验收
- **A/B 验收协议**§5已落 `~/.gstack/projects/.../test-plan-20260609.md`):端到端证 quality_score 驱动排序。
- **单元测试(各单元独立可测)**:① aigc 填充器(合法/非法参数各验产物 schema② FeedApi.upsertRank给分验 sort_score + 幂等)③ telemetry 同步(灌 play_start/end 验 stat 累加 + quality_score 公式 + 调 feed④ project 钩子APPROVE→FeedApi 被调REJECT→不调⑤ Runtime 轻经营(给 config 验售卖 money/stock/结束判定)。
- **前端 6 前置项验证**beacon 走 baseURL、token=test1+tenant-id、mock 门控。
- **主 agent 独立复跑**(铁律,不采信子代理自报)。
**完成判据**A/B 验收全绿 + 5 单元测试绿 + 黄金闭环浏览器肉眼可演示创作→生成→发布→审核→feed→试玩→排序变化
---
## 8. 边界 / 回滚 / 风险
- **改动边界**:不动 project 门禁/状态机D2 锚点)、不动 feed 读端排序逻辑、不接 MQ/OSS/真实编译/真实 LLM。
- **回滚单元(各自独立 commit**:① 前端联调开关+轻经营 ② aigc 填充器 ③ runtime 落包+manifest 端点 ④ FeedApi+project 钩子 ⑤ telemetry 同步落库。后端非零改动,回滚=按单元 revert + cleanup SQL。
- **数据污染清理**:测试数据用固定 traceId 前缀 + 固定 gameId 清单 + cleanup SQL验证前后表快照。
- **风险**:① Runtime 轻经营撞 <15KB缓解状态机最小化)② 模板填充骨架与 Canvas Runtime gameConfig 解析口径需对齐缓解business.schema 为单一事实源两侧都校验)③ DDL 是否需新增game_runtime_package package JSON——若现有列不够需 V10 迁移实现时确认)。
---
## 9. 开放项 / 依赖
- **DDL 确认**`game_runtime_package` 是否有列存完整 GamePackage JSONmanifest 端点要返它feed/telemetry 现有表列已足够(勘察确认 stat 有 quality_score 列)。若 runtime 需加列 → V10 迁移。
- **defaultZone**feed 默认专区 zoneId 取值(读 project game_zone 或约定常量)——实现时定。
- **经营骨架内容**:王蓝莓商品清单/封面/文案——种子数据,实现时填(注意 IPstaging demo 可用,公开上线需换皮,撞 ip/compliance 锁风门)。