lili cbfd4d871b
Some checks failed
contract-gates / contract-gates (push) Has been cancelled
docs-gate / docs-gate (push) Has been cancelled
feat(acceptance): 闭合 playtest v3 与 A+ 可信消费链
固化 Match-3 生产者、视觉、音频与双 Judge 证据闭包。

将《山海行纪》r1.1 绑定新的不可变 release,并以生产预检现场核验 bundle、Registry/2 和 25 项 Writer 快照。

同步地图1平衡锁值、跨游戏回归修复、验收契约与 SoT 证据。
2026-07-28 20:16:13 -07:00

343 lines
21 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

openapi: 3.0.3
# 契约 #1 API | 模块:aigc(game-module-aigc,"做得出"生成入口)| owner:WS2 主笔,全员 review
# 职责:生成入口——异步提交生成任务(prompt+templateId)+ 轮询状态;产物=GamePackage(契约#4),落到 project 的 game_version(经 game_id 关联);对接 Dify(契约#6) 仅留对接点。
# 端:/app-api(产品端 game-studio,用户 Token+DataPermission,创作者只见自己任务) /admin-api(管理端 game-admin,RBAC);前缀由 huijing 框架按 controller.app/admin 包名自动添加。
# 错误码段:aigc = 1-101-***-***(本模块独占,禁止与他模块重叠)
# 响应统一 Huijing CommonResult 信封:{ code, data, msg };code=0 成功
# 范围纪律:只做 MVP 核心闭环(异步提交+轮询+取消+重试+模板列表);LLM 实名充值为人工闸门,Dify/MQ/LLM 只定义对接点不实现。
info:
title: 绘境AI aigc 模块 API
version: 1.0.0
description: >-
AI 生成("做得出"入口)。一句话 Prompt + 模板 → 异步生成任务 → 轮询状态 → 产物 GamePackage(契约#4)。
任务状态机对齐 Dify(契约#6) 输出 + RocketMQ 队列态(queued/running/succeeded/failed/timed_out/canceled)。
成功产物经 game_id 关联落到 project.game_version;前端据此 vite-plugin-mock 自动生成 mock。
servers:
- url: http://localhost:48080
description: 本地(Swagger/Knife4j http://localhost:48080/doc.html)
paths:
# ===================== 产品端 /app-api(创作者)=====================
/app-api/aigc/template/list:
get:
tags: [app-aigc]
summary: 玩法模板列表(P-TPL-01;提交生成前选模板,MVP 3-5 个 P0 模板)
description: 返回 aigc 模板注册表中启用的模板(T-AGC-03)。templateId 取值如 dodge/runner/clicker/puzzle/shooter(最终由产品拍板)。
responses:
'200':
description: 成功
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultTemplateList' }
/app-api/aigc/generate:
post:
tags: [app-aigc]
summary: 提交生成任务(P-CRT-01 自然语言生成;异步入队,立即返回 taskId)
description: >-
校验 prompt 非空 + templateId 存在;落库 game_aigc_task(status=0 queued),生成 traceId,投递 RocketMQ 异步队列(T-AGC-07,仅对接点)。
不同步等待生成结果;客户端据 taskId 轮询 GET /app-api/aigc/task/{id}。
关联的 gameId/versionId 由调用方(studio 草稿)传入。服务端在任何幂等命中前校验资源归属:非空 gameId
必须属于当前用户;versionId/baseVersionId 必须属于当前用户且与 gameId 是同一作品;非空 mode 必须携带
baseVersionId,反向地 baseVersionId 非空时 mode 也必须非空。校验收敛在 AIGC 服务边界,直连本端点不能绕过 studio 编排层。
写权属(决策5):aigc 成功后只回填 game_version.id 关联 + 生成元数据(gen_task_id);不写 game_version 的 package_url/checksum/bundle_size——这些的权威写者=runtime 编译成功后回写(见 runtime V3.0.0 / runtime.yaml)。
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/AigcGenerateReqVO' }
responses:
'200':
description: 返回新建生成任务(含 taskId + 初始状态 queued + traceId)
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultGenerateResp' }
/app-api/aigc/task/{id}:
get:
tags: [app-aigc]
summary: 轮询生成任务状态(P-CRT-12/13;前端轮询直至终态)
description: >-
返回任务当前状态机 + 进度。终态 succeeded 携带产物引用(gameId/versionId + qualityScore;packageUrl 由 runtime 编译成功后回写,aigc 仅透传只读);
终态 failed/timed_out 携带 failureReason(结构化错误分类,映射用户可读提示)。DataPermission:创作者只见自己任务。
注:qualityScore=生成质量分(0-1),对齐契约#6 Dify output;≠ telemetry/feed 的运营质量分(0-100),两者口径不同、不可互相回灌。
parameters:
- { name: id, in: path, required: true, schema: { type: integer, format: int64 }, description: 生成任务 ID(taskId) }
responses:
'200':
description: 成功
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultTask' }
/app-api/aigc/task/{id}/cancel:
post:
tags: [app-aigc]
summary: 取消生成任务(仅 queued/running 可取消 → canceled)
description: 服务端校验状态机,仅非终态可取消;终态拒绝(错误码 1-101-002-001)。
parameters:
- { name: id, in: path, required: true, schema: { type: integer, format: int64 }, description: 生成任务 ID }
responses:
'200':
description: 成功
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
/app-api/aigc/task/{id}/retry:
post:
tags: [app-aigc]
summary: 重试生成任务(P-CRT-09 重生成;仅 failed/timed_out 可重试,新建任务)
description: >-
基于原任务 prompt+templateId 新建一条 game_aigc_task 重新入队(retry_of 指向原任务),返回新 taskId。
仅终态失败可重试;成功/进行中拒绝(错误码 1-101-002-002)。
parameters:
- { name: id, in: path, required: true, schema: { type: integer, format: int64 }, description: 原生成任务 ID }
responses:
'200':
description: 返回新建重试任务
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultGenerateResp' }
/app-api/aigc/task/my:
get:
tags: [app-aigc]
summary: 我的生成任务列表(创作者只见自己数据)
parameters:
- { name: status, in: query, required: false, schema: { type: integer }, description: 按状态机筛选(见 status 枚举) }
- { name: gameId, in: query, required: false, schema: { type: integer, format: int64 }, description: 按所属游戏筛选 }
- { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } }
- { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } }
responses:
'200':
description: 成功
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultTaskPage' }
# ===================== 管理端 /admin-api(运营/排障)=====================
/admin-api/aigc/task/page:
get:
tags: [admin-aigc]
summary: 生成任务全量分页(管理端 RBAC,排障/健康监控 T-TEL-14)
description: 跨创作者查看生成任务,支持按状态/模板/traceId 检索,用于生成成功率统计与失败排障。
parameters:
- { name: status, in: query, required: false, schema: { type: integer }, description: 按状态机筛选 }
- { name: templateId, in: query, required: false, schema: { type: string }, description: 按模板筛选 }
- { name: traceId, in: query, required: false, schema: { type: string }, description: 按全链路 traceId 精确检索 }
- { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } }
- { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } }
responses:
'200':
description: 成功
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultTaskPage' }
/admin-api/aigc/quota/claims/{userId}/reconcile:
post:
tags: [admin-aigc]
summary: 幂等补偿指定玩家的 new-api 额度 claim
description: >-
仅供受信管理端在注册消息异常后补偿单个目标玩家,后端以 aigc:quota:reconcile 权限强制鉴权,
并确认 userId 对应真实 game_player。已有 CLAIMED 记录时直接返回成功;未绑定时通过数据库 CAS
从 FREE 池领取一条;并发或重复调用由 claimed_by 与 biz_no 唯一键收敛,不会多领。
返回 true 表示调用结束时该玩家已有 CLAIMED 记录;返回 false 表示功能未启用、玩家不存在或额度池已空。
parameters:
- { name: userId, in: path, required: true, schema: { type: integer, format: int64, minimum: 1 }, description: 待补偿的 game_player.id }
responses:
'200':
description: 补偿结果
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
# ===================== Dify 回调对接点(仅契约,不实现)=====================
/admin-api/aigc/dify/callback:
post:
tags: [admin-aigc]
summary: '[对接点] Dify workflow 完成回调(契约#6 output → 驱动任务状态机;不在 MVP 实现)'
description: >-
【外部依赖对接点,仅定义契约,不实现、不 mock】Dify 工作流(6 节点)完成后回调本端点,
以契约#6 output 形态(status/traceId/gameConfig/assets/qualityScore/failureReason)驱动 game_aigc_task 状态机:
succeeded → 只回填 game_version.id 关联 + 生成元数据(gen_task_id)并置 status=2,不写 game_version 的 package_url/checksum/bundle_size(权威写者=runtime 编译成功后回写,见 runtime V3.0.0 / runtime.yaml);failed → 落 failure_reason 置 status=3。
端鉴权:内网/服务间调用,MVP 阶段经 admin-api 受 RBAC 保护;正式实现需加签名校验。LLM 实名充值为人工闸门,本端点不触发计费。
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/DifyCallbackReqVO' }
responses:
'200':
description: 回调受理(幂等:同一 traceId 重复回调只生效一次)
content:
application/json:
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
components:
schemas:
# ---- Huijing CommonResult 信封 ----
CommonResultBoolean:
type: object
properties:
code: { type: integer, description: '0=成功,非0=错误码 1-101-***-***' }
data: { type: boolean }
msg: { type: string }
CommonResultGenerateResp:
type: object
properties:
code: { type: integer }
data: { $ref: '#/components/schemas/GenerateRespVO' }
msg: { type: string }
CommonResultTask:
type: object
properties:
code: { type: integer }
data: { $ref: '#/components/schemas/AigcTaskRespVO' }
msg: { type: string }
CommonResultTaskPage:
type: object
properties:
code: { type: integer }
data:
type: object
properties:
list: { type: array, items: { $ref: '#/components/schemas/AigcTaskRespVO' } }
total: { type: integer, format: int64 }
msg: { type: string }
CommonResultTemplateList:
type: object
properties:
code: { type: integer }
data: { type: array, items: { $ref: '#/components/schemas/TemplateRespVO' } }
msg: { type: string }
# ---- 请求 VO ----
AigcGenerateReqVO:
type: object
description: >-
异步生成/调整请求。服务端先校验作品和版本归属,再执行幂等查询:非空 gameId 必须归当前用户;
versionId/baseVersionId 必须归当前用户且所属 gameId 与请求一致;任一版本字段非空时 gameId 必填;
mode 与 baseVersionId 必须同时为空或同时非空;空 mode 表示 create。
required: [prompt]
properties:
prompt: { type: string, minLength: 1, maxLength: 1000, description: 创作者一句话自然语言 Prompt(→ 契约#6 input.prompt) }
templateId: { type: string, description: '玩法模板 ID(→ 契约#6 input.templateId;可选,缺省=generic:W-CLEAN 废玩法模板层后,一句话创作无需选模板,入口将缺省 templateId 归一为 generic 单一通用口径,品类区分留 prompt 层;前端亦可显式从模板列表选)' }
gameId: { type: integer, format: int64, description: '关联游戏 ID(project.game_project.id)。非空时服务端强制校验 owner=当前用户;versionId/baseVersionId 必须属于同一 gameId' }
versionId: { type: integer, format: int64, description: '关联版本 ID(project.game_version.id)。非空时 gameId 必填,且服务端强制校验版本 owner=当前用户、版本所属游戏=gameId' }
mode:
type: string
nullable: true
enum: [deterministic, regenerate-module, extend]
description: '生命周期模式;空=create。mode 与 baseVersionId 必须同时为空或同时非空'
baseVersionId:
type: integer
format: int64
description: 'modify/extend 的可信基线版本。非空时 mode/gameId 必填,且服务端强制校验版本 owner=当前用户、版本所属游戏=gameId'
modifyPatch:
type: string
nullable: true
description: '调整寻址与载荷 JSON 字符串;create 可空'
assetContext:
type: string
nullable: true
description: '六类素材引用上下文 JSON 字符串;可空'
idempotencyKey:
type: string
nullable: true
description: '请求幂等键;资源归属校验先于幂等命中,不能用旧 key 绕过当前请求授权'
attachments:
type: array
description: 附件驱动创作上下文(图片/参考,可空;→ 契约#6 input.attachments)
items:
type: object
properties:
type: { type: string, description: 附件类型 }
url: { type: string, format: uri, description: 附件 URL }
DifyCallbackReqVO:
type: object
description: '[对接点] 形态对齐契约#6 dify-workflow-io.json 的 output。仅契约,不实现。'
required: [traceId, status]
properties:
traceId: { type: string, description: 贯穿全链路 traceId(与 game_aigc_task.trace_id 匹配定位任务) }
status: { type: string, enum: [succeeded, failed], description: 'Dify 生成结果状态(→ 驱动任务状态机 2/3)' }
templateId: { type: string, description: 最终匹配/使用的模板 ID }
gameConfig: { type: object, description: '生成的可玩配置(注入 GamePackage.gameConfig)' }
assets:
type: array
description: 生成资源引用(合入 GamePackage.assets)
items:
type: object
properties:
type: { type: string }
url: { type: string, format: uri }
qualityScore: { type: number, minimum: 0, maximum: 1, description: '生成质量分(0-1),对齐契约#6 Dify output 0-1,质量门禁参考;≠ telemetry/feed 的运营质量分(0-100),两者不可互相回灌' }
failureReason: { $ref: '#/components/schemas/FailureReason' }
# ↓↓ P3 生成主线 additive 字段(Java DifyCallbackReqVO 已加,此处补 yaml 漂移对齐;契约先行收口)↓↓
engineBundle:
type: string
nullable: true
description: >-
引擎包路 bundle 文本(P3 additive;worker 产 __GameBundle iife bundle 全文,
→ DifyCallbackTxService.resolveEngineBundleText 取值写入 GamePackage.engineBundle 落包)。
与 GamePackage.engineBundle(契约#4 落库侧产物)分清:本字段是回调入参侧载体。
存量 M-b 回调不带本字段时为 null,组包链省略该键、字节零变化。
# ↓↓ W-G1 组B 新增(additive 可选):9d 富生成轨迹账本 ↓↓
trace:
type: object
nullable: true
description: >-
9d 生成轨迹账本(W-G1 组B,additive 可选):worker 已产出的富生成数据
(guards 九门逐门/cost/models/attempts/gatespec/verdict/repairs/wall/similarity)。
字段全可选(worker 路径未定,按必填子集统计完整率);后端 best-effort 落
game_aigc_task.trace_json,落库失败不阻断主回调链。存量回调不带本字段时为 null,trace_json 列 NULL,字节零变化。
# ---- 响应 VO ----
GenerateRespVO:
type: object
description: 提交/重试生成任务的返回(异步,立即返回任务句柄)
properties:
taskId: { type: integer, format: int64, description: 生成任务 ID(轮询用) }
status: { type: integer, description: '初始状态:0 queued', example: 0 }
traceId: { type: string, description: 贯穿全链路 traceId(透传给前端/日志关联) }
AigcTaskRespVO:
type: object
description: 生成任务详情(轮询返回)。终态 succeeded 带产物引用;失败带 failureReason。
properties:
id: { type: integer, format: int64, description: 生成任务 ID(taskId) }
prompt: { type: string, description: 创作者 Prompt }
templateId: { type: string, description: 模板 ID }
gameId: { type: integer, format: int64, description: '关联游戏 ID(project.game_project.id)' }
versionId: { type: integer, format: int64, description: '产物落地的版本 ID(project.game_version.id);succeeded 后回填' }
status: { type: integer, description: '状态机:0 queued 排队 / 1 running 生成中 / 2 succeeded 成功 / 3 failed 失败 / 4 timed_out 超时 / 5 canceled 已取消' }
a2aStatus: { type: string, enum: [submitted, working, input-required, completed, failed, canceled], description: 'A2A 语义状态(M3a U3 additive):由 status 映射(queued→submitted / running→working / succeeded→completed / failed,timed_out→failed / canceled→canceled);前端可选消费、底层换实现不感知;input-required 为协议占位(内部六态无对应、M3a 不可触发,交互待切片三 A11)。保留上方 integer status 不破坏既有调用方。' }
progress: { type: integer, minimum: 0, maximum: 100, description: 进度百分比(0-100,前端进度条 P-CRT-12) }
qualityScore: { type: number, minimum: 0, maximum: 1, description: '生成质量分(0-1),对齐契约#6 Dify output 0-1(succeeded 后回填);≠ telemetry/feed 的运营质量分(0-100),两者不可互相回灌' }
packageUrl: { type: string, description: '产物 GamePackage(manifest) OSS URL(= game_version.package_url,只读透传)。aigc 不写此字段,权威写者=runtime 编译成功后回写(见 runtime V3.0.0 / runtime.yaml)' }
failureReason: { $ref: '#/components/schemas/FailureReason' }
retryOf: { type: integer, format: int64, description: 若本任务为重试,指向原任务 ID }
traceId: { type: string, description: 全链路 traceId }
createTime: { type: string, format: date-time, description: 创建时间 }
finishTime: { type: string, format: date-time, description: 终态完成时间 }
TemplateRespVO:
type: object
description: 玩法模板(aigc 模板注册表 T-AGC-03)
properties:
templateId: { type: string, description: '模板 ID(如 dodge/runner/clicker/puzzle/shooter)' }
name: { type: string, description: 模板名称 }
description: { type: string, description: 模板说明 }
coverUrl: { type: string, description: 模板封面/示意图 URL }
examplePrompt: { type: string, description: 示例 Prompt(P-CRT-11 示例 Prompt 库) }
# ---- 共享枚举:失败原因分类(T-AGC-10;取值对齐契约#6 output.failureReason)----
FailureReason:
type: string
description: >-
生成失败原因分类(结构化错误码,映射用户可读提示)。取值与契约#6 dify-workflow-io.json output.failureReason 一致。
unsafe_prompt=Prompt 不安全 / intent_unclear=意图不清 / no_template_match=无匹配模板 /
config_invalid=配置校验失败 / llm_error=LLM 异常 / timeout=超时 / asset_gen_failed=素材生成失败 /
quota_exhausted=生成额度已用完(WU2 new-api per-user 额度耗尽/池空,网关 402/403)。
enum: [unsafe_prompt, intent_unclear, no_template_match, config_invalid, llm_error, timeout, asset_gen_failed, quota_exhausted]