合并 master 最新更新
合入 master 的 BgFilter、CI、运维与现役平台改造。 保留 AI 游戏创作 Runtime、独立锁文件与原生壳验证链路。 修复共享充值账单组件、LLM 网关与退役 Agent 兼容边界。 同步冲突文档、锁文件和开发脚本。
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# 【前端架构】Platform Selection Stage Model 收口计划
|
||||
|
||||
> 2026-07-18 退役覆盖:本文涉及旧玩法 stage、生成页、结果页、公开详情和运行态的规则仅作为历史设计记录,不再进入现役前端编译链。当前稳定入口为 `/creation`、`/project` 与 `/profile`。
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 在受保护数据失效后会清空当前用户的私有作品、运行态、草稿 notice 和生成状态。清理完成后,壳层还要判断当前 `SelectionStage` 是否还能继续展示:公开首页、公开详情、工作台入口等阶段可保留;结果页、生成页、运行态、个人反馈等依赖私有数据或运行态快照的阶段必须回到首页。
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -68,7 +68,7 @@
|
||||
## 第六阶段模块
|
||||
|
||||
- `ImageCanvasInteractionModel.ts`
|
||||
- 承载画布交互纯计算:适合视图、中心缩放、普通滚轮纵向滚动、Ctrl / Cmd 滚轮缩放、画布坐标换算、框选命中、平移、生成占位框拖拽、图层拖拽吸附、小地图投影、小地图点击定位和小地图拖拽视图移动。
|
||||
- 承载画布交互纯计算:适合视图、中心缩放、普通滚轮按原始 `deltaX / deltaY` 二维平移、Shift 且 `deltaX = 0` 时由输入适配层把 `deltaY` 映射为横向位移、Ctrl / Cmd 滚轮缩放、画布坐标换算、框选命中、平移、生成占位框拖拽、图层拖拽吸附、小地图投影、小地图点击定位和小地图拖拽视图移动。
|
||||
- 主视图继续负责 React 事件对象、pointer capture、history 快照、生成对象回写、选中态和 `setState`。
|
||||
- 该模块用独立单测覆盖小地图灵敏度、吸附、多选拖拽和滚轮缩放等之前容易回退的交互规则。
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
## 第十七阶段模块
|
||||
|
||||
- `useImageCanvasViewportControls.ts`
|
||||
- 承载画布视口控制:`viewport`、`canvasSize`、小地图投影、适合视图、中心缩放、普通滚轮纵向滚动、Ctrl / Cmd 滚轮缩放、屏幕点到画布 / 世界坐标换算和小地图点击 / 拖拽移动视图。
|
||||
- 承载画布视口控制:`viewport`、`canvasSize`、小地图投影、适合视图、中心缩放、普通滚轮按原始 `deltaX / deltaY` 二维平移、Shift 且 `deltaX = 0` 时的横向位移适配、Ctrl / Cmd 滚轮缩放、屏幕点到画布 / 世界坐标换算和小地图点击 / 拖拽移动视图。
|
||||
- 主视图继续负责图层拖拽、生成占位框拖拽、框选、多选、历史触发时机、上传 drop 分流和小地图 pointer down 事件;该 hook 只作为视口控制协调器,不接管画布完整 pointer 状态机。
|
||||
- 该 hook 用独立单测覆盖尺寸同步、适合视图、中心缩放、坐标换算、滚轮语义和小地图移动,为后续抽 `useImageCanvasStageInteractions` 预留更清晰的视口接口。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 后台管理多账号与 Tab 访问权限方案
|
||||
|
||||
更新时间:`2026-07-14`
|
||||
更新时间:`2026-07-23`
|
||||
|
||||
## 1. 文档定位
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
|
||||
## 2. 当前基线与目标
|
||||
|
||||
当前后台由 `GENARRATIVE_ADMIN_USERNAME`、`GENARRATIVE_ADMIN_PASSWORD` 提供唯一管理员账号,`apps/admin-web/src/app/adminRoutes.ts` 定义 18 个一级 Tab,`server-rs/crates/api-server/src/modules/admin.rs` 中的后台路由只校验统一的管理员 JWT。
|
||||
当前后台由 `GENARRATIVE_ADMIN_USERNAME`、`GENARRATIVE_ADMIN_PASSWORD` 提供唯一管理员账号,`apps/admin-web/src/app/adminRoutes.ts` 定义 15 个可分配业务 Tab,并另有 owner-only 的“账号管理”Tab;`server-rs/crates/api-server/src/modules/admin.rs` 中的后台路由只校验统一的管理员 JWT。
|
||||
|
||||
改造后的目标如下:
|
||||
|
||||
1. 现有环境变量账号升级为 `owner`,仍由部署环境提供,不迁移、不复制到 SpacetimeDB。
|
||||
2. owner 始终拥有全部 18 个业务 Tab 权限,并独占“账号管理”Tab 和账号管理 API。
|
||||
2. owner 始终拥有全部 15 个业务 Tab 权限,并独占“账号管理”Tab 和账号管理 API。
|
||||
3. owner 可以创建、修改、启停 member;member 保存在 SpacetimeDB 私有表 `admin_account`。
|
||||
4. member 按一级 Tab 分配权限;获得一个 Tab 权限即获得该页面内全部读写能力,页面内部二级 Tab、弹窗和操作区继承一级权限。
|
||||
5. member JWT 每次请求都重新读取当前账号并校验 `enabled`、`token_version` 和实时权限,权限、密码或启停变更应立即让旧 JWT 失效。
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
- owner 用户名和密码继续读取 `GENARRATIVE_ADMIN_USERNAME`、`GENARRATIVE_ADMIN_PASSWORD`。
|
||||
- owner 是环境变量构造的虚拟账号,不写入 `admin_account`,不允许通过后台改名、改密、禁用或删除。
|
||||
- owner 始终拥有本文列出的全部 18 个可分配权限,不能在前端取消,也不从数据库加载权限。
|
||||
- owner 始终拥有本文列出的全部 15 个可分配权限,不能在前端取消,也不从数据库加载权限。
|
||||
- “账号管理”是 owner-only 能力。它可以作为新增一级路由 `accounts` / `#accounts` 展示,但 `accounts` 不进入 `ADMIN_TAB_PERMISSIONS`,不能写入 member 的 `permissions_json`。
|
||||
- owner 会话返回 `accountRole = "owner"`、`roles = ["admin", "owner"]`;账号管理权限必须根据服务端确认的 `accountRole` 判断,不能只相信前端角色字符串。
|
||||
- owner 配置缺失时,后台整体保持未启用状态;不能依赖数据库中的 member 绕过 owner 引导配置启动后台。
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
## 4. 权限标识
|
||||
|
||||
`ADMIN_TAB_PERMISSIONS` 必须是 shared-contracts 与 admin-web 共用的闭合集合,值与现有 `AdminRouteId` 一致。18 个可分配权限如下,顺序同时作为前端寻找“第一可访问项”的稳定顺序:
|
||||
`ADMIN_TAB_PERMISSIONS` 必须是 shared-contracts 与 admin-web 共用的闭合集合,值与现有 `AdminRouteId` 一致。15 个可分配权限如下,顺序同时作为前端寻找“第一可访问项”的稳定顺序:
|
||||
|
||||
| permission id | 一级 Tab | hash |
|
||||
| --- | --- | --- |
|
||||
@@ -60,9 +60,6 @@
|
||||
| `editor-generation-pricing` | 模型定价 | `#editor-generation-pricing` |
|
||||
| `editor-showcase` | 精选审核 | `#editor-showcase` |
|
||||
| `editor-assets` | 素材查询 | `#editor-assets` |
|
||||
| `creation-announcement` | 入口公告 | `#creation-announcement` |
|
||||
| `creation-entry` | 入口开关 | `#creation-entry` |
|
||||
| `work-visibility` | 作品可见性 | `#work-visibility` |
|
||||
|
||||
权限数组必须去重并按上表顺序规范化后保存。保存时拒绝未知值和 `accounts`;读取旧数据时遇到未知值应忽略并记录告警,绝不能将未知值解释为全权限。空数组合法,表示 member 可以登录但没有业务页面权限。
|
||||
|
||||
@@ -83,7 +80,7 @@
|
||||
| `username` | `String` | `unique`;登录名,创建后不可修改;按 `trim + ASCII lowercase` 规范化 |
|
||||
| `display_name` | `String` | 展示名,去除首尾空白后 1 至 64 字符 |
|
||||
| `password_hash` | `String` | Argon2id PHC 字符串;只在内部登录查询中返回给 api-server,永不进入 HTTP DTO、日志或前端状态 |
|
||||
| `permissions_json` | `String` | 规范化后的 Tab permission JSON;只允许第 4 节 18 个值,空数组为 `[]` |
|
||||
| `permissions_json` | `String` | 规范化后的 Tab permission JSON;只允许第 4 节 15 个值,空数组为 `[]` |
|
||||
| `enabled` | `bool` | 是否允许登录和继续使用现有 JWT |
|
||||
| `token_version` | `u64` | 初始为 `1`;权限、密码或启停状态发生有效变化时加 `1` |
|
||||
| `created_by` | `String` | 创建者后台 subject;当前只能是 owner subject |
|
||||
@@ -163,7 +160,7 @@ accountRole: "owner" | "member"
|
||||
tabPermissions: string[]
|
||||
```
|
||||
|
||||
owner 返回全部 18 个 permission id;member 返回数据库中的实时规范化数组。`GET /admin/api/me` 同样执行逐请求校验并返回实时权限,供刷新页面后恢复导航。
|
||||
owner 返回全部 15 个 permission id;member 返回数据库中的实时规范化数组。`GET /admin/api/me` 同样执行逐请求校验并返回实时权限,供刷新页面后恢复导航。
|
||||
|
||||
后台所有面向运营展示的管理员身份统一使用 `displayName`。审计表继续保存稳定 subject,例如 owner subject 或 `admin-account-<uuid>`;api-server 在返回兑换码、邀请码等操作记录时,按 owner 运行态和 `admin_account` 批量解析显示名称,同时兼容历史用户名记录。已无法解析的历史主体统一展示“已停用管理员”,前端不得直接渲染 `operatorUserId`、账号 ID 或登录用户名代替显示名称。对写接口,显示名目录必须在主事务前加载,或在主事务成功后降级为占位文案;不得因二次读取失败把已提交写入伪装成失败。
|
||||
|
||||
@@ -196,10 +193,6 @@ owner 返回全部 18 个 permission id;member 返回数据库中的实时规
|
||||
| `GET` | `/admin/api/tracking/event-keys` | `tracking OR tasks` |
|
||||
| `GET` | `/admin/api/database/tables` | `tables` |
|
||||
| `GET` | `/admin/api/database/tables/{table_name}/rows` | `tables` |
|
||||
| `GET` | `/admin/api/creation-entry/config` | `gray-release OR creation-announcement OR creation-entry` |
|
||||
| `POST` | `/admin/api/creation-entry/config` | `creation-entry` |
|
||||
| `POST` | `/admin/api/creation-entry/config/banners` | `creation-announcement` |
|
||||
| `POST` | `/admin/api/creation-entry/config/interactions` | `creation-entry` |
|
||||
| `GET` | `/admin/api/feature-gates` | `gray-release` |
|
||||
| `PUT` | `/admin/api/feature-gates` | `gray-release` |
|
||||
| `GET` | `/admin/api/editor-generation-pricing` | `editor-generation-pricing` |
|
||||
@@ -212,8 +205,6 @@ owner 返回全部 18 个 permission id;member 返回数据库中的实时规
|
||||
| `GET` | `/admin/api/editor-showcase/campaign` | `editor-showcase` |
|
||||
| `POST` | `/admin/api/editor-showcase/campaign` | `editor-showcase` |
|
||||
| `POST` | `/admin/api/editor-showcase/campaign/image-upload-ticket` | `editor-showcase` |
|
||||
| `GET` | `/admin/api/works/visibility` | `work-visibility` |
|
||||
| `POST` | `/admin/api/works/visibility` | `work-visibility` |
|
||||
| `GET` | `/admin/api/profile/redeem-codes` | `redeem` |
|
||||
| `POST` | `/admin/api/profile/redeem-codes` | `redeem` |
|
||||
| `POST` | `/admin/api/profile/redeem-codes/disable` | `redeem` |
|
||||
@@ -231,17 +222,18 @@ owner 返回全部 18 个 permission id;member 返回数据库中的实时规
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/execute` | `recharge-orders` |
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/register` | `recharge-orders` |
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/manual-review/resolve` | `recharge-orders` |
|
||||
| `GET` | `/admin/api/profile/users/detail` | `tables OR tracking OR recharge-orders OR editor-showcase OR editor-assets OR work-visibility` |
|
||||
| `GET` | `/admin/api/profile/users/detail` | `tables OR tracking OR recharge-orders OR editor-showcase OR editor-assets` |
|
||||
| `POST` | `/admin/api/profile/wallet-restriction` | `recharge-orders` |
|
||||
| `GET` | `/admin/api/accounts` | owner-only |
|
||||
| `POST` | `/admin/api/accounts` | owner-only |
|
||||
| `PUT` | `/admin/api/accounts/{account_id}` | owner-only |
|
||||
|
||||
三个共享读取接口必须按 OR 规则实现,不能为了复用简单中间件扩大成“任意 member 可访问”:
|
||||
两个共享读取接口必须按 OR 规则实现,不能为了复用简单中间件扩大成“任意 member 可访问”:
|
||||
|
||||
- `/admin/api/assets/read-url` 只服务素材查询和精选审核。
|
||||
- `/admin/api/profile/users/detail` 只服务当前实际包含用户详情入口的表查询、埋点数据、充值管理、精选审核、素材查询和作品可见性页面。
|
||||
- `GET /admin/api/creation-entry/config` 同时为灰度发布、入口公告和入口开关提供页面初始化数据;写操作仍按具体页面单独收紧。
|
||||
- `/admin/api/profile/users/detail` 只服务当前实际包含用户详情入口的表查询、埋点数据、充值管理、精选审核和素材查询页面。
|
||||
|
||||
`gray-release` 页面只调用 `GET/PUT /admin/api/feature-gates`;旧 `/admin/api/creation-entry/config*` 已退役,不能再作为灰度页面初始化依赖。页面固定目标只登记现役功能,其他通用 gate 仍可通过 Gate Key 直接管理。
|
||||
|
||||
## 10. 账号管理 HTTP 契约
|
||||
|
||||
@@ -303,7 +295,7 @@ accounts: Array<{
|
||||
|
||||
### 11.1 路由与导航
|
||||
|
||||
- `adminRoutes` 增加权限元数据;18 个业务路由使用同名 permission id。
|
||||
- `adminRoutes` 增加权限元数据;15 个业务路由使用同名 permission id。
|
||||
- `accounts` 路由只在 `admin.accountRole === "owner"` 时加入侧栏和移动底栏,不属于 member 可分配列表。
|
||||
- member 导航只渲染 `admin.tabPermissions` 包含的业务路由。页面组件也必须只在当前路由已授权时挂载,避免隐藏导航后仍发起无权限 API。
|
||||
- owner 渲染全部业务路由和账号管理路由。
|
||||
@@ -315,13 +307,13 @@ accounts: Array<{
|
||||
1. 当前 hash 对应可访问路由时保持不变。
|
||||
2. hash 未知、属于无权限业务 Tab,或 member 访问 `#accounts` 时,使用 `replaceState` 回落到按第 4 节顺序找到的第一可访问业务 Tab。
|
||||
3. member 权限为空时,不回落 Dashboard;渲染独立的零权限空态,只保留账号信息和退出登录,不挂载任何业务页,也不发起业务 API。
|
||||
4. owner 的默认项仍可保持 Dashboard;账号管理不改变 18 个业务路由的排序。
|
||||
4. owner 的默认项仍可保持 Dashboard;账号管理不改变 15 个业务路由的排序。
|
||||
|
||||
后端返回 `403` 时,前端重新请求 `/me` 获取实时权限并执行上述回落。即使前端状态陈旧或被篡改,后端权限 middleware 仍必须拒绝越权请求。
|
||||
|
||||
### 11.3 账号管理页
|
||||
|
||||
- 权限编辑器展示 18 个明确的 checkbox,每项使用现有 Tab 中文名称;不能展示或提交 `accounts`。
|
||||
- 权限编辑器展示 15 个明确的 checkbox,每项使用现有 Tab 中文名称;不能展示或提交 `accounts`。
|
||||
- 创建和编辑使用独立弹窗或抽屉,不在列表下方追加表单。
|
||||
- 编辑时密码字段默认空,空表示请求中省略 `password`;页面永不展示现有密码或 hash。
|
||||
- 停用使用开关并二次确认。保存成功后以 API 返回 account snapshot 更新列表。
|
||||
@@ -391,12 +383,12 @@ spacetime publish <database> \
|
||||
- 权限、密码、启停更新各自会递增 `token_version`;同一次请求修改多项只递增一次;仅改展示名不递增。
|
||||
- member 被停用、改密或改权限后,旧 JWT 下一次请求返回 401;重新登录后获得实时权限。
|
||||
- API-to-Tab 矩阵逐路由覆盖 `modules/admin.rs`,每条路由至少测试 owner 成功、具备权限的 member 成功、缺权限 member 返回 403。
|
||||
- 三个共享读取接口分别覆盖每个允许 permission 的成功用例,以及无关 permission 的 403 用例。
|
||||
- 两个共享读取接口分别覆盖每个允许 permission 的成功用例,以及无关 permission 的 403 用例。
|
||||
- owner-only 账号 API 对任意 member 都返回 403,即使其 `permissions_json` 被污染为包含 `accounts`。
|
||||
|
||||
### 14.2 前端
|
||||
|
||||
- owner 看到 18 个业务 Tab 和账号管理;member 只看到被分配的业务 Tab。
|
||||
- owner 看到 15 个业务 Tab 和账号管理;member 只看到被分配的业务 Tab。
|
||||
- 每个一级 Tab 内的二级 Tab、弹窗和写操作继承一级权限并正常使用,不出现“页面可见但内部 API 403”的错误映射。
|
||||
- 直接输入无权限 hash 自动替换为第一可访问项,不短暂挂载无权限页面。
|
||||
- 当前 Tab 权限被 owner 收回后,下一请求触发重新登录;新会话恢复后落到第一可访问项。
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,10 @@
|
||||
# 外部生成 Worker 化方案
|
||||
|
||||
更新时间:`2026-07-15`
|
||||
> 2026-07-18 退役覆盖:旧创作模板 job 类型、玩法写回和玩法恢复链路均已退出现役 worker。当前 worker 只领取 `source_module = editor-canvas` 的任务;本文涉及拼图、跳一跳、拼消消、敲木鱼等玩法的内容仅作为历史设计记录,历史队列行不得被领取或改写。
|
||||
|
||||
> 2026-07-21 已实施、待生产压测专题:BgFilter 作为受限内部资源,仍遵守“单用户动作一个外部生成 job”;用户可见层与调度层都只有父 `external_generation_job`。父 future 保持原 lease 和 attempt,在当前调用栈内同步请求唯一 `bgfilter-worker` 的内部 HTTP,成功图片字节直接返回父流程。首版不新增 SpacetimeDB 子任务表、父 checkpoint / continuation 或 raw 中间结果 OSS。完整边界见 [`BgFilter 受限资源调度方案(同步内部 HTTP 原地等待版)`](./【后端架构】BgFilter受限资源调度方案-2026-07-21.md)。
|
||||
|
||||
更新时间:`2026-07-21`
|
||||
|
||||
## 背景
|
||||
|
||||
@@ -122,7 +126,11 @@ worker 配置:
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_WORKER_POLL_INTERVAL_MS`:空队列轮询间隔。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_WORKER_LEASE_SECONDS`:任务 lease 时长,默认 `600`;worker 会按约三分之一 lease、最长 30 秒的间隔续租。该值应覆盖一次心跳网络抖动窗口,不需要大于完整外部生成链路耗时。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_WORKER_JOB_TIMEOUT_SECONDS`:普通外部生成 job 的执行预算,默认 `900`。超过预算后当前 worker 停止续租并释放 worker 槽位,但不取消已启动的业务 future,也不主动写入失败 / 重试状态;在途执行交由 lease fencing 仲裁:写回在租约有效期内到达则照常完成,否则被拒绝,租约过期后任务可被重新认领,attempt 耗尽时由认领事务原子标记失败并结算退款。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_WORKER_LONG_JOB_TIMEOUT_SECONDS`:视频、角色动作等长耗时 job 的执行预算,默认 `1800`。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_WORKER_LONG_JOB_TIMEOUT_SECONDS`:VectorEngine 图片生成 / 编辑、图标 spritesheet 生成、UI 素材提取以及角色动作、视频等长耗时 job 的执行预算,默认 `1800`。其中四类 VectorEngine 图片 job 固定为 `editor_image_generation`、`editor_image_edit`、`editor_icon_spritesheet_generation` 和 `editor_ui_design_asset_extraction`;手动去背景等不直接调用 VectorEngine 的 job 继续使用普通预算。
|
||||
|
||||
worker 在单次 job 开始执行时从同一个单调时钟起点计算绝对 `job deadline` 和更早的 `provider deadline`:常规情况下为终态审计、OSS 持久化及 `complete/fail` 回写保留 `60` 秒;当整个 job 预算小于 `120` 秒时,保留其一半,避免 provider 预算被全部吃掉。该 deadline 只通过进程内 `RequestContext` 传给 VectorEngine 图片调用,不写入 HTTP DTO、队列 payload 或 SpacetimeDB;普通 HTTP / `inline` 上下文没有 deadline,保持原有行为。
|
||||
|
||||
VectorEngine 每次发送的实际 timeout 取 `min(VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS, provider 剩余预算)`。遇到可重试传输错误或 408 / 429 / 5xx 时,只有当剩余预算还容得下本次退避和下一次 attempt 才继续;否则立即停止重试并返回当前 provider 错误,deadline 耗尽时返回 timeout。同一绝对 deadline 同时覆盖参考图下载、provider 请求 / 响应和响应图片 URL 下载,不允许请求已返回后的图片下载越过 provider 预算。`VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 默认仍为 `1000000`;配置加载层允许显式值低于默认值,不再在读取环境变量时强制抬升。
|
||||
|
||||
controller 配置:
|
||||
|
||||
@@ -134,7 +142,7 @@ controller 配置:
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_SERVICE_TEMPLATE`:systemd worker 模板,默认 `genarrative-external-generation-worker@{}.service`。
|
||||
- `GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_DRY_RUN`:只记录决策不执行 systemctl,默认 `false`。
|
||||
|
||||
动态缩扩容方式:生产默认由 `deploy/systemd/genarrative-external-generation-controller.service` 启动 `GENARRATIVE_PROCESS_ROLE=external-generation-controller`,controller 读取 `get_external_generation_queue_stats_and_return` 后对 `genarrative-external-generation-worker@N.service` 执行精确 `systemctl start/stop`;无需改变 HTTP 进程数。controller 只操作 `@1..@MAX` 中的缺口或最高编号多余实例,保留 `@1` 作为保底 worker。缩容或发布重启 worker 时,进程收到 SIGINT/SIGTERM 后会停止 claim 新任务并等待当前任务完成;若进程被硬杀、机器断电或超过 systemd `TimeoutStopSec`,未完成任务会在 lease 过期后被其它 worker 重新领取。若 worker 内业务 future 长时间无返回,执行预算到期后 worker 会停止续租并释放槽位,在途 future 继续运行至租约仲裁窗口;有效租约内的写回仍可完成,租约过期后才会由其它 worker 重新认领,避免客户端取消与服务端写回竞态。容器链路已有独立 `external-generation-worker` compose service;扩 worker 必须扩这个 worker service,不能只扩 `api-server` HTTP service。
|
||||
动态缩扩容方式:生产默认由 `deploy/systemd/genarrative-external-generation-controller.service` 启动 `GENARRATIVE_PROCESS_ROLE=external-generation-controller`,controller 读取 `get_external_generation_queue_stats_and_return` 后对 `genarrative-external-generation-worker@N.service` 执行精确 `systemctl start/stop`;无需改变 HTTP 进程数。controller 只操作 `@1..@MAX` 中的缺口或最高编号多余实例,保留 `@1` 作为保底 worker。缩容或发布重启 worker 时,进程收到 SIGINT/SIGTERM 后会停止 claim 新任务并等待当前任务完成;若进程被硬杀、机器断电或超过 systemd `TimeoutStopSec`,未完成任务会在 lease 过期后被其它 worker 重新领取。VectorEngine 图片链路会先于整个 job 执行预算停止 provider 发送 / 重试,以便 worker 在有效 lease 内完成终态写回;若其他业务 future 仍长时间无返回,执行预算到期后 worker 会停止续租并释放槽位,在途 future 继续运行至租约仲裁窗口;有效租约内的写回仍可完成,租约过期后才会由其它 worker 重新认领,避免客户端取消与服务端写回竞态。本次预算收口不改变 lease 续租 / fencing、迟到写回仲裁、attempt 耗尽收口和原子退款语义。容器链路已有独立 `external-generation-worker` compose service;扩 worker 必须扩这个 worker service,不能只扩 `api-server` HTTP service。
|
||||
|
||||
## 已接入的拼图纵切
|
||||
|
||||
@@ -186,7 +194,7 @@ controller 配置:
|
||||
|
||||
- `editor_image_generation`:普通图片、生成规范、角色形象、UI 设计图、宣发素材和图片快速编辑。
|
||||
- `editor_image_edit`:图片编辑 / 修改结果。
|
||||
- `editor_background_removal`:手动去除任意图片背景,worker 使用 BgFilter complex 模式、首次失败后重试 `1` 次,并把执行阶段标记为 `processing`。
|
||||
- `editor_background_removal`:手动去除任意图片背景,父 `external-generation-worker` 至多让 worker 接收一次内部 HTTP RPC(连接从未建立时按调度方案 §5.1 有界重连)并把执行阶段标记为 `processing`;唯一 `bgfilter-worker` 使用 BgFilter complex 模式,对同一次逻辑调用最多执行两次顺序 provider attempt,两次都失败时把类型化错误返回父流程。
|
||||
- `editor_icon_spritesheet_generation`:图标素材 spritesheet 生成和拆分。
|
||||
- `editor_ui_design_asset_extraction`:UI 设计图红框素材提取。
|
||||
- `editor_character_animation_generation`:角色动作视频和帧素材生成。
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 统一公开作品 ReadModel 设计
|
||||
|
||||
> 2026-07-18 退役覆盖:统一公开作品 BFF、read model、逐玩法 source view 和互动链路均已退出现役编译与路由。相关历史表只作为 schema 数据壳保留;新版 `/creation` 只读取编辑器精选素材。本文其余内容仅作为历史设计记录。
|
||||
|
||||
更新时间:`2026-05-26`
|
||||
|
||||
## 背景
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
- 代理路径上的上游连接失败会返回统一 JSON;`502` 使用 `GATEWAY_UPSTREAM_ERROR`,`504` 使用 `GATEWAY_UPSTREAM_TIMEOUT`,避免 shadow / canary 阶段把框架默认错误页透给前端或巡检。
|
||||
- 上游超时显式配置在 Pingora peer 上:连接超时默认 `3000ms`,没有 Nginx 显式长超时的代理路由读取默认 `60s`,通用 `/api`、公开列表 / 详情和 SpacetimeDB subscribe 读取默认 `3600s`,写入超时默认 `3600s`。读 / 写 / 连接超时统一映射为 JSON `504 GATEWAY_UPSTREAM_TIMEOUT`。
|
||||
- 默认开启 gzip 响应压缩,`GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL=5` 和 `GENARRATIVE_PINGORA_GATEWAY_GZIP_MIN_LENGTH_BYTES=1024` 对齐当前 Nginx `gzip_comp_level 5` / `gzip_min_length 1024`;`GENARRATIVE_PINGORA_GATEWAY_GZIP_ENABLED=false` 时禁用。`GENARRATIVE_PINGORA_GATEWAY_COMPRESSION_ALGORITHMS=gzip` 是当前唯一允许的压缩算法白名单;网关会在进入 Pingora compression 模块前把 `Accept-Encoding` 收敛为 gzip,避免未验收的 `br` / `zstd` 被隐式打开。压缩能力由 `check:pingora-gateway-smoke` 用小响应不压缩、图片资源不压缩、大响应 `Accept-Encoding: gzip`、`Accept-Encoding: br, gzip`、`Content-Encoding: gzip`、`Vary: Accept-Encoding` 和解压后的响应体一起验证。Brotli 不进入当前 Pingora 正式化口径,仍由 Nginx / 前置代理能力探测承担;直连 Pingora 时不把 Brotli parity 作为切换门禁。
|
||||
- 默认开启单进程内接流保护,按客户端 IP 与路由组执行并发上限、RPS 和 burst 限制。保护组默认对齐当前 Nginx:`admin_api=64/30rps/16burst`、`gallery_list=320/5000rps/4096burst`、`gallery_detail=32/300rps/32burst`、`api=64/300rps/64burst`、`spacetime=256/1000rps/256burst`。超限返回 JSON `429` 并带 `Retry-After: 1`。
|
||||
- 默认开启单进程内接流保护,按客户端 IP 与路由组执行并发上限、RPS 和 burst 限制。保护组默认对齐当前 Nginx:`admin_api=64/30rps/16burst`、`api=64/300rps/64burst`、`spacetime=256/1000rps/256burst`。超限返回 JSON `429` 并带 `Retry-After: 1`。
|
||||
- 当前接流保护的默认正式口径是单 Pingora 实例。`GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT` 默认 `1`;若 `GENARRATIVE_PINGORA_GATEWAY_PROTECTION_ENABLED=true` 且 `GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT>1`,必须先落地共享限流 / 共享并发保护层,并显式设置 `GENARRATIVE_PINGORA_GATEWAY_SHARED_PROTECTION_CONFIRMED=true`,否则网关启动和目标机 direct preflight 都会失败。若关闭网关保护后横向多实例运行,则全局接流保护必须由前置 Nginx / LB 承担。
|
||||
- 默认以 TCP 对端 IP 作为限流 client key;只有在 Pingora 前置代理已经清洗 `X-Forwarded-For` 时,才允许开启 `GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR=true` 使用首个转发 IP。开启时必须同时设置 `GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED=true`,否则网关会拒绝启动。若 Pingora 直接监听公网地址,`TRUST_X_FORWARDED_FOR` 必须保持 `false`,目标机 direct preflight 会在看到公网监听加该开关时失败。
|
||||
- 可选配置 `GENARRATIVE_PINGORA_GATEWAY_GITEA_HOSTS` 和 `GENARRATIVE_PINGORA_GATEWAY_GITEA_UPSTREAM` 后,网关会先按 `Host` 归一化匹配 Gitea 域名,命中时整站代理到 Gitea 上游。Gitea 路由不走应用维护页、API 请求体上限或网关接流保护,避免影响 git clone / push。用于同一公网 IP 同时承载 `dev.genarrative.world` 与 `git.genarrative.world` 的直连切换时,TLS 证书必须同时覆盖两个域名;当前单 listener 配置只加载一组 cert/key。
|
||||
@@ -70,7 +70,7 @@ npm run check:pingora-release-readiness
|
||||
|
||||
`check:pingora-canary-docker` 会启动 mock `api-server`、mock SpacetimeDB、真实 `pingora-gateway` 和 Docker Nginx,把前缀 canary 与真实路径 canary 两份 snippet 都渲染到临时 Nginx 中,再复用 `check:pingora-canary-live` 验证 Nginx -> Pingora -> 上游的 handoff 链路。临时 Nginx 使用生产同口径 `genarrative_upstream` access log,live smoke 后会继续调用 `scripts/check-pingora-canary-access-log-parity.mjs`,按同一 `request_id` 对账 canary healthz、代表性 API、SpacetimeDB identity 和静态资源路径;前缀模式确认 Nginx rewrite 后路径与 Pingora access log 一致,真实路径模式除 healthz 探针外要求 Nginx path 与 Pingora path 完全一致。默认不拉取镜像;缺少 Docker daemon 或 `nginx:1.27-alpine` 镜像时跳过。CI / 目标 agent 上需要把它作为硬门禁时执行 `node scripts/check-pingora-canary-docker.mjs --require-docker --pull`。
|
||||
|
||||
`check:pingora-canary-access-log-parity` 会烟测只读脚本 `scripts/check-pingora-canary-access-log-parity.mjs`,用于目标机 canary 后按 `request_id` 对照 Nginx handoff access log 和 Pingora tab-separated access log。真实目标机前缀 canary 执行时使用 `/opt/genarrative/current/scripts/check-pingora-canary-access-log-parity.mjs --nginx-log-file /var/log/nginx/genarrative.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_canary/healthz --path /__genarrative_pingora_canary/api/creation-entry/config`;真实路径 canary 执行时追加 `--realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log --path /__genarrative_pingora_realpath_canary/healthz --path /api/creation-entry/config --path /v1/identity --path /assets/app.js`。脚本只读日志,不修改日志、不 reload Nginx 或 Pingora。
|
||||
`check:pingora-canary-access-log-parity` 会烟测只读脚本 `scripts/check-pingora-canary-access-log-parity.mjs`,用于目标机 canary 后按 `request_id` 对照 Nginx handoff access log 和 Pingora tab-separated access log。真实目标机前缀 canary 执行时使用 `/opt/genarrative/current/scripts/check-pingora-canary-access-log-parity.mjs --nginx-log-file /var/log/nginx/genarrative.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_canary/healthz --path /__genarrative_pingora_canary/api/editor/showcase/resources`;真实路径 canary 执行时追加 `--realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log --path /__genarrative_pingora_realpath_canary/healthz --path /api/editor/showcase/resources --path /v1/identity --path /assets/app.js`。脚本只读日志,不修改日志、不 reload Nginx 或 Pingora。
|
||||
|
||||
`check:pingora-direct-preflight` 默认只检查仓库内主 service、direct-entry drop-in 和 env 示例,适合本机提交前护栏。目标机直连切换窗口必须提供真实 env 并打开现场检查:
|
||||
|
||||
@@ -351,8 +351,8 @@ node -- /opt/genarrative/current/scripts/deploy/pingora-health-patrol-env-switch
|
||||
4. 在 `server {}` 内人工 include 该 snippet,并保持 `allow 127.0.0.1; allow ::1; deny all;` 或改成当次可信来源。
|
||||
5. 执行 `npm run check:nginx-pingora-canary`;目标机或 CI 有 Nginx 时执行 `node scripts/check-nginx-pingora-canary.mjs --require-nginx`,再执行 `nginx -t && nginx -s reload`。
|
||||
6. 执行 `GENARRATIVE_PINGORA_CANARY_BASE_URL=http://127.0.0.1 GENARRATIVE_PINGORA_CANARY_HOST=<域名> npm run check:pingora-canary-live`,确认 healthz、API、SpacetimeDB identity、静态资源和拒绝入口都带 `X-Genarrative-Nginx-Handoff: pingora-canary`。
|
||||
7. 必要时再用同一前缀访问其它代表性路由,例如 `/__genarrative_pingora_canary/api/creation-entry/config`、`/__genarrative_pingora_canary/v1/identity` 和 `/__genarrative_pingora_canary/assets/app.js`,并对照直连 Nginx 正常入口和 Pingora shadow 日志。
|
||||
8. 执行 current release 随包 `scripts/check-pingora-canary-access-log-parity.mjs --nginx-log-file /var/log/nginx/genarrative.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_canary/healthz --path /__genarrative_pingora_canary/api/creation-entry/config`,确认同一 `request_id`、`path`、`status` 和 `proxy_target` 与 Nginx access log 可对齐。对账脚本的日志路径、canary prefix、必需路径和 `--since-lines` / `GENARRATIVE_PINGORA_CANARY_ACCESS_LOG_SINCE_LINES` 都不能包含换行或 NUL;若 access log 行里解析出的 URI / path 含控制字符,脚本也会把对应行记为失败,避免污染值进入 JSON 对账输出。
|
||||
7. 必要时再用同一前缀访问其它代表性路由,例如 `/__genarrative_pingora_canary/api/editor/showcase/resources`、`/__genarrative_pingora_canary/v1/identity` 和 `/__genarrative_pingora_canary/assets/app.js`,并对照直连 Nginx 正常入口和 Pingora shadow 日志。
|
||||
8. 执行 current release 随包 `scripts/check-pingora-canary-access-log-parity.mjs --nginx-log-file /var/log/nginx/genarrative.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_canary/healthz --path /__genarrative_pingora_canary/api/editor/showcase/resources`,确认同一 `request_id`、`path`、`status` 和 `proxy_target` 与 Nginx access log 可对齐。对账脚本的日志路径、canary prefix、必需路径和 `--since-lines` / `GENARRATIVE_PINGORA_CANARY_ACCESS_LOG_SINCE_LINES` 都不能包含换行或 NUL;若 access log 行里解析出的 URI / path 含控制字符,脚本也会把对应行记为失败,避免污染值进入 JSON 对账输出。
|
||||
9. 验证结束后移除 include 并 reload Nginx;不要把该前缀入口当作正式公网 URL。
|
||||
|
||||
## dev shadow service 验收记录
|
||||
@@ -499,7 +499,7 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_CONNECT_TIMEOUT_MS` | `3000` | 连接上游的超时,必须大于 `0`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_DEFAULT_READ_TIMEOUT_SECONDS` | `60` | 没有 Nginx 显式长超时的代理路由读取超时,必须大于 `0`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_API_READ_TIMEOUT_SECONDS` | `3600` | 通用 `/api` 路由读取超时,对齐当前 Nginx `proxy_read_timeout 3600s`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_LONG_READ_TIMEOUT_SECONDS` | `3600` | 公开列表 / 详情和 SpacetimeDB subscribe 长连接读取超时。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_LONG_READ_TIMEOUT_SECONDS` | `3600` | SpacetimeDB subscribe 长连接读取超时。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_WRITE_TIMEOUT_SECONDS` | `3600` | 写上游请求头 / 请求体超时,对齐当前 Nginx `proxy_send_timeout 3600s` 口径。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR` | `false` | 是否用 `X-Forwarded-For` 首个 IP 作为接流保护 client key;公网直连 Pingora 时必须保持 `false`,direct preflight 会阻断公网监听误开启。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED` | `false` | 开启 `TRUST_X_FORWARDED_FOR` 时必须显式设为 `true`,表示前置代理会清洗 `X-Forwarded-For`。 |
|
||||
@@ -509,12 +509,6 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_MAX_CONCURRENT` | `64` | `/admin/api/*` 每 client 并发上限;`0` 表示不限制并发。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_RATE_PER_SECOND` | `30` | `/admin/api/*` 每 client token bucket 回填速率;`0` 表示不限制 RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_BURST` | `16` | `/admin/api/*` 每 client 额外 burst。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GALLERY_LIST_MAX_CONCURRENT` | `320` | 公开列表路由每 client 并发上限。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GALLERY_LIST_RATE_PER_SECOND` | `5000` | 公开列表路由每 client RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GALLERY_LIST_BURST` | `4096` | 公开列表路由每 client burst。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GALLERY_DETAIL_MAX_CONCURRENT` | `32` | 公开详情兼容路由每 client 并发上限。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GALLERY_DETAIL_RATE_PER_SECOND` | `300` | 公开详情兼容路由每 client RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GALLERY_DETAIL_BURST` | `32` | 公开详情兼容路由每 client burst。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_MAX_CONCURRENT` | `64` | 通用 `/api` 路由每 client 并发上限。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_RATE_PER_SECOND` | `300` | 通用 `/api` 路由每 client RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_BURST` | `64` | 通用 `/api` 路由每 client burst。 |
|
||||
@@ -536,13 +530,11 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
|
||||
| `/admin/assets/*` | 从 Web 根目录精确读取静态文件;带 Vite 指纹的文件默认长期缓存,其它文件默认 `no-cache`,并支持条件请求返回 `304` 与单段 `Range: bytes=` 返回 `206` / 越界返回 `416`。 |
|
||||
| `/admin/*` | 先读取静态文件或目录 index,失败回退 `/admin/index.html`,HTML 默认 `no-cache`,并支持条件请求返回 `304` 与单段 `Range: bytes=` 返回 `206` / 越界返回 `416`。 |
|
||||
| `/assets/*` | 从 Web 根目录精确读取静态文件;带 Vite 指纹的文件默认长期缓存,其它文件默认 `no-cache`,并支持条件请求返回 `304` 与单段 `Range: bytes=` 返回 `206` / 越界返回 `416`。 |
|
||||
| `/api/runtime/puzzle/gallery`、`/api/runtime/custom-world-gallery` | 转发到 `api-server`。 |
|
||||
| `/api/runtime/puzzle/gallery/{id}`、`/api/runtime/custom-world-gallery/{profile}/{owner}` | 转发到 `api-server`。 |
|
||||
| `/api`、`/api/*` | 转发到 `api-server`,按配置执行 `Content-Length` 与流式 body 累计上限检查。 |
|
||||
| `/v1/database/{db}/subscribe`、`/v1/identity*` | 转发到 SpacetimeDB,保留 WebSocket Upgrade 头。 |
|
||||
| `/__genarrative_pingora/healthz` | 仅在携带 `X-Genarrative-Pingora-Probe` 且匹配配置 token 时返回 shadow JSON,否则 404。 |
|
||||
| `/v1/*`、`/generated-*`、`/healthz*`、`/readyz*` | 返回 404,保持生产公网不暴露口径。 |
|
||||
| 主站 SPA allowlist | 只对当前前端完整路由及兼容恢复路径 `/creation/rpg/agent` 失败回退 `/index.html`;匹配大小写不敏感并允许一个尾部斜杠,HTML 默认 `no-cache`。 |
|
||||
| 主站 SPA allowlist | 只对 `/`、`/creation`、`/project`、`/profile` 与 `/editor/canvas` 失败回退 `/index.html`;匹配大小写不敏感并允许一个尾部斜杠,HTML 默认 `no-cache`。 |
|
||||
| 其它 Web 路径 | 只读取真实静态文件或目录 index,缺失时返回真实 404;`/creation/not-exist`、`/runtime/not-exist`、`/puzzle/not-exist` 不进入 SPA fallback。 |
|
||||
|
||||
维护模式下,公网 API-like 路由返回 JSON `503`;公网 Web 静态路由先读取 `GENARRATIVE_PINGORA_GATEWAY_MAINTENANCE_PAGE_FILE` 指向的 release 外运行态公告,缺失时回退 `GENARRATIVE_PINGORA_GATEWAY_WEB_ROOT/maintenance.html`,两者都不存在时返回纯文本 `503`。版本化默认页不得包含日期或具体时段,临时公告由 `maintenance-on.sh --page-file` 安装并在 `maintenance-off.sh` 时清理。IPv4 loopback / RFC1918 / link-local 和 IPv6 loopback / ULA / link-local 来源绕过整站维护闸,主站页面与静态资源、普通 API、后台页面与后台 API、SpacetimeDB 路由均按非维护状态继续处理;应用层登录、管理员鉴权和其它业务鉴权保持不变。Pingora 直连按 TCP peer 判定来源;仅当 peer 是 loopback 的同机 Nginx 时才接受 Nginx 强制覆盖的 `X-Real-IP`,绝不使用客户端可伪造的 `X-Forwarded-For` 做维护放行。该放行只绕过网关维护响应;若 `pause-after-stdb` 已停止 api-server,内网普通 API 和后台 API 仍不可用。
|
||||
@@ -556,7 +548,7 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
|
||||
2. 涉及 Nginx 模板、Pingora 路由、限流分组或路由文档时,同步更新 `deploy/pingora/nginx-route-parity.matrix.json`,并运行 `npm run check:pingora-route-parity` 与 `cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml matches_nginx_route_parity_matrix`。
|
||||
3. 容器内使用同一份 Web 产物、同一组真实上游地址跑 Pingora smoke,并继续对照 `deploy/nginx/genarrative.conf` 扩展真实上游路由 parity 自动测试。
|
||||
4. 使用 `deploy/nginx/snippets/genarrative-pingora-canary.conf` 做 Nginx 前缀 canary;启用前先跑 `npm run check:nginx-pingora-canary` 和 `npm run check:pingora-canary-docker`,有 Nginx 或 Docker 的目标环境分别强制跑 `node scripts/check-nginx-pingora-canary.mjs --require-nginx` 与 `node scripts/check-pingora-canary-docker.mjs --require-docker --pull`,其中 Docker handoff 会自动对账临时 Nginx 与 Pingora access log。启用后跑 `npm run check:pingora-canary-live`,再用 current release 随包 access log parity 脚本对账目标机 Nginx 与 Pingora access log。canary live 的 base URL、prefix、Host、额外 path 和 timeout 不能包含换行或 NUL;canary live timeout 和 access log `since-lines` 必须是正整数,非法值直接失败。
|
||||
5. 前缀 canary 通过后,再使用 current release 随包 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-enable.sh --apply --probe-token <token> --host <域名> --base-url http://127.0.0.1:18083` 启用真实路径 canary;它会把 `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 渲染成独立本机 `server`,写入 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`,不能 include 到生产 `443` server 内。该片段使用 `access_log ... genarrative_upstream`,文件名必须保证晚于定义 `log_format genarrative_upstream` 的主站配置加载;否则 `nginx -t` 会报 `unknown log format "genarrative_upstream"`。启用脚本会先执行 `nginx -t`、reload Nginx,再默认运行 realpath live smoke,任一阶段失败都会恢复写入前配置。关闭时执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-disable.sh --apply`,脚本会在 `nginx -t` 或 reload 失败时恢复删除前配置。启用后跑 `node -- /opt/genarrative/current/scripts/check-pingora-canary-live.mjs --realpath --base-url http://127.0.0.1:18083 --host <域名>`,再用 current release 随包 access log parity 脚本传 `--realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log` 对账 `/api/creation-entry/config`、`/v1/identity` 和 `/assets/app.js` 等真实路径。
|
||||
5. 前缀 canary 通过后,再使用 current release 随包 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-enable.sh --apply --probe-token <token> --host <域名> --base-url http://127.0.0.1:18083` 启用真实路径 canary;它会把 `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 渲染成独立本机 `server`,写入 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`,不能 include 到生产 `443` server 内。该片段使用 `access_log ... genarrative_upstream`,文件名必须保证晚于定义 `log_format genarrative_upstream` 的主站配置加载;否则 `nginx -t` 会报 `unknown log format "genarrative_upstream"`。启用脚本会先执行 `nginx -t`、reload Nginx,再默认运行 realpath live smoke,任一阶段失败都会恢复写入前配置。关闭时执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-disable.sh --apply`,脚本会在 `nginx -t` 或 reload 失败时恢复删除前配置。启用后跑 `node -- /opt/genarrative/current/scripts/check-pingora-canary-live.mjs --realpath --base-url http://127.0.0.1:18083 --host <域名>`,再用 current release 随包 access log parity 脚本传 `--realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log` 对账 `/api/editor/showcase/resources`、`/v1/identity` 和 `/assets/app.js` 等真实路径。
|
||||
6. 目标机 canary include 后必须跑正式切换聚合门禁,并按现场已启用的 canary 入口选择参数:前缀 canary 已启用时,源码 checkout / CI / 构建环境执行 `node scripts/check-pingora-release-readiness.mjs --require-docker --pull-docker --require-nginx --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`,目标机 current release 执行 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;真实路径 canary 已启用时,追加或单独使用 `--require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`。如果现场只启用了真实路径 canary,不要同时传 `--require-live`;缺少 Host 会直接失败,避免 live canary 误测默认 vhost。live smoke 后还会按 `request_id` 对账 Nginx 与 Pingora access log,缺少同一请求的 Pingora 日志、状态码、方法或 path 漂移都会失败。
|
||||
7. 如需评估 Pingora 直连公网入口,必须显式配置 `TLS_LISTEN`、证书、私钥和 `HTTP_REDIRECT_LISTEN`;Certbot 证书先用随包 `scripts/deploy/pingora-tls-cert-sync.mjs` 同步到 `/etc/genarrative/pingora-tls/<域名>/`,不要直接 chmod Let’s Encrypt live/archive 原路径;同一 IP 上还有 Gitea 域名时,还必须配置 `GITEA_HOSTS` / `GITEA_UPSTREAM` 并确认 TLS 证书覆盖所有由 Pingora 直连接管的 Host。绑定 `80/443` 时还必须人工启用 `genarrative-pingora-gateway-direct-entry.conf` drop-in 授予 `CAP_NET_BIND_SERVICE`。随后用 `npm run check:pingora-gateway-smoke` 覆盖 TLS / HTTP/2 ALPN / redirect / WSS subscribe / Gitea Host 分流;目标机必须先跑 `npm run check:pingora-direct-preflight -- --env-file /etc/genarrative/pingora-gateway.env --require-live-env --systemd-cat --check-cert-readable --check-service-env-file --check-service-user-cert-readable --check-service-binary-executable --check-ports-free`,再跑 `npm run check:pingora-direct-live` 或 release readiness 的 `--require-direct`,且 `--require-direct` 必须带 direct HTTPS base URL、direct HTTP base URL、正式域名 Host/SNI、redirect Location host、Pingora access log 文件、health patrol env 文件、direct preflight env 文件、systemd drop-in 生效检查、service EnvironmentFile 一致性检查、当前用户和 systemd 服务用户证书可读检查、service 二进制可执行检查、显式 SpacetimeDB 数据库名,并会拒绝 `--skip-wss`。高端口 rehearsal 使用 `https://127.0.0.1:<高端口>` 打入但期望 HTTP redirect Location 指向正式域名默认 HTTPS 入口时,额外传 `--direct-redirect-base-url https://<域名>`;`--direct-redirect-host` 仍必须保留,用于 runbook Host 一致性约束。direct live 会用生成的 `request_id` 反查 Pingora access log;缺少对应日志、method 漂移、path 漂移或 status 漂移都算直连门禁失败。direct preflight 会拒绝开启网关保护但未确认共享保护层的 `GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT>1` 配置;`--env-file`、`--systemd-service`、服务用户和 env 中的 listen / cert / key 值都不能包含换行或 NUL,执行 `systemctl cat` 或 `sudo -u <serviceUser> test -r <file>` 前还会复核子命令参数,避免污染参数进入目标机预检命令;direct live 的 URL、Host、redirect base URL、probe token、额外 path、数据库名、access log 路径、timeout 和布尔 env 也不能包含换行或 NUL,且会在发起请求前失败;direct live timeout 必须是正整数,直连相关布尔 env 只接受 `true/false`、`1/0`、`yes/no`、`on/off` 或空值,非法值直接失败。证书申请与续期仍由 Certbot / 外部自动化承担,网关只读取现有文件。
|
||||
8. 正式直连 runbook 的启用前基础门禁和启用后 `--require-direct` 复核必须调用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only`。该脚本、`scripts/check-pingora-canary-live.mjs`、`scripts/ops/pingora-direct-rehearsal-status.mjs`、realpath canary 启停脚本和 `deploy/nginx/` 必须进入生产 API release、Jenkins API Build 归档、Jenkins API Deploy 复制清单和目标机 current release;缺失时部署应 fail-fast,切换窗口不能依赖源码 checkout 或 Jenkins workspace。
|
||||
|
||||
@@ -1426,16 +1426,16 @@ V1.41 为 V1.40 明确留下的成功响应交接窗口增加 `.agent/runtime/pr
|
||||
|
||||
### 确定性测试矩阵
|
||||
|
||||
| 范围 | 故障/恢复窗口 | 必须断言 | 定向用例 |
|
||||
| --- | --- | --- | --- |
|
||||
| sidecar 契约 | 首次写入、同内容重写、`.previous`、损坏/超限/路径或内容冲突 | 严格 schema、`0600` 原子交接、实际 requestId 稳定、tool call 拒绝、thinking/密钥/路径零落盘 | `provider_handoff_*` 单元用例 |
|
||||
| final-reply handoff-first | Provider 成功交接后、lifecycle 终态前停止 | 恢复零新网络请求,原 requestId `started -> completed`,唯一 assistant/completed/committed stream,终局零 retry/handoff/finalization | `provider_handoff_final_reply_restart_replays_success_without_network_request` |
|
||||
| compaction consumer | 前置压缩 handoff 已落盘、compaction sidecar 未提交,以及 sidecar 已提交但 handoff 未清理 | 压缩结果回读后清理,不重放 tool-plan/压缩,只发后续 final-reply | `provider_handoff_final_reply_compaction_restart_only_requests_final_reply` |
|
||||
| identity/retry 冲突 | Goal/steer/revision/request/config 漂移,或 retry identity/attempt/slot 与 handoff 不一致 | 漂移先闭合旧 lifecycle 再作废,审计零响应正文;retry 冲突进入 reconciliation 且零 Provider 请求 | `provider_handoff_identity_drift_closes_lifecycle_without_leaking_response` 及 `provider_handoff_` 冲突门禁 |
|
||||
| finalization schema/身份 | v4 slot 被篡改,或读取缺少 slot 的 v3 journal 与 v1-v4 lifecycle | v4 `responseRequestSlot` 进入 `finalizationId` 指纹;v3 按旧身份可读;Agent DB lifecycle 白名单接受 v1-v4 journal schema | `finalization_v4_binds_response_request_slot_into_identity`、`finalization_v3_without_response_request_slot_remains_readable`、`finalization_lifecycle_accepts_supported_v1_through_v4_journals` |
|
||||
| finalization 四 checkpoint | `Prepared / AssistantAppended / RuntimeCompleted / ResponseStreamCommitted` 任一点中断 | 原 messageId/finalizationId 幂等恢复,无 Provider/工具重放,最终 journal/handoff 清理 | `finalization_*` 与 `response_stream_committed_checkpoint_recovers_by_idempotent_cleanup` |
|
||||
| 回复流重建 | stream 缺失、停在 streaming、commit 写失败、已 committed 后清理中断 | 按 v4 固定 slot 从 journal 重建 ready,committed 写后回读,正文唯一,冲突失败关闭 | `response_stream_finalization_recovers_missing_stream_after_project_revision_drift`、`response_stream_finalization_repairs_streaming_after_commit_write_failure`、`response_stream_committed_checkpoint_recovers_by_idempotent_cleanup` |
|
||||
| Runner busy | primary、`.previous` 或损坏 handoff 存在时请求 idle shutdown | `idle=false`,不进入 draining;清理后才可 shutdown | `durable_provider_handoff_prevents_shutdown_even_when_corrupt` |
|
||||
| 范围 | 故障/恢复窗口 | 必须断言 | 定向用例 |
|
||||
| -------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| sidecar 契约 | 首次写入、同内容重写、`.previous`、损坏/超限/路径或内容冲突 | 严格 schema、`0600` 原子交接、实际 requestId 稳定、tool call 拒绝、thinking/密钥/路径零落盘 | `provider_handoff_*` 单元用例 |
|
||||
| final-reply handoff-first | Provider 成功交接后、lifecycle 终态前停止 | 恢复零新网络请求,原 requestId `started -> completed`,唯一 assistant/completed/committed stream,终局零 retry/handoff/finalization | `provider_handoff_final_reply_restart_replays_success_without_network_request` |
|
||||
| compaction consumer | 前置压缩 handoff 已落盘、compaction sidecar 未提交,以及 sidecar 已提交但 handoff 未清理 | 压缩结果回读后清理,不重放 tool-plan/压缩,只发后续 final-reply | `provider_handoff_final_reply_compaction_restart_only_requests_final_reply` |
|
||||
| identity/retry 冲突 | Goal/steer/revision/request/config 漂移,或 retry identity/attempt/slot 与 handoff 不一致 | 漂移先闭合旧 lifecycle 再作废,审计零响应正文;retry 冲突进入 reconciliation 且零 Provider 请求 | `provider_handoff_identity_drift_closes_lifecycle_without_leaking_response` 及 `provider_handoff_` 冲突门禁 |
|
||||
| finalization schema/身份 | v4 slot 被篡改,或读取缺少 slot 的 v3 journal 与 v1-v4 lifecycle | v4 `responseRequestSlot` 进入 `finalizationId` 指纹;v3 按旧身份可读;Agent DB lifecycle 白名单接受 v1-v4 journal schema | `finalization_v4_binds_response_request_slot_into_identity`、`finalization_v3_without_response_request_slot_remains_readable`、`finalization_lifecycle_accepts_supported_v1_through_v4_journals` |
|
||||
| finalization 四 checkpoint | `Prepared / AssistantAppended / RuntimeCompleted / ResponseStreamCommitted` 任一点中断 | 原 messageId/finalizationId 幂等恢复,无 Provider/工具重放,最终 journal/handoff 清理 | `finalization_*` 与 `response_stream_committed_checkpoint_recovers_by_idempotent_cleanup` |
|
||||
| 回复流重建 | stream 缺失、停在 streaming、commit 写失败、已 committed 后清理中断 | 按 v4 固定 slot 从 journal 重建 ready,committed 写后回读,正文唯一,冲突失败关闭 | `response_stream_finalization_recovers_missing_stream_after_project_revision_drift`、`response_stream_finalization_repairs_streaming_after_commit_write_failure`、`response_stream_committed_checkpoint_recovers_by_idempotent_cleanup` |
|
||||
| Runner busy | primary、`.previous` 或损坏 handoff 存在时请求 idle shutdown | `idle=false`,不进入 draining;清理后才可 shutdown | `durable_provider_handoff_prevents_shutdown_even_when_corrupt` |
|
||||
|
||||
2026-07-20 当前最终实现的最新验证证据为:`provider_retry_` 21/21、`response_stream_` 23/23、`finalization_resume_` 12/12;Tauri/Rust 串行全量共 989 tests,`985 passed / 4 ignored / 0 failed`。这些数字替代 V1.40 较早快照,后续当前结果统一使用本行口径。
|
||||
|
||||
@@ -1524,7 +1524,7 @@ V1.43 不放宽 V1.41 的文本型 `game-creator-provider-handoff.v1`,而是
|
||||
- `npm run test -- apps/ai-game-creator-shell/tests`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml`
|
||||
- `cargo check --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --target x86_64-pc-windows-gnu`
|
||||
- `cargo test -p platform-agent --manifest-path server-rs/Cargo.toml game_creation`
|
||||
- `cargo test -p platform-llm --manifest-path server-rs/Cargo.toml`
|
||||
- `cargo test -p shared-contracts --manifest-path server-rs/Cargo.toml game_creation_app`
|
||||
- `npm run ai-game-creator-shell:agent-run:smoke`
|
||||
- `npm run ai-game-creator-shell:agent-runtime:real-e2e -- --config-dir <AppData> --suite llm-runtime`
|
||||
|
||||
@@ -499,7 +499,7 @@ game-project/
|
||||
- 单窗口首页和项目组页可选择、打开、新建或显示当前输入的项目绝对路径;最近项目行也可显示目录,非法或相对路径不会调用系统文件管理器。
|
||||
- 普通用户侧的生成、上传、运行、自检、预览状态 / 启动 / 打开 / 停止、记忆写入和画板资产导入都必须先完成 `/project` 初始化;未初始化时只提示设置本地项目,不落到默认临时目录。
|
||||
- 终端可用 `npm run ai-game-creator-shell:llm-status` 检查 LLM 客户端配置是否就绪;桌面 App 主窗口“配置”面板可读写 Tauri 应用配置目录中的 `game-creator.config.json`,`/llm-status` / 生成入口读取同一份配置,CLI 开发入口无 AppHandle 时才回退读取仓库旁边的配置模板和 gitignored 本机覆盖文件;不请求上游、不显示 API Key,缺配置时以非零状态退出或在聊天里提示未就绪。
|
||||
- 终端可用 `npm run ai-game-creator-shell:check` 跑 v1 开发验收:壳 typecheck、`platform-agent` 编排测试、共享契约测试、Tauri Rust 测试和无密钥本地 provider 端到端 smoke。
|
||||
- 终端可用 `npm run ai-game-creator-shell:check` 跑 v1 开发验收:壳 typecheck、`platform-llm` 网关测试、共享契约测试、Tauri Rust 测试和无密钥本地 provider 端到端 smoke;已退役的 `platform-agent` 不再进入 workspace 或该门禁。
|
||||
- 终端可用 `npm run ai-game-creator-shell:agent-run -- /绝对项目路径 "游戏创作需求"` 跑一次真实 LLM 生成、落盘、`game.static_smoke` 和本地 HTTP 预览;发布 App 读取 Tauri 应用配置目录中的 `game-creator.config.json`,开发 CLI 无 AppHandle 时才读取仓库旁边的配置模板和 gitignored 本机覆盖文件,不把 API Key 写入仓库或项目文件。自动验证可加 `--no-wait`,例如 `npm run ai-game-creator-shell:agent-run -- --no-wait /tmp/genarrative-ai-game-test "像素风反弹弹幕厨房"`,生成预览 trace 后立即停止本地预览,避免终端卡在回车等待。默认 API kind 为 `openai_responses`;旧 Chat Completions 兼容网关设置 `llm.apiKind` 为 `openai_chat`,Anthropic Messages 网关设置 `llm.apiKind` 为 `anthropic`。真实 OpenAI-compatible 网关建议设置 `llm.stream` 为 `true` 跑 Planner 和 Generator,避免长请求非流式空闲断连。
|
||||
- 终端可用 `npm run ai-game-creator-shell:agent-run:smoke` 跑一次无密钥本地端到端 smoke:脚本启动本机 OpenAI-compatible SSE 流式测试 provider,预置一个本地上传图片和一个本地上传音频,复用真实 `--agent-run`、Planner / Orchestrator / 角色 agent / Generator / Evaluator loop、本地落盘、`game.static_smoke` 和本地 HTTP 预览,并断言每次 provider 请求都使用 `stream: true`、Planner 与 Generator 分别命中自己的 `agentLlm` provider 配置、provider prompt 收到图片与音频资产上下文以及最近对话上下文、生成 HTML 引用这些资产、预览服务能用 `GET` 读取 `/assets/...`、用 `HEAD` 返回真实资源长度和对应 MIME、headless Chrome 打开预览后至少执行一帧游戏 JS,且通过确定性亮色探针采样证明 canvas 不是空白画布、`.agent/run.latest.json` 的 step group 覆盖 design / balance / art / audio / code / publishing 六组、第二轮会重跑 Evaluator 命中任务及其下游影响任务,未受影响角色 carry-over;随后脚本自动给 CLI 发送回车停止预览。该脚本只用于开发验证,不进入产品生成路径。
|
||||
- `npm run ai-game-creator-shell:dev` 的 Tauri `devUrl` 固定为 `http://127.0.0.1:3080/`,Vite 必须 `strictPort` 对齐;`beforeDevCommand` 先复用已经跑在 3080 且页面标题为 `AI 游戏创作` 的本 app Vite server,否则才启动新的 Vite,若端口被其它服务占用则直接失败并提示释放端口。
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# 旧创作模板业务退役方案
|
||||
|
||||
更新时间:`2026-07-21`
|
||||
|
||||
## 目标
|
||||
|
||||
退役整个旧创作模板体系及其专属运行态,同时保留全部相关历史持久化表、迁移白名单和必要的兼容读取定义。历史数据不删除,持久化表及字段不删除、不改名、不重排、不改变类型。
|
||||
|
||||
本次执行口径是“数据壳保留,业务实现退役”:历史表继续以最小 schema 代码随 `spacetime-module` 编译和发布;旧模板选择、生成、发布、公开业务详情、作品广场、排行榜和专属运行态不再进入正式编译链或运行路由。基于图片编辑器项目与公开素材的新版 `/creation` 创作工具主页及平台公共侧边栏继续作为现役能力保留。
|
||||
|
||||
## 范围
|
||||
|
||||
### 退役
|
||||
|
||||
- RPG / 自定义世界、拼图、拼消消、大鱼吃小鱼、敲木鱼、方洞挑战、视觉小说、汪汪声浪、寓教于乐、Creative Agent、Match3D、跳一跳和儿童动作 Demo 的前端页面、路由、工作台、结果页、运行态、service、测试及专属素材生成入口。
|
||||
- `api-server` 中上述业务的 router、handler、service、生成与发布编排、公开详情、专属 runtime 和 worker 启动点。
|
||||
- `spacetime-client` 的模板业务 facade、mapper、reducer/procedure 调用和业务 DTO 依赖。
|
||||
- `spacetime-module` 的模板 reducer、procedure、业务 view、初始化逻辑和领域规则调用。
|
||||
- 所有纯模板 crate、RPG 专属运行态 crate 及旧 Creative Agent 的 `platform-agent` crate 的 workspace/default 目标和在运依赖边。
|
||||
- 后台及主站中只服务于旧创作入口的配置、灰度、展示、搜索、启动、追踪和运维门禁。
|
||||
|
||||
### 保留
|
||||
|
||||
- 全部旧模板历史持久化表及其原有字段顺序、字段类型、默认值、索引和可见性。
|
||||
- `migration.rs` 中相关表的迁移白名单、表名兼容和字段目录。
|
||||
- 为历史审计、迁移、资产归属核对所必需的最小只读表定义;不得借兼容读取重新暴露旧创作、发布、公开详情或运行接口。
|
||||
- 编辑器、项目、账号、钱包、资产、HostBridge、运维和安全等平台公共能力。
|
||||
- 通用 `feature_gate_config`、`GET/PUT /admin/api/feature-gates` 与后台 `#gray-release` 控制页。灰度页只读取通用 gate,不再请求旧 `/admin/api/creation-entry/config`,固定目标只登记现役功能;不得恢复 `creation-entry:*` 动态目标。
|
||||
- 新版 `/creation` 创作工具主页、`/project` 项目入口、稳定的 `/profile` 个人页路由、`creation-home` 展示组件与现役静态资产。桌面端保留“创作 / 项目 / 我的”公共侧边栏,移动端保留同样三项的底部 dock;“我的”保留头像 / 昵称编辑、陶泥号复制、钱包与账单、统计、充值、兑换码、玩家社区、反馈、通用设置、开发者 API Key 和法律信息,不恢复旧模板入口、旧作品架或生成队列。
|
||||
- `runtime_setting` 是账号级公共设置事实,不属于旧模板运行态。原表结构和数据不变,继续由鉴权后的 `GET/PUT /api/runtime/settings`、`get_runtime_setting_or_default` 与 `upsert_runtime_setting_and_return` procedure 支撑音乐音量和平台主题读写。
|
||||
- 旧页面、测试、素材、handler、service、worker、生成 bindings 和纯业务 crate 的源码目录;它们仅用于历史追溯,不属于任何正式入口或编译目标。
|
||||
|
||||
## 编译边界
|
||||
|
||||
### 前端
|
||||
|
||||
- 主路由、Vite 入口和运行时动态 import 不得引用旧业务目录。
|
||||
- 正式应用入口使用 `active-main.tsx`、`ActiveApp.tsx`、`routing/activeAppRoutes.tsx`、`routing/activeAppPageRoutes.ts`、`services/activeAppTitle.ts`、`platform-entry/PlatformEntryActiveFlowShell.tsx`、`platformEntryActiveTypes.ts` 和 `creation-home/`;原同名非 active 文件保持历史源码原貌并退出 Vite、TypeScript、ESLint 和 Vitest。
|
||||
- 旧业务源码与素材保留在仓库中;Vite 不得再引用入口或动态 import,TypeScript、ESLint、Vitest 必须明确排除旧目录和专属测试。退役源码只用于历史追溯,不允许从在运代码重新导入。
|
||||
- 平台公共 profile 请求与展示模型必须位于 `services/platform-entry/` 和现役 `platform-entry` 文件,不得因为沿用账号、钱包或设置能力而继续 import `services/rpg-entry`、`services/rpg-runtime` 或 `components/rpg-entry`。Vite 对退役模块实行实际 module graph 门禁,命中即中止 dev/build;ESLint restricted imports 作为更早的源码反馈。
|
||||
- 原 `main.tsx`、`App.tsx`、旧路由、旧标题映射、`PlatformEntryFlowShellImpl.tsx` 与旧入口类型保持原样;正式链路由 `active-main.tsx`、`ActiveApp.tsx`、`activeApp*`、`PlatformEntryActiveFlowShell.tsx` 和 `platformEntryActiveTypes.ts` 承载,只包含新版创作主页、项目、编辑器、账号、设置与钱包公共能力。`retired/legacy-creation-templates/frontend/original/` 另保留逐文件原样快照。
|
||||
- 退役 CSS 的源码过滤必须在 Tailwind/Vite 转换前执行,产物过滤留在 `generateBundle`;禁止在 `post` transform 中把 Vite 已生成的 JavaScript 样式模块重新交给 PostCSS 解析。
|
||||
- 顶层退役 module 也必须受编译门禁约束:`src/uiAssets.ts`、`src/types.ts`、`src/types/**`、`src/services/runtimeAudioFeedback.ts`、`src/services/publicWorkCode.ts`、`creationEntryConfigService`、`creationUrlState`、`customWorld*`、`runtimeGuestAuth`、`runtimeRequest`、`input-devices/**`、`useMocapInput`、`wechatMiniProgramSubscribe`、`useCombatFlow` 和 `useStoryOptions` 不得进入 Vite module graph、TypeScript、ESLint 或 Vitest,现役公共品牌资产应从独立公共定义导入。
|
||||
- `src/games/**`、`src/data/**`、`src/prompts/**`、旧 `App.tsx` / `main.tsx` / `RpgRuntimeApp.tsx` / `*PlaygroundApp.tsx`、旧 `appRoutes` / `appPageRoutes` 和 `services/ai.ts` 同属退役源码边界;Vite 必须在 pre-transform 阶段拒绝直接请求,不能只依赖现役入口未 import 或构建 tree-shaking。
|
||||
- `src/components`、`src/hooks`、`src/persistence`、`src/routing` 和 `src/services` 根级文件是新旧混合区,Vite 与 ESLint 必须使用显式现役白名单。当前只放行正式入口实际依赖的根级公共模块;新增根级公共模块时必须同步登记,未登记文件按退役源码处理。各现役子目录继续按独立目录边界放行。
|
||||
- 退役静态资产 `/audio/**`、`/chat.png` 和 `/fusion-pixel.ttf` 不得由 Vite dev server 提供,也不得进入生产产物;旧 pixel / story-tab / 玩法 CSS 仅能在历史源码中存在。
|
||||
- 所有同源 `/generated-*` 裸读路径在 Vite、Nginx、Pingora 和 `api-server` 均返回空 `404`;历史对象 key 只允许作为 `legacyPublicPath` 进入 `/api/assets/read-url` 等现役签名读取链,不恢复旧生成资产代理。
|
||||
- `/creation`、`/project` 和 `/profile` 是现役稳定路由,刷新及浏览器前进 / 后退必须保持当前页签。生产网关对旧子路径返回 404,客户端若收到未知旧页面路径则回落当前平台公共首页;Vite dev 对旧 `/api/creation*` 与 `/api/public-works*` 直接返回 404,不能回落为 SPA HTML。小程序不再注册旧生成结果订阅授权页。
|
||||
|
||||
### Rust
|
||||
|
||||
- `api-server` 不声明模板模块,不挂模板路由,不保留模板 worker 启动点。
|
||||
- 现役 external generation worker 只领取 `source_module = editor-canvas` 的任务,历史旧玩法 pending / running 行不得被领取或改写。
|
||||
- `spacetime-client` 不保留模板业务 facade 和 mutation 调用;历史表生成绑定只允许服务必要兼容读取。
|
||||
- `spacetime-module` 对旧模板只编译历史表结构,不导出旧 reducer、procedure、业务 view 或领域规则。
|
||||
- 旧 `public_work_asset_read_grant` view 与十类旧作品授权计算退出 module;匿名资产读取只保留现役 editor showcase 授权。
|
||||
- `spacetime-module` 与 `spacetime-client` 的 Cargo `lib.path` 固定指向各自的 `src/active.rs`;原 `src/lib.rs` 及旧业务源码继续原位保留,但不再作为 crate 根参与编译。
|
||||
- 历史表最小定义集中在 `spacetime-module/src/legacy_schema/` 与 `spacetime-module/src/runtime/legacy_schema/`,混合 profile 表的在运数据壳位于 `spacetime-module/src/runtime/active/profile.rs`;这些目录只允许 schema 和必要兼容读取定义。
|
||||
- SpacetimeDB schema guard 比较当前工作树与基线提交时,两侧都必须分别读取各自 `Cargo.toml` 的 `lib.path`,再沿 `mod` / `#[path]` 只扫描该快照 crate root 可达的 schema;不得递归扫描整个 `src/`,否则原位保留的旧源码会与现役历史数据壳产生假 accessor 重复。
|
||||
- `module-runtime` 仍是账号、钱包、公共设置、追踪和 feature gate 的现役领域 crate;其混合源码中的 `CreationEntry*`、旧公开作品、旧存档 / 浏览历史 / 游玩统计 DTO、command、mapper 和规则必须以编译条件退出,且不再依赖只为旧创作契约存在的 `shared-contracts`。历史 schema 只继续编译 `RuntimeBrowseHistoryThemeMode` 六个变体和完整保序的 `RuntimeProfileWalletLedgerSourceType` 等持久化 ABI,不保留围绕这些类型的旧业务实现。
|
||||
- 纯模板 crate 和专属运行态 crate 不属于 workspace members、default members 或任何在运 crate 的依赖图;源码目录保持原样。
|
||||
- `platform-agent` 及其专属 `langchainrust` 依赖同样退出 workspace 与 `api-server` 依赖图;现役编辑器 Agent 仅需的模型常量收口到 `platform-llm`,不再通过旧拼图 Phase 1 / Creative Agent 执行器 crate 复用。
|
||||
- `platform-auth` 不再编译 runtime guest token;`platform-wechat` 不再编译旧生成结果订阅服务,只保留现役认证和支付协议。
|
||||
|
||||
## 验收
|
||||
|
||||
- 旧 URL 不再命中旧页面或后端路由。
|
||||
- `/creation`、`/project` 与 `/profile` 在桌面端显示“创作 / 项目 / 我的”公共侧边栏,在 `390x844` 等移动视口显示同样三项的底部 dock;点击、刷新及浏览器前进 / 后退均保持路由与选中态一致。新创作主页只调用编辑器项目和公开编辑器素材接口;顶栏保持现役搜索、公共泥点入口与账号胶囊,不重新拼装平行账号按钮组。
|
||||
- “我的”桌面布局按原平台公共资料页全宽展示四个常用入口、两行设置和法律栏;头像、昵称、复制、充值、兑换码、社区、反馈、API Key 等入口可用,但不发起旧模板、旧公开作品或旧运行态请求。
|
||||
- 鉴权访问 `GET/PUT /api/runtime/settings` 不得返回 404,读写必须经 `spacetime-client` 调用现役 settings procedure;未鉴权请求返回 401,不恢复任何旧运行态设置路由。
|
||||
- `tsc --listFilesOnly` 与 Vite 干净加载均不得出现旧业务目录、上述顶层退役 module 或小程序旧订阅授权实现。
|
||||
- 直接请求代表性的旧 `games` / `data` / `prompts` / 顶层 App 模块必须被 Vite module graph 门禁拒绝;现役 `creation-home`、项目、profile 与 editor 模块仍正常转换。
|
||||
- 根级旧组件、hook、persistence、routing 和 service 必须同时被 Vite 拒绝并被 ESLint 忽略;现役根级图片解析、设置、路由和 API client 文件必须继续参与两套门禁。
|
||||
- Vite 构建产物和依赖图不包含旧前端业务目录、Fusion Pixel / pixel 业务样式或旧 runtime 声音签名;产物中不存在 `dist/audio/**`、`dist/chat.png` 或 `dist/fusion-pixel.ttf`。
|
||||
- 旧生成资产前缀和现役 editor 对象前缀的同源裸读都必须返回空 `404`,不能返回 `index.html` 形成 soft 404;编辑器真实资产读取继续走签名 URL。
|
||||
- `cargo tree` 中不存在纯模板 crate、专属运行态 crate、`platform-agent` 或 `langchainrust`。
|
||||
- `npm run check:module-runtime-artifact` 对实际 `module_runtime.rlib` 的 object 成员执行负向扫描:旧创作、存档、浏览与游玩符号和字面量必须为零,同时 `RuntimeBrowseHistoryThemeMode`、`RuntimeProfileWalletLedgerSourceType` 与 `RuntimeSettingSnapshot` 等兼容 ABI 必须仍存在。
|
||||
- `spacetime-module` 编译结果仍包含全部历史表,但不包含任何旧模板 reducer、procedure 和业务 view。
|
||||
- `platform_auth.rlib` 不包含 runtime guest token 符号,`platform_wechat.rlib` 不包含订阅消息发送符号;历史旧队列行不满足现役 worker claim 条件。
|
||||
- `npm run check:spacetime-schema`、定向 Rust 检查、前端类型检查、`npm run check:encoding`、`git diff --check` 通过。
|
||||
|
||||
## 历史记录兼容
|
||||
|
||||
- 历史数据库行、作品号、`worldType` 和资产对象前缀可以继续被审计或迁移工具识别,但不再形成面向用户的列表、详情、创作或运行入口。
|
||||
- 旧公开作品号、创作 URL 和统一创作规格不再作为前端可启动契约;未知或旧 URL 统一回落平台公共首页。
|
||||
- 历史表名、资产对象前缀和后台数据库表目录可继续出现旧域名,它们属于数据审计与资产读取边界,不代表旧业务仍在运行。
|
||||
- 原本与历史表混在同一文件的 reducer/procedure 实现按模块保存在 `retired/legacy-creation-templates/rust/`,不属于任何 Cargo workspace/module;从全局样式表移出的专属 CSS 保存在同目录的 `frontend/` 下且不被 Vite 导入。
|
||||
Reference in New Issue
Block a user