完成素材无限画布最终审计与恢复安全收口
同步资源管理最新稳定的 manifest relay、Runner 重挂与 MCP 域名修复 统一网站与 Tauri 的共享画布 history、缩放恢复和宿主边界 移除生成账本中的临时上传凭证与 Provider 敏感字段 补齐事务快照发布前故障恢复和同幂等身份重放 更新最终 PRD、技术方案、decision-log 与 pitfalls 补齐前端、Tauri Rust、竞态、锁、恢复和原生壳门禁
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -94,6 +94,12 @@ npm run test -- apps/ai-game-creator-shell/tests/agentRuntimeModel.test.ts --run
|
||||
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml autonomous_completion_contract -- --nocapture --test-threads=1
|
||||
```
|
||||
|
||||
修改 manifest invalidation relay、GUI owner attach 或其测试夹具后,所有会读写进程全局事件 sink 的测试统一使用 `manifest_invalidation_sink_isolation_` 前缀,并至少以 2 个 test thread 重复运行该 filter。测试 fixture 的 accept 和 payload 读取都必须使用总 deadline,不能只在 accept 成功后给 `TcpStream` 设置 read timeout;全局 sink 只能在共享 test-only 串行锁内由 RAII guard 配置和清理。
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml manifest_invalidation_sink_isolation_ -- --nocapture --test-threads=2
|
||||
```
|
||||
|
||||
source allowlist、game-chat 单轮完成门和 Canvas spritesheet 引用门禁均须命中实际测试;若完整 Rust suite 受 Windows `os error 32` 既有文件锁竞态影响,应单独复跑新增 filter 并如实记录,不能把锁竞态失败改报为本次改动通过。
|
||||
|
||||
Windows release 的非交互后台命令统一使用 `CREATE_NO_WINDOW`,包括 `command.exec / project.verify`、STDIO MCP、Repository Context Git、`git.inspect / project.git_commit` 和 `taskkill` 清理命令;需要进程组终止时再叠加 `CREATE_NEW_PROCESS_GROUP`,不要使用 `DETACHED_PROCESS`。smoke 时应在实际任务运行期间观察无额外控制台窗口,并在关闭客户端后核对整棵后台进程树为零,再重启确认 reconciliation 可继续。
|
||||
|
||||
@@ -4205,6 +4205,28 @@
|
||||
- 处理:文件头白名单同时覆盖 32/64 位与 fat Mach-O 的正序和反字节序合法魔数,并由桌面配置门禁同时反查 staging 脚本和根级产物检查,不能改成只按扩展名或构建退出码判断。
|
||||
- 验证:在 macOS 上构建真实 desktop-shell release,运行 `npm run desktop-shell:stage-release-binary`,再由 `npm run check:native-shells` 校验 staged 产物。
|
||||
|
||||
## 托管 MCP 新增公开域名时不能只更新网关路由(2026-08-05)
|
||||
|
||||
- 现象:`https://dev.genarrative.world/api/external/v1/mcp` 的 manifest、OpenAPI 和 Bearer 鉴权都正常,但鉴权后的 `initialize` 返回 `403 FORBIDDEN`;通过 SSH 隧道访问同一 api-server 的 loopback 地址却可以正常列出 tools/resources。
|
||||
- 原因:`rmcp` Streamable HTTP transport 自带 DNS rebinding 防护。公网网关已经接入 dev 域名,但 `external_mcp::service()` 的 `allowed_hosts` / `allowed_origins` 仍只登记正式域名和 localhost,因此请求在 MCP 协议处理前被 transport 拒绝。
|
||||
- 处理:新增公开 MCP 环境时,同批登记对应 Host 与 HTTPS Origin;不要通过客户端伪造 `Host`、关闭防护或改走内部 SpacetimeDB MCP 规避。allowlist 变更属于 api-server 发布内容,必须随正常 API release 部署到目标环境。
|
||||
- 验证:自动测试使用真实公开 Host/Origin 执行 `initialize`;部署后再从公网域名完成带 Key 的 `initialize`、`tools/list`、`resources/list`、Skill resource 读取和至少一个只读业务 tool 调用。loopback 成功只能证明 MCP 实现和 Key 可用,不能替代公网 Host 验收。
|
||||
- 关联:`server-rs/crates/api-server/src/external_mcp.rs`、`docs/【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md`。
|
||||
|
||||
## GUI owner 锁不能替代逐 boot 的事件接收端登记(2026-08-05)
|
||||
|
||||
- 现象:GUI 首次启动后 manifest 事件转发正常,但 Runner 被替换为新 boot 后只剩 owner 锁和 endpoint 可用,后台更新不再到达 GUI;或者 attach 响应只确认 owner,客户端却误记当前 boot 已完整登记,后续 ensure 不再重试。
|
||||
- 原因:把 OS owner 生命周期约束与进程内事件 sink attachment 混成同一状态,或在 `ensure_external_agent_runner` 之外执行一次性 attach;测试若用 actionId 等无关字段代替真实 sink port/token,也无法证明新 boot 重放的是可用接收端。
|
||||
- 处理:GUI 按规范化 AppData 私有登记真实 sink port/token,`ensure_external_agent_runner` 的 endpoint 复用和新 Runner 就绪两条成功路径都按 `bootId` 重放。同 boot 成功后幂等,新 boot 必须重挂;RPC、`attached` 或 `eventSinkAttached` 任一失败或缺失都不得记录成功 boot,并允许同 boot 后续重试。不同 AppData 不共享登记,未登记 CLI 不触发 attach;sink token 不进入日志、错误或公共状态。
|
||||
- 验证:分别覆盖真实 port/token 跨 boot 原样重放、同 boot 幂等、新 boot 重挂、普通 attach 失败、`eventSinkAttached` 缺失与 false 后同 boot 重试、AppData 隔离和未登记 CLI 零副作用。
|
||||
|
||||
## manifest relay 测试不能并行覆盖同一个全局 sink(2026-08-05)
|
||||
|
||||
- 现象:crate 根 relay 测试在配置全局 sink 后阻塞等待 `TcpListener::accept()`,同时 Runner GUI owner attach 测试通过另一条路径覆盖并清空 sink;事件可能被发往另一端口,原 listener 随后永久等待。断言或 `expect` 提前失败时,成功路径末尾的手动 clear 也不会执行。
|
||||
- 原因:两个跨模块测试读写同一进程全局状态,却没有共用隔离边界;只给 accept 后取得的 stream 设置 read timeout 无法约束 accept 本身,payload 读取也缺少总 deadline。
|
||||
- 处理:全部全局 sink 测试共用一把 test-only 串行锁,并由 RAII guard 在 `Drop` 中无条件清空;测试统一使用 `manifest_invalidation_sink_isolation_` 前缀。relay fixture 对 accept 和 payload 分别使用非阻塞轮询与总 deadline,不使用固定 sleep;生产 loopback、token、连接 / 写入超时和 payload 大小校验保持不变。
|
||||
- 验证:用 `--test-threads=2` 重复运行统一 filter,覆盖正常 relay、无事件 accept 超时、不完整 payload 超时、panic 展开清理,以及 GUI owner attach 配置与 guard 清理。
|
||||
|
||||
## 远端图片 completed 不能冒充本地资源创建成功(2026-08-05)
|
||||
|
||||
- 现象:External operation 已返回 completed,但稳定引用缺失、下载失败、正式资产事务中断或 manifest 已提交而 UI 事件丢失时,界面仍可能提前显示“资源创建成功”,重复回调还可能再次下载、写文件或登记资源。
|
||||
@@ -4213,3 +4235,24 @@
|
||||
- 精修补充:`sourceImageSrc` 是可下载的稳定媒体引用,`sourceResourceId` 是资源身份,二者不能因为都可表现为字符串就填同一个 objectKey。本地 `local-asset:*` 只保留在本地 manifest 血缘;没有真实 External resourceId 时省略 `sourceResourceId`。
|
||||
- 验证:覆盖确认前零调用、同 key 连点、accepted 重启 GET-only、重复 completed、取消后迟到、下载后本地事务恢复、事件丢失、切项目/改选择、旧轮询隔离、实时布局与选择、精修血缘及敏感字段零泄漏。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project/asset_canvas/generation.rs`、`apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx`、`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`。
|
||||
|
||||
## 可恢复生成账本不能持久化 direct-upload ticket(2026-08-05)
|
||||
|
||||
- 现象:为支持参考图上传中断恢复,把完整 upload ticket 放进 generation ledger;账本随之包含 Provider host、formFields、policy、signature 或临时 Authorization,项目目录泄露即可复用临时凭证。
|
||||
- 原因:把“恢复所需的稳定远端身份”和“仅供一次上传的临时授权材料”当成同一种持久状态。原子 sidecar 只能保证写入完整,不能让敏感字段变安全。
|
||||
- 处理:ticket 结构不实现 Serialize/Deserialize,host/formFields 只在本次内存调用中使用。账本在上传前只保存稳定 bucket/objectKey;重启先用这组身份调用 object confirm,确认成功后只保留 objectKey/assetObjectId 并清掉上传中间态。账本测试必须直接序列化完整 ledger,扫描 Provider URL、Authorization、policy、signature、API Key 和 ticket 字段名。
|
||||
- 验证:运行 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml private_generation_ledger_never_serializes_upload_credentials_or_provider_url`,并继续检查公开草稿、manifest、事件和普通错误不含 prompt、operationId、Key、绝对路径或媒体正文。
|
||||
|
||||
## prepared journal 之前同样存在正式事务崩溃窗口(2026-08-05)
|
||||
|
||||
- 现象:事务依次安装 before/after 快照后才写 journal;若进程在首个快照、全部快照或 journal 已写但 ledger 未写时退出,重启扫描看到 transaction 目录却无法进入原先只覆盖 prepared 之后的恢复状态机,可能留下孤儿目录或阻塞项目后续提交。
|
||||
- 原因:把 `prepared` 当成事务的第一个可观察持久阶段,忽略了构造 prepared 证据本身也由多次原子文件安装组成。
|
||||
- 处理:把首个快照、全部快照和 journal 后/ledger 前加入故障矩阵。无 ledger 时只允许清理受控快照与本模块临时文件;若 journal 已存在,还必须证明正式目标不存在、manifest 和 project revision 精确等于 before。未知文件、正式文件存在或权威状态漂移全部失败关闭,不能递归猜测清理。清理后同步 transaction 父目录,并允许同 commit/idempotency 身份安全重放。
|
||||
- 验证:故障矩阵逐阶段恢复;额外用同一幂等身份在快照残留清理后提交两次,必须得到一次 committed、一次 already-committed,manifest 仍只有一个 canvas asset。
|
||||
|
||||
## 宿主事件接线不能顺手复制共享 history 栈(2026-08-05)
|
||||
|
||||
- 现象:Tauri Surface 已复用共享 viewport/transform/renderer 数学,却另外维护 undo/redo refs、快照克隆和恢复逻辑;网站共享 hook 后续增加内容安全或字段恢复时,两端会静默分叉。
|
||||
- 原因:把 Pointer 事件接线、宿主生命周期胶水和可复用 history 算法放在同一组件中,误以为没有复制整个画布目录就已经满足共享源码边界。
|
||||
- 处理:两宿主直接消费共享 `useCanvasHistory`;共享 snapshot 统一覆盖 viewport、selection、图层位置和 width/height,宿主只声明本地媒体是否允许安全移除/重做。Tauri 仍可保留 Pointer capture/epoch/host callback 接线,但选择、平移、缩放、变换、renderer 和 history 状态机不得在宿主重写。
|
||||
- 验证:主站 history 定向测试覆盖 resize undo/redo;Tauri 新建、导入、编辑、撤销重做和 durable commit 用例必须在同一共享 hook 下通过。
|
||||
|
||||
Reference in New Issue
Block a user