Files
Genarrative/.hermes/shared-memory/pitfalls.md

54 KiB
Raw Blame History

踩坑与排障记录

用途:记录已验证、未来很可能再次遇到的问题。每条都应包含现象、原因、处理方式和验证方式。

记录格式

## 问题标题

- 现象:看到什么错误或异常行为
- 原因:确认后的根因
- 处理:具体修复步骤
- 验证:如何确认修复有效
- 关联:相关文件、文档、提交或 Issue

OSS V4 签名时间和 bucket/object_key 兼容

  • 现象OSS V4 私有读签名在部分时间点失败,可能出现 OSS V4 签名时间格式化失败 或服务端判定签名格式错误;排查用例中 bucket 为 xushi-devobject_key 为 generated-square-hole-assets/.../image.png
  • 原因:旧逻辑依赖 time::Time::to_string() 再去掉冒号,小时小于 10 时输出不稳定补零;同时排查时容易把 bucket 名误当成 object_key 的一部分。
  • 处理OSS V4 x-oss-date 使用固定宽度 yyyyMMdd'T'HHmmss'Z' 格式化;调用读签名或 HEAD Object 时只传 object_key不要传 bucket/object_key 拼接路径。
  • 验证:运行 cd server-rs && cargo test -p platform-oss -- --nocapture,并用 bucket=xushi-dev、object_key=generated-square-hole-assets/square-hole-session-546d881972684be2980a2a882cd0cc71/square-hole-profile-134411276ce1469cbe398f946a25d7f8/square-hole-shape-image/rabbit-option/asset-1777979289912039/image.png 覆盖签名生成。
  • 关联:server-rs/crates/platform-oss/src/lib.rsserver-rs/crates/platform-oss/README.md

中文乱码与编码风险

  • 现象:中文文案、注释、剧情或文档显示为乱码,或被改写成英文。
  • 原因Windows/PowerShell/终端编码不一致,或整文件重写导致编码变化。
  • 处理:
    • 不要直接沿用乱码文本。
    • 不要用英文替换中文,除非用户明确要求翻译。
    • 在 PowerShell 5.1 中显式使用 UTF-8。
    • 优先用 Python/Node 或 Get-Content -Encoding UTF8 核对原文。
    • 修改中文文件时优先局部补丁,避免无关内容重写。
  • 验证:运行仓库已有编码检查;人工抽查修改文件中的中文内容。
  • 关联:AGENTS.mdnpm run check:encoding

.hermes 只放共享内容,不放个人 Hermes 配置

  • 现象:团队成员误把个人 Hermes 配置、会话或密钥复制进仓库。
  • 原因:仓库 .hermes/ 与个人 ~/.hermes/ 名称相似。
  • 处理:仓库 .hermes/ 只放 Markdown 共享记忆、计划和可公开 skills不提交 .envconfig.yamlsessions/auth.json
  • 验证:提交前检查 git diff -- .hermes,确认没有密钥、会话记录或个人路径敏感信息。
  • 关联:.hermes/README.md

儿童动作 Demo 挥手阶段不推进先查 mocap 消费链路

  • 现象:/child-motion-demo 能打开摄像头画面,但到“打个招呼”或左右手挥动阶段时,真实硬件动作无法检测通过,只能用鼠标拖拽或键盘调试继续。
  • 原因:摄像头视频流只是舞台背景;如果热身关没有消费 useMocapInput 的动作名和手部坐标,就不会把硬件动作转换成热身状态机完成事件。
  • 处理:确认 src/components/child-motion-demo/ChildMotionWarmupDemo.tsxstep.kind === 'gesture' 时启用 useMocapInput;确认 src/services/useMocapInput.ts 能解析 /stream 包里的 actions/action/gesture/gestures/event/name/typehands[]leftHand/rightHandleft_hand/right_hand、左右手标记和 open_palm/grab 状态。热身关应由 mocap 推进,键鼠只作为本地调试兜底。
  • 验证:运行 npx vitest run src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx,并在本地硬件服务启动后进入 /child-motion-demo 实测招手、左右手挥动和跳跃阶段。
  • 关联:src/services/useMocapInput.tssrc/components/child-motion-demo/ChildMotionWarmupDemo.tsxdocs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md

GPT-image-2 不再读 APIMart 图片配置

  • 现象:配置了 APIMART_BASE_URL / APIMART_API_KEYRPG、拼图或方洞的 GPT-image-2 生图仍返回缺配置,或请求体里还出现 official_fallback / image_urls
  • 原因2026-05-09 后 GPT-image-2 图片生成已切到 VectorEngine gpt-image-2-allAPIMart 只保留给创意 Agent 的 gpt-5 Responses 文本/多模态链路。
  • 处理:为图片生成配置 VECTOR_ENGINE_BASE_URL=https://api.vectorengine.aiVECTOR_ENGINE_API_KEYVECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS;排查请求体时确认路径为 /v1/images/generations、模型为 gpt-image-2-all、参考图字段为 image
  • 验证:运行 cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml 和相关玩法图片生成测试;真实联调只在本地私密环境放置 VectorEngine key。
  • 关联:docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.mdserver-rs/crates/api-server/src/openai_image_generation.rs

拼图参考图没有影响生成时先查 action payload 和阶段日志

  • 现象:拼图上传参考图后生成出的画面明显不像参考图,或结果页重新生成没有按保存的参考图走图生图。
  • 原因:首图生成只通过 compile_puzzle_draft.referenceImageSrc 临时传 Data URL不持久化到 SpacetimeDB结果页重新生成则要把当前上传图或关卡 pictureReference 作为 generate_puzzle_images.referenceImageSrc 继续传给后端。
  • 处理:浏览器 Network 里确认 action payload 带 referenceImageSrcapi-server 日志按同一 session_id 查看 拼图参考图解析完成拼图 VectorEngine 图片生成 HTTP 返回拼图 VectorEngine 图片下载完成拼图生成图片已写入 OSS 与资产索引可定位慢在参考图读取、VectorEngine、下载或 OSS。
  • 验证:前端测试覆盖上传图 + AI 重绘、结果页保存的 pictureReference 重新生成;后端单测覆盖 VectorEngine 请求体 image 字段。
  • 关联:src/components/puzzle-agent/PuzzleAgentWorkspace.tsxsrc/components/puzzle-result/PuzzleResultView.tsxserver-rs/crates/api-server/src/puzzle.rs

