为 AGC 新增项目定时快照上传(目标 OSS agc-dev)
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m21s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m4s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 6m9s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m48s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m32s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m48s
Project CI / Frontend tests (pull_request) Successful in 4m18s
Project CI / Repository checks (pull_request) Successful in 5m32s
Project CI / Native shell tests (pull_request) Successful in 8m12s
Project CI / AI game creator shell web tests (pull_request) Failing after 5m1s
Project CI / Backend tests (pull_request) Successful in 12m32s

- 新增客户端 project_snapshot 模块:项目扫描与排除口径、增量索引、差异对比、上传编排与状态查询
- 新增周期定时器与工作区窗口关闭触发;应用退出只做有界等待,不重复发起同步
- 新增 api-server 两条登录态路由:单文件上传与清单覆盖写入,落在服务端私有前缀内
- platform-oss 新增内部对象精确写入与探测、项目快照对象键构造,并修复 HEAD 读取长度恒为 0
- shared-contracts 新增 agc_project_snapshots DTO 与项目 ID、相对路径、摘要校验
- 新增真实 OSS 存储层冒烟示例与客户端真实链路冒烟用例(默认忽略)
- 登记 check-config 的 native-only 命令白名单,恢复 npm run agc 可启动
- 同步主规范、里程碑、实施计划、开发运维文档与 .env.example
This commit is contained in:
kdletters
2026-09-17 15:45:34 +08:00
parent ebc5dfe7db
commit c722fd844b
24 changed files with 3093 additions and 6 deletions
@@ -1524,3 +1524,49 @@ Direct 回合的所有权属于进程内项目身份锁,不属于当前页面
- 两个窗口同时对同一项目发起 Runtime 写请求时,用户体验仍由项目级写锁串行决定;本次不引入跨窗口排队提示。
- 平台会话在窗口间传播依赖共享 localStorage 与 Runner 权威;渲染层不做跨窗口事件推送,另一个窗口在下一次会话校验或刷新时收敛。
## 2026-09-17 AGC 项目定时快照上传(agc-dev)
### 目标与非目标
- 目标:AGC 在项目工作区打开期间按固定周期把用户项目增量上传到 OSS `agc-dev`,并在项目关闭(工作区窗口关闭、切回启动器、应用退出)时立即补一次同步;重复内容不重复上传。
- 非目标:不做云端下载/恢复、不做跨设备合并、不做远端多余对象清理、不新增 UI 面板、不修改 `/api/external/v1` 与 OpenAPI、不新增 SpacetimeDB 表。
- 非目标:不把 OSS AccessKey 放进客户端;客户端不直连 OSS。
### 参与入口、状态与跨模块边界
- 触发入口有两个:工作区窗口 `main` 存活期间的周期定时器、工作区窗口关闭事件(`CloseRequested`)。两者共用同一个进程内同步器,同一项目的同步串行执行,周期触发在已有同步进行时直接让位,不排队堆积。
- 应用退出(`RunEvent::Exit`)不重复发起同步:该时刻窗口已销毁,按窗口重新枚举项目只会得到空集;退出路径只负责在有界预算(15 秒)内等待在途同步收尾,让关窗触发的那一次同步能写完索引再退出。
- 客户端扫描、差异对比、索引持久化与上传编排都在 Tauri Rust 进程(`src-tauri/src/project_snapshot/`);WebView 只读状态,不参与差异计算。
- 本地索引是增量对比的唯一依据:`<AppData>/project-snapshots/<projectId>/index.json` 保存上次成功同步的相对路径、`sha256`、字节数和修改时间。项目根使用现有 manifest 的稳定 `project_id` 作为远端身份,路径不再作为身份。
- 远端写入经 `api-server`,客户端只持平台登录态 Access Token。两条登录态路由:`POST /api/agc/project-snapshots/files`(单文件,正文为原始字节,元数据走查询串)与 `POST /api/agc/project-snapshots/manifest`(本次同步后的完整清单)。
- 对象键与清单由服务端决定:文件键为 `agc/project-snapshots/v1/{userId}/{projectId}/files/{sizeBytes}-{checksumDigest}/{relPath}`,清单键为 `agc/project-snapshots/v1/{userId}/{projectId}/manifest.json`。键里带字节数与摘要,因此"对象已存在且长度一致"可以作为内容一致的判据;路径按原始大小写保留,不走 `put_object` 的低位规范化。`agc` 前缀继续是服务端专用私有前缀,通用对象键解析与客户端直传票据都不覆盖它。
- 目标 bucket 使用独立配置 `GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_BUCKET` / `_ENDPOINT` / `_ACCESS_KEY_ID` / `_ACCESS_KEY_SECRET`,默认 `agc-dev` + `oss-rg-china-mainland.aliyuncs.com`,未配置时回退 `ALIYUN_OSS_*`;与"资源 bucket 与备份 bucket 分离"的既有口径一致。
### 正常、失败、重试与幂等行为
- 差异对比口径:先按 `相对路径 + 字节数 + 修改时间` 判定是否候选变更,命中旧记录则复用已存 `sha256`,只有 `(size, mtime)` 变化才重算摘要。产出新增、修改、删除三类集合,只上传新增与修改的文件。
- 每次成功同步的最后一步上传该项目的 `manifest.json`(当前全量文件清单:相对路径、摘要、字节数、同步序号)。删除文件只在清单中消失,本期不删除远端对象;远端清理留给后续里程碑。
- 幂等:同一摘要与字节数的对象重复提交由服务端 HEAD 校验后跳过;探测失败按"未存在"处理并照常 PUT,宁可多传一次也不漏传。索引只在清单写入成功后推进,失败时保留旧索引以便下次重算。
- 失败关闭:单个文件失败不推进整次同步的完成位,失败文件与剩余文件在下一次周期或下次关闭时重试。鉴权失败(401/403)、权限、额度与身份类失败不做自动重试,只记录分类结果并等待用户重新登录后的下一次触发。
- 生命周期:同步有界超时(单文件与整次同步分别设上限),项目关闭与应用退出路径不因同步失败而阻塞或延迟退出超过超时上限。
- 上传内容边界:复用项目索引与 checkpoint 同一份 `should_skip_project_snapshot_path` 口径——整个 `.agent`(含 runtime、logs、checkpoint、manifest、project.lock)、版本控制目录、`node_modules`/`target`/`dist`/`build`/`coverage`/`.cache`、凭据目录与 `.pem`/`.key` 等敏感后缀都不参与同步;符号链接与重解析点同样跳过。单文件(64 MiB)与单次同步总量(512 MiB)各有上限,超限文件进入跳过或延后清单而不是静默丢弃。
### 契约与兼容
- 新增登录态内部路由 `POST /api/agc/project-snapshots/files`,请求 DTO 放在 `shared-contracts`;不属于 `/api/external/v1`,因此不更新 External OpenAPI,与 `/api/error-reports` 同类。
- 服务端校验 `projectId` 形态(拒绝路径分隔符、`..`、控制字符与超长值)、相对路径规范(正斜杠、拒绝绝对路径与穿越)、摘要形态(64 位十六进制)和字节数上限,任何越界返回 4xx 而不是写入 OSS。
- 不改变客户端与 Runner 的本机协议、平台会话语义、项目写锁与 manifest 结构;新增索引文件位于 AppData,不进入用户项目目录。
### 验收标准与证据来源
- 定向 Rust 测试:首次同步全量、仅改一个文件时只产生一个修改项、删除文件只体现在清单、`(size,mtime)` 未变时复用旧摘要、排除规则与上限跳过、同步失败不推进索引、同一项目并发触发串行化。
- 服务端测试:越界 `projectId`/相对路径/摘要被拒;相同摘要重复提交走跳过分支;鉴权缺失返回 401;OSS 未配置返回明确的 5xx 而不是写入空对象。
- 运行时 smoke:AGC 开发态打开项目、观察索引写入与同步日志、关闭工作区窗口后确认关闭触发的那次同步执行;报告为"客户端 diff 已验证 / 服务端已配置环境联调"两层,不合并成一句"已通"。
- 边界:新增日志与错误文案不含 Access Token、AccessKey、绝对路径与项目内容。
### 未决问题
- 远端删除对象清理、配额与保留策略未定;本期只写清单,OSS 侧对象只增不减。
- 目标 bucket 的私有前缀权限与生命周期规则(例如转低频/过期删除)需要在部署环境确认后单独收口。
- 大项目(素材数量多、单文件大)的首轮全量上传耗时与带宽占用未实测;必要时后续里程碑引入并发上限与断点续传。