docs(wave4): Round1双lane收口检查点——链路②闭合harness + Wave4(community+biz)双spec定稿

- 链路②: admin审核台真UI走查闭合(CDP-on-mini harness: admin_walk.py/run_walk.sh)——
  创作者提交→admin点9137「通过」→项目PUBLISHED(4)+feed_rank写入实证(队列3→2+DB);附带修admin构建腐化(vite8→钉5.1.4清装)
- Wave4评审版 HJ-WAVE4-001 + 评审纪要(两轮对抗评审R1 23+R2 6全接受) + 创始人拍板D-A/D-B/D-C回填+ERRATA回流
- Wave4 execution版 HJ-WAVE4-EXEC-001(4路深度recon→起草→三镜头评审逮aigc回调blocker+PII口径订正;主agent抽查file:line属实)
- 总账§3链路②行+§6波次史、作战清单⑩ 同步收口

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zizi 2026-06-11 10:09:14 +00:00
parent 3652cd9d86
commit 64a390e640
7 changed files with 1467 additions and 2 deletions

View File

@ -0,0 +1,165 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
admin 审核台 链路② UI 走查 —— 最小 CDP 客户端(websocket-client 直驱)。
在 mini-desktop 本地驱动 headless chrome(:9222)走查 game-admin(:4174):
登录 → 项目审核队列 → 点「通过」approve 真实待审游戏 → 取证。
普通 DOM 页面,evaluate 用于受控点击(非游戏试玩,不受 player_cdp 只读纪律约束)。
阶段:recon(看登录页) / login(登录+进队列+dump) / approve(登录+进队列+点通过+确认+验证)。
"""
import json, base64, time, sys, urllib.request
import websocket # websocket-client 1.7.0
CDP_HTTP = "http://localhost:9222"
ADMIN = "http://localhost:4174"
TARGET_GAME = sys.argv[2] if len(sys.argv) > 2 else "" # approve 阶段指定 gameId
def new_target(url="about:blank"):
"""Chrome 111+ 必须用 PUT /json/new 建 target。"""
req = urllib.request.Request(f"{CDP_HTTP}/json/new?{url}", method="PUT")
d = json.load(urllib.request.urlopen(req, timeout=10))
return d["webSocketDebuggerUrl"], d["id"]
class CDP:
def __init__(self, ws_url):
self.ws = websocket.create_connection(ws_url, max_size=None, timeout=40,
suppress_origin=True)
self._id = 0
def send(self, method, params=None, timeout=40):
self._id += 1
mid = self._id
self.ws.send(json.dumps({"id": mid, "method": method, "params": params or {}}))
end = time.time() + timeout
while time.time() < end:
self.ws.settimeout(max(0.1, end - time.time()))
try:
msg = json.loads(self.ws.recv())
except Exception:
continue
if msg.get("id") == mid:
if "error" in msg:
raise RuntimeError(f"{method}: {msg['error']}")
return msg.get("result", {})
raise TimeoutError(method)
def evaljs(self, expr, timeout=40):
r = self.send("Runtime.evaluate",
{"expression": expr, "returnByValue": True, "awaitPromise": True},
timeout)
if "exceptionDetails" in r:
return {"__js_error__": json.dumps(r.get("exceptionDetails"), ensure_ascii=False)[:300]}
return r.get("result", {}).get("value")
def nav(self, url, settle=6):
self.send("Page.navigate", {"url": url})
time.sleep(settle)
def shot(self, path):
r = self.send("Page.captureScreenshot", {"format": "png"})
with open(path, "wb") as f:
f.write(base64.b64decode(r["data"]))
def close(self):
try:
self.ws.close()
except Exception:
pass
# ---- JS 片段 ----
JS_DUMP_LOGIN = r"""(()=>{
const inp=[...document.querySelectorAll('input')].map(e=>({type:e.type,ph:e.placeholder||'',val:(e.value||'').slice(0,20)}));
const btn=[...document.querySelectorAll('button')].map(e=>(e.innerText||'').replace(/\s+/g,'')).filter(Boolean);
return {href:location.href, title:document.title, inputs:inp, buttons:btn};
})()"""
JS_CLICK_LOGIN = r"""(()=>{
const btns=[...document.querySelectorAll('button')];
const b=btns.find(e=>(e.innerText||'').replace(/\s+/g,'')==='登录');
if(b){b.click(); return 'clicked登录';}
return 'notfound登录|avail:'+btns.map(e=>(e.innerText||'').replace(/\s+/g,'')).filter(Boolean).join(',');
})()"""
JS_DUMP_QUEUE = r"""(()=>{
const rows=[...document.querySelectorAll('.el-table__row')].map(r=>{
const cells=[...r.querySelectorAll('td')].map(td=>(td.innerText||'').trim().replace(/\s+/g,' ').slice(0,40));
const btns=[...r.querySelectorAll('button')].map(b=>(b.innerText||'').replace(/\s+/g,''));
return {cells, btns};
});
return {href:location.href, rowCount:rows.length, rows:rows.slice(0,8),
bodyText:(document.body.innerText||'').replace(/\s+/g,' ').slice(0,260)};
})()"""
def js_click_pass(game):
return r"""(()=>{
const rows=[...document.querySelectorAll('.el-table__row')];
for(const r of rows){
const txt=r.innerText||'';
if(txt.includes('%s')){
const pass=[...r.querySelectorAll('button')].find(b=>(b.innerText||'').replace(/\s+/g,'')==='通过');
if(pass){pass.click(); return 'clicked通过 for %s';}
return 'row matched but no通过btn';
}
}
return 'gameid %s not found in rows';
})()""" % (game, game, game)
JS_CONFIRM = r"""(()=>{
const btns=[...document.querySelectorAll('.el-message-box button, .el-overlay button, .el-dialog button')];
const ok=btns.find(b=>{const t=(b.innerText||'').replace(/\s+/g,'');return t==='确定'||t==='确认';});
if(ok){ok.click(); return 'confirmed';}
return 'no-confirm-dialog|btns:'+btns.map(b=>(b.innerText||'').replace(/\s+/g,'')).join(',');
})()"""
def do_login(c):
"""导航到 / → 被弹去登录 → 点登录(表单已预填 admin/admin123/芋道源码)。"""
c.nav(f"{ADMIN}/", settle=7)
c.shot("/tmp/aw_login.png")
print("LOGIN_PAGE:", json.dumps(c.evaljs(JS_DUMP_LOGIN), ensure_ascii=False))
print("CLICK_LOGIN:", c.evaljs(JS_CLICK_LOGIN))
time.sleep(6) # 等鉴权 + 跳转
print("AFTER_LOGIN_HREF:", c.evaljs("location.href"))
def main():
stage = sys.argv[1] if len(sys.argv) > 1 else "recon"
ws_url, tid = new_target("about:blank")
c = CDP(ws_url)
c.send("Page.enable")
c.send("Runtime.enable")
if stage == "recon":
c.nav(f"{ADMIN}/", settle=7)
c.shot("/tmp/aw_recon.png")
print(json.dumps(c.evaljs(JS_DUMP_LOGIN), ensure_ascii=False, indent=1))
elif stage == "login":
do_login(c)
c.nav(f"{ADMIN}/wanxiang/review", settle=6)
c.shot("/tmp/aw_queue.png")
print("QUEUE:", json.dumps(c.evaljs(JS_DUMP_QUEUE), ensure_ascii=False, indent=1))
elif stage == "approve":
do_login(c)
c.nav(f"{ADMIN}/wanxiang/review", settle=6)
c.shot("/tmp/aw_before.png")
before = c.evaljs(JS_DUMP_QUEUE)
print("BEFORE:", json.dumps(before, ensure_ascii=False))
print("CLICK_PASS:", c.evaljs(js_click_pass(TARGET_GAME)))
time.sleep(2)
c.shot("/tmp/aw_dialog.png")
print("CONFIRM:", c.evaljs(JS_CONFIRM))
time.sleep(4) # 等 API 回 + 刷新队列
c.shot("/tmp/aw_after.png")
after = c.evaljs(JS_DUMP_QUEUE)
print("AFTER:", json.dumps(after, ensure_ascii=False))
c.close()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,15 @@
#!/bin/bash
# admin 走查 runner:整个流程输出重定向到文件,chrome 全 fd 脱离 ssh 管道。
# 用法:ssh mini 'nohup bash /tmp/run_walk.sh <stage> >/dev/null 2>&1 & ';再轮询 /tmp/walk_out.txt
STAGE="${1:-recon}"
exec > /tmp/walk_out.txt 2>&1
echo "=== run_walk stage=$STAGE @ $(date +%H:%M:%S) ==="
pkill -f "chrome.*9222" 2>/dev/null; sleep 1; rm -rf /tmp/chrome-aw
setsid google-chrome --headless=new --no-sandbox --no-zygote --disable-gpu \
--disable-dev-shm-usage --remote-debugging-port=9222 --remote-allow-origins=* \
--user-data-dir=/tmp/chrome-aw </dev/null >/tmp/chrome-aw.log 2>&1 &
sleep 6
echo "VER:"; curl -s http://localhost:9222/json/version 2>/dev/null | head -c 110; echo
echo "WALK:"; python3 /tmp/admin_walk.py "$STAGE" "$2" 2>&1
pkill -f "chrome.*9222" 2>/dev/null
echo "=== DONE @ $(date +%H:%M:%S) ==="

View File

@ -0,0 +1,749 @@
# Wave4(community + biz)· execution 版
- **文档编号**:HJ-WAVE4-EXEC-001
- **日期**:2026-06-11
- **状态**:待实现 agent 认领 + 主 agent 验收(评审版三项战略决策 D-A/D-B/D-C 已创始人拍板,见 §0)
- **唯一设计依据**:`docs/agent-specs/2026-06-11-Wave4-community-biz-review.md`(编号 HJ-WAVE4-001,已两轮对抗评审定稿)+ `docs/agent-specs/2026-06-11-Wave4-community-biz-review-评审纪要.md`(R1 24 条 + R2 6 条逐条处置)。**本文不得与评审版矛盾**;执行中发现矛盾,停工上报,以评审版为准。
- **读者**:实现 agent 分工认领(建议 community 先、biz 后,单序列资源由 §0 单 agent 收口);主 agent 验收。
- **环境铁律**(继承项目记忆 `internal-build-infra-servers` / `m1-runtime-bringup-state` / `frontend-spine-built`):
- 重型构建/测试一律上 **mini-desktop**(`ssh mini-desktop`,15G 内存,有 java17/mvn/docker);**本机严禁 mvn/npm build**(小内存会 OOM);源码同步走 `git push`(阿里云 Gitea `ssh://git@101.200.34.71:2222`)→ mini-desktop `git pull`(scp 源码树被分类器拦)。
- staging app 在 mini-desktop 隔离容器 `http://100.64.0.7:48080`(对内 `http://localhost:48080`)。
- 含中文 SQL 在 mini-desktop 执行务必 `--default-character-set=utf8mb4`(防乱码,记忆铁律)。
- **「骨架编译过 ≠ 真实可验收」**(评审版 R10 铁律 + 记忆 `golden-loop-b1-done` / `frontend-link-ui-walk-publish-fix`):完成条件强制要求真实写链路 staging 实测(§9.3 两条系统身份写链路)+ biz/community UI 入口真人走查(§9.4),不以批跑/API 全绿替代。
---
## 0. 拍板前提(评审版 §0.1 / §7,本文全按拍板结果起草)
| 决策 | 拍板结果(本文落点) |
|---|---|
| **D-A** biz 形态 | **轻量 lead form 先行**——只交付「询单→报价→进度→demo 确认」信息流闭环,资金流(收款/分账/在线签章)留 M4(§7 biz 建设 + §10 M4 留债) |
| **D-B** community 功能优先级 | **通知底座最小集**——T-CMU-10 编排 + T-CMU-06 站内信 + T-CMU-11 等级引擎(+可选短信 T-CMU-09),一次覆盖 5/5 P0;SOC/GRW 域仅留接口桩、不实现(§6 community 建设) |
| **D-C** 是否本波接 ip seam | **不接、维持 D5 推迟**——本波 P-BIZ-02 直接复用 aigc 4 模板 + runtime 沙箱(绕开 T-IP-09 模板市场),无需 ip seam |
| 建设次序(execution 单 agent 定) | **先 community 后 biz**(community 触达机制最独立、复用面集中、风险最低;biz 外部对接多、受 pay 桩制约,适合后段) |
| P-INC-01 计数权威源(execution §0 单 agent 拍) | **(a) project 发布成功后同进程 notify community 计数**(与三上游同范式;实查 ProjectApi **无** `countPublishedByCreator` 方法,方案 b 需 project 新增端点,故取 a 上游单点改动)。详见 §6.4 |
---
## 1. 目标与范围边界
### 1.1 目标
交付 **Wave4 总 11 P0 = community 5 + biz 6 可验证**,不破坏已建 11 模块(错误码段/Flyway 序号/前缀/契约文件名零冲突)。
- **community(5 P0 = P-NTF-01/02/03/05 + P-INC-01)**:通知底座(编排 + 站内信 + 等级引擎)。触达机制 = **同进程 `-api` notify-push**(评审版 §3.3 定型):aigc 生成完成 / compliance·project 审核出结果 / trade 收益变动后,由各自 service 在本地事务**提交后**同进程调用 community 暴露的 `CommunityNotifyApi`(复用 `@Primary` 本地实现),写站内信。**不新建 MQ、不动 `events.schema.json`**。
- **biz(6 P0 = P-BIZ-01/02/03/04/08/12)**:轻量 lead form 信息流闭环(询单→报价→进度看板→demo 试玩确认),复用 aigc 4 模板 / runtime 取包 / admin 队列 / project 轻量状态机范式。**资金流(收款/分账/在线签章)留 M4**。
### 1.2 明确不做(继承评审版 §1 非目标,实现 agent 不得擅自扩界)
- biz **不收款结算**(T-BIZ-08 收费、T-BIZ-03 在线电子签章)→ 本波只到「状态机走通 + 线下签署后台标记兜底」,资金流并入 M4。
- community **不全量铺开 T-CMU 全家桶**——SOC/GRW 域(关系图谱/排行/成就/扇出/邮件,全 P1/P2)只留接口桩、不实现。
- **不引入 Flowable / yudao-bpm**(当前注释未装配)——复用 project 已落地的轻量状态机范式(`ProjectStatusEnum` + `ProjectServiceImpl` 写前流转校验,**无独立 `ProjectStateService.java`**)。
- **community「记入钱包/流量包」本波不跨模块写**——实查 `feed.yaml` 仅 `boost`/`pinned`(无流量包发放端点)、`trade.yaml` 无 grant/incentive 端点,两个跨模块写 seam **当前均不存在**;本波只写 community **自有 reward 触发记录表**,真实记账/到账留 M4。
- **P-BIZ-11 学生防沉迷不在 biz**——经 Doc C 已划归 compliance(`T-CMP-36/37`),由 compliance 承接(不计入 biz)。
- **本波两类留债分开记账**(评审版 R1,勿混):**①资金流债**(biz 签署/收款、community reward 真实记账)→ 统一并入 **M4**;**②前端 UI 入口债**(biz 运营代客发起=WS4 / 客户进度看板=WS3)→ 跨工位协调项,可与本波后端**并行**推进,不与 M4 资金流债捆绑延期(§10)。
- **`events.schema.json` 本波零改**(community 走 notify-push、不新增遥测事件)——主动规避「只改契约不改 `TelemetryEventEnum` = 事件静默 rejected」双处同步坑(评审版 §3.3 保留口径)。
---
## 2. 涉及模块与文件路径(克隆 + 接线全清单)
> 路径前缀统一 `/root/games-development-ai/`;包名固定 `cn.wanxiang.game.module.{community|biz}.{层}`(克隆黄金模块 `game-module-project` 范式,**禁** `cn.iocoder` 业务包名)。
### 2.1 新建模块(克隆黄金模块)
| 模块 | 子模块 | 关键文件(举黄金模块 project 实例为样板,file:line) |
|---|---|---|
| **community** | `game-module-community-api` | `pom.xml`(artifactId→`game-module-community-api`)/ `enums/ApiConstants.java`(NAME→`community-server`、PREFIX→`RPC_API_PREFIX+"/community"`)/ `enums/ErrorCodeConstants.java`(段 `1_107_000_000` 起)/ `api/CommunityNotifyApi.java`(Feign 接口,照 `ProjectApi.java:23` `@FeignClient(name=ApiConstants.NAME)`)/ `dto/`(跨模块 DTO) |
| | `game-module-community-server` | `pom.xml`(artifactId→`game-module-community-server`)/ `controller/app/message/AppCommunityMessageController.java`(`@RequestMapping("/community")`,前缀 `/app-api` 框架自动加,照 `AppProjectController.java:33-37`)/ `controller/admin/notification/AdminCommunityNotificationController.java`(mock-trigger,照 `AdminProjectController.java:31-35`)/ `service/{message,level,notify}/`(业务规则 + `@Transactional` 只加此层)/ `dal/dataobject/community/{MessageDO,LevelDO,RewardDO}.java`(`extends TenantBaseDO`,照 `ProjectDO.java:18-83`)/ `dal/mysql/community/`(`@Mapper extends BaseMapperX`,显式列禁裸 `select*`,照 `ProjectMapper.java:16-87`)/ `convert/`(`BeanUtils`+手写 static,**非 MapStruct**,照 `ProjectConvert.java`)/ `api/CommunityNotifyApiImpl.java`(`@RestController @Validated @Primary implements CommunityNotifyApi`,照 `ProjectApiImpl.java:22-25`)/ `src/test/java/.../service/.../*ServiceImplTest.java`(`extends BaseMockitoUnitTest`) |
| **biz** | `game-module-biz-api` | 同 community-api 结构(artifactId→`-biz-api`、NAME→`biz-server`、段 `1_110_000_000` 起)+ `enums/BizLeadStatusEnum.java`(照 `ProjectStatusEnum.java:23-68`) |
| | `game-module-biz-server` | 同 community-server 结构(artifactId→`-biz-server`);controller 偏 `controller/admin/biz/`(运营为主)+ 少量 `controller/app/biz/`(客户提单 + 我的订单);`service/lead/BizLeadServiceImpl.java`(流转校验内联,照 `ProjectServiceImpl.java:111-116/350-370`) |
### 2.2 公共装配点(⚠️ **两处**,缺任一处克隆即卡 compile,实现 agent **不得以旧 skill 为唯一依据**)
> **评审版 §2.4 + R2-2 硬提示**:`add-business-module.md` 步骤表当前**仅列「步骤5=yudao-server/pom.xml 加依赖」,未把 root `game-cloud/pom.xml` 的 `<modules>` 注册列为独立步**(开头还明写「无需改 Application/yaml,只需在 yudao-server/pom.xml 加依赖」)。该 root pom 注册步**计划本波收口后才回写进 skill**。故本波克隆**以本节「装配点=两处」为准**——只读 skill 会漏掉 root pom 的 `<module>` 注册,触发「缺此步父 pom 找不到聚合定义 → `mvn -pl … compile` 失败」。
| # | 文件(亲核 2026-06-11 dev/2.0.0) | 改动 | 缺此步的后果 |
|---|---|---|---|
| **①** | `game-cloud/pom.xml`(root 聚合 pom)`<modules>` 块(实查 `:10` `<modules>` … `:34` 末为 `game-module-studio` … `:35` `</modules>`) | 在 **line34 之后 / line35 `</modules>` 之前**追加 `<module>game-module-community</module>` + `<module>game-module-biz</module>` | `mvn -pl game-module-community compile` 找不到父 pom 聚合定义 → **compile 失败** |
| **②** | `game-cloud/yudao-server/pom.xml` `<dependencies>` 块(实查 `:23` `<dependencies>` … `:80` 末 game-module 依赖为 `game-module-studio-server`) | 在 **studio-server `<dependency>` 块之后**追加两个 `<dependency>`(groupId=`cn.iocoder.cloud` / artifactId=`game-module-community-server`、`game-module-biz-server` / version=`${revision}`) | 单体不把模块编进 JAR → 启动后无此模块、Swagger 无分组 |
| ③ | `YudaoServerApplication.java` / `application.yaml` | **零改**(`YudaoServerApplication` 组件扫描 + `@MapperScan` + `application.yaml` type-aliases 已通配 `cn.wanxiang.game.module`;端前缀按 `controller.app`/`controller.admin` 包名自动加) | —(明确写入本文降认知负荷) |
### 2.3 契约文件(新建,独占无冲突)
- `contracts/db-schemas/V12.0.0__create_game_community.sql` + `game-cloud/yudao-server/src/main/resources/db/migration/V12.0.0__create_game_community.sql`(两处**字节一致**,见 §0/§5)
- `contracts/db-schemas/V13.0.0__create_game_biz.sql` + `game-cloud/yudao-server/src/main/resources/db/migration/V13.0.0__create_game_biz.sql`(两处字节一致)
- `contracts/api-schemas/community.yaml`、`contracts/api-schemas/biz.yaml`(独占新文件,实查均不存在)
- `contracts/README.md §四`:community=107 / biz=110 **已在册**(`:79`,只读确认,**不改**)
### 2.4 上游 notify 接线点(本波在上游各新增 1 处同进程调用 + 上游 `-server/pom.xml` 加 `game-module-community-api` 依赖)
| 挂点 | 文件:line(亲核 2026-06-11) | 接线说明(详见 §6.5) |
|---|---|---|
| 挂点1 P-NTF-02 生成完成 | `game-module-aigc/.../service/callback/DifyCallbackServiceImpl.java:~52`(`return txService.handleCallbackTx(reqVO);` 内层 `@Transactional` 三表写入链于此提交) | 改为先接 `Boolean result`,**提交后经 `aigcTaskMapper.selectByTraceId(reqVO.getTraceId())` 回查任务**,仅 `task.status==SUCCEEDED 且 versionId!=null`(真实成功终态,排除 config_invalid/failed/幂等无新版本)才调 `notifyGenerateDone(task.getCreatorUserId(), task.getGameId(), task.getVersionId())`,再 return(实参三字段均取自回查任务,**不走 `projectApi.getCreatorUserId`**)|
| 挂点2 P-NTF-03 审核结果 + P-INC-01 发布计数 | **外层 caller** `game-module-project/.../controller/admin/project/AdminProjectController.java:55-57`(`projectService.reviewProject(reviewReqVO, reviewerUserId)`;service 本身 `@Transactional` 于 `ProjectServiceImpl.java:207`) | service 返回后调 `notifyReviewResult(...)`;`isApprove` 时多调一次 `notifyProjectPublished(...)`(合并在同一 caller) |
| 挂点3 P-NTF-05 收益变动 | `game-module-trade/.../service/settle/SettlementServiceImpl.java:~78`(settle 循环内 `firstRecord` 分支 / `newlyRecorded++`;`recordIncome` 为 `@Transactional` 单条已提交,循环体非事务) | `if (firstRecord) {...}` 分支内调 `notifyIncomeChanged(...)`(仅首次真实入账才通知,幂等复跑不重发) |
> ⚠️ **行号漂移声明**:以上 file:line 基于 dev/2.0.0 工作区快照(亲核日 2026-06-11)。落地时若上游模块有改动,**以语义定位为准**(aigc=`handleCallbackTx` 返回处、project=`AdminProjectController.reviewProject` 调用返回后、trade=settle 循环 `firstRecord` 分支)。
---
## 3. 数据流与依赖(一图看清边界)
```mermaid
graph TB
subgraph UP["上游(本波各加 1 处提交后同进程 notify)"]
AGC["aigc DifyCallbackServiceImpl<br/>handleCallbackTx 提交后"]
PRJ["project AdminProjectController<br/>reviewProject 返回后"]
TRD["trade SettlementServiceImpl<br/>settle firstRecord 分支"]
end
subgraph CMU["community(V12 / 段 1-107 / CommunityNotifyApi @Primary)"]
NAPI["CommunityNotifyApi 入口<br/>(系统身份注入 LoginUser(id=0)+finally clearContext)"]
MSG["game_community_message 站内信"]
LVL["game_community_level 等级<br/>(published_count 原子自增)"]
RWD["game_community_reward 触发记录<br/>(uk 幂等;发放留 M4)"]
end
subgraph BIZ["biz(V13 / 段 1-110)"]
LEAD["game_biz_lead 询单(状态机)"]
QUOTE["game_biz_quote 报价"]
PROG["game_biz_progress 进度工单"]
ACC["game_biz_acceptance 验收"]
end
subgraph REUSE["只读复用(biz 不反写)"]
RTPKG["runtime 取包双路由<br/>/app-api/runtime/package/{versionId}(+/manifest)"]
TPL["aigc 4 模板 clicker/merge/idle/tycoon"]
end
subgraph M4["M4 留债(明确推迟)"]
PAY["pay 收款 / 在线签章 / trade IncentiveCreditApi / feed 流量包-api / 现金到账"]
end
AGC -.notifyGenerateDone.-> NAPI
PRJ -.notifyReviewResult + notifyProjectPublished.-> NAPI
TRD -.notifyIncomeChanged.-> NAPI
NAPI --> MSG
NAPI --> LVL --> RWD
LEAD --> QUOTE
LEAD --> PROG
LEAD --> ACC
RTPKG -.demo 可预览判定(只读).-> BIZ
TPL -.模板复用(只读).-> BIZ
RWD -.真实记账/到账.-> M4
ACC -.签署/收款.-> M4
classDef deb fill:#fee,stroke:#c33;
class PAY deb;
classDef new fill:#eef,stroke:#36c;
class AGC,PRJ,TRD new;
```
**事务边界铁律(评审版 §3.3/§4 红线)**:三上游写方法本身均 `@Transactional`,notify 必须在**本地事务提交后**调用(=外层 caller 或循环内已提交分支),避免「通知失败回滚已提交业务」。三个 notify 调用点统一**`try-catch` 吞异常 + 仅记 error log**,不得让通知失败中断业务/结算循环(详见 §6.5、§8.3)。
---
## 4. 接口与数据契约(DDL + 端点 + CommunityNotifyApi)
> DDL 套黄金模块公共列模板(recon `errorcode-flyway-ddlconv` 实证范式):InnoDB + utf8mb4;显式列 + 列级中文 COMMENT;状态机用 `TINYINT`,**非法流转由 Service 校验、DO 层不承载**;每表强制 6 审计列(`creator`/`create_time`/`updater`/`update_time`/`deleted BIT(1) DEFAULT b'0'`/`tenant_id BIGINT DEFAULT 0`,全 NOT NULL);唯一键含 `(业务键, deleted, tenant_id)`;业务归属列 `creator_user_id`/`user_id`(**归属隔离边界**)**区别于** Yudao 审计列 `creator`(VARCHAR64)。
> **⚠️ 归属隔离口径钉实(R-medium PII 红线,实查黄金模块未用 @DataPermission)**:实查 `game-module-project` 的 DAL/Mapper **零 `@DataPermission` 注解**(全 game-module grep 无命中),其归属隔离实为 **Mapper 查询显式以 `creator_user_id`/`user_id` 作 WHERE 谓词、且该 userId 取自 `SecurityFrameworkUtils.getLoginUserId()`(token 解析、非前端入参)**——范本=`ProjectMapper.selectMyPage`「强制按当前用户过滤」、`TradeController` 各 app 端点 `getLoginUserId()`。**故 community/biz 客户侧端点(`/app-api/community/messages`、`/app-api/biz/leads/my` 等)必须在 Mapper 查询里强制以登录态 user_id 作谓词 enforce 隔离,不得依赖「黄金模块并未启用的 @DataPermission 框架机制」**(照搬「靠 DataPermission」会既不加注解、又误以为框架已隔离 → 用户可读他人站内信/收益数额/审核拒绝原因等 PII)。隐私隔离必须在后端查询层 enforce(可信边界),前端不够。
### 4.1 community DDL — `V12.0.0__create_game_community.sql`(三表)
> 文件头注释块须含:契约#2 DB 迁移标识 + 模块名 community + owner WS5 / 文件名 + Flyway 纪律(只新增、已合入禁改、回滚写补偿迁移 V12.0.1)/ 表清单 + 职责(引架构 Doc B community 章节)/ 错误码段 `community = 1-107-***-***` / 约定(InnoDB+utf8mb4、显式列、Yudao **6 审计列**(`creator`/`create_time`/`updater`/`update_time`/`deleted`/`tenant_id`,与 §4 表头及黄金模块 V1/V9 头注对齐,勿误作「七」去找幽灵第 7 列)、状态机 tinyint、中文注释、禁裸 select*)。
```sql
-- 表1:game_community_message 站内消息中心(P-NTF-01/02/03/05 投递落点)
CREATE TABLE `game_community_message` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '消息 ID',
`user_id` BIGINT NOT NULL COMMENT '收件人用户 ID(归属隔离:Mapper 强制以 getLoginUserId() 作 WHERE 谓词,用户只见自己消息)',
`type` TINYINT NOT NULL COMMENT '消息类型:1系统 2公告 3审核 4收益',
`biz_ref` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '业务关联键(如 gameId/taskId/settleId,供前端跳转)',
`title` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '消息标题',
`content` VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '消息正文(审核拒绝含原因)',
`read_status` TINYINT NOT NULL DEFAULT 0 COMMENT '已读状态:0未读 1已读',
`read_time` DATETIME NULL COMMENT '已读时间',
`creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列;系统身份投递=0)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者(系统身份写=0,防 NOT NULL 拒绝)',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删',
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(MVP 单租户=0)',
PRIMARY KEY (`id`),
KEY `idx_user_read` (`user_id`, `read_status`, `create_time`) COMMENT '我的消息中心列表(按已读态+时间倒序)',
KEY `idx_user_type` (`user_id`, `type`, `create_time`) COMMENT '按四类聚合查询'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '站内消息中心(四类聚合,T-CMU-06)';
-- 表2:game_community_level 创作者等级(P-INC-01 计数权威落点 published_count)
CREATE TABLE `game_community_level` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '等级记录 ID',
`creator_user_id` BIGINT NOT NULL COMMENT '创作者用户 ID',
`level` TINYINT NOT NULL DEFAULT 1 COMMENT '创作者等级:1小白(MVP 仅维护计数+新人里程碑,分层留 P1)',
`published_count` INT NOT NULL DEFAULT 0 COMMENT '已发布作品数(计数权威源=project 发布成功 notify 原子累加)',
`last_milestone` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '末次已触发里程碑键(如 publish_3,幂等防重复触发)',
`last_milestone_time` DATETIME NULL COMMENT '末次里程碑触发时间',
`creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除',
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_creator` (`creator_user_id`, `deleted`, `tenant_id`) COMMENT '一创作者一条等级记录(幂等初始化)'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '创作者等级(T-CMU-11 等级引擎)';
-- 表3:game_community_reward 新人奖励触发记录(P-INC-01;本波只写触发记录,发放留 M4)
CREATE TABLE `game_community_reward` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '奖励触发记录 ID',
`creator_user_id` BIGINT NOT NULL COMMENT '创作者用户 ID',
`milestone` VARCHAR(32) NOT NULL COMMENT '里程碑键(如 publish_3 发满3作品)',
`reward_type` TINYINT NOT NULL COMMENT '奖励类型:1流量包 2现金',
`reward_amount` BIGINT NOT NULL DEFAULT 0 COMMENT '奖励额度(流量包=曝光量;现金=分,BIGINT 防浮点;与 trade 同口径)',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '发放状态:0已触发待发放(本波终态) 1已发放(M4 回写)',
`granted_ref` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'M4 发放回执(trade 流水 id / feed 流量包 id),本波空',
`creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(系统身份触发=0)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者(系统身份写=0)',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除',
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_creator_milestone` (`creator_user_id`, `milestone`, `deleted`, `tenant_id`) COMMENT '同创作者同里程碑只触发一次(幂等防重复发钱)',
KEY `idx_status` (`status`) COMMENT 'M4 待发放扫描'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '新人奖励触发记录(P-INC-01,发放留 M4)';
```
### 4.2 biz DDL — `V13.0.0__create_game_biz.sql`(四表)
> 文件头注释块同 §4.1 范式(模块名 biz / owner WS5 / 错误码段 `biz = 1-110-***-***` / 资金流边界:本波只写自有表,不写 pay/trade,签署/收款/分账留 M4)。
```sql
-- 表1:game_biz_lead B/G 端定制询单主单(含轻量状态机;CRM=工单)
-- 状态机 status:0待跟进 →1已报价 →2制作中 →3待验收 →4已交付(单线性五态,BizLeadService 写前校验)
CREATE TABLE `game_biz_lead` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '定制询单 ID',
`customer_user_id` BIGINT NULL COMMENT '提单客户用户 ID(B 端自助提单时落;运营代客发起可空)',
`owner_user_id` BIGINT NULL COMMENT '跟进负责人用户 ID(运营,admin 队列认领)',
`biz_type` TINYINT NOT NULL DEFAULT 3 COMMENT '定制场景:1品牌营销 2文旅 3通用定制',
`contact_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '联系人',
`contact_phone` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '联系电话',
`company` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '企业/机构名称',
`title` VARCHAR(120) NOT NULL DEFAULT '' COMMENT '需求标题',
`scene_desc` VARCHAR(2000) NOT NULL DEFAULT '' COMMENT '场景需求描述(B/G 端在线提交的定制需求正文)',
`template_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '选定的玩法模板 ID(复用 aigc 4 模板 clicker/merge/idle/tycoon)',
`demo_version_id` BIGINT NULL COMMENT 'demo 预览版本 ID(runtime game_version.id;取包走 /app-api/runtime/package/{versionId})',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态机:0待跟进 1已报价 2制作中 3待验收 4已交付',
`creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者(Yudao 审计列)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者(系统/异步写须注入 LoginUser(id=0),否则 NOT NULL 拒绝)',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除:0未删 1已删',
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID(MVP 单租户=0)',
PRIMARY KEY (`id`),
KEY `idx_owner_status` (`owner_user_id`, `status`) COMMENT 'admin 处理队列:按负责人+状态',
KEY `idx_customer` (`customer_user_id`, `create_time`) COMMENT '客户侧我的订单看板(/app-api/biz/leads/my)',
KEY `idx_status_update` (`status`, `update_time`) COMMENT '运营队列按状态+时间'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = 'B端定制询单主单(轻量状态机)';
-- 表2:game_biz_quote 报价表(报价≠可支付订单,参数化估价输出;收款留 M4)
CREATE TABLE `game_biz_quote` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '报价 ID',
`lead_id` BIGINT NOT NULL COMMENT '所属询单 ID(game_biz_lead.id)',
`quote_no` INT NOT NULL DEFAULT 1 COMMENT '报价版本号(同询单内可多轮报价,递增;唯一性由 service 以 max(quote_no)+1 串行生成保证、DB 不强约束——与黄金模块 game_version.version_no 同范式,非唯一键。若后续需 DB 级唯一可加 uk_lead_quote(lead_id,quote_no,deleted,tenant_id))',
`amount_fen` BIGINT NOT NULL DEFAULT 0 COMMENT '报价金额(单位:分,与 trade 同口径;本波仅展示不收款)',
`service_items` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '服务项明细(编排内容,参数化估价表单产出)',
`valid_days` INT NOT NULL DEFAULT 30 COMMENT '报价有效天数',
`remark` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
`creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除',
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID',
PRIMARY KEY (`id`),
KEY `idx_lead` (`lead_id`, `quote_no`) COMMENT '按询单查报价历史'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = 'B端定制报价表(报价≠下单,收款留 M4)';
-- 表3:game_biz_progress 进度工单表(状态机各节点流转记录,驱动定制进度看板)
CREATE TABLE `game_biz_progress` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '进度工单 ID',
`lead_id` BIGINT NOT NULL COMMENT '所属询单 ID',
`from_status` TINYINT NULL COMMENT '流转前状态(首条为 null)',
`to_status` TINYINT NOT NULL COMMENT '流转后状态(0-4)',
`operator_user_id` BIGINT NULL COMMENT '操作人用户 ID(运营推进;系统推进可空)',
`note` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '进度说明(看板时间线展示)',
`creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除',
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID',
PRIMARY KEY (`id`),
KEY `idx_lead_time` (`lead_id`, `create_time`) COMMENT '按询单取进度时间线(看板)'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = 'B端定制进度工单表(看板时间线)';
-- 表4:game_biz_acceptance 交付验收表(试玩→反馈→确认三态;签署/收款留 M4)
CREATE TABLE `game_biz_acceptance` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '验收记录 ID',
`lead_id` BIGINT NOT NULL COMMENT '所属询单 ID',
`demo_version_id` BIGINT NOT NULL COMMENT '交付 demo 版本 ID(runtime game_version.id,试玩取包用)',
`feedback` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '客户试玩反馈',
`confirm_status` TINYINT NOT NULL DEFAULT 0 COMMENT '确认态:0待确认 1已确认',
`confirmed_time` DATETIME NULL COMMENT '客户确认时间',
`signed_offline` BIT(1) NOT NULL DEFAULT b'0' COMMENT '线下签署后台标记兜底(M4 接在线签章前过渡:0未签 1已线下签)',
`creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '逻辑删除',
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户 ID',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_lead_version` (`lead_id`, `demo_version_id`, `deleted`, `tenant_id`) COMMENT '同询单同 demo 版本一条验收(含 deleted/tenant 维度)',
KEY `idx_lead` (`lead_id`) COMMENT '按询单查验收'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = 'B端定制交付验收表(试玩/反馈/确认;签署收款留 M4)';
```
### 4.3 CommunityNotifyApi 方法签名(`@Primary` 本地实现)
> 路径:`game-module-community-api/.../api/CommunityNotifyApi.java`(接口,`@FeignClient(name=ApiConstants.NAME)`、`ApiConstants.NAME="community-server"`、`PREFIX=RpcConstants.RPC_API_PREFIX+"/community"`)+ `community-server/.../api/CommunityNotifyApiImpl.java`(`@RestController @Validated @Primary implements CommunityNotifyApi`,照 `ProjectApiImpl.java:22-25`)。实现入口**注入系统身份 `LoginUser(id=0)`+finally `clearContext`**(§8.2)。统一 `CommonResult` 信封;幂等以 `(userId,type,bizRef)` 去重(重复投递不重复落库)。
```java
public interface CommunityNotifyApi {
// P-NTF-02 生成完成通知:aigc 在 DifyCallbackServiceImpl.handleCallback 提交后调
// 触发判定 = 提交后回查任务为 SUCCEEDED 终态且 versionId 已回填(非 result==TRUE,见 §6.5 挂点1);
// 三实参均取自回查的 AigcTaskDO(creatorUserId/gameId/versionId)
CommonResult<Boolean> notifyGenerateDone(@RequestParam("creatorUserId") Long creatorUserId,
@RequestParam("gameId") Long gameId,
@RequestParam("versionId") Long versionId);
// P-NTF-03 审核结果通知:project 在 reviewProject 提交后调;
// decision 三态(1通过/2拒绝/3下架,对齐 ReviewDecisionEnum)——本波仅对 1/2 发通知,
// decision=3 下架(含封禁联动)本波不发(caller 侧以 decision∈{1,2} 守门,见 §6.5 挂点2);reject(2) 须带 reason
CommonResult<Boolean> notifyReviewResult(@RequestParam("creatorUserId") Long creatorUserId,
@RequestParam("gameId") Long gameId,
@RequestParam("decision") Integer decision,
@RequestParam(value = "reason", required = false) String reason);
// P-NTF-05 收益变动通知:trade 在 SettlementServiceImpl.settle 入账成功后调
// netAmount=创作者实得净额(分);settle 作用域无现成 net(recordIncome 内部算、不返回),
// 须在 settle 内现算 BigDecimal.valueOf(gross).multiply(creatorShare).setScale(0,DOWN)(见 §6.5 挂点3)
CommonResult<Boolean> notifyIncomeChanged(@RequestParam("creatorUserId") Long creatorUserId,
@RequestParam("source") Integer source,
@RequestParam("sourceRef") String sourceRef,
@RequestParam("netAmount") Long netAmount);
// P-INC-01 发布计数(方案a):project 发布成功后同进程 notify,累加 published_count 并触发里程碑
CommonResult<Boolean> notifyProjectPublished(@RequestParam("creatorUserId") Long creatorUserId,
@RequestParam("gameId") Long gameId);
}
```
### 4.4 community.yaml 端点清单(`contracts/api-schemas/community.yaml`,段注释 1-107)
| 方法 路由 | P0 | 说明 |
|---|---|---|
| GET `/app-api/community/messages` | P-NTF-01 | 站内信分页列表(query: `type` 1系统/2公告/3审核/4收益 可选, `readStatus` 可选, `cursor`/`size`) |
| POST `/app-api/community/messages/{id}/read` | P-NTF-01 | 标记单条已读(落 `read_status=1`+`read_time`) |
| GET `/app-api/community/messages/unread-count` | P-NTF-01 | 未读数角标(按 type 分组计数) |
| GET `/app-api/community/level` | P-INC-01 | 当前创作者等级 + `published_count` + 下一里程碑进度(如 2/3 距新人奖励) |
| GET `/app-api/community/rewards` | P-INC-01 | 我的奖励触发记录列表(status 触发态/发放态可见) |
| [RPC] `/rpc-api/community/*` | — | `CommunityNotifyApi` 四方法,供上游同进程 `@Primary` 调用,非对外业务路由 |
| [staging only] POST `/admin-api/community/notifications/{type}/mock-trigger` | — | 上游未接通前独立验收四类投递,**仅 dev/staging 生效**(`@Profile({"dev","staging"})` 或网关层关闭,生产关闭)。⚠️ 实查全 game-module **无 `@Profile` 先例**(现有 dev/staging 差异化手段是 `@ConditionalOnProperty` 灰度开关,如 aigc `AIGC_EXECUTOR_ENABLED`)——若用 `@Profile` 须知是本仓首例、在类或方法级标注、并验证 staging profile 下 Bean 注册生效;亦可改用 `@ConditionalOnProperty(name="community.mock-trigger.enabled", havingValue="true")` 与现有灰度开关风格一致。二选一钉死 |
### 4.5 biz.yaml 端点清单(`contracts/api-schemas/biz.yaml`,段注释 1-110;端前缀框架按包名自动加,`@RequestMapping` 只写 `/biz`)
**客户侧 `/app-api/biz/*`(game-studio,用户 Token + Mapper 强制 `getLoginUserId()` 归属谓词,WS3)**
| 方法 路由 | P0 | 说明 |
|---|---|---|
| POST `/app-api/biz/leads` | P-BIZ-01 | 提交定制需求询单(落库即开状态机=待跟进0 + 落 progress 首条 to_status=0);req=`BizLeadCreateReqVO`(bizType/title/sceneDesc/contactName/contactPhone/company);resp=`CommonResult<Long>` |
| GET `/app-api/biz/leads/my` | P-BIZ-03 | 我的定制订单列表(`customer_user_id` 过滤,客户只见自己) |
| GET `/app-api/biz/leads/{id}` | — | 询单详情(含当前状态/最新报价/进度时间线/demo 可预览判定) |
| GET `/app-api/biz/leads/{id}/progress` | P-BIZ-03 | 进度看板时间线(`game_biz_progress` 按时间) |
| GET `/app-api/biz/templates` | P-BIZ-02 | 按场景检索可选模板(复用 aigc 4 模板);query=`bizType` |
| POST `/app-api/biz/acceptances/{leadId}/feedback` | P-BIZ-04 | 客户试玩后提交反馈;req=`BizFeedbackReqVO`(demoVersionId/feedback) |
| POST `/app-api/biz/acceptances/{leadId}/confirm` | P-BIZ-04 | 客户确认验收(`confirm_status=1`,状态机 3→4 已交付) |
> demo 试玩取包**不在 biz.yaml 自建端点**——直接复用 runtime 真实双路由:`GET /app-api/runtime/package/{versionId}`(清单 meta,`CommonResult<RuntimePackageRespVO>`,biz 看板「是否可预览」判定走此条)+ `GET /app-api/runtime/package/{versionId}/manifest`(原始 JSON,宿主 sha256 比对后注入)。**两条须写全**(评审版 R2-3)。
**admin `/admin-api/biz/*`(game-admin,RBAC `@PreAuthorize`,WS4)**
| 方法 路由 | P0 | 权限点 | 说明 |
|---|---|---|---|
| GET `/admin-api/biz/leads/page` | — | `biz:lead:query` | 处理队列(按 owner/status/bizType 筛选) |
| POST `/admin-api/biz/leads` | P-BIZ-08/12 | `biz:lead:create` | 运营代客发起定制单(`customer_user_id` 可空) |
| PUT `/admin-api/biz/leads/{id}/assign` | — | `biz:lead:assign` | 认领/指派负责人(`owner_user_id`) |
| POST `/admin-api/biz/leads/{id}/quotes` | P-BIZ-08 | `biz:quote:create` | 报价输出(参数化估价,报价≠可支付订单;状态机 0→1) |
| GET `/admin-api/biz/leads/{id}/quotes` | — | `biz:quote:query` | 报价历史 |
| POST `/admin-api/biz/leads/{id}/advance` | P-BIZ-03 | `biz:lead:advance` | 推进状态机(→2制作中/→3待验收,→3 守 demo 可预览);req=`BizAdvanceReqVO`(toStatus/note/demoVersionId) |
| PUT `/admin-api/biz/acceptances/{id}/sign-offline` | P-BIZ-04 | `biz:acceptance:sign` | 线下签署后台标记兜底(`signed_offline=1`) |
> **权限点登记(§7.4 必做)**:`biz:lead:query/create/assign/advance`、`biz:quote:create/query`、`biz:acceptance:sign` 需在 yudao `system_menu` 登记后 RBAC 才放行(范本=`project:review:query` 登记方式)。
---
## §0. 全局单序列资源预占登记(⚠️ 开篇第一节,单 agent 一次性收口提交)
> **目的**(评审版 §8 §0 / R1-5 / R6):防 V12/V13 双占、文件名碰撞、错误码撞段。这是**一个可勾的中央登记动作,由单 agent 收口提交**(建 community 的 agent 在动任何代码前先做完本节并提交,biz agent 从该提交 pull)。
### §0.1 预占前置校验(落地时实跑,证据入提交说明)
```bash
cd /root/games-development-ai
# ① Flyway 文件名占用校验(按文件名,非内容;应全空、exit≠0=未占用)
ls contracts/db-schemas/V12* contracts/db-schemas/V13* \
game-cloud/yudao-server/src/main/resources/db/migration/V12* \
game-cloud/yudao-server/src/main/resources/db/migration/V13* 2>/dev/null; echo "exit=$? # 期望 exit=2(无此文件)"
# ⚠️ 不要用 `grep -r 'V12\|V13'` 判占用——它是内容匹配,会命中
# V11.0.0__create_passport_player_invite.sql 的让位声明注释(两处副本各一行,实测返 2 行),命中仅此注释属预期、非真实占用。
# ② 错误码段占用校验(应全返 0)
grep -rn '1_107\|1_110' game-cloud/ | wc -l # 期望 0
# ③ 契约 yaml 占用校验(应不存在)
ls contracts/api-schemas/community.yaml contracts/api-schemas/biz.yaml 2>/dev/null; echo "exit=$? # 期望 exit=2"
```
> 亲核 2026-06-11(dev/2.0.0):① exit=2(V12/V13 均无);② 返 0;③ exit=2。**三项均空 = 可安全预占。**
### §0.2 预占登记表(单 agent 写死锁定)
| 资源 | community | biz | 一致性要求 |
|---|---|---|---|
| Flyway 主版本 | `V12.0.0__create_game_community.sql` | `V13.0.0__create_game_biz.sql` | 两处副本(`contracts/db-schemas/` + `yudao-server/.../db/migration/`)**字节一致**(`md5sum` 全等;命名 `V{x.y.z}__{desc}.sql`,**只增不改旧**——改 V1–V11 任一 → `flyway validate` 阻断 CI) |
| 错误码段 | `1_107_***_***`(`-api` 的 `ErrorCodeConstants.java`) | `1_110_***_***`(同) | `contracts/README.md §四:79` **已在册**(复用不改);段内子码按 `new ErrorCode(1_107_00X_***, "中文")` 分块、文件头列全 13 段防重叠 |
| 契约 yaml | `contracts/api-schemas/community.yaml` | `contracts/api-schemas/biz.yaml` | 独占新文件 |
| W4 埋点加列占位 | — | — | **V14 预留**给 W4 埋点加列(本波不写,仅在登记表占号防后续撞 V12/V13) |
> **Flyway 副本数裁决(recon `errorcode-flyway-ddlconv` 必裁项)**:**取「两副本」**(`contracts/db-schemas/` 授权源 + `yudao-server/.../db/migration/` 唯一执行副本),**不放单模块 `-server/db/migration/`**。依据:V10/V11(跨模块件)已开「两副本」先例;单体部署下 yudao-server 聚合全部 migration,单模块副本是冗余且多一处漂移风险;放进单模块 classpath jar 反而可能触发同版本 Flyway 在多 jar 重复校验失败(V11 守门①根因)。**community/biz `-server` 下不放任何 sql。**
### §0.3 收口动作
单 agent 一次性创建并提交:两个 V12/V13 SQL(各两副本字节一致)+ 两个空 yaml 骨架(或含完整端点)+ root `game-cloud/pom.xml` 的 `<module>` 注册 + yudao-server pom 依赖。**先提交 push,community/biz 实现 agent 再从此提交 pull**,避免并行同改 root pom/Flyway 冲突。
---
## 5. 逐模块克隆关键步骤(community 先,biz 后)
> 通用「克隆 5 步骨架」(评审版 §8):cp 黄金模块 → 改 3 个 pom artifactId → **① root pom 注册 `<module>` + ② yudao-server pom 注册 `-server` 依赖** → -api 写 `ErrorCodeConstants`+`ApiConstants`+枚举+DTO+Feign → -server 写 controller/service/dal/convert/ApiImpl@Primary。**装配=两处**(§2.2),克隆开篇先复述,勿以旧 skill 为唯一依据。
### 5.1 community 建设步骤(13 步,逐步可勾)
| # | 步骤 | 关键点 / 防雷 |
|---|---|---|
| C0 | 完成 §0 预占收口(V12 两副本 + community.yaml + root pom `<module>` + yudao-server pom 依赖),提交 push | 单 agent 收口;biz 从此提交 pull |
| C1 | `cp -r game-module-project game-module-community`,包路径 `project`→`community`、artifactId 三处改(api/server/聚合 pom) | 包名固定 `cn.wanxiang.game.module.community.*`;⚠️ 克隆后清理 community-server pom 中 project 发布编排专用、community 不依赖的跨模块 -api(compliance-api/runtime-api/feed-api/system PlayerApi 等)——community 作为通知收件 + 等级引擎**自包含、不调任何跨模块 -api**(卫生项,留孤儿依赖与「最小变更」相悖;与黄金模块列集一致由 §0 装配点保障,非 pom 依赖集) |
| C2 | 改 `ApiConstants.java`:`NAME="community-server"`、`PREFIX=RPC_API_PREFIX+"/community"` | ⚠️ **不改则 Feign 服务名撞 project-server**(R8) |
| C3 | 改 `ErrorCodeConstants.java`:段 `1_107_000_000` 起,文件头列全 13 段防重叠 | ⚠️ **顺手复制 project 100 段忘改 → 撞段**(R8) |
| C4 | 写 `CommunityNotifyApi.java`(四方法,§4.3 签名)+ DTO | `@FeignClient(name=ApiConstants.NAME)` |
| C5 | 写 `CommunityNotifyApiImpl.java`(`@RestController @Validated @Primary`),入口注入 `LoginUser(id=0)`+finally `clearContext` | ⚠️ **R3 系统身份写雷**(§8.2),单测 mock 测不到 |
| C6 | 写三表 DO(`extends TenantBaseDO`,`@TableName` 改对应新表名+`@KeySequence` 名随表改如 `game_community_message_seq` 或删除)+ Mapper(`BaseMapperX`,显式列禁裸 select*,客户侧查询强制 `getLoginUserId()` 归属谓词,`published_count` 原子自增方法)+ Convert | ⚠️ 克隆继承 `@KeySequence("game_project_seq")`(实查 ProjectDO:19,MySQL 忽略但残留语义错乱)须改名或删;`level` 自增用 `UPDATE … SET published_count=published_count+1 WHERE creator_user_id=?`(§6.4 防并发丢更新) |
| C7 | 写 `service/message/`(投递/已读/未读数)+ `service/notify/`(编排:路由四类→去重→写站内信)+ `service/level/`(计数累加→里程碑判定→插 reward) | `@Transactional` 只加 service 层 |
| C8 | 写 `controller/app/message/`(5 个 app 端点,§4.4,**Mapper 强制 `getLoginUserId()` 归属谓词**)+ `controller/admin/notification/`(mock-trigger,`@Profile({"dev","staging"})` 本仓首例 或 `@ConditionalOnProperty` 灰度开关,§4.4/§8.4 二选一) | mock-trigger 须限定 staging,否则成匿名伪造投递入口(§8.4);客户侧端点隔离靠显式 user_id 谓词,非 @DataPermission(黄金模块未用) |
| C9 | 接挂点1(aigc):`DifyCallbackServiceImpl` succeeded 后调 `notifyGenerateDone`(§6.5);aigc `-server/pom.xml` 加 `game-module-community-api` 依赖 | try-catch 吞异常 |
| C10 | 接挂点2(project):`AdminProjectController.reviewProject` 返回后调 `notifyReviewResult`,`isApprove` 时多调 `notifyProjectPublished`(§6.5);project `-server/pom.xml` 加 community-api 依赖 | reject 须传 `reqVO.getReason()` |
| C11 | 接挂点3(trade):`SettlementServiceImpl` settle 循环 `firstRecord` 分支调 `notifyIncomeChanged`(§6.5);trade `-server/pom.xml` 加 community-api 依赖 | 仅首次入账才通知,幂等不重发 |
| C12 | Nacos 配置注册(§7.3)+ 单测(`extends BaseMockitoUnitTest`,mock insert/updateById 用 `any(XxxDO.class)` 消歧) | 单测覆盖:投递/已读/计数累加/里程碑触发幂等 |
| C13 | mini-desktop 工程门 + staging 链路①实测(§9.2/§9.3) | 「编译过≠可验收」 |
### 5.2 biz 建设步骤(11 步)
| # | 步骤 | 关键点 / 防雷 |
|---|---|---|
| B0 | 从 §0 收口提交 pull(V13 两副本 + biz.yaml + root pom + yudao-server pom 已就位) | — |
| B1 | `cp -r game-module-project game-module-biz`,包/artifactId 改 `biz` | 包名 `cn.wanxiang.game.module.biz.*` |
| B2 | 改 `ApiConstants.java`:`NAME="biz-server"`、`PREFIX=…+"/biz"`;`ErrorCodeConstants.java` 段 `1_110` | ⚠️ R8 同 community |
| B3 | 写 `BizLeadStatusEnum.java`(单线性五态 + `of`/`canTransit`,§6.6 范本)+ 错误码(`BIZ_LEAD_NOT_EXISTS`/`BIZ_LEAD_STATUS_ILLEGAL_TRANSITION`/`BIZ_DEMO_NOT_READY`) | 合法流转矩阵:仅 `current→current+1` 单跳 |
| B4 | 写四表 DO + Mapper + Convert(§4.2 DDL 对应) | ⚠️ 客户侧查询 Mapper 强制以 `getLoginUserId()`=`customer_user_id` 作 WHERE 谓词(范本 `ProjectMapper.selectMyPage`,黄金模块**无** @DataPermission 可抄,勿假定框架已隔离);克隆后清理 biz-server pom 中 project 发布编排专用、biz 不依赖的跨模块 -api(compliance-api/feed-api/system PlayerApi 等),**保留 runtime-api**(demo 取包用,project-server pom :44 已携带、克隆即继承);DO 上 `@KeySequence` 名随表改(如 `game_biz_lead_seq`)或删除(本工程恒 MySQL,该注解被忽略),`@TableName` 改对应新表名 |
| B5 | 写 `service/lead/BizLeadServiceImpl.java`:提单/报价/`advanceStatus`(写前 `canTransit` 校验,→3 额外 `validateDemoPreviewable`,§6.6) | 流转校验内联,**不引 Flowable** |
| B6 | `validateDemoPreviewable`:同进程注入 `RuntimePackageApi`(`@Primary` 本地实现)调 `getStatus(versionId)` 判 demo 可预览(§6.7 负路径) | ⚠️ demo 取包失败降级,不透传 500 |
| B7 | 写 `controller/app/biz/`(7 个客户端点,§4.5)+ `controller/admin/biz/`(7 个 admin 端点,`@PreAuthorize("@ss.hasPermission('biz:...')")`) | 端前缀框架自动加 |
| B8 | 写 `BizTemplateService`:返 aigc 4 可玩模板(clicker/merge/idle/tycoon);dodge/runner/match 仅契约未实现**不返** | §6.7 |
| B9 | 权限点 SQL 登记(`system_menu`,§7.4)+ Nacos 配置(§7.3)+ 单测(`BizLeadServiceImplTest`:流转合法/非法/→3 无 demo 拒) | mock RuntimePackageApi.getStatus 测负路径 |
| B10 | mini-desktop 工程门 + staging 链路②实测(§9.2/§9.3)+ 客户/admin UI 入口走查跟进项(§9.4) | 「编译过≠可验收」 |
---
## 6. 关键实现细节(业务逻辑 + 接线)
### 6.1 community 通知编排(T-CMU-10)
四类消息(type 1系统/2公告/3审核/4收益)统一经 `notify` service:路由 type → 组装 title/content(审核拒绝含 reason)→ **幂等去重 `(user_id,type,biz_ref)`**(重复投递不重复落库)→ `mapper.insert(MessageDO)`。
### 6.2 community 站内信(T-CMU-06)
`AppCommunityMessageController` 5 端点:列表(按 type/readStatus 过滤、cursor 分页)/ 标记已读(`read_status=1`+`read_time=now`)/ 未读数(按 type 分组 count)/ 等级查询 / 奖励记录列表。**归属隔离=Mapper 查询强制以 `SecurityFrameworkUtils.getLoginUserId()` 作 `user_id` WHERE 谓词**(范本 `ProjectMapper.selectMyPage`「强制按当前用户过滤」,黄金模块**无** @DataPermission;标记已读须先校验该消息 `user_id==当前登录 id` 再落 read,防越权改他人消息已读态)。
### 6.3 community 等级引擎(T-CMU-11)
`level` service:`notifyProjectPublished` 触发 → **原子自增** `published_count`(§6.4)→ 同事务内判 `published_count == publish-threshold`(默认 3)→ 达标且 `uk_creator_milestone` 不存在则插一条 `game_community_reward`(`milestone=publish_3`、`status=0` 已触发待发放、`reward_type`/`reward_amount` 按配置)。**本波终态=触发可观测+本地记账,不跨模块发放。**
### 6.4 P-INC-01 计数权威源(方案 a 落地,§0 单 agent 已拍)
- **计数权威源 = project 发布成功**(非 feed 互动回流)。实查 ProjectApi **无** `countPublishedByCreator` 方法(只有 getCreatorUserId/getCurrentVersionId/getStatus/getFeedMeta/createProject/unlistIfPublished),故方案 b 需 project 新增端点;**取方案 a = project 发布成功后同进程 notify community 计数**(上游单点改动、与三上游 notify 同范式)。
- **⚠️ 计数交付口径 = 「尽力计数」(R-high 对齐,承认 at-most-once 边界)**:方案 a 的 notify 在事务提交后调用且 try-catch 吞异常(§6.5 挂点2),本质是 **at-most-once 投递**——notify 因瞬时故障/重启丢一次,`published_count` 不会再被补发(project 侧无重放、ProjectApi 无 `countPublishedByCreator` 兜底真相源)。因此本波**明确把 `published_count` 定为「尽力计数」**:允许极端情况下少计/漏触发里程碑,不宣称「绝对权威强一致」(与 reward 的 `uk_creator_milestone` 强幂等是两件事——后者只保证「到达阈值后只发一次」,不保证「计数一定到得了阈值」)。**M4 真实记账时**由 project 补 `countPublishedByCreator` 只读端点 + community 读时对账重建,届时升为可对账权威源(§10.2 ①资金流债已含此项)。AC-CMU-5 据此订正为「计数到达阈值即触发」(§9.1)。
- **并发安全(recon 必落项)**:`notifyProjectPublished` 并发到达同一 creator 时,`published_count` 累加**须 DB 原子 `UPDATE … SET published_count=published_count+1 WHERE creator_user_id=?`**(Mapper default 方法),避免读改写丢更新导致计数偏少漏触发里程碑;累加后**同事务**判 `count==threshold` 才插 reward,`uk_creator_milestone` 兜底幂等(重复发布不重触发)。
- **首次初始化**:`game_community_level` 一创作者一条(`uk_creator`),首次 notify 时 `INSERT … ON DUPLICATE KEY` 或先查后插(幂等)。
### 6.5 三上游 notify 挂点接线(事务提交后 + try-catch 吞异常)
> **通用规则**:三上游写方法本身均 `@Transactional`,notify 必在本地事务**提交后**调用(外层 caller 或循环内已提交分支),避免通知失败回滚业务。**统一 `try-catch` 包裹、仅记 error log**,不得让通知失败中断业务/结算循环。
- **挂点1 P-NTF-02(aigc)**:`DifyCallbackServiceImpl.handleCallback`(亲核 `:50-71` `return txService.handleCallbackTx(reqVO);`,内层 `@Transactional` 三表写入链于此提交;成功回填动作=内层 `DifyCallbackTxService.handleCallbackTx` 步骤⑦调 `aigcTaskService.completeWithVersion(taskId, versionId)`,**不是** `DifyCallbackService` 的「成功路径」——勿误引)。
- **⚠️ 三处硬约束(R-blocker,照字面无法落地,必须按下方处方)**:
1. **`handleCallbackTx` 返回值无法判别真成功**:实查内层 succeeded(:204)/ config_invalid 业务性失败(:166)/ failed(:155)/ 幂等短路(:122)**全部 `return Boolean.TRUE`**,故 `result==TRUE` **不能**作为 succeeded 判据(照搬会对生成失败/重复回调误发「生成完成」)。
2. **外层作用域无 gameId/versionId/creatorUserId**:`DifyCallbackReqVO` 字段仅 `traceId/status/templateId/gameConfig/assets/qualityScore/failureReason`(**无三者**);`versionId` 是内层事务步骤④新建的局部变量、不随 `Boolean` 返回;`gameId/creatorUserId` 是内层经 `aigcTaskMapper.selectByTraceId` 反查的 `task` 字段。三个 notify 实参在外层均无来源。
3. **不走 `projectApi.getCreatorUserId(gameId)`**:该路径前提 `gameId` 本身在外层不可达;且 `AigcTaskDO`(实查 :37/:41/:45)已自带 `creatorUserId/gameId/versionId` 三字段,回查任务一次即全得,更直接。
- **可落地处方(实查 `selectByTraceId` 已存在、`AigcTaskDO` 三字段齐备,aigc 自有 `AigcTaskMapper` 可直接注入)**:内层事务提交后,在外层 `handleCallback` 末尾按 traceId 回查任务,仅真实成功终态才发:
```java
Boolean result = txService.handleCallbackTx(reqVO); // 内层 @Transactional 已提交
// 提交后回查任务:仅 SUCCEEDED 且 versionId 已回填 = 真实成功终态,
// 天然排除 config_invalid/failed(status=FAILED)与幂等短路无新版本场景
try {
AigcTaskDO task = aigcTaskMapper.selectByTraceId(reqVO.getTraceId());
if (task != null
&& Objects.equals(task.getStatus(), AigcTaskStatusEnum.SUCCEEDED.getStatus())
&& task.getVersionId() != null) {
communityNotifyApi.notifyGenerateDone(task.getCreatorUserId(), task.getGameId(), task.getVersionId());
}
} catch (Exception e) { log.error("[handleCallback] 生成完成通知失败(不回滚业务)traceId={}", reqVO.getTraceId(), e); }
return result;
```
> 注:`notifyGenerateDone` 的触发判定**不是** `result==TRUE`,而是「回查任务为 SUCCEEDED 终态且 versionId 已回填」(§4.3 签名注释同步修正)。aigc `-server` 已依赖 project/runtime `-api`,本处只新增对 community-api 的依赖;`AigcTaskMapper`/`AigcTaskStatusEnum` 为 aigc 自有,直接注入/引用。
- **挂点2 P-NTF-03 + P-INC-01(project)**:`ProjectServiceImpl.reviewProject`(亲核 `:206-242` `@Transactional`,落 review_record + 状态机 +(APPROVE)发布编排 :240)。notify **不可放方法内**(会被回滚牵连)。真实位置=**外层 caller** `AdminProjectController.reviewProject`(亲核 `:55-59`,`projectService.reviewProject(reviewReqVO, reviewerUserId)`)。
- **⚠️ 作用域处方(R-medium,caller 侧 `creatorUserId`/`isApprove` 均不在原作用域,须显式取得)**:实查 `AdminProjectController.reviewProject` 方法体只有 `reviewReqVO`(字段 `gameId/versionId/decision/reason`,**无 creatorUserId**)与 `reviewerUserId`;`isApprove` 概念仅存在于 `ProjectServiceImpl` 内部(:213),控制器取不到。caller 须自行派生(`AdminProjectController` 增注入 `ProjectApi`,project-server 已依赖 project-api,自调 `@Primary` ProjectApiImpl 可达):
```java
projectService.reviewProject(reviewReqVO, reviewerUserId); // service @Transactional 已提交
try {
Long creatorUserId = projectApi.getCreatorUserId(reviewReqVO.getGameId()).getCheckedData();
if (creatorUserId != null) { // 游戏不存在/已删时 null,跳过 notify
Integer decision = reviewReqVO.getDecision();
boolean isApprove = Objects.equals(decision, ReviewDecisionEnum.APPROVE.getDecision());
// ★ 仅对「通过(1)/拒绝(2)」发审核结果通知;下架(3, 含封禁联动)本波不发(消除未定义文案分支)
if (isApprove || Objects.equals(decision, ReviewDecisionEnum.REJECT.getDecision())) {
communityNotifyApi.notifyReviewResult(creatorUserId, reviewReqVO.getGameId(), decision, reviewReqVO.getReason());
}
if (isApprove) { communityNotifyApi.notifyProjectPublished(creatorUserId, reviewReqVO.getGameId()); }
}
} catch (Exception e) { log.error("[reviewProject] 审核/发布通知失败(不回滚审核)gameId={}", reviewReqVO.getGameId(), e); }
```
- ⚠️ **owner 钉准(评审版 §6.2 注 / recon `community-design` risk)**:Doc C 标 P-NTF-03 辅技术功能=compliance「审核状态机事件源」,但**实查审核结果落库点是 project 侧 `ProjectServiceImpl.reviewProject`**(写 review_record + 状态机),compliance `ComplianceGateServiceImpl.evaluate` 只做 Gate 二态不碰 project 审核态。故 notify 挂 **project.reviewProject 外层 caller,不挂 compliance**(否则 reason 取不到)。**该改挂与「唯一设计依据」评审版 §2.1/§2.2/AC-CMU-3 的 compliance 上游源矛盾,已回流评审版勘误#1(§11)订正——验收以 project 挂点为准。**
- ⚠️ **UNLIST 第二调用路径漏通知(R-medium,已知不通知项)**:`ProjectApi.unlistIfPublished`(`ProjectServiceImpl:282-297`)以 `SYSTEM_REVIEWER_ID` **直接调 service 层 `reviewProject(reqVO, SYSTEM_REVIEWER_ID)`(:295)绕过 AdminProjectController**,供 compliance 封禁联动调用。本波 notify 钩子只在 controller,故**经封禁联动/系统下架路径一律不发通知**——属本波**非 P0 的已知不通知项**(下架通知本就不在 community 5 P0 内),不补钩子(补则须下沉 service 用 `TransactionSynchronization.afterCommit`,超本波边界)。
- ⚠️ **published_count 计数幂等口径(R-high,承认边界)**:`notifyProjectPublished` 仅在 `isApprove` 分支调用,而 `reviewProject` 受状态机「仅审核中(REVIEWING)可通过」约束(再审被挡死),故正常每款游戏只产生一次 approve→一次计数。**但 P-INC-01 计数权威源建立在「事务提交后 notify + try-catch 吞异常」=at-most-once 投递上**,存在两类已知风险:① notify 因瞬时故障/重启丢失 → 该次发布永久少计;② framework 重试/前端重复提交致同一 gameId 二次 approve 通知 → 计数偏大。`uk_creator_milestone` 只防「重复发钱」、不防「count 本身偏差」。**本波取「尽力计数」口径**(见 §6.4 收紧 + AC-CMU-5 订正),不引入按 gameId 的计数级幂等表(属 M4 真实记账时统一对账范畴)。
- **挂点3 P-NTF-05(trade)**:`SettlementServiceImpl.settle`(亲核 :55-89 循环内 `if (firstRecord) { newlyRecorded++; }` 分支;`recordIncome` 为 `@Transactional` 单条已提交,循环体非事务)。
- **⚠️ `net` 是幽灵变量(R-medium,照字面编译不过)**:实查 settle 循环作用域只有 `revenue`(`AdRevenueRespDTO`,含 `getRevenueAmount()`=gross 毛额 / `getCreatorUserId()` / `getId()`)与 `creatorShare`(`@Value("${trade.creator-share:0.80}")`);`net` 在 `IncomeServiceImpl.recordIncome` **内部**由 `calcNet(gross, shareRate)` 算(`BigDecimal.valueOf(gross).multiply(shareRate).setScale(0, DOWN)`,:86-89),且 `recordIncome` 返回 `boolean`(**不返 net**)。settle 局部无 `net` 这个名字。
- **可落地处方(二选一,钉准取 a 现算——口径与 recordIncome 落账完全一致、零跨改)**:
- **(a) settle 内现算 net**(推荐):`firstRecord` 分支内先 `long net = BigDecimal.valueOf(revenue.getRevenueAmount()).multiply(creatorShare).setScale(0, java.math.RoundingMode.DOWN).longValue();`(与 `IncomeServiceImpl.calcNet` 同公式同舍入,保证通知净额=落账净额),再 `try { communityNotifyApi.notifyIncomeChanged(revenue.getCreatorUserId(), TradeIncomeSourceEnum.AD.getSource(), String.valueOf(revenue.getId()), net); } catch (Exception e) { log.error("[settle] 收益变动通知失败(不中断结算循环)revenueId={}", revenue.getId(), e); }`(仅首次真实入账才通知,幂等复跑不重发)。
- (b) 改 `recordIncome` 返回入账净额(或入账 DO 含 net),settle 据返回值传——但 `recordIncome` 返回值语义(首次入账 boolean)被结算回标逻辑依赖,改签名需回归 trade 现有调用方,故不取。
- 访问器核实:`TradeIncomeSourceEnum.AD`(实查 :22 `AD(1,"广告")`,`@Getter` 于字段 `source`)访问器为 `getSource()`(文档用法正确)。
- ⚠️ **事务边界 nuance(recon `community-design` risk)**:settle 是定时任务、循环体非事务、内层 `recordIncome` 单条已提交;`firstRecord` 分支调 notify 满足「入账已提交」,但若后续条目或回标失败,已发 notify 无法回滚——评审版接受此口径(通知失败不回滚业务、反之亦然),故 notify 异常**须 try-catch 吞掉 + 告警日志**,不得中断结算循环。
### 6.6 biz 轻量状态机(范本 project ProjectStatusEnum/ProjectServiceImpl)
```java
// game-module-biz-api/.../enums/BizLeadStatusEnum.java
// 合法流转:0待跟进→1已报价→2制作中→3待验收→4已交付(仅相邻正向单跳,不可跳级/回退)
// 非法流转由 BizLeadService 写前拒(抛 BIZ_LEAD_STATUS_ILLEGAL_TRANSITION)
// 硬规则:→3待验收 必须已有可玩 demo(demo_version_id 非空且 runtime 取包可预览),否则抛 BIZ_DEMO_NOT_READY
@Getter @AllArgsConstructor
public enum BizLeadStatusEnum {
PENDING(0, "待跟进"), QUOTED(1, "已报价"), PRODUCING(2, "制作中"), ACCEPTING(3, "待验收"), DELIVERED(4, "已交付");
private final Integer status; private final String name;
public static BizLeadStatusEnum of(Integer status){ return Arrays.stream(values()).filter(e->Objects.equals(e.status,status)).findFirst().orElse(null); }
/** 合法流转矩阵:仅允许 current→current+1 单跳(单线性,5 态 4 条边) */
public static boolean canTransit(Integer from, Integer to){ return from!=null && to!=null && to == from + 1 && of(from)!=null && of(to)!=null; }
}
```
```java
// game-module-biz-server/.../service/lead/BizLeadServiceImpl.java(流转校验骨架,范本 ProjectServiceImpl:207 同款事务边界)
// ★ 必须 @Transactional:两写(改 lead.status + insert progress 进度行)须原子,否则崩溃会留下
// 「状态已翻但时间线缺该节点行」或反之的不一致,看板与状态机错位(事务边界铁律 §3 红线)
@Transactional(rollbackFor = Exception.class)
public void advanceStatus(Long leadId, Integer toStatus, Long operatorUserId){
BizLeadDO lead = bizLeadMapper.selectById(leadId);
if (lead == null) throw exception(BIZ_LEAD_NOT_EXISTS);
Integer from = lead.getStatus();
if (!BizLeadStatusEnum.canTransit(from, toStatus)) throw exception(BIZ_LEAD_STATUS_ILLEGAL_TRANSITION); // 写前拒非法流转
// ★ →3待验收 守门:必须有可玩 demo(复用 runtime 取包判定,无产物/编译失败/versionId 缺失即拒)
if (Objects.equals(toStatus, BizLeadStatusEnum.ACCEPTING.getStatus())) { validateDemoPreviewable(lead.getDemoVersionId()); }
BizLeadDO update = new BizLeadDO(); update.setId(leadId); update.setStatus(toStatus); bizLeadMapper.updateById(update);
// 落进度工单(看板时间线);若由非 Web 线程/系统身份触发,入口须 LoginUser(id=0)+finally clearContext(否则 updater NOT NULL 拒绝 500)
BizProgressDO p = new BizProgressDO(); p.setLeadId(leadId); p.setFromStatus(from); p.setToStatus(toStatus); p.setOperatorUserId(operatorUserId); bizProgressMapper.insert(p);
}
```
> **confirm 端点跨表原子性(R-medium,必规约)**:客户 `POST /app-api/biz/acceptances/{leadId}/confirm`(§4.5)涉及**两表写 + 状态机 3→4**——置 `game_biz_acceptance.confirm_status=1`+`confirmed_time` **与** `advanceStatus(leadId, 4, ...)`(推进 lead 3→4 + 落 progress 行)**必须在同一 `@Transactional(rollbackFor=Exception.class)` 内**,任一失败整体回滚,杜绝「已确认验收但 lead 未推进到已交付」的跨表不一致。前置约束:`canTransit` 仅允许相邻正向单跳,confirm 走 3→4 合法,但**前置态须确为 3(待验收)**,否则 `canTransit(非3, 4)` 抛 `BIZ_LEAD_STATUS_ILLEGAL_TRANSITION`——端点文案须说明「仅待验收态可确认」。
### 6.7 biz demo 预览取包接线(复用 runtime 双路由 + 负路径降级)
- **模板列表 P-BIZ-02**:`BizTemplateService` 返 aigc **4 可玩模板**(clicker/merge/idle/tycoon,`contracts/templates/` 已有 runtime 真实实现);dodge/runner/match 仅契约落盘未实现,**不返**。
- **demo 取包**:复用 runtime 双路由 `GET /app-api/runtime/package/{versionId}`(清单 meta,biz 看板「是否可预览」走此条)+ `/manifest`(原始 JSON)。
- **`validateDemoPreviewable(demoVersionId)`(→3 守门 + 负路径)**:同进程注入 `RuntimePackageApi`(`@Primary` 本地实现)调 `getStatus(versionId)`;判定口径=`status∈{0预览就绪,1已发布}` 即可预览,`null`=无就绪包=不可预览。**负路径(R1 必验)**:`demoVersionId` 缺失/非法、无编译产物(runtime 抛 `RUNTIME_PACKAGE_NOT_READY` 1-102-001-001)、编译失败时,**抛 `BIZ_DEMO_NOT_READY`**(看板降级显示「该模板暂不可预览」),**不透传 500**;状态机**不允许在无可玩 demo 时推进到「待验收」**。
- ⚠️ **预览鉴权口径缺口(R-low 拆分表述,勿合并为单一警告)**:两条路径不同,须分别处理:
- **「是否可预览」判定(`validateDemoPreviewable` 走 `RuntimePackageApi.getStatus`)= owner-agnostic、不受 owner 校验影响**。实查 `RuntimePackageApi.getStatus`(:60-63)委托 `RuntimePackageService` **纯只查 status 单列、不做 owner/scene 校验**,故此判定路径**不会**撞 `RUNTIME_PACKAGE_PREVIEW_NOT_OWNER`——实现 agent 勿误以为 getStatus 会被 owner 拦。
- **客户真实试玩取包(走控制器 `GET /app-api/runtime/package/{versionId}`)才有 owner+scene 门禁**:preview 场景 `userId==null` 即拒;biz 客户(`customer_user_id`)≠版本创作者(运营代建)时可能触发 `RUNTIME_PACKAGE_PREVIEW_NOT_OWNER`(1-102-001-003)。**建议**:biz demo 走 **play 场景(status=1 已发布)** 而非 preview 场景,或 biz demo 先发布到 status=1 后再供客户试玩(避免 owner 校验拦截)。**此 owner 风险仅存在于真实试玩取包路径,与上一条 getStatus 判定无关**。落地前与 runtime owner 确认此口径。
---
## 7. 配置与权限登记
### 7.1 装配点(§2.2 复述)
两处:① root `game-cloud/pom.xml` `<modules>` 注册 `<module>game-module-community</module>`+`<module>game-module-biz</module>`(line34 后);② `yudao-server/pom.xml` `<dependencies>` 注册两个 `-server` 依赖(studio-server 后)。
### 7.2 上游 -server pom 加 community-api 依赖
aigc / project / trade 三个 `-server/pom.xml` 各加 `game-module-community-api` 依赖(照 `project-server/pom.xml:34-53` 加 compliance-api/runtime-api/feed-api 的写法)。
### 7.3 Nacos 模块配置注册(对齐 add-business-module.md 步骤6)
| 配置项 | 默认值 | 用途 |
|---|---|---|
| `community.sms.enabled` | `false` | 短信发送开关(收益高优可选短信;默认关,站内信成功即 AC 达标) |
| `community.level.publish-threshold` | `3` | 新人里程碑阈值(发满 3 作品触发 reward) |
| `biz.state-machine.timeout-hours` | (评审版列)| 状态机节点超时(如需;硬编码则可跳过) |
`deploy/nacos/` 加配置项并导入,验证 Nacos 控制台可见。**若 community/biz 通知模板/阈值硬编码无模块级配置,本步可精简**(评审版未强制要求新增 Nacos 配置)。
### 7.4 biz admin 权限点登记(⚠️ recon `biz-design` 必做缺口)
`/admin-api/biz/*` 用 `@PreAuthorize("@ss.hasPermission('biz:...')")`,权限点 `biz:lead:query/create/assign/advance`、`biz:quote:create/query`、`biz:acceptance:sign` **需在 yudao `system_menu` 登记后 RBAC 才放行**——否则 admin 端点建好但管理员无权限调用(403)。本文要求:execution 落地时以 **V13 或单独菜单初始化 SQL** 登记这些权限点(范本=`project:review:query` 在 yudao 的登记方式)。
---
## 8. 边角失败路径(必落)
### 8.1 事务提交后 notify + try-catch 吞异常
见 §6.5:三上游 notify 均在事务提交后调用 + try-catch 仅记 error log,通知失败不回滚业务、不中断结算循环。
### 8.2 系统身份写防 updater NOT NULL 雷(R3,单测 mock 测不到)
> community 通知投递 / reward 记账 / biz 状态机推进若由**非 Web 线程或系统身份**触发 → 无登录态 → `updater` NOT NULL 拒绝 → 链路 500/任务卡死。**单测 mock 测不到此雷(项目记忆铁律)。**
范本(照 `EventIngestServiceImpl.injectSystemIdentityIfAnonymous`,`game-module-telemetry/.../service/event/EventIngestServiceImpl.java:187-198` + finally `:144-147`,canonical 源头=`AigcGenerateExecutor.executeWithSystemIdentity`):
```java
// CommunityNotifyApiImpl 四方法入口 + BizLeadServiceImpl 系统/异步触发入口
LoginUser loginUser = new LoginUser().setId(0L).setUserType(UserTypeEnum.ADMIN.getValue()).setTenantId(1L);
SecurityContextHolder.setContext(new SecurityContextImpl(...)); // 注入系统身份
try {
// ... 业务写(DefaultDBFieldHandler 自动填 creator/updater='0')
} finally {
SecurityContextHolder.clearContext(); // 防 Web 线程上下文泄漏
}
```
### 8.3 demo 取包失败降级
见 §6.7:`validateDemoPreviewable` 对取包失败/无产物/versionId 缺失抛 `BIZ_DEMO_NOT_READY`、看板降级「该模板暂不可预览」、状态机拒推进到待验收,**不透传 500**。
### 8.4 mock-trigger 安全边界
`/admin-api/community/notifications/{type}/mock-trigger` 须 `@Profile({"dev","staging"})`(**本仓首例**,全 game-module 无 @Profile 先例,须验证 staging profile 下 Bean 注册生效)**或** `@ConditionalOnProperty`(与现有 `AIGC_EXECUTOR_ENABLED` 灰度开关风格一致,推荐)**或**网关层关闭,**生产一律关闭**——否则成匿名伪造投递入口(安全须在可信边界 enforce,前端不够)。三者二选一钉死给最小代码范本。
### 8.5 @Primary Bean 冲突(R7)
新模块 Feign 接口 + 本地 `@Primary` 实现可能 Bean 冲突致启动失败(M1 实证 4 个 CommonApi)。`ApiImpl` 标 `@RestController @Primary`;仍冲突则给 `@FeignClient` 补 `primary=false`(修法 commit d7fac90);启动盯 `BeanDefinition` 冲突日志。
---
## 9. 验证方法(community 5 + biz 6 P0 AC + 工程门 + staging 实测 + UI 走查)
### 9.1 AC 逐条口径
**community(5 P0)**
| # | 口径 |
|---|---|
| AC-CMU-1 P-NTF-01 | 四类消息(系统/公告/审核/收益)可投递、`/app-api/community/messages` 列表可读、`{id}/read` 后 `read_status=1` 且 unread-count 递减 |
| AC-CMU-2 P-NTF-02 | aigc 生成完成后 `notifyGenerateDone` 被调→通知到达消息中心(前提:aigc 已接挂点1 或经 mock-trigger 验投递) |
| AC-CMU-3 P-NTF-03 | 审核出结果后 `notifyReviewResult` 被调→落站内信。**仅 `decision=1通过/2拒绝` 发通知**;`decision=3下架`(含封禁联动经 `unlistIfPublished` 系统身份路径)**本波不发通知**(非 P0 已知边界,caller 以 `decision∈{1,2}` 守门,§6.5 挂点2)。`decision=2拒绝`须带 reason——但实查 `ReviewReqVO.reason` 仅 `@Size(max=500)`、**无 @NotNull**,全链路不强制 reject 时 reason 非空,故 community 侧 notify service 对 `decision=2 且 reason 空`须给兜底文案(如「未提供原因」)或记 warn,避免落「拒绝原因为空」站内信(配合 P-OPN-02) |
| AC-CMU-4 P-NTF-05 | trade 结算入账后 `notifyIncomeChanged` 被调→站内信(+`community.sms.enabled=true` 时可选短信)送达 |
| AC-CMU-5 P-INC-01 | **计数到达阈值(默认 3,`community.level.publish-threshold`)即触发**:计数权威源=project 发布成功 `notifyProjectPublished` 累加 `published_count`(**尽力计数口径**——at-most-once 投递,承认丢通知会漏触发,§6.4),到达阈值后等级引擎自动写一条 `game_community_reward`(`uk_creator_milestone` 幂等,重复发布不重触发);本波终态=触发可观测+本地记账,`status=0`待发放;**记入钱包/流量包+现金真实到账、以及计数可对账重建(project 补 `countPublishedByCreator` 兜底)留 M4** |
**biz(6 P0)**
| # | 口径 |
|---|---|
| AC-BIZ-1 P-BIZ-01 | POST `/app-api/biz/leads` 提交即落 `game_biz_lead` 一行 `status=0`待跟进 + 落 `game_biz_progress` 首条 `to_status=0`。Swagger/curl 可验 |
| AC-BIZ-2 P-BIZ-02 | GET `/app-api/biz/templates` 返 aigc 4 可玩模板;选定后经 runtime 双路由预览(`/package/{versionId}` + `/manifest`)。**★负路径(R1 必验)**:versionId 缺失/非法、无编译产物、编译失败(`RUNTIME_PACKAGE_NOT_READY`)时看板降级「该模板暂不可预览」而非透传 500;`advanceStatus` 到 `toStatus=3` 时 `validateDemoPreviewable` 拒→抛 `BIZ_DEMO_NOT_READY` |
| AC-BIZ-3 P-BIZ-03 | 状态机各节点可经 `/admin-api/biz/leads/{id}/advance` 推进、`/app-api/biz/leads/{id}/progress` 取看板时间线(**后端 API 层=本波验收**)。客户侧看板(game-studio WS3)+admin 队列(WS4)=完成条件增强项,随对应工位排期走真人 UI 走查 |
| AC-BIZ-4 P-BIZ-04 | 客户「试玩+反馈+确认」三态走通——feedback 落 `game_biz_acceptance`、confirm 置 `confirm_status=1` 且状态机 3→4。签署/收款留 M4,本波线下签署+`sign-offline` 兜底 |
| AC-BIZ-5 P-BIZ-08 | 经 `/admin-api/biz/leads`(代客发起)+`/admin-api/biz/leads/{id}/quotes`(报价)Swagger/curl 可发起品牌营销定制单并完成报价(报价≠可支付订单)。运营代客发起 UI(WS4)=独立跟进项 |
| AC-BIZ-6 P-BIZ-12 | 文旅场景(`bizType=2`)可发起定制单并走验收。P0 子集=场景需求 CRUD+模板复用+专属看板必交(与 P-BIZ-01/03 同能力栈);P1「快速交付增强」列 M4 |
### 9.2 工程门精确 CLI(mini-desktop 执行,逐项可勾)
```bash
# 1) 编译(community;biz 换路径)
mvn -DskipTests clean install -pl game-cloud/game-module-community/game-module-community-server -am
# 2) 单测(继承 BaseMockitoUnitTest 绿)
# community: 投递/已读/计数累加/里程碑触发幂等;biz: BizLeadServiceImplTest 流转合法/非法/→3 无 demo 拒
# 3) 启动 yudao-server → 应用层 Flyway 自动 migrate V12/V13(两处副本字节一致校验:md5sum 全等)
# 4) Swagger 可见(返回非空且含新端点)
curl http://localhost:48080/v3/api-docs?group=community # biz 换 group=biz
# 5) doc.html 可见 community/biz 分组;Spring 启动无 @Primary Bean 冲突日志
```
### 9.3 staging 两条系统身份写链路实测(⚠️ R3 必测,单测 mock 测不到,「骨架编译过≠可验收」)
- **链路①=community 通知投递**:上游 notify(或 staging `mock-trigger`)→ 写 `game_community_message`,验**系统身份注入 `LoginUser(id=0)` 生效、`updater` 落 `'0'`、不 500**。
- **链路②=biz 状态机推进**:admin 推进定制单状态(`/admin-api/biz/leads/{id}/advance`)→ 写 `game_biz_lead`/`game_biz_progress`(含 reward 触发记账若由非 Web 线程触发),验**非 Web 线程/系统身份写 `updater` NOT NULL 不 500**。
### 9.4 UI 入口真人走查(完成条件增强项,跨工位绑定,不阻塞本波后端收口)
> 项目记忆铁律「编排器旁路/批跑 API 全绿掩盖 UI 缺陷」(`frontend-link-ui-walk-publish-fix`)——UI 走查是唯一手段。
- **community**:消息中心列表/已读/未读角标在 game-studio(WS3)真人走查(6c6g CDP 走查配方:`run_in_background` 起 chrome + `player_cdp.CdpSession`,记忆 `frontend-link-ui-walk-publish-fix`)。
- **biz**:客户侧进度看板(game-studio `/biz-orders/my-orders` 或邀请链,WS3)+ 运营代客发起/处理队列(game-admin,WS4,权限点 §7.4 登记后)真人走查。
---
## 10. 完成条件 + M4 留债 + 回滚
### 10.1 完成条件(全勾才算 done,「无验证证据不得 claim 完成」)
1. §0 单序列资源预占收口提交(V12/V13 两副本字节一致 + 错误码段 + yaml + 两处装配点),三项前置校验证据入提交说明。
2. community/biz 各自 `mvn -DskipTests clean install -pl … -am` 通过(mini-desktop)+ 单测绿。
3. 启动 yudao-server,Flyway migrate V12/V13 成功(两副本 md5 全等)+ `curl …group=community`/`group=biz` 非空含新端点 + doc.html 可见 + 无 @Primary Bean 冲突。
4. community 5 P0 + biz 6 P0 AC(§9.1)逐条满足(含 AC-BIZ-2 负路径、AC-CMU-5 幂等)。
5. **staging 两条系统身份写链路实测通过**(§9.3,`updater` 落 '0' 不 500)——**这是「骨架编译过≠真实可验收」的硬验收**。
6. UI 入口真人走查(§9.4)作为完成条件增强项跟进(绑 WS3/WS4 排期,不阻塞本波后端收口)。
7. 收口后按知识沉淀铁律回写 `.agents/skills/add-business-module.md`(107/110 升「已落地」、补 root pom 注册独立步、补「跨模块通知优先同进程 `-api` notify-push 而非 MQ」范式)+ 新踩坑,更新 `.agents/README.md`;更新 `docs/mvp/MVP进度总账.md` + 两模块 `.agent`。
### 10.2 M4 留债(两类债分离,评审版 R1)
**①资金流债(受 pay 桩 + ICP/支付进件日历闸门制约)**
| 留债 | 说明 |
|---|---|
| biz T-BIZ-08 收费 | 报价转可支付订单 → pay 进件后接 `PayOrderApi` 收款(`amount_fen` 已按「分」口径预留,与 trade 同,直接复用) |
| biz T-BIZ-03 在线电子签章 | 三方签章;本波 `game_biz_acceptance.signed_offline` 线下兜底过渡 |
| community reward 真实记账/到账 | 记入钱包/流量包+现金到账=trade 暴露 `IncentiveCreditApi` + feed 暴露流量包分配 `-api`(**当前均不存在**)后补 seam;本波只写 `game_community_reward.status=0`,M4 回写 `granted_ref`+`status=1` |
**②前端 UI 入口债(跨工位协调,可与本波后端并行,非 M4 资金流债)**
| 留债 | 工位 |
|---|---|
| 客户侧进度看板 game-studio `/biz-orders/my-orders` 或邀请链 | WS3 |
| 运营代客发起 + 处理队列 game-admin(权限点 §7.4 登记) | WS4 |
| community 消息中心 UI | WS3 |
### 10.3 回滚
- **Flyway**:已合入迁移**禁止修改**,回滚只写新补偿迁移(`V12.0.1`/`V13.0.1` DROP,两副本字节一致);CI 跑 `flyway validate` 阻断改旧。
- **代码**:两模块纯新增(克隆),回滚=root pom 撤 `<module>` + yudao-server pom 撤依赖 + 删两模块目录 + 撤三上游 notify 调用点(三处 try-catch 块)+ 撤 community.yaml/biz.yaml;对已建 11 模块**零行为变更**,回滚面收敛。
- **上游接线回滚**:三个 notify 调用点均 try-catch 独立块,注释/删除即恢复上游原行为(不影响 aigc/project/trade 既有事务)。
---
## 11. 与评审版的勘误回流登记(治理铁律:以评审版为准 → 矛盾须回流不得单方改写)
> 本文 line6 自定铁律「本文不得与评审版矛盾;执行中发现矛盾,停工上报,以评审版为准」。落地实查发现评审版 3 处结论与真实代码不符,**已回流评审版勘误段(HJ-WAVE4-001 顶部「⚠️ 勘误 ERRATA」+ §2.1/§2.2/AC-CMU-3 inline 标注)**,本文相应处方与之一致,验收口径唯一。
| 勘误# | 评审版原结论 | 实查订正 | 本文落点 |
|---|---|---|---|
| 勘误#1 | P-NTF-03 上游源 = **compliance**「审核状态机事件源」(§2.1/§2.2 Mermaid + P0 表 + AC-CMU-3) | 审核态权威在 **project** `ProjectServiceImpl.reviewProject`(写 review_record + 状态机 + reason);compliance 不持审核态(Gate 二态 + 封禁)。notify 挂 project `AdminProjectController.reviewProject` 外层 caller | §6.5 挂点2(含作用域处方)+ §2.4 挂点表 |
| 勘误#2 | (评审版未展开 decision 态数)approve/reject 两态 | `ReviewDecisionEnum` 三态(1通过/2拒绝/3下架);本波仅 1/2 发通知,3下架(含 `unlistIfPublished` 系统身份第二路径)不发 | §4.3 签名注释 + §6.5 挂点2 + AC-CMU-3 |
| 勘误#3 | P-INC-01 计数权威源(隐含强一致语义) | at-most-once 投递→「尽力计数」,丢通知会漏触发;可对账重建留 M4 | §6.4 + AC-CMU-5 |
> **未采纳项(实现 agent 已能直接定、无需回流评审版的实现细节)**:aigc 挂点1 的「succeeded 判别 + ID 取数」走提交后 `selectByTraceId` 回查(评审版未涉及代码级取数,属 execution 细节);trade 挂点3 的 `net` 现算(同上);biz advanceStatus/confirm 的 `@Transactional` 边界(同上)。这些是 execution 版职责内的可落地化,不构成与评审版的结论矛盾,故只在本文钉准、不回流。
---
*证据边界(2026-06-11 亲核 dev/2.0.0):root pom `<modules>` :10-35(末 game-module-studio :34);yudao-server pom `<dependencies>` :23-…(末 game-module 依赖 studio-server :80);Flyway 两副本 ceiling=V11(V1–V11 各两副本,最高 V11.0.0__create_passport_player_invite.sql;V10 golden-loop + V11 passport 均在两处),V12/V13 文件名占用 `ls` exit=2(未占用);错误码段 `grep '1_107\|1_110' game-cloud/` 返 0;community.yaml/biz.yaml 不存在(exit=2);ProjectApi 现有方法=getCreatorUserId/getCurrentVersionId/getStatus/getFeedMeta/createProject/unlistIfPublished(**无** countPublishedByCreator,故 P-INC-01 取方案 a);aigc `DifyCallbackServiceImpl.handleCallback` 内 `return txService.handleCallbackTx(reqVO)`(内层 @Transactional 三表写入链);project `AdminProjectController.reviewProject:55-57` 调 `projectService.reviewProject`(service @Transactional :207,APPROVE 同事务发布编排 :237-239);trade `SettlementServiceImpl.settle` 循环 `firstRecord`/`newlyRecorded++` 分支(recordIncome @Transactional 单条提交、循环体非事务)。设计依据=评审版 HJ-WAVE4-001 + 两轮评审纪要 + 四份深度 recon(含可直接入文 DDL/端点/挂点 artifacts)。行号漂移以语义定位为准。本文为 execution 设计产出,未实跑 mvn/Flyway/单测——落地后须经 §9 工程门 + staging 两链路实测 + UI 走查验证方可 claim 完成(评审版 R10 铁律)。*

View File

@ -0,0 +1,100 @@
# Wave4(community + biz)评审版 spec · 两轮对抗评审纪要
> 关联 spec:[`2026-06-11-Wave4-community-biz-review.md`](./2026-06-11-Wave4-community-biz-review.md)(编号 HJ-WAVE4-001)
> 类型:评审纪要(findings 列表 + 逐条处置存档)| 2026-06-11
> 范围:第一轮(R1,24 条)+ 第二轮(R2,6 条)对抗评审 findings 全量列出与逐条处置。
> 结论:**两轮全部接受或部分接受/重定向,无「拒绝」条目**;spec 已据此定稿(状态=已定稿待创始人拍板)。
> 说明:spec 正文末已内嵌「评审整改纪要(R1)」「评审整改纪要(R2)」两段为 spec 自洽;本文是**独立存档**,把两轮原始 findings 与处置归并一处,供审计与后续波次复用。
---
## 0. 两轮总览
| 轮次 | findings 数 | 严重度分布 | 处置结论 | 最大变更 |
|---|---|---|---|---|
| **R1** | 24(含合并近重复) | blocker×1 / high×多 / medium×多 / low×多 | 全接受/部分接受/重定向,无拒绝 | community 触达机制定型=同进程 `-api` notify-push(取代「事件驱动/MQ」含糊口径),消除 MQ/事件契约整组前置依赖 |
| **R2** | 6 | medium×2 / low×4 | 全接受(含 1 条事实订正后仍采纳改进诉求),无拒绝 | 校正 6 处精度/口径 + 新增 §0.1 三项战略决策置顶(D-A biz 形态 / D-B community 优先级 / D-C ip seam 是否本波接) |
**两轮共性**:findings 多为「口径不一致 / 落点不精确 / 缺中央登记 / 范式引用错」类精度问题,无方向性推翻——印证「克隆零创新」范式判断成立。R1/R2 各有 1 条触及**事实订正**(R1:无 `ProjectStateService.java`;R2:M5 实有定义),均以「采纳问题但按实情调整解法」处置。
---
## 1. 第一轮(R1)findings 逐条处置
> 处置口径:**接受**=已改对应节;**部分接受/重定向**=采纳问题但解法按实查证据调整;**拒绝**=保留原口径(本轮无)。多条近重复 finding 合并处置(标 ⊃,主条吸收子条)。
### 1.1 贯穿性裁决(一处定死多条收敛)
本轮多条 finding(blocker events.schema.json / high「事件基建不存在」/ R1 风险项 / 多条 medium 触发机制)实为**同一未决架构点的不同侧面**:community 用什么机制触达。R1 据实查证据**一次定死 = 同进程 `-api` notify-push**(spec §3.3)。连锁后果:
- (a) community **不走 events.schema.json**,故 blocker「先在 events.schema.json 注册三事件」的前置门禁**不再需要**——底层关切(只改契约不改枚举=静默 rejected)以「保留口径」写入 §3.3 备未来 MQ 路线;
- (b) 上游不发域事件,改为新增 3 个 notify 调用点,工作量画像在 §3.3 表与 §4 显式登记;
- (c) INC-01 计数权威源改 project、记账留 M4,消除孤儿子链路。
### 1.2 R1 逐条表
| # | Finding(severity|定位) | 处置 | 改了哪 / 理由 |
|---|---|---|---|
| R1-1 | 事件基建整体不存在,应框为「机制未建+选型未定」(high|§2.2/§3.3/R1) ⊃ blocker(events.schema.json 三事件未注册) ⊃ medium(事件消费手工触发形态未明) | **接受(核心整改)** | §0/§2.1/§2.2/§3.3 全面重写:机制定型为同进程 `-api` notify-push;§3.3 新增四维对比表+工作量画像+staging `mock-trigger` 端点;R1 风险行改「已缓解」。blocker 关切以「保留口径」并入 §3.3,不再设 events.schema.json 前置门禁 |
| R1-2 | P-INC-01 触发+记账依赖 trade 发放/feed 流量是半截孤儿链路(medium|§2.2/AC-CMU-5) ⊃ high(feed 流量包/trade 发放无 -api 契约) ⊃ medium(INC-01 计数权威源歧义 A/B) | **接受** | §2.2 新增「P-INC-01 落地形态」:计数权威源**定为 project 发布成功**(二选一 project notify / `ProjectApi.countPublishedByCreator`),**不依赖 feed 计数 seam**;「记入钱包/流量包」本波只写自有 reward 触发表,跨模块写留 M4;AC-CMU-5 收紧 |
| R1-3 | AC-BIZ-3/5 硬依赖 game-admin UI 而 admin 端零视图(medium|§6.2/R5) ⊃ high(AC-BIZ-5 依赖 WS4 未排期 UI 违反独立可交付) | **接受** | §6.2 加「UI 落点与跨工位」说明 + AC-BIZ-3/5 拆两层(后端 API 本波独立验收 / UI 入口列增强项绑 WS3·WS4 走 UI 走查);§1 非目标补「两类债分开记账」;§7-6 同步 |
| R1-4 | biz 模板预览只描述 happy path,缺取包失败影子路径(medium|§2.3/AC-BIZ-2) | **接受** | §2.3 加「B-02 模板预览影子路径」(无产物/编译失败/versionId 缺失降级 + 状态机不许无 demo 推进);AC-BIZ-2 补负路径必验 |
| R1-5 | Flyway V12/V13、错误码、yaml 文件名缺中央预占登记动作(low|§4/R6/R8/§8) | **接受** | §8 新增「§0 全局单序列资源预占登记」为 execution 开篇第一节(字节一致 + 前置校验 + 单 agent 收口) |
| R1-6 | 异步写链路 staging 实测未点名 community 通知/biz 状态机两条(low|§6.3/R3) | **接受** | §6.3 把两条异步/系统身份写链路逐条点名为必测项;R3 同步具体化 |
| R1-7 | §2.4「唯一公共装配点」与 §4 blast radius 不一致,漏 root 聚合 pom(high|§2.4/§4) ⊃ high(克隆 pom 注册点不清,未明 game-cloud vs yudao-server) | **接受** | §2.4 改「公共装配点=两处」(① root `game-cloud/pom.xml` 注册 `<module>` ② `yudao-server/pom.xml` 注册 `-server` 依赖);§4 首行同步;§8 把 root pom 注册列独立一步 |
| R1-8 | P0 计数术语含糊,未陈述 Wave4 总新增 P0 数(high|§2.3/§7-4) | **接受** | §0 与 §2.3 明确「Wave4 总交付=11 P0=community 5 + biz 6;P-BIZ-11 经 Doc C 划归 compliance(compliance +1)」;§7-4 同步 |
| R1-9 | BIZ-02 轻量状态机「替代 BPM」是缩水还是等价替代表述不清(high|§2.3/R2) | **接受** | §3.1 新增「轻量状态机 vs BPM 等价替代而非缩水」澄清段(本波 6 P0 仅需单线性五态、多线流程留 M4+Flowable);R2 风险行重写 |
| R1-10 | Flyway V12/V13 三处一致性未验证(medium|§4/R6/§6 脚注) | **接受** | §6 脚注记两处 ceiling=V11 一致;§8 §0 补 grep 校验 + 字节一致校验 |
| R1-11 | BIZ-03 客户端入口形态未明(medium|§2.3/§6.2) | **接受** | §2.3 表 P-BIZ-03 + AC-BIZ-3 明确客户侧入口=game-studio `/biz-orders/my-orders`(WS3)或邀请链 |
| R1-12 | §1 非目标(资金流 M4)与 R5(UI 入口缺失)混为一个债项(medium|§1/R5/§7) | **接受** | §1 非目标新增「两类留债分开记账」(①资金流债→M4 ②前端 UI 入口债→跨工位并行);§8 M4 留债段同结构分离 |
| R1-13 | feed↔community 计数回流机制不清(反写/读表/异步事件)(medium|§4) | **接受(重定向)** | 采纳「机制须写死」,但计数权威源改 project 后**此 seam 取消**:§4 加澄清「feed.yaml line84 指归属权非反写」,旧「feed 信号→community 计数串行依赖」项删除 |
| R1-14 | §6.3 工程门缺 exact CLI(medium|§6.3) | **接受** | §6.3 补三组精确 CLI(`mvn -DskipTests clean install -pl ... -am` / `curl .../v3/api-docs?group=community` / staging 两链路实测) |
| R1-15 | §8 遗漏 Nacos 模块配置注册落点(low|§8) | **接受** | §8 补 Nacos 配置注册条(community.sms.enabled / community.level.publish-threshold / biz.state-machine.timeout-hours + deploy/nacos 导入验证) |
| R1-16 | P-BIZ-12 P0 子集「可下单+进度看板」模糊、与 01/03 是否重叠(low|§2.3/§7) | **接受** | §2.3 表 P-BIZ-12 + AC-BIZ-6 + §7-5 收紧为「场景需求 CRUD + 模板复用 + 专属看板(与 01/03 同能力栈、无额外定制)」;P1 列 M4 |
| R1-17 | T-PRJ-06 轻量状态机引用不精确,缺代码路径(low|§2.4/§3.1/§8) | **接受(含修正事实错误)** | 实查**无 `ProjectStateService.java`**——全文将「复用 T-PRJ-06/ProjectStateService」更正为「复用 project 范式=`ProjectStatusEnum`(流转判定)+ `ProjectServiceImpl`(写前流转校验)」(§2.1/§2.3 图/§2.4/§8)。原 finding 建议落点亦不存在,按实情改 |
> **R1 小结**:24 条(合并后呈 17 行,含 ⊃ 吸收的近重复子条)全部接受/重定向。3 条(R1-13 feed↔community 计数机制、R1-1 内 blocker events.schema.json 前置门禁、R1-17 T-PRJ-06 引用)采纳问题本身但**解法与原 suggestion 不同**——实查证据显示原 suggestion 落点(feed 反写 / events.schema.json 路线 / ProjectStateService.java)与本仓现状或更优范式不符。
---
## 2. 第二轮(R2)findings 逐条处置
> 输入:R2 findings JSON(6 条:medium×2 + low×4)。**每条均先实查仓库核实再处置**,逐条证据见 §3「R2 实查证据」。
| # | Finding(severity|定位) | 实查核实结论 | 处置 | 改了哪 |
|---|---|---|---|---|
| R2-1 | §8 §0 预占校验 `grep -r 'V12\|V13'` 不返 0(会命中 V11 让位声明注释 2 行),实现者开篇第一步被自身注释绊住(low|§8 §0 行334) | **属实**:实测返 2 行(`V11…invite.sql:13` 两处副本各一行,内容匹配非文件名匹配);`ls V12*/V13*` 实测 exit=2 空 | **接受** | §8 §0 校验改「按文件名」:`ls contracts/db-schemas/V12* … V13* …`(应空/exit≠0);显式标注「不要用 grep 内容匹配,命中仅 V11 让位声明注释属预期非占用」;错误码段仍用 `grep -rn '1_107\|1_110'`(应返 0) |
| R2-2 | `add-business-module.md` 旧版仅列「步骤5=yudao-server pom」、未把 root `game-cloud/pom.xml` `<modules>` 注册列独立步,与 spec §8「装配点=两处」口径不一致,只读 skill 者漏 root pom 注册触发 compile 失败(low|§2.4/§8/skill 行66) | **属实**:`grep 'game-cloud/pom.xml\|<modules>\|聚合 pom'` 该 skill **零命中**;步骤表开头明写「无需改 Application/yaml,只需 yudao-server/pom.xml 加依赖」 | **接受**(不改 skill 内容、加 spec 提示——回写 skill 维持「本波收口后」原计划,避免开工前动 canonical) | §2.4 加 ⚠️ 硬提示:『skill 步骤表尚未含 root pom 注册(待本波收口回写),本波克隆以本节「装配点=两处」为准,勿以旧 skill 为唯一依据』;execution 版克隆步骤开篇须复述 |
| R2-3 | biz 取包端点写 `/app-api/runtime/package/{versionId}`,真实路由带 `/manifest` 后缀;属端点精度问题不影响可行性结论(low|§2.3 B-02 行161/AC-BIZ-2 行293) | **部分属实**:实查 `AppRuntimeController` 两端点**都存在**——`GET /package/{versionId}`(line60, 清单 meta)+`GET /package/{versionId}/manifest`(line75, 原始 JSON);原稿写的是真实端点之一,但漏列 `/manifest` 那条 | **接受**(按真实双路由钉准,不止补 /manifest) | §2.3 B-02 新增「取包端点真实路由」段(两端点真实路由+用途+门禁同源);AC-BIZ-2 同步双路由;提示 execution 版端点清单两条写全 |
| R2-4 | §266 兼容性段把 events.schema.json 列为「须串行收口的公共写点」,与 §239/§322「本波零改该文件」自相矛盾(medium|§266 vs §239 vs §322) | **属实**:§239 影响面表标「本波零改/无」、§322(§7-7)标「本波不改」,§266 却列入公共写点 | **接受** | §266 改写:公共写点仅 root pom/yudao-server pom 须串行收口;events.schema.json 本波零改、无写点竞争、不在串行收口范围(显式标 R2 校正消歧义) |
| R2-5 | §7 第 2 项「community 被 M5 依赖」中 M5 未在全文定义,理由对拍板无参考且不可验证(medium|§312 待确认项 2) | **订正**:M5 **实有定义**(`mvp-scope-and-milestones.md:101`/`MVP进度总账.md:32`=MVP 全链路交付里程碑 6/27 Day15);但「被 M5 依赖」确属同义反复(M5 含全部模块) | **接受**(采纳 suggestion(b):改自洽可验证表述) | §7-2 改:以「触达机制最独立、无外部业务卡点、复用面集中、风险最低」排序;补注 M5 定义 + 说明不以「被 M5 依赖」作依据(同义反复) |
| R2-6 | P-INC-01 计数权威源「二选一」(project notify vs ProjectApi.countPublishedByCreator) 在待确认项未显式决策,execution 期或重复思考(low|§2.2/§7-2~4) | **属实**:原稿仅在 §2.2 正文列二选一,§7 无对应决策行 | **接受**(采纳 suggestion:补 §7 决策点 + 标「可由 execution §0 单 agent 直接拍 (a)」) | §7 新增第 8 项(计数权威源二选一,推荐 (a) project notify,标可单 agent 定无须创始人介入);§8 §0 同步标「单 agent 拍 (a)」 |
> **R2 顺带补强(非 finding 直接要求,同一处自洽性收口)**:
> ① 新增 spec §0.1「待创始人拍板的关键决策」汇总段,提炼 **D-A(biz 形态)/D-B(community 功能优先级)/D-C(ip seam 是否本波接)** 三项战略决策置顶供拍板;
> ② §7 表给战略项标 ⭐ 并与 §0.1 互链,区分「创始人决策项 vs execution 单 agent 可定的实现细节」;
> ③ 据实查(D5 决策「ip 寄宿 compliance」+ Wave4 仅 P-BIZ-02 沾 T-IP-09 且本波复用 aigc 模板绕开)补 **D-C 推荐「本波不接 ip seam、维持 D5 推迟」**,并入 §7 第 10 项。
---
## 3. R2 实查证据(2026-06-11 仓库核实)
| 证据 | 命令 / 文件 | 结论 |
|---|---|---|
| ① §0 预占校验口径 | `grep -r 'V12\|V13' contracts/db-schemas/ game-cloud/yudao-server/.../db/migration/` | **返 2 行**(`V11.0.0__create_passport_player_invite.sql:13` 让位声明注释,两处副本各一行),非「应全返 0」;`ls V12*/V13*` 实测 **exit=2、空**(确未占用)→ 改按文件名判定 |
| ② runtime 取包真实路由 | `AppRuntimeController.java` | 两端点:`@GetMapping("/package/{versionId}")`(line60, 返 `RuntimePackageRespVO` 清单 meta) + `@GetMapping("/package/{versionId}/manifest")`(line75, 返原始 JSON);门禁同源(同走 `getPackageManifest` 做 scene+status 权威判定)。原稿写其一、漏 `/manifest` 那条 |
| ③ skill 缺 root pom 注册步 | `.agents/skills/add-business-module.md` 步骤表(行66 起);`grep 'game-cloud/pom.xml\|<modules>\|聚合 pom'` | 步骤表仅「步骤5=yudao-server/pom.xml 加 -server 依赖」,开头明写「无需改 Application/yaml」;root pom 注册 **零命中** → skill 待本波收口回写,本波以 spec §2.4「装配点=两处」为准 |
| ④ M5 定义 | `.agents/knowledge/mvp-scope-and-milestones.md:101` / `docs/mvp/MVP进度总账.md:32` | M5=「MVP 交付」全链路交付里程碑(6/27 Day15)。M5 **有定义**,但「community 被 M5 依赖」属同义反复(M5 含全部模块)→ 改独立性/风险理由排序 |
| ⑤ ip seam(D-C 决策证据) | `docs/mvp/MVP进度总账.md:53`/`:95`;`需求模块映射.md:207` | D5 决策「ip 不独立建、seam 寄宿 compliance」、line95 标推迟 TODO;Wave4 11 P0 中**仅 P-BIZ-02 协同含 `T-IP-09 模板市场`**,community 5 P0 零 ip 关联;本波 P-BIZ-02 复用 aigc 4 模板即不触 T-IP-09 → D-C 推荐「本波不接、维持 D5」 |
| ⑥ §266 矛盾 | spec §239 / §266 / §322 | §239 影响面表标 events.schema.json「本波零改/无」、§322(§7-7)标「本波不改」,§266 却列入「须串行收口的公共写点」→ 自相矛盾,已订正 |
---
## 4. 拍板与遗留
- **拍板项(创始人,详见 spec §0.1 + §7 标 ⭐ 行)**:
- **D-A** biz 形态:轻量 lead form 先行(推荐)vs 全 biz;
- **D-B** community 功能优先级:通知底座最小集(推荐)vs 全量 T-CMU;
- **D-C** 是否本波即接 ip seam(寄宿 compliance):推荐**本波不接、维持 D5 推迟**。
- **可由 execution 版单 agent 直接定的实现细节**(spec §7 未标 ⭐ 行):建设次序(先 community)、P-BIZ-12 P0 子集、UI 入口排期、并行/串行收口、P-INC-01 计数权威源(拍 (a) project notify)。
- **残留风险(不阻塞拍板,execution 期管控,见 spec §5 R1-R10)**:匿名/非 Web 线程写审计列雷(staging 必测两链路)、Flyway 撞号、@Primary Bean 冲突、错误码段误复制、「骨架编译过≠真实可验收」。
- **收口回写计划**:Wave4 收口后把克隆实证回写 `.agents/skills/add-business-module.md`(107/110 升「已落地」、补 root pom 注册独立步、补「跨模块通知优先同进程 `-api` notify-push 而非 MQ」范式)+ 新踩坑,更新 `.agents/README.md` 索引。

View File

@ -0,0 +1,434 @@
# Wave4(community + biz)建模块 · 评审版
> 编号 **HJ-WAVE4-001** | 2026-06-11 | 类型:Review(结论先行,供创始人拍板;定稿后出 execution 版)
> 任务:为 MVP 最后两个未建业务模块 **community(社区/通知/激励)+ biz(B/G 端定制)** 写评审版 spec。
> 状态:**已定稿(两轮对抗评审整改收口),待创始人拍板**。本文是「须双 spec」铁律下的**第一份(评审版)**;execution 版(含可落地代码级细节、DDL、端点清单、克隆步骤)**待创始人拍板后再出**,本文不含代码级细节以降认知负荷。
> **修订**:R1 第一轮对抗评审整改 + R2 第二轮对抗评审整改(均 2026-06-11),逐条处置见文末「评审整改纪要(R1)」「评审整改纪要(R2)」。**R1 最大变更=community 通知机制定型为同进程 `-api` notify-push(取代含糊的「事件驱动/MQ」口径),消除 MQ/事件契约整组前置依赖;R2 修订=校正 6 处精度/口径(§0 预占校验口径按文件名、§266 events.schema.json 兼容性矛盾、§7 M5 排序理由、runtime 取包真实双路由、skill 与本节装配点口径差提示、P-INC-01 计数方案显式决策点)。**
> ## ⚠️ 勘误 ERRATA(execution 落地回流,2026-06-11,权威订正本文上游源结论)
> execution 版(HJ-WAVE4-EXEC-001)实查仓库后回流以下勘误,**以勘误为准**(本文正文 §2.1/§2.2 Mermaid、§2.2 P0 表、§6.1 AC-CMU-3 相应处已 inline 标注「勘误#1」,未及处以本段为准):
> - **勘误#1(P-NTF-03 审核结果通知上游源:compliance → project)**:本文多处(§2.1 Mermaid `CMP` 框、§2.2 Mermaid `E2` 框、§2.2 P0 表「compliance 新增 1 处调用」、AC-CMU-3「compliance 审核出结果后…」)写 **compliance** 为 P-NTF-03 上游事件源。**实查证据订正为 project**:审核结果落库点是 `ProjectServiceImpl.reviewProject`(写 review_record + 状态机 + reason),`ReviewReqVO` 含 `gameId/versionId/decision/reason`;而 compliance 不持审核态(`ComplianceGateServiceImpl.evaluate` 只做锁风门 Gate 二态、`UserBanService` 只管封禁,均不碰 project 审核态)。**故 P-NTF-03 的 `CommunityNotifyApi` 调用挂在 project 侧 `AdminProjectController.reviewProject` 外层 caller(service 提交后),不挂 compliance**(挂 compliance 取不到 reason)。验收(AC-CMU-3)以 project 挂点为准。execution §6.5 挂点2 含完整可落地处方。
> - **勘误#2(P-NTF-03 通知边界三态)**:`ReviewDecisionEnum` 实为三态(1通过/2拒绝/3下架)。本波 notify **仅覆盖 1/2**;`decision=3下架`(含 compliance 封禁经 `ProjectApi.unlistIfPublished` 系统身份直调 service 的第二路径)**本波不发通知**,属非 P0 已知不通知项。
> - **勘误#3(P-INC-01 计数口径=尽力计数)**:方案 a 的 `notifyProjectPublished` 在事务提交后 try-catch 调用=at-most-once 投递,`published_count` 丢一次不补发。本波明确为「尽力计数」(允许极端少计/漏触发),AC-CMU-5 订正为「计数到达阈值即触发」;可对账重建(project 补 `countPublishedByCreator`)留 M4。
---
## 0. 结论(一句话)
**两模块都按已验证的「黄金模块克隆 + 契约先行 + 轻量状态机」范式建,零创新风险;唯一真正的产品决策在 biz——推荐「轻量 lead form 先行」而非「全 biz」,因为 biz 的 6 个 P0 没有一个直接依赖支付/分账(pay/trade),资金流本就不在 P0 必经路径,全 biz 的 BPM/Flowable/在线收款是远期形态、且被 pay 桩与 ICP/支付进件日历闸门卡死。**
**Wave4 总交付 = 11 P0 = community 5 + biz 6**(P-BIZ-11 学生防沉迷经 Doc C 已重划至 compliance,由 compliance 承接,不计入 biz;详见 §2.3 与 §7 第 4 项)。
- **community(5 P0)**:本质是「通知底座 + 新人激励」。**触达机制(R1 定型)= 同进程 `-api` notify-push**——aigc 生成完成 / **project 审核出结果(勘误#1:原写 compliance,见顶部勘误段)** / trade 收益变动后,由各自 service 在本地事务提交后**同进程调用 community 暴露的 `CommunityNotifyApi`**(与 feed↔project 同范式,复用 `@Primary` 本地实现),**不新建 MQ 生产者/消费者,也不走 telemetry 的 events.schema.json 事件总线**(该总线是前端遥测专用,非后端域事件,见 §3.3)。风险最低、被全链路 M5 依赖,**优先建**。
- **biz(6 P0)**:本质是「询单 → 报价 → 进度看板 → demo 试玩确认」的**信息流闭环**,复用面极大(aigc 模板/runtime 沙箱/admin 队列/轻量状态机全是已建资产);**资金流(收款/分账/在线签章)留 M4**,本波只交付信息流。
**全文证据边界**:模块克隆范式、错误码段、Flyway ceiling、契约文件、**上游事件发射基建(compliance/trade 零域事件、telemetry MQ 消费者仅骨架、feed↔project 同进程 `-api` 注入为真范式)**均经 2026-06-11 实查仓库核实(见 §6 脚注与 §3.3);P0 计数依 Doc A/B/C 三文档交叉核对。BPM/Flowable 装配状态、pay/trade 真实度引自项目总账(标注为桩)。
---
## 0.1 ⭐ 待创始人拍板的关键决策(汇总,详表见 §7)
> 本文已两轮对抗评审定稿,结论可直接执行;以下 **3 项是真正需要创始人拍板的战略决策**(其余 §7 第 2/5/6/7/8 项属实现细节,可由 execution 版单 agent 直接定,已在 §7 标注)。**按推荐拍即可开 execution 版。**
>
> **✅ 2026-06-11 创始人拍板:D-A / D-B / D-C 全按推荐采纳**(D-A=biz 轻量 lead form 先行、资金流留 M4;D-B=community 通知底座最小集、社交/成长域留接口桩;D-C=本波不接 ip seam、维持 D5 寄宿 compliance 推迟)。execution 版据此启动(`2026-06-11-Wave4-community-biz-execution.md`)。
| ⭐ | 关键决策 | 推荐 | 不这样做的代价 |
|---|---|---|---|
| **D-A** | **biz 形态:轻量 lead form 先行 vs 全 biz** | **lead form 先行**——只交付「询单→报价→进度→demo 确认」信息流闭环,资金流(收款/分账/在线签章)留 M4 | 全 biz 强依赖 yudao-bpm(未装配)+Flowable(未建表)+pay(桩)+三方签章,被 ICP/支付进件日历闸门卡死;且 6 P0 无一直接依赖 pay/trade,全 biz 是远期形态、近期投产即孤儿 |
| **D-B** | **community 功能优先级:通知底座最小集 vs 全量 T-CMU** | **通知底座最小集**——T-CMU-10 编排+T-CMU-06 站内信+T-CMU-11 等级引擎(+可选短信 T-CMU-09),一次覆盖 5/5 P0;SOC/GRW 域(关系图谱/排行/成就/扇出,全 P1/P2)只留接口桩 | 全量铺开会做大量非 MVP 验收项,违反最小交付;触达机制已 R1 定型为同进程 `-api` notify-push(不建 MQ、不动 events.schema.json),此项无悬念 |
| **D-C** | **是否本波即接 ip seam(D5 已定「寄宿 compliance、不独立建模块」)** | **本波不接、维持 D5 推迟**——Wave4 P0 中**仅 P-BIZ-02 协同涉及 IP 模板市场 T-IP-09**,而本波 P-BIZ-02 推荐**直接复用 aigc 4 模板 + runtime 沙箱**(绕开 T-IP-09 模板市场),故**无需 ip seam**;ip seam 维持「寄宿 compliance」的 D5 决策,待真做素材/IP 市场或 DMCA 下架(P-IPX-05,P1+/远期)时再接 | 本波若强接 ip seam=为唯一沾边的 P-BIZ-02 引入一整套未被 P0 必需的 IP 授权/版权链(T-IP-02/04/05/09),与「最小交付+复用优先」相悖;且 compliance 当前 style/ip 原子本就是桩(总账 line51),强接即叠桩上桩 |
> **D-C 证据**(2026-06-11 实查):`docs/mvp/MVP进度总账.md:53` D5 决策「ip 不独立建、seam 寄宿 compliance」,line95 标为推迟 TODO;`需求模块映射.md` 中 Wave4 11 P0 仅 **P-BIZ-02 协同含 `T-IP-09 模板市场`**(line207)、community 5 P0 零 ip 关联;本波 P-BIZ-02 推荐复用 aigc 模板(§2.3)即天然不触 T-IP-09。**故 D-C 推荐「不接」不是回避,是本波 P0 真实依赖边界本就不含 ip。**
---
## 1. 背景与目标
- **背景**:MVP 13 模块中已建 11 个(9 个 `game-module-*` + passport 寄宿 system + studio)。Doc B(技术架构与模块.md:10)明确 **community/biz = 「未建(Wave4)」**,是最后两块。两模块同归 **WS5(数据与变现工位)**。
- **目标**:交付 community 5 P0 + biz 6 P0 **可验证**,且不破坏已建 11 模块(错误码段/Flyway 序号/前缀零冲突)。
- **非目标(硬边界,写入 spec 防孤儿/缩水)**:
- biz **不收款结算**(Doc B:308 明文 OUT)——T-BIZ-08 收费、T-BIZ-03 在线电子签章降级为 M4 跟进项,本波只到「状态机走通 + 线下签署后台标记」。
- community 不全量铺开 T-CMU 全家桶——SOC/GRW 域(关系图谱/排行/成就/扇出)全是 P1/P2,本波只留接口桩、不实现。
- 不引入 Flowable / yudao-bpm(当前注释未装配)——复用 project 已落地的轻量状态机范式。
- **本波两类留债须分开记账(R1,勿混为一谈)**:**①资金流债**(biz 签署/收款、community reward 记入钱包/流量包与现金到账)= 受 pay 桩 + ICP/支付进件日历闸门制约,统一并入 **M4**;**②前端 UI 入口债**(biz 运营代客发起=WS4 / 客户进度看板=WS3)= 跨工位协调项,可与本波后端**并行**推进,与 M4 资金流债是不同维度,不应捆在一起延期。
---
## 2. 推荐方案(结论先行 + Mermaid)
### 2.1 两模块定位与依赖(一图看清边界)
```mermaid
graph TB
subgraph W4["Wave4 本波新建(信息流闭环)"]
CMU["community<br/>错误码段 1-107<br/>Flyway V12<br/>暴露 CommunityNotifyApi"]
BIZ["biz<br/>错误码段 1-110<br/>Flyway V13"]
end
subgraph UPSTREAM["上游 notify 调用点(本波在各上游新增 3 处同进程调用)"]
AGC["aigc<br/>生成完成后调 CommunityNotifyApi"]
CMP["project(勘误#1:原写 compliance)<br/>reviewProject 审核出结果后调 CommunityNotifyApi"]
TRD["trade<br/>收益变动后调 CommunityNotifyApi"]
end
subgraph REUSE["可直接复用资产(biz 复用,零新建)"]
TPL["aigc 4 模板库 T-AGC-03"]
SANDBOX["runtime 沙箱预览 / 取包链路"]
ADMINQ["admin 审核队列 + 轻量状态机<br/>(范本=project ProjectStatusEnum<br/>+ ProjectServiceImpl 流转校验)"]
end
subgraph M4["M4 变现真实化(本波留债,明确推迟)"]
PAY["pay 微信支付进件(桩)"]
SIGN["在线电子签章 T-BIZ-03(三方)"]
SETTLE["trade 真实分账/打款(桩)"]
end
AGC -.同进程 -api 调用(提交后).-> CMU
CMP -.同进程 -api 调用(提交后).-> CMU
TRD -.同进程 -api 调用(提交后).-> CMU
TPL --> BIZ
SANDBOX --> BIZ
ADMINQ --> BIZ
BIZ -.询单/报价/进度/demo确认(信息流,本波交付).-> BIZ
BIZ -.收款/在线签章(资金流,留债).-> M4
CMU -.新人现金奖励真实到账.-> M4
classDef deb fill:#fee,stroke:#c33;
class PAY,SIGN,SETTLE deb;
classDef done fill:#efe,stroke:#3a3;
class TPL,SANDBOX,ADMINQ deb2;
classDef new fill:#eef,stroke:#36c;
class AGC,CMP,TRD new;
```
> **图例说明(R1 校正)**:上游三框是**本波要新增的 notify 调用点**,不是「已有事件源」——实查证实 compliance/trade 当前**零域事件发射**(`grep publishEvent/ApplicationEvent/RocketMQTemplate` 全空),telemetry 唯一的 MQ 消费者 `TelemetryEventConsumer` 仍是**无 `@RocketMQMessageListener` 注解的骨架**(类注释明写「MQ 消费 待对接」)。本仓真正在跑的跨模块范式是**同进程 `-api` 注入**(`FeedServiceImpl` 注入 `ProjectApi`/`RuntimePackageApi`,`@Primary` 本地实现就地解析)。community 沿用此范式。
### 2.2 community:通知底座最小集(一次到位覆盖 4/5 P0)
**核心洞察**:5 个 P0 里 4 个是通知类(NTF-01/02/03/05),共用同一底座;第 5 个(INC-01 新人激励)加一个等级引擎即可。
**触达机制(R1 定型)= 同进程 `-api` notify-push**:community 暴露 `CommunityNotifyApi`(`@Primary` 本地实现),上游 aigc/**project(勘误#1:P-NTF-03 源 compliance→project,见顶部勘误段)**/trade 在各自 service 的本地事务**提交后**同进程调用之,写入站内信表。**不新建 MQ,不走 events.schema.json**(理由见 §3.3)。
```mermaid
graph LR
subgraph CALL["上游 notify 调用点(本波在上游各加 1 处提交后同进程调用)"]
E1["aigc 生成完成<br/>→ CommunityNotifyApi"]
E2["project 审核出结果(勘误#1:原写 compliance)<br/>→ CommunityNotifyApi"]
E3["trade 收益变动<br/>→ CommunityNotifyApi"]
E4["project 发布成功计数<br/>→ 等级引擎(计数权威源=project)"]
end
subgraph CORE["community 通知底座(CommunityNotifyApi 入口)"]
ORCH["T-CMU-10 通知编排<br/>(路由/模板/去重)"]
INBOX["T-CMU-06 站内信<br/>(投递/已读)"]
LEVEL["T-CMU-11 等级引擎<br/>(里程碑触发)"]
end
subgraph OUT["产出"]
MSG["站内消息中心<br/>四类聚合"]
SMS["短信(可选)<br/>T-CMU-09"]
REWARD["新人奖励触发记录<br/>(本波写自有 reward 触发表;<br/>记入钱包/流量包 → M4)"]
end
E1 & E2 & E3 --> ORCH --> INBOX --> MSG
ORCH -.高优.-> SMS
E4 --> LEVEL --> REWARD
classDef new fill:#eef,stroke:#36c;
class E1,E2,E3,E4 new;
```
| P0 | 语义 | 落在底座哪一块 | 上游接入(R1 校正:均为同进程 `-api` notify,非「已有事件」) |
|---|---|---|---|
| P-NTF-01 站内消息中心 | 系统/公告/审核/收益四类聚合 | 站内信 T-CMU-06 + 编排 T-CMU-10 | 无(自身聚合) |
| P-NTF-02 生成完成通知 | AI 生成完成推送创作者 | 编排 T-CMU-10 | **aigc 新增 1 处 `CommunityNotifyApi` 调用** |
| P-NTF-03 审核结果通知 | 通过/拒绝(拒绝带原因)推送 | 编排 T-CMU-10 | **(勘误#1:原写 compliance)project `AdminProjectController.reviewProject` 外层 caller 新增 1 处 `CommunityNotifyApi` 调用**(compliance 不持审核态,挂 compliance 取不到 reason) |
| P-NTF-05 收益变动通知 | 结算/到账变动推送 | 编排 T-CMU-10(+可选短信 T-CMU-09) | **trade 新增 1 处 `CommunityNotifyApi` 调用** |
| P-INC-01 新人任务奖励 | 发满 3 作品领流量包+现金 | 等级引擎 T-CMU-11 | 计数权威源=**project 发布成功**(见下「P-INC-01 落地形态」);发放/流量包记入 → M4 |
> **P-INC-01「触发+记账」落地形态(R1 定型,消除孤儿子链路)**:
> - **计数权威源 = project 发布成功**(非 feed 互动信号回流)。实查 project 当前无 `project_published` 域事件、无 `publishCount` 字段、ProjectApi 也无对应查询方法——故 execution 版二选一收口:**(a)** project 发布成功后**同进程 notify** community 计数(与三上游同范式,推荐);或 **(b)** community 经 ProjectApi 新增的 `countPublishedByCreator(creatorUserId)` 主动读数。**两者都不依赖 feed 计数 seam**,让 community 可独立验收。
> - **「记入钱包/流量包」本波不跨模块写**:实查 `feed.yaml` 无「流量包发放」端点(仅有 `boost`/`pinned` 运营加权)、`trade.yaml` 无「向 community 发放激励」端点(无 grant/发放/incentive 接口)。两个跨模块写方向**当前均无契约 seam**。故本波 community **只写自有的 reward 触发记录表**(达标即落一条可观测触发记录),**「实际记入钱包/流量包」连同现金到账一并挂 M4**(届时由 trade 暴露 `IncentiveCreditApi`、feed 暴露流量包分配 `-api` 后补 seam 一致性校验)。AC-CMU-5 口径据此收紧(见 §6.1)。
### 2.3 biz:轻量 lead form(信息流闭环,资金流留 M4)
```mermaid
graph LR
subgraph IN["B/G 端 / 运营代客发起"]
FORM["①需求询单表单<br/>P-BIZ-01"]
PICK["②模板库选择+预览<br/>P-BIZ-02"]
QUOTE["④⑤询单+报价<br/>P-BIZ-08/12(报价≠下单)"]
end
subgraph FLOW["③轻量状态机(复用 project 范式:枚举守门流转,不引 Flowable)"]
S1["待跟进"] --> S2["已报价"] --> S3["制作中"] --> S4["待验收"] --> S5["已交付"]
end
subgraph ACCEPT["⑥交付验收 P-BIZ-04"]
PLAY["试玩(runtime沙箱)"] --> FB["反馈"] --> CONFIRM["确认"]
CONFIRM -.签署/收款.-> DEBT["留债 M4<br/>(线下签署+后台标记兜底)"]
end
FORM --> S1
PICK --> S1
QUOTE --> S2
S4 --> PLAY
classDef deb fill:#fee,stroke:#c33;
class DEBT deb;
```
**biz 本波 6 P0 = P-BIZ-01/02/03/04/08/12**(Doc C 交叉核对)。**P-BIZ-11 学生账号与防沉迷经 Doc C 已划归 compliance**(映射 = `T-CMP-36 防沉迷 · T-CMP-37 实名`,主模块=compliance),**不计入 biz**,由 compliance 承接(compliance 因此 +1 P0)。
| P0 | 语义 | 本波交付形态 | 留债 |
|---|---|---|---|
| P-BIZ-01 需求表单提交 | B/G 端在线提交定制需求 | 纯 CRUD 落库(CRM = 工单表 biz_lead) | — |
| P-BIZ-02 模板库选择+预览 | 按场景浏览模板并预览 | **复用 aigc 4 模板 + runtime 沙箱取包**(零新建);**须处理取包失败影子路径**(见下) | — |
| P-BIZ-03 定制进度看板 | 客户看各阶段进度 | 轻量状态机驱动 + admin 处理队列;**客户侧入口=game-studio `/biz-orders/my-orders`(WS3 横算)或邀请链** | — |
| P-BIZ-04 交付验收 | 试玩→反馈→确认→签署 | 做到「试玩+反馈+确认」三态 | **签署/收款留 M4** |
| P-BIZ-08 品牌营销定制 | 报价+编排(端=运营) | 参数化估价表单 + 落工单(报价≠可支付订单) | 收款留 M4 |
| P-BIZ-12 文旅定制 | 景区/博物馆等(端=运营,P0/P1 混标) | **P0 子集=场景需求 CRUD + 模板选择复用 + 专属看板**(与 P-BIZ-01/03 同能力栈,无额外定制) | P1「快速交付增强」列 M4 后增量 |
> **biz 形态 = lead form ≠「缩水验收」**:6 P0 逐条核对 Doc C 映射,**无一直接依赖 pay/trade**(pay/trade 仅出现在 P1 的 P-BIZ-06 套餐 / P-BIZ-10 教育)。所以「只交付信息流」是按真实依赖边界交付,不是偷工。
>
> **B-02 模板预览影子路径(R1 补,落地易断处)**:biz 复用 runtime 取包,已证存在且**有失败态**——runtime `-api` 实查含 `RUNTIME_PACKAGE_NOT_READY`(1-102-001-001) 等 1-102 段错误码 + `BuildRespVO.failCode/failReason`。
>
> **取包端点真实路由(R2 校正,execution 版按真实路由钉准)**:runtime 实暴露**两个**取包端点(`AppRuntimeController` 实查)——**①`GET /app-api/runtime/package/{versionId}`**(返 `RuntimePackageRespVO` 清单 meta,包 `CommonResult`,宿主据此渲染 iframe/做完整性校验;biz 看板取「是否可预览」判定走此端点最直接);**②`GET /app-api/runtime/package/{versionId}/manifest`**(返 manifest 原始 JSON 文本、不包 `CommonResult`,供宿主算 sha256 严格比对后注入运行容器)。两端点门禁同源(同走 Service `getPackageManifest` 的 scene+status 权威判定)。**execution 版端点清单须把两条按真实路由写全**(含 `/manifest` 后缀那条),勿只写其一。故 biz 作为复用方**必须处理**:B 端选的模板版本**无编译产物 / 编译失败 / versionId 缺失或非法**时,看板降级显示「该模板暂不可预览」而非透传 500;状态机**不允许在无可玩 demo 时推进到「待验收」**。AC-BIZ-2 据此补负路径验收(见 §6.2)。
>
> **B-12 P0 子集(R1 收紧,防范围蔓延)**:P-BIZ-12 P0 必交 = 上行三栏(CRUD + 模板复用 + 专属看板),与 P-BIZ-01/03 同能力栈、不引入额外定制开发;P1「快速交付增强」单列 M4 增量。
### 2.4 建模块技术范式(已验证,零创新)
两模块均**克隆黄金模块 game-module-project**(双 Maven 子模块:`-api` 契约层 + `-server` 实现层)。**单体装配 = 零改 Application/yaml**(`YudaoServerApplication` 已通配扫描 `cn.wanxiang.game.module`)。
**公共装配点 = 两处(R1 校正,与 §4 影响面表一致)**,缺任一处克隆即卡:
- **① `game-cloud/pom.xml`(root 聚合 pom)**:`<modules>` 块追加 `<module>game-module-community</module>` + `<module>game-module-biz</module>`——**缺此步 `mvn -pl game-module-community compile` 找不到父 pom 聚合定义而失败**(已知漏注册点);
- **② `yudao-server/pom.xml`**:`<dependencies>` 块追加两个 `-server` `<dependency>`(`game-module-community-server` / `game-module-biz-server`,单体把全部模块编进一个 JAR)。
**轻量状态机范式 = 复用 project 的「枚举守门流转」**(实查:无独立 `ProjectStateService.java`;范本 = `ProjectStatusEnum`(含 `isDraft` 等合法流转判定)+ `ProjectServiceImpl` 内在写之前校验合法流转、非法即 `throw exception(...)`)。biz 照此落 `BizLeadStatusEnum` + service 流转校验,**不引 Flowable**。
详见 `.agents/skills/add-business-module.md`,execution 版展开 6 类落点清单(含 root pom 注册作为独立一步)。
> ⚠️ **canonical skill 与本节口径差(R2 提示,execution 版克隆步骤开篇须复述)**:`add-business-module.md` 步骤表当前**仅列「步骤5 = yudao-server/pom.xml 加 -server 依赖」,未把 root `game-cloud/pom.xml` 的 `<modules>` 注册列为独立步骤**(开头甚至明写「新模块无需改 Application/yaml,只需在 yudao-server/pom.xml 加依赖」)。该 root pom 注册步**计划在本波收口后才回写进 skill**(见行末知识沉淀计划)。故**本波克隆以本节「公共装配点=两处」为准,勿以旧 skill 为唯一依据**——只读 skill 会漏掉 root pom 的 `<module>` 注册,触发 §4/R 项已点名的「缺此步父 pom 找不到聚合定义 → `mvn -pl … compile` 失败」。
---
## 3. 关键权衡
### 3.1 biz:轻量 lead form 先行 vs 全 biz(核心决策)
| 维度 | 方案 A:轻量 lead form 先行 ✅推荐 | 方案 B:全 biz |
|---|---|---|
| **范围** | 询单/报价/进度/demo 确认(信息流闭环) | + BPM 实例编排 + 在线收款 + 在线电子签章 + 完整 CRM |
| **依赖就绪度** | 复用已建资产(模板/沙箱/admin 队列/轻量状态机),**零外部阻塞** | 强依赖 yudao-bpm(注释未装配)+ Flowable(建表未覆盖)+ pay(桩)+ 三方签章 |
| **被日历闸门卡** | 否 | 是(ICP 7-20 工作日 + 微信支付进件 1-3 周 + Flowable 装配) |
| **工程量** | 小(4 张能力,大量复用) | 大(BPM+签章+CRM+收费全新建) |
| **服务现金线** | **直接命中**:先把「线索→报价→demo 交付」跑通,首批成交走线下合同/对公转账即可 | 在线收款对首批 B 端成交非必需(线下合同即可成交) |
| **风险** | 低 | 高(多依赖桩 + 远期形态 + 易做成依赖桩的孤儿设计) |
**推荐 A 的三条理由**:① 6 P0 无一直接依赖 pay/trade,资金流不在 P0 必经路径;② 复用面极大;③ 直接服务总账「近期现金靠 B 端」命门(单位经济模型:自有端 IAA 是万级 DAU 才回本的远期账,B 端项目制收费是近期现金线,命门 = 「拿到询单 + 能报价 + 能交付 demo」而非「平台内在线收款」)。
> **方案 B 不是错,是时序错**:全 biz 的资金流闭环(在线收款/预付尾款/分账)应与 ad 自有端 IAA 一起并入 **M4 变现真实化波**,与 pay 接线 + 支付进件日历闸门对齐。本波 lead form 把信息流跑通,M4 补资金流。
> **「轻量状态机 vs BPM」是等价替代而非缩水(R1 澄清)**:Doc B 中 T-BIZ-02 原起为完整 BPM 编排。本波 6 P0 的真实流程需求 = P-BIZ-03/04 的**单线性五态推进**(待跟进→已报价→制作中→待验收→已交付),**复用 project 已落地的枚举守门流转范式即完全满足**,并非「规避 BPM 这一标准」。BPM/Flowable 真正解决的「多级审批 / 会签 / 可视化流程配置」是 P1+远期场景的诉求,本波 P0 不涉及——故**多线/分支流程推迟到 M4 与 Flowable 一起补**,本波用轻量状态机是按 P0 真实复杂度选型,不是降级。
### 3.2 community:通知底座最小集 vs 全量 T-CMU
| 方案 | 范围 | 取舍 |
|---|---|---|
| **最小底座 ✅推荐** | T-CMU-10 编排 + T-CMU-06 站内信 + T-CMU-11 等级引擎 + T-CMU-09 短信(可选) | 一次覆盖 5/5 P0;SOC/GRW(全 P1)留接口桩 |
| 全量 T-CMU | + 关系图谱/计数/排行/成就/扇出/邮件 | 做大量非 MVP 验收项,违反最小交付 |
**推荐最小底座**:5 个 MVP P0 只实际用到 4 个 T-CMU 能力,其余服务的 SOC/GRW 全是 P1。
### 3.3 community 触达机制:同进程 `-api` notify-push(R1 定型,取代「事件驱动/MQ」含糊口径)
> **这是本评审该定的架构决策,R1 在此一次定死,消除实现者无法据本文开工的歧义。**
**结论:community 通知触达 = 同进程 `-api` notify-push。** community 暴露 `CommunityNotifyApi`(`@Primary` 本地实现),上游 aigc/**project(勘误#1:P-NTF-03 源 compliance→project)**/trade 在各自 service 的**本地事务提交后**同进程调用之写站内信。**不新建 MQ 生产者/消费者,不走 telemetry 的 `events.schema.json` 事件总线。**
**为什么选 notify-push 而非 MQ/事件总线(实查证据)**:
| 维度 | 同进程 `-api` notify-push ✅推荐 | MQ / events.schema.json 事件总线 |
|---|---|---|
| **本仓现状** | **唯一在跑的跨模块范式**:`FeedServiceImpl` 注入 `ProjectApi`/`RuntimePackageApi`,`@Primary` 本地实现就地解析(同进程方法调用,与调用方同 `@Transactional`) | **整组基建不存在**:compliance/trade `grep publishEvent/ApplicationEvent/RocketMQTemplate` **全空**;9 个 game-module 中**无一个 MQ 生产者目录**;唯一 MQ 消费者 `TelemetryEventConsumer` 是**无 `@RocketMQMessageListener` 注解的骨架**(「MQ 消费 待对接」),telemetry 这条「先例」**从未真正跑通 MQ** |
| **events.schema.json 适配性** | 不涉及——community 自有契约 `community.yaml` | **不匹配**:该 schema 是 **telemetry 专用**(单 topic `telemetry-event`、单消费者、`additionalProperties:false`),`generate_succeeded/income_settled` 是**前端上报的遥测串**、不是后端发的域事件;强塞会污染遥测契约 |
| **工作量画像** | 符合「克隆零创新」:**新增 3 个上游 notify 调用点 + 1 个 `CommunityNotifyApi` 契约**(与 feed↔project 同范式,复用 `@Primary`) | **远超「零创新」**:telemetry MQ listener 从骨架补成真实可跑 + 三上游各新增 producer + RocketMQ 中间件就绪(mini-infra 缺 RocketMQ,见项目记忆) |
| **事务/可靠性** | 上游本地事务**提交后**调用(避免把通知失败回滚业务);同步路径异常可观测 | 至少一次投递 + 幂等 + DLQ,可靠性更强但本波无收益(无削峰需求、无跨进程) |
**联调与独立验收(不被上游阻塞死)**:上游 3 个 notify 调用点未接通前,community 暴露**仅 staging/dev 生效**的手工触发 mock 端点 `@PostMapping /admin-api/community/notifications/{type}/mock-trigger`,供 community 单独验收四类通知投递;上游接通后此端点仅留联调用途。
> **保留口径(给坚持 MQ 路线者)**:若未来确需把某类通知改走 MQ/遥测事件,则 `engineering-conventions.md §「契约事件登记须同步后端枚举守门人」` 红线生效——**只改 `events.schema.json` 不同步 `TelemetryEventEnum` = 事件被静默 rejected**(accepted:0 信封仍 code:0,极难发现)。本波选 notify-push 正是为**绕开这道易错的双处同步**;execution 版**不**在 `events.schema.json` 新增 community 事件,故无此前置门禁。
---
## 4. 影响面(blast radius)
| 落点 | 改动 | 性质 | 风险 |
|---|---|---|---|
| `game-cloud/pom.xml`(root 聚合 pom) | `<modules>` 追加 `<module>game-module-community</module>` + `<module>game-module-biz</module>` | **公共文件**(两波共享写点;**克隆必经第①点**) | 并行同改冲突 → 建议串行注册或单 agent 收口;缺此步父 pom 找不到 → compile 失败 |
| `yudao-server/pom.xml` | `<dependencies>` 追加两个 `-server` `<dependency>`(装配第②点) | **公共文件** | 同上 |
| `YudaoServerApplication.java` / `application.yaml` | **零改**(通配扫描已覆盖 `cn.wanxiang.game.module`) | 无 | 无(明确写入 spec 降认知负荷) |
| `contracts/db-schemas/` + `yudao-server/.../db/migration/` | 新增 V12(community)+ V13(biz),**两处副本字节一致** | 全局单线性序列 | 撞号 → flyway validate 阻断(须中央预占,见 §8 execution 预告第 0 节) |
| `contracts/api-schemas/` | 新增 community.yaml + biz.yaml(独占新文件) | 各自独占 | 无冲突 |
| `contracts/README.md §四` | community=107/biz=110 **已在册**(无需改,只复用) | 只读确认 | 无 |
| **aigc/compliance/trade `-server`**(上游) | **各新增 1 处 `CommunityNotifyApi` 调用点**(提交后同进程)+ community `-api` 暴露 `CommunityNotifyApi` 契约 | 新增跨模块同进程调用(与 feed↔project 同范式) | 上游 service 改动有限,须在事务提交后调用以免通知失败回滚业务 |
| `events.schema.json` + 后端 `TelemetryEventEnum` | **本波零改**(community 走 notify-push,**不新增遥测事件**,见 §3.3) | 不涉及 | 无(主动规避「只改契约不改枚举静默 rejected」双处同步坑) |
| `game-admin`(WS4) | biz 运营代客发起 + 处理队列 UI 入口(BIZ-08/12 端=运营) | 跨工位 | 无 UI 入口 → 孤儿;本波 biz 后端 API 可独立验收,UI 入口与 WS4 排期绑定(见 §6.2/§7 第 6 项) |
| **project → community 计数 seam** | project 发布成功后 notify community 计数(或 community 经 ProjectApi 新增 `countPublishedByCreator` 读数);**计数权威源=project,非 feed** | 新增跨模块同进程调用 | 二选一收口(§2.2「P-INC-01 落地形态」);**不依赖 feed 计数回流改造**,community 可独立验收 |
> **feed↔community 计数 seam 澄清(R1)**:`feed.yaml` 注释「累计计数最终态归 project/community」指的是**计数最终态的归属权**,**非 feed 反写 community**。INC-01 的「已发布作品数」计数权威源**定为 project 发布成功**(见 §2.2),与 feed 互动信号无关——故 §旧表「feed 信号→community 计数串行依赖」这一项**取消**,避免 community 被 feed 计数回流改造卡成串行依赖。
**新增表**:community 表(站内信/通知/等级 + **reward 触发记录表**,V12)、biz 表(biz_lead 工单/报价/进度,V13)。每张表强制带 Yudao 七审计列(creator/updater/create_time/update_time/deleted/tenant_id 全 NOT NULL)+ 唯一键含 (业务键, deleted, tenant_id)。
---
## 5. 风险与兼容
> 问题按严重度排序,每条给影响 / 根因 / 缓解方向。
| # | 风险 | 严重度 | 影响 / 根因 | 缓解方向(execution 版须给细节) |
|---|---|---|---|---|
| R1 | **community 触达机制曾未定型(已在 §3.3 定死)** | 🔴 高→已缓解 | 原稿把 community 框成「上游已有事件的消费方」,但实查上游**零域事件、MQ 消费者仅骨架**——实现者无法据本文开工。**根因=机制未建+选型未定,非「上游未就绪」** | **R1 已定型=同进程 `-api` notify-push**(§3.3):新增 3 上游 notify 调用点 + 1 个 `CommunityNotifyApi` 契约,复用 `@Primary` 本地实现,**不建 MQ、不动 events.schema.json**;上游未接通前以 staging 限定 mock 端点独立验收 |
| R2 | **biz 用轻量状态机替代 BPM(等价替代,非缩水)** | 🟠 中 | T-BIZ-02 原义是 BPM 编排;本波 6 P0 仅需单线性五态推进,**复用 project 枚举守门流转即满足**(§3.1 已澄清非「规避标准」) | 复用 project 范式(`ProjectStatusEnum` + `ProjectServiceImpl` 流转校验,非独立 `ProjectStateService`);spec 标注「多级审批/会签等多线流程回补 Flowable 属 M4+远期」 |
| R3 | **匿名写 / 非 Web 线程写审计列雷** | 🟠 中 | community 通知投递、biz 状态机推进、reward 触发记账几乎必含**非 Web 线程写或系统身份写** → 无登录态 → updater NOT NULL 拒绝 → 链路 500/任务卡死。**单测 mock 测不到此雷(项目记忆铁律)** | 入口注入系统身份 LoginUser(id=0)+finally clearContext(范本 EventIngestServiceImpl / AigcGenerateExecutor);**工程门把两条具体异步/系统身份写链路点名为 staging 必测项**(见 §6.3):①community 通知投递链路(上游 notify→写站内信表)、②biz 状态机推进链路(admin 推进→写状态/看板表) |
| R4 | **资金流子环节受 pay 桩制约** | 🟠 中 | biz P-BIZ-04 签署/收款、community P-INC-01 现金奖励真实到账、P-NTF-05 收益最终落 pay 打款;pay 桩 + ICP/支付进件日历闸门 | 明确归 M4:lead form 交付信息流,资金流(收款/分账/提现/在线签章)记债挂 M4 轨,不阻塞本波 |
| R5 | **BIZ-08/12 端=运营,无 UI 入口成孤儿** | 🟠 中 | 两项端为运营而非 B 端自助,更可能由运营后台代客发起;game-admin 无入口则后端能力建好却无 UI | 优先在 game-admin 做运营代客发起入口;与 WS4 确认排期(编排器旁路掩盖 UI 缺陷模式,UI 走查是唯一验收手段) |
| R6 | **Flyway 撞号 / 改旧迁移** | 🟠 中 | 单体单库全局单序列;两 agent 各自起 V12 → validate 阻断;改 V1-V11 任何旧文件 → validate 失败 CI 阻断 | 中央预分配 V12(community)/V13(biz)/V14(W4 埋点加列);**只增不改旧迁移**红线写入 spec |
| R7 | **@Primary 多实例 Bean 冲突** | 🟡 低 | 新模块若新增 Feign 接口,单体下 @FeignClient 代理与本地 @Primary 实现可能 Bean 冲突致启动失败(M1 实证 4 个 CommonApi) | ApiImpl 标 @RestController @Primary;仍冲突的给 @FeignClient 补 primary=false(修法 commit d7fac90);启动盯 BeanDefinition 冲突日志 |
| R8 | **错误码段误复制 project 100 段** | 🟡 低 | 顺手复制 project 骨架忘改段号 → 与 project 重叠 | 务必用预分配 community=1_107 / biz=1_110;ApiConstants.NAME 改 community-server/biz-server(否则 Feign 服务名撞 project) |
| R9 | **P-BIZ-12 P0/P1 边界模糊导致范围蔓延** | 🟡 低 | Doc A 标 P0/P1 混合,P1「快速交付增强」边界未拆分 | 开工前界定 P0 必交子集(可下单+进度看板),P1 列 M4 增量 |
| R10 | **骨架编译过 ≠ 真实可验收** | 🟡 低 | 项目记忆教训:批跑/API 全绿 ≠ 真实可验收 | spec 完成条件要求 mvn 编译+单测+Flyway migrate+Swagger doc.html 可见+真实写链路 staging 实测,「无验证证据不得 claim 完成」 |
**兼容性**:本波纯新增模块(克隆范式),对已建 11 模块**零行为变更**;**公共写点(root `game-cloud/pom.xml` 的 `<modules>` / `yudao-server/pom.xml` 的 `<dependencies>`)须串行收口防并行冲突**;**`events.schema.json` 本波零改**(community 走 notify-push、不新增遥测事件,见 §3.3/§4),故**无写点竞争**、不在串行收口范围内(R2 校正:原稿误把 events.schema.json 列入公共写点,与 §239/§322「本波零改」自相矛盾)。
---
## 6. 验收标准(community 5 P0 + biz 6 P0 可验证)
> 逐项可勾,强调「无验证证据不得 claim 完成」。
### 6.1 community(5 P0)
> **通用前提(R1)**:AC-CMU-2/3/4 的上游触达均为**同进程 `-api` notify**(aigc/compliance/trade 各新增 1 处对 `CommunityNotifyApi` 的调用,见 §3.3),**不经 events.schema.json**;上游 notify 调用点未接通前,用 staging 限定 `mock-trigger` 端点独立验收四类投递。
| # | 验收口径 |
|---|---|
| AC-CMU-1 | **P-NTF-01**:四类消息(系统/公告/审核/收益)可投递、消息中心列表可读、已读状态正确 |
| AC-CMU-2 | **P-NTF-02**:aigc 生成完成后 `CommunityNotifyApi` 被调用 → 通知到达消息中心(与 P-CRT-13 超时通知互补)。**验收前提:aigc 已接入 notify 调用点(或经 mock-trigger 验投递)** |
| AC-CMU-3 | **P-NTF-03**:(勘误#1:上游源 compliance→project)**project `reviewProject` 审核出结果后** `CommunityNotifyApi` 被调用 → 通知落站内信,**仅 decision=1/2 发、拒绝须带原因**(配合 P-OPN-02;decision=3 下架本波不发,勘误#2)。**验收前提:project 已接入 notify 调用点(或经 mock-trigger 验投递)** |
| AC-CMU-4 | **P-NTF-05**:trade 收益结算后 `CommunityNotifyApi` 被调用 → 站内信(+可选短信)送达。**验收前提:trade 已接入 notify 调用点(或经 mock-trigger 验投递)** |
| AC-CMU-5 | **P-INC-01**:创作者发布第 3 款(计数权威源=**project 发布成功**,见 §2.2)后,等级引擎自动触发并**写一条可观测的 reward 触发记录**(本波到「触发事件可观测 + 本地记账」)。**「记入钱包/流量包 + 现金真实到账」连同 trade `IncentiveCreditApi`/feed 流量包 `-api` 一并落 M4**(本波这两个跨模块写 seam 尚不存在,见 §2.2/§4) |
### 6.2 biz(6 P0)
> **UI 落点与跨工位(R1)**:biz 的 admin 队列(AC-BIZ-3)、运营代客发起入口(AC-BIZ-5)属 **game-admin(WS4)**;客户侧进度看板属 **game-studio(WS3)**。本项目记忆铁律「编排器旁路/API 全绿掩盖 UI 缺陷」——故下表凡依赖 UI 的口径**拆两层**:**本波 biz 后端 API 层可独立验收(Swagger/curl)**;**UI 入口列为完成条件增强项、与 WS3/WS4 排期绑定并走真人 UI 走查闭环**,不以另一工位未交付的 UI 阻塞本波 biz 收口。
| # | 验收口径 |
|---|---|
| AC-BIZ-1 | **P-BIZ-01**:需求表单提交即落库,开启一条轻量状态机实例(待跟进态) |
| AC-BIZ-2 | **P-BIZ-02**:可按场景检索 aigc 模板并 **runtime 沙箱预览**(复用 4 款可玩模板,取包走 runtime 真实路由 `GET /app-api/runtime/package/{versionId}`(清单 meta)/ `…/{versionId}/manifest`(原始 JSON),execution 版按真实双路由钉准,见 §2.3 B-02)。**负路径(R1 必验):选无编译产物/编译失败/versionId 缺失或非法的模板时,看板降级显示「该模板暂不可预览」而非透传 500;状态机不允许在无可玩 demo 时推进到「待验收」** |
| AC-BIZ-3 | **P-BIZ-03**:状态机各节点状态可经 `/admin-api/biz/*` 查询并推进(**后端 API 层=本波验收**);**客户侧进度看板(game-studio `/biz-orders/my-orders` 或邀请链,WS3)+ admin 处理队列视图(WS4)=完成条件增强项**,随对应工位排期走 UI 走查 |
| AC-BIZ-4 | **P-BIZ-04**:客户「试玩 + 反馈 + 确认」三态走通(**签署/收款留 M4**,本波线下签署+后台标记已签兜底) |
| AC-BIZ-5 | **P-BIZ-08**:**本波 biz 侧验收 = 经 `/admin-api/biz/*` Swagger/curl 可发起品牌营销定制单并完成报价输出**(报价≠可支付订单)+ 编排(后端能力可独立验收);**game-admin 运营代客发起 UI 入口(WS4)= 独立跟进项**,需 WS4 排期并走 UI 走查闭环 |
| AC-BIZ-6 | **P-BIZ-12**:文旅场景模板可发起定制单并走验收(**P0 子集=场景需求 CRUD + 模板复用 + 专属看板必交**,后端 API 层本波验收;P1 快速交付增强列 M4) |
### 6.3 工程门(两模块共用,逐项可勾)
`mvn -pl game-module-{name}-server compile 通过` → `单测 XxxServiceImplTest(继承 BaseMockitoUnitTest)绿` → `Flyway migrate 通过(V12/V13)` → `yudao-server 编译通过` → `启动后 doc.html 可见新模块分组` → `Spring 无 @Primary Bean 冲突` → `staging 实测两条具体异步/系统身份写链路不 500(逐条点名,见下)`。
**精确 CLI(execution 版固化,R1 补,避免「Flyway migrate/doc.html/staging」笼统口径)**:
1. **编译 + Flyway**:`mvn -DskipTests clean install -pl game-cloud/game-module-community/game-module-community-server -am`(biz 同理)→ 启动后由 yudao-server 应用层 Flyway 自动 migrate(V12/V13);migrate 一致性校验 = `contracts/db-schemas/` 与 `yudao-server/.../db/migration/` 两处副本**字节一致**。
2. **Swagger 可见**:`curl http://localhost:48080/v3/api-docs?group=community`(biz 换 `group=biz`)返回非空且含新端点。
3. **staging 实测两条异步/系统身份写链路(R3 点名,单测 mock 测不到)**,逐条验「非 Web 线程 / 系统身份写 `updater` NOT NULL 不 500」:
- **链路①=community 通知投递**:上游 notify(或 staging `mock-trigger`)→ 写站内信表,验系统身份注入 `LoginUser(id=0)` 生效、`updater` 落值、不 500;
- **链路②=biz 状态机推进**:admin 推进定制单状态 → 写状态/看板表(含 reward 触发记账若由非 Web 线程触发),验同上。
---
## 7. 待确认项(每项带推荐)
> 标 **⭐** 者为创始人战略决策(已在 §0.1 汇总);未标者属实现细节,可由 execution 版单 agent 直接按推荐落地。
| # | 决策 | 推荐 |
|---|---|---|
| 1 ⭐ | **D-A · biz 形态:轻量 lead form 先行 vs 全 biz** | **lead form 先行**(6 P0 无一依赖 pay/trade;复用面大;命中近期现金线;资金流并入 M4) |
| 2 | community 与 biz 建设次序 | **先 community 后 biz**(community 触达机制最独立、无外部业务卡点、复用面集中、风险最低;biz 外部对接多 + 受 pay 桩制约,适合后段)。<br/>注:M5=MVP 全链路交付里程碑(`.agents/knowledge/mvp-scope-and-milestones.md` §5,6/27 Day15),通知底座是全链路收尾必备项;此处不以「被 M5 依赖」作排序依据(M5 含全部模块,为同义反复),改以上述可自洽验证的独立性/风险理由排序 |
| 3 | **community 触达机制(R1 已定型,供拍板复核)** | **同进程 `-api` notify-push**(§3.3):新增 3 上游 notify 调用点 + `CommunityNotifyApi` 契约,复用 `@Primary`,**不建 MQ、不动 events.schema.json**;上游未接通前以 staging 限定 `mock-trigger` 端点独立验收 |
| 4 | biz P0 清单 + Wave4 总数确认 | biz 本波 **6 P0 = P-BIZ-01/02/03/04/08/12**;**P-BIZ-11 学生防沉迷经 Doc C 已划归 compliance(compliance +1 P0),不计入 biz**;**Wave4 总交付 = 11 P0 = community 5 + biz 6** |
| 5 | P-BIZ-12 的 P0 必交子集边界 | **场景需求 CRUD + 模板复用 + 专属看板 = P0 必交**(与 P-BIZ-01/03 同能力栈、无额外定制);P1「快速交付增强」列 M4 后增量 |
| 6 | BIZ-08/12 运营 UI 入口(game-admin / WS4)是否排期 | **本波 biz 后端 API 独立验收(Swagger/curl);UI 入口(运营代客发起=WS4 / 客户看板=WS3)列完成条件增强项**,与对应工位排期绑定走 UI 走查,**不阻塞本波 biz 收口** |
| 7 | 是否允许两模块并行建设 | 模块内实现可并行;**全局单序列资源(root pom / yudao-server pom / Flyway 序号 / 错误码段 / 契约 yaml 文件名)一次性预占后串行收口或单 agent 聚合接线**(见 §8 execution 预告第 0 节);**events.schema.json 本波不改** |
| 8 | **P-INC-01 计数权威源落地方案(R2 补,二选一须显式定)** | **(a) project 发布成功后同进程 notify community 计数**(与三上游同范式,**推荐**——上游单点改动、与现行 `@Primary` notify 一致);或 (b) community 经 ProjectApi 新增 `countPublishedByCreator(creatorUserId)` 主动读数。**两者都不依赖 feed 计数 seam**,均可让 community 独立验收。**此项可由 execution 版 §0 单 agent 直接拍 (a) 落地,无须创始人介入**(属实现细节,列此仅为消除 execution 期重复思考) |
| 9 ⭐ | **D-B · community 功能优先级:通知底座最小集 vs 全量 T-CMU**(§3.2) | **通知底座最小集**——T-CMU-10 编排+T-CMU-06 站内信+T-CMU-11 等级引擎(+可选短信),一次覆盖 5/5 P0;SOC/GRW 域(关系图谱/排行/成就/扇出,全 P1/P2)仅留接口桩,不实现 |
| 10 ⭐ | **D-C · 是否本波即接 ip seam(D5 已定「寄宿 compliance、不独立建模块」)** | **本波不接、维持 D5 推迟**——Wave4 仅 P-BIZ-02 协同涉及 IP 模板市场 T-IP-09,而本波 P-BIZ-02 复用 aigc 4 模板(绕开 T-IP-09),故无需 ip seam;待真做素材/IP 市场或 DMCA 下架(P-IPX-05,P1+/远期)再接(证据见 §0.1 D-C 行脚注) |
---
## 8. 下一步(双 spec 铁律)
创始人拍板后出 execution 版(`2026-06-11-Wave4-community-biz-execution.md`),展开:
- **§0 全局单序列资源预占登记(R1 补,开篇第一节,一次性写死锁定,防 V12 双占/文件名碰撞/错误码撞段)**:一个可勾的中央登记动作,由**单 agent 收口提交**——
- Flyway:`community = V12.0.0__create_game_community.sql` / `biz = V13.0.0__create_game_biz.sql`(**含 W4 埋点加列 V14 占位**);**两处副本**(`contracts/db-schemas/` 与 `yudao-server/.../db/migration/`)须**字节一致**;
- 错误码段:`community = 1_107` / `biz = 1_110`(已在 `contracts/README.md §四` 在册,复用即可,`-api` 的 `ErrorCodeConstants` 登记);
- 契约文件名:`contracts/api-schemas/community.yaml` · `biz.yaml`(独占新文件);
- 预占前置校验(R2 校正:Flyway 占用须**按文件名而非内容**判定,否则会被 V11 让位声明注释绊住):
- **Flyway 文件名占用校验(应全为空/无此文件,exit≠0 即未占用)**:`ls contracts/db-schemas/V12* contracts/db-schemas/V13* game-cloud/yudao-server/src/main/resources/db/migration/V12* game-cloud/yudao-server/src/main/resources/db/migration/V13* 2>/dev/null`;
- ⚠️ **不要用 `grep -r 'V12\|V13' …` 判占用**——它是内容匹配,会命中 `V11.0.0__create_passport_player_invite.sql:13` 的让位声明注释「…Wave4 community/biz 原规划的 V10/V11 顺延 V12/V13」(两处副本各一行,实测返 2 行非 0),**命中仅此注释属预期、非真实占用**;
- **错误码段占用校验(应全返 0)**:`grep -rn '1_107\|1_110' game-cloud/`。
- **克隆 5 步骨架**(cp 黄金模块 → 改 3 个 pom artifactId → **① root `game-cloud/pom.xml` 注册 `<module>`(独立一步,缺则父 pom 找不到)+ ② `yudao-server/pom.xml` 注册 `-server` 依赖** → -api 写 ErrorCodeConstants(1_107/1_110)+ApiConstants(改 community-server/biz-server 防 Feign 服务名撞 project)+枚举+DTO+Feign → -server 写 controller/service/dal/convert/ApiImpl@Primary);execution 版列出特定 artifactId;
- **community 通知底座 DDL(V12,含 reward 触发记录表)+ `CommunityNotifyApi` 契约 + 三上游 notify 调用点接入(aigc/compliance/trade 各 1 处,提交后同进程)+ 等级引擎触发(计数权威源=project;§0 由单 agent 直接拍 §7 第 8 项推荐方案 (a)=project 发布成功后同进程 notify community 计数,无须创始人介入)+ staging 限定 `mock-trigger` 联调端点**;
- **biz lead form DDL(V13)+ 轻量状态机定义(`BizLeadStatusEnum` + service 流转校验,范本=project `ProjectStatusEnum`/`ProjectServiceImpl`)+ 模板预览取包接线(含取包失败/无产物/versionId 缺失负路径降级)+ 进度看板/admin 队列后端 API**;
- **契约 community.yaml/biz.yaml 端点清单**(/app-api/community/* + /admin-api/community/*(含 `CommunityNotifyApi` Feign + `mock-trigger`);biz 偏 /admin-api/biz/*);
- **Nacos 模块配置注册(R1 补,对齐 add-business-module.md 步骤 6)**:community(短信发送开关 `community.sms.enabled` / 里程碑阈值 `community.level.publish-threshold`)+ biz(状态机节点超时 `biz.state-machine.timeout-hours`);`deploy/nacos/` 导入并验证 Nacos 控制台可见;
- **审计列系统身份注入范本预置(LoginUser(id=0)+finally clearContext)+ staging 实测计划(点名两条异步/系统身份写链路,见 §6.3)**;
- **M4 留债登记段(两类债分离,R1)**:**①资金流债**(biz:T-BIZ-08 收费、T-BIZ-03 在线签章;community:reward 记入钱包/流量包 = trade `IncentiveCreditApi` + feed 流量包分配 `-api`、现金真实到账)= M4 留债清单;**②前端 UI 入口债**(biz 运营代客发起 UI=WS4 / 客户看板=WS3)= 跨工位协调(可与本波并行),走 UI 走查闭环。
收口后按 §7 知识沉淀铁律:把 Wave4 克隆实证回写 `.agents/skills/add-business-module.md`(把 107/110 从「推断顺延」升为「已落地」、补 root pom 注册为独立步、补「跨模块通知优先同进程 `-api` notify-push 而非 MQ」范式)+ 新踩坑,更新 `.agents/README.md` 索引。
---
*证据边界(2026-06-11 实查):Flyway ceiling = V11(contracts/db-schemas 与 yudao-server/.../db/migration 两处一致,最高 V11.0.0__create_passport_player_invite.sql);contracts/api-schemas 现 10 份,community.yaml/biz.yaml 不存在(可新建无冲突);现有 9 个 game-module-*(ad/aigc/compliance/feed/project/runtime/studio/telemetry/trade);错误码段实占 grep 确认 1_107(community)/1_110(biz) 均 0 占用、空闲。P0 计数依 Doc A 产品需求清单.md / Doc B 技术架构与模块.md / Doc C 需求模块映射.md 三文档交叉核对(community=5 / biz=6;P-INC-01 映射 = `T-TRD-12 发放 · T-FED-03 流量 · T-CMU-10`,line168;P-BIZ-11 主模块=compliance `T-CMP-36/37`,line216)。BPM/Flowable 装配状态、pay/trade 真实度引自 docs/mvp/MVP进度总账.md(标注为桩),属现状引用非本审计动作。错误码段权威源 = contracts/README.md §四 line79(engineering-conventions.md §1.3 措辞为「推断顺延」,以 README/契约固定值为准)。*
*R1 整改新增实查证据(2026-06-11):①跨模块事件基建——`grep publishEvent/ApplicationEvent/RocketMQTemplate` 在 compliance/trade 全空(零域事件);9 个 game-module 无 MQ 生产者目录;唯一 MQ 消费者 `TelemetryEventConsumer.java` 无 `@RocketMQMessageListener` 注解、类注释「MQ 消费 待对接」(telemetry MQ 从未跑通);真范式 = `FeedServiceImpl` 注入 `ProjectApi`/`RuntimePackageApi`(`@Primary` 本地实现)。②events.schema.json eventRegistry 仅 telemetry 事件,`generate_succeeded/income_settled` 为前端遥测串非后端域事件;`TelemetryEventEnum` 为上报真守门人(engineering-conventions §「契约事件登记须同步后端枚举守门人」)。③轻量状态机范本——无独立 `ProjectStateService.java`;实为 `ProjectStatusEnum`(`isDraft` 等流转判定)+ `ProjectServiceImpl` 写前流转校验。④runtime 取包有失败态——`RuntimePackageApi`/`ErrorCodeConstants` 含 1-102 段错误码(`RUNTIME_PACKAGE_NOT_READY` 1-102-001-001)+ `BuildRespVO.failCode/failReason`。⑤feed/trade 契约缺口——`feed.yaml` 仅 `boost`/`pinned`(无流量包发放端点)、`trade.yaml` 无 grant/发放/incentive 端点;`feed.yaml` line84「累计计数最终态归 project/community」指归属权非反写。⑥project 侧——无 `project_published` 事件、无 `publishCount` 字段,ProjectApi 现有方法 = getCreatorUserId/getCurrentVersionId/getStatus/getFeedMeta/createProject/unlistIfPublished。*
*R2 整改新增实查证据(2026-06-11):①§0 预占校验口径——`grep -r 'V12\|V13' contracts/db-schemas/ game-cloud/yudao-server/.../db/migration/` 实测**返 2 行**(`V11.0.0__create_passport_player_invite.sql:13` 让位声明注释,两处副本各一行),非「应全返 0」;改用 `ls V12*/V13*`(实测 exit=2、空,确未占用)。②runtime 取包真实路由——`AppRuntimeController` 实暴露**两端点**:`GET /package/{versionId}`(line60,返 `RuntimePackageRespVO` 清单 meta)+ `GET /package/{versionId}/manifest`(line75,返原始 JSON),门禁同源(同走 `getPackageManifest`);原稿简写 `/package/{versionId}` 是真实端点之一但漏列 `/manifest` 那条。③`add-business-module.md` 步骤表(行 66)实测**仅 `步骤5=yudao-server/pom.xml`、无 root `game-cloud/pom.xml` `<modules>` 注册步**,开头明写「无需改 Application/yaml,只需 yudao-server/pom.xml 加依赖」——`grep 'game-cloud/pom.xml\|<modules>\|聚合 pom'` 该文件**零命中**,证实 skill 待回写。④M5 定义——`.agents/knowledge/mvp-scope-and-milestones.md:101` 与 `docs/mvp/MVP进度总账.md:32` 均定义 M5=「MVP 交付」全链路里程碑(6/27 Day15),故「community 被 M5 依赖」属同义反复(M5 含全部模块),改以独立性/风险理由排序。⑤ip seam(D-C 决策证据)——`docs/mvp/MVP进度总账.md:53` D5 决策「ip 不独立建、seam 寄宿 compliance」、line95 标推迟 TODO;`需求模块映射.md` 中 Wave4 11 P0 仅 P-BIZ-02 协同含 `T-IP-09 模板市场`(line207),community 5 P0 零 ip 关联,本波 P-BIZ-02 复用 aigc 模板即不触 T-IP-09。⑥§266 与 §239/§322 矛盾——§239 影响面表与 §322(§7-7)均明写 events.schema.json「本波零改/不改」,§266 兼容性段却把它列入「须串行收口的公共写点」,自相矛盾,已订正。*
---
## 评审整改纪要(R1)
> 第一轮对抗评审 findings 逐条处置(2026-06-11)。**接受=已改对应节;部分接受/重定向=采纳问题但解法调整,理由在备注;拒绝=保留原口径,理由在备注。** 多条近重复 finding 合并处置(标 ⊃)。
**贯穿性裁决(一处定死多条收敛)**:本轮多条 finding(blocker events.schema.json / high「事件基建不存在」/ R1 / 多条 medium 触发机制)实为**同一个未决架构点的不同侧面**:community 用什么机制触达。R1 据实查证据**一次定死 = 同进程 `-api` notify-push**(§3.3)。此决策的连锁后果:(a) community **不走 events.schema.json**,故 blocker「先在 events.schema.json 注册三事件」的前置门禁**不再需要**——但其底层关切(只改契约不改枚举=静默 rejected)以「保留口径」写入 §3.3 以备未来 MQ 路线;(b) 上游不发域事件,改为新增 3 个 notify 调用点,工作量画像在 §3.3 表与 §4 显式登记;(c) INC-01 计数权威源改 project、记账留 M4,消除孤儿子链路。
| Finding(severity|定位) | 处置 | 改了哪 / 理由 |
|---|---|---|
| 事件基建整体不存在,应框为「机制未建+选型未定」(high|§2.2/§3.3/R1) ⊃ blocker(events.schema.json 三事件未注册) ⊃ medium(事件消费手工触发形态未明 §3.3/§6.1) | **接受(核心整改)** | §0/§2.1/§2.2/§3.3 全面重写:机制定型为同进程 `-api` notify-push;§3.3 新增四维对比表 + 工作量画像 + staging `mock-trigger` 端点;R1 行改为「已缓解」。blocker 关切以「保留口径」并入 §3.3,不再设 events.schema.json 前置门禁(本波不改该文件)。 |
| P-INC-01 触发+记账依赖 trade 发放/feed 流量是半截孤儿链路(medium|§2.2/AC-CMU-5) ⊃ high(feed 流量包/trade 发放无 -api 契约) ⊃ medium(INC-01 计数权威源歧义 A/B 两路) | **接受** | §2.2 新增「P-INC-01 落地形态」段:计数权威源**定为 project 发布成功**(二选一:project notify / `ProjectApi.countPublishedByCreator`),**不依赖 feed 计数 seam**;「记入钱包/流量包」本波只写自有 reward 触发表,跨模块写(trade `IncentiveCreditApi`/feed 流量包 `-api`)连同现金到账**留 M4**;AC-CMU-5 口径相应收紧。 |
| AC-BIZ-3/5 硬依赖 game-admin UI 而 admin 端零视图(medium|§6.2/R5) ⊃ high(AC-BIZ-5 依赖 WS4 未排期 UI 违反独立可交付) | **接受** | §6.2 加「UI 落点与跨工位」前置说明 + AC-BIZ-3/5 拆两层(后端 API 本波独立验收 / UI 入口列完成条件增强项绑 WS3·WS4 排期走 UI 走查);§1 非目标补「两类债分开记账」;§7 第 6 项同步。 |
| biz 模板预览只描述 happy path,缺取包失败影子路径(medium|§2.3/AC-BIZ-2) | **接受** | §2.3 加「B-02 模板预览影子路径」段(无产物/编译失败/versionId 缺失降级 + 状态机不许无 demo 推进);AC-BIZ-2 补负路径必验口径。 |
| Flyway V12/V13、错误码、yaml 文件名缺中央预占登记动作(low|§4/R6/R8/§8) | **接受** | §8 新增「§0 全局单序列资源预占登记」为 execution 开篇第一节(含字节一致 + grep 前置校验 + 单 agent 收口)。 |
| 异步写链路 staging 实测未点名 community 通知/biz 状态机两条(low|§6.3/R3) | **接受** | §6.3 把两条异步/系统身份写链路逐条点名为必测项;R3 行同步具体化。 |
| §2.4「唯一公共装配点」与 §4 blast radius 不一致,漏 root 聚合 pom(high|§2.4/§4) ⊃ high(克隆 pom 注册点不清,未明 game-cloud vs yudao-server) | **接受** | §2.4 改为「公共装配点=两处」(① root `game-cloud/pom.xml` 注册 `<module>` ② `yudao-server/pom.xml` 注册 `-server` 依赖);§4 首行同步细化;§8 克隆步骤把 root pom 注册列为独立一步。 |
| P0 计数术语含糊,未陈述 Wave4 总新增 P0 数(high|§2.3/§7-4) | **接受** | §0 与 §2.3 明确「Wave4 总交付 = 11 P0 = community 5 + biz 6;P-BIZ-11 经 Doc C 划归 compliance(compliance +1 P0)」;§7 第 4 项同步。 |
| BIZ-02 轻量状态机「替代 BPM」是缩水还是等价替代表述不清(high|§2.3/R2) | **接受** | §3.1 新增「轻量状态机 vs BPM 是等价替代而非缩水」澄清段(本波 6 P0 仅需单线性五态、多线流程留 M4+Flowable);R2 行重写为「等价替代」。 |
| Flyway V12/V13 三处一致性未验证(medium|§4/R6/§6 脚注) | **接受** | §6 脚注已记两处 ceiling=V11 一致;§8 §0 预占段补 `grep -r 'V12\|V13'` 两处应返 0 + 字节一致校验。 |
| BIZ-03 客户端入口形态未明(medium|§2.3/§6.2) | **接受** | §2.3 表 P-BIZ-03 行 + AC-BIZ-3 明确客户侧入口 = game-studio `/biz-orders/my-orders`(WS3 横算)或邀请链。 |
| §1 非目标(资金流 M4)与 R5(UI 入口缺失)混为一个债项(medium|§1/R5/§7) | **接受** | §1 非目标新增「两类留债分开记账」(①资金流债→M4 ②前端 UI 入口债→跨工位并行);§8 M4 留债段同结构分离。 |
| feed↔community 计数回流机制不清(反写/读表/异步事件)(medium|§4) | **接受(重定向)** | 采纳「机制须写死」,但**计数权威源改 project 后此 seam 取消**:§4 加澄清「feed.yaml line84 指归属权非反写」,旧「feed 信号→community 计数串行依赖」项删除,community 不再被 feed 计数回流卡成串行。 |
| §6.3 工程门缺 exact CLI(medium|§6.3) | **接受** | §6.3 补三组精确 CLI(`mvn -DskipTests clean install -pl ... -am` / `curl .../v3/api-docs?group=community` / staging 两链路实测)。 |
| §8 遗漏 Nacos 模块配置注册落点(low|§8) | **接受** | §8 补 Nacos 配置注册条(community.sms.enabled / community.level.publish-threshold / biz.state-machine.timeout-hours + deploy/nacos 导入验证)。 |
| P-BIZ-12 P0 子集「可下单+进度看板」模糊、与 01/03 是否重叠(low|§2.3/§7) | **接受** | §2.3 表 P-BIZ-12 行 + AC-BIZ-6 + §7 第 5 项收紧为「场景需求 CRUD + 模板复用 + 专属看板(与 01/03 同能力栈、无额外定制)」;P1 列 M4。 |
| T-PRJ-06 轻量状态机引用不精确,缺代码路径(low|§2.4/§3.1/§8) | **接受(含修正事实错误)** | 实查**无 `ProjectStateService.java`**——全文将「复用 T-PRJ-06/ProjectStateService」更正为「复用 project 范式 = `ProjectStatusEnum`(流转判定)+ `ProjectServiceImpl`(写前流转校验)」(§2.1 图/§2.3 图/§2.4/§8)。原 finding 建议的「ProjectStateService.java」落点亦不存在,故按实情改。 |
**无「拒绝」条目**:本轮 24 条 findings 全部接受或部分接受/重定向。其中 3 条(feed↔community 计数机制、blocker events.schema.json 前置门禁、T-PRJ-06 引用)采纳了问题本身但**解法与原 suggestion 不同**——因实查证据显示原 suggestion 的落点(feed 反写、events.schema.json 路线、ProjectStateService.java)与本仓现状或更优范式不符,已在上表备注理由。
---
## 评审整改纪要(R2)
> 第二轮对抗评审 findings 逐条处置(2026-06-11,6 条:medium×2 + low×4)。**全部经实查仓库核实后接受**(含 1 条事实订正:M5 实有定义)。逐条证据见文末「R2 整改新增实查证据」脚注。
| Finding(severity|定位) | 实查核实结论 | 处置 | 改了哪 |
|---|---|---|---|
| §8 §0 预占校验 `grep -r 'V12\|V13'` 不返 0(会命中 V11 让位声明注释 2 行),实现者开篇第一步即被自身注释绊住(low|§8 §0 行334) | **属实**:实测返 2 行(`V11…invite.sql:13` 两处副本各一行,内容匹配非文件名匹配);`ls V12*/V13*` 实测 exit=2 空 | **接受** | §8 §0 校验口径改「按文件名」:用 `ls contracts/db-schemas/V12* … V13* …`(应空/exit≠0);显式标注「不要用 grep 内容匹配,命中仅 V11 让位声明注释属预期非占用」;错误码段仍用 `grep -rn '1_107\|1_110'`(应返 0) |
| `add-business-module.md` 旧版仅列「步骤5=yudao-server pom」、未把 root `game-cloud/pom.xml` `<modules>` 注册列独立步,与本 spec §8「装配点=两处」口径不一致,只读 skill 者漏 root pom 注册触发 compile 失败(low|§2.4/§8/skill 行66) | **属实**:`grep 'game-cloud/pom.xml\|<modules>\|聚合 pom'` 该 skill **零命中**;步骤表开头明写「无需改 Application/yaml,只需 yudao-server/pom.xml 加依赖」 | **接受**(不改 skill 内容,加 spec 提示——回写 skill 维持「本波收口后」原计划,避免开工前动 canonical) | §2.4 加 ⚠️ 硬提示:『skill 步骤表尚未含 root pom 注册(待本波收口回写),本波克隆以本节「装配点=两处」为准,勿以旧 skill 为唯一依据』;execution 版克隆步骤开篇须复述 |
| biz 取包端点写 `/app-api/runtime/package/{versionId}`,真实路由是带 `/manifest` 后缀那条;属端点精度问题不影响可行性结论(low|§2.3 B-02 行161/AC-BIZ-2 行293) | **部分属实**:实查 `AppRuntimeController` 两端点**都存在**——`GET /package/{versionId}`(line60,清单 meta)+`GET /package/{versionId}/manifest`(line75,原始 JSON);原稿写的是真实端点之一,但漏列 `/manifest` 那条 | **接受**(按真实双路由钉准,不止补 /manifest) | §2.3 B-02 新增「取包端点真实路由」段,列两端点真实路由+用途+门禁同源;AC-BIZ-2 同步双路由;提示 execution 版端点清单两条写全 |
| §266 兼容性段把 events.schema.json 列为「须串行收口的公共写点」,与 §239/§322「本波零改该文件」自相矛盾(medium|§266 vs §239 vs §322) | **属实**:§239 影响面表标「本波零改/无」、§322(§7-7)标「本波不改」,§266 却列入公共写点 | **接受** | §266 改写:公共写点仅 root pom/yudao-server pom 须串行收口;events.schema.json 本波零改、无写点竞争、不在串行收口范围(显式标 R2 校正消歧义) |
| §7 第 2 项「community 被 M5 依赖」中 M5 未在全文定义,理由对拍板无参考且不可验证(medium|§312 待确认项 2) | **订正**:M5 **实有定义**(`mvp-scope-and-milestones.md:101`/`MVP进度总账.md:32` = MVP 全链路交付里程碑 6/27 Day15);但「被 M5 依赖」确属同义反复(M5 含全部模块) | **接受**(采纳 suggestion(b):改自洽可验证表述) | §7 第 2 项改:以「触达机制最独立、无外部业务卡点、复用面集中、风险最低」排序;补注 M5 定义+说明不以「被 M5 依赖」作依据(同义反复) |
| P-INC-01 计数权威源「二选一」(project notify vs ProjectApi.countPublishedByCreator) 在待确认项未显式决策,execution 期或重复思考(low|§2.2/§7-2~4) | **属实**:原稿仅在 §2.2 正文列二选一,§7 无对应决策行 | **接受**(采纳 suggestion:补 §7 决策点 + 标「可由 execution §0 单 agent 直接拍 (a)」) | §7 新增第 8 项(计数权威源二选一,推荐 (a) project notify,标可单 agent 定无须创始人介入);§8 §0 同步标「单 agent 拍 (a)」 |
**R2 顺带补强(非 finding 直接要求,但同一处自洽性收口)**:①新增 §0.1「待创始人拍板的关键决策」汇总段,提炼 **D-A(biz 形态)/D-B(community 功能优先级)/D-C(ip seam 是否本波接)** 三项战略决策置顶供拍板;②§7 表给战略项标 ⭐ 并与 §0.1 互链,区分「创始人决策项 vs execution 单 agent 可定的实现细节」;③据实查(D5 决策「ip 寄宿 compliance」+Wave4 仅 P-BIZ-02 沾 T-IP-09 且本波复用 aigc 模板绕开)补 D-C 推荐「本波不接 ip seam、维持 D5 推迟」,并入 §7 第 10 项。
**无「拒绝」条目**:R2 全 6 条接受(含 1 条事实订正后仍采纳其改进诉求)。三处「采纳但解法/事实微调」(取包端点真实是两条非一条、M5 实有定义、skill 回写维持收口后计划改为加 spec 提示)已在上表「实查核实结论」列说明。

File diff suppressed because one or more lines are too long

View File

@ -63,7 +63,7 @@
| # | 链路 | 状态 | 真实进度 |
|---|---|---|---|
| 1 | 创作 → 生成 → 预览试玩 | 🟢 | **前端 UI 实证全绿(2026-06-11 走查 HJ-FE-WALK-001)**:一句话→4 模板(clicker/merge/idle/tycoon)→开始生成→`studio/generate`→`/task`轮询(0%)→~18s→`/preview` canvas 500×700 可玩(《霓虹深空实验室》能量0/5),鉴权守卫/受控输入/异步轮询/自动转场/沙箱 runtime 逐项真实,0 error |
| 2 | 发布 → 审核 → 游戏流 | 🟢→🟡 | **创作者发布段 UI 已通(2026-06-11 HJ-FE-WALK-001 修+重走查闭环)**:去发布→选专区(创作广场UGC)→提交发布→「✓已提交审核·进审核队列」截图实证;**逮的 2 缺陷已修+重部署验证**(A `project.currentVersionId` 生成回写 GameVersionServiceImpl=9137 实证 currentVersionId=93109;B `feed/zones` 落实 getZones=返 2 专区、launchZoneId=2 实证;jar 含两修+四模板+M1+回包,admin-api 200 无回归)。**余审核→feed(admin 侧锁风门/审核台 approve)**:UI 发布后置「审核中」status=1,审核通过才入 feed=admin 台surface(未接) |
| 2 | 发布 → 审核 → 游戏流 | 🟢 | **全段 UI e2e 闭合**。创作者段(2026-06-11 HJ-FE-WALK-001):去发布→选专区→提交→「✓已提交审核」+ currentVersionId/launchZoneId 落值。**admin 审核段已闭合(2026-06-11 链路②波,CDP-on-mini 真 UI 走查)**:game-admin 接 staging(:4174)→登录(admin/admin123)→`/wanxiang/review` 队列渲染 3 条真实待审游戏→点 9137「通过」+确认→队列 3→2 条(9137 移除)→DB 实证 `game_project 9137 status=4(PUBLISHED)` + `game_feed_rank(zone2,status1,score0)` 写入(同事务)。sort_score=0 因新游戏无试玩排尾(正确,被玩后 quality 升→上爬,B2′ 已证)。**附带修 admin 构建腐化(vite8→钉 5.1.4 清装)**。观察项:row1 旧 smoke 数据标题乱码(灌数编码,非代码) |
| 3 | 游戏流 → 试玩 → 互动 → 分享 | 🟢 | **真人试玩闭环 e2e 真实(B2′)**:feed(真标题)→点卡→自研 Canvas clicker 真玩通关→game_play_start/end 真遥测→quality→**feed 重排(浏览器实证翻转)**;like/share 接真遥测;真实编译打包/分享落地页=桩 |
| 4 | 广告展示 → 收益 → 钱包可见 | 🟡 | 骨架;**真实广告/pay=桩** |
| 5 | 遥测 → 质量分 → 推荐优化(数据回路) | 🟢 | **数据回路 e2e 真实(B2)**:事件→quality_score→feed 重排(B超A)+重放幂等;MQ 异步化/真实试玩前端待补 |
@ -132,6 +132,8 @@
| **M-c 正式批(merge-prod-20,spec §8-3 验收)** | 2026-06-10 续开:20 条正式批(14 具象+3 跨域+3 模糊,与校准批零重叠)→ **accept 19/20=95% ≥80% 量级过门**(结构 20/20=1.0,infra=0 全自动零中断,830s/57 次 LLM);1 kill=fix 后 P1 残留(QA 门诚实在岗);fix 闭环 3 轮 2 成;金丝雀 10/10 触顶发布+feedVisible(**feed 内容池→50 款**),9 个 accept 按 ≤10/批 配额留池。**观察项**:对抗稳定性复测连续两批 2/3(累计 4/6,样本小、Golden 门 PASS 兜底,prompt v1.1.3 候选议题)。**操作事故复盘**:批产物 tar 回本地入库后 mini 端残留原件卡死后续 pull(git stderr 先出、tail -1 误读「Updating」为成功)→ 首发批秒死「创意文件不存在」;铁律=入库后立清 mini 原件 + pull 看全量输出/HEAD 对账 | `1ab2d15`(创意)+`b7c9d33`(产物);批报告 `runs/merge-prod-20/` | 主 agent 读 report.json 终判+金丝雀 feedVisible 机器验证×10+执行器恢复 banner 双模板 |
| **M-c 模板波批②(HJ-MC-TPL-EXEC-002 idle+tycoon 全链收口)** | 2026-06-10~11:execution 双 spec 四镜头核验整改+创始人拍板不升 patch → 契约+五主题并行建设(runtime 追加 **initIdle 等待型**〔离线补发+在线点击产料+自动产出+升级,finish at targetResource〕+**initTycoon 经营型**〔进货→带客→售出配对赚差价,finish at targetCoin〕;后端纯加行〔白名单+idle/tycoon、资源 Map 两行、getTemplateList 2→4〕;编排器 `_play_idle_once`/`_play_tycoon_once` 双策略;host storage 三端〔contract+GamePlayer 受信边界四闸+inject 应答端〕;Golden harness 六守卫)→ 构建门(四模板 isReady banner `templates=[clicker,merge,idle,tycoon]`+体积 9577B<12288 软门+63 后端测+八资源内嵌 fat jar)→ **五级验收门**:①构建✅ ②在线五 AND✅(idle 200 通关+tycoon 153 通关真包全绿+clicker/merge 回归五绿+果园/奶茶店视觉验收,按钮布局契约双侧 0.28/0.72 实证)③Golden✅(两轮 VERDICT=PASS,**题文不符 kill×四模板+accept 不误杀全稳**)④校准批✅(idle-cal-10 + tycoon-cal-10 各 **accept 10/10**,infra=0,report.json acceptRate=1.0 亲读)⑤金丝雀🟡(idle 10/10 入 feed;tycoon 抽检模型分歧 50%>10% 被 R8 冻结=观察项)。**越模板机制守卫口径终裁**:spec §4.5 起草标 kill→创始人 6/11 拍板 P2 放行(依细则①)→同输入温 0.2 重采样实证**软边界抖动**(idle k-1d1ebeefca 2/4、merge/tycoon 0/4,fresh 回归轮 tycoon 亦翻 kill)→**降为 Golden 观察项**(expectation=observe 不纳入 verdict;两向 fail-safe;prompt 正文零改动仍 v1.1.2;让模型稳定输出 P2 属 v1.1.3 backlog)。**⚠ 遗留债·idle 离线补发运行时未通**(P1:host→iframe `host_to_game` 回包方向运行时未闭合,疑同样波及 ad/pay 回包闭环;优雅降级为无离线态不影响在线;创始人拍板=记债先收口在线核心;专项 follow-up=深调回包链路+验 ad/pay 同断) | `f82d480`(storage)→`91d651e`(后端)→`4864853`(runtime)→`83233d0`(ideas)→`f70cc13`(Golden harness)→`509acb7`(spec v2)→`26319b3`(越模板终裁+Golden 取证);批报告 `runs/idle-cal-10/`+`runs/tycoon-cal-10/`+Golden `runs/golden-regression-v1.1.2-b2/`+收口入本总账 | 主 agent 独立核验:四模板 isReady banner+体积门+八资源内嵌 grep;idle/tycoon 真包五 AND 亲验;两轮 Golden 亲跑 VERDICT=PASS+越模板同输入重采样 12 次取证(idle 2/4 实锤抖动);cal-10 acceptRate=1.0 亲读;执行器恢复 banner 四模板装载 |
| **链路②闭合 + Wave4 评审 spec(Round1 双 lane · Ultracode)** | 2026-06-11 用户选 Ultracode 双 lane 并发:**Lane A**=链路② admin 审核台真 UI 走查闭合(CDP-on-mini:登录→`/wanxiang/review`→点 9137「通过」+确认→项目 PUBLISHED+写 feed_rank)+修 admin 构建腐化(vite8→钉 5.1.4 清装)+admin 接 staging(:4174);**Lane B**=Wave4(community+biz)评审版 spec(4 路 recon→起草→两轮对抗评审 R1 23+R2 6 全接受/重定向→定稿,12 子代理/~39min) | 未提交(工作树改动 + 新 spec 两档 + 走查 harness 三件套入 orchestrator/) | **链路②**:队列 3→2 条(9137 移除)+DB `status=4`/`feed_rank(zone2)` 实证+前后截图;**Wave4**:`HJ-WAVE4-001` spec+评审纪要,3 战略决策(D-A lead form/D-B 通知底座最小集/D-C 不接 ip seam)待创始人拍板 |
> 各波次详细 spec:`docs/agent-specs/`(review + execution 双 spec)。
---