拼图首图生成后要把入口参考图写回 pictureReference

  • 现象:入口页上传图后,首图看着像没吃到参考图;结果页重新生成时默认只沿用关卡旧图,没有继续带入口上传图。
  • 原因:首图生成请求虽然已经把 referenceImageSrc 传给 VectorEngine但如果后端只更新 cover_image_src / selected_candidate_id 而不回写首关 pictureReference,结果页后续重绘就会丢失参考图。
  • 处理:在 compile_puzzle_draftgenerate_puzzle_images 的成功与 SpacetimeDB 降级快照路径里,都把本次入口参考图写入首关 pictureReference
  • 验证:后端单测覆盖 build_puzzle_levels_with_primary_updateapply_generated_puzzle_candidates_to_session_snapshot;结果页重新生成应在未重新上传时继续带入 level.pictureReference
  • 关联:server-rs/crates/api-server/src/puzzle.rssrc/components/puzzle-result/PuzzleResultView.tsx

拼图图生图仍不像参考图时先看是否走了 edits

  • 现象Network payload 已带 referenceImageSrc,但 VectorEngine 生成结果仍明显不像上传图。
  • 原因:gpt-image-2-all/v1/images/generations 更适合纯文生图;有参考图且需要重绘时应切到 /v1/images/edits 的 multipart 图生图接口。
  • 处理:referenceImageSrc 存在且 aiRedraw = true 时直接走 editsprompt 仍保留参考图强约束;入口页关闭 AI 重绘时直接应用上传图,不调用图片生成;前端把参考图压到单边 1024 内,后端解析后拒绝超过 8MB 的参考图字节。
  • 验证:后端单测应覆盖 images/edits 路由、b64_json 响应解码和参考图强提示;真实联调先看日志里是否命中 拼图 VectorEngine 图片编辑 HTTP 返回
  • 关联:server-rs/crates/api-server/src/puzzle.rssrc/services/puzzleReferenceImage.tsdocs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md

旧后端路线文档造成判断漂移

  • 现象:开发时参考到 Express、Node、PostgreSQL 或 Go 方向旧文档,导致接口、数据真相或部署路径与当前主线不一致。
  • 原因:项目历史文档较多,部分旧方案仍保留作迁移参考。
  • 处理涉及服务端、数据真相、SpacetimeDB、运行时状态时先看 CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md,再看 DDD 总纲和具体技术方案。
  • 验证:代码改动应落在 server-rs + Axum + SpacetimeDB 主线;旧路线只作为迁移参考,不作为兼容目标。
  • 关联:docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.mdAGENTS.md

SpacetimeDB 表结构变更不能按 PostgreSQL 迁移直觉处理

  • 现象:发布时 schema 冲突、自动迁移拒绝、旧客户端调用 reducer 失败、private 表数据迁移遗漏。
  • 原因SpacetimeDB 对字段删除、类型变化、索引/主键/RLS/reducer 变化有不同自动迁移边界。
  • 处理:变更前阅读 SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md;涉及表变化时同步 migration.rsSPACETIMEDB_TABLE_CATALOG.md 和 bindings必要时走 JSON 导入导出与分片导入迁移流程。
  • 验证:发布前完成 schema 检查、bindings 生成、表目录更新和相关 smoke。
  • 关联:docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.mddocs/technical/SPACETIMEDB_TABLE_CATALOG.md

本地 SpacetimeDB replica identity 不匹配

  • 现象:本地 standalone 启动时报 mismatched database identity
  • 原因root-dir / replica 数据残留与当前数据库身份不一致。
  • 处理:按本地 replica identity mismatch 文档进行备份、重建和脚本诊断。
  • 验证:本地 SpacetimeDB 可正常启动并 publish / 访问。
  • 关联:docs/technical/SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md

本地 SpacetimeDB publish 403 优先查 CLI root 身份

  • 现象:spacetime publishPre-publish check 阶段返回 403 Forbidden,提示当前 identity 无权对目标 database identity 执行 update database
  • 原因:裸 spacetime 命令使用了全局 CLI 登录态,但本地开发库权限绑定在 server-rs/.spacetimedb/local root-dir 的另一个身份上。
  • 处理:对比 spacetime login showspacetime --root-dir server-rs/.spacetimedb/local login show;本地开发发布必须使用 npm run dev:rust,或显式追加 --root-dir=server-rs/.spacetimedb/local
  • 验证:spacetime --root-dir server-rs/.spacetimedb/local list --server http://127.0.0.1:3101 能看到目标库;重新发布不再使用无权限的全局 identity。
  • 关联:scripts/dev-rust-stack.shdocs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md

npm run dev 本地 SpacetimeDB 401 / 403 可重置默认 local 身份

  • 现象:npm run dev 启动本地开发栈时SpacetimeDB 在登录、发布或预检查阶段返回 401 / 403,清理后仍像在使用旧 token 或旧本地库。
  • 原因:本机 spacetime CLI 保存的旧 token、默认 server、正在运行的 standalone 进程或默认 local 数据库与当前发布身份不一致。
  • 处理:确认只是本地测试库且数据可丢弃后,先查看并停止本地 spacetimedb-standalone,执行 spacetime logout,确认并设置 spacetime server set-default local,停 server 后用 spacetime server clear -y 清空默认本地库,再 spacetime start,另开终端执行 spacetime login --server-issued-login local,最后用 spacetime publish --server local A 或项目脚本重新发布。
  • 验证:spacetime server list 默认目标为 local重新登录后发布不再返回 401 / 403npm run dev 可以完成 SpacetimeDB publish 并继续启动 api-server
  • 关联:docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.mdscripts/dev-rust-stack.sh

本地 SpacetimeDB 联调可按阶段跳过宿主或发布

  • 现象:本地 npm run dev3101 已占用、重复发布 SpacetimeDB wasm 编译太慢,或只想检查 spacetime-module 语法而被完整联调链路拖慢。
  • 原因:npm run dev 默认同时启动 SpacetimeDB standalone、发布 server-rs/crates/spacetime-module、启动 Rust api-server、主站 Vite 与后台 Vite并非每个阶段都需要完整重启和重新发布。
  • 处理:npm run dev 启动后会把实际 SpacetimeDB URL 记录到 server-rs/.spacetimedb/local/data/dev-rust-spacetime-url。下次启动即使没有传 --skip-spacetime,脚本也会先检查 spacetime.pid 对应进程和该 URL 是否在线;在线则直接复用现有宿主。确认需要新启动 SpacetimeDB 时,脚本先检测 3101,被占用则输出占用进程并选择最近可用端口,保证 publish 与 api-server 都连接同一个实际 SpacetimeDB URL。api-server 启动前也会检测 8082 并选择最近可用端口。Windows / Git Bash 下不要用 tr/head/xargs 管道读取 spacetime.pid 或 URL 记录,脚本应使用 Node 读取并短重试,避免 tr: read error: Device or resource busy;未修改 spacetime-module 时使用 npm run dev -- --skip-publish;只查模块语法时执行 cargo check -p spacetime-module --manifest-path server-rs/Cargo.tomlnpm run dev 会在启动前检查 SpacetimeDB、api-server、主站 Vite、后台 Vite 端口,不可用时自动寻找后续可用端口,并把实际端口传给 publish、后端环境变量和前端代理目标。
  • 验证:--skip-spacetime 后脚本复用现有 http://127.0.0.1:310131018082 被其他进程占用时,脚本输出占用进程并使用最近可用端口;--skip-publish 后不再进入 publish 阶段;cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml 能完成 Rust 语法和类型检查。端口漂移时控制台会打印 [dev:ports] ... 不可用,改用 ...,后续 [dev:rust] web/admin web/rust api/spacetime 地址应与实际端口一致。
  • 关联:docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.mdscripts/dev-rust-stack.sh

