docs(wave3): HJ-BUILD-004 review+execution 双 spec(隔离 staging+contractGaps+发布闸门/治理补全)

经 2 个调研编排(7+6 agent,1.16M token)+ 2 轮对抗审查产出:
- review(HJ-BUILD-004):两主线 + 已锁决策 D1-D5(守草稿+studio / compliance只Gate /
  bucket级最小权限 / 单体+同步+mock token / ip不独立建)
- execution(HJ-BUILD-004-EXEC):Phase A 跨主线收口(R1-R12)消除审查挑出的 blocker——
  publish 唯一化(GAP-1 RespVO 权威,compliance 只交付 Gate seam)、ProjectApi seam 单点收敛、
  封禁不破坏状态机(状态分支)、studio 治理登记、V6/V7 全局 G0、真实 API 路径、实测前置门

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zizi 2026-06-09 00:50:09 +00:00
parent 8901350247
commit 5be2251498
2 changed files with 1200 additions and 0 deletions

View File

@ -0,0 +1,240 @@
# Wave3 Review — 隔离 staging 联调 + contractGaps 回填 + 发布闸门/治理补全
> **HJ-BUILD-004review 版,结论先行供创始人决策)** |生成 2026-06-08
> 上游HJ-BUILD-003 草稿studio+compliance+ip+ Wave1/2 已建脊柱 + 本轮 7-agent 只读调研INFRA/BACKEND/DOCA/GAPS+ 2 轮对抗式审查
> 定位:本文是 Wave3 **主 review**。HJ-BUILD-003 的模块 MVP 切片方法论compliance 狠切、ip 薄壳、接入已建模块带回归门、三相编排)**全部继承**;本文补 003 缺的「隔离 staging 联调 + contractGaps 回填」主线,并把范围决策、审查纠正、验收标准收口到一处。
---
## 一、结论先行5 分钟拍板)
**一句话**Wave3 把已建的 7 模块脊柱从 mock 推进到「隔离 staging 真实联调」,并补发布闸门与治理底座。两条主线、根子不同,建议并行但解耦。
1. **主线1基础设施/联调,方向已验证,方案经加固后可落地)**:在 mini-infra 拉一套与现有服务**物理零交集**的隔离 staging 栈(独立 MySQL/Redis 实例 + 独立网络 172.30/卷/命名空间),回填 4 个 contractGaps按 12 段闭环分「端到端真跑/需桩」验真。
2. **MVP 单体启动只硬依赖 MySQL + Redis**(后端实测 `nacos.config/discovery.enabled=false`)。**Nacos/RocketMQ 非启动前置,本波建议不接线**(容器可拉起备用)。⚠️ 但「不接 RocketMQ 也能启动」**须先在 mini-desktop 实跑验证**(见 §三 前置门 G0
3. **主线2业务补全= 按 Doc A 剩余 P0 补模块。存在范围冲突,须你拍板(§八 D1**。优先级(有硬依赖不可平铺):**compliance > ip > biz ≈ community**compliance 卡在 M3 发布关键路径咽喉、集成面最大。
4. **对抗式审查重塑了方案(§二)**3 个 CRITICAL范围冲突 / 审核归属双写 / member 不在 fork+ 多个高危OOM 兜底、MinIO 软隔离、RocketMQ 启动未验、GAP-4 破坏性、Flyway V6/V7 散落)。**这些纠正已并入本文,是它与 synthesis 初稿的本质差异。**
5. **Doc A 口径(已核对)**55 项 P0已建脊柱覆盖约 30 项;剩余主要在 **studio(7创作主链路)/compliance(6)/biz(6)/community(5)/pay(1)**。**注意:community/ip/compliance/biz 四模块不补创作主链路(studio)**——这是覆盖盲区,是 D1 决策核心。
---
## 二、对抗式审查的关键纠正(必须先看 — 这些改变了执行路径)
| # | 严重度 | synthesis 初稿的错 | 已核实事实 | 本文采用的纠正 |
|---|---|---|---|---|
| C1 | CRITICAL | 主线2 平铺 community/ip/compliance/biz、删掉 studio | 已批准草稿 HJ-BUILD-003 + 创始人 2026-06-08 决策 = Wave3=studio+compliance+ipcommunity/biz→Wave4studio 承接 7 项创作 P0最大未覆盖块 | **范围回退为 D1 决策门**:推荐 studio(min)+compliance+ipcommunity/biz 留 Wave4 |
| C2 | CRITICAL | compliance 新建审核状态机,称对 project「切分归属」 | project 已有并已测 `game_review_record`+`reviewProject()`+`ReviewDecisionEnum`+审核队列9 单测绿) | **compliance MVP 只做锁风门 Gateproject 续拥审核状态机**(§八 D2 选项 B避免拆除已测代码 |
| C3 | CRITICAL | 推荐「解注释 yudao-module-member」启用产品端鉴权 | `yudao-module-member` 源码**不在 fork 内**(裁剪时整体移除,非注释),无源码可解注释 | **产品端 /app-api MVP 走 mock token**`yudao.security.mock-enable`);引入 member 列独立技术项另评估 |
| H1 | 高 | 仅 RocketMQ broker 限 -Xmx | MySQL 默认 innodb buffer pool 按宿主自取、Nacos 未限堆OOM-killer 不保证只杀新栈 | **全容器 `mem_limit` + MySQL `--innodb-buffer-pool-size=512M` + Nacos `JVM_XMX` + 给现有 infra-* 设负 `oom-score-adj`**OOM 优先杀新栈,护住现有服务=铁约束) |
| H2 | 高 | MinIO「additive 新建 bucket 不影响现有」 | yudao 文件存储由 `infra_file_config` 运行时配置;新建 bucket/key/policy 是对**共享 MinIO 实例**的 IAM 写操作;配错越权读写现有对象 | **二选一**bucket 级须给**最小权限 policy**`Resource arn:aws:s3:::game-staging/*`,评审门核验 JSON或采 §八 D3 物理隔离(新起 game-staging-minio 19000/19001 |
| H3 | 高 | 「单体不需 RocketMQ不启不影响启动」当事实 | `starter-biz-tenant` 传递依赖 rocketmq-spring`application.yaml` 配了 name-server=127.0.0.1:9876+producer.groupproducer 是否启动期连无日志证据 | **降级为「待验证」+ 前置门 G0**mini-desktop 用 dev profile 指向不存在 name-server 实跑,看是否阻塞 ContextRefreshed若阻塞则 staging 指向 game-staging-rmq-namesrv 或排除 RocketMQAutoConfiguration |
| H4 | 高 | GAP-4 命名统一是「非破坏,老消费者忽略未知字段」 | events.schema.json 的 envelope/user 是 `additionalProperties:false`严格snake↔camel 是重命名严格字段 | **GAP-4 按破坏性变更处理**:版本并行或同步改全部生产/消费端(前端 SDK + telemetry @JsonProperty + MQ 落库);不能依赖「忽略未知」 |
| H5 | 高 | biz「须核实 bpm fork 保留」 | yudao-module-bpm 在磁盘+根 pom但 yudao-server pom L98 **被注释未装配**Flowable 需 act_* 表,建库步骤未覆盖 | biz 若做须先解注释 bpm + 验证 Flowable 建表(更支持 biz 归 Wave4 不触 bpm |
| M | 中 | V6/V7 靠 classpath 合并,「启动后核实」 | 已核实 V1-V5 在 yudao-server、V6/V7 仅在 ad/trade 模块 jar | **开工即把 V6/V7 集中复制到 yudao-server/db/migration**(前置硬门,非事后) |
| M | 中 | 回滚 `down -v`无备份compose 无 healthcheck连接串机密未定义172.30 未全量核验 | — | 全部并入 §三:备份/重建脚本、healthcheck+`depends_on: service_healthy`、机密走 env 占位、起栈前 `docker network` 全量核验 172.30 |
---
## 三、主线1隔离 staging 拉起 + contractGaps 回填
### 3.1 隔离建栈mini-infra物理零交集 + 加固)
| 容器 | 镜像(占位,须按兼容矩阵二次定版) | 宿主端口 | 内存硬限H1 加固) |
|---|---|---|---|
| game-staging-mysql | mysql:8.4.8(对齐现有) | 13306→3306 | `mem_limit=1.5g` + `--innodb-buffer-pool-size=512M` |
| game-staging-redis | redis:7-alpine | 16379→6379 | `mem_limit=512m` + `--maxmemory 384mb` |
| game-staging-nacos | nacos/nacos-server:v2.x | 18848/19848/19849 | `JVM_XMS=256m JVM_XMX=512m` + `mem_limit=768m` |
| game-staging-rmq-namesrv | apache/rocketmq:5.x | 19876 | `mem_limit=512m` |
| game-staging-rmq-broker | apache/rocketmq:5.x | 10911/10909/10912 | `-Xmx1g` + `mem_limit=1.5g``brokerIP1=100.64.0.8` |
- **网络**:独立 bridge `game-staging-net` 子网 172.30.0.0/16起栈前 `docker network ls`+全量 `inspect` 核验含 stopped 网络无占用;查 daemon.json default-address-pool。不接 infra-shared。
- **卷**`game-staging-*` 具名卷 / `/srv/game-staging/*` 全新目录;绝不挂 `/srv/infra/*`
- **OOM 保护**:给现有 infra-mysql/redis/minio/ragflow 设负 `oom-score-adj`(或给新栈正值),确保 OOM 优先杀新栈。**分步起:先 MySQL+Redis 起且 `free -g` 观察基线 → 再按需起 Nacos/RMQ。**
- **healthcheck**各容器加mysqladmin ping / redis-cli ping / nacos actuator / namesrv 端口探活broker `depends_on: {namesrv: condition: service_healthy}`;后端启动前 `wait-for-it` 探 13306/16379。
- **MinIOH2**:默认 bucket 级隔离须最小权限 policy`game-staging/*`bucket 名加随机后缀,留 `mc admin policy info` 证据;或采 D3 物理隔离。
- **备份/回滚**staging 数据定位为「可幂等重建」→ 提供自动化重建脚本(建库+导基表+Flyway回滚用 `docker compose -p game-staging down`**不带 -v**,保卷留证据),确认无用再单独 `volume rm`;钱财闭环验通后 `mysqldump``/srv/game-staging/backup`
### 3.2 建库 + 框架基表(启动硬前置)
1. 建空库(默认 `ruoyi-vue-pro`单库game_* 与 yudao system_*/infra_* 共 schema 前缀区分)。
2. **先手动导入 yudao 框架基表** `game-cloud/sql/mysql/*.sql`system_*/infra_* 含 tenant/user/menu/dict/file_config + 初始租户/管理员),否则多租户/登录/Swagger 不可用;此步使库非空,匹配 Flyway `baseline-on-migrate``tenant.enable=true` ⇒ game_* 带 tenant_id须有效租户。
3. **V6/V7 集中复制为前置硬门**M开工即把 `V6.0.0`/`V7.0.0` 从 ad/trade 模块复制到 `yudao-server/.../db/migration`,与 contracts/db-schemas diff 一致;构建后 `unzip -l yudao-server.jar | grep migration` 核验 7 个文件在 `BOOT-INF/classes/db/migration`
### 3.3 后端接线(新建 application-staging.yaml不改 dev/local
- `jdbc:mysql://100.64.0.8:13306/ruoyi-vue-pro`dev 默认 127.0.0.1:3306 必改)
- `spring.data.redis host=100.64.0.8 port=16379 password=${STAGING_REDIS_PWD}`dev 默认公网 400-infra 必改)
- 机密走**环境变量占位**`${STAGING_DB_PWD}` 等),避免明文入库
- **no_proxy/nonProxyHosts 放行 100.64.0.0/10**mihomo 代理占 7890/9090防 jdbc/redis/nacos 被代理劫持)
- nacos/rocketmq本波 `enabled=false` 保持(除非 D4 选接线)
### 3.4 启动验证前置门(按序)
- **G0H3最先**mini-desktop 用 dev profile 指向不存在 name-server 实跑 `java -jar`,确认 producer 不阻塞 ContextRefreshed。阻塞则 staging 指 game-staging-rmq-namesrv 或排除 RocketMQAutoConfiguration。
- **G1**:在 mini-desktop15G避免本机 OOM`mvn -pl yudao-server -am clean package`JDK17/Maven3.8.7)。
- **G2**`java -jar --spring.profiles.active=staging`Flyway 自动建 13+ 张 game_* 表。
- **G3 自检**`/doc.html` 出齐 7 模块接口 + `/actuator/health` db/redis UP + `flyway_schema_history` ≥7 条 success + game_* 表全建。
- **G4 鉴权**:管理端 `/admin-api/system/auth/login`**产品端 /app-api 走 mock tokenC3member 不在 fork**。
### 3.5 分段联调验真12 段)
- **端到端真跑(先验)**(5)审核状态机 /(6)feed 按 zone SQL 出流 /(7)试玩 session /(8)互动 /(9)广告 mock 计费ecpm/1000幂等键 trace_id+event_type+tenant_id
- **钱财闭环(次验,手动触发)**(10)trade T+1 结算xxl-job 关闭,手动调 SettlementJob→聚合×0.80→game_trade_income uk 幂等+累加 balance/(12)提现≥500 且≤balance原子冻结→mock 打款)。
- **必须桩(最后串)**(2)Dify手动 POST dify/callback 驱动状态机)/(3)runtime 编译(直写 build 成功态+伪 package_url/(4)发布回写 project.game_versionTODO Feign 对接点)/(11)telemetry MQ 聚合(改同步直写绕 MQ
### 3.6 contractGaps 回填4 个Phase A 由契约 owner 定稿后再开真实联调)
- **GAP-1 发布门禁明细**project.yaml publish 仅 CommonResultBoolean → 新增 `ProjectPublishRespVO{admitted, gates: GateResultVO[]}`key/label/pass/code/message。**兼容性:新增字段而非替换 data 类型**。与 compliance 锁风门 -api 联动设计。
- **GAP-2 GamePackage 取包端点**runtime.yaml 仅返 manifest 元数据CSP 沙箱 iframe 无法跨域 fetch。二选一(A 推荐) RuntimePackageRespVO 加 `manifestUrl` + OSS CORS 放行宿主 origin + 宿主 fetch+sha256 注入;或 (B) 新增 `GET /package/{versionId}/full` 返全量 GamePackage。
- **GAP-3 ShareMeta 缺 versionId**ShareMetaRespVO 加 `versionId`=project.currentVersionId前端 onPlay 改 `/play/:gameId/:versionId`(路由已预留,无破坏)。**轻量低风险**。
- **GAP-4 遥测命名H4 破坏性)**events.schema.json(snake) vs telemetry.yaml(camel)。**envelope/user 是 additionalProperties:false重命名是 breaking**。定 camelCase改 events.schema.json须走版本并行或同步改前端 SDK+telemetry @JsonProperty+MQ 落库,迁移完再收紧严格模式。
---
## 四、主线2Doc A 剩余 P0 模块(范围见 §八 D1
### 4.1 模块拆解(错误码/Flyway 已对照 contracts/README §4 预留)
| 模块 | 错误码 | Flyway | P0 | MVP 切片 | 集成面 |
|---|---|---|---|---|---|
| **compliance** | 109 | V9 | 6P-LIC-05/P-OPN-01/04/P-ACC-02/03/P-BIZ-11 | **只做锁风门 Gate**(聚合 aigc T-AGC-19 +可选ip 原子→pass/review/block+ 分级 + 封禁降权;**审核状态机仍归 projectC2/D2-B**;安全硬化 T-CMP-20~38 + 防沉迷/实名 + GDPR 全延后RBAC 复用 yudao | 最大:焊 project.publish(替 mock 门禁+回归门)/feed(降权) |
| **ip** | 108 | V10薄壳可不占 | 0 | **优先答 openQuestion**:若锁风门用 aigc 风格原子即可,则 ip 本波**不建独立模块**,仅在 compliance 锁风门 -api 预留 ip 扩展点(避免孤儿/为 seam 而 seam | 仅被 compliance 单向消费 |
| **studio**D1 选 A 时纳入) | 112须同步登记 README当前漂移 | V8 | 7P-CRT-01/02/04/09/10/12/P-TPL-01/03 等创作主链路) | 草稿最小版(任务链调 aigcMVP 可轮询进度) | 创作侧,调 aigc/project |
| **community**(建议 Wave4 | 107 | V11 | 5P-INC-01/P-NTF-01/02/03/05 | 通知编排+站内信+新人激励;纯事件消费方。**但上游 aigc/compliance/trade 当前不产 MQ 事件(全 TODO且本波不接 RMQ → 本波跑不通** | 隐性:须改事件源生产侧 |
| **biz**(建议 Wave4 | 110 | V12 | 6P-BIZ-01/02/03/04/08/12 | B 端定制 BPM 闭环;**强依赖 yudao-bpm当前注释未装配+ Flowable 建表(建库步骤未覆盖)** | 中:新表+BPM |
### 4.2 接入已建模块 + 回归门compliance 专属,最高危)
- project.publish T-PRJ-05 当前 Wave1 mock → 改调 compliance 锁风门 -api**必须带 project 既有 9 单测 + publish 流程回归断言**Wave2 F5 教训)。
- feed 接降权信号MQ 或同步)。
- **审核归属C2**compliance 不接管 project 审核状态机;仅在 publish 链注入 Gate 裁决。由主 agent 在 Phase C 串行做,不挂新建 agent。
### 4.3 共享资源锁定(开工 Phase A 一次性锁 + 同步 README
- 错误码community=107/ip=108/compliance=109/biz=110/studio=112已用 ad=111/trade=106。**studio=112 与 README §4 当前漂移,须本波同步登记 ErrorCodeConstants+README**。
- FlywayV1-7 已用studio=V8/compliance=V9/ip=V10(薄壳跳过则不占)community=V11/biz=V12Wave4。**一个主版本号一个 owner agent**,主 agent Phase C 校验执行副本与 contracts/ diff 一致。
---
## 五、图示
### 5.1 隔离部署拓扑(与现有 infra 物理零交集)
```mermaid
flowchart LR
subgraph EXIST["mini-infra 现有服务绝不动oom-score-adj 负值保护)"]
EM[(infra-mysql 3306)]
ER[(infra-redis 6379)]
EMO[infra-minio 9000/9001]
end
subgraph STG["game-staging-net 172.30.0.0/16全新隔离栈全容器 mem_limit"]
SM[(game-staging-mysql 13306)]
SR[(game-staging-redis 16379)]
SN[game-staging-nacos 18848 备用]
SQ[rmq namesrv19876+broker brokerIP1=100.64.0.8 备用]
end
subgraph DESK["mini-desktop"]
APP[yudao-server.jar profile=staging 7模块脊柱]
end
APP -->|13306| SM
APP -->|16379| SR
APP -.可选不接.-> SN
APP -.可选不接.-> SQ
APP -->|新建bucket=game-staging 最小权限key| EMO
EXIST -. "零交集:独立网络/端口/卷/命名空间" .-x STG
RES["⚠ 共享面:整机内存9.3G+单磁盘294G → 全容器限内存+oom-score-adj+磁盘水位"]
```
### 5.2 全链路闭环联调时序12 段)
```mermaid
sequenceDiagram
participant C as 创作者
participant ST as studio/aigc
participant DF as Dify(桩)
participant RT as runtime
participant CMP as compliance锁风门
participant PRJ as project.publish
participant FD as feed
participant P as 玩家
participant AD as ad(mock)
participant TR as trade
C->>ST: 1.一句话生成【端到端】
ST->>DF: 2.Dify生成【桩:手动callback】
DF-->>RT: 3.编译打包【桩:直写build+伪url】
RT->>CMP: 锁风门校验(聚合aigc原子)
CMP-->>PRJ: 4.Gate裁决 pass/review/block【替mock+回归门】
PRJ->>FD: project审核状态机=4 进流(归属仍在project)
P->>FD: 5/6.审核后按zone出流【端到端SQL】
FD->>RT: 7.点封面试玩 session【端到端】
P->>FD: 8.赞/藏/享/举报【端到端幂等】
P->>AD: 9.激励视频 mock计费【端到端 ecpm/1000】
AD-->>TR: 10.T+1结算【端到端·手动触发×0.80】
C->>TR: 12.提现≥500 mock打款【端到端原子冻结】
P-->>FD: 11.遥测上报【桩:同步直写绕MQ】
```
### 5.3 模块依赖与 seam
```mermaid
flowchart TD
subgraph BUILT["已建脊柱(接入对象,带回归门)"]
PRJ["project 100审核状态机权威"]
AIGC[aigc 101]
FEED[feed 103]
end
subgraph NEW["Wave3 新建compliance>ip>studiocommunity/biz→Wave4"]
CMP["compliance 109/V9仅锁风门Gate+分级+封禁降权(不接管审核状态机)"]
IP["ip 108/V10薄壳/或并入compliance扩展点先答openQ"]
STU["studio 112/V8创作主链路最小版D1选A纳入"]
end
AIGC -->|T-AGC-19风格原子| CMP
IP -.可选原子.-> CMP
CMP -->|Gate替mock·回归门| PRJ
CMP -->|降权信号| FEED
STU -->|调生成| AIGC
STU -->|建项目| PRJ
```
---
## 六、Blast Radius / 风险 / 兼容 / 回滚
- **主线1 对现有服务**:容器层零影响(独立网络/端口/卷/命名空间,不动现有容器)。**唯一真实间接影响面 = 整机内存(9.3G avail)+单磁盘卷(294G)** → 已用全容器 mem_limit + oom-score-adj + 磁盘水位告警兜底。MinIO 是软隔离面 → 最小权限 policy 或物理隔离。
- **主线2 集成面**compliance 最大(改 project.publish/feed带回归门ip 最小或不建studio 中创作侧community/biz 若做集成面见 §四biz 依赖 bpm 未装配、community 依赖事件源未产)。
- **兼容**GAP-1 新增字段不替换 dataGAP-4 是 breaking 须版本并行;错误码 107-112 不重叠studio=112 须同步 README已合入 Flyway 禁改,回滚写补偿迁移。
- **回滚**staging 栈 `down`(不带 -v 保卷)+ 幂等重建脚本4 模块未接入前可独立删compliance 接入 project.publish 后保留 mock 门禁可切换分支。
---
## 七、验收标准AC
**主线1**AC-S1 隔离性infra-* 容器 inspect 前后 diff=空AC-S2 脊柱启动health db+redis UP、/doc.html 出齐 7 模块、flyway 7 条 success+game_* 全建AC-S3 资源安全(落地后 `free -g` 余 ≥4G、无 OOM 日志、现有服务全 healthyAC-S4 端到端真跑段(审核/feed/试玩/互动/广告计费 5 段+钱财闭环 2 段幂等正确AC-S5 contractGaps 4 个定稿落 contracts/ + 前后端同步。
**主线2**AC-M1 每模块 `mvn -pl <m> -am test` 绿 + 前端 `npm run build` 绿vue-tsc 假门禁不采信AC-M2 P0 逐项可验证(对照 Doc A 编号AC-M3 锁风门接入后 project 既有单测全绿+publish 回归断言通过AC-M4 错误码/Flyway 独占无重叠、执行副本与 contracts/ diff 一致AC-M5 审核归属:状态机权威在 project、compliance 仅注入 Gate一次拒绝→重提交验状态一致
---
## 八、绑定决策2026-06-09 创始人已锁)
> **✅ 决策结果(全部采推荐口径)**
> - **D1 范围 = 守草稿 + studio**Wave3 = 主线1(staging+contractGaps 回填) + **studio(创作主链路最小版, 112/V8) + compliance(只做锁风门 Gate, 109/V9) + ip(seam)****community(107)/biz(110) 留 Wave4**(本轮调研结论归档作 Wave4 预研)。
> - **D2 审核归属 = compliance 只做 Gate**:锁风门 Gate(pass/review/block)+分级+封禁降权;**审核状态机权威仍归 project**compliance 仅在 publish 链注入 Gate不拆/不重写 project 已测代码。
> - **D3 MinIO = bucket 级 + 最小权限 policy**:复用 infra-minio新建 bucket(加随机后缀)+仅绑 `game-staging/*` 的最小权限 key评审门核验 policy JSON。
> - **D4 联调形态 = 单体 + 同步直写 + mock token**:只接 MySQL+Redistelemetry MQ 改同步直写绕过;产品端 /app-api 用 `yudao.security.mock-enable` mock tokenmember 不在 forkNacos/RocketMQ 容器备用不接线。
> - **D5 ip = 不独立建模块**:锁风门用 aigc T-AGC-19 风格原子,仅在 compliance 锁风门 -api 预留 ip 扩展点(避免为 seam 而 seam 的孤儿设计ip 错误码 108/Flyway V10 预留不占用。
>
> **本波净范围 = 主线1(隔离 staging+4 contractGaps) + 2 个新模块(studio min / compliance Gate-only) + 接入 project.publish 回归门。** 下一步:写 execution 双 spec → 二轮 review → 三相 Workflow build。
### 原始待确认项(留档)
> **D1范围最关键**你的「1,2」里 mainline2 = community/ip/compliance/biz与已批准草稿 + 你 2026-06-08 决策Wave3=studio+compliance+ipcommunity/biz→Wave4冲突且漏了承接 7 项创作 P0 的 studio。
- **D1 范围**A) **守草稿staging+contractGaps + studio(min)+compliance(Gate)+ip(seam)**community/biz→Wave4推荐B) 你的字面口径staging+contractGaps + community/ip/compliance/biz无 studio需重评 5-6 并行 agent 与内存容量C) 本波只做 staging+contractGaps模块全下一波。
- **D2 compliance 审核归属**A) compliance 接管 project 审核状态机改黄金模块高危重写B) **compliance 只做锁风门 Gate、project 续拥状态机**〔推荐,避免拆已测代码〕。
- **D3 MinIO 隔离**A) **bucket 级 + 最小权限 policy**推荐省资源B) 物理隔离新起 game-staging-minio(19000/19001)。
- **D4 联调形态**A) **MVP 单体 + 同步直写 + mock tokenNacos/RMQ 容器备用不接线**推荐B) 接 Nacos 配置中心C) 接 RocketMQ 异步链路。
- **D5 ip 形态**(依赖 openQuestionA) **不建独立 ip 模块,锁风门用 aigc 原子 + 预留 ip 扩展点**推荐避免孤儿B) 建 ip 薄壳(确有非 aigc 可覆盖的风格校验时)。
**开放问题不阻塞决策execution 前补)**telemetry quality_score 公式(待产品/数据侧);跨模块 TODO Feign 对接点runtime→project/telemetry 回写本波是否接staging 库名/初始租户/管理员SettlementJob 触发手段Nacos/RMQ 镜像与 spring-cloud-alibaba 2025.0.0.0 兼容矩阵二次定版。
---
## 九、执行路径D1-D5 拍板后)
1. 按 `wave-needs-review-and-execution-spec-pair` 铁律:本 review 二轮确认 → 写 **execution 双 spec**(到命令粒度:隔离 compose+起栈脚本、建库导表脚本、application-staging.yaml、G0-G4 前置门、模块 Phase A 契约锁/Phase B 并行建/Phase C 接入回归)。
2. 三相 Workflow 编排(契约锁→并行建→主 agent 验证门),沿用 Wave1/2 方法论与 6 条教训。
3. 全程:重型构建上 mini-desktop、基建只增量上 mini-infra绝不动现有、主 agent 独立复跑验证不采信子 agent 自报。
---
> 调研原始素材workflow `wf_7bf2fb26-75d`7 agent / 576k tokenINFRA/BACKEND/DOCA/GAPS + 2 轮对抗审查)。本文已并入两轮审查的全部 CRITICAL/高危纠正。

