Merge remote-tracking branch 'origin/master' into hermes/hermes-1e775b03
Some checks failed
CI / verify (pull_request) Has been cancelled
Some checks failed
CI / verify (pull_request) Has been cancelled
# Conflicts: # docs/technical/README.md # src/components/custom-world-home/CustomWorldCreationHub.tsx # src/components/custom-world-home/creationWorkShelf.ts # src/components/platform-entry/PlatformEntryFlowShellImpl.tsx # src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
This commit is contained in:
310
.hermes/plans/2026-05-11_195214-k6-works-list-load-test-plan.md
Normal file
310
.hermes/plans/2026-05-11_195214-k6-works-list-load-test-plan.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# K6 作品列表压测计划(使用 spacetime-migration-7.json 作为数据源)
|
||||
|
||||
## 目标
|
||||
|
||||
使用 K6 对 Genarrative 的“作品列表”相关接口进行压测,并将用户提供的 `spacetime-migration-7.json` 作为压测数据源;数据处理时**只导入作品列表相关数据**,不导入用户、会话、钱包、埋点、运行存档等非作品表,避免把敏感或无关数据带入压测环境。
|
||||
|
||||
## 当前上下文
|
||||
|
||||
- 工作区:`/c/proj/Genarrative`
|
||||
- 原始迁移文件:`C:\Users\DSK\AppData\Local\hermes\cache\documents\doc_150e84029b2d_spacetime-migration-7.json`
|
||||
- 已确认原始迁移文件结构:
|
||||
- `schema_version = 1`
|
||||
- `tables = 53`
|
||||
- 作品相关表中当前有数据的重点表:
|
||||
- `puzzle_work_profile`:80 行
|
||||
- `custom_world_profile`:1 行
|
||||
- `match3d_work_profile`:0 行
|
||||
- `big_fish_*`:当前样本中相关表为 0 行
|
||||
- 原始文件还包含 `user_account`、`auth_identity`、`refresh_session`、`profile_wallet_ledger`、`asset_object`、运行记录等数据,压测导入时必须过滤。
|
||||
- 当前仓库未发现现成 K6 脚本或 `k6` 相关文件,需要新增压测脚本与数据提取脚本。
|
||||
- `package.json` 当前有 `dev/dev:rust/test/check` 等脚本,未发现 K6 npm script。
|
||||
|
||||
## 范围约束
|
||||
|
||||
### 本次只导入/使用
|
||||
|
||||
1. 作品列表表:
|
||||
- `puzzle_work_profile`
|
||||
- `custom_world_profile`
|
||||
- 后续若接口覆盖其他玩法,可扩展:
|
||||
- `match3d_work_profile`
|
||||
- `square_hole_work_profile`(以实际 SpacetimeDB 表名为准)
|
||||
- `big_fish_work_profile`(以实际 SpacetimeDB 表名为准)
|
||||
- `visual_novel_work_profile`(以实际 SpacetimeDB 表名为准)
|
||||
2. 为作品列表卡片展示所需的最小字段:
|
||||
- 稳定 ID:`profile_id`、`work_id` 或 `public_work_code`
|
||||
- 标题:`work_title` / `level_name` / `world_name`
|
||||
- 描述:`work_description` / `summary` / `summary_text` / `subtitle`
|
||||
- 作者:`owner_user_id`、`author_display_name`、`author_public_user_code`
|
||||
- 封面:`cover_image_src`、`cover_asset_id`(如果接口只返回 asset id,则压测阶段不额外导入二进制 asset)
|
||||
- 状态与计数:`publication_status`、`published_at`、`play_count`、`like_count`、`remix_count`
|
||||
- 作品内容摘要:`levels_json`、`profile_payload_json`、`theme_tags_json` 等列表渲染或进入作品详情可能需要的 JSON 字段
|
||||
|
||||
### 本次不导入/不使用
|
||||
|
||||
- 认证与账号:`user_account`、`auth_identity`、`refresh_session`、`auth_store_snapshot`
|
||||
- 用户资产与钱包:`profile_wallet_ledger`、`profile_dashboard_state`、`profile_redeem_*`、`profile_invite_*`
|
||||
- 游玩历史/存档/运行态:`profile_played_world`、`public_work_play_daily_stat`、`puzzle_runtime_run`、`profile_save_archive`、`runtime_snapshot` 等
|
||||
- AI 任务过程:`ai_task`、`ai_task_stage`、`ai_text_chunk`
|
||||
- asset 二进制与绑定:`asset_object`、`asset_entity_binding`,除非后续确认作品列表接口强依赖它们;即便需要,也只导入作品列表封面所需的最小 metadata,不导入原始大对象。
|
||||
|
||||
## 推荐目录与文件
|
||||
|
||||
建议新增:
|
||||
|
||||
```text
|
||||
.hermes/plans/2026-05-11_195214-k6-works-list-load-test-plan.md # 本计划
|
||||
scripts/loadtest/extract-works-list-data.mjs # 从迁移文件提取作品列表数据
|
||||
scripts/loadtest/k6-works-list.js # K6 压测脚本
|
||||
scripts/loadtest/data/works-list.sample.json # 过滤后的样例数据(不要提交敏感原始迁移全量)
|
||||
scripts/loadtest/README.md # 执行说明与指标阈值
|
||||
```
|
||||
|
||||
可选新增 npm scripts:
|
||||
|
||||
```json
|
||||
{
|
||||
"loadtest:extract-works": "node scripts/loadtest/extract-works-list-data.mjs",
|
||||
"loadtest:k6:works": "k6 run scripts/loadtest/k6-works-list.js"
|
||||
}
|
||||
```
|
||||
|
||||
## 数据提取方案
|
||||
|
||||
### 输入
|
||||
|
||||
默认读取:
|
||||
|
||||
```bash
|
||||
node scripts/loadtest/extract-works-list-data.mjs \
|
||||
--input "C:/Users/DSK/AppData/Local/hermes/cache/documents/doc_150e84029b2d_spacetime-migration-7.json" \
|
||||
--output scripts/loadtest/data/works-list.local.json
|
||||
```
|
||||
|
||||
### 输出结构
|
||||
|
||||
建议输出为 K6 直接可读的 JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"source": "spacetime-migration-7.json",
|
||||
"generatedAt": "<iso datetime>",
|
||||
"tables": {
|
||||
"puzzle_work_profile": [
|
||||
{
|
||||
"profile_id": "...",
|
||||
"work_id": "...",
|
||||
"owner_user_id": "...",
|
||||
"work_title": "...",
|
||||
"work_description": "...",
|
||||
"publication_status": "Published",
|
||||
"published_at": { "__timestamp_micros_since_unix_epoch__": 0 },
|
||||
"play_count": 0,
|
||||
"like_count": 0,
|
||||
"levels_json": "..."
|
||||
}
|
||||
],
|
||||
"custom_world_profile": []
|
||||
},
|
||||
"workIds": {
|
||||
"puzzle": ["<profile_id>"],
|
||||
"customWorld": ["<profile_id>"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 过滤原则
|
||||
|
||||
1. 按 `tables[].name` 白名单过滤,只保留作品 profile 表。
|
||||
2. 对每个 row 再按字段白名单过滤,避免误带账号、手机号、token、钱包流水等字段。
|
||||
3. 对特别大的字段进行处理:
|
||||
- `cover_image_src` 如果是 `data:image/...base64`,默认替换为占位符或截断,避免压测数据文件过大。
|
||||
- `levels_json`、`profile_payload_json` 保留原文,但可以记录大小;如果过大,再提供 `--compact` 选项只保留摘要。
|
||||
4. 输出 `.local.json` 默认加入 `.gitignore`;如果要提交样例数据,只提交脱敏/裁剪后的 `works-list.sample.json`。
|
||||
|
||||
## K6 压测接口矩阵
|
||||
|
||||
需要先确认本地 api-server 实际端口。默认以 `http://127.0.0.1:8787` 为例,实际运行时通过环境变量覆盖:
|
||||
|
||||
```bash
|
||||
BASE_URL=http://127.0.0.1:<actual-api-port> k6 run scripts/loadtest/k6-works-list.js
|
||||
```
|
||||
|
||||
初版建议覆盖以下“作品列表”读接口,具体路径以仓库服务端路由为准,实施时需要通过搜索 api-server 路由确认:
|
||||
|
||||
| 场景 | 目的 | 候选路径 |
|
||||
| --- | --- | --- |
|
||||
| 拼图作品列表 | 作品列表主场景之一,当前数据量最多 | `/api/creation/puzzle/works` 或实际 puzzle works list route |
|
||||
| RPG/自定义世界作品列表 | 使用 `custom_world_profile` 数据 | `/api/creation/custom-world/works` 或实际 custom world works route |
|
||||
| 作品详情/启动前读取 | 模拟用户从列表点进作品 | `/api/creation/*/works/:profileId` 或 `/api/runtime/*/works/:profileId` |
|
||||
| 公开作品库 | 如果首页/发现页依赖 | `/api/runtime/*/works` 或 gallery/list route |
|
||||
|
||||
> 注意:不要凭空固定 endpoint。实施阶段先用 `search_files` / 路由源码确认真实路径,再写入 K6 脚本。
|
||||
|
||||
## K6 场景设计
|
||||
|
||||
### 阶段 1:基线 smoke
|
||||
|
||||
目的:确认脚本、数据和目标服务可用。
|
||||
|
||||
```js
|
||||
export const options = {
|
||||
scenarios: {
|
||||
smoke: {
|
||||
executor: 'constant-vus',
|
||||
vus: 1,
|
||||
duration: '30s'
|
||||
}
|
||||
},
|
||||
thresholds: {
|
||||
http_req_failed: ['rate<0.01'],
|
||||
http_req_duration: ['p(95)<800']
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 阶段 2:常规读压
|
||||
|
||||
目的:模拟日常列表浏览。
|
||||
|
||||
- `constant-vus`: 10/25/50 三档
|
||||
- 每个 VU 随机选择作品类型和列表分页参数
|
||||
- `sleep(0.5~2s)` 模拟用户停留
|
||||
- 阈值建议:
|
||||
- `http_req_failed < 1%`
|
||||
- `p95 < 800ms`
|
||||
- `p99 < 1500ms`
|
||||
|
||||
### 阶段 3:峰值/突刺
|
||||
|
||||
目的:模拟首页入口或活动导致的作品列表突增。
|
||||
|
||||
- `ramping-arrival-rate`
|
||||
- 从 5 RPS 增长到 100 RPS,维持 2~5 分钟,再降回
|
||||
- 单独输出 `checks`:列表接口状态码、响应 JSON shape、items 数量
|
||||
|
||||
### 阶段 4:容量探索
|
||||
|
||||
目的:找瓶颈,不作为每次回归必跑。
|
||||
|
||||
- 每轮提升 RPS 或 VU
|
||||
- 观察:api-server CPU/内存、SpacetimeDB 日志、错误率、p95/p99
|
||||
- 一旦 `http_req_failed >= 5%` 或 p95 持续超过 2s,停止继续加压并记录容量点。
|
||||
|
||||
## K6 脚本设计要点
|
||||
|
||||
1. 使用 `SharedArray` 加载 `works-list.local.json`,避免每个 VU 重复解析大 JSON。
|
||||
2. 基于数据源里的 `profile_id` / `work_id` 随机抽样,保证请求覆盖真实作品 ID。
|
||||
3. 对列表接口添加分页/排序 query,例如:
|
||||
- `?limit=20&offset=0`
|
||||
- `?pageSize=20&cursor=...`(以真实 API 为准)
|
||||
4. 使用 `check()` 验证:
|
||||
- HTTP 200
|
||||
- 响应体是 JSON
|
||||
- `items` 或 `works` 是数组
|
||||
- 列表项包含 `profileId/profile_id`、标题字段、状态字段
|
||||
5. 使用 `Trend` / `Rate` 细分指标:
|
||||
- `works_list_duration`
|
||||
- `works_detail_duration`
|
||||
- `works_list_shape_error_rate`
|
||||
6. 支持环境变量:
|
||||
|
||||
```bash
|
||||
BASE_URL=http://127.0.0.1:8787 \
|
||||
WORKS_DATA=scripts/loadtest/data/works-list.local.json \
|
||||
SCENARIO=baseline \
|
||||
k6 run scripts/loadtest/k6-works-list.js
|
||||
```
|
||||
|
||||
## 实施步骤
|
||||
|
||||
1. **确认路由**
|
||||
- 搜索 api-server / BFF 的作品列表路由。
|
||||
- 明确各玩法对应 endpoint、鉴权要求、分页参数、返回字段。
|
||||
2. **实现数据提取脚本**
|
||||
- 新增 `scripts/loadtest/extract-works-list-data.mjs`。
|
||||
- 只按表白名单读取作品列表 profile 表。
|
||||
- 对字段做白名单与脱敏/截断。
|
||||
- 输出 `works-list.local.json`。
|
||||
3. **生成本地压测数据**
|
||||
- 用用户提供的迁移文件生成 `scripts/loadtest/data/works-list.local.json`。
|
||||
- 验证输出只包含作品表和作品字段。
|
||||
4. **实现 K6 脚本**
|
||||
- 新增 `scripts/loadtest/k6-works-list.js`。
|
||||
- 支持 `BASE_URL`、`WORKS_DATA`、`SCENARIO`。
|
||||
- 覆盖列表接口,必要时增加详情/启动前读取接口。
|
||||
5. **新增执行说明**
|
||||
- 在 `scripts/loadtest/README.md` 写明:安装 K6、启动本地 dev 栈、提取数据、运行 smoke/baseline/spike、查看结果。
|
||||
6. **本地验证**
|
||||
- 启动 Genarrative dev 栈;注意端口可能漂移,使用实际 api-server 端口。
|
||||
- 跑 smoke:`SCENARIO=smoke`。
|
||||
- 确认失败率、p95、响应 shape。
|
||||
7. **可选集成 npm scripts**
|
||||
- 如果团队希望标准化入口,再加入 `package.json` scripts。
|
||||
8. **记录结果**
|
||||
- 将 smoke/baseline/spike 的结果摘要追加到 `scripts/loadtest/README.md` 或单独保存到 `.hermes/plans/` 的结果文档中。
|
||||
|
||||
## 启动与运行建议
|
||||
|
||||
本地服务启动按当前 Genarrative dev 栈约定:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
如果 SpacetimeDB/API/Vite 端口被占用,项目脚本会寻找可用端口;压测时必须从启动日志中读取实际 api-server 地址,并传给 K6:
|
||||
|
||||
```bash
|
||||
BASE_URL=http://127.0.0.1:<actual-api-port> \
|
||||
WORKS_DATA=scripts/loadtest/data/works-list.local.json \
|
||||
SCENARIO=smoke \
|
||||
k6 run scripts/loadtest/k6-works-list.js
|
||||
```
|
||||
|
||||
## 验证标准
|
||||
|
||||
### 数据源验证
|
||||
|
||||
- `works-list.local.json` 中只出现作品 profile 表。
|
||||
- 不出现以下字段或内容:
|
||||
- `password_hash`
|
||||
- `refresh_token_hash`
|
||||
- `phone_number_e164`
|
||||
- `phone_number_masked`
|
||||
- `wallet_ledger_id`
|
||||
- `auth_identity`
|
||||
- `user_account`
|
||||
- `puzzle_work_profile` 行数应接近原始文件中的 80 行。
|
||||
- `custom_world_profile` 行数应接近原始文件中的 1 行。
|
||||
|
||||
### K6 smoke 验证
|
||||
|
||||
- 所有目标接口返回 2xx。
|
||||
- `http_req_failed < 1%`。
|
||||
- 响应 JSON shape 与 shared contracts 对齐:`items` 或 `works` 数组。
|
||||
- K6 输出中能区分不同 endpoint 的耗时。
|
||||
|
||||
### 性能阈值初稿
|
||||
|
||||
- Smoke:`p95 < 800ms`,失败率 `< 1%`
|
||||
- Baseline:`p95 < 1000ms`,`p99 < 2000ms`,失败率 `< 1%`
|
||||
- Spike:允许短暂 p95 抖动,但 1 分钟内应恢复;失败率 `< 5%`
|
||||
|
||||
阈值后续需要结合本地机器性能、SpacetimeDB 本地模式和正式部署规格调整。
|
||||
|
||||
## 风险与注意事项
|
||||
|
||||
1. **原始迁移文件包含敏感数据。** 必须只提取作品列表白名单字段,禁止把原始 JSON 全量提交到仓库。
|
||||
2. **base64 封面可能导致压测数据膨胀。** 默认截断或替换为占位符,除非本次明确要测封面 payload 对响应体积的影响。
|
||||
3. **本地 SpacetimeDB 与 api-server 端口会漂移。** 不要硬编码端口,运行时通过 `BASE_URL` 注入。
|
||||
4. **列表接口可能需要鉴权。** 若实际接口要求登录,不要导入真实 refresh session;应使用本地测试账号或专门的压测 token 生成流程。
|
||||
5. **作品表名/接口路径可能与候选名称不完全一致。** 实施前必须以源码路由为准。
|
||||
6. **本计划仅保存压测方案,不执行实际压测。** 后续执行时再创建/修改脚本、导出过滤数据、跑 K6 并记录结果。
|
||||
|
||||
## 开放问题
|
||||
|
||||
1. 压测目标是本地 dev 栈、测试环境,还是预发/生产只读接口?不同环境阈值和安全边界不同。
|
||||
2. “作品列表”是否只包含拼图和自定义世界,还是要覆盖 match3d、square-hole、big-fish、visual-novel 的统一列表入口?
|
||||
3. 是否允许使用专门压测账号/token?如果接口无鉴权则无需处理。
|
||||
4. 是否需要测封面/asset 加载,还是只测作品列表 JSON API?
|
||||
447
.hermes/plans/2026-05-11_205645-genarrative-disaster-recovery.md
Normal file
447
.hermes/plans/2026-05-11_205645-genarrative-disaster-recovery.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# Genarrative 容灾方案设计计划
|
||||
|
||||
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 基于当前 Genarrative 单机生产部署、Jenkins 流水线、SpacetimeDB 与 Rust `api-server` 架构,补齐一套可落地、可演练、可审计的容灾方案。
|
||||
|
||||
**Architecture:** 首版容灾不引入复杂多活系统,优先围绕现有 `systemd + Nginx + SpacetimeDB + api-server + Jenkins` 单机生产推荐方案做“备份可恢复、版本可回滚、故障可切换、演练可复盘”。方案采用分层容灾:入口层、静态资源层、API 服务层、SpacetimeDB 数据层、外部服务与密钥层、Jenkins/发布链路层。
|
||||
|
||||
**Tech Stack:** Nginx、systemd、SpacetimeDB self-hosting、Rust `api-server` / Axum、Jenkins Pipeline、Shell/Node.js 运维脚本、仓库 `deploy/` 与 `docs/technical/` 文档体系。
|
||||
|
||||
---
|
||||
|
||||
## 1. 当前上下文与已确认事实
|
||||
|
||||
### 1.1 当前生产部署口径
|
||||
|
||||
来自 `docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md` 的现状:
|
||||
|
||||
- 生产为单机推荐方案,不使用 Docker。
|
||||
- 公网入口为 Nginx,负责 HTTPS、静态站点、后台静态页面、维护页、`/admin/api/` 与临时 `/api/*` 反向代理。
|
||||
- SpacetimeDB 作为 systemd 服务运行:
|
||||
- `spacetimedb.service`
|
||||
- 监听:`127.0.0.1:3101`
|
||||
- 数据根目录:`/stdb`
|
||||
- Rust `api-server` 作为 systemd 服务运行:
|
||||
- `genarrative-api.service`
|
||||
- 监听:`127.0.0.1:8082`
|
||||
- 环境文件:`/etc/genarrative/api-server.env`
|
||||
- 静态站点发布到 release/current 目录:
|
||||
- `/opt/genarrative/releases/<version>/`
|
||||
- `/opt/genarrative/current`
|
||||
- `/srv/genarrative/web`
|
||||
- 已有维护模式:
|
||||
- 开关文件:`/var/lib/genarrative/maintenance/enabled`
|
||||
- API 发布、SpacetimeDB 模块发布、数据库导入、服务器配置变更必须进入维护模式。
|
||||
- 已有数据库导入导出 Jenkins Job:
|
||||
- `Genarrative-Database-Export`
|
||||
- `Genarrative-Database-Import`
|
||||
- 对应文件:`jenkins/Jenkinsfile.production-database-export`、`jenkins/Jenkinsfile.production-database-import`
|
||||
- 已有回滚基本口径:
|
||||
- Web 回滚:切 `/srv/genarrative/web` 或 `/opt/genarrative/current` 到上一版本并 reload Nginx。
|
||||
- API 回滚:切 `/opt/genarrative/current` 到上一版本并重启 `genarrative-api.service`。
|
||||
- SpacetimeDB 模块回滚:发布上一版本 `spacetime_module.wasm`。
|
||||
- 数据回滚:使用导入流水线恢复指定备份,必须进入维护模式。
|
||||
|
||||
### 1.2 关键风险
|
||||
|
||||
- 当前是单机生产拓扑,单机磁盘、系统盘、`/stdb`、Nginx 或公网 IP 故障会造成整体不可用。
|
||||
- SpacetimeDB 是核心业务真相,容灾重点必须围绕 `/stdb`、数据库导出产物、schema 迁移与导入验证。
|
||||
- `/etc/genarrative/api-server.env` 持有生产密钥,不能进入 Git,也不能写进普通备份明文归档。
|
||||
- Jenkins controller/agent 同时承担构建、发布、备份、导入导出编排;Jenkins 不可用时仍需要有最小人工恢复路径。
|
||||
- 外部 LLM、图片、语音、3D 网关不是本仓库可控系统,容灾只能做到配置降级、超时隔离、能力熔断与可观测告警。
|
||||
|
||||
---
|
||||
|
||||
## 2. 容灾目标
|
||||
|
||||
### 2.1 恢复目标建议
|
||||
|
||||
| 灾难类型 | 目标 RTO | 目标 RPO | 首版策略 |
|
||||
| --- | ---: | ---: | --- |
|
||||
| Web 静态资源发布失败 | 5 分钟 | 0 | release/current 原子切换回滚 |
|
||||
| API 发布失败 | 10 分钟 | 0 | 维护模式 + 上一版二进制回滚 |
|
||||
| SpacetimeDB wasm 发布失败 | 15 分钟 | 0 或按迁移前备份 | 发布前导出 + 上一版 wasm 回滚 |
|
||||
| 数据误写 / 迁移失败 | 30-60 分钟 | 最近一次导出点 | 导入流水线从备份恢复 |
|
||||
| 生产机磁盘损坏 | 2-4 小时 | 最近一次异地备份 | 新机器 provision + 拉取 release 包 + 恢复数据库 |
|
||||
| Jenkins controller 不可用 | 1-2 小时 | 不影响线上数据 | 手工脚本恢复 + Jenkins 备份恢复 |
|
||||
| 第三方模型网关不可用 | 5-15 分钟内降级 | 不丢核心数据 | 配置切换 / 功能熔断 / 队列失败可重试 |
|
||||
|
||||
### 2.2 首版不做
|
||||
|
||||
- 不做跨地域双活写入。
|
||||
- 不做 SpacetimeDB 在线主从复制,除非后续官方能力与项目压测验证支持。
|
||||
- 不让前端绕过 `api-server` 直接承担正式业务真相。
|
||||
- 不把生产密钥、Token、数据库 dump、Jenkins secret 写入 Git。
|
||||
- 不恢复旧 `server-node`、Express、PostgreSQL 或 Docker 一体化部署方案。
|
||||
|
||||
---
|
||||
|
||||
## 3. 总体容灾设计
|
||||
|
||||
### 3.1 分层策略
|
||||
|
||||
1. **入口层:Nginx / DNS / HTTPS**
|
||||
- 保留 Nginx 配置模板在 Git:`deploy/nginx/genarrative.conf`、`deploy/nginx/genarrative-dev-http.conf`。
|
||||
- 为 release 环境建立 Nginx 配置备份与证书恢复流程。
|
||||
- 明确 DNS 切换预案:生产机不可恢复时,将域名指向灾备机公网 IP。
|
||||
|
||||
2. **静态资源层:Web / Admin Web**
|
||||
- 依赖 `web.tar.gz`、`web.tar.gz.sha256`、`release-manifest.json`。
|
||||
- 保留最近 N 个 release 目录与构建产物指针。
|
||||
- 回滚只切软链,不重新构建。
|
||||
|
||||
3. **API 服务层:Rust `api-server`**
|
||||
- 依赖归档的 `api-server` 二进制、checksum、`release-manifest.json`。
|
||||
- `/etc/genarrative/api-server.env` 通过加密备份或密钥管理恢复,不进入 release 包。
|
||||
- systemd unit 由 `deploy/systemd/genarrative-api.service` 重新安装。
|
||||
|
||||
4. **数据层:SpacetimeDB**
|
||||
- 每次高风险发布前强制导出数据库。
|
||||
- 定时导出:建议每天至少 1 次;高活跃期可每 4 小时 1 次。
|
||||
- 导出产物同时保存在:Jenkins 归档 + 生产机 `SERVER_BACKUP_DIRECTORY` + 异地对象存储/备份机。
|
||||
- 导入前自动生成安全备份,保留当前实现口径。
|
||||
|
||||
5. **发布编排层:Jenkins**
|
||||
- Jenkins Job、Jenkinsfile 在 Git 中可恢复。
|
||||
- Jenkins controller 配置、凭据、插件清单需要额外备份。
|
||||
- 发布 agent 使用 inbound + systemd 自恢复,agent secret 仅存在目标机或 Jenkins 凭据。
|
||||
|
||||
6. **密钥与外部服务层**
|
||||
- `/etc/genarrative/api-server.env`、Jenkins Secret Text、SSH PEM、agent secret 不进 Git。
|
||||
- 制定密钥清单和恢复责任人,但不在仓库记录明文。
|
||||
- 外部服务配置按 `docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md` 维护必配项。
|
||||
|
||||
---
|
||||
|
||||
## 4. 建议新增/更新的文档
|
||||
|
||||
### Task 1: 新增生产容灾技术方案文档
|
||||
|
||||
**Objective:** 形成团队可共享、可执行的容灾总纲。
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md`
|
||||
- Modify: `docs/technical/README.md`(若已有技术索引,应加入该文档入口)
|
||||
- Optional Modify: `.hermes/shared-memory/project-overview.md`(只加稳定索引,不写敏感信息)
|
||||
|
||||
**文档必须覆盖:**
|
||||
|
||||
1. 容灾目标:RTO/RPO 表。
|
||||
2. 生产资产清单:Nginx、systemd、release/current、`/stdb`、`/etc/genarrative/api-server.env`、Jenkins、构建产物。
|
||||
3. 备份策略:
|
||||
- 数据库导出。
|
||||
- release 产物保留。
|
||||
- Nginx/systemd/env 配置备份。
|
||||
- Jenkins 配置备份。
|
||||
4. 恢复流程:
|
||||
- Web 回滚。
|
||||
- API 回滚。
|
||||
- Stdb module 回滚。
|
||||
- 数据恢复。
|
||||
- 整机重建。
|
||||
5. 演练计划:每月一次数据库恢复演练,每季度一次整机重建演练。
|
||||
6. 安全边界:密钥不进 Git,备份加密,最小权限。
|
||||
7. 验收命令与人工检查清单。
|
||||
|
||||
**Verification:**
|
||||
|
||||
```bash
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
Expected: PASS,无中文乱码、无 BOM/CRLF 问题。
|
||||
|
||||
---
|
||||
|
||||
## 5. 建议新增/更新的脚本与流水线
|
||||
|
||||
### Task 2: 增强数据库定时备份流水线
|
||||
|
||||
**Objective:** 把现有人工导出扩展为可定时执行、可异地保存、可审计的备份流程。
|
||||
|
||||
**Files:**
|
||||
- Modify: `jenkins/Jenkinsfile.production-database-export`
|
||||
- Modify: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md`
|
||||
- Optional Create: `scripts/deploy/production-backup-sync.sh`
|
||||
|
||||
**Implementation notes:**
|
||||
|
||||
- 在 Jenkins Job 中保留人工触发能力,同时建议配置 cron:
|
||||
- development:每天凌晨。
|
||||
- release:每天凌晨或业务低峰。
|
||||
- 增加备份命名规范:
|
||||
- `spacetime-migration-<database>-<yyyyMMdd-HHmmss>-<source_commit>.json`
|
||||
- 增加 `SERVER_BACKUP_DIRECTORY` 默认建议:
|
||||
- `/var/backups/genarrative/spacetimedb/<database>/`
|
||||
- 增加备份保留策略:
|
||||
- 本机保留 7-14 天。
|
||||
- 异地保留 30-90 天。
|
||||
- 如实现 `production-backup-sync.sh`,只做同步框架,不硬编码真实 bucket、账号、endpoint 或密钥。
|
||||
|
||||
**Verification:**
|
||||
|
||||
```bash
|
||||
bash -n scripts/deploy/production-backup-sync.sh
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
Expected: shell 语法通过;文档编码检查通过。
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 增加灾备恢复 Runbook
|
||||
|
||||
**Objective:** 在真正故障时不依赖临场推理,按清单执行恢复。
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/operations/PRODUCTION_DR_RUNBOOK_2026-05-11.md`
|
||||
- Modify: `docs/operations/README.md`(如果存在)
|
||||
|
||||
**Runbook sections:**
|
||||
|
||||
1. 故障分级:P0/P1/P2。
|
||||
2. 第一响应:
|
||||
- 判断 Nginx 是否在线。
|
||||
- 判断 `genarrative-api.service` 是否在线。
|
||||
- 判断 `spacetimedb.service` 是否在线。
|
||||
- 判断磁盘是否满。
|
||||
- 判断 Jenkins agent 是否在线。
|
||||
3. 快速止血:
|
||||
- 开维护模式。
|
||||
- 禁止继续发布。
|
||||
- 保留现场日志。
|
||||
4. 回滚流程:
|
||||
- Web 回滚命令。
|
||||
- API 回滚命令。
|
||||
- Stdb wasm 回滚命令。
|
||||
5. 数据恢复流程:
|
||||
- 选择备份。
|
||||
- dry-run 导入。
|
||||
- 确认导入。
|
||||
- smoke test。
|
||||
6. 整机重建流程:
|
||||
- 新机器 provision。
|
||||
- 恢复 `/etc/genarrative/api-server.env`。
|
||||
- 恢复 SpacetimeDB 数据。
|
||||
- 发布最近稳定 release。
|
||||
- DNS 切换。
|
||||
7. 复盘模板。
|
||||
|
||||
**Verification:**
|
||||
|
||||
```bash
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
Expected: PASS。
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 增加备份健康检查与恢复演练记录模板
|
||||
|
||||
**Objective:** 防止“有备份但不可恢复”。
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/operations/DR_DRILL_REPORT_TEMPLATE.md`
|
||||
- Optional Create: `scripts/deploy/verify-database-backup.sh`
|
||||
- Modify: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md`
|
||||
|
||||
**建议检查项:**
|
||||
|
||||
- 备份文件存在且大小非 0。
|
||||
- 备份文件 checksum 可验证。
|
||||
- 备份文件可被 `Genarrative-Database-Import` dry-run 解析。
|
||||
- 最近一次备份时间未超过 RPO 阈值。
|
||||
- 导入后 `/healthz` 可用。
|
||||
- 首页、后台登录页、关键 API smoke 可用。
|
||||
|
||||
**Verification:**
|
||||
|
||||
```bash
|
||||
bash -n scripts/deploy/verify-database-backup.sh
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
Expected: PASS。
|
||||
|
||||
---
|
||||
|
||||
## 6. 具体恢复流程草案
|
||||
|
||||
### 6.1 Web 静态资源回滚
|
||||
|
||||
1. 进入目标机。
|
||||
2. 查看 release 目录:`/opt/genarrative/releases/`。
|
||||
3. 选择上一个稳定版本。
|
||||
4. 切换 `/srv/genarrative/web` 或 `/opt/genarrative/current` 软链。
|
||||
5. 执行 Nginx 配置检查与 reload。
|
||||
6. 访问首页与后台静态入口。
|
||||
|
||||
验收:
|
||||
|
||||
- `/` 返回最新稳定页面。
|
||||
- `/admin/` 返回后台页面。
|
||||
- 静态资源无 404。
|
||||
|
||||
### 6.2 API 回滚
|
||||
|
||||
1. 开维护模式。
|
||||
2. 切 `/opt/genarrative/current` 到上一版包含稳定 `api-server` 的 release。
|
||||
3. 重启 `genarrative-api.service`。
|
||||
4. 本机检查 `http://127.0.0.1:8082/healthz`。
|
||||
5. 检查 Nginx 反代路径。
|
||||
6. 解除维护模式。
|
||||
|
||||
验收:
|
||||
|
||||
- `systemctl status genarrative-api.service` 正常。
|
||||
- `/healthz` 正常。
|
||||
- 后台 `/admin/api/*` 基础接口正常。
|
||||
|
||||
### 6.3 SpacetimeDB 模块回滚
|
||||
|
||||
1. 开维护模式。
|
||||
2. 确认目标数据库名与当前 API 环境一致:`GENARRATIVE_SPACETIME_DATABASE`。
|
||||
3. 选择上一版 `spacetime_module.wasm`。
|
||||
4. 使用 `spacetimedb` 服务用户发布上一版 wasm。
|
||||
5. 重启或检查 `spacetimedb.service`。
|
||||
6. 检查 `api-server` 对目标数据库访问。
|
||||
7. 解除维护模式。
|
||||
|
||||
注意:如果 schema 已迁移且旧 wasm 不兼容当前数据,需要走数据恢复,不应直接盲目发布旧 wasm。
|
||||
|
||||
### 6.4 数据恢复
|
||||
|
||||
1. 开维护模式。
|
||||
2. 从 Jenkins 归档或 `SERVER_BACKUP_DIRECTORY` 选择备份。
|
||||
3. 先执行导入 dry-run。
|
||||
4. 真正导入前生成当前数据库安全备份。
|
||||
5. 执行导入。
|
||||
6. 执行 smoke test。
|
||||
7. 解除维护模式。
|
||||
|
||||
必须记录:
|
||||
|
||||
- 备份文件名。
|
||||
- 来源 Job/build number。
|
||||
- 恢复目标 database。
|
||||
- 恢复开始/结束时间。
|
||||
- 恢复后验证结果。
|
||||
|
||||
### 6.5 整机重建
|
||||
|
||||
1. 准备新 Linux 机器。
|
||||
2. 接入 Jenkins release deploy agent,或准备人工 SSH 运维路径。
|
||||
3. 运行 `Genarrative-Server-Provision`:
|
||||
- 创建用户和目录。
|
||||
- 安装 SpacetimeDB。
|
||||
- 安装 systemd unit。
|
||||
- 安装 Nginx 配置。
|
||||
4. 恢复 `/etc/genarrative/api-server.env`。
|
||||
5. 发布最近稳定 Web/API/Stdb 产物。
|
||||
6. 导入最近一次有效数据库备份。
|
||||
7. smoke test。
|
||||
8. 切 DNS。
|
||||
9. 观察 30-60 分钟。
|
||||
|
||||
---
|
||||
|
||||
## 7. 文件可能变更清单
|
||||
|
||||
首版落地建议按以下文件收口:
|
||||
|
||||
- Create: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md`
|
||||
- Create: `docs/operations/PRODUCTION_DR_RUNBOOK_2026-05-11.md`
|
||||
- Create: `docs/operations/DR_DRILL_REPORT_TEMPLATE.md`
|
||||
- Modify: `docs/technical/README.md`
|
||||
- Modify: `docs/operations/README.md`(若存在)
|
||||
- Modify: `.hermes/shared-memory/project-overview.md`(仅增加文档索引)
|
||||
- Optional Modify: `jenkins/Jenkinsfile.production-database-export`
|
||||
- Optional Modify: `jenkins/Jenkinsfile.production-database-import`
|
||||
- Optional Create: `scripts/deploy/production-backup-sync.sh`
|
||||
- Optional Create: `scripts/deploy/verify-database-backup.sh`
|
||||
|
||||
---
|
||||
|
||||
## 8. 测试与验收
|
||||
|
||||
### 8.1 文档与编码
|
||||
|
||||
```bash
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
Expected: PASS。
|
||||
|
||||
### 8.2 Shell 脚本语法
|
||||
|
||||
如新增 shell 脚本:
|
||||
|
||||
```bash
|
||||
bash -n scripts/deploy/production-backup-sync.sh
|
||||
bash -n scripts/deploy/verify-database-backup.sh
|
||||
```
|
||||
|
||||
Expected: PASS。
|
||||
|
||||
### 8.3 Jenkinsfile 静态检查
|
||||
|
||||
建议在 Jenkins UI 或本地 Jenkins Pipeline Linter 中检查:
|
||||
|
||||
- `jenkins/Jenkinsfile.production-database-export`
|
||||
- `jenkins/Jenkinsfile.production-database-import`
|
||||
|
||||
Expected: Pipeline syntax valid。
|
||||
|
||||
### 8.4 演练验收
|
||||
|
||||
至少完成一次 development 目标演练:
|
||||
|
||||
1. 触发 `Genarrative-Database-Export`。
|
||||
2. 确认备份产物存在并归档。
|
||||
3. 使用 `Genarrative-Database-Import` dry-run 验证备份可解析。
|
||||
4. 不覆盖生产数据的前提下,记录演练报告。
|
||||
|
||||
release 目标演练应在业务低峰进行,并先确认通知渠道可用。
|
||||
|
||||
---
|
||||
|
||||
## 9. 风险、取舍与开放问题
|
||||
|
||||
### 9.1 风险
|
||||
|
||||
- 单机生产仍存在物理机级单点故障,首版只能通过“快速重建 + 异地备份”降低恢复时间。
|
||||
- SpacetimeDB schema 回滚不一定可逆,必须把发布前备份作为强约束。
|
||||
- Jenkins controller 若在本地 Windows,controller 自身备份和恢复需要单独制定,不应只依赖 agent 自恢复。
|
||||
- 外部模型网关失败可能影响创作能力,但不应影响已发布作品浏览和后台基础能力。
|
||||
|
||||
### 9.2 取舍
|
||||
|
||||
- 选择先做可执行 runbook 和备份恢复演练,而不是直接引入复杂多活。
|
||||
- 选择继续复用现有 Jenkins 导入导出流水线,降低工程改造风险。
|
||||
- 选择不把密钥恢复细节写死到 Git 文档,避免泄露。
|
||||
|
||||
### 9.3 开放问题
|
||||
|
||||
1. release 环境是否已经有独立备份机或对象存储?如果有,需要补充备份同步目标,但不能提交密钥。
|
||||
2. Jenkins controller 的 `JENKINS_HOME` 当前实际部署在哪里?是否已有周期备份?
|
||||
3. 生产域名 DNS TTL 当前是多少?是否可降低到适合故障切换的值?
|
||||
4. `/stdb` 所在磁盘是否独立于系统盘?是否已有磁盘水位告警?
|
||||
5. release 环境的通知渠道除邮件外是否需要接入企业微信/飞书/Telegram?
|
||||
|
||||
---
|
||||
|
||||
## 10. 推荐实施顺序
|
||||
|
||||
1. 先只落文档:技术方案 + runbook + 演练模板。
|
||||
2. 在 development 目标做一次数据库导出 + dry-run 导入演练。
|
||||
3. 根据演练结果补脚本:备份同步、备份健康检查。
|
||||
4. 再把 release 备份设置为定时任务。
|
||||
5. 最后规划整机重建演练与 DNS 切换演练。
|
||||
|
||||
首版完成标准:
|
||||
|
||||
- 团队任一成员打开 runbook,即可在 30 分钟内完成 Web/API 回滚或数据库备份 dry-run 恢复。
|
||||
- 最近一次数据库备份时间、备份位置、checksum、恢复演练结果可追溯。
|
||||
- 生产密钥仍只存在于服务器/Jenkins 凭据/加密备份中,不进入 Git。
|
||||
403
.hermes/plans/2026-05-11_205658-security-vulnerability-scan.md
Normal file
403
.hermes/plans/2026-05-11_205658-security-vulnerability-scan.md
Normal file
@@ -0,0 +1,403 @@
|
||||
# 当前项目安全漏洞检查计划
|
||||
|
||||
> **For Hermes:** Use subagent-driven-development skill only if the user later asks to execute this plan. 本计划当前仅用于规划,不实施代码修改。
|
||||
|
||||
**Goal:** 对 Genarrative 当前工作区做一次可复现的安全漏洞基线检查,覆盖依赖漏洞、密钥泄露、常见高风险代码模式、后端 Rust crate 风险和前端/Node 供应链风险,并输出可落地的整改清单。
|
||||
|
||||
**Architecture:** 采用“只读扫描 → 结果归档 → 人工分级 → 最小修复建议”的方式推进。先不直接升级依赖或改代码,避免安全扫描引入不可控 breaking change;执行阶段只在用户确认后运行扫描命令,并把报告保存到 `docs/audits/` 或 `.hermes/plans/` 附件中。
|
||||
|
||||
**Tech Stack:** Node/Vite/React/TypeScript、Rust workspace/Axum/SpacetimeDB、npm lockfile、Cargo.lock、Git worktree。
|
||||
|
||||
---
|
||||
|
||||
## 当前上下文 / 假设
|
||||
|
||||
- 当前有效工作区:`C:/proj/Genarrative/.worktrees/hermes-3337436a`。
|
||||
- 本次用户以 `/plan` 模式要求“检查一下当前项目的安全漏洞”,因此本轮只制定计划,不执行会产生报告、安装工具、修改依赖、提交或推送的操作。
|
||||
- 已确认项目包含:
|
||||
- 根 `package.json`,脚本包括 `npm run lint`、`npm run test`、`npm run build`、`npm run check:encoding`。
|
||||
- 根 `package-lock.json`。
|
||||
- `server-rs/Cargo.toml` 和 `server-rs/Cargo.lock`。
|
||||
- `apps/admin-web/package.json`、`packages/shared/package.json`。
|
||||
- `.hermes/shared-memory/development-workflow.md` 要求开发前读取共享记忆,并以当前代码、`docs/`、`AGENTS.md` 为准。
|
||||
- 安全扫描不应把真实密钥写入仓库;发现疑似密钥时只记录文件位置、变量名、脱敏片段和处置建议。
|
||||
|
||||
## 总体策略
|
||||
|
||||
1. 先做仓库状态和范围确认,避免扫描其他 worktree 或错误路径。
|
||||
2. 优先运行不会修改文件的安全检查:`npm audit --json`、`cargo audit`、密钥扫描、危险代码模式扫描。
|
||||
3. 分前端供应链、后端供应链、源码安全、配置/脚本安全四类归档。
|
||||
4. 对结果按严重级别分层:Critical / High / Medium / Low / Informational。
|
||||
5. 对每个真实问题给出:影响范围、证据、可行修复、验证命令、是否需要业务回归。
|
||||
6. 只有在用户确认进入执行/修复阶段后,才做依赖升级、代码修复、文档更新、测试和提交。
|
||||
|
||||
---
|
||||
|
||||
## Step-by-step Plan
|
||||
|
||||
### Task 1: 确认扫描工作区和基线状态
|
||||
|
||||
**Objective:** 确保后续扫描针对当前 worktree,且不会误把既有未提交变更当成安全修复结果。
|
||||
|
||||
**Files:**
|
||||
- Read-only: `AGENTS.md`
|
||||
- Read-only: `.hermes/README.md`
|
||||
- Read-only: `.hermes/shared-memory/development-workflow.md`
|
||||
- Read-only: `package.json`
|
||||
- Read-only: `server-rs/Cargo.toml`
|
||||
|
||||
**Commands:**
|
||||
|
||||
```bash
|
||||
pwd
|
||||
git status --short
|
||||
git branch --show-current
|
||||
git rev-parse --show-toplevel
|
||||
```
|
||||
|
||||
**Expected:**
|
||||
- `pwd` / `git rev-parse --show-toplevel` 指向 `C:/proj/Genarrative/.worktrees/hermes-3337436a` 对应路径。
|
||||
- 分支为当前隔离 worktree 分支。
|
||||
- 记录是否已有未提交变更;如存在,扫描报告需标注“基于含未提交变更的工作区”。
|
||||
|
||||
**Validation:**
|
||||
- 不修改任何项目文件。
|
||||
- 如发现路径不是当前 worktree,停止并重新确认路径。
|
||||
|
||||
### Task 2: 生成依赖清单和锁文件基线
|
||||
|
||||
**Objective:** 明确 Node 与 Rust 依赖入口,避免漏扫子包或 admin web。
|
||||
|
||||
**Files:**
|
||||
- Read-only: `package.json`
|
||||
- Read-only: `package-lock.json`
|
||||
- Read-only: `apps/admin-web/package.json`
|
||||
- Read-only: `packages/shared/package.json`
|
||||
- Read-only: `server-rs/Cargo.toml`
|
||||
- Read-only: `server-rs/Cargo.lock`
|
||||
|
||||
**Commands:**
|
||||
|
||||
```bash
|
||||
npm --version
|
||||
node --version
|
||||
cargo --version
|
||||
rustc --version
|
||||
```
|
||||
|
||||
可选只读清单:
|
||||
|
||||
```bash
|
||||
npm ls --all --json > /tmp/genarrative-npm-ls.json
|
||||
cargo metadata --manifest-path server-rs/Cargo.toml --format-version 1 > /tmp/genarrative-cargo-metadata.json
|
||||
```
|
||||
|
||||
**Expected:**
|
||||
- 明确 npm / Node / Rust / Cargo 版本。
|
||||
- 若 `npm ls` 因 peer dependency 或历史依赖问题非 0,保留输出并继续 audit。
|
||||
|
||||
**Validation:**
|
||||
- `/tmp` 输出不进入 Git。
|
||||
- 不运行 `npm install`、`npm update`、`cargo update`。
|
||||
|
||||
### Task 3: Node 供应链漏洞扫描
|
||||
|
||||
**Objective:** 检查根 lockfile 覆盖的前端、脚本和 admin web 依赖漏洞。
|
||||
|
||||
**Files:**
|
||||
- Read-only: `package-lock.json`
|
||||
- Read-only: `package.json`
|
||||
|
||||
**Commands:**
|
||||
|
||||
```bash
|
||||
npm audit --json > /tmp/genarrative-npm-audit.json
|
||||
npm audit --audit-level=moderate
|
||||
```
|
||||
|
||||
**Expected:**
|
||||
- `npm audit --json` 生成机器可读结果。
|
||||
- 第二条命令给出人类可读摘要;如返回非 0,按漏洞严重度记录,不直接执行 `npm audit fix`。
|
||||
|
||||
**Result fields to extract:**
|
||||
- package name
|
||||
- vulnerable versions
|
||||
- installed version
|
||||
- severity
|
||||
- CVE / GHSA
|
||||
- via chain
|
||||
- fixAvailable 是否为 major/breaking
|
||||
- affected direct dependency or transitive dependency
|
||||
|
||||
**Validation:**
|
||||
- 不执行 `npm audit fix`。
|
||||
- 如 npm registry 网络不可用,记录阻塞原因和可重试命令。
|
||||
|
||||
### Task 4: Rust 供应链漏洞扫描
|
||||
|
||||
**Objective:** 检查 `server-rs` workspace 的 Cargo 依赖漏洞、弃用 crate 和 yanked crate。
|
||||
|
||||
**Files:**
|
||||
- Read-only: `server-rs/Cargo.toml`
|
||||
- Read-only: `server-rs/Cargo.lock`
|
||||
|
||||
**Commands:**
|
||||
|
||||
优先:
|
||||
|
||||
```bash
|
||||
cargo audit --json --manifest-path server-rs/Cargo.toml > /tmp/genarrative-cargo-audit.json
|
||||
cargo audit --manifest-path server-rs/Cargo.toml
|
||||
```
|
||||
|
||||
如果本机没有 `cargo audit`:
|
||||
|
||||
```bash
|
||||
cargo install cargo-audit --locked
|
||||
cargo audit --manifest-path server-rs/Cargo.toml
|
||||
```
|
||||
|
||||
**Execution note:**
|
||||
- 安装 `cargo-audit` 会改变用户 Cargo 工具目录,不属于纯只读扫描;执行前需用户确认。
|
||||
- 如果用户不希望安装工具,则记录“Rust 漏洞扫描未完成”,并给出本地安装或 CI 执行建议。
|
||||
|
||||
**Result fields to extract:**
|
||||
- advisory id
|
||||
- package
|
||||
- version
|
||||
- patched versions
|
||||
- unaffected versions
|
||||
- severity / CVSS if available
|
||||
- dependency path
|
||||
- whether it is runtime reachable in `api-server` / `spacetime-module`
|
||||
|
||||
**Validation:**
|
||||
- 不运行 `cargo update`。
|
||||
- 不改 `Cargo.lock`。
|
||||
|
||||
### Task 5: 密钥和敏感配置泄露扫描
|
||||
|
||||
**Objective:** 检查仓库中是否误提交 API key、token、私钥、cookie、`.env` 类文件或个人 Hermes 配置。
|
||||
|
||||
**Files / paths to scan:**
|
||||
- Full repo excluding `.git/`, `node_modules/`, `target/`, `dist/`, build artifacts。
|
||||
- 特别关注:`.hermes/`、`scripts/`、`server-rs/`、`apps/admin-web/`、`src/`、`docs/`。
|
||||
|
||||
**Preferred commands:**
|
||||
|
||||
如果有 gitleaks:
|
||||
|
||||
```bash
|
||||
gitleaks detect --source . --no-git --redact --report-format json --report-path /tmp/genarrative-gitleaks.json
|
||||
```
|
||||
|
||||
如果没有 gitleaks,先用只读 grep/ripgrep 兜底:
|
||||
|
||||
```bash
|
||||
git ls-files -z | xargs -0 grep -nIE "(api[_-]?key|secret|password|passwd|token|private[_-]?key|BEGIN (RSA|OPENSSH|EC|DSA)? ?PRIVATE KEY|AKIA[0-9A-Z]{16}|xox[baprs]-|sk-[A-Za-z0-9_-]{20,})" > /tmp/genarrative-secret-grep.txt || true
|
||||
```
|
||||
|
||||
**Execution note:**
|
||||
- 安装 gitleaks 需要用户确认。
|
||||
- grep 结果包含 false positive,必须人工分级,不得直接当作泄露结论。
|
||||
|
||||
**Validation:**
|
||||
- 报告中对值做脱敏,只保留前后 3-4 位或完全不记录值。
|
||||
- 如果发现 `.env.local` 或真实 token 被跟踪,立即标为 Critical。
|
||||
|
||||
### Task 6: 常见源码安全模式扫描
|
||||
|
||||
**Objective:** 快速发现高风险代码模式:命令注入、动态执行、路径穿越、危险反序列化、XSS、日志泄密、宽松 CORS 等。
|
||||
|
||||
**Files / paths:**
|
||||
- `src/**/*.{ts,tsx,js,mjs,cjs}`
|
||||
- `apps/admin-web/**/*.{ts,tsx,js,mjs,cjs}`
|
||||
- `scripts/**/*.{js,mjs,cjs,ts}`
|
||||
- `server-rs/crates/**/*.rs`
|
||||
|
||||
**Commands:**
|
||||
|
||||
```bash
|
||||
# JS/TS 动态执行与 HTML 注入
|
||||
rg -n "\beval\(|new Function\(|dangerouslySetInnerHTML|innerHTML\s*=|document\.write\(" src apps scripts packages
|
||||
|
||||
# Node 命令执行风险
|
||||
rg -n "exec\(|execSync\(|spawn\(|spawnSync\(|shell:\s*true|child_process" scripts src apps packages
|
||||
|
||||
# Rust 命令、文件路径、unwrap 风险热点
|
||||
rg -n "Command::new|std::process|\.unwrap\(|\.expect\(|fs::|File::open|PathBuf|set_header|cors|CorsLayer" server-rs/crates
|
||||
|
||||
# 宽松 CORS / Cookie / Auth 相关热点
|
||||
rg -n "allow_origin|Any|cookie|Authorization|Bearer|refresh|access_token|set_cookie|SameSite|Secure|HttpOnly" server-rs/crates src apps scripts
|
||||
```
|
||||
|
||||
**Expected:**
|
||||
- 输出作为“热点清单”,不等同于漏洞。
|
||||
- 对 auth/session、文件上传、OSS 签名、外部 LLM/图片服务请求、SpacetimeDB 访问 facade 做人工复核。
|
||||
|
||||
**Validation:**
|
||||
- 每个疑似问题必须能说明可利用条件,无法说明则降级为 Informational。
|
||||
|
||||
### Task 7: Web/API 安全配置人工复核
|
||||
|
||||
**Objective:** 对项目特有的安全边界做代码审阅,补足工具扫描无法覆盖的业务风险。
|
||||
|
||||
**Likely files to review:**
|
||||
- `server-rs/crates/api-server/src/**`
|
||||
- `server-rs/crates/platform-auth/src/**`
|
||||
- `server-rs/crates/platform-oss/src/**`
|
||||
- `server-rs/crates/platform-llm/src/**`
|
||||
- `server-rs/crates/spacetime-client/src/**`
|
||||
- `src/services/**`
|
||||
- `apps/admin-web/src/**`
|
||||
- `scripts/*deploy*`
|
||||
- `scripts/*api-server*`
|
||||
- `.github/workflows/**` if present
|
||||
|
||||
**Checklist:**
|
||||
- Auth / session:access token 与 refresh cookie 的生命周期、SameSite/Secure/HttpOnly、错误日志是否泄露 token。
|
||||
- CORS:开发环境与生产环境是否区分,是否存在生产 `Any`。
|
||||
- SSRF / outbound:LLM、图片生成、OSS、任意 URL 下载是否校验协议和大小。
|
||||
- Upload / Data URL:大小限制、MIME 校验、base64 解析错误处理。
|
||||
- Path traversal:脚本和后端是否拼接用户输入路径。
|
||||
- Admin:后台接口是否有权限校验,是否复用普通用户 token。
|
||||
- SpacetimeDB:private table / reducer 是否绕过 api-server facade 暴露敏感数据。
|
||||
- Logging:日志是否打印 API key、token、cookie、用户私密内容。
|
||||
|
||||
**Validation:**
|
||||
- 对每个命中的真实风险,记录具体文件路径和函数名。
|
||||
- 对“需要运行环境才能验证”的风险,列出 smoke 或单测建议。
|
||||
|
||||
### Task 8: 汇总漏洞分级与整改建议
|
||||
|
||||
**Objective:** 把扫描结果转成团队可执行的安全整改报告。
|
||||
|
||||
**Deliverable candidates:**
|
||||
- `docs/audits/SECURITY_VULNERABILITY_SCAN_YYYY-MM-DD.md`
|
||||
- 或如果用户只要临时报告:`.hermes/plans/assets/security-scan-YYYY-MM-DD.md`
|
||||
|
||||
**Report structure:**
|
||||
|
||||
```markdown
|
||||
# 安全漏洞扫描报告 YYYY-MM-DD
|
||||
|
||||
## 扫描范围
|
||||
## 扫描命令与环境
|
||||
## 摘要
|
||||
## Critical
|
||||
## High
|
||||
## Medium
|
||||
## Low
|
||||
## Informational / False Positive
|
||||
## 依赖升级建议
|
||||
## 代码修复建议
|
||||
## 需要人工确认的问题
|
||||
## 验证命令
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
- 报告不包含真实密钥。
|
||||
- 每条问题都有“证据、影响、建议、验证”。
|
||||
- 明确哪些是工具扫描结果,哪些是人工判断。
|
||||
|
||||
### Task 9: 如用户要求修复,再分批执行最小修复
|
||||
|
||||
**Objective:** 避免一次性大规模升级导致回归,把修复拆为可验证的小批次。
|
||||
|
||||
**Suggested order:**
|
||||
1. Critical secrets:立即移除、轮换密钥、补 `.gitignore`/文档约束(注意项目约束:不要在 `.gitignore` 中添加 `.env.local`)。
|
||||
2. Critical/High direct dependencies:优先升级 direct dependency,运行最小测试。
|
||||
3. Critical/High transitive dependencies:评估是否由 direct dependency patch/minor 升级带出。
|
||||
4. 源码漏洞:按入口编写回归测试,再修复。
|
||||
5. Medium/Low:按风险和 breaking change 代价排期。
|
||||
|
||||
**Required verification after fixes:**
|
||||
|
||||
```bash
|
||||
npm run check:encoding
|
||||
npm run lint:eslint
|
||||
npm run typecheck
|
||||
npm run test
|
||||
npm run build
|
||||
cd server-rs && cargo test --workspace
|
||||
```
|
||||
|
||||
后端 API 或 auth 修复涉及运行态时,还需要:
|
||||
|
||||
```bash
|
||||
npm run api-server
|
||||
# 另一个终端检查 /healthz 并执行对应 smoke
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
- 修复后重新跑对应 audit / secret scan。
|
||||
- 走 `requesting-code-review` 的独立安全复核流程。
|
||||
|
||||
---
|
||||
|
||||
## Files likely to change(仅修复阶段)
|
||||
|
||||
本计划阶段不修改以下文件;只有用户确认执行修复时才可能变化:
|
||||
|
||||
- `package.json`
|
||||
- `package-lock.json`
|
||||
- `apps/admin-web/package.json`
|
||||
- `server-rs/Cargo.toml`
|
||||
- `server-rs/Cargo.lock`
|
||||
- `server-rs/crates/api-server/src/**`
|
||||
- `server-rs/crates/platform-auth/src/**`
|
||||
- `server-rs/crates/platform-oss/src/**`
|
||||
- `server-rs/crates/platform-llm/src/**`
|
||||
- `src/services/**`
|
||||
- `apps/admin-web/src/**`
|
||||
- `scripts/**`
|
||||
- `docs/audits/SECURITY_VULNERABILITY_SCAN_YYYY-MM-DD.md`
|
||||
- `.hermes/shared-memory/pitfalls.md`(仅当发现长期有效、会反复踩的安全排障经验时更新)
|
||||
|
||||
## Tests / Validation
|
||||
|
||||
安全扫描执行阶段:
|
||||
|
||||
```bash
|
||||
npm audit --json > /tmp/genarrative-npm-audit.json
|
||||
npm audit --audit-level=moderate
|
||||
cargo audit --manifest-path server-rs/Cargo.toml
|
||||
rg -n "\beval\(|new Function\(|dangerouslySetInnerHTML|innerHTML\s*=|document\.write\(" src apps scripts packages
|
||||
rg -n "exec\(|execSync\(|spawn\(|spawnSync\(|shell:\s*true|child_process" scripts src apps packages
|
||||
rg -n "Command::new|std::process|\.unwrap\(|\.expect\(|fs::|File::open|PathBuf|set_header|cors|CorsLayer" server-rs/crates
|
||||
```
|
||||
|
||||
修复执行阶段:
|
||||
|
||||
```bash
|
||||
npm run check:encoding
|
||||
npm run lint:eslint
|
||||
npm run typecheck
|
||||
npm run test
|
||||
npm run build
|
||||
cd server-rs && cargo test --workspace
|
||||
```
|
||||
|
||||
如变更后端运行态、安全中间件、auth/session:
|
||||
|
||||
```bash
|
||||
npm run api-server
|
||||
# 检查 /healthz
|
||||
# 执行相关 auth / API smoke
|
||||
```
|
||||
|
||||
## Risks, tradeoffs, and open questions
|
||||
|
||||
- `npm audit fix` 可能升级 major version,破坏 Vite/React/ESLint/Vitest 兼容性;必须先人工审查 `fixAvailable`。
|
||||
- `cargo audit` 可能需要安装 `cargo-audit`;安装工具属于用户环境变更,应先确认。
|
||||
- 密钥扫描极易产生 false positive;必须人工复核,报告中禁止输出真实密钥。
|
||||
- Rust `unwrap/expect` 不是天然漏洞;只有对外部输入、网络、文件、数据库响应等不可信数据造成 panic/DoS 时才升级为真实风险。
|
||||
- Web 安全检查需要区分开发环境和生产环境;开发 CORS 放宽不等于生产漏洞,但生产配置必须有明确边界。
|
||||
- 如果扫描发现历史提交中曾泄露密钥,删除当前文件不够,必须轮换密钥并考虑历史清理策略。
|
||||
- 当前计划未直接访问 CI/Jenkins/生产配置;若用户希望覆盖 CI/CD、镜像、部署主机和运行时端口,需要补充 Jenkins console、部署脚本和生产环境配置的只读访问方式。
|
||||
|
||||
## Missing artifacts / follow-up checkpoints
|
||||
|
||||
- 尚未获得用户确认是否允许安装 `cargo-audit` / `gitleaks` 等工具。
|
||||
- 尚未执行真实扫描,因此当前没有漏洞结论;执行后需要生成正式报告。
|
||||
- 如果用户希望“检查当前项目”包含远端仓库历史 secrets、Docker 镜像、Jenkins 凭据和生产运行时配置,需要另行确认访问范围和凭据边界。
|
||||
@@ -16,6 +16,14 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-11 拼图与抓大鹅结果页音频资产复用通用创作音频链路
|
||||
|
||||
- 背景:拼图和抓大鹅结果页需要接入 Suno 背景音乐,抓大鹅还需要物体点击音效,但当前两类作品没有独立的作品级音频表或 metadata 字段。
|
||||
- 决策:新增 `/api/creation/audio/*` 通用创作音频路由,后端统一负责 VectorEngine 音频任务、OSS 转存、`asset_object` 与 `asset_entity_binding` 写入;视觉小说旧路由保留并复用同一持久化逻辑。拼图背景音乐暂存到首关 `levels_json[0].backgroundMusic/background_music`;抓大鹅背景音乐暂存到 `generated_item_assets_json[0].backgroundMusic/background_music`,单物体点击音效存到对应 item 的 `clickSound/click_sound`。本轮不新增 SpacetimeDB 表和字段。
|
||||
- 影响范围:拼图结果页、抓大鹅结果页、抓大鹅运行态音频播放、通用创作音频 shared contracts、`api-server` 音频路由和资产绑定。
|
||||
- 验证方式:执行拼图/抓大鹅结果页定向测试、`npm run typecheck`、`cargo test -p api-server vector_engine_audio_generation`、`cargo test -p shared-contracts creation_audio`、`cargo check -p api-server`,真实生成需配置 VectorEngine 与 OSS 私密环境。
|
||||
- 关联文档:`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`。
|
||||
|
||||
## 2026-05-10 儿童动作 Demo 视觉资产统一为绘本草地舞台
|
||||
|
||||
- 背景:儿童动作 Demo 需要从暗色科技风切换到更适合儿童互动的卡通绘本草地风格,并且要预留 image-2 真实背景图的固定接入位。
|
||||
@@ -24,6 +32,14 @@
|
||||
- 验证方式:检查 `/child-motion-demo` 舞台是否在未生成资产时仍有可用草地绘本兜底;补齐 VectorEngine 私密配置后运行 `npm run assets:child-motion-demo -- --live` 应能写出默认背景文件。
|
||||
- 关联文档:`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`、`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`。
|
||||
|
||||
## 2026-05-10 方洞挑战从创作页入口和作品架隐藏
|
||||
|
||||
- 背景:运营节奏要求创作页完全隐藏方洞挑战,不能只隐藏新建入口后仍从创作页作品架暴露已有方洞草稿或已发布作品。
|
||||
- 决策:SpacetimeDB `creation_entry_type_config` 中 `square-hole.visible=false` 作为创作页统一开关;创作 Tab 模板入口、旧选择弹层、创作 Hub 卡带和创作页作品架都基于该开关隐藏方洞挑战。既有方洞详情、作品号、广场和运行态链路暂不删除,api-server 路由熔断只按 `open=false` 禁用玩法 API。
|
||||
- 影响范围:SpacetimeDB 入口配置默认种子、`platformEntryCreationTypes`、`CustomWorldCreationHub`、`PlatformEntryFlowShellImpl` 以及创作入口相关文档和回归测试。
|
||||
- 验证方式:执行入口配置、创作 Hub 和平台入口交互定向测试,确认看不到“方洞挑战” Tab、按钮和作品架条目。
|
||||
- 关联文档:`docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md`、`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`。
|
||||
|
||||
## 2026-05-10 运行态输入设备抽象层全项目通用化
|
||||
|
||||
- 背景:拼图运行态接入 mocap 后,鼠标/触控和 mocap 各自维护输入逻辑会导致合并大块、拖拽语义和取消会话行为不一致;后续其他玩法也需要复用体感、摇杆、键盘等设备输入。
|
||||
@@ -32,6 +48,14 @@
|
||||
- 验证方式:执行 `npm run test -- src\services\input-devices\runtimeDragInputController.test.ts`、`npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx`、`npm run typecheck` 和编码检查。
|
||||
- 关联文档:`docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md`、`docs/technical/PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md`。
|
||||
|
||||
## 2026-05-11 前端调试模式统一判断
|
||||
|
||||
- 背景:拼图 mocap 调试面板此前在运行态常驻展示,生产构建和正式体验里容易遮挡棋盘内容;后续其它局部诊断 UI 也需要统一的调试模式入口。
|
||||
- 决策:前端新增 `src/config/debugMode.ts` 作为全局调试模式判断,默认跟随 Vite 开发态,允许 `VITE_DEBUG_MODE=true/false` 显式覆盖。拼图运行态 mocap 调试面板只在调试模式下渲染,并默认折叠,只保留连接状态行。
|
||||
- 影响范围:前端局部调试 UI、拼图运行态 mocap 诊断面板、`.env.example` 和运行态输入技术文档。
|
||||
- 验证方式:执行 `npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx`、`npm run typecheck` 和编码检查。
|
||||
- 关联文档:`docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md`。
|
||||
|
||||
## 2026-05-10 儿童动作热身关直接消费 mocap 数据源
|
||||
|
||||
- 背景:儿童动作 Demo 不能只依赖浏览器摄像头状态和键鼠调试输入,否则真实硬件接入后会出现“mocap 在线但页面提示摄像头不可用”或“能看到画面但动作不推进”的卡点。
|
||||
@@ -110,7 +134,7 @@
|
||||
|
||||
- 背景:平台计划新增 2048 游戏玩法模板,需要同时适配前端 stage、HTTP 路由、Rust 模块、SpacetimeDB 表和公开作品号;裸 `2048` 不适合作为模块或文件命名前缀。
|
||||
- 决策:面向用户展示名保持 `2048`,工程玩法 ID 固定为 `twenty-forty-eight`,Rust 模块与表前缀使用 `twenty_forty_eight`,公开作品号前缀使用 `TF-`;玩法按完整闭环设计,包含 Agent 创作、结果页、试玩、发布、公开运行、后端棋盘裁决、排行榜和作品架 / 广场接入。
|
||||
- 影响范围:后续 `src/config/newWorkEntryConfig.ts`、平台 `SelectionStage`、前端 `twenty-forty-eight-*` 组件与 service、`module-twenty-forty-eight`、`shared-contracts`、`spacetime-module` 表、`spacetime-client` facade、`api-server` 路由、作品号和 PRD 索引。
|
||||
- 影响范围:后续 SpacetimeDB 创作入口配置、平台 `SelectionStage`、前端 `twenty-forty-eight-*` 组件与 service、`module-twenty-forty-eight`、`shared-contracts`、`spacetime-module` 表、`spacetime-client` facade、`api-server` 路由、作品号和 PRD 索引。
|
||||
- 验证方式:后续落地时确认用户可见标题为 `2048`,代码、路由和表统一使用 `twenty-forty-eight` / `twenty_forty_eight`;移动、合并、生成新方块、目标达成、失败和榜单成绩由后端正式裁决,前端不伪造分数或目标达成。
|
||||
- 关联文档:`docs/prd/AI_NATIVE_2048_GAMEPLAY_TEMPLATE_PRD_2026-05-05.md`。
|
||||
|
||||
@@ -213,10 +237,26 @@
|
||||
## 2026-05-10 视觉小说入口收敛为单句创作 + 画风选择
|
||||
|
||||
- 背景:视觉小说入口页要对齐抓大鹅式的线性创作入口,只保留最小可用输入,避免再暴露文档 / 空白 / 对话式工作台。
|
||||
- 决策:入口页只展示一句话创作输入框和横向视觉画风卡片;画风通过 `seedText` 追加 `视觉画风` 和 `画风要求` 两行透传给既有创作链路;点击生成后先进入 `visual-novel-generating` 过程页,再自动进入 `visual-novel-result`。
|
||||
- 影响范围:`VisualNovelAgentWorkspace`、`PlatformEntryFlowShellImpl`、`platformEntryTypes`、视觉小说 PRD;不新增后端字段或数据库结构。
|
||||
- 验证方式:视觉小说工作台单测通过,`npm run check:encoding` 通过;`npm run typecheck` 仍受仓库里 `src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx` 的既有类型错误影响。
|
||||
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`。
|
||||
- 决策:入口页只展示一句话创作输入框和横向视觉画风卡片;画风通过 `seedText` 追加 `视觉画风` 和 `画风要求` 两行透传给既有创作链路;点击生成后先进入 `visual-novel-generating` 过程页,再自动进入 `visual-novel-result`。画风卡片主视觉固定消费 `public/visual-novel-style-references/` 下由 VectorEngine `gpt-image-2-all` 生成的静态参考图,不在前端运行时现场调用生图接口。
|
||||
- 影响范围:`VisualNovelAgentWorkspace`、`visualNovelEntryGeneration`、`PlatformEntryFlowShellImpl`、视觉小说 PRD 和创作 Tab 设计文档;不新增后端字段或数据库结构。
|
||||
- 验证方式:执行 `npm run test -- VisualNovelAgentWorkspace`、视觉小说工作台相关 ESLint、`npx prettier --check` 和 `npm run check:encoding`;`npm run typecheck` 若失败需先区分是否来自无关 Match3D / RPG 既有改动。
|
||||
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`。
|
||||
|
||||
## 2026-05-10 用户标签只做后端白名单投影
|
||||
|
||||
- 背景:运营邀请码需要给账号打标签,但标签默认不能暴露到前端通用用户资料;拼图排行榜仅需展示特定标签。
|
||||
- 决策:`user_account.user_tags` 保存账号标签,数据库默认 `None`,业务按空数组读取;后台预置邀请码使用后授予的标签不再使用独立列,统一存放并解析自 `profile_invite_code.metadata_json.userTags`,兼容读取 `user_tags`。通用登录态和个人资料不返回原始标签。首版只在拼图排行榜 `visibleTags` 中白名单投影 `北科`。
|
||||
- 影响范围:用户认证表、邀请码后台、邀请兑换事务、拼图排行榜响应和 UI。
|
||||
- 验证方式:表结构变更需同步 `migration.rs`、`SPACETIMEDB_TABLE_CATALOG.md` 和 SpacetimeDB bindings;后端运行 `cargo check -p api-server`,后台运行 `npm run admin-web:typecheck`。
|
||||
- 关联文档:`docs/technical/USER_TAG_INVITE_AND_PUZZLE_LEADERBOARD_2026-05-10.md`。
|
||||
|
||||
## 2026-05-10 抓大鹅草稿元信息由 gpt-4o 生成
|
||||
|
||||
- 背景:抓大鹅草稿生成需要基于入口题材设定生成作品名称,结果页作品信息要对齐拼图草稿,不再把封面和作品名称拆成两个模块。
|
||||
- 决策:`match3d_compile_draft` 使用 `gpt-4o` 生成 `gameName` 与 3 到 6 个标签;`summary` 默认保持空字符串;标签可由结果页 `作品信息` Tab 手动编辑或再次 AI 生成。草稿生成会产出 3 个物品图片并立即调用 Rodin 生成 GLB,图片和模型一起写入 `generated_item_assets_json`,运行态必须优先消费 `generatedItemAssets[].modelSrc` / `modelObjectKey`,默认积木只做兜底。
|
||||
- 影响范围:`api-server` Match3D 编译、Match3D works 标签接口、结果页 `作品信息` 与 `3D素材` Tab、运行态 `Match3DRuntimeShell` / `Match3DPhysicsBoard`、生成进度和 Match3D 技术文档。
|
||||
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts`、`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`,并用 `npm run api-server` 检查 `/healthz`。
|
||||
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`、`docs/technical/MATCH3D_RODIN_ASSET_TAB_2026-05-10.md`。
|
||||
|
||||
## 2026-05-07 移动端整页缩放由入口统一锁定
|
||||
|
||||
|
||||
@@ -155,6 +155,14 @@
|
||||
- 验证:重新发布日志应显示创建新的数据库,而不是更新旧数据库;若仍显示更新或继续 `401`,继续检查数据目录、库名和 CLI 身份。
|
||||
- 关联:`docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md`、`docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`。
|
||||
|
||||
## SpacetimeDB 模块 publish 报 `wasm-bindgen detected`
|
||||
|
||||
- 现象:`spacetime publish` 已经完成 Rust 编译,但随后报 `wasm-bindgen detected`,提示依赖树里有面向 Web 平台的 wasm-bindgen。
|
||||
- 原因:SpacetimeDB 模块是数据库内 WASM,不允许拉入 Web/HTTP client 链路;常见误因是 `spacetime-module -> module-* -> shared-contracts -> platform-* -> reqwest -> wasm-bindgen` 这类反向依赖。
|
||||
- 处理:执行 `cargo tree -i wasm-bindgen --manifest-path server-rs/Cargo.toml -p spacetime-module --target wasm32-unknown-unknown` 找到链路;把平台实现类型从 `shared-contracts` 或 `module-*` 中移除,只保留公开 DTO,平台响应到 DTO 的转换放回 `api-server` 等 adapter 层。
|
||||
- 验证:上述 `cargo tree` 输出 `warning: nothing to print`;`cargo check -p shared-contracts`、`cargo check -p api-server` 通过;重新 `spacetime publish ... --module-path server-rs/crates/spacetime-module` 不再报 wasm-bindgen。
|
||||
- 关联:`docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md`、`server-rs/crates/shared-contracts/src/assets.rs`、`server-rs/crates/api-server/src/assets.rs`。
|
||||
|
||||
## Vite SPA fallback 吞掉 API 请求
|
||||
|
||||
- 现象:本地请求 `/api/profile/*` 等接口时返回 HTML,被前端当 JSON 解析报错。
|
||||
@@ -383,10 +391,10 @@
|
||||
|
||||
## Rust 构建不要让不可用的 sccache 阻断 rustc
|
||||
|
||||
- 现象:Cargo 报 `could not execute process sccache ... rustc.exe -vV (never executed)`,或 `sccache: caused by: Failed to send data to or receive data from server / Failed to read response header / failed to fill whole buffer`;真实 `rustc -Vv` 可以执行,但构建在调用包装器时失败。
|
||||
- 原因:环境、Jenkinsfile 或 `server-rs/.cargo/config.toml` 启用了 `sccache` wrapper,但当前 agent 没有可执行的 `sccache`、PATH 中 shim 损坏,或本地 sccache server/client 通道状态损坏。
|
||||
- 处理:本地临时排障可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`;`npm run dev:rust` 的 SpacetimeDB publish 已在命中 sccache 通信失败时自动清空 wrapper 重试一次;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`。
|
||||
- 验证:`rustc -Vv` 能输出版本;清空 wrapper 后 `cargo check --target=wasm32-unknown-unknown --release` 能通过;Jenkins 日志出现“未找到可用 sccache,改用 rustc 直接构建”后仍继续真实构建。
|
||||
- 现象:Cargo 报 `could not execute process sccache ... rustc.exe -vV (never executed)`、`sccache: error: Timed out waiting for server startup`,或 `sccache: caused by: Failed to send data to or receive data from server / Failed to read response header / failed to fill whole buffer`;真实 `rustc -Vv` 可以执行,但构建在调用包装器时失败。
|
||||
- 原因:环境、Jenkinsfile 或 `server-rs/.cargo/config.toml` 启用了 `sccache` wrapper,但当前 agent 没有可执行的 `sccache`、PATH 中 shim 损坏,或本地 sccache server/client 通道状态损坏。Windows 本机若配置了 `SCCACHE_OSS_*`,sccache daemon 冷启动会先经 OSS/本机代理完成缓存读写检查,再监听 `127.0.0.1:4226`;代理或 OSS 链路慢时,Cargo 的 `sccache rustc -vV` 可能先超时。
|
||||
- 处理:保留 `server-rs/.cargo/config.toml` 的 `rustc-wrapper = "sccache"`;Windows 本机优先在 `%APPDATA%\Mozilla\sccache\config\config` 写入 `server_startup_timeout_ms = 60000`,拉长 client 等待 daemon 完成 OSS 初始化的时间,然后删除 `server-rs/target/.rustc_info.json` 里缓存的失败探测结果并重跑原始 Cargo 命令。冷启动验证优先用 `sccache --stop-server`,不要在另一个 `cargo` / `rustc` 仍在编译时 `taskkill /F /IM sccache.exe /T`,否则 proc-macro crate 可能被打断并表现为 `serde_derive` / `spacetimedb-bindings-macro` 的 `sccache ... exit code: 1`。若只做临时排障,可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`,或在 PowerShell 用 `cargo check -p api-server --config "build.rustc-wrapper=''"` 一次性绕过 wrapper;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`。
|
||||
- 验证:`rustc -Vv` 能输出版本;冷启动后原始 `cargo check -p api-server` 和 `cargo check -p spacetime-module` 能通过;`sccache --show-stats` 显示 `Cache location oss, name: genarrative-sccache`,证明仍在使用 sccache/OSS 缓存;Jenkins 日志出现“未找到可用 sccache,改用 rustc 直接构建”后仍继续真实构建。
|
||||
- 关联:`scripts/dev-rust-stack.sh`、`jenkins/Jenkinsfile.production-stdb-module-build`、`docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md`、`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
||||
|
||||
## 生产发布入口不要沿用旧 Jenkinsfile / 一体化脚本
|
||||
@@ -437,13 +445,21 @@
|
||||
- 验证:`cargo test -p api-server accepts_opaque_subscription_key_without_length_cap --manifest-path server-rs/Cargo.toml`。
|
||||
- 关联:`server-rs/crates/api-server/src/hyper3d_generation.rs`、`docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md`。
|
||||
|
||||
## 抓大鹅草稿生成不要阻塞在 Rodin 模型下载
|
||||
## 抓大鹅草稿生成恢复 Rodin 后要并行生成模型、同步长超时和 GLB 私有读取
|
||||
|
||||
- 现象:抓大鹅草稿生成时 Hyper3D 状态已完成,但下载列表为空或没有可用模型文件,`/api/creation/match3d/sessions/{sessionId}/actions` 返回 `502 Bad Gateway`,前端提示 `Hyper3D 已完成但未返回可下载模型文件`。
|
||||
- 原因:草稿生成链路曾在切割图片后立即并行调用 Rodin 图生模型,并把模型下载成功作为草稿完成前置条件;上游完成态和可下载文件列表不是强一致,容易把本来可用的图片草稿卡死。
|
||||
- 处理:草稿阶段只生成物品名、素材图、切割独立图片并上传 OSS,返回 `status = image_ready`;Rodin 3D 模型生成留到结果页 `3D素材` Tab 手动触发。
|
||||
- 验证:草稿响应中的 `generatedItemAssets[].imageSrc` 有值、`modelSrc` 为空、状态为 `image_ready`;结果页显示 `图片已就绪` 和 `0 文件`,不会自动请求 Hyper3D 下载。
|
||||
- 关联:`server-rs/crates/api-server/src/match3d.rs`、`src/components/match3d-result/Match3DResultView.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
- 现象:抓大鹅草稿生成重新接回 Rodin 后,前端可能在模型轮询和 GLB 转存完成前超时;或 Hyper3D 控制台显示 3 个任务已完成,但草稿进度页仍停留在 `生成3D模型`;或结果页把 `/generated-match3d-assets/.../model.glb` 直接当浏览器 URL 加载导致私有 OSS/CORS 读取失败。
|
||||
- 原因:`match3d_compile_draft` 会完成作品元信息、素材图、切图上传、3 件 Rodin 图生模型、GLB 下载和 OSS 转存,耗时远长于普通 Agent action;如果 3 件 Rodin 模型逐个提交和轮询,等待时间会线性叠加;同时 generated 私有资产不能被 Three.js 直接 `fetch`。
|
||||
- 处理:切图和图片入库后,所有 Rodin 图生模型任务必须并行提交、并行轮询、并行下载转存;Match3D creation client 的 `executeAction` 必须给长超时,当前为 20 分钟;生成进度页要包含 `生成3D模型` 阶段,但它不是 Hyper3D task 订阅页,而是在长 action 执行期间旁路轮询 session / work detail,并用 profile 的 `generatedItemAssets` 更新完成数量;控制台看到 Rodin `Done` 后仍需等待下载列表、GLB 下载、OSS 转存和草稿 JSON 写回。结果页模型预览、场内运行态和备选栏预览都必须通过 `/api/assets/read-bytes` 读取 GLB 字节后交给 Three.js GLTFLoader,不要直接请求裸 generated 路径。排查时按同一个 session/profile 查看 api-server 日志:`抓大鹅 Rodin 状态轮询返回`、`抓大鹅 Rodin 下载列表轮询返回`、`抓大鹅 Rodin GLB 下载完成`、`抓大鹅 Rodin GLB 转存 OSS 完成`;同时检查前端 work detail 响应里的 `generatedItemAssets[].status/modelObjectKey/error`。
|
||||
- 验证:`npm run test -- src\services\miniGameDraftGenerationProgress.test.ts src\components\match3d-result\Match3DResultView.test.tsx src\components\match3d-runtime\Match3DRuntimeShell.test.tsx`、`npm run typecheck`;真实联调需配置 `VECTOR_ENGINE_API_KEY`、`HYPER3D_API_KEY` 和 OSS 变量。
|
||||
- 关联:`src/services/match3d-creation/match3dCreationClient.ts`、`src/services/creation-agent/creationAgentClientFactory.ts`、`src/components/match3d-result/Match3DModelPreview.tsx`、`src/components/match3d-runtime/Match3DPhysicsBoard.tsx`、`server-rs/crates/api-server/src/match3d.rs`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
|
||||
## Rodin 完成态后下载列表可能延迟或字段漂移
|
||||
|
||||
- 现象:抓大鹅草稿生成时 Rodin 状态已完成,但 `/api/creation/match3d/sessions/{sessionId}/actions` 返回 502,提示 `{物品名} 3D 模型已完成但未返回可下载模型文件:{taskUuid}`。
|
||||
- 原因:Hyper3D Rodin 官方 `check-status_reset_v` 示例要求看 `jobs` 列表,只有所有 job 都 `Done` 才能进入下载;`download-results_reset_v` 还明确要求用生成响应顶层 `uuid` 作为 `task_uuid`,不要用 `jobs.uuids` 子任务 uuid。旧聚合若只看 root status 或第一个 job,可能在 preview job 完成但模型 job 仍在生成时提前下载。另外任务完成和下载列表文件发布不是强同步的;上游下载结果还可能使用 `fileUrl`、`signedUrl`、`presignedUrl`、`fileName` 等字段别名,旧解析器只识别 `url/downloadUrl/name/file_name/filename` 时会得到空列表。
|
||||
- 处理:`query_task_status` 聚合状态必须以 `jobs` 为准:任一 failed 即 failed,全部 done 才 done;`match3d_compile_draft` 在状态完成后对 `query_downloads` 继续轮询;下载解析兼容常见 URL 和文件名字段别名;模型选择优先 `.glb`,可兜底到非图片下载文件,但只有 preview/png/jpg/webp 这类预览图时必须继续失败,不能伪装成 GLB。
|
||||
- 验证:`cargo test -p api-server match3d_model_download --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server extracts_download_files --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server hyper3d --manifest-path server-rs/Cargo.toml`。
|
||||
- 关联:`server-rs/crates/api-server/src/match3d.rs`、`server-rs/crates/api-server/src/hyper3d_generation.rs`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
|
||||
## 抓大鹅切图路径不能只用中文物品名
|
||||
|
||||
@@ -460,3 +476,35 @@
|
||||
- 处理:compile 成功时把独立物品图片列表序列化写入 `match3d_work_profile.generated_item_assets_json`;`update_match3d_work` / `publish_match3d_work` 保留该字段;API work summary/detail 映射反序列化为 `generatedItemAssets`。前端保持“本次 draft 优先,重进 profile 兜底”的读取顺序。
|
||||
- 验证:`cargo test -p spacetime-client match3d --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`。
|
||||
- 关联:`server-rs/crates/spacetime-module/src/match3d/*`、`server-rs/crates/spacetime-client/src/mapper.rs`、`server-rs/crates/api-server/src/match3d.rs`、`src/components/match3d-result/Match3DResultView.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
|
||||
## 抓大鹅试玩和正式运行态不要只读草稿页本地模型预览
|
||||
|
||||
- 现象:历史草稿页 `3D素材` Tab 能看到水果模型,但点击试玩或从推荐 / 公开作品进入正式抓大鹅时,局内仍显示默认积木素材。
|
||||
- 原因:结果页手动 `重新生成` 曾只更新本地 `assetDrafts.downloads`,没有把新的 GLB 写回 `generatedItemAssets`;历史数据还可能只有 `modelObjectKey` 而没有 `modelSrc`;推荐流内嵌运行态若只读卡片摘要,卡片缺素材时会把已持久化 profile 模型丢掉;本次生成 response 的 draft 也可能比 profile 旧,只带图片而不带模型。
|
||||
- 处理:结果页模型预览和运行态都按 `modelSrc || modelObjectKey` 读取;手动重新生成成功后把素材草稿重新序列化并写回作品 profile;`Match3DResultView` 合并同 `itemId` 的 draft/profile 素材,用 profile 已有模型补齐旧 draft;推荐流内嵌运行态启动前若卡片摘要没有生成素材,补读 `getMatch3DWorkDetail(profileId)` 并把详情资产传给 `Match3DRuntimeShell`。
|
||||
- 验证:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`,并检查历史草稿和公开 M3 作品的 Network 响应里 `generatedItemAssets[].modelSrc/modelObjectKey`。
|
||||
- 关联:`src/components/match3d-result/Match3DResultView.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/match3d-runtime/Match3DPhysicsBoard.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
|
||||
## 抓大鹅标签清洗不要把 `3D素材` 当编号剥掉
|
||||
|
||||
- 现象:AI 或兜底生成的 `3D素材` 标签在后端规范化后变成 `D素材`。
|
||||
- 原因:标签清洗在去掉编号列表前缀后,又无条件剥离开头数字和标点,把合法标签中的 `3D` 当成列表编号处理。
|
||||
- 处理:只移除明确的编号列表前缀,例如 `1. 标签`、`1、标签`、`1) 标签`;不要对普通标签开头数字做二次剥离。
|
||||
- 验证:`cargo test -p api-server match3d_tag_normalization --manifest-path server-rs/Cargo.toml`,并保留 `normalize_match3d_tag("3D素材") == "3D素材"` 的单测。
|
||||
- 关联:`server-rs/crates/api-server/src/match3d.rs`。
|
||||
|
||||
## 用户标签不要直接外显,SpacetimeDB Vec 字段不要写 default 宏
|
||||
|
||||
- 现象:给 `user_account.user_tags` 或邀请码独立标签列写 `#[default(Vec::<String>::new())]` 时,SpacetimeDB WASM 构建报 `destructor of Vec<String> cannot be evaluated at compile-time`。
|
||||
- 原因:SpacetimeDB 的 table default 宏会走编译期常量求值,不能直接使用有析构逻辑的堆分配类型默认值。
|
||||
- 处理:`user_account.user_tags` 使用 `Option<Vec<String>>` + `#[default(None::<Vec<String>>)]` 表达数据库默认空,业务层统一把 `None` 归一化为空数组;邀请码授予标签复用 `metadata_json.userTags` 存储和解析,不再新增独立 Vec 列。用户标签原始值不得进入登录态、个人资料等通用响应,只能在明确业务白名单里投影,例如拼图排行榜 `visibleTags` 首版仅允许 `北科`。
|
||||
- 验证:`npm run spacetime:generate -- --rust-only` 能通过;`user_account` 旧迁移 JSON 缺字段时能导入,`profile_invite_code` 缺 `metadata_json` 时按 `{}` 兼容。
|
||||
- 关联:`docs/technical/USER_TAG_INVITE_AND_PUZZLE_LEADERBOARD_2026-05-10.md`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`。
|
||||
|
||||
## 公开作品详情深链找不到作品不能停在空详情页
|
||||
|
||||
- 现象:直接访问 `/works/detail?work=PZ-...`,作品不存在或已下架时会弹出“作品不存在或已下架,将返回首页。”;关闭提示后仍可能停在大白屏。
|
||||
- 原因:旧恢复逻辑只覆盖 `/runtime/...`,没有覆盖 `/works/detail`。同时 `selectionStage === 'work-detail'` 且 `selectedPublicWorkDetail === null` 时没有兜底渲染,详情数据为空就只剩空页面。
|
||||
- 处理:公开详情失效统一走 `resolveWorkNotFoundRecoveryAction(...)`,覆盖 `/works/detail`、`/gallery/puzzle/detail` 和 `/gallery/visual-novel/detail`;搜索失败和拼图详情 404 分支清理详情/运行态临时状态并回首页;`work-detail` 空数据阶段显示轻量读取态,避免异步间隙白屏。
|
||||
- 验证:`npm run test -- src/routing/runtimeNotFoundRecovery.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct missing public work detail alert returns to platform home"`。
|
||||
- 关联:`docs/technical/PUBLIC_WORK_DETAIL_NOT_FOUND_RECOVERY_2026-05-11.md`、`src/routing/runtimeNotFoundRecovery.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`。
|
||||
|
||||
Reference in New Issue
Block a user