本地 SpacetimeDB publish 401 可清本地库重发

  • 现象:本地 spacetime publish 显示 401 无权限,或重新发布仍像是在更新旧库。
  • 原因:本地开发 root-dir 中保留的数据库、控制库身份或发布身份与当前目标不一致。
  • 处理:确认本地开发数据可以丢弃后,执行 spacetime --root-dir=server-rs/.spacetimedb/local server clear,再重新运行 npm run dev 或本地 publish。
  • 验证:重新发布日志应显示创建新的数据库,而不是更新旧数据库;若仍显示更新或继续 401,继续检查 root-dir、库名和 CLI 身份。
  • 关联:docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.mddocs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md

Vite SPA fallback 吞掉 API 请求

  • 现象:本地请求 /api/profile/* 等接口时返回 HTML被前端当 JSON 解析报错。
  • 原因Vite 代理缺少对应 /api/* 前缀API 请求落到 SPA fallback。
  • 处理:补齐 Vite 代理,让 API 请求转发到 Rust api-server
  • 验证:请求返回 JSON相关页面不再出现 HTML parse 错误。
  • 关联:docs/technical/PROFILE_MAIN_ROUTE_VITE_PROXY_FIX_2026-05-02.md

反馈页清空 file input 前必须先拷贝 FileList

  • 现象:点击上传凭证会打开文件选择框,但选择图片后页面没有展示预览,提交时也没有携带图片凭证。
  • 原因:浏览器传入的 FileList 可能跟 <input type="file"> 保持 live 绑定;如果先执行 input.value = '',再从参数里的 FileList 读取文件,列表可能已经为空。
  • 处理:在清空 file input 前先执行 const selectedFiles = files ? Array.from(files) : []后续图片类型、大小、Data URL 读取和预览都基于这个普通数组。
  • 验证:PlatformFeedbackView.test.tsx 用 mock FileReader 断言选择图片后出现 反馈凭证预览,且提交 payload 带 evidenceItems[].dataUrl
  • 关联:src/components/platform-entry/PlatformFeedbackView.tsxdocs/technical/PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md

拼图 VectorEngine 图片生成密钥不能复用 DashScope / ARK key

  • 现象:拼图新手引导或拼图创作点击生成后返回 VectorEngine 图片生成密钥未配置
  • 原因:拼图 gpt-image-2 / 历史 nanobanana2 图片生成已统一走 VectorEngine后端只读取 VECTOR_ENGINE_BASE_URLVECTOR_ENGINE_API_KEYVECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS,不会用 DASHSCOPE_API_KEYLLM_API_KEYARK_API_KEYAPIMART_API_KEY 兜底。
  • 处理:在本机私密配置 .env.secrets.local 或进程环境中配置真实 VECTOR_ENGINE_API_KEY,不要提交到 Git填入后必须重启 api-server / npm run dev,运行中的进程不会自动加载新 env。
  • 验证:不打印密钥内容,只检查 VECTOR_ENGINE_API_KEY 非空;重启后触发拼图生成不再返回本地配置缺失的 503。
  • 关联:docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md.codex/skills/gpt-image-2-apimart/SKILL.md

npm run api-server 读取 env 的顺序必须让 .env.secrets.local 最后覆盖

  • 现象:POST /api/assets/hyper3d/text-to-model 在本地返回 503详情里提示 HYPER3D_API_KEY 未配置,但开发者明明已经在本地私密文件里写了 key。
  • 原因:scripts/api-server-dev.mjs 之前按 .env.secrets.local → .env.local → .env 合并,结果仓库里的 .env 空示例值会把前面已经设置好的私密 key 覆盖掉。
  • 处理:npm run api-server / npm run dev:rust / npm run dev 统一按“外层 shell 变量优先,其后 .env.env.local.env.secrets.local 逐层覆盖”的顺序加载;真实密钥优先放 .env.secrets.local
  • 验证:本地加入临时测试后,HYPER3D_API_KEY 应能被 .env.secrets.local 覆盖,且 shell 变量仍然最高优先级。
  • 关联:scripts/api-server-dev.mjsserver-rs/crates/api-server/src/hyper3d_generation.rsdocs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md

拼图图片生成 98% 后报 OSS V4 签名时间格式化失败

  • 现象:拼图创作表单生成进度卡在 98%POST /api/runtime/puzzle/agent/sessions/{sessionId}/actions 返回 502 Bad Gateway,前端提示 拼图图片生成失败OSS V4 签名时间格式化失败
  • 原因:platform-oss 曾用 OffsetDateTime::time().to_string() 拼接 x-oss-dateUTC 小时、分钟或秒为个位数时可能缺少前导零,导致 V4 签名时间不是固定 YYYYMMDDTHHMMSSZ
  • 处理OSS V4 签名日期统一显式补零格式化;签名 scope 用 YYYYMMDD,完整签名时间用 YYYYMMDDTHHMMSSZ,不要再依赖 time().to_string()
  • 验证:运行 cargo test -p platform-osscargo check -p api-server;重启 npm run api-server 后检查 /healthz,再重新触发拼图生成。
  • 关联:server-rs/crates/platform-oss/src/lib.rsserver-rs/crates/api-server/src/assets.rsdocs/technical/M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md

拼图生成完成后图片只显示破图或 alt 文案

  • 现象:拼图结果页生成完成后,“画面图”区域出现破图图标和作品名,图片无法正常预览;但打开历史拼图素材时同一张图可能可以正常预览。
  • 原因:拼图正式图保存为 /generated-puzzle-assets/* 兼容标识,旧 /generated-* 直读代理已删除;如果前端没有通过 ResolvedAssetImage / /api/assets/read-url 换签,或收到无前导斜杠的 generated-puzzle-assets/* object key 后未识别为 generated 私有资源,浏览器会直接请求裸路径并失败。生成完成后的结果图还会传入 refreshKey,它只能用于重新请求 /api/assets/read-url,不能给 OSS V4 签名 URL 追加 _vOSS 会把 query 纳入签名,额外参数会让签名失效,历史素材常因未传 refreshKey 而表现正常。
  • 处理:拼图结果页、发布预览、运行态和历史素材预览都走 ResolvedAssetImageuseResolvedAssetReadUrlisGeneratedLegacyPath(...) 必须同时识别 /generated-*generated-*refreshKey 只绕过前端签名缓存并重新换签,不修改已返回的 OSS 签名 URL禁止恢复 /generated-puzzle-assets 直读代理。
  • 验证:运行 npm run test -- src\services\assetReadUrlService.test.ts src\hooks\useResolvedAssetReadUrl.test.tsx src\components\puzzle-result\PuzzleResultView.test.tsx,再触发一次真实生成确认 Network 中先请求 /api/assets/read-url,图片 src 为未追加 _v 的签名 URL。
  • 关联:src/services/assetReadUrlService.tssrc/components/ResolvedAssetImage.tsxdocs/technical/PUZZLE_IMAGE_ASSET_PROXY_FIX_2026-04-27.md

本地短信登录页签突然消失

  • 现象:登录弹窗只剩密码登录,短信登录页签看起来像被删掉,但 LoginScreen 中手机号验证码表单仍存在。
  • 原因:前端根据 GET /api/auth/login-options 返回的 availableLoginMethods 渲染页签;常见根因有两类:
    • 本地启动脚本没有让 .env.local 覆盖 .envSMS_AUTH_ENABLED=true 不生效,后端只返回 ["password"]
    • Rust API 直连已返回 ["phone","password"],但 Vite 代理目标指向未监听端口,导致 3000 域名下的 login-options 返回 500AuthGate 降级成 ["password"]
    • 3000 端口被旧 dev:web 占用后,新的完整栈 Vite 自动漂移到 3001/3002浏览器仍打开旧 3000 页面,旧页面继续代理到已经下线的端口。
    • 单独 npm run dev:web 启动瞬间另一个临时 API 端口可用,脚本若自动切过去,之后临时 API 停掉也会让 3000 继续代理到空端口。
  • 处理:优先用 npm run api-servernpm run dev:rustnpm run dev 启动,这些入口应保持 shell 环境变量最高优先级,并允许 .env.local 覆盖 .env;完整栈启动时还要确保脚本计算出的 RUST_SERVER_TARGET 不被 .env.local 里的旧值覆盖。排查时先请求 3000 域名下的 /api/auth/login-options,再直连 Rust API 目标,并核对 .env.localSMS_AUTH_ENABLED 与代理端口;若 3001/3002 才返回正确结果,说明当前 3000 是旧前端进程,应清理旧进程后重启。
  • 验证:http://127.0.0.1:3000/api/auth/login-options 返回至少 {"availableLoginMethods":["phone","password"]} 后,登录弹窗会恢复短信登录页签和“获取验证码”按钮。
  • 关联:scripts/api-server-dev.mjsscripts/api-server-maincloud.mjsscripts/dev-rust-stack.shscripts/dev-web-rust.mjsdocs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md

本地短信收不到验证码先查 provider

  • 现象:登录弹窗可以进入短信页签,但点击“获取验证码”后,手机没有收到短信。
  • 原因:本地 .env.local 里如果是 SMS_AUTH_PROVIDER="mock",后端不会发真实短信,只会返回固定 mock 验证码;另外 npm run api-server 过去曾让 .env 覆盖 .env.local,导致本地真实短信配置被错误压回默认值。
  • 处理:真实短信联调时把 .env.localSMS_AUTH_PROVIDER 显式设为 aliyun,然后重启 api-server;如果只想验证 UI 和账号链路,则保留 mock 并使用 SMS_AUTH_MOCK_VERIFY_CODE
  • 验证:GET /api/auth/login-options 返回 ["phone","password"]api-server 日志里 provider=aliyun 才说明真实短信链路已生效。
  • 关联:scripts/api-server-dev.mjsdocs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.mddocs/technical/PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md

手机验证码登录 500 先查短信 provider 语义

  • 现象:登录弹窗手机号验证码登录失败,浏览器看到 POST /api/auth/phone/login 500,后端日志里同时出现阿里云短信 UNKNOWNbiz.FREQUENCYcheck frequency failed
  • 原因:真实短信 provider 的配置错误或上游失败曾被 module-auth 折叠成 PhoneAuthError::StoreHTTP 层只能按内部错误返回 500,掩盖了 provider 失败。
  • 处理:保留 provider 错误语义,配置错误映射 503 Service Unavailable,上游短信失败映射 502 Bad Gateway;本地只验证 UI/账号链路时可用 shell 临时覆盖 SMS_AUTH_PROVIDER=mock 后启动 npm run api-server
  • 验证:cargo test -p api-server phone_auth_sms_provider_errors_keep_upstream_http_semantics --manifest-path server-rs/Cargo.toml,真实 provider 频控时接口不再返回 500
  • 关联:server-rs/crates/module-auth/src/errors.rsserver-rs/crates/api-server/src/phone_auth.rsdocs/technical/PHONE_SMS_PROVIDER_ERROR_HTTP_MAPPING_FIX_2026-05-08.md

手机验证码登录成功后又瞬间回到未登录

  • 现象:手机号验证码登录先成功,随后 UI 又闪回“未登录”,登录弹窗可能重新出现。
  • 原因:AuthGate 首次 hydrate 会异步轮换 refresh cookie 并请求 /api/auth/me。如果用户在 hydrate 完成前已经登录,晚到的旧 hydrate 仍可能把刚写入的 user 覆盖成 null
  • 处理:给 AuthGate 的 hydrate 增加版本号保护;登录成功、退出登录和全局 auth 事件都会推进版本号,旧 hydrate 结果到达后直接丢弃。
  • 验证:npm run test -- src/components/auth/AuthGate.test.tsx,新增用例应覆盖“旧 guest hydrate 不覆盖新登录态”。
  • 关联:src/components/auth/AuthGate.tsxsrc/components/auth/AuthGate.test.tsxdocs/technical/AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md

刷新网页后登录态失效

  • 现象:刷新网页后,用户明明有本地 access token却回到未登录状态。
  • 原因:AuthGate hydrate 曾先强制调用 refreshStoredAccessToken();当 refresh cookie 临时失效、代理错配或后端返回 401 时,该方法会先清空本地 access token随后 /api/auth/me 只能恢复成未登录。
  • 处理:refreshStoredAccessToken() 增加 clearOnFailure 选项;AuthGate 在已有本地 access token 时先用 /api/auth/me 确认用户,确认成功后再后台 refresh 续期与写每日登录埋点,后台 refresh 失败不清 token。
  • 验证:npm run test -- src/services/apiClient.test.ts src/components/auth/AuthGate.test.tsx -t "explicit refresh opts out|auth gate keeps a valid local token login"
  • 关联:src/services/apiClient.tssrc/components/auth/AuthGate.tsxdocs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md

登录后推荐页加载出作品又回到未登录

  • 现象:前端登录成功后进入推荐页,推荐页自动加载出一个作品,随后瞬间回到未登录;停留在其他页面或推荐页没加载出作品时不复现。
  • 原因:推荐页 embedded 运行态会自动发起受保护写请求。若这些卡片级后台请求遇到 401 或 refresh 失败,默认请求层曾清空 access token 并广播全局 auth 事件,导致 AuthGate 重新 hydrate 成未登录态。更隐蔽的是,refreshAccessToken() 自身曾在 refresh 失败时静默清 token即便调用方关闭了 clearAuthOnUnauthorized,也可能让后续 hydrate 变成未登录。
  • 处理:请求层统一使用 authImpact: 'global' | 'local' 区分账号权威请求与局部后台请求;推荐页自动运行态、图片换签、公开拼图运行态和平台 bootstrap 私有投影刷新统一使用 BACKGROUND_AUTH_REQUEST_OPTIONS / RUNTIME_BACKGROUND_AUTH_OPTIONS,并等 canReadProtectedData 为 true 后再启动;用户主动点击的账号动作仍保留默认全局鉴权失败处理。
  • 追加处理generated 私有图片换签 /api/assets/read-url 也属于展示层后台请求;推荐页拼图运行态挂载后会立即解析封面图,若换签 401 触发全局鉴权事件,也会表现成“进入拼图作品后瞬间未登录”。资源换签失败只应让当前图片为空,不应清 token、广播 auth 事件或主动 refresh。
  • 追加处理:从推荐页点进公开拼图作品并启动完整运行态后,startPuzzleRun、通关自动 submitPuzzleLeaderboard、下一关 advancePuzzleNextLevel 和重开同样属于当前玩法局部同步;这些请求失败时只应留在拼图错误态,不应清 token 或广播 auth 事件。
  • 追加处理:通关后 refreshSaveArchives()、首屏 bootstrap 的个人看板/作品架/浏览历史读写也只是平台投影刷新,失败应显示局部错误,不能充当全局登录态判定。
  • 验证:npm run test -- src/services/apiClient.test.ts src/services/assetReadUrlService.test.tsnpm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation starts embedded puzzle"npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle runtime uses frontend move merge logic and backend leaderboard"npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle similar work keeps current run level progression"
  • 关联:src/services/apiClient.tssrc/services/assetReadUrlService.tssrc/services/puzzle-runtime/puzzleRuntimeClient.tssrc/components/platform-entry/PlatformEntryFlowShellImpl.tsxdocs/technical/RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md

推荐页作品卡一直显示加载中

  • 现象:推荐页有公开作品,但主视口一直停在“加载中...”,没有进入作品,也没有显示可操作错误。
  • 原因:推荐页自动启动嵌入运行态时先设置 activeRecommendEntryKey / activeRecommendRuntimeKind / isStartingRecommendEntry,但失败或并发切换时外层缺少稳定错误态和请求版本保护,旧启动请求可能晚到覆盖新状态。
  • 处理:selectRecommendRuntimeEntry 使用启动请求版本号丢弃旧请求;启动失败统一设置 activeRecommendRuntimeError = "作品暂时无法进入,请稍后再试。" 并关闭 isStartingRecommendEntry
  • 验证:npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation surfaces start failure"
  • 关联:src/components/platform-entry/PlatformEntryFlowShellImpl.tsxsrc/components/rpg-entry/RpgEntryHomeView.tsxdocs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md

推荐页未登录入口误打开公开详情

  • 现象:新用户默认在发现页,但点击推荐页或推荐封面后,如果复用公开作品详情入口,可能绕过推荐页“登录后游玩”的产品门禁。
  • 原因:RpgEntryHomeView 曾只有 onOpenGalleryDetail 一个回调,同时服务发现页公开详情和推荐页作品入口;一旦为发现页保留公开浏览能力,推荐页也会跟着打开详情。
  • 处理:公开详情与推荐页入口分离为 onOpenGalleryDetailonOpenRecommendGalleryDetail。发现页、搜索和排行榜保留公开详情;推荐 Tab、推荐封面、推荐运行态错误重试和桌面推荐模块统一走登录门禁。未登录推荐页只显示封面点击封面只弹登录窗不携带登录后自动打开详情的回调。
  • 验证:npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "logged out recommend"
  • 关联:src/components/rpg-entry/RpgEntryHomeView.tsxsrc/components/platform-entry/PlatformEntryFlowShellImpl.tsxdocs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md

Rust 冷编译导致 api-server 健康检查误超时

  • 现象:npm run dev:rust 在 Windows 冷编译/链接阶段误判 /healthz 等待超时并杀掉 cargo run
  • 原因:脚本把 SpacetimeDB 与 api-server 等待窗口混在一起,未考虑 Rust 冷编译耗时。
  • 处理:按冷编译超时修复文档拆分等待窗口。
  • 验证:冷启动时不再误杀仍在编译的 api-server。
  • 关联:docs/technical/API_SERVER_DEV_STACK_COLD_BUILD_TIMEOUT_FIX_2026-04-25.md

Windows debug api-server 主线程栈溢出

  • 现象:cargo check -p api-serverbuild_router 测试通过,但 npm run api-server 在 Windows debug 启动时 thread 'main' has overflowed its stack
  • 原因:api-server Axum 路由树已经很深debug 主线程默认栈偏小,初始化状态和构造路由时容易触顶。
  • 处理:入口 main 用显式 16MB 栈线程启动 Tokio runtime并把实际服务逻辑放入 run_server();新增路由时优先用小 router .merge(),避免继续拉长主链。
  • 验证:npm run api-server/healthz 返回 200相关路由冒烟通过。
  • 关联:server-rs/crates/api-server/src/main.rsserver-rs/crates/api-server/src/app.rs

Windows debug api-server.exe 锁文件与强杀退出码容易混淆

  • 现象:cargo run -p api-servernpm run api-serverfailed to remove file ... target\debug\api-server.exe;清理旧进程后,旧终端可能继续打印 process didn't exit successfully: server-rs\target\debug\api-server.exe (exit code: 0xffffffff)
  • 原因Windows 不能覆盖仍在运行的 exe通常是上一条 npm run api-server 链路仍在运行,进程树为 npm run api-server -> node scripts/api-server-dev.mjs -> cargo run -> api-server.exe0xffffffff 常见于排障时用 Stop-Process -Force 强制结束旧 api-server.exe 后由 Cargo 回显,不一定代表新启动失败。
  • 处理:先按目标路径确认并停止本仓库的旧 api-server.exe 及其父级 cargo/node/cmd 启动链路,再重新启动;不要同时开多个 npm run api-server
  • 验证:确认没有匹配 C:\Genarrative\server-rs\target\debug\api-server.exe 的进程后,Remove-Item 能删除旧 exe随后 npm run api-server 启动并访问 /healthz 返回 200。
  • 关联:scripts/api-server-dev.mjsserver-rs/crates/api-server/src/main.rs

dev-rust-stack 端口被旧进程占用时会误判健康检查

  • 现象:node scripts/run-bash-script.mjs scripts/dev-rust-stack.sh --skip-spacetime 输出 Port 3000 is in use, trying another one...,随后 api-server.exeAddrInUse / code: 10048
  • 原因:旧 api-server 仍监听默认 8082 时,脚本的 /healthz 探测会命中旧进程并误判新服务已就绪;旧 Vite 占住 3000Vite 默认漂移到新端口,浏览器仍可能打开旧页面。
  • 处理:scripts/dev-rust-stack.sh 已在 publish / 编译前预检 api-server、主站 Vite、后台 Vite 端口,并让 Vite 使用 --strictPort;遇到端口占用时按脚本打印的 PID 停止旧进程,或显式传入 --api-port / --web-port / --admin-web-port
  • 验证:默认端口被占用时,完整栈应在发布模块前直接失败并打印监听进程;清理端口后重新启动不再漂移端口或命中旧 /healthz
  • 关联:scripts/dev-rust-stack.shdocs/technical/DEV_RUST_STACK_PORT_CONFLICT_PRECHECK_2026-05-09.md

Windows debug 长 SSE Future 触发 api-server 断连

  • 现象:前端 Vite 代理请求 /api/runtime/creative-agent/sessions/{sessionId}/messages/streamread ECONNRESET,随后 api-server.exe0xffffffff 退出,dev:rust 回收 SpacetimeDB、Vite 和后台 Vite。
  • 原因:单个 async_stream::stream! 中塞入 Agent 执行、外部模型请求、会话更新和大量 SSE 事件,会在 Windows debug 下生成很大的 Future真实消费 SSE body 时容易触发 worker 线程栈压力或进程级中断,单元测试若只测函数和路由状态会漏掉。
  • 处理:长 SSE 路由优先使用 tokio::spawn 跑业务流程,通过 mpsc + UnboundedReceiverStream 向 Axum 返回轻量 stream失败时更新会话为 failed 并发送 SSE error,不要把大段执行逻辑内联到路由返回的 stream future 中。
  • 验证:补充实际 collect() SSE body 的路由测试,确认首轮包含 stagepuzzle_template_catalogdone,且不会提前发送 puzzle_template_selection / puzzle_cost_range;再执行 cargo check -p api-servercargo test -p api-server creative_agent,联调时用 npm run api-server 检查 /healthz
  • 关联:server-rs/crates/api-server/src/creative_agent.rsserver-rs/crates/api-server/src/app.rs

creative-agent 过程项不要把历史事件渲染成运行中

  • 现象:智能创作页过程中多个阶段从一开始同时转圈,生成结束或进入模板确认后仍有过程项保持转圈。
  • 原因:前端把历史 stagetool_startedthought_summary_delta 都按 active 渲染;后端工具开始/完成事件如果 toolCallId 不一致,也会导致开始事件无法收口。
  • 处理:
    • 只有最新且仍在执行的 stage 可为 active等待确认、等待用户、target ready 和 failed 都是静态状态。
    • 工具开始事件必须等同一 toolCallIdtool_completed 收口;兼容旧流时可按后续同名完成事件兜底。
    • 思考摘要只展示用户可见摘要,且流结束或会话进入等待/完成/失败态后必须改成 done。
  • 验证:前端测试断言完成后 CreativeAgentProcessItem 不再存在 tone === 'active';后端测试确认工具开始/完成事件使用相同 toolCallId
  • 关联:src/components/creative-agent/creativeAgentViewModel.tsserver-rs/crates/api-server/src/creative_agent.rsdocs/prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md

creative-agent 会话切换要清理本地待确认模板

  • 现象:用户在一个智能创作会话中点开模板确认面板后,立即切到另一条创作会话,可能看到上一会话的确认面板残留。
  • 原因:模板确认面板的 pendingSelectionCreativeAgentWorkspace 本地 UI 状态,不属于后端 session 快照;组件复用时如果不监听 sessionId 清理,会跨会话泄漏。
  • 处理:工作区以 session?.sessionId 为边界清空 pendingSelection;服务端仍以 puzzleTemplateSelection / targetBinding 作为正式业务状态。
  • 验证:前端测试先点开模板确认面板,再 rerender 到另一 session断言确认面板消失。
  • 关联:src/components/creative-agent/CreativeAgentWorkspace.tsxsrc/components/creative-agent/CreativeAgentWorkspace.test.tsx

视觉小说 VN-10 不要绕过平台资产引用

  • 现象:文档、封面、场景背景、角色立绘或音乐为了预览方便被写成 Data URL、裸对象路径、外部 URL 或本地临时文件路径。
  • 原因前端上传与预览容易混在一起若不走平台资产对象SpacetimeDB 和长期草稿会被大文本或大二进制污染。
  • 处理VN 资产统一用 /api/assets/direct-upload-tickets、OSS 直传、/api/assets/objects/confirm,长期状态只保存 assetObjectId/generated-* 引用;运行时图片用 ResolvedAssetImage 换签。
  • 验证:文档模式 sourceAssetIds 为平台资产 id草稿中不出现 data:;图片和音乐字段为平台 generated 引用或 null。
  • 关联:docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.mdsrc/services/visual-novel-creation/visualNovelAssetClient.ts

视觉小说 VN-13 交接时不要再回头找旧迁移方案

  • 现象:接手视觉小说的人容易重新打开旧 TXT 迁移文档,把“外部平台工程迁入”误当成当前实现目标。
  • 原因:视觉小说历史资料里保留了很多迁移阶段的讨论,而当前真正的实现口径已经收口到 PRD、表目录、Prompt 工具说明、实现收口文档和负向扫描报告。
  • 处理:维护视觉小说时优先看 AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.mdSPACETIMEDB_TABLE_CATALOG.mdVISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.mdVISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.mdVISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.mdVN11_NEGATIVE_SCAN_REPORT_2026-05-07.md
  • 验证:新开发者只读这组文档即可继续维护,不需要把旧 TXT 迁移方案重新当作编码依据。
  • 关联:docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.mddocs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.mddocs/experience/VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md

视觉小说公开广场不要触发登录刷新

  • 现象:未登录用户进入平台公开广场或从推荐流读取视觉小说公开作品时,前端可能先尝试 /api/auth/refresh,失败后再读取公开列表,导致无意义的鉴权噪声或 401 状态刷新。
  • 原因:公开只读接口如果复用默认 requestJson 选项,缺少 access token 时会先走静默 refresh。
  • 处理:视觉小说公开广场列表使用 skipAuth: trueskipRefresh: true;鉴权 mutation 仍保持默认鉴权链路。
  • 验证:执行 src/services/visual-novel-runtime/visualNovelRuntimeClient.test.ts,确认 /api/runtime/visual-novel/gallery 请求携带 skipAuth / skipRefresh,而 run、重生成和存档 mutation 仍走受保护路由。
  • 关联:src/services/visual-novel-runtime/visualNovelRuntimeClient.tsdocs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md

创作 Tab 语义迁移后,旧“新建作品”测试要改看智能创作首页

  • 现象:把 create 从旧创作中心切到 CreativeAgentHome 后,旧测试仍尝试在创作页找“新建作品”类型卡,导致用例失败或定位不到元素。
  • 原因:产品语义已经变成“创作 = 智能创作首页,草稿 = 旧作品架”,但测试夹具和 helper 还沿用旧入口。
  • 处理:把这类测试改成验证智能创作首页、快捷胶囊、抽屉与草稿 Tab同时给 useRpgEntryLibraryDetail 这类恢复路径补上 setPlatformTabToDraft
  • 验证:定向 vitesteslinttypecheckcheck:encoding 都通过。
  • 关联:src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsxsrc/components/rpg-entry/useRpgEntryAgentDraftRestore.test.tsxsrc/components/rpg-entry/useRpgEntryLibraryDetail.ts

server-rs 默认 cargo build 不能等同于构建 SpacetimeDB 模块

  • 现象:在 server-rs 下无参数 cargo build 期望同时构建 spacetime-module,导致链接或构建范围误判。
  • 原因workspace default-members 当前只包含 crates/api-serverSpacetimeDB module 有独立构建/发布方式。
  • 处理:默认 Rust 构建只覆盖原生 api-server;本地模块发布继续走 spacetime publish --module-path ... --build-options="--debug" / bindings 生成流程。
  • 验证:查看 server-rs/Cargo.toml default-members并按相关 SpacetimeDB 文档执行模块构建。
  • 关联:server-rs/Cargo.tomldocs/technical/RUST_WORKSPACE_DEFAULT_BUILD_SCOPE_FIX_2026-04-25.md

Windows 原生 spacetime-module 单测会链接缺失 SpacetimeDB 宿主符号

  • 现象:在 Windows 上执行 cargo test -p spacetime-module --manifest-path server-rs/Cargo.toml 可能编译到链接阶段后失败,出现 LNK2019 / LNK1120,缺失 datastore_insert_bsatnprocedure_start_mut_txconsole_log 等 SpacetimeDB 宿主符号。
  • 原因:spacetime-module 依赖的 SpacetimeDB runtime API 面向 wasm 宿主环境,原生 test exe 链接不到这些宿主导出。
  • 处理:日常语法和类型验证使用 cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml;需要验证模块行为时走 SpacetimeDB publish/dev 或模块域纯 Rust crate 的单测,不把该原生链接错误当作业务测试失败。
  • 验证:cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml 能通过;原生 cargo test 若仍报上述宿主符号缺失,按当前限制记录为未执行。
  • 关联:server-rs/crates/spacetime-moduledocs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md

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 直接构建”后仍继续真实构建。
  • 关联:scripts/dev-rust-stack.shjenkins/Jenkinsfile.production-stdb-module-builddocs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.mddocs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md

生产发布入口不要沿用旧 Jenkinsfile / 一体化脚本

  • 现象:部署、回滚或 Jenkins Job 重建时参考旧发布文档,导致 systemd、Nginx、SpacetimeDB 自托管和生产包拆分不一致。
  • 原因:旧 Jenkins / 旧本地远端部署脚本文档仍作为历史经验保留。
  • 处理:生产相关操作先看 PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md,再按需追溯旧文档。
  • 验证:发布链路使用当前 deploy/systemddeploy/nginxscripts/deployjenkins/Jenkinsfile.production-*
  • 关联:docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md

Jenkins 可选参数在 set -u 下不能裸读

  • 现象:数据库导入或导出流水线报 INCLUDE_TABLES: unbound variable,或其它可选参数在 Bash 中未定义即退出。
  • 原因Jenkins string/boolean 参数留空时不一定会导出同名环境变量,而生产数据库导入导出脚本块启用了 set -u
  • 处理:进入 Bash 执行块后先使用 ${VAR:-}${VAR:-默认值} 收敛成本地变量;必填项使用 ${VAR:?中文错误} 明确失败原因。
  • 验证:扫描 jenkins/Jenkinsfile.production-database-exportjenkins/Jenkinsfile.production-database-import,确认 INCLUDE_TABLESCHUNK_SIZESERVER_BACKUP_DIRECTORYSMOKE_HEALTH_URL 等可选参数不再裸读。
  • 关联:docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.mdjenkins/Jenkinsfile.production-database-exportjenkins/Jenkinsfile.production-database-import

个人任务 scope 不得扩成 work/site/module

  • 现象:个人任务配置为 work / site / module 后进度串桶或静默按 0 处理。
  • 原因:首版个人任务只支持用户维度,非 user scope 会造成任务进度读取语义错误。
  • 处理Admin 任务配置页不展示范围选择,保存时固定 scopeKind: 'user'API 和领域构造层拒绝非 User
  • 验证:非 user scope 返回错误;相关测试覆盖 Site / Module / Work 被拒绝。
  • 关联:docs/technical/RUNTIME_PROFILE_TASK_SCOPE_2026-05-04.mddocs/technical/ANALYTICS_DATE_DIMENSION_IMPLEMENTATION_2026-05-04.md

拼图发布 409 不一定是接口故障

  • 现象:拼图结果页点击发布后,控制台出现 POST /api/runtime/puzzle/agent/sessions/{sessionId}/actions 409 (Conflict),用户只看到发布失败。
  • 原因:publish_puzzle_work 是资产操作发布入口,发布前会预扣 1 枚光点;余额不足时后端按业务冲突返回 409 CONFLICTdetails.message光点余额不足
  • 处理:前端发布弹窗在用户点击发布后必须保留并展示后端业务错误,不能只把错误写到弹窗背后的页面 banner。
  • 验证:PuzzleResultView 单测覆盖发布弹窗内展示 光点余额不足
  • 关联:src/components/puzzle-result/PuzzleResultView.tsxdocs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.mddocs/technical/ASSET_GENERATION_POINTS_CONSUMPTION_2026-04-27.md

WebGL 画布在高 DPR 移动端放大溢出

  • 现象抓大鹅试玩入口进入后3D 锅体和物体从中心圆形区域向右下溢出,顶部状态和底部备选栏也可能看起来被右侧裁切。
  • 原因:WebGLRenderer.setPixelRatio(...) 会把绘图缓冲区乘上设备 DPR如果没有给 renderer.domElement 单独设置 CSS width/height: 100% 和绝对铺满,浏览器可能把高 DPR 缓冲区尺寸当成页面显示尺寸。
  • 处理:中心棋盘和托盘预览的 WebGL canvas 统一套用 position:absolute; inset:0; width:100%; height:100%; display:blockrenderer.setSize(..., false) 只负责同步绘图缓冲区。
  • 验证:强制移动端 390x844、DPR 2 截图确认棋盘左右边界在视口内canvas CSS 尺寸等于容器尺寸,内部 width/height 属性可大于 CSS 尺寸。
  • 关联:src/components/match3d-runtime/Match3DPhysicsBoard.tsxdocs/technical/MATCH3D_RUNTIME_3D_GEOMETRY_EXPERIMENT_2026-05-02.md

Hyper3D subscriptionKey 不要按固定短文本限长

  • 现象:抓大鹅生成草稿时,内联 Rodin 图生 3D 模型提交成功后,状态轮询报 subscriptionKey 超过 256 字符,导致 /api/creation/match3d/sessions/{sessionId}/actions 返回 400。
  • 原因:subscriptionKey 是 Hyper3D 返回的 opaque token长度由上游决定后端状态查询曾复用普通文本校验把它限制在 256 字符。
  • 处理:query_task_statussubscriptionKey 只做 trim 和非空校验,不做固定长度限制;前端临时任务和 Match3D 草稿响应可继续展示该 token但不要把它当作可编辑短文本。
  • 验证: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.rsdocs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md

抓大鹅草稿生成不要阻塞在 Rodin 模型下载

  • 现象:抓大鹅草稿生成时 Hyper3D 状态已完成,但下载列表为空或没有可用模型文件,/api/creation/match3d/sessions/{sessionId}/actions 返回 502 Bad Gateway,前端提示 Hyper3D 已完成但未返回可下载模型文件
  • 原因:草稿生成链路曾在切割图片后立即并行调用 Rodin 图生模型,并把模型下载成功作为草稿完成前置条件;上游完成态和可下载文件列表不是强一致,容易把本来可用的图片草稿卡死。
  • 处理:草稿阶段只生成物品名、素材图、切割独立图片并上传 OSS返回 status = image_readyRodin 3D 模型生成留到结果页 3D素材 Tab 手动触发。
  • 验证:草稿响应中的 generatedItemAssets[].imageSrc 有值、modelSrc 为空、状态为 image_ready;结果页显示 图片已就绪0 文件,不会自动请求 Hyper3D 下载。
  • 关联:server-rs/crates/api-server/src/match3d.rssrc/components/match3d-result/Match3DResultView.tsxdocs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md

抓大鹅切图路径不能只用中文物品名

  • 现象:草稿页 3D素材 Tab 中多个素材名称不同,但预览图片完全一样;点击图生模型生成时还可能提示 参考图必须是 data URL
  • 原因:中文物品名经过 OSS 路径段清洗后都可能退化成 item,多张切割图片写到同一个 items/item/image.png object key后写入覆盖先写入结果页手动 Rodin 图生模型还曾把 /generated-match3d-assets/... 私有路径直接作为 imageDataUrls 提交。
  • 处理:切割图上传路径必须带稳定唯一 itemId 前缀,例如 items/match3d-item-1-item/image.png结果页提交图生模型前generated 私有路径先经同源 /api/assets/read-bytes 由后端换签并读取字节,前端再转成 data:image/...;base64,...,不要在浏览器里直接 fetch OSS 签名 URL否则会被 bucket CORS 拦截。
  • 验证:后端单测覆盖中文名路径唯一;前端单测覆盖 generated 参考图会换签、fetch 并以 Data URL 调用 submitHyper3dImageToModel
  • 关联:server-rs/crates/api-server/src/match3d.rssrc/components/match3d-result/Match3DResultView.tsxdocs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md

抓大鹅生成素材不能只挂在 compile response

  • 现象:抓大鹅草稿生成完成后停留在结果页能看到切割好的 3D素材 图片;退出后从草稿 Tab 重新进入同一草稿,素材列表变回默认占位或为空,已生成的物品名称和图片丢失。
  • 原因:generatedItemAssets 如果只附加在 match3d_compile_draft 的 HTTP response draft 上,刷新或重进时 getMatch3DWorkDetail 只能读取 SpacetimeDB 中的 match3d_work_profile;旧 mapper 返回空数组,自然无法恢复素材。拼图链路已经通过 save_puzzle_generated_images 把候选图和 levels 写回 work profile抓大鹅也必须同样写持久字段。
  • 处理compile 成功时把独立物品图片列表序列化写入 match3d_work_profile.generated_item_assets_jsonupdate_match3d_work / publish_match3d_work 保留该字段API work summary/detail 映射反序列化为 generatedItemAssets。前端保持“本次 draft 优先,重进 profile 兜底”的读取顺序。
  • 验证:cargo test -p spacetime-client match3d --manifest-path server-rs/Cargo.tomlcargo test -p api-server match3d --manifest-path server-rs/Cargo.tomlnpm run test -- src/components/match3d-result/Match3DResultView.test.tsx
  • 关联:server-rs/crates/spacetime-module/src/match3d/*server-rs/crates/spacetime-client/src/mapper.rsserver-rs/crates/api-server/src/match3d.rssrc/components/match3d-result/Match3DResultView.tsxdocs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md