View File

@ -0,0 +1,960 @@
# Wave3 Execution Spec — 隔离 staging 联调 + contractGaps 回填 + compliance/studio 补全
> **HJ-BUILD-004-EXEC**execution 版,给子 agent 照建,命令粒度)|生成 2026-06-09
> **上游**review `HJ-BUILD-004``docs/agent-specs/2026-06-08-wave3-staging联调与P0补全-review.md`,§八 D1-D5 已锁)+ 6-agent 草案staging/gaps/compliance/studio+ 2 轮对抗审查(可执行性 critique[0] + 决策符合性 critique[1])。
> **本 spec 已并入全部 blocker 收口R1-R12**:草案里已核实、可执行的命令级内容予以保留;凡草案与 R1-R12 冲突,一律以 R1-R12 为准。
> **三态标注约定**:【已验证】= 来自代码/契约/README 只读核实;【实测前置】= Phase A/G0 须在内网机实跑后才能定稿的门;【待裁定】= 须主 agent/创始人 Phase A 关闭的开放项。
---
## §0 定位与范围
**一句话**:把已建 7 模块脊柱project/aigc/runtime/feed/telemetry/ad/trade从 mock 推进到「隔离 staging 真实联调」,回填 4 个 contractGaps补 compliance只做锁风门 Gate与 studio创作主链路最小版两个新模块并以回归门把 compliance Gate 注入 project.publish。
**本波净范围D1-D5 已锁)**
- 主线1-Amini-infra 隔离 staging 栈MySQL+Redis**不接 Nacos/RocketMQ/MinIO 容器**MinIO 复用 infra-minio+ 建库导基表 + G0-G4 门 + 12 段联调。
- 主线1-B4 个 contractGapsGAP-1 publish 结构化、GAP-2 取包、GAP-3 ShareMeta versionId、GAP-4 遥测命名)。
- 主线2-Acompliance 模块(**只做 Gate seam**,不建审核状态机表)+ 接入 project.publish。
- 主线2-Bstudio 模块(创作主链路最小版,治理新登记 112/V8
**明确不做(留 Wave4 / P1**community(107)/biz(110) 模块feed 降权出流屏蔽P1本波不生效studio rig/对白树/六路并发资产attachments 透传 aigc本波仅持久化
**锁决策回顾**D1 守草稿+studioD2 compliance 只做 Gate、审核状态机权威归 projectD3 MinIO bucket 级最小权限D4 单体+同步直写+mock tokenD5 ip 不独立建模块、仅留 seam。
---
## §1 Phase A 跨主线收口(开工前必须完成,主 agent 串行)
> **为什么必须先收口**critique[1] 判定 REJECT根因是 3 条主线并行各自改同一权威资产project.submitPublish / ProjectApi / V6V7 / 错误码 005互相覆盖、回归门形同虚设。Phase A 由主 agent 串行把这些「单点」定死,再分发子 agent。
### 1.1 R1 publish 唯一化blocker最高优先
【已验证】`ProjectServiceImpl.submitPublish`(L89-108) 现为 `void` + 抛异常;`AppProjectController`(L75-83) `@PostMapping("/{id}/publish")` 硬编码 `return success(true)`project 现有 9 单测绿。
**唯一权威形态 = GAP-1 的结构化 `ProjectPublishRespVO`**
- `ProjectPublishRespVO { Boolean admitted; List<GateResultVO> gates; }`
- `GateResultVO { String key; String label; Boolean pass; String code; String message; }`
- `submitPublish` 签名 `void``ProjectPublishRespVO`
- **block → `admitted=false` 不抛异常**,由对应 gate 项(`pass=false` + `code`/`message`)携带 block 原因;硬门禁(适龄/compliance=block失败时 `admitted=false` 且不改状态机。
- compliance 锁风门是 `gates[]` 中的一条 `key=compliance``GateResultVO`
**这是 in-place 破坏性契约变更**`project.yaml` publish 端点 200 响应 `CommonResultBoolean` → 结构化信封。publish **只有这一个端点**,改其 `$ref` 即破坏,**不存在「旧 boolean 端点保留」**——删除草案 GAP-1 中"旧 boolean 端点保留/避免破坏既有回归"的错误兼容承诺。前端所有 boolean 消费点(`api/project.ts`/`store/project.ts`/`mock/project.ts`/`Publish.vue`**同批迁移到 `admitted`,无并存期**。
**compliance 侧只交付 seam**`ComplianceGateApi.evaluate(...) → GateVerdict{ verdict:pass/review/block, detail }`(语义等价草案 `ComplianceGateRespDTO`)。**删除 compliance 草案 §4 自有的 submitPublish 改写、独立 `ProjectPublishReqVO` 改动、和它自登记的 `PROJECT_PUBLISH_BLOCKED_BY_GATE` 错误码**。
**错误码全局只登记一次**publish-blocked 不再需要独立错误码block→admitted=false 不抛异常)。若仍保留一个 gate 失败码用于 `GateResultVO.code` 文案,**只在 project `ErrorCodeConstants`(段 100登记一次**,禁 compliance 重复登记。
**串行独占纪律**:主 agent 串行独占改 `ProjectServiceImpl.submitPublish` + `AppProjectController` + `project.yaml`publish 端点)。**禁任何子 agent 并行碰 project.publish**。
### 1.2 R2 ProjectApi seam 单点收敛blocker
【已验证】`ProjectApi``game-module-project-api`)现仅 `getCreatorUserId(gameId)→CommonResult<Long>``@GetMapping(PREFIX+"/get-creator-user-id")`@FeignClient + 同进程 @Primary 解析)。
本波三方都要改它compliance 调 reviewProject、studio 要 createProject、GAP-3 要 getCurrentVersionId。**全部新增方法 Phase A 一次性定稿,主 agent 串行改 `ProjectApi` + `ProjectApiImpl`,带 project 9 单测回归门。禁子 agent 并行改 project-api。**
**ProjectApi seam 单点定稿表**
| 方法签名 | 来源 | 读/写 | 语义 | 归属校验 |
|---|---|---|---|---|
| `getCreatorUserId(Long gameId) → CommonResult<Long>` | 已有 | 读 | 反查创作者(归因) | 不存在返回 null |
| `getCurrentVersionId(Long gameId) → CommonResult<Long>` | GAP-3 (R10) | 读 | 当前生效版本 ID**无已发布版本返回 null** | 调用方按 isGamePublished 判定 |
| `createProject(Long creatorUserId, ...建项目入参) → CommonResult<Long>` | studio (R2) | **写** | studio 草稿移交建游戏,返回 gameId | **写类 RPC 必带显式 creatorUserId 入参**RPC 无登录上下文service 层补归属校验,**不裸暴露** |
| `forceUnlist(Long gameId) → CommonResult<Boolean>` | R3 备选 | 写 | 受控下架任意可见态→UNLISTED+ project 单测回归 | **默认不采用**(见 R3 默认走状态分支);仅当 Phase A 选受控 seam 时才加 |
> `createProject` 是写类 RPC跨模块暴露须谨慎显式 `creatorUserId` 入参 + service 层归属校验,绝不绕过 DataPermission 裸暴露。`forceUnlist` 默认不建(见 R3
### 1.3 R3 compliance 封禁不破坏状态机blockerD2
【已验证】`resolveReviewTargetStatus`(ProjectServiceImpl L190-195)`UNLIST(3)` 仅当 `currentStatus == PUBLISHED` 才合法,否则抛 `PROJECT_REVIEW_STATUS_INVALID`。封禁对象可能处于 DRAFT/REVIEWING/REJECTED/UNLISTED 任意态。
**禁止无条件调 `reviewProject(UNLIST=3)`**。MVP 采【**按状态分支**】(默认):
- 封禁动作:写 `game_user_ban` 台账(主操作,恒执行)。
- 下架动作:**仅当 `game.status == PUBLISHED` 时**才调 `reviewProject(decision=3)` 走 project 权威状态机;**非 PUBLISHED 态只记封禁、不调状态机**(不抛异常、封禁不失败)。
**备选(标注,默认不采用)**project-api 加受控 `forceUnlist(gameId)` seam任意可见态→UNLISTED + project 单测回归)。**默认用状态分支**;若 Phase A 改采 forceUnlist则随 R2 ProjectApi 收敛一并定稿并补 project 单测。
### 1.4 R4 studio 治理登记high非"修漂移"
【已验证】README §4 段位表 = `project=100 / aigc=101 / runtime=102 / feed=103 / telemetry=104 / pay=105 / trade=106 / community=107 / ip=108 / compliance=109 / biz=110 / ad=111`**不含 studio**CLAUDE.md 13 后端模块定位无 studio`game-studio` 是产品端**前端仓名**)。
故新建 studio 后端模块 = **一次治理变更**(非"修 README 漂移"。Phase A 由主 agent 正式登记:
- studio = 错误码段 **112** / Flyway **V8**
- 更新 `CLAUDE.md` 模块定位:新增 studio 后端模块条目,**注明"与 game-studio 产品端前端仓区分"**。
- 更新 `contracts/README.md` §4 段位表(追加 `studio=112`+ §1 契约清单/目录树(追加 `studio.yaml` + `V8.0.0__create_game_studio.sql`+ §2 波次说明(追加 Wave3 studio
**【待裁定】** 这是治理决策,需创始人 Phase A 知会确认。D1 已选"守草稿+studio"方向已定,但**段位 112 登记须显式做**(不得自分配后默认通过)。
### 1.5 R5 feed 降权 = 本波 P1 不生效medium
【已验证】feed 当前无 `FeedDownweightApi` 入口(仅 `FeedRankDO` 注释提及降权字段,无 APIfeed 本波不重建。
故 compliance 封禁的"降权出流屏蔽" = **P1本波不生效**。compliance 完成条件**显式标注**:「封禁台账写库 +(若 PUBLISHED下架**降权屏蔽 P1 本波不生效**」。**不得用一个默认关的开关掩盖语义残缺****不建只有调用方无提供方的 `FeedDownweightApi` 孤儿 seam**(本波 compliance 侧不引入该调用;若保留代码占位须留 TODO 明注,不写默认关开关伪装"已支持")。
### 1.6 R6 V6/V7 提为全局 G0medium
【已验证】`yudao-server/src/main/resources/db/migration/` 现仅 V1-V5V6/V7 仅在 `game-module-ad/trade-server` 的 db/migration。
把"复制 V6/V7 执行副本到 yudao-server"从 compliance/studio 章节**抽出**,作为 **Phase B 开工的全局前置 G0**:主 agent 做一次 + diff-identical 校验 + 构建后 `unzip -l jar` 核验 7 文件。compliance/studio 章节只引用「G0 已完成」,**不重复 cp**。(详见 §6 G0。
### 1.7 R8 GAP-2 manifestUrl 来源Phase A 定low
【待裁定】让 runtime owner 确认 `game_runtime_package` 是否有可派生 manifest 路径的列:
- **分支 A纯派生**GAP-2 维持 additive 零 DDL`manifestUrl``packageUrl` 同前缀派生(拼 `.../manifest.json`)。
- **分支 B需增列**:显式升级为 runtime 补偿迁移(新 Flyway 版本,如 `V3.1.0`,纳入 Flyway 唯一性校验),**不藏在 VO 回填里**。
spec 两分支并列Phase A 闭合后只走其一。
### 1.8 R7 studio attachments 持久化-onlymedium
【已验证】aigc 的 `AigcGenerateReqVO``attachments` 字段。
MVPstudio **仅持久化 attachments不透传 aigc**`studio.yaml``StudioGenerateReqVO.attachments` 描述改为「**本波仅持久化P1 透传 aigc**」。不写"透传"而下游无接收字段。P1 方案:给 aigc-api 新 DTO 加 attachmentsadditive随 AigcApi seam 定。)
### 1.9 R9 GAP-4 破坏性收口low
【已验证】`events.schema.json` envelope/user 为 `additionalProperties:false`(严格)+ snake_casetelemetry.yaml `EnvelopeReqVO` / `TelemetryEventDO` / 前端 SDK 全 camelCase。
统一 **camelCase**(改 `events.schema.json`)。**Phase A 评审记录显式写明**
- 利用「无生产数据 + MQ 未实现(`dispatchToMq` TODO」窗口做一次性破坏收敛。
- 豁免 README §2「不兼容变更用新事件名并行消费」规则——**理由改的是信封envelope/user非事件名**,属信封字段命名统一而非单事件演进。
- 确认仓外BI/数据)无 snake 消费者后执行。
(最小 camelCase 样例 JSON 见 §3 GAP-4`npx ajv-cli` + 必要 `ajv-formats` 校验。)
### 1.10 实测前置门清单R11Phase A/G0 必须实跑,不留运行时再试)
| # | 门 | 命令mini-infra 执行,只读核验) | 通过判定 / 兜底 |
|---|---|---|---|
| P1 | clone 端点 | `git ls-remote http://101.200.34.71:3000/zizi-al/games-development-ai.git` | 【实测前置】本会话已在 mini-desktop 验证 :3000 匿名可用,**但 mini-infra 须复核**;不可达 → SSH/2222 兜底(`ssh://git@101.200.34.71:2222/...`),主 agent 注入 deploy key |
| P2 | mysql 镜像 tag | `docker inspect infra-mysql --format '{{.Config.Image}}'` | 取确切 tag 对齐 compose替换写死的 `8.4.8`;或用浮动 `mysql:8.4` |
| P3 | maven 基础镜像 | `docker pull maven:3.9-eclipse-temurin-17` | 用确认可拉的 `maven:3.9-eclipse-temurin-17`**非 deprecated 的 `maven:3.8-jdk17`** |
| P4 | MinIO 凭据/端点 | `docker inspect infra-minio` 取真实 root 凭据 + 端口映射 | 填入 `.env`端点端口须核实9000 是否映射宿主) |
| P5 | MinIO 最小权限 key | 见 §2 A9**独立用户**,非 root 子账号) | `mc admin user add``policy attach``svcacct add` |
```bash
# Phase A 实测前置门mini-infra只读
ssh mini-infra
hostname && docker ps --format '{{.Names}}' | sort # 确认 infra-mysql/redis/minio 在跑
# P1 clone 端点复核
git ls-remote http://101.200.34.71:3000/zizi-al/games-development-ai.git | head -3 \
&& echo "CLONE-HTTP-OK" || echo "CLONE-HTTP-FAIL → 改 SSH/2222 兜底"
# P2 mysql tag
docker inspect infra-mysql --format '{{.Config.Image}}'
# P3 maven 基础镜像可拉性
docker pull maven:3.9-eclipse-temurin-17 >/dev/null 2>&1 && echo "MAVEN-IMG-OK" || echo "MAVEN-IMG-FAIL"
# P4 MinIO 真实凭据/端点
docker inspect infra-minio --format '{{range .Config.Env}}{{println .}}{{end}}' | grep -iE 'MINIO_ROOT|MINIO_ACCESS|MINIO_SECRET'
docker inspect infra-minio --format '{{json .NetworkSettings.Ports}}'
```
### 1.11 Phase A 待裁定开放项(汇总,见 §9
R8(manifestUrl 派生/增列)、R5(feed 降权 P1 边界确认)、R7(attachments 持久化口径)、R4(studio 112 段位治理确认)、P2(mysql tag)、G0(RMQ 不阻塞实跑结论)。
---
## §2 主线1-A隔离 stagingmini-infra
> 范围铁律D1-D5只建隔离 stagingMySQL+Redis联调单体**不接 Nacos/RocketMQ/MinIO 容器**MinIO 复用 infra-minio。所有命令在 `mini-infra`(100.64.0.8) 执行,除非显式标注 `[本机]`(6c6g有 java17+mvn)。
### A0. build + run 拓扑
【已验证】jar 产物名 `yudao-server.jar`pom `finalName=yudao-server`),主类 `cn.iocoder.yudao.server.YudaoServerApplication`,端口 `48080`;框架基表 `game-cloud/sql/mysql/ruoyi-vue-pro.sql`(48 表 + 租户 id=1 + admin userId=1) + `quartz.sql`mock token 机制:`mock-enable=true` 且 token 以 `mock-secret`(默认 `test`) 开头 → userId=去前缀tenantId 取请求头 `tenant-id`Nacos discovery/config 已 `enabled:false`Flyway `baseline-on-migrate=true`
**推荐方案 A = mini-infra Docker 多阶段构建 + 同网运行**:构建在容器内(`maven:3.9-eclipse-temurin-17`),运行容器接入 `game-staging-net` 与 mysql/redis 容器 DNS 直连,源码匿名 HTTP clone 落 mini-infra。**备选方案 B兜底= [本机] 构建 jar + 经 Gitea release/临时分支 HTTP 拉取到 mini-infra 的 `eclipse-temurin:17` 容器 `java -jar`**jar 不走 scp被拦仅当方案 A 构建受阻时启用。
### A1. 源码 clonemini-infra
```bash
ssh mini-infra
hostname && docker ps --format '{{.Names}}' | sort
# no_proxy 与 NO_PROXY 同给R12
export no_proxy="100.64.0.0/10,127.0.0.1,localhost,101.200.34.71"
export NO_PROXY="$no_proxy"
mkdir -p /srv/game-staging/build && cd /srv/game-staging/build
# 【实测前置 P1】优先 HTTP失败回退 SSH/2222主 agent 注入 deploy key
git clone http://101.200.34.71:3000/zizi-al/games-development-ai.git src \
|| git clone ssh://git@101.200.34.71:2222/zizi-al/games-development-ai.git src
cd src && git checkout dev/2.0.0 && git log --oneline -1
```
### A2. 起栈前置核验mini-infra只读
```bash
# 1) 网络子网核验172.30.0.0/16 须空闲(占用则改 172.31 并同步改 compose
for n in $(docker network ls -q); do
docker network inspect "$n" --format '{{.Name}} {{range .IPAM.Config}}{{.Subnet}}{{end}}'
done | sort
# 断言:输出中不得出现 172.30.
# 2) 端口空闲
ss -ltn | grep -E ':13306|:16379' && echo "PORT-OCCUPIED-ABORT" || echo "PORTS-FREE-OK"
# 3) 内存基线留档
free -g
docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}'
# 4) 保护现有 infra-* 容器OOM 优先杀新栈)
for c in $(docker ps --format '{{.Names}}' | grep -E '^infra-|minio|mysql|redis|postgres|gitea'); do
pid=$(docker inspect -f '{{.State.Pid}}' "$c")
[ -n "$pid" ] && echo -500 > /proc/$pid/oom_score_adj && echo "protected $c pid=$pid"
done
```
### A3. docker-compose.yml落盘 `/srv/game-staging/docker-compose.yml`
> 【实测前置 P2】`image` tag 以 A 章 P2 `docker inspect infra-mysql` 实测为准;下方 `mysql:8.4` 为浮动占位,须对齐。
```yaml
# /srv/game-staging/docker-compose.yml
# Wave3 隔离联调栈:仅 MySQL + RedisD4Nacos/RMQ 不接,注释备用)
name: game-staging
networks:
game-staging-net:
name: game-staging-net
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/16 # 起栈前 A2 已核验空闲;占用则改 172.31
services:
game-staging-mysql:
image: mysql:8.4 # 【实测前置 P2】对齐 infra-mysql 确切 tag
container_name: game-staging-mysql
command:
- --innodb-buffer-pool-size=512M
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --default-time-zone=+08:00
- --max-connections=200
environment:
MYSQL_ROOT_PASSWORD: ${STAGING_MYSQL_ROOT_PWD}
MYSQL_DATABASE: ruoyi-vue-pro
ports:
- "13306:3306"
volumes:
- /srv/game-staging/mysql-data:/var/lib/mysql
networks: [game-staging-net]
mem_limit: 1536m
healthcheck:
# 密码插值的 $ 需 .env 用 $$ 转义R12 硬约束密码仅 [A-Za-z0-9_-] 可免转义
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p${STAGING_MYSQL_ROOT_PWD}"]
interval: 10s
timeout: 5s
retries: 12
start_period: 40s
restart: unless-stopped
game-staging-redis:
image: redis:7-alpine
container_name: game-staging-redis
command: ["redis-server", "--save", "", "--appendonly", "no", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
ports:
- "16379:6379"
volumes:
- /srv/game-staging/redis-data:/data
networks: [game-staging-net]
mem_limit: 512m
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 10
start_period: 10s
restart: unless-stopped
# ===== 备用D4本波不接保持注释=====
# game-staging-nacos / game-staging-rmq-namesrv —— 本波不起
```
`.env`同目录chmod 600不入库
```bash
# /srv/game-staging/.env (chmod 600)
# 【R12 硬约束】口令仅用 [A-Za-z0-9_-],避开 shell 元字符与 compose $ 插值
STAGING_MYSQL_ROOT_PWD=ChangeMe_Staging_2026
```
### A4. 建库 + 导框架基表
> 跨机时序R12先 A3/A4 起栈,再 `[本机]` `nc -zv 100.64.0.8 13306/16379` 通,再做 G0。
```bash
cd /srv/game-staging
docker compose --env-file .env up -d game-staging-mysql game-staging-redis
# 等 MySQL healthy条件轮询非 sleep
until [ "$(docker inspect -f '{{.State.Health.Status}}' game-staging-mysql)" = "healthy" ]; do
echo "waiting mysql..."; docker logs --tail 2 game-staging-mysql; sleep 3; done
echo "MYSQL-HEALTHY"
# 显式 utf8mb4
docker exec -i game-staging-mysql mysql -uroot -p"$STAGING_MYSQL_ROOT_PWD" \
-e "ALTER DATABASE \`ruoyi-vue-pro\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# 导框架基表
SQLDIR=/srv/game-staging/build/src/game-cloud/sql/mysql
docker exec -i game-staging-mysql mysql -uroot -p"$STAGING_MYSQL_ROOT_PWD" ruoyi-vue-pro < $SQLDIR/ruoyi-vue-pro.sql
docker exec -i game-staging-mysql mysql -uroot -p"$STAGING_MYSQL_ROOT_PWD" ruoyi-vue-pro < $SQLDIR/quartz.sql
# 断言framework_tables>=48system_tenant id=1 status=0system_users id=1 admin status=0
docker exec -i game-staging-mysql mysql -uroot -p"$STAGING_MYSQL_ROOT_PWD" ruoyi-vue-pro -e "
SELECT COUNT(*) AS framework_tables FROM information_schema.tables WHERE table_schema='ruoyi-vue-pro';
SELECT id,name,status FROM system_tenant WHERE id=1;
SELECT id,username,status,tenant_id FROM system_users WHERE id=1;"
```
> 跨机可达性预检(`[本机]`G0 前必跑,区分"卡 DB/Redis"与"卡 RMQ"
> ```bash
> nc -zv 100.64.0.8 13306 && nc -zv 100.64.0.8 16379 && echo "STAGING-DB-REDIS-REACHABLE"
> ```
### A5. application-staging.yaml`[本机]` 新增 `game-cloud/yudao-server/src/main/resources/application-staging.yaml`
> 不改 dev/local。机密走 env 占位;容器运行时用 env 覆写 host 为容器 DNS见 G2
```yaml
# application-staging.yaml —— Wave3 隔离联调单体 profile新增不动 dev/local
server: { port: 48080 }
spring:
autoconfigure:
exclude:
- org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration
- org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration
# 若 G0 判定 RMQ 阻塞,追加:
# - org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration
datasource:
dynamic:
primary: master
datasource:
master:
url: jdbc:mysql://${STAGING_DB_HOST:100.64.0.8}:${STAGING_DB_PORT:13306}/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
username: ${STAGING_DB_USER:root}
password: ${STAGING_DB_PWD}
slave:
lazy: true
url: jdbc:mysql://${STAGING_DB_HOST:100.64.0.8}:${STAGING_DB_PORT:13306}/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: ${STAGING_DB_USER:root}
password: ${STAGING_DB_PWD}
data:
redis:
host: ${STAGING_REDIS_HOST:100.64.0.8}
port: ${STAGING_REDIS_PORT:16379}
database: 0
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true
baseline-version: 0
url: jdbc:mysql://${STAGING_DB_HOST:100.64.0.8}:${STAGING_DB_PORT:13306}/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
user: ${STAGING_DB_USER:root}
password: ${STAGING_DB_PWD}
spring.cloud.nacos.discovery.enabled: false
spring.cloud.nacos.config.enabled: false
xxl: { job: { enabled: false } }
rocketmq:
name-server: ${STAGING_RMQ_NAMESRV:127.0.0.1:9876} # D4 不接G0 验证不阻塞
producer: { group: yudao-server_PRODUCER }
management:
endpoints: { web: { base-path: /actuator, exposure: { include: '*' } } }
yudao:
security:
mock-enable: true # D4member 不在 fork用 mock token
mock-secret: test # admin: test1+tenant-id:1产品端: test{memberId}+tenant-id:1
captcha: { enable: false }
access-log: { enable: false }
demo: false
tenant: { enable: true } # 须有效租户 id=1随框架表导入
logging: { file: { name: ${user.home}/logs/yudao-server-staging.log } }
debug: false
```
### A6. G0 前置门RocketMQ 不阻塞启动验证,`[本机]`R12
> 前置(必须先满足,否则误判):① A3/A4 已起栈且 `nc -zv 100.64.0.8 13306/16379` 通;② `[本机]` 已完成 G0 全局复制§6 G0`mvn -pl yudao-server -am -DskipTests package` 出 jar。
```bash
cd <repo>/game-cloud
export STAGING_DB_HOST=100.64.0.8 STAGING_DB_PORT=13306 STAGING_DB_USER=root STAGING_DB_PWD="$STAGING_MYSQL_ROOT_PWD"
export STAGING_REDIS_HOST=100.64.0.8 STAGING_REDIS_PORT=16379
export STAGING_RMQ_NAMESRV=127.0.0.1:19999 # 故意不存在
export no_proxy=100.64.0.0/10,127.0.0.1,localhost NO_PROXY=100.64.0.0/10,127.0.0.1,localhost
timeout 180 java -jar -Dspring.profiles.active=staging yudao-server/target/yudao-server.jar 2>&1 | tee /tmp/g0-rmq.log &
until grep -qE "Started YudaoServerApplication|APPLICATION FAILED TO START" /tmp/g0-rmq.log; do sleep 5; done
grep -E "Started YudaoServerApplication" /tmp/g0-rmq.log && echo "G0-PASS: RMQ 不阻塞启动"
```
**判定R12区分卡点**
- 日志先报 **DB/Redis 连接失败** → 不是 RMQ 问题,回 A4 确认库可达,重跑(不触发兜底)。
- **PASS**(打出 Started→ 放行 G1staging profile 无需额外处理。
- **FAIL 且卡在 RocketMQ** → 兜底1推荐`application-staging.yaml``spring.autoconfigure.exclude` 追加 `org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration`,复跑通过。**【待裁定】** 须创始人确认 telemetry 同步直写绕 MQ 已在 telemetry 模块就位。
- 完成后 `kill %1` + 清 `/tmp/g0-rmq.log`
### A7. G1 构建(方案 Amini-infra
`mini-infra:/srv/game-staging/build/Dockerfile`
```dockerfile
# 多阶段maven 构建 → temurin 运行
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY src/game-cloud /app/game-cloud
WORKDIR /app/game-cloud
RUN --mount=type=cache,target=/root/.m2 mvn -q -pl yudao-server -am -DskipTests package
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /app/game-cloud/yudao-server/target/yudao-server.jar /app/yudao-server.jar
EXPOSE 48080
ENTRYPOINT ["java","-Xmx1536m","-Dspring.profiles.active=staging","-jar","/app/yudao-server.jar"]
```
```bash
cd /srv/game-staging/build
# §6 G0 复制硬门必须先在 src 内完成
ls src/game-cloud/yudao-server/src/main/resources/db/migration/V*.sql | wc -l # 必须 7V1-V7否则回 G0
DOCKER_BUILDKIT=1 docker build --memory 4g -t game-staging-app:wave3 .
```
### A8. G2 运行(容器接入 staging 网DNS 直连)— R12 命令修正
```bash
docker run -d --name game-staging-app \
--network game-staging-net \
-p 48080:48080 \
--memory 2g \
-e STAGING_DB_HOST=game-staging-mysql -e STAGING_DB_PORT=3306 \
-e STAGING_DB_USER=root -e STAGING_DB_PWD="$STAGING_MYSQL_ROOT_PWD" \
-e STAGING_REDIS_HOST=game-staging-redis -e STAGING_REDIS_PORT=6379 \
-e STAGING_RMQ_NAMESRV=127.0.0.1:19999 \
-e no_proxy="100.64.0.0/10,127.0.0.1,localhost,game-staging-mysql,game-staging-redis" \
-e NO_PROXY="100.64.0.0/10,127.0.0.1,localhost,game-staging-mysql,game-staging-redis" \
game-staging-app:wave3
until docker logs game-staging-app 2>&1 | grep -qE "Started YudaoServerApplication|APPLICATION FAILED"; do sleep 5; docker logs --tail 3 game-staging-app; done
```
> 【已验证修正】`docker run``--memory 2g`**非 `--mem-limit`**CLI 无此 flag`no_proxy``NO_PROXY` 同给R12。本节命令与上方逐字一致。
### A9. G3/G4 自检门(真实 API 路径R10
> **【已验证】全仓 controller 无 `game/` 路径前缀**AppProjectController `@RequestMapping("/project")` + `/my`(GET 分页)/`/{id}`/`/{id}/publish`AppFeedController `@RequestMapping("/feed")` + `/stream`/`/zones`/`/zone/stream`/`/interact`/`/share/{gameId}`**无 list**)。框架自动加 `/app-api``/admin-api`
> **铁律**G3/G4 断言先 dump 真实 paths 再写。禁 grep `game/`、禁 `/feed/list`、禁 `/admin-api/game/project/page`
```bash
# G3-1 健康
curl -s http://127.0.0.1:48080/actuator/health | grep -o '"status":"UP"' && echo HEALTH-UP
# G3-2 接口文档出齐 7 模块 —— 先 dump 真实 pathsR10
curl -s http://127.0.0.1:48080/v3/api-docs \
| python3 -c 'import sys,json;[print(k) for k in json.load(sys.stdin)["paths"]]' \
| sort -u | tee /tmp/staging-paths.txt
# 断言 7 模块各有真实路径(按真实前缀,非 game/
grep -qE '/app-api/project' /tmp/staging-paths.txt && \
grep -qE '/app-api/aigc' /tmp/staging-paths.txt && \
grep -qE '/app-api/runtime' /tmp/staging-paths.txt && \
grep -qE '/app-api/feed' /tmp/staging-paths.txt && \
grep -qE '/app-api/telemetry' /tmp/staging-paths.txt && \
grep -qE '/admin-api/ad' /tmp/staging-paths.txt && \
grep -qE '/admin-api/trade' /tmp/staging-paths.txt && echo "7-MODULES-OK"
# 补 studio/compliance 接入后追加grep -qE '/app-api/studio' && grep -qE '/admin-api/compliance'
# G3-3 Flyway 迁移历史 ≥7 + game_* 表 18 张
docker exec -i game-staging-mysql mysql -uroot -p"$STAGING_MYSQL_ROOT_PWD" ruoyi-vue-pro -e "
SELECT version,success FROM flyway_schema_history WHERE version IS NOT NULL ORDER BY installed_rank;
SELECT COUNT(*) AS game_tables FROM information_schema.tables WHERE table_schema='ruoyi-vue-pro' AND table_name LIKE 'game\_%';"
# 断言version 含 1.0.0~7.0.0 且 success=1game_tables=18compliance/studio 接入后再加 +6 表)
# G4 端到端鉴权门真实路径R10
# 管理端 mockadmin userId=1 / tenant 1—— 用真实存在端点
curl -s -H "Authorization: Bearer test1" -H "tenant-id: 1" \
'http://127.0.0.1:48080/admin-api/project?pageNo=1&pageSize=1' | head -c 300; echo
# 产品端 app-api mock —— project/myGET 分页,非 page
curl -s -H "Authorization: Bearer test1" -H "tenant-id: 1" \
'http://127.0.0.1:48080/app-api/project/my?pageNo=1&pageSize=1' | head -c 300; echo
# feed 用 stream无 list
curl -s -H "Authorization: Bearer test1" -H "tenant-id: 1" \
'http://127.0.0.1:48080/app-api/feed/stream?pageNo=1&pageSize=1' | head -c 300; echo
# 断言:返回 {"code":0,...}(非 401/未登录)。具体路径以 /tmp/staging-paths.txt 实际为准。
```
> 主线1-A 收口G0 PASS + G3 三项全绿HEALTH-UP / 7-MODULES-OK / Flyway≥7 且 game 表 18+ G4 三条 mock 均非未登录。
### A10. MinIO bucket 级最小权限D3复用 infra-minioR11 独立用户)
> 【实测前置 P4/P5】root 凭据/端点从 §1 P4 `docker inspect infra-minio` 取真实值key 用**独立用户**(非 root 子账号)。
```bash
# 0) 配 mc 别名(端点/凭据来自 P4 实测)
mc alias set infra http://127.0.0.1:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PWD"
mc admin info infra # 只读核验连通
# 1) 建带随机后缀 bucket
SUFFIX=$(head -c4 /dev/urandom | od -An -tx1 | tr -d ' ')
BUCKET="game-staging-${SUFFIX}"; mc mb "infra/${BUCKET}"; echo "BUCKET=${BUCKET}"
# 2) 最小权限 policy仅该 bucket 下 game-staging/* 前缀)
cat > /srv/game-staging/minio-policy.json <<JSON
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["s3:GetObject","s3:PutObject","s3:DeleteObject"],
"Resource": ["arn:aws:s3:::${BUCKET}/game-staging/*"] },
{ "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": ["arn:aws:s3:::${BUCKET}"],
"Condition": {"StringLike": {"s3:prefix": ["game-staging/*"]}} }
]
}
JSON
# >>> 评审门:人工核验 policy JSON 无通配越权(无 *:* / 无跨 bucket后再继续 <<<
mc admin policy create infra game-staging-rw /srv/game-staging/minio-policy.json
# 3) 独立最小权限 USERR11非 root 子账号)
GS_SECRET=$(head -c16 /dev/urandom | od -An -tx1 | tr -d ' ')
mc admin user add infra game-staging-app "$GS_SECRET"
mc admin policy attach infra game-staging-rw --user game-staging-app
mc admin user svcacct add infra game-staging-app # 输出 Access/Secret Key → 仅这对 key 写入 application-staging 文件存储配置(env 占位)
```
> 落地MinIO key/endpoint/bucket 经 yudao `infra_file_config` 或 env 注入产品端上传链路本节只交付「bucket+policy+独立 user+key」。
### A11. 备份 / 重建 / 回滚R12down 不带 -v
```bash
# 幂等重建(保留数据卷)
cat > /srv/game-staging/rebuild.sh <<'SH'
#!/usr/bin/env bash
set -euo pipefail
cd /srv/game-staging
docker compose --env-file .env down # 不带 -v保留 mysql/redis 数据卷
docker rm -f game-staging-app 2>/dev/null || true
docker compose --env-file .env up -d game-staging-mysql game-staging-redis
until [ "$(docker inspect -f '{{.State.Health.Status}}' game-staging-mysql)" = "healthy" ]; do sleep 3; done
echo "REBUILD-OK (数据卷保留)"
SH
chmod +x /srv/game-staging/rebuild.sh
# 钱财闭环验收后备份
cat > /srv/game-staging/backup.sh <<'SH'
#!/usr/bin/env bash
set -euo pipefail
source /srv/game-staging/.env
TS=$(date +%Y%m%d-%H%M%S); OUT=/srv/game-staging/backup/dump-${TS}.sql
mkdir -p /srv/game-staging/backup
docker exec game-staging-mysql mysqldump -uroot -p"${STAGING_MYSQL_ROOT_PWD}" \
--single-transaction --routines --triggers ruoyi-vue-pro > "${OUT}"
gzip "${OUT}" && echo "BACKUP-OK ${OUT}.gz"
SH
chmod +x /srv/game-staging/backup.sh
```
> 回滚铁律:`down` **不带 `-v`**`-v` 仅人工显式执行不写入任何脚本。app 容器无状态可随意 `docker rm -f game-staging-app` 重建。
### A12. 12 段联调验真
- **端到端真跑(先验)**(5)审核状态机 /(6)feed 按 zone SQL 出流 /(7)试玩 session /(8)互动幂等 /(9)广告 mock 计费ecpm/1000幂等键 `trace_id+event_type+tenant_id`)。
- **钱财闭环(次验,手动触发)**(10)trade T+1 结算xxl-job 关,手动调 SettlementJob→×0.80→`game_trade_income` uk 幂等+累加 balance/(12)提现≥500 且≤balance原子冻结→mock 打款)。
- **必须桩(最后串)**(2)Dify手动 POST dify/callback/(3)runtime 编译(直写 build 成功态+伪 package_url/(4)发布回写 project.game_versionTODO Feign 对接点)/(11)telemetry 改同步直写绕 MQ。
---
## §3 主线1-B4 个 contractGaps命令级
> 三相纪律:契约改动属 Phase A 契约锁(过 2 轮 review后端/前端代码属 Phase B/C。改 `project.publish` 由主 agent 串行R1带 project 9 单测 + 4 新断言回归门。
### GAP-1 发布门禁结构化in-place 破坏性R1
【已验证】`project.yaml` publish 响应 = `CommonResultBoolean``submitPublish` 返回 void`AppProjectController` 硬编码 `success(true)`;前端 `publish-gates.ts` 纯客户端预检。
**A. 契约 `contracts/api-schemas/project.yaml`**publish 端点 200 响应 `$ref` 改结构化信封,**in-place 破坏,非 additive**;删"旧 boolean 端点保留"承诺):
```yaml
# publish 端点 200 响应in-place 升级,破坏性)
schema: { $ref: '#/components/schemas/CommonResultProjectPublish' }
# components.schemas 新增三个:
CommonResultProjectPublish:
type: object
properties:
code: { type: integer }
data: { $ref: '#/components/schemas/ProjectPublishRespVO' }
msg: { type: string }
ProjectPublishRespVO:
type: object
required: [admitted, gates]
properties:
admitted: { type: boolean, description: 'true=已进审核false=被门禁拦截未进审核(不抛异常)' }
gates:
type: array
items: { $ref: '#/components/schemas/GateResultVO' }
GateResultVO:
type: object
required: [key, label, pass]
properties:
key: { type: string, description: 'status/title/cover/version/age/zone/compliance' }
label: { type: string }
pass: { type: boolean }
code: { type: string, description: '失败码(通过为空)' }
message: { type: string }
```
**B. 后端project-server**
| 文件 | 改动 |
|---|---|
| 新增 `controller/app/project/vo/GateResultVO.java` | `String key,label,code,message; Boolean pass;` + `@Data`+`@Schema` |
| 新增 `controller/app/project/vo/ProjectPublishRespVO.java` | `Boolean admitted; List<GateResultVO> gates;` |
| `ProjectService.java` | `submitPublish` 签名 `void``ProjectPublishRespVO`blast radius 仅 controller+单测,无跨模块 RPC 依赖此方法) |
| `ProjectServiceImpl.java`(L89-108) | 校验项改为**收集 gates**(不立即抛);注入 compliance gate`ComplianceGateApi.evaluate` seamR1映射为 `key=compliance` 一条;全 pass→`status=1`+`admitted=true`;硬门禁失败→`admitted=false` 不改状态、**不抛异常**。**不动 `reviewProject()`/`game_review_record`/`ReviewDecisionEnum`D2** |
| `AppProjectController.java`(L78-83) | 返回 `CommonResult<Boolean>``CommonResult<ProjectPublishRespVO>` |
> 主 agent 串行独占改本组R1`ComplianceGateApi` 未就绪时走桩(恒 pass与 Phase C 接入对齐(开关 `game.publish.compliance-gate-enabled`)。
**C. 前端 game-studio同批迁移无并存期R1**`api/types.ts`(加 `GateResult`/`ProjectPublishResp`)、`api/project.ts`(publish 返回 `Promise<ProjectPublishResp>`)、`store/project.ts`(透传)、`mock/project.ts`(`publishHandler``{admitted,gates}`)、`Publish.vue`(用 `resp.admitted` 判定 + 渲染服务端 gates)、`publish-gates.ts`(头注改"软提示,权威以服务端 gates 为准")。
**验证**`mvn -pl game-module-project/game-module-project-server -am test`9 旧单测全绿 + 新断言适龄缺失→admitted=false 且 gate(age).pass=false全通过→admitted=true+status=1compliance 桩 block→admitted=false`cd game-studio && npm run build`
### GAP-2 取包端点manifestUrlR8 两分支)
【已验证】`RuntimePackageRespVO``packageUrl``manifestUrl``SandboxPolicyVO.allowOrigins` 已存在;前端 `GamePlayer.vue resolvePackage`(L99-127) 无条件用 demo 兜底。
**A. 契约 `runtime.yaml`**`RuntimePackageRespVO``manifestUrl`additive`SandboxPolicyVO` 描述补 CORS 约束(不改字段)。
**B. 后端R8 Phase A 定)**
- **分支 A派生零 DDL**`RuntimePackageRespVO``private String manifestUrl;``RuntimePackageServiceImpl`/`RuntimeConvert``packageUrl` 同前缀派生 `.../manifest.json`
- **分支 B增列**:若 `game_runtime_package` 无可派生列 → **新 Flyway 补偿迁移**(如 `V3.1.0__add_manifest_path.sql`,纳入唯一性校验),不藏 VO 回填。
- MinIO/OSS CORSD3staging bucket 设 CORS rule`AllowedOrigin`=宿主 origin、`AllowedMethod=GET/HEAD``ExposeHeader=ETag`),过 D3 评审门。宿主 origin 具体值待联调形态定(见 §9
**C. 前端**`host/contract.ts``manifestUrl?``GamePlayer.vue resolvePackage` 去无条件 demo 兜底,有 `manifestUrl``fetch`→按 `checksum` sha256 校验(`crypto.subtle.digest`)→通过返真包,失败才落 demo+`emit('error')``mock/runtime.ts``manifestUrl`
**验证**`mvn -pl game-module-runtime/game-module-runtime-server -am test`manifestUrl 非空);`npm run build`;联调 `curl -H 'Origin: <宿主源>' -I '<manifestUrl>'` 命中 `Access-Control-Allow-Origin`
### GAP-3 ShareMeta versionId轻量
【已验证】`ShareMetaRespVO` 无 versionId`FeedServiceImpl.getShareMeta`(L154-170) 不读 project 版本;`FeedServiceImpl` 现有 `isGamePublished`
**A. 契约 `feed.yaml`**`ShareMetaRespVO``versionId`additive
**B. 后端**`ShareMetaRespVO.java``private Long versionId;``getShareMeta` —— **仅当 `isGamePublished` 为真时** `setVersionId(projectApi.getCurrentVersionId(gameId).getCheckedData())`否则留空critique 修正null 路径明示;`getCheckedData()` 对 code=0 的 null data 返回 null 不抛)。`ProjectApi.getCurrentVersionId` 由主 agent 在 §1.2 ProjectApi 单点收敛中新增(无已发布版本返回 null
**C. 前端**`api/types.ts` `ShareMeta``versionId?``Share.vue onPlay` 路由 `/play/${gid}/${meta.value?.versionId ?? ''}`(缺省兜底);`mock/feed.ts` 补 versionId删契约缺口注释。
**验证**`mvn -pl game-module-feed/game-module-feed-server,game-module-project/game-module-project-api -am test`(已发布游戏 versionId 非空);`npm run build`
### GAP-4 遥测命名(破坏性收敛 camelCaseR9
【已验证】`events.schema.json` envelope/user 为 snake + `additionalProperties:false``EnvelopeReqVO`/`TelemetryEventDO`/前端 SDK 全 camelCase`dispatchToMq` 仍 TODO无生产数据
**改动(单文件 `contracts/events.schema.json`,代码侧零改)**envelope/user 键 snake→camel`schema_version→schemaVersion``trace_id→traceId``session_id→sessionId``user_id→userId``anon_id→anonId`、context 内 `game_id→gameId`/`version_id→versionId`/`device_type→deviceType``ts` 不变;事件私有 props 如 `duration_ms` 不改description 标注 snake 已废弃。保持 `additionalProperties:false`
**Phase A 评审记录R9**:写明利用无生产数据+MQ 未实现窗口一次性破坏收敛、豁免 README §2「新事件名并行」规则改信封非事件名、确认仓外无 snake 消费者。
**最小 camelCase 样例(内联,供 ajv** —— 落 `/tmp/envelope-sample.json`
```json
{
"schemaVersion": "1.0.0",
"ts": 1733740800000,
"traceId": "t-abc-123",
"sessionId": "s-xyz-789",
"user": { "userId": 1, "anonId": "anon-1" },
"context": { "gameId": 1024, "versionId": 1, "deviceType": "mobile" },
"event": "play_start",
"props": { "durationMs": 0 }
}
```
```bash
# 工具npx ajv-clipattern 默认可;若用 format 追加 ajv-formats
npx ajv-cli validate -s contracts/events.schema.json -d /tmp/envelope-sample.json
# 改前 failsnake schema 拒 camel→ 改后 pass
```
**验证**:上述 ajv 改前 fail/改后 pass`mvn -pl game-module-telemetry/game-module-telemetry-server -am test`VO/DO 未改,回归零破坏);`npm run build`。**回滚**:还原 events.schema.json 单文件(零代码回滚)。
---
## §4 主线2-Acompliance只 Gate seamD2
> 范围铁律D2/D5/R1/R3/R5compliance **只做 Gate**verdict + 分级 + 封禁台账),**绝不**重写 project 审核状态机;**只交付 `ComplianceGateApi.evaluate` seam 供 GAP-1 注入**,不自改 submitPublish、不自登记 publish-blocked 错误码R1封禁按状态分支不破坏状态机R3降权 P1 本波不生效、不建孤儿 seamR5。错误码段 **109**README §4 已登记Flyway **V9**
### 1. compliance.yaml 契约
新建 `contracts/api-schemas/compliance.yaml`,对齐 ad.yaml 头部风格。三类端点 + 一个 RPC seam
**1.1 锁风门 GateRPC seam给 project.publish 同进程调R1 唯一形态)**
```yaml
# /rpc-api/compliance —— ComplianceGateApi.evaluateproject.publish 同进程 @Primary 调)
components:
schemas:
ComplianceGateReqDTO:
type: object
required: [gameId, versionId]
properties:
gameId: { type: integer, format: int64 }
versionId: { type: integer, format: int64 }
title: { type: string }
summary: { type: string }
ageRating: { type: string, description: 'all/8+/12+/16+' }
promptHash: { type: string, description: '生成 Prompt 摘要(可空)' }
GateVerdict: # = 草案 ComplianceGateRespDTOR1 命名对齐
type: object
required: [verdict]
properties:
verdict: { type: string, enum: [pass, review, block] }
rating: { type: string }
detail: { type: array, items: { $ref: '#/components/schemas/ComplianceGateItemDTO' } }
ComplianceGateItemDTO:
type: object
properties:
atom: { type: string, description: 'textContent/imageContent/ip-reserved' }
verdict: { type: string, enum: [pass, review, block] }
reason: { type: string }
# 接口RPCPOST /rpc-api/compliance/evaluate → CommonResult<GateVerdict>
```
> 聚合语义Service 固化,可单测):`verdict = max(各原子)``block > review > pass`。ip 扩展点:聚合预留 `IpComplianceSeam`(默认 pass 桩Wave4 接 ip 只换实现。
**1.2 封禁admin-apiRBACR3 状态分支)**
```yaml
# POST /admin-api/compliance/ban/create body: BanCreateReqVO{targetType(1游戏/2用户), targetId, banType(1封禁/2降权), reason}
# → 落 game_user_ban主操作恒执行
# → 下架动作【R3 状态分支】:仅当 targetType=游戏 且 game.status==PUBLISHED 时才调 project reviewProject(decision=3);非 PUBLISHED 态只记封禁、不调状态机、不抛异常
# → 降权出流屏蔽P1 本波不生效R5不调 feed不建 FeedDownweightApi 孤儿 seam
# POST /admin-api/compliance/ban/cancel body:{banId} → 解封status=0
# GET /admin-api/compliance/ban/page → 封禁台账分页admin RBAC
```
**1.3 分级app-api 只读)**`GET /app-api/compliance/rating/get?gameId=&versionId= → ContentRatingRespVO{rating,ratedBy,updateTime}`(分级写入仅由 Gate evaluate 时落 `game_content_rating` 与 admin 复核产生)。
**错误码compliance.yaml 头部 + ErrorCodeConstants段 109publish-blocked 不在此登记R1**
```
1_109_001_000 COMPLIANCE_GATE_TARGET_NOT_FOUND 锁风门:游戏/版本不存在
1_109_002_000 COMPLIANCE_BAN_TARGET_INVALID 封禁:目标类型非法
1_109_002_001 COMPLIANCE_BAN_ALREADY_EXISTS 封禁:该目标已被封禁
1_109_002_002 COMPLIANCE_BAN_NOT_EXISTS 解封:封禁记录不存在
1_109_003_000 COMPLIANCE_RATING_NOT_FOUND 分级:无分级记录
```
### 2. Flyway V9 DDL不建审核状态机表
新建 `contracts/db-schemas/V9.0.0__create_game_compliance.sql`(授权源)+ 复制执行副本diff-identical。**三张表**`game_compliance_gate_result`lockwind gate resultid/game_id/version_id/verdict/rating/details/trace_id + 审计列+tenant_id`idx_game_version`)、`game_content_rating`id/game_id/version_id/rating/rated_by + 审计列+tenant_id`uk_game_version(game_id,version_id,deleted,tenant_id)`)、`game_user_ban`id/target_type/target_id/ban_type/status/reason + 审计列+tenant_id`uk_target_active(target_type,target_id,status,deleted,tenant_id)` + `idx_type_status`)。全表 InnoDB+utf8mb4、显式列、中文列注释、状态机 tinyint 由 Service 校验。**明确不建 audit_record/审核状态机表**(归 project `game_review_record`。DDL 全文见草案 compliance §2保留事实准确
### 3. 模块骨架(克隆 game-module-ad
```bash
cd /root/games-development-ai/game-cloud
mkdir -p game-module-compliance/game-module-compliance-api/src/main/java/cn/wanxiang/game/module/compliance/{api,dto,enums}
mkdir -p game-module-compliance/game-module-compliance-server/src/main/java/cn/wanxiang/game/module/compliance/{api,controller/admin/vo,controller/app/vo,service/gate,service/ban,framework/atom,convert,dal/dataobject/gate,dal/dataobject/rating,dal/dataobject/ban,dal/mysql}
mkdir -p game-module-compliance/game-module-compliance-server/src/main/resources/db/migration
mkdir -p game-module-compliance/game-module-compliance-server/src/test/java/cn/wanxiang/game/module/compliance/service/gate
```
**3 个 pom**(克隆 adaggregator + `-api`yudao-common+springdoc(provided)+validation+openfeign(optional)+ `-server`(本模块-api + **game-module-project-api**R3 下架仅 PUBLISHED 调 reviewProject+ env/biz-tenant/security/mybatis/redis/rpc/test。**不依赖 feed-api**R5 不建降权孤儿 seamaigc 风格原子在 compliance 内自包含aigc 无 T-AGC-19
**-api**`enums/ApiConstants`(NAME=compliance-server, PREFIX=/rpc-api/compliance)、`enums/ErrorCodeConstants`(段109 五码)、`enums/ComplianceVerdictEnum`(pass/review/block + max)、`api/ComplianceGateApi`(@FeignClient`@PostMapping(PREFIX+"/evaluate")``CommonResult<GateVerdict>`)、`dto/{ComplianceGateReqDTO,GateVerdict,ComplianceGateItemDTO}`
**-server**`api/ComplianceGateApiImpl`(@RestController @Primary,委托 GateService仿 ProjectApiImpl)、`service/gate/ComplianceGateService(Impl)`(聚合→verdict落 gate_result + content_rating)、`framework/atom/StyleComplianceAtom`(桩默认 pass+TODO 接 aigc seam)、`framework/atom/IpComplianceSeam`(D5 默认 pass)、`service/ban/UserBanService(Impl)`(**R3 状态分支**:写 ban 台账 + 仅 PUBLISHED 调 reviewProject(3))、controller app/admin + VO + convert + DO×3 + Mapper×3。
**接 yudao-serverPhase C 主 agent**:根 `game-cloud/pom.xml` `<modules>``game-module-compliance``yudao-server/pom.xml``game-module-compliance-server`。【已验证】无需改 `YudaoServerApplication`(scan/@MapperScan 通配 `cn.wanxiang.game.module`) 与 application.yamltype-aliases 通配)。
### 4. 接入 project.publishPhase C 主 agent 串行R1 唯一注入点)
compliance **只交付 `ComplianceGateApi.evaluate` seam**;注入逻辑在 GAP-1 的 `ProjectServiceImpl.submitPublish` 内由主 agent 串行写(见 §3 GAP-1 B映射为 `key=compliance` 一条 GateResultVO`verdict=block`→该 gate `pass=false`+`admitted=false`**不抛异常**R1`pass/review``pass=true` 进 REVIEWINGreview 由人工台兜底,状态机权威归 project
- `project-server/pom.xml``game-module-compliance-api` 依赖(无 Maven 环project-server→compliance-apicompliance-server→project-api四 jar 无环,已核实 ad 同构)。
- 开关 `@Value("${game.publish.compliance-gate-enabled:true}")`false 跳过 Gate回旧行为运行期降级非"双实现")。
**回归门R1 唯一实现下,铁律)**
- project 既有 9 单测全绿不改断言(`testSubmitPublish_success``@Mock ComplianceGateApi`stub `evaluate``verdict=pass``ReflectionTestUtils.setField(...,"gateEnabled",true)`)。
- 4 新 publish 断言(加进 `ProjectServiceImplTest`,不改旧用例):① gate block→`admitted=false``verify(projectMapper,never()).updateById(...)`;② gate pass→`admitted=true`+status=REVIEWING③ gate review→进 REVIEWING④ gateDisabled→`verify(gateApi,never()).evaluate(...)` 且进 REVIEWING。
- `reviewProject` 三断言pass/block/UNLIST 流转)保持原样 = D2 锚点。
### 5. feed 降权R5P1 本波不生效)
【已验证】feed 无降权入口。**本波 compliance 不调 feed、不建 FeedDownweightApi 孤儿 seam**。compliance 完成条件显式标注「封禁台账写库 +(若 PUBLISHED下架**降权屏蔽 P1 本波不生效**」。若保留代码占位,仅留 TODO 明注,不写默认关开关伪装"已支持"。
### 6. 验证
```bash
cd /root/games-development-ai/game-cloud
mvn -f game-module-compliance/pom.xml test # (a) compliance 独立建+单测
mvn -f game-module-project/pom.xml test # (b) project 回归9旧+4新全绿
mvn -pl yudao-server -am compile -f /root/games-development-ai/game-cloud/pom.xml # (c) 全量集成编译
diff /root/games-development-ai/contracts/db-schemas/V9.0.0__create_game_compliance.sql \
game-module-compliance/game-module-compliance-server/src/main/resources/db/migration/V9.0.0__create_game_compliance.sql # (d) Flyway diff-identical
grep -rn "game_review_record" game-module-compliance/ && echo "D2-VIOLATION" || echo "D2-OKcompliance 零写审核状态机表)" # (e) D2 验证
```
完成条件:(a)(b)(c) BUILD SUCCESSproject 13 单测9+4全绿(d) 无差异;(e) D2-OK。
### 7. 回滚
代码:根 pom 删 module + yudao-server pom 删依赖 + 删模块目录 + revert GAP-1 注入。运行期降级:`game.publish.compliance-gate-enabled=false` 回旧行为。DBV9 已合入则写 V9.0.1 补偿迁移 `DROP TABLE`,禁改 V9.0.0。
---
## §5 主线2-Bstudio创作主链路最小版
> 错误码段 **112R4 治理新登记,非"修漂移"** Flyway **V8** 包名 `cn.wanxiang.game.module.studio` 目录 `game-cloud/game-module-studio/`。三相Phase A 锁 studio.yaml/V8 + 治理登记 + ProjectApi.createProject 硬前置(随 §1.2 收敛)+ aigc-api `AigcApi` seam 预审Phase B 子 agent 互斥建 `game-module-studio/`(消费 aigc-api/project-api mockPhase C 主 agent 接 aigc加 AigcApi/Impl + aigc 回归门)+ 接 yudao-server pom + 前端切换。
> 边界Doc B §71-76studio = 有状态创作工作台后端,**不跑 LLM委托 aigc、不持久化项目版本交 project、不做锁风裁决调 compliance**。
### 1. P0 范围(逐项 owner=studio调 aigc
| 草稿 scope | Doc A P0 | studio 落点 | 下游 |
|---|---|---|---|
| 创作会话 | P-CRT-10 | `game_studio_session`,进工作坊 upsert | — |
| 任务链(线性单步 DRAFT→GENERATING→DONE/FAILED | P-CRT-01/09 | `game_studio_taskchain`,编排调 aigc | aigc submitGenerate |
| 单步出整包 | P-CRT-02 | 资产图 `game_studio_asset` 仅落槽位 seam不六路并发 | aigc |
| 附件上下文 | P-CRT-04 | `attachments`(JSON) 随草稿持久化,**本波仅持久化不透传 aigcR7** | — |
| 草稿移交 project | P-TPL-03 | 调 `ProjectApi.createProject(creatorUserId,...)` 拿 gameId 写回会话R2 硬前置) | project createProject |
| 进度推送(轮询) | P-CRT-12 | `GET /app-api/studio/task/{id}` 透传 aigc progress不上 SSED4 | aigc getTask |
| 模板浏览/应用 | P-TPL-01/03 | 透传 aigc `getTemplateList` | aigc getTemplateList |
> 狠切留 seam 不实现P1rig/对白树/可视化批量;资产图仅槽位表+单步调度。
### 2. studio.yaml 契约(端 /app-api/studio/*,错误码段 112
新建 `contracts/api-schemas/studio.yaml`OpenAPI 3.0.3CommonResult 信封。path`/template/list`(GET 透传 aigc)、`/draft`(POST 建草稿:编排 `ProjectApi.createProject` 拿 gameId + upsert 会话)、`/generate`(POST 一句话生成:调 aigc submitGenerate)、`/task/{id}`(GET 进度轮询)、`/task/{id}/regenerate`(POST 重生成)。schema`StudioDraftCreateReqVO`/`StudioGenerateReqVO`(其 `attachments` 描述改 **"本波仅持久化P1 透传 aigc"**R7)/`StudioSessionRespVO`/`StudioTaskChainRespVO``x-feign-contracts`: studio 消费 aigc-api AigcApi + project-api ProjectApi.createProject。契约全文见草案 studio §2保留仅改 attachments 描述)。
**治理登记动作Phase A 必做R4**`contracts/README.md` §4 追加 `studio=112`、§1 清单/目录树追加 `studio.yaml`+`V8.0.0__create_game_studio.sql`、§2 追加 Wave3 studio`CLAUDE.md` 模块定位加 studio 后端模块(注明与 game-studio 前端仓区分)。
### 3. Flyway V8 DDL
新建 `contracts/db-schemas/V8.0.0__create_game_studio.sql`(授权源)+ 复制执行副本diff-identical。三表`game_studio_session`(id/creator_user_id/game_id/template_id/title/prompt/attachments/status + 审计列+tenant_id`idx_creator`)、`game_studio_taskchain`(id/session_id/creator_user_id/aigc_task_id/game_id/version_id/status/progress/retry_of + 审计列+tenant_id`idx_session`+`idx_aigc`)、`game_studio_asset`(id/session_id/asset_type/asset_url/status + 审计列+tenant_id`idx_session_type`)。InnoDB+utf8mb4、显式列、中文列注释。DDL 全文见草案 studio §3保留事实准确
```bash
# 复制执行副本到 yudao-server + diff-identical
cp contracts/db-schemas/V8.0.0__create_game_studio.sql \
game-cloud/yudao-server/src/main/resources/db/migration/V8.0.0__create_game_studio.sql
diff contracts/db-schemas/V8.0.0__create_game_studio.sql \
game-cloud/yudao-server/src/main/resources/db/migration/V8.0.0__create_game_studio.sql # 须零差异
```
### 4. 模块骨架(克隆 ad/project 黄金模板Phase B 子 agent 互斥建)
```bash
cd /root/games-development-ai/game-cloud
mkdir -p game-module-studio/game-module-studio-api/src/main/java/cn/wanxiang/game/module/studio/{api,dto,enums}
mkdir -p game-module-studio/game-module-studio-server/src/main/java/cn/wanxiang/game/module/studio/{controller/app/studio/vo,convert/studio,dal/dataobject/studio,dal/mysql/studio,service/studio}
mkdir -p game-module-studio/game-module-studio-server/src/main/resources/db/migration
mkdir -p game-module-studio/game-module-studio-server/src/test/java/cn/wanxiang/game/module/studio/service/studio
```
骨架文件克隆同名文件改包名→studio、错误码段→112、Feign NAME→studio-server3 pom`-server` 依赖 **game-module-aigc-api + game-module-project-api**)、`enums/{ApiConstants(NAME=studio-server,PREFIX=/rpc-api/studio),ErrorCodeConstants(段112),StudioSessionStatusEnum}`、3 DO继承 TenantBaseDO 对齐 V8 三表、3 Mapper显式列无裸 select*)、`StudioService(Impl)`(编排见 §5)、`AppStudioController`(@RequestMapping("/studio"))、`StudioConvert``StudioServiceImplTest`(mock aigc-api/project-api)。接 yudao-server pom`yudao-server/pom.xml` 追加 `game-module-studio-server` 依赖Phase C 主 agent
### 5. studio→aigc 编排R2 createProject 硬前置MVP 轮询非 MQ
【已验证】`game-module-aigc-api` 现仅含 enums**无任何 Feign `*Api` 接口**。故 Phase C 主 agent 串行给 aigc 补 `-api` seam仿 ProjectApi/AdRevenueApi
```java
// game-module-aigc-api/.../api/AigcApi.java@FeignClient(name="aigc-server"), PREFIX=/rpc-api/aigc
CommonResult<List<AigcTemplateDTO>> getTemplateList();
CommonResult<AigcGenerateRespDTO> submitGenerate(AigcGenerateReqDTO req); // 入参带 userIdRPC 无登录上下文)
CommonResult<AigcTaskRespDTO> getTask(Long taskId, Long userId);
CommonResult<AigcGenerateRespDTO> retryTask(Long taskId, Long userId); // P-CRT-09
```
`game-module-aigc-server/.../api/AigcApiImpl`@RestController @Primary 委托既有 `AigcTaskService`DTO 新建在 aigc-api/dto。**aigc 回归门**`mvn -pl game-module-aigc/game-module-aigc-server -am test` 既有 9 单测仍绿(只暴露不改 service
**StudioServiceImpl 编排R2 createProject 是硬前置,不接受 game_id 恒空兜底)**
1. `createDraft`:调 `ProjectApi.createProject(creatorUserId, ...)`§1.2 已定稿)拿 gameId 回写 `session.game_id`;落 session(status=0)。
2. `generate`:校验 session 归属→落 taskchain(status=1)→`AigcApi.submitGenerate`(带 prompt/templateId/gameId/userId)→回填 aigc_task_id→返回 taskChainId。同步直写无 MQD4。**attachments 仅持久化R7不入 aigc reqDTO**。
3. `getTask`(轮询)`AigcApi.getTask`→透传 progress/status→映射链态aigc succeeded→链完成回填 version_idfailed/timed_out→链失败
4. `regenerate`:新建 taskchain(retry_of=原 id),调 `AigcApi.retryTask`
- 失败路径aigc 超时/异常→链态 3 + 中文日志traceId 贯穿);归属校验失败→`1_112_000_001`
### 6. 前端 game-studio 接线mock→真实切换
新建 `src/api/studio.ts`(贴 studio.yaml+ `src/mock/studio.ts`(克隆 mock/aigc.ts 进度节奏,`src/mock/index.ts` 注册);`api/types.ts` 加 StudioSession/StudioTaskChain 类型;`store/create.ts` `generate` 改单步调 `studioApi.generate`(删前端"先建 project 再调 aigc"两步)、`pollTask``studioApi.getTask``Create.vue onSubmit` 改调 `studioApi.createDraft+generate`。切真实:`.env` 注入 `VITE_API_BASE` 指 staging 后端 + mock token不改 `request.ts`。若 studio.yaml 落地缺字段记 openItems 不私造。
### 7. 错误码登记(段 112
```java
// game-module-studio-api/.../enums/ErrorCodeConstants.java克隆 aigc 范式,段 1-112
ErrorCode STUDIO_SESSION_NOT_EXISTS = new ErrorCode(1_112_000_000, "创作会话不存在");
ErrorCode STUDIO_SESSION_NOT_OWNER = new ErrorCode(1_112_000_001, "无权操作他人的创作会话");
ErrorCode STUDIO_TEMPLATE_REQUIRED = new ErrorCode(1_112_000_002, "玩法模板不能为空");
ErrorCode STUDIO_TASKCHAIN_NOT_EXISTS = new ErrorCode(1_112_001_000, "创作任务链不存在");
ErrorCode STUDIO_AIGC_DELEGATE_FAIL = new ErrorCode(1_112_002_000, "生成委托失败aigc 调用异常)");
```
同步 `contracts/README.md` §4 追加 `studio=112`R4 治理登记)。
### 8. 回滚
代码studio 全新建,回滚=删未跟踪文件 + 还原 yudao-server/pom.xml studio 依赖 + aigc-api/server 的 AigcApi/Impl + 前端两步直连。DBV8 未执行前删文件,已执行写 V8.0.1 补偿 `DROP TABLE game_studio_*`。aigc 接入回滚门AigcApi/Impl 纯暴露层,删除后 aigc 9 单测仍绿。前端:`VITE_API_BASE` 留空回 mock。
---
## §6 三相编排
```mermaid
flowchart TD
subgraph PA["Phase A 收口(主 agent 串行,开工前必须完成)"]
A1["R1 publish 唯一化:改 ProjectServiceImpl.submitPublish + AppProjectController + project.yamlin-place 破坏)"]
A2["R2 ProjectApi seam 单点定稿getCurrentVersionId/createProject(+forceUnlist?) 串行改 ProjectApi+Impl + 9 单测回归"]
A3["R4 studio 治理登记 112/V8CLAUDE.md+README §4创始人确认"]
A4["契约锁project/runtime/feed/events + compliance/studio.yaml + V8/V9过 2 轮 review"]
A5["实测前置门 P1-P5clone/mysql tag/maven 镜像/MinIO 凭据+独立 key"]
end
subgraph G0["全局前置 G0Phase B 开工,主 agent 一次)"]
G["R6 复制 V6/V7 → yudao-server/db/migration + diff-identical + 构建后 unzip -l jar 核验 7 文件"]
end
subgraph PB["Phase B 子 agent 并行建(各写互斥目录)"]
B1["compliancegame-module-compliance/(不碰 project.publish只交付 ComplianceGateApi seam"]
B2["studiogame-module-studio/(消费 aigc-api/project-api mock"]
B3["GAP-1/2/3 后端 VO/Service各模块互斥GAP-4 改 events.schema.json"]
end
subgraph PC["Phase C 主 agent 串行接入 + 回归门"]
C1["GAP-1 注入 compliance gate + 改 publish前端同批迁移 admitted"]
C2["aigc 加 AigcApi/Implaigc 9 单测回归);接 yudao-server pom"]
C3["回归门project 9 单测 + publish 4 新断言 + grep game_review_record 零命中(验 D2) + Flyway diff-identical"]
C4["staging 12 段联调"]
end
PA --> G0 --> PB --> PC
```
**互斥目录纪律**compliance 子 agent 只写 `game-module-compliance/`studio 子 agent 只写 `game-module-studio/`**两者都不碰 `project.publish`/`ProjectApi`/`AigcApi`/`yudao-server/pom.xml`(全归主 agent Phase A/C 串行)**。
### G0 全局前置R6主 agent 一次)
```bash
cd /root/games-development-ai
cp game-cloud/game-module-ad/game-module-ad-server/src/main/resources/db/migration/V6.0.0__create_game_ad.sql \
game-cloud/yudao-server/src/main/resources/db/migration/V6.0.0__create_game_ad.sql
cp game-cloud/game-module-trade/game-module-trade-server/src/main/resources/db/migration/V7.0.0__create_game_trade.sql \
game-cloud/yudao-server/src/main/resources/db/migration/V7.0.0__create_game_trade.sql
# diff-identical授权源 vs 执行副本,零差异)
diff contracts/db-schemas/V6.0.0__create_game_ad.sql game-cloud/yudao-server/src/main/resources/db/migration/V6.0.0__create_game_ad.sql
diff contracts/db-schemas/V7.0.0__create_game_trade.sql game-cloud/yudao-server/src/main/resources/db/migration/V7.0.0__create_game_trade.sql
ls game-cloud/yudao-server/src/main/resources/db/migration/V*.sql | wc -l # 期望 7V1-V7V8/V9 由 studio/compliance 各自后补)
# 构建后核验 jar 内 7 文件
unzip -l game-cloud/yudao-server/target/yudao-server.jar | grep -E 'db/migration/V[0-9]' | wc -l # 期望 7V8/V9 接入后为 9
```
> compliance/studio 章节只引用「G0 已完成」,**不重复 cp V6/V7**R6
---
## §7 验收 AC
| AC | 判定 |
|---|---|
| AC-1 每模块单测 | `mvn -f game-module-compliance/pom.xml test``mvn -f game-module-studio/pom.xml test`、GAP 各模块 `mvn -pl <m> -am test` 全 BUILD SUCCESS |
| AC-2 project 回归 | project 13 单测9 旧不改断言 + 4 新 publish 断言)全绿 |
| AC-3 D2 锚点 | `grep -rn game_review_record game-module-compliance/` 零命中;`reviewProject` 三断言原样绿 |
| AC-4 staging 门 | G0 PASS + G3HEALTH-UP/7-MODULES-OK/Flyway≥7 且 game 表数对)+ G4 三条 mock 非未登录真实路径R10 |
| AC-5 12 段联调 | 端到端 5 段 + 钱财闭环 2 段幂等正确 + 桩 4 段串通 |
| AC-6 错误码/Flyway 唯一 | 段 109/112 独占无重叠V8/V9 唯一;执行副本与 contracts/ diff-identicalpublish-blocked 错误码全局仅登记一次R1 |
| AC-7 前端 | `cd game-studio && npm run build` 绿vue-tsc 假门禁不采信) |
---
## §8 回滚
- **staging**`docker compose --env-file .env down`**不带 -v**,保数据卷)+ `rebuild.sh` 幂等重建app 容器 `docker rm -f game-staging-app` 无状态重建MinIO 回滚 `mc rb infra/${BUCKET} --force` + `mc admin policy rm` + `mc admin user rm game-staging-app`infra-minio 主体零影响oom_score_adj 进程重启即失效无残留。
- **配置**application-staging.yaml/V6V7 复制为新增文件,`git checkout -- game-cloud/yudao-server/src/main/resources/` 还原,不动 dev/local。
- **模块**compliance/studio 未接入前可独立删(根 pom 删 module + yudao-server pom 删依赖 + 删目录)。
- **publish**:保留 mock 可切换分支 = 运行期 `game.publish.compliance-gate-enabled=false` 回旧行为(**非"双实现"**R1实现唯一开关切是否调 Gate代码层 revert GAP-1 注入 commit。
- **DB**Flyway 只增不改;已合入则写 V_x.0.1 补偿 `DROP TABLE`,禁改已合入迁移(校验和敏感)。
- **GAP-4**:还原 events.schema.json 单文件(零代码回滚)。
---
## §9 开放项 / 待裁定Phase A 须关闭)
| 项 | 来源 | 须裁定内容 | 默认/建议 |
|---|---|---|---|
| O1 studio 段位 112 | R4 | 是否新增 studio 后端模块 + 段位归属 | 创始人确认D1 方向已定,登记须显式做 |
| O2 manifestUrl 来源 | R8 | `game_runtime_package` 能否纯派生 manifest 路径 | 能→零 DDL不能→runtime 补偿迁移 |
| O3 feed 降权边界 | R5 | 降权出流屏蔽本波是否做 | **P1 不生效**(不建孤儿 seam完成条件显式标注 |
| O4 attachments 透传 | R7 | studio attachments 是否透传 aigc | **本波仅持久化**P1 给 aigc-api 加 DTO 字段 |
| O5 G0 RMQ 结论 | A6 | RMQ 不可达是否阻塞启动 | 实跑定FAIL→兜底1 排除 RocketMQAutoConfiguration须确认 telemetry 同步直写就位) |
| O6 mysql 镜像 tag | P2 | infra-mysql 确切 tag | `docker inspect infra-mysql` 取真值对齐 compose |
| O7 clone 端点 | P1 | mini-infra :3000 匿名是否可达 | 复核不可达→SSH/2222+deploy key |
| O8 MinIO 凭据/端点 | P4 | infra-minio root 凭据/端口映射 | `docker inspect infra-minio` 取真值入 .env |
| O9 GAP-2 CORS 宿主 origin | GAP-2 | game-studio dev 端口 | 待联调形态定稿后填 CORS rule过 D3 评审门) |
| O10 events.schema.json 外部消费者 | R9 | 仓外 BI/数据是否按 snake 取数 | 确认无后做破坏性统一 |
| O11 ProjectApi.createProject 入参集 | R2 | 建项目最小必要入参 | Phase A 随 ProjectApi 收敛定稿,带 creatorUserId + 归属校验 |
| O12 R3 下架联动方式 | R3 | 状态分支 vs forceUnlist seam | **默认状态分支**;改 forceUnlist 须补 project 单测 |
---
> **自检(写完只读核对)**:无 `game/` 路径断言残留G3/G4 全用真实 `/app-api/*``/admin-api/*`,先 dump paths无 publish 双实现compliance 只交付 evaluate seam注入唯一在 GAP-1无无条件 reviewProject(UNLIST)R3 状态分支,仅 PUBLISHED 调V6/V7 在 §6 G0 唯一复制compliance/studio 不重复ProjectApi seam 集中在 §1.2docker 用 `--memory`(非 `--mem-limit`)。