合并 origin/master:保留 3D 契约与 provider checkpoint,旧玩法表随主线退役

- server-rs/crates/shared-contracts/src/lib.rs:采用主线的模块裁剪(删掉 cfg(any()) 遗留模块、补上 agc_analytics 与 game_distribution),同时保留本分支的 editor_canvas、model3d 模块与 EDITOR_GENERATION_OPERATION_KINDS 导出
- server-rs/crates/spacetime-module/src/migration.rs:主线删除的 410 行旧玩法表 normalize 段保持删除,保留本分支的 provider_kind / provider_task_id 兼容段与对应用例
- docs/【开发运维】本地开发验证与生产运维-2026-05-15.md:校验清单同时保留 Tripo 3D 生成任务与 editor_background_music_generation / model3d_text_to_model / model3d_image_to_model
- .gitignore:补回本分支新增的 3D 模型文件忽略规则(*.glb / *.gltf 等 12 行),压测数据段随主线一并删除
- 共享记忆:本分支的 3D 决策与踩坑条目保留在主线重排后的 decision-log.md 与 pitfalls.md 中
This commit is contained in:
2026-09-23 19:39:30 +08:00
997 changed files with 56264 additions and 80302 deletions
@@ -10,7 +10,7 @@
- 允许修改:
- `apps/ai-game-creator-shell/scripts/channel-identity.mjs`(新增,渠道身份单点定义)
- `apps/ai-game-creator-shell/scripts/build-release.mjs`、`build-macos-ci.mjs`、`check-config.mjs`、`agent-swarm-test-chat.mjs`
- `apps/ai-game-creator-shell/scripts/build-release.mjs`、`build-macos-ci.mjs`、`check-config.mjs`
- `apps/ai-game-creator-shell/src-tauri/src/main.rs`、`src-tauri/src/windows.rs`、`src-tauri/src/config.rs`
- 对应测试:`build-release.test.mjs`、`prepare-macos-codex.test.mjs`
- 文档:AGC 更新主规范、共享记忆与本计划对
@@ -47,3 +47,10 @@
- 风险:非默认渠道首次以新身份安装,老 `dev` 用户不会自动迁移本地数据。回滚点:渠道身份只影响非默认渠道构建,撤销该渠道的构建产物即可,仓库侧无数据迁移。
- 风险:窗口标题改为构建期产品名后,标题不再等于配置里的字面量。回滚点:去掉 `main.rs` 的标题覆盖调用,行为回到配置标题。
- 风险:ACL managed 识别放宽到前缀族。回滚点:`is_game_creator_packaged_app_data_leaf` 收紧回单一直线值,但非默认渠道的提权修复会重新失败关闭。
## 2026-09-23 追加:dev 渠道展示名统一
- `dev` 渠道继续复用 `world.genarrative.ai-game-creator`,保证既有安装、升级链和 AppData 路径不变;展示名统一为 `陶泥儿开发版`。
- `channel-identity.mjs` 是展示名单一来源。发布构建将同一 `productName` 同时注入 Tauri 安装配置、原生窗口标题和 `VITE_AGC_PRODUCT_NAME`;React 自绘标题栏及关于/运行时配置展示从该注入值读取。
- 因此 Windows NSIS 默认生成的快捷方式、开始菜单/卸载注册表展示名随 Tauri `productName` 变为 `陶泥儿开发版`;未新增自定义注册表或快捷方式实现。
- 静态 Tauri 基线配置同步为 `陶泥儿开发版`,本地壳与正式 `dev` 包的显示名保持一致。
@@ -38,6 +38,6 @@ Parent Milestone: `【里程碑】AGC项目定时快照上传-2026-09-17.md`
## 风险与回滚
- 上传体积与带宽:首轮全量可能很大,先设单文件与单次同步总量上限并把超限项记入跳过清单;不静默截断。
- 数据出境边界:只上传项目目录内普通文件,排除 `.agent/runtime`、`.agent/logs`、`.git`、构建产物与临时文件;凭据类文件不在白名单内。
- 数据出境边界(2026-09-22 修订):只上传项目目录内普通文件。`.agent` 承载项目身份与 Agent 状态,整目录上传(含 conversations、logs、runtime、checkpoint、`agent.db`);`.git`、构建产物、临时文件与凭据类文件仍不在白名单内。该修订只作用于快照同步,项目索引与 checkpoint 继续排除整个 `.agent`。
- 服务端未配置 bucket 时客户端必须失败关闭,不能把本地索引推进成"已同步",否则后续同步会漏传。
- 回滚:客户端可停用触发接线(保留模块与测试)即可回到无上传行为;服务端路由与配置项可单独移除,不影响既有 OSS 前缀与错误报告链路。
@@ -0,0 +1,63 @@
# 引用粘贴解析实施计划
对应:[引用候选由宿主注入](../../adr/【ADR】引用候选由宿主注入-2026-09-22.md)(含 2026-09-22 修订节);决策记录见 `docs/project-memory/shared-memory/decision-log.md` 的 2026-09-22 条目。上一步的重构计划见 [引用输入区重构与宿主注入](【实施计划】引用输入区重构与宿主注入-2026-09-22.md),那份明确不含本项。
## 一句话交付与验收判据
把从用户消息气泡(或任何同口径文本)复制出来的 `@显示名` / `$名称` 粘贴进引用输入区时,原位重建同顺序的引用芯片;其余文字逐字保留。
验收判据:
1. 粘贴文本 → 引用:`@显示名` 与 `$名称` 逐字命中当前宿主注入的 provider 候选时原位换成芯片,一次 Ctrl+Z 整体回退,token 之外的每个字符(含换行与空白)原样保留。
2. 逐字一致、不做兼容别名:不认 `@hero.png`、`resourceId`、大小写变体、全角 `@`;token 前后必须是行首 / 行尾或空白。
3. 宁可不成芯片也不能认错:同名多候选(同一个 token 对应多条引用身份)一律按文本保留;未命中的 token 静默保留,不提示、不猜路径或文件名。
4. 只有真的解析出引用时才接管:同 namespace 的 `application/x-lexical-editor` 负载、不含 token 的纯文本、图片文件粘贴一律放行编辑器默认导入,现有粘贴行为逐字不变。
5. 附件与运行画面区域不参与粘贴解析(静默 provider 没有候选),它们的 token 粘贴时按文本保留。
6. Skill 目录冷启动不阻塞粘贴:`lookup()` 是纯函数,目录没到就是空数组——冷启动时粘贴 `$名称` 保留为文本(不等待、不补读),用户敲过一次 `$` 后即可解析。
## 流程判定
本次是共享组件的一处行为增量 + provider 契约加两个可选能力,不动 schema、不动公开 API/DTO、不动后端;按轻量流程只建本实施计划,不新建主规范与里程碑规范(与上一步重构同一判定)。
## 提交切分
1. **反解析口径**:`resourceReferences.ts` 新增 `buildContentFromPastedText(text, references)`(粘贴侧唯一反解析;与 `buildContentFromTextTokens` 同一套边界规则),配规则矩阵单测。
2. **provider 契约**:`reference-source/types.ts` 把菜单查询改名为 `fuzzyLookup(query)`、新增精确查找 `lookup()`(两者共用同一份候选来源);`resourceReferenceProvider` 给全量可提及候选,`skillReferenceProvider` 给去重后的 Skill 候选。
3. **输入区接管**:`ResourceReferenceInput` 注册 `COMMAND_PRIORITY_CRITICAL` 的 `PASTE_COMMAND`,只在解析出引用时 `preventDefault` 并在一次 `editor.update`(`PASTE_TAG`)内按选区插入;配集成用例。
4. **文档**:`CONTEXT.md` 术语、本计划、ADR 修订节、`docs/【功能说明】AGC聊天素材引用-2026-09-08.md` 与决策记录。
## 验证
定向:`npx vitest run apps/ai-game-creator-shell/tests/resourceReferences.test.ts apps/ai-game-creator-shell/tests/referenceSourceProviders.test.ts apps/ai-game-creator-shell/tests/resourceReferenceInput.test.tsx`;全量:`npm run typecheck`、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`。真实客户端手感(粘贴时的候选菜单位置、大段文本粘贴观感、Tauri 剪贴板只带图片时的行为)留待真机验收。
## 风险与回滚
- 解析接管会绕过 Lexical 的 `text/html` 富文本导入:只在文本里真的解析出引用时接管,取舍已在上一条判据里限定;要完全避开富文本场景可以后续按 `clipboardData.types` 再收窄。
- 气泡里若出现与素材同名的 `@区域标签` / `@附件名`,粘贴会被认成素材引用——纯文本无法区分,属已知取舍。
- 回滚按提交粒度 revert;canonical content 形状、provider 的既有能力与出站文本口径都不变。
## 执行状态(2026-09-22)
已完成:`buildContentFromPastedText` 与规则矩阵单测(命中 / 未命中 / 相邻中文 / 扩展名 / 大小写 / 全角 / 同名歧义 / 重复出现 / 换行 / 与显示口径互为逆运算);provider 的 `fuzzyLookup` / `lookup` 及用例;输入区 `PASTE_COMMAND` 接管与 5 条集成用例(粘贴重建芯片、未命中保持字面、纯文本走默认导入、Lexical 负载让位、Skill 冷启动保持字面且敲过 `$` 后可解析);文档同步。
未做(本次范围外):斜杠命令 `/` 解析、拖拽文本(drop)、附件 / 运行画面区域 / 文件路径 / URL / 剪贴板图片的解析、复制侧 `text/plain` 形态调整、扩展安装卸载后的目录即时失效。
## 追加执行状态(2026-09-23):前缀重叠按「引用名无空白」收口
自动评审留下的唯一破坏性项(`@hero` 与 `@hero v2` 互为前缀时粘贴会多插一枚短名芯片)不改反解析,改为把不变量前移到引用名:
- `resourceReferences.ts` 新增共享 `normalizeMentionName(value)`(内部空白折 `-`、裁首尾),素材显示名(`resourceDisplayName`)、Skill 名(目录读入与 `toReference` / `mentionToken`)、附件名(导入映射与 `toReference` / `mentionToken`)与两个 token 投影(`chatReferenceMentionToken`、`directCodexContentToPromptText`)统一过这一份;token 层重复归一化是幂等的。
- 名称里不做 `resourceId` 兜底这一条按评审校正:词干为空(`.env` / `.gitignore` 这类整名就是扩展名的文件)时 `resourceDisplayName` 回退 `asset.id`,回退值同样过 `normalizeMentionName`;`normalizeMentionName` 自己仍只做归一化。
- 代价与取舍:`hero v2` 与 `hero-v2` 归一化后同名时走既有的「同名多候选按文本保留」;改动前生成的旧文本里的 `@hero v2` 不再解析。
- 验证:`normalizeMentionName` / 显示名 / token 口径单测 + 前缀重叠回归用例(归一化后粘贴只剩正确的那一枚芯片)+ provider 两处用例;把 `normalizeMentionName` 变异成恒等后新增用例全红。
## 追加执行状态(2026-09-23 第二轮自动评审):空名字与插入兜底收口
第二轮自动评审的四个问题逐个提交处理,落点都在 `apps/ai-game-creator-shell`:
- 显示名不再可能为空:`resourceDisplayName` 在文件名词干为空(`.env` / `.gitignore`)时回退 `asset.id`,回退值同样过 `normalizeMentionName`(提交 `4e90465c4`)。这条校正了上一节「不做兜底」的写法,决策记录与功能说明同步。
- 粘贴解析跳过退化 token:`buildContentFromPastedText` 组装候选时跳过 `token.length <= 1` 的条目,裸 `@` / `$` 不再认领正文里的触发符(提交 `3f9c698a6`)。
- 粘贴接管改成「插入真的发生之后」才 `preventDefault`:`$insertContentAtSelection` 返回「插进去没有」,插入为空时放行默认粘贴;编辑器已在更新中(回调被排队)时按原口径先接管(提交 `3391ecf7d`)。
- 插入兜底不再留空:provider 的 `toReference` 与 `mentionToken` 都答不出来的 part 退到新增的 `contentPartText`(通用文本形态,资源落 `@resourceId`)。此前这一支会什么都不插,是「粘贴内容逐字保留」唯一的例外分支;现在没有例外。新增单测覆盖四类 part 的文本形态,新增输入区集成用例证明被破坏的 provider 契约下这段粘贴仍按文本落下,去掉兜底即变红。
- 归一化撞名(`hero v2` 与 `hero-v2` 折成同一个 token)只在解析侧兜住、候选菜单不提示冲突,这一条涉及「哪些素材能被 @ 到」的产品取舍,未改代码,留给下一轮决定。
- 润色回写与粘贴共用同一条落点兜底:新增 `mentionTokenOrText`(provider 的 `mentionToken`,拿不到就退 `contentPartText` 的通用文本形态),润色回写的候选扫描、整根替换的落点与粘贴插入的兜底都走它。由此润色回写不再有「provider 答不出 token 的 part 直接消失」和「整根替换时 part 解析不出引用就整条吃掉」两条静默丢弃路径;初始草稿(`applyContent`)与润色回写(`applyPolishedTextToRoot`)共用 `applyContentToRoot`,所以恢复出来的草稿里已解析不出的引用现在落成 `@resourceId` 文本而不是被吃掉。
@@ -0,0 +1,63 @@
# 引用输入区重构与宿主注入实施计划
对应:[引用候选由宿主注入](../../adr/【ADR】引用候选由宿主注入-2026-09-22.md);决策记录见 `docs/project-memory/shared-memory/decision-log.md` 的 2026-09-22 条目。本计划不含粘贴解析。
## 一句话交付与验收判据
把 `ResourceReferenceInput` 拆成「纯输入区 + 宿主注入的引用 provider + 独立的选择器面板」,并让附件芯片成为本轮附件的唯一事实源。
验收判据:
1. 输入区不 import 任何具体引用种类,也不判断 `part.type` / `reference.type`:候选、身份解析、正文文本形态都经注入的 provider。
2. 宿主按需选择性注入:只注入资源 → 只有 `@`;资源 + Skill → `@` 与 `$`;未注入的宿主连触发符都不存在,不需要额外的可见性开关。
3. 素材选择器面板独立成组件、自己拿数据、由宿主渲染;输入区不再有 `versions` / `activeVersionId` / `showTriggerButton` / `onReferencePickerOpen` / `skills`。
4. 附件导入成功后以芯片进入正文,失败或异常不插入;控制器不再持有附件数组,「待发送附件列表」删除;上限 8 按草稿中的附件芯片数计算;导入进行中禁止发送。
5. 行为零变化:改名后芯片显示名自动刷新、打开面板前重读清单、`@`/`$` 候选与键盘交互、上限与失败提示文案逐条一致;唯一例外是已裁决的 Skill 可见性缺陷修复(非 DirectProject 宿主不再出现 `$`)。
## 流程判定
本次是无行为变化重构加两条已裁决缺陷修复(Skill 可见性、附件单一事实源),按轻量流程只建本实施计划,不新建主规范与里程碑规范;若评审要求补 SDD 里程碑,再单独补。
## 提交切分
1. **接口与 provider 工厂**:新增 `features/project-workspace/reference-source/types.ts`(`ReferenceProvider`)与四个工厂(资源 / Skill / 附件 / 运行画面区域),配规则矩阵单测:触发符有无、`match` 过滤、`toReference`、`refresh`(改名 / 删除 / 恒等)、`mentionToken`。
2. **输入区接入注入**:输入区改为按 `providers` 派生候选菜单(不再写死两个触发符)、按数组顺序取第一个非空回答做草稿回填与改名刷新,删除 `skills` prop;测试夹具(22 处 `assets={assets}`)同批迁移为注入 provider。
3. **面板外移**:素材选择器面板与缩略图预览搬进独立组件,宿主渲染并接线(`onConfirm → insertReferences`),输入区删除相关 props 与其死 CSS 规则(`styles.css` 里 `resource-reference-*` 共 78 条,逐条核对哪些随面板离开而失效)。
4. **附件并入联合**:附件成为 `ChatReference` 成员,节点层收敛为单一节点类型,附件 chip 的 DOM 契约逐字保留(`data-attachment-reference`、`data-attachment-status`、`title`、移除按钮 `aria-label`)。
5. **附件单一事实源**:`uploadFiles` 改为返回导入结果,composer 在导入成功后插入芯片、失败不插入;删除控制器附件状态、`ComposerPendingAttachments` 与提交时的附件 parts 拼接;上限改为按草稿芯片数计算,导入进行中禁止发送。
6. **显示口径与文档**:`directCodexContentToPromptText` 的 token 前后补空白(不重复)并加 `// TODO we will rewrite this with ref as component later.`,显示名反查改由调用方注入;同步更新 `docs/【功能说明】AGC聊天素材引用-2026-09-08.md`(`ResourceReferenceInput` 共用口径、`@`/`$` 可见性、附件入口与上限口径都会变)。
`resource-reference-*` 的 CSS 类名重命名(78 条规则 / 15 个文件 / 22 处测试断言)留作最后一个独立机械提交,必须与 `RESOURCE_REFERENCE_OVERLAY_SELECTOR` 常量同批修改。
## 验证
每个提交后跑定向用例:`npx vitest run apps/ai-game-creator-shell/tests/resourceReferenceInput.test.tsx`,加上该提交触达的宿主用例(聊天 composer / 策划输入盒 / 画布生成面板 / 资源卡快速编辑)。全部完成后跑 `npm run typecheck`、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`,再按验收判据第 5 条逐条对照行为。真实客户端手感(候选菜单位置、面板内滚轮、附件插入位置)留待真机验收,未执行前按未验证项记录。
## 风险与回滚
- 漏注入某个宿主会让该宿主静默失去候选;fail-closed 语义正确,但必须在用例里逐宿主覆盖。
- `LexicalTypeaheadMenuPlugin` 由两个写死实例改为按 provider 派生后,Enter 让位与 Escape 关闭口径要重新覆盖(`mentionMenuOpenRef` 需聚合成「任一菜单开着」)。
- 附件插入位置在异步导入期间可能漂移:导入失败一律不插入;文档或选区已改动时退到草稿末尾。
- 删除 `ComposerPendingAttachments` 会连带影响样式与既有断言,属于预期内的行为收敛,需在提交说明里写清。
- 回滚按提交粒度 revert;无数据迁移,canonical content 形状不变。
## 执行状态(2026-09-22)
已落地的提交内容:
1. 接口与 provider 工厂:`features/project-workspace/reference-source/types.ts` + 四个工厂(资源 / Skill / 附件 / 运行画面区域),规则矩阵单测落在 `apps/ai-game-creator-shell/tests/referenceSourceProviders.test.ts`(触发符有无、`match` 过滤 / 去重 / 截断、`toReference`、`refresh`(改名 / 删除 / 恒等)、`mentionToken`、`isReady`、Skill 目录「首次 `match` 才读、失败可重试」)。
2. 输入区接入注入:`providers` / `inputActions` / `submitSuppressed` 三个通用接缝;`skills`、`assets`、`versions`、`activeVersionId`、`showTriggerButton`、`openPicker` 全部删除;`ProviderMentionMenu` 按带触发符的 provider 派生(Enter 让位按 `mentionMenuOpenRef` 聚合)。
3. 面板外移:`ResourceReferencePicker` + `ResourceReferencePickerAction` 由宿主渲染并接线(`onInsert → insertReferences`、`onOpenChange → submitSuppressed`),三个宿主的 `@` 触发钮位置逐处保持原样。
4. 附件并入联合:附件成为 `ChatReference` 成员,编辑器只剩 `ResourceReferenceNode` 一种节点,附件 chip 的 DOM 契约逐字保留。
5. 附件单一事实源:`uploadFiles(files, draftAttachmentCount)` 只返回导入成功的附件、失败不插入;控制器附件状态、`ComposerPendingAttachments`、提交时的附件 parts 拼接都删除;上限按草稿芯片数计算;导入进行中禁止发送。
6. 显示口径与文档:`directCodexContentToPromptText(content, resolver)` 的 token 前后各补一个空白(不重复)并加 `// TODO we will rewrite this with ref as component later.`;显示名反查改由 `resourceLabelResolver(assets)` 注入;同步更新功能说明、ADR 与决策记录。
顺带清理:`ProjectChatComponentProps.activeVersionId` 在策划输入盒不再有 `@` 触发钮之后已无消费方,随本次删除(工作台壳自己的那份仍归 `ProjectDevelopmentView`)。
验证结果:`npx tsc -p tsconfig.json --noEmit`、`npm run typecheck`、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check` 全通过;定向用例(`referenceSourceProviders`、`resourceReferenceInput`、`chatPromptPolish`、`resourceReferences`、画布生成面板、快速编辑、资源卡实时集成、`appSurface` 215 项、工作台壳清单合并 / 窗口同步、资源标签统计刷新、版本切换 / 替换)全绿。整仓 `npx vitest run` 仍有与本改动无关的既有环境失败(jsdom 缺 `localStorage`:`clientApi` / `clientAuthStorage` / `clientHttp` / `projectCreationDirectory` / `recentProjectsHook` 与 `src/components/image-editor/*` 若干),失败集合与本改动触及的模块不相交。
未完成项:
- `resource-reference-*` CSS 类名重命名(78 条规则 / 15 个文件 / 22 处断言)仍是独立机械提交,须与 `RESOURCE_REFERENCE_OVERLAY_SELECTOR` 同批修改;本次未动,输入区与面板的既有类名逐字保留。
- 真实客户端手感(候选菜单位置、面板内滚轮、附件插入位置、`@` 触发钮落点)未做真机验收,按未验证项记录。
- 粘贴解析仍不在本次范围(未来接入点是 provider 的 `mentionToken` 与既有 `buildContentFromTextTokens`)。
@@ -0,0 +1,128 @@
# 【实施计划】游戏分发阶段A领域合同
| 字段 | 值 |
| --- | --- |
| Milestone | `docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md` 阶段 A |
| Status | in_progress |
| Owner | Codex |
## 修改边界
- 允许修改:`module-game-distribution` 新领域 crate、workspace 声明、Rust shared-contracts DTO、领域单测及文档证据。
- 明确不修改:发行域名与 CDN、旧 public work/runtime、AGC native 上传命令、网页发布表单。
## 行为范围
- 定义游戏身份、版本状态、包摘要、owner 权限、幂等 key 冲突和 publication revision CAS。
- 领域服务先以纯内存仓储验证状态合同,再接入已冻结的 SpacetimeDB 游戏/版本/幂等收据表;不把内存仓储作为生产事实源。
- 阶段 A 只提供身份、版本、真实包确认和状态回读基础,不提供“发布成功”或公开可玩状态,不绕过人工审核。
## 验证命令
1. `cargo test -p module-game-distribution --manifest-path server-rs/Cargo.toml`
2. `cargo fmt --all --manifest-path server-rs/Cargo.toml -- --check`
3. `npm run check:encoding`
4. `git diff --check`
## 风险与回滚点
- API 接入前端前必须完成 SpacetimeDB facade 和 schema 门禁;任何失败都保留旧版本数据,不覆盖已有业务表。
- 内存仓储只用于行为合同测试,不得被前端当作正式数据源。
## 已完成证据(当前切片)
- `module-game-distribution` 已落地游戏/版本状态机、owner 校验、幂等摘要冲突、上传确认、验证失败重试、审核 CAS、撤回和管理员暂停的纯领域服务与内存测试仓储。
- 同一 crate 已加入不执行上传代码的 ZIP 包校验:根 `index.html`、路径穿越/大小写冲突、符号链接、加密文件、敏感文件、嵌套压缩包、单文件/总展开量/压缩比上限和逐文件 SHA-256 清单;并新增按白名单内容类型取单个发行资源的读取层。
- Rust/TypeScript 跨端 DTO、SpacetimeDB 三张持久表、migration 白名单、生成 bindings、typed facade 和游戏分发 HTTP 路由已接入,覆盖创建、上传、送审、审核、下架、公开目录与详情。
- `api-server` 新增发行网关 `GET /api/game-distribution/releases/{gameId}/{assetPath}`:只服务当前已公开版本,未知扩展名 404,附带 nosniff / CORP / HTML CSP,拒绝带 Cookie 请求,并按对象键做有界包缓存。
- 重复发布复用游戏身份:游戏表末尾新增可空 `local_project_id`,`create_game_distribution_game` 在 owner + local_project_id 命中时复用既有 `gameId` 并只新增版本;`localProjectId` 经 shared-contracts(Rust/TS)透传,AGC 发布链路写入并在缺失时于发请求前失败关闭,api-server 对短标识做路径分隔符与控制字符校验。
- 后台新增 `#game-distribution` 游戏审核页:待审列表、通过(要求 HTTPS 发行入口)、拒绝(要求理由)、幂等键与列表刷新;对应 `editor-showcase` Tab 权限映射、API client 与页面测试已加入。
- 路由级测试证明发行网关、公开目录与登录发布路由确实挂载:带 Cookie 的发行请求 403、未知扩展名 404 且不触达对象存储、无 Bearer 的发布请求 401、目录在无数据库时返回 502 而不是 404。
- 自动化证据:`cargo test -p module-game-distribution`(11 passed)、`cargo test -p api-server` 游戏分发模块(9 passed)与全量 `cargo test -p api-server`(1069 passed / 6 ignored)、`cargo test -p shared-contracts`、admin-web Vitest(149 passed,含新增游戏审核页与 client 用例)、前端游戏定向 Vitest(15 passed)、AGC `typecheck` 与 `local_project_export` Rust 测试(7 passed)、Vite build。
- 版本回读、撤回与管理员安全下架闭环:新增 SpacetimeDB `get_game_distribution_version_and_return` / `cancel_game_distribution_version_and_return`(幂等收据 + 公开修订号 CAS)、`spacetime-client` façade、`GET /api/game-distribution/versions/{versionId}`(作者,未知与非 owner 一律 404)、`GET /admin/api/game-distribution/versions/{versionId}`(管理员)与 `POST /api/game-distribution/versions/{versionId}/cancel`(作者,`Idempotency-Key` + `expectedPublicationRevision`);版本私有投影新增服务端派生的 `recoveryAction`(upload/submit/wait/none/reupload/fix_package/fix_metadata)。后台游戏审核页补齐「安全下架」入口(原因输入 + 二次确认弹窗 + 幂等键)。
- 幂等响应语义修正:写操作 procedure 现在把「命中既有幂等收据」如实回传为 `replayed: true`(此前所有写操作都固定返回 false),覆盖创建游戏/版本、确认包、上传失败、送审、审核、撤回、下架与暂停。
- 网页端恢复与撤回:`/games/publish` 在创建版本后写入带 owner 的发布草稿,窗口关闭或上传中断后同一账号可见「继续上传/继续送审」并复用原 `versionId`,换账号只忽略不读取;`/games/mine` 对可撤回状态提供二次确认的「撤回审核」,成功后刷新状态。
- AGC 发布真实链路回归测试:新增 `apps/ai-game-creator-shell/tests/gameDistributionPublishLive.test.ts`(默认跳过,设置 `GENARRATIVE_AGC_PUBLISH_E2E_BASE_URL` 指向运行中的 api-server 才执行)。它不 mock 请求层,而是走真实的 `clientApi`/`clientHttp` 调用 `publishLocalProjectGame`,覆盖本地导出包读取、创建游戏、同 `localProjectId` 复用游戏身份、真实 ZIP 上传、送审、版本回读与 `my-games` 聚合,把「AGC 一键发布」从请求组装单测提升到真实后端链路证据。
- AGC 客户端回归修复:导出试玩包后自动打开的发布面板是焦点陷阱模态,会让既有 `appSurface` 导出快捷操作用例静默失效;用例改为先断言面板出现、关闭后再继续,`appSurface.test.ts` 552 项(535 passed / 17 skipped)恢复全绿,并顺手补上该面板此前缺失的行为断言。同一轮修掉 `gameDistributionPublish.test.ts` 的模块 mock 缺少 `getClientServerBaseUrl` 导出的既有失败。
- 发行来源(每游戏独立 origin)部署工件:新增 `deploy/nginx/genarrative-release-origin.conf` 与 `scripts/check-release-origin-config.mjs`(`npm run check:release-origin-config`)。模板按命名捕获 `game_id` 把 `https://<gameId>.games.<域>/` 映射到该游戏的 `index.html`、其余路径映射到发行网关前缀,边缘拒绝并清空 Cookie,不代理平台 API/后台/SPA;门禁逐条校验模板约束、交叉检查发行网关仍在设置 `nosniff`/CORP/CORS/CSP 与 Cookie 403,并在本机渲染临时配置跑 `nginx -t`。
- AGC 发布面板组件测试:新增 `apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx`,覆盖打开时预填资料、发行包摘要、提交入参(项目路径/发行包/资料/幂等键)、连续点击只提交一次、服务端失败保留面板与无 Tauri 宿主/缺包时失败关闭。
- 视觉与交互巡检修复:真实栈 + headless Chromium 逐页巡检 `/games`、`/games/detail`、`/games/play`、`/games/mine`、`/games/publish`(桌面 1440×900 与移动 390×844)。修掉两处:① 详情页「游玩方式」只读公开投影里恒为空的 `version.controls`,作者声明的 `inputModes` 被忽略,现在优先展示「键盘 · 鼠标 · 触屏」再退回自由文本;② 桌面端 hero 过高导致首屏看不到任何游戏卡片,改为仅桌面压缩 hero 高度,首排卡片进入 900px 视口。
- 容量与限额边界(阶段 D 证据):新增真实栈脚本覆盖 5 类包——① 99.0 MiB(两个 50/49 MiB 文件 + `index.html`,均在单文件/展开量/压缩比限制内)上传 200、耗时 11.8s、api-server RSS 81.6→378.0 MB(峰值 +296 MB)后回落 83.2 MB,随后送审 202 进入 `pending_review`;② 声明的 `packageBytes` 为 101 MiB 时在创建版本即被拒 413 `PAYLOAD_TOO_LARGE`("发行包大小超出限制",不落版本);③ 声明合法但请求体 101 MiB 时被请求体限制拒绝 413(0.03s、内存零增长、版本保持 `awaiting_upload`/`upload`,可原版本重传);④ 60 MiB 全零文件压到 61 KB(压缩比约 1000)拒绝 422 `PACKAGE_VALIDATION_FAILED`(0.01s,`upload_failed`/`reupload`);⑤ 单文件 65 MiB 与 10,001 个文件同样 422(0.05s / 0.02s)。失败包都不出现在公开目录。
- 发行包 PUT 受控重试:`platform-oss` 新增 `put_internal_object_with_retry`(复用既有 `oss_error_is_retryable` 分类:传输/超时/connect、408、429、5xx 与 400+RequestTimeout 可重试,确定性 4xx 不重试;body 只转一次 `Bytes` 供各 attempt 复用),发行包上传接入 3 次尝试 + 250/500ms 退避。触发原因:本机实测 99 MiB 单次 PUT 三次里出现过一次 `UPSTREAM_ERROR`(`请求 OSS 失败:error sending request`),客户端需要白传整包。
- 网页端「为既有游戏发布新版本」:发布页新增 `updateGameId` 更新模式(按作者中心的最近版本回读冻结资料与公开修订号预填,提交时跳过创建游戏、直接在既有 `gameId` 下创建不可变新版本),作者中心新增「发布新版本」入口,壳层支持 `/games/publish?game=<gameId>`。浏览器真实链路:从 `/games/mine` 点「发布新版本」→ `/games/publish?game=…` 预填并显示「为《…》发布新版本 v2」→ 选包提交 → 该 `gameId` 下出现 `versions [(2, pending_review), (1, published)]`,公开入口仍指向并返回 v1 内容。此前网页端只能新建游戏,更新无法沿用 `gameId`,与里程碑「更新沿用相同 gameId」不符。
- 全生命周期回归(真实栈,11 步):待审公开不可见(目录不返回 + 详情 404)→ 管理员通过 v1 后目录/详情/网关可玩 V1 → v2 待审期间网关仍是 V1 → v2 被拒后仍是 V1 → 重复批准 `rejected` 版本返回 409(`rejected` 是终态,必须新建版本,与主规范一致)→ 新建 v3 通过后公开入口切换为 V3 → 作者回读被拒版本为 `rejected`/`fix_metadata` → 作者下架后目录/详情/网关全部关闭。同一轮修掉 5 条既有的壳层导航用例失败(游戏分发新增「游戏」页签与移动底栏后,测试仍断言旧导航集合)。
- 边界、越权与隔离运行时证据(真实栈):① 跨作者越权——作者 B 对作者 A 的版本上传 403、送审 403、撤回 404、回读 404,且 A 的版本状态未被改变;② 过期 CAS——管理员用错误 `publicationRevision` 批准返回 409,正确修订号才通过;③ 包边界——符号链接条目、`.env` 凭据文件、嵌套 ZIP 分别返回 422 `PACKAGE_VALIDATION_FAILED`,版本落到 `upload_failed`/`reupload`;④ 私有对象直取——按对象键直接 GET 私有 OSS 地址返回 **403**,发行文件只能经网关读取。
- 浏览器沙箱隔离实测:发布一个自检探测包(内联脚本主动探测并 `postMessage` 回传结果),在 `/games/play` 里点「开始游戏」后收到 `{ origin: "http://127.0.0.1:10001", cookie: "throw:SecurityError", storage: "throw:SecurityError", parentDom: "throw:SecurityError", externalFetch: "throw:TypeError" }`——主站 Cookie、localStorage、父页面 DOM 全部不可访问,外站 fetch 被 CSP `connect-src 'self'` 阻断。键盘可达抽查:桌面端 Tab 顺序覆盖导航 → 搜索 → 下载客户端 → 账户,游戏分发自己的控件(发布游戏/我的游戏/分类页签/游戏卡片)聚焦时都有可见的浏览器默认焦点环。同一探测包还验证了音频链路:沙箱内 `fetch('assets/beep.wav')` 返回 `status:200 type:audio/wav`,WebAudio `decodeAudioData` 成功解出 `0.40s / 48000Hz`(`AudioContext.state = suspended`,与浏览器“首次播放需帧内用户手势”的策略一致,玩家进入游戏后的第一次交互即提供该手势)。游戏内真实触屏输入也已验证(见下一条)。
- 上传中断与进程重启恢复(真实栈,两次独立实验):60 MiB 包 PUT 进行到 ~2.5s 时 `SIGKILL` api-server,客户端拿到 `RemoteDisconnected`(等价于响应丢失);重启后该版本仍是 `awaiting_upload`/`upload`,没有半确认状态。① 用不同字节重传返回 422 `PACKAGE_VALIDATION_FAILED`(`InvalidArchive`)并转 `upload_failed`/`reupload`;② 用固定时间戳构造的确定性 60 MiB 包重传**相同字节**返回 200(7.64s),版本转 `uploaded`/`submit`,随后送审 202 进入 `pending_review`。两次实验中游戏都未出现在公开目录。
- 发布开关(回滚/事故能力):新增 `game-distribution:publish` 灰度 gate(复用现役灰度配置机制与后台入口)。默认开放;`enabled=true` 时按白名单/用户标签/灰度百分比放行,`rolloutPercent=0` 且无白名单即全部关闭。关闭时作者写入与管理员批准返回 `503 GAME_DISTRIBUTION_PUBLISH_DISABLED`,而目录、详情、版本回读、发行网关、`/my-games`、审核队列读取、拒绝审核与安全下架继续可用;开关读取失败按关闭处理。路由级测试覆盖「默认放行 / 全关拦截 / 读取不受影响 / 白名单放行」,真实栈验证同口径:关闭后作者写入 503、目录/详情/网关/my-games 全 200、管理员批准 503 而拒绝 200、白名单用户可发布、重新开放后恢复。
- 游戏内真实触屏输入(本机 Chromium + CDP `Input.dispatchTouchEvent`,390×844 视口):在 `/games/play` 点「开始游戏」后,向 iframe 可视区域坐标派发 `touchStart`/`touchEnd`;`sandbox="allow-scripts"` 且 `ready` 的沙箱内探测包回传 `{touchstart: 1, touchend: 1, pointerdown: 1, pointerTypes: ["touch"], click: 1, target: "pad", touches: 1}`,证明真实触摸事件经平台进入游戏容器并命中目标元素。注意:触摸模拟必须在 iframe 文档创建之前启用,否则文档不会注册 touch 事件支持(首次实测只收到 pointerdown,重载后 touchstart/touchend 才出现)。
- 可观测性(阶段 D「上传失败、校验耗时、审核积压、撤销传播」):`api-server` 的游戏分发模块补齐结构化事件,均带 `request_id`(可 join 访问日志)与 `operation`,不记录 Token、signed URL、完整文件内容或本地路径。事件与字段:`package_confirmed`(game_id/version_id/package_bytes/file_count/sha256_prefix/oss_put_skipped/elapsed_ms)、`package_rejected`(code/reason/uploaded_bytes/elapsed_ms)、`version_submitted`、`version_cancelled`、`review_backlog_listed`(pending_versions/limit)、`review_decided`(decision/admin_user_id/publication_revision)、`game_unpublished`(visibility/publication_revision/active_version_id)、`game_suspended`(admin/reason/publication_revision)、`publish_switch_blocked`/`publish_switch_unavailable`、发行网关 `release_rejected`(debug 级:reason=cookie_present/unsupported_extension/not_public/no_active_version/version_not_published)。真实栈复跑一次完整链路后,8 类事件各出现 1-2 次,字段与耗时均可用(样例:`package_rejected … reason=InvalidArchive uploaded_bytes=9 elapsed_ms=6`、`game_unpublished … visibility=unpublished publication_revision=2 active_version_id=""`)。
- 工程门禁:`npm run check:spacetime-schema`(147 tables)、`npm run check:server-rs-ddd`、`cargo fmt --all -- --check`、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`。
## 运行时证据(2026-09-20,真实本地栈)
`npm run dev` 在无冲突端口段启动(spacetime `127.0.0.1:10004`、api-server `127.0.0.1:10001`、真实 OSS bucket `xushi-dev`),用 HTTP 全链路脚本验证:
- 管理员登录 + 用户口令注册/登录均走真实路由。
- 创建游戏后,用相同 `localProjectId` 再次创建返回**同一个 `gameId`**(去重在真实数据库上生效)。
- 真实 ZIP(根 `index.html` + `assets/app.js`)上传私有 OSS,服务端重算 SHA-256 / 字节数 / 文件数并确认 `uploaded`。
- 送审返回 `202` 与 `pending_review`;此时公开目录、公开详情都不返回该游戏。
- 管理员用 HTTPS `entryUrl` 审核通过后,目录与详情出现该游戏并带当前版本 `entryUrl`。
- 发行网关返回 `text/html` 与 `text/javascript`;带 `Cookie` 的发行请求 403,未知扩展名 404。
- 作者下架后公开详情回到 404。
- 更新链路:v2 送审期间与 v2 被拒后,公开入口始终停留在 v1 且 v1 内容仍可读取;v3 通过审核后才替换公开版本。
- 真实浏览器(headless Chromium)加载发行网关的 `index.html`:文档渲染、同源 `assets/app.js` 在现役 CSP 下执行成功(`booted=1`、`window.__gd=1`),控制台只剩 favicon 404。
- 浏览器打开 `http://127.0.0.1:10000/games` 能看到已发布游戏卡片,`/games/detail?id=<id>` 渲染标题与「立即玩」。
- 平台页面内嵌游玩:浏览器打开 `/games/play?id=<id>`,点「开始游戏」后 iframe 以 `sandbox="allow-scripts"` 挂载发行网关地址,`game-player-frame--ready` 置位,控制台无 CSP/CORP 拦截,api-server 访问日志显示该版本 `assets/app.js` 返回 200。
- 非生产环境允许 http 回环 `entryUrl`(与前端 `normalizeGameEntryUrl` 同口径),生产仍只接受 HTTPS;这样本地无需 TLS 即可验证内嵌游玩。
- 作者中心 `/games/mine`:SpacetimeDB 新增 `list_owner_game_distribution_games_and_return`(owner 只从认证主体派生,每游戏最多回 10 个最近版本),`api-server` 暴露 `GET /api/game-distribution/my-games`;浏览器里用真实作者 token 打开该页,能看到「已公开」状态与「下架」按钮,点击后状态变为「未公开」、按钮消失,公开目录同步不再返回该游戏。
- 网页端发布入口 `/games/publish`:浏览器里用真实文件选择框上传 ZIP(根 `index.html` + `assets/app.js`),api-server 访问日志记录 `POST /api/game-distribution/games` 200 → `POST /api/game-distribution/games/{gameId}/versions` 200 → `PUT /api/game-distribution/versions/{versionId}/package` 200 → `POST /api/game-distribution/versions/{versionId}/submit` 202;随后同一账号在 `/games/mine` 看到该游戏为「未公开 / 版本 v1 · 审核中」,公开目录不返回它。
- 版本回读/撤回真实链路(`npm run dev`,api-server `127.0.0.1:10001`、SpacetimeDB `127.0.0.1:10004`):作者送审后 `GET /versions/{id}` 返回 `pending_review` + `recoveryAction=wait`;其他账号与未知版本都是 404;过期修订号撤回 409;正常撤回 200 且状态变 `cancelled`、`recoveryAction=none`;同 key 同请求重放返回 `replayed=true`,同 key 不同摘要 409;撤回后公开目录不返回该游戏。
- 主链路回归(同一真实栈):正式发布 `replayed=false`、重复送审 `replayed=true`;待审期间目录不可见;管理员读版本 200、审核通过后状态 `published` 且目录与详情返回 `currentVersion.entryUrl`;发行网关 `index.html` 200、带 Cookie 403、未知扩展名 404。
- AGC 发布真实链路(同一真实栈):`GENARRATIVE_AGC_PUBLISH_E2E_BASE_URL=http://127.0.0.1:10001 npx vitest run apps/ai-game-creator-shell/tests/gameDistributionPublishLive.test.ts` 通过——AGC 发布函数依次完成创建游戏、创建版本、上传 ZIP、送审,返回 `pending_review`;版本回读为 `pending_review`/`wait`;不带旧幂等键的第二次发布复用同一 `gameId` 并生成 v2;`/my/games` 的 `latestVersion` 指向 v2。
- 非法包真实链路(同一真实栈):缺根 `index.html` 且含 `../` 条目的 ZIP 上传返回 422(包校验错误),版本进入 `upload_failed` 且 `recoveryAction=reupload`,此时送审被拒(409)、公开目录不返回该游戏,作者仍可撤回该版本重新出包。
- 发行来源真实边缘验证(同一真实栈 + 本机 nginx 1.28.3,模板渲染到 `~/data/tmp` 后监听高位端口):`Host: <gameId>.games.example.com` 时根路径 200 `text/html`(游戏 `index.html`)、`/assets/app.js` 200 `text/javascript`;带 `Cookie` 403;`/api/auth/me` 404(平台命名空间未暴露);未知 gameId 404;http 301 到 https;响应头经边缘透传后仍是 `nosniff` + CORP `cross-origin` + 无凭据 CORS + HTML CSP + `Cache-Control: public, max-age=60, must-revalidate`。
- 移动视口真实游玩:headless Chromium 以 `390x844` 打开已发布游戏 `/games/play?id=<id>`,页面显示「横屏设计,旋转设备」提示与移动端底部导航,点击「开始游戏」后 iframe 以 `sandbox="allow-scripts"` + `allow="fullscreen"` 挂载发行网关地址并 `ready`,`index.html` 与 `assets/app.js` 均返回 200,控制台无 CSP/CORP 报错;截图存于本轮验证记录(不入库)。
本轮同时修掉三个真实缺陷:Vite dev 代理缺少 `/api/game-distribution` 前缀(本地全部 404);详情页在「已发布但没有 controls」时误显示「暂未发布可玩版本」;发行网关的 `Cross-Origin-Resource-Policy: same-origin` 会让 opaque origin 沙箱内的游戏加载不了自己的脚本(改为 `cross-origin` + 无凭据 CORS,详见 `pitfalls.md`)。
- 资料冻结与展示闭环(封面 + 截图):游戏表末尾新增可空 `cover_object_key` / `screenshots_json`,版本表末尾新增可空 `metadata_json`;创建版本时 api-server 校验「必需封面、≤6 张截图、素材属于当前作者且 `content_type` 为 `image/`」,并从素材记录派生对象键生成冻结快照,审核通过时整体生效到游戏行。
- 公开素材读授权:只有 `published` 且存在有效 `active_version_id` 的游戏,其封面/截图素材才在 `/api/assets/read-url` 获得匿名读授权;其余素材仍按 owner 校验。
- 作者续发复用:版本回读(作者本人)与审核回读(管理员)的版本投影新增 `frozenMetadata`,带回 `coverAssetId` / `screenshots[].assetId`;`/games/publish` 更新模式据此预填封面与截图,不要求作者为沿用封面重新上传;快照缺素材 ID 的旧版本明确要求重新选择封面。公开投影仍只暴露对象键。
- 网页发布资料入口:`/games/publish` 新增「封面与截图」区(封面必需、截图 ≤6、可逐张移除、上传中禁用提交),复用平台图片直传 + confirm 通道;本地校验与服务端口径对齐(缺封面/超 6 张在发请求前拦截)。
- 网页展示:游戏广场卡片与详情页 hero 用 `useResolvedAssetReadUrl` 换签展示真实封面,详情页在存在截图时给出可点击缩略图条(封面 + 截图,选中态 + 键盘可达),换签失败或无素材时静默回退原有渐变占位,不出现空框。
- 本切片验证:`cargo check -p api-server -p spacetime-module -p spacetime-client`;`cargo test -p api-server game_distribution`(17 passed,含新增 `version_detail_payload_exposes_frozen_metadata_to_owner`);`npm run typecheck`;`npx vitest run src/components/game-distribution src/services/gameDistributionClient.test.ts`(46 passed);改动文件 `eslint --max-warnings 0` 与 `npm run check:encoding`。
- AGC 发布面板资料入口:新增 `apps/ai-game-creator-shell/src/services/assetDirectUpload.ts`(凭证 → 直传 → confirm,直传固定走 Tauri HTTP 插件并校验目标主机必须是平台素材存储),面板支持封面必选 + 截图 ≤6、本地预览、逐张移除、上传中禁用发布;缺封面时在创建游戏前失败关闭,服务端「封面」类错误原样展示;同一文件重复提交复用素材 ID 不重复直传。为支持直传,`src-tauri/capabilities/main.json` 的 http 作用域新增 `https://*.aliyuncs.com/*`(配合客户端主机白名单,避免把本地文件发给任意主机)。
- AGC 测试证据:`npx vitest run apps/ai-game-creator-shell/tests/assetDirectUpload.test.ts apps/ai-game-creator-shell/tests/gameDistributionPublish.test.ts apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx apps/ai-game-creator-shell/tests/clientApi.test.ts`(27 passed);`npm --prefix apps/ai-game-creator-shell run typecheck`;改动文件 `eslint --max-warnings 0`。
- 真实本地栈媒体验证(2026-09-20,`npm run dev:api-server` + `dev:web`,api-server `127.0.0.1:12401`、SpacetimeDB `127.0.0.1:12402`、真实 dev OSS 桶):`E2E_ADMIN_USER=… E2E_ADMIN_PASSWORD=… npm run check:game-distribution-media-e2e`(`scripts/check-game-distribution-media-e2e.mjs`)27 项全部通过——真实直传封面与 2 张截图素材、缺封面 400、截图 7 张 400、不存在素材 400(创建游戏与创建版本两处)、他人素材 403、创建游戏/版本/上传/送审(202 + `pending_review`)、作者回读拿到 `frozenMetadata` 且素材 ID 与顺序一致、待审期间公开目录不含该游戏且封面匿名读返回 404、管理员审核通过后公开投影带封面与截图对象键且不含素材 ID、匿名 `read-url` 对封面与截图都返回签名地址、发行网关 `index.html` 与 `assets/app.js` 均 200 且带 nosniff。
- 真实浏览器展示验证(同一栈,headless Chromium `390x844` 与 `1280x900`):`/games` 卡片渲染真实封面签名地址(无占位回退),`/games/detail?id=…` hero 显示封面、缩略图条渲染「封面 + 2 张截图」,点击第 3 张后 `aria-pressed` 与 hero 图片同步切换;移动端布局正常。截图存于 `~/data/tmp/gd3/`(不入库)。
- 网页发布页真实上传验证(同一栈 + headless Chromium):在 `/games/publish` 里用作者会话填写资料、选择真实 PNG 封面与 ZIP 并提交,页面返回「已提交审核」;随后 `my-games` 显示该游戏 `pending_review`,版本回读的 `frozenMetadata.coverAssetId` / `coverObjectKey` 正是本次浏览器上传的素材,证明网页端封面直传(凭证 → OSS → confirm)与冻结链路真实可用。
- 发布资料入口交互打磨:原生 file 控件在上传后清空 value 时会显示「未选择任何文件」,与「已选择」提示互相矛盾;网页发布页与 AGC 面板都改成透明 input 覆盖自定义胶囊按钮(点击命中原生控件,文案显示「选择/更换封面图片」「添加截图」),并在验证中用 `elementFromPoint` 确认点击命中的是 `input[type=file]`。
- AGC 直传能力作用域静态验证:用 `tauri-plugin-http` 同一套 `urlpattern` 解析逻辑验证 `capabilities/main.json` 新增的 `https://*.aliyuncs.com/*` 命中 dev 桶(`xushi-dev.oss-cn-beijing.aliyuncs.com/…`)与生产桶(`genarrative-assets.oss-cn-shanghai.aliyuncs.com/…`)、拒绝 `evil.example.com`;直传失败(含作用域拒绝/网络不可达)统一转成中文可操作文案,并有单测覆盖。桌面端真实执行仍需在装有 AGC 的机器上跑一次。
- 创建游戏同步校验素材归属:此前 `POST /api/game-distribution/games` 只校验「封面必填 / 截图 ≤6 / ID 非空」,素材是否存在与是否属于当前作者要等到创建版本才失败,游戏行会先落一个无效素材 ID;现在创建游戏与创建版本都走 `resolve_owned_game_media`,不存在返回 400、他人素材返回 403。
- 合并 master(`81e0c41f1`,含 `429f991bd` 定向回滚、Godot 原生绑定迁移与策划 Agent 模型控件):回滚提交把混入 master 的游戏分发代码整体删掉,其中 25 个文件在本次合并里属于“双方都改动同一区域之外”的自动删除,直接合并会静默丢功能。处理方式是按“分支补回功能、master 保留迁移”逐类归位:模块注册(`spacetime-module/src/active.rs`)、api-server 路由与权限映射、后台审核页、AGC 发布命令与 payload 类型、`SelectionStage` 的 games 阶段、`ProjectSupervisorView` 的 `overlay` 挂载点、主站路由/标题、Cargo 依赖与锁、vite/vitest 代理与 tailwind source 全部取回分支版本;master 的 Godot C++ 迁移(删除旧 vendored GDExtension、新的 composer 控件样式)保留。合并后用“按文件对比 game-distribution 引用数不得下降”的脚本复核 85 个相关文件,无残留丢失。
- 顺带修掉 master 自带的一处红灯:`src/config/viteProxyConfig.test.ts` 断言 `/api/creation-entry` 会被代理,但 master 的 `vite.config.ts` 并无该代理项,且 api-server 已把 `/api/creation-entry/config` 列入 retired 路由测试(dev 中间件按退役路径返回 404)。测试改为断言“退役路径不进入代理”,与 `isRetiredApiPath` 事实一致。
- 第二次合并 master(`81e0c41f1` → `500835407`,304 个提交:DirectProject 聊天容器重构、Project Supervisor 退役、策划附件导入、CI 隔离编译缓存等):冲突 9 个文件,其中真正需要集成决策的是 AGC 侧——master 把 `ProjectSupervisorView` / `SupervisorChatOnlyView` 整体退役(含我们此前挂 overlay 的挂载点),并且前端不再调用 `export_local_project_package`(`check-config.mjs` 把它登记为 native-only)。处理方式:接受 master 的退役与删除;把发布入口重新接到新架构上——`DirectProjectChatHeader` 新增「发布到游戏广场」按钮(没有回调时不渲染、忙态禁用),`DirectProjectChatView` 透传 `onRequestGamePublish`,`App.tsx` 用工作台壳持有试玩包导出与 `GameDistributionPublishPanel`(沿用 `project.export_package` 权限确认队列),并把该命令从 `check-config.mjs` 的 native-only 清单移回 App invoke。
- 同轮修掉 master 自带的红灯断言:`src/config/viteProxyConfig.test.ts` 曾断言 `/api/creation-entry` 会被代理,但配置无该代理项且 api-server 已把 `/api/creation-entry/config` 列为退役路由,测试改为断言退役路径不进入代理。
- 覆盖率与门禁(合并后):全量 `npm test` 392 文件 / 4371 用例通过(appSurface 重构后 215 用例)、两端与 admin-web typecheck、`cargo check`(api-server / spacetime-module / spacetime-client)、`cargo test`(api-server 游戏分发 17、module-game-distribution 11)、`check:encoding`、`check:doc-index`、`check:rustfmt`、SpacetimeDB schema guard(对比 `origin/master`)。
- 第三次合并 master(`500835407` → `fdc14404b`:DirectProject 回合三态、投影 memo、宿主崩溃后的回合收口、策划对话布局修复、AGC release 每日调度):git 自动合并无冲突,但产生了**静默拼接缺陷**——新加的聊天头 CSS 被并进了 master 策划态分组选择器中间,导致 `.project-chat-topbar-status` 在策划态丢失 `font-size`(`chatDialogFrameLayout` 用例抓到)。修复方式是按 master 原文重建分组规则、把发布入口规则独立成块,并把重复的状态规则删掉。
- 合并后复核:全量 `npm test` 393 文件 / 4374 用例通过,root / AGC / admin-web 三端 typecheck 通过,发布入口(聊天头「发布到游戏广场」→ 试玩包导出 → 发布面板)在新回合三态下保持接线。
- 发布入口灰度下发:`GET /api/runtime/frontend-config` 新增 `gameDistributionPublishEnabled`,复用既有 `is_game_distribution_publish_enabled_for_user`(未配置 `game-distribution:publish` 或 `enabled=false` 时对已登录作者默认开放,显式收紧后只放行白名单/灰度命中,匿名恒为 false),避免前端入口与写入口出现两套判据。网页端 `PlatformEntryActiveFlowShell` 据此隐藏「发布游戏 / 发布新版本」入口,`/games/publish` 直接访问时渲染「发布功能正在灰度中」并提供重新检查;AGC 端 `readGamePublishAvailability` 同样读该字段,只有命中才把发布回调交给 DirectProject 聊天头。
- 灰度验证:`cargo test -p api-server frontend_runtime_config`(6 passed,含新增的 `frontend_runtime_config_game_distribution_publish_is_scoped_to_authenticated_gate`:无 gate 行 → 登录作者 true/匿名 false;`enabled=true` 无白名单 → false;白名单命中 → true;`deny_user_ids` → false;`enabled=false` → true;`rolloutPercent=100` → true)、网页发布页 15 用例(含灰度未命中隐藏表单与「重新检查」放行)、平台壳 18 用例(含广场入口按灰度隐藏/显示)、AGC 发布服务 6 用例(含字段缺失与读取失败按不开放处理)。
- Phaser 一键发布闭环(作者不构建、不打 ZIP):`export_local_project_package` 改为发布前构建——已有可玩入口直接打包,否则解析 `game/` 或项目根的 npm `build` 脚本(`resolve_publish_build_plan`),缺 `game/node_modules` 时先跑 `project.bootstrap`,再走 `project.verify` 的受控 npm 运行器执行 build,最后校验入口并打包;构建或安装失败返回带日志尾部的可操作错误。真实 Phaser 4.2.1 + Vite 7 工程验证:构建产物使用相对引用(`./assets/...`),ZIP 370,969 B 经真实素材直传 + 创建游戏/版本/上传/送审/审核通过后,发行网关 `index.html` 200(323 B)与 `assets/index-DZGg_tPs.js` 200(1,388,719 B),网页播放页在 `allow-scripts` 沙箱 iframe 内渲染出 `PHASER-PUBLISH-OK` 与可点击按钮。
- 发行网关根路径:`GET /api/game-distribution/releases/{gameId}` 与带尾斜杠的同一路径等价于 `index.html`(生产由每游戏 origin 映射根路径,本地直连网关或入口直接填网关地址时同样可玩);路由级用例覆盖 Cookie 拒绝门与根路径。
- 发布灰度改为**默认关闭**并修掉客户端“看得到点不动”:`is_game_distribution_publish_enabled_for_user` 现在要求 gate 行存在且 `enabled=true`(未登录、无行、`enabled=false` 一律 false),因此没配灰度时 `gameDistributionPublishEnabled=false`,AGC 不再渲染「发布到游戏广场」按钮、网页入口也不出现;AGC 侧新增 `announcePublishMessage`,把「已构建并打包试玩包」「先打开一个项目再发布」等提示通过 DirectProject 聊天容器的 `announce` 出口回话(普通项目不渲染工作台状态行,之前只写 workspaceStatus 才会表现为点击无反应)。后台「灰度发布配置」新增「可配置开关」列表:预设开关在未创建行时也可见并可一键配置(不再需要先猜 gate key)。
## 尚未完成
- 真实独立发行域名、通配 TLS 与 CDN 仍属部署侧:边缘模板与门禁已就绪,本地已用真实 nginx 验证按主机映射、Cookie 403 与命名空间隔离,但仍需在真实域名/证书下跑一次“审核通过 → 游玩 → 换版 → 下架”并确认 CDN TTL 不超过 60 秒窗口。
- 版本回读、撤回与管理员安全下架已实现;主规范 HTTP 表中不再有待落地路由。容量与限额边界、发行包 PUT 重试已有真实栈证据;仍未做的是 CDN purge 失败行为、回滚演练与清理策略(不删除仍被公开版本引用的对象)的上线验收。
- 生产调用依赖已初始化的 editor generation runtime service identity;未初始化时 procedure 拒绝写入,不会退回 API 进程内存状态。
@@ -0,0 +1,47 @@
# 实施计划:游戏分发阶段 C · AGC 发布资料 AI 生成
- 状态:`in_progress`
- 日期:`2026-09-23`
- 上游:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` 的“AGC 游戏分发与在线游玩合同”
- 里程碑:`docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md` 阶段 C
## 1. 交付结果
AGC 发布面板完成三项收敛:
1. 移除 ZIP 路径、文件数、体积和“只上传字节”等技术摘要。
2. 打开面板时基于有界、脱敏的项目上下文免费生成一句话简介和七类白名单分类;失败保留本地兜底,不阻断发布,生成结果可编辑。
3. 游戏封面支持基于项目上下文生成,复用现役图片生成与泥点扣费链路;生成结果登记为当前账号平台素材后自动作为 `coverAssetId`,不二次上传。
## 2. 实现边界
- 只改 AGC 发布面板、AGC 发布 service、api-server 内部发布资料建议路由、shared DTO、定向测试和文档。
- 不改网页发布表单、游戏分发审核 API、发行包上传/审核状态机、SpacetimeDB schema 或 `/api/external/v1` OpenAPI。
- 简介/分类生成不写用户泥点账本;封面生成继续由现役 `execute_billable_asset_operation_with_cost` 负责预扣、幂等、失败退款和结果登记。
- 传给文本模型的上下文只包含项目名称、创作目标、任务标题/状态、素材 kind/相对路径、运行状态和最近编辑提示;不包含绝对路径、聊天记录、凭据、Token 或完整 manifest。
## 3. 实现步骤
1. `packages/shared` 与 `shared-contracts` 增加发布资料建议请求/响应 DTO,分类继续使用 `GAME_DISTRIBUTION_CATEGORIES`。
2. api-server 增加 `POST /api/game-distribution/publish-metadata/suggestions`,经 Bearer 鉴权后使用内部文本模型生成严格 JSON;解析失败或模型不可用时返回本地确定性兜底,不触发钱包扣费。
3. AGC `GameDistributionPublishPanel` 打开时调用建议接口;用户未修改字段时回填,用户已编辑或迟到响应不得覆盖,失败不影响发布。
4. AGC 封面生成复用 `POST /api/editor/images/generations` 的 `publication-material`、`gpt-image-2`、`16:9`、`2K` 合同;按钮和确认弹窗从 `/api/editor/generation-pricing` 读取并显示具体泥点数。若响应进入队列,轮询 `/api/runtime/external-generation/jobs/{operationId}`,完成后直接使用 `assetObjectId`。
5. 发布截图同批并行上传;成功缩略图悬浮显示“删除|预览”,失败缩略图内部显示错误并在下方保留删除按钮。失败项不参与发布,不阻断同批其它截图。
6. 定向测试覆盖面板删除技术摘要、免费资料回填、封面价格/确认/生成/`coverAssetId` 直用、截图并行与单张失败跳过、服务端 parser/route 与输入边界。
## 4. 验收判据
- 发布面板不出现 `发行包摘要`、ZIP 相对路径或文件数/体积。
- 建议接口成功时简介和分类自动回填;分类只可能是七类之一;接口失败时保留原创作目标或通用兜底。
- 建议请求不会调用钱包、不会写 `asset_operation_consume` 或 LLM Router 额度账本。
- 封面按钮和确认弹窗显示后端运行时定价对应的具体泥点数;确认后调用现役图片生成接口,生成结果直接成为发布 `coverAssetId`,没有第二次上传或素材身份分叉。
- 同批截图并行上传;成功图悬浮显示删除/预览,失败图内部显示错误并有独立删除按钮;失败项不参与发布且不影响其它截图。
- 生成失败、余额不足和队列失败均在面板内可见且可重试,不进入聊天历史。
- 定向 vitest、api-server Rust 测试、AGC typecheck、`check:encoding`、`git diff --check` 通过。
## 5. 非目标
- 不做标题 AI 改写。
- 不做网页发布页的自动生成。
- 不新增发布草稿持久化、生成历史、重试队列或 SpacetimeDB 表。
- 不改变封面上传入口和手动选择封面的能力。
@@ -17,7 +17,7 @@ AGC 在项目打开期间按周期把用户项目增量上传到 OSS `agc-dev`
- 目标 bucket 配置:`GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_*`,默认 `agc-dev`。
- 契约:`shared-contracts::agc_project_snapshots` 新增请求/响应 DTO 与项目 ID、相对路径、摘要校验函数。
- 客户端增量索引:`<AppData>/project-snapshots/<projectId>/index.json`,按用户身份判等,换号后按冷启动全量重算。
- 排除口径:复用 `should_skip_project_snapshot_path`(整个 `.agent`、`.git`、构建与依赖目录、凭据目录、敏感后缀、符号链接与重解析点)。
- 排除口径:快照同步使用 `should_skip_project_snapshot_sync_path`(2026-09-22 起)。`.agent` 承载项目身份与 Agent 状态,整目录同步;`.git`、构建与依赖目录、凭据目录、敏感后缀、符号链接与重解析点仍然排除。项目索引、checkpoint、Agent 上下文与 git 检查继续沿用 `should_skip_project_snapshot_path` 的整个 `.agent` 排除口径。
## 不做
@@ -32,7 +32,7 @@ AGC 在项目打开期间按周期把用户项目增量上传到 OSS `agc-dev`
1. 首次同步上传项目内全部符合条件的普通文件;再次同步在无改动时上传 0 个文件。
2. 只修改一个文件时,差异集合恰好包含一个修改项;删除一个文件时上传集合为空且清单中不再包含该文件。
3. `(字节数, 修改时间)` 未变的文件复用已存摘要,不重复读取内容计算摘要。
4. 排除规则命中项(`.agent/runtime`、`.agent/logs`、`.git`、`node_modules`、构建产物、临时文件、符号链接)与超限文件进入跳过清单,不进入上传集合。
4. 项目内 `.agent` 的全部普通文件(`manifest.json`、`agent.db` 与其 WAL/SHM、`.manifest.json.lock`、`project.lock`、conversations、logs、runtime、checkpoint、workbench)进入上传集合;`.git`、`node_modules`、构建产物、凭据目录与敏感后缀仍不进入;超限文件进入跳过或延后清单,不静默丢弃。
5. 任一次同步失败(非鉴权类)不推进本地索引,下一次触发重算并重试;鉴权/权限类失败不自动重试。
6. 同一项目的并发触发串行执行,不产生两路重复上传。
7. 工作区窗口关闭与应用退出都会触发一次同步,且关闭路径不因同步失败而阻塞退出超过超时上限。
@@ -42,7 +42,7 @@ AGC 项目开发对话的显示与恢复只依赖两项输入:**项目对话
- 未知 item 类型由 Rust 原样透传(只有类型与身份,Rust 侧留 TODO),当前由前端投影丢弃。
- 前端聊天卡片的工具形状是 `Omit<GameCreatorDirectToolCall, 'turnId'>`;`tool-calls.jsonl` 的持久化形状与 DirectRuntime 的写入保持不变。
- 「可显示」的判据取**前端回合反馈**:一次翻页操作连拉到「合并后聊天投影的回合数增加」为止。工具卡片与思考文本虽然能通过 `projectDirectThreadItem`,但可能整页落进已渲染回合的折叠「执行过程」,不构成用户可见反馈;口径只在 `directHistoryPaging.ts` 里实现一份,首屏与「显示更早」共用。
- 首屏切片的**新端边界**只认 `subscribe` 回执里的 `lastCompletedItemId`(含该条):回执到达之前不读首屏,也不退化成「取文件尾」;锚点缺失(订阅不可用 / 失败 / 历史为空)时才按文件尾取尾屏,`/history` 手动重读保持按当前文件尾取尾屏的恢复语义。
- 首屏切片的**新端边界**只认 `subscribe` 回执里的 `lastCompletedItemId`(含该条):回执到达之前不读首屏,也不退化成「取文件尾」;锚点缺失(订阅不可用 / 失败 / 历史为空)时才按文件尾取尾屏,手动重读保持按当前文件尾取尾屏的恢复语义。
## 依赖与前置条件
@@ -0,0 +1,139 @@
# 【里程碑】游戏分发、发布与在线游玩
| 字段 | 值 |
| --- | --- |
| Version | 0.2 |
| Status | accepted(用户“继续”确认按既定假设进入阶段 A;A 未验收) |
| Date | 2026-09-18 |
| Parent Spec | `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` |
## 交付与评审边界
本文件拆分主规范“AGC 游戏分发与在线游玩合同”的完整业务:真实上传、持久化发行、审核、隔离托管、AGC 发布、网页上传与游玩、上线运维。当前只授权阶段 A 实现,阶段 B/C/D 仍为 `proposed`,没有已上线/已验收结论。
先完成主规范与本文件评审,再为一个获准阶段创建单独实施计划;未经该阶段验收,不进入依赖它的阶段。里程碑只规定行为与证据,不预先写代码步骤。
| 阶段 | 行为切片 | 依赖 | 状态 |
| --- | --- | --- | --- |
| A | 真实包、游戏身份与可恢复发行 | 主规范评审及持久化合同冻结 | accepted(实现中) |
| B | 人工审核、公开投影与隔离托管 | A 验收;发行站点/私有存储/审核策略确认 | proposed |
| C | AGC 与网页发布、现代游戏目录和跨端游玩 | B 验收;导航与交互稿评审 | proposed |
| D | 端到端验收、容量与撤销、上线准备 | C 验收;实际部署资源可用 | proposed |
## 实现进度(2026-09-20,未验收)
以下是已落地的实现与本地运行时证据,**不等于阶段验收**:B/C 的隔离托管、域名与生产验收仍缺少真实发行域名、TLS/CDN 与生产账号。
- 阶段 A:真实 ZIP 上传、游戏身份、owner/幂等/CAS、状态机、DTO 与 schema 门禁已完成;真实 SpacetimeDB + 私有 OSS 的创建/上传/确认/重启恢复已有证据。
- 阶段 B:人工审核(后台列表、通过需 HTTPS 入口、拒绝需理由)、公开投影、发行网关(按公开版本服务、扩展名白名单、`nosniff`/CORP/CSP、带 Cookie 403)与作者下架已实现;每游戏独立来源已有可执行工件 `deploy/nginx/genarrative-release-origin.conf` 与门禁 `npm run check:release-origin-config`,并已在本机用真实 nginx + 真实网关验证按主机映射、Cookie 403 与平台命名空间 404;生产域名、通配证书与 CDN TTL 仍需上线环境确认。
- 阶段 C:AGC 客户端「发布到平台」面板与发布链路(dist 归一化根 `index.html`、摘要/字节数声明、幂等键、`localProjectId` 复用)已实现并有请求组装与 Rust 导出测试;网页 `/games/publish` 走同一服务端管道,本地已用真实文件选择验证;AGC GUI 自身的端到端发布仍待客户端环境验收。目录(关键词/分类/设备筛选、滚动与筛选恢复)、详情、游玩页(主动作后加载、超时重试、旋转提示、全屏、移动端门槛)与作者中心(状态、驳回理由、撤回、下架)已实现;本地已在桌面与 `390x844` 移动视口真实游玩。
- 阶段 D:容量/额度、重启恢复、CDN 撤销与回滚演练尚未开始,依赖生产资源。
细节与命令级证据见[实施计划【游戏分发阶段A领域合同】](【实施计划】游戏分发阶段A领域合同-2026-09-19.md)的「已完成证据」「运行时证据」「尚未完成」。
## 共通范围与不做项
- 正式状态来自后端,素材仍复用平台上传与归属能力;前端和 AGC 不另建公开游戏状态、owner 事实或审核结果。
- 真实发行包具有不可变版本和 SHA-256,AGC dist 归一化为发行根 `index.html`;网页 ZIP 与 AGC 共用一条服务管道。
- 不恢复退役玩法 API、公开作品表或专属 runtime;不把私有项目源码镜像公开。
- 首版不包含原生/Wasm 游戏、任意外网依赖、服务端进程、多人联机、云存档、评论/评分/关注、排行榜、推荐算法和收益结算。
- 本文件只协调本业务;不顺带改造图片编辑器、Agent Runtime 执行模型或无关项目数据。
## 阶段 A:真实包、身份与可恢复发行
### 前置条件
- 主规范的包类型、限额、身份/幂等/CAS、状态机和内部 API 已评审。
- 游戏、版本、审核与操作账本的完整字段、索引、唯一约束、受信服务身份及清理策略已经冻结;若涉及已有表破坏性变更,另有已确认迁移计划。
### 行为与验收
- [ ] 登录用户创建服务端分配的游戏,owner 不能由请求伪造;其他账号不能读取私有版本、上传、提交或撤销。
- [ ] 服务端接收真实 ZIP 字节,重算摘要/字节数并建立展开清单;只有 metadata 的请求不能获得已上传或已发布状态。
- [ ] 缺入口、越界/重复/大小写冲突路径、符号链接、压缩炸弹、敏感内容和额度超限均失败关闭,原私有对象和公开状态保持一致。
- [ ] 一份版本只接受一份已确认内容;同 key 同请求重放无重复游戏/版本,不同请求冲突;同版本并发上传不混写。
- [ ] 校验可异步恢复,响应丢失、服务进程退出和客户端重试均回到原版本;确定失败和未知结果在响应中可区分。
- [ ] 正常及失败状态、私有查询和错误 envelope 在 Rust 与 TypeScript DTO 中一致;新增 schema、迁移、表目录与绑定一致。
### 证据要求
- 自动化:领域状态机、owner、幂等/CAS、包读取与错误路径、DTO 和 schema 定向测试。
- 运行时:真实 SpacetimeDB 与私有对象存储完成上传/回读/重启恢复;`/healthz` 正常。
- 边界:服务端记录的 ZIP 摘要与测试上传字节一致;未审核目录不能匿名读取;无凭据或本地路径泄漏。
## 阶段 B:审核、公开投影与隔离托管
### 前置条件
- A 已验收;人工审核角色、资料检查标准与拒绝/封禁行为已确认。
- 已确定不同可注册站点的发行域名、每游戏独立 origin、私有存储和网关能力;同源临时路径不能作为验收替代。
### 行为与验收
- [ ] 自动校验通过只进入待审,管理员可查看真实待审游戏并批准/拒绝;审核记录可追溯且普通作者不能提交审核动作。
- [ ] 新游戏审核通过并核验发行文件可读后才公开;更新待审或失败不改变旧版资料、URL 与可玩性。
- [ ] 审核激活与下架使用 `publicationRevision` CAS;过期审核、重复批准、并发更新和下架不会恢复本应关闭的游戏。
- [ ] 游客目录、详情和启动接口只返回已公开投影;未公开和已下架状态均不可见,不返回私有快照地址。
- [ ] 每游戏在独立 HTTPS origin 上,iframe sandbox、网关 CSP/CORS/MIME/禁止 Worker 等策略与主规范一致。
- [ ] 实际 npm/Vite 模块和同包资源在 opaque sandbox 下可载入;外站 fetch/WebSocket、平台 Cookie/storage/DOM、顶层跳转、弹窗和敏感权限被阻断。
- [ ] 作者下架及管理员安全下架会关闭新启动和发行读取;不能绕过网关直取公开 OSS 对象;撤销传播符合最大缓存窗口。
### 证据要求
- 自动化:审核权限、版本切换事务、公开投影与入口 allowlist、响应头和缓存策略测试。
- 运行时:真实独立域名内运行至少一个代表性游戏;真实审核、更新失败、并发下架和旧 URL 回读证据。
- 边界:外部请求阻断、匿名私有对象拒绝、跨游戏 origin 隔离及 60 秒以内缓存撤销(以获批值为准)。
## 阶段 C:双端发布与现代游戏体验
- AGC 发布资料生成切片实施计划:`docs/project-memory/plans/【实施计划】游戏分发阶段C-AGC发布资料AI生成-2026-09-23.md`。
### 前置条件
- B 已验收;网页根入口、桌面/移动导航、暖色主题交互稿及首版设备范围已评审。
- AGC 现有 npm 构建/导出能提供完整 dist,不需要把源码同步管道改作发行管道。
### 行为与验收
- [ ] AGC 从已构建 dist 生成根入口为 `index.html` 的真实包,一次提交动作完成检查、资料确认、上传和送审;状态及失败原因与服务端回读一致。
- [ ] AGC 发布面板隐藏发行包技术摘要;打开时基于有界、脱敏的项目上下文免费生成一句话简介与白名单分类,失败保留本地兜底且不阻断发布;作者始终可以直接编辑生成结果。
- [ ] AGC 发布封面支持基于项目上下文生成,复用现役图片生成与泥点扣费链路;生成结果登记为当前账号平台素材后自动作为 `coverAssetId`,不二次上传。
- [ ] 网页可选 ZIP、提交封面和必需资料,进入相同上传/校验/审核流程;任一客户端可以查看同账号游戏状态,更新沿用相同 `gameId`。
- [ ] 上传中断、双击、登录失效、窗口关闭后恢复原操作;换账号不能恢复前账号私有状态;待审不能显示为已发布。
- [ ] 目录支持真实数据、关键词/分类/设备筛选、空/错/加载态;详情提供明确主动作,搜索与返回恢复上下文。
- [ ] 游客从目录/分享链接进入详情再启动真实已发布游戏;开始操作后才加载 iframe,加载失败可重试,退出不会自动重启游戏。
- [ ] 桌面与移动导航、详情、上传/发布面板、旋转提示、全屏、安全区和焦点可达符合主规范;不适配移动端的游戏有明确门槛。
- [ ] 视觉沿用现有 warm token,通用交互复用共享组件;没有固定假统计、本地演示兜底或同源 iframe 放宽。
### 证据要求
- 自动化:AGC 打包与操作恢复定向 Rust 测试、两端客户端/组件/路由/状态测试及类型检查;发布资料免费生成必须验证不写入钱包账本,封面生成必须验证扣费幂等、失败退款与 `assetObjectId` 直用。
- 运行时:AGC 一次真实发布、网页一次真实 ZIP 上传,分别审核后从桌面和手机游玩;浏览器覆盖游戏模块、素材、音频、触屏及横竖屏。
- 边界:未构建/失效 dist、资料缺失、换账号、迟到响应、审核拒绝、非移动游戏及真实空态。
## 阶段 D:端到端、运维与上线准备
### 前置条件
- C 已验收;生产域名/TLS、CDN、私有存储、审核账号及保留/清理周期可用且已确认。
### 行为与验收
- [ ] 真实环境中完整跑通“首次上传 → 校验 → 审核 → 公开 → 游客游玩 → 更新待审旧版在线 → 新版切换 → 下架撤销”。
- [ ] 100 MiB 包与获批文件数/展开量边界有可复核耗时、内存和失败证据;校验不会执行上传代码,服务资源有界。
- [ ] 校验执行器重启可恢复,审核积压与失败可观测,清理不删除仍被公开版本引用的文件。
- [ ] CDN purge 失败时仍在获批缓存 TTL 内拒绝新资源;明确已下载脚本无法远程抹除的边界。
- [ ] 发布/回滚步骤保留当前公开版本,能关闭新提交和新版本激活;部署路由、缓存、响应头、日志脱敏和告警完成检查。
- [ ] 主规范逐条证据矩阵齐全,未验证项明确列出;有任何核心路径未验证时不标记上线完成。
### 证据要求
- 自动化:范围匹配前后端、schema/契约、编码与文档索引门禁,部署配置检查。
- 运行时:真实发行域名、真实存储、真实账号和两类客户端的完整链路证据,桌面及移动视口记录。
- 边界:压力/额度、重启恢复、缓存撤销、误删防护和回滚演练。
## 当前待审项与下一门禁
主规范仍待确认人工审核策略、导航、首版能力/额度、对象保留、发行域名及运营责任;当前没有主规范与里程碑通过评审的记录,也没有任何阶段验收证据。先评审这些决策,再冻结 A 的持久化与 DTO 明细并创建仅覆盖 A 的实施计划。不得把该文档状态改为 accepted 以代替评审。
所有阶段完成并验收后,稳定事实回写主规范,删除本临时里程碑与各阶段实施计划;过程记录不进入产品代码、用户文案或长期共享记忆。
@@ -0,0 +1,85 @@
# 【里程碑】退役AGC项目对话斜杠命令与终端swarm chat入口-2026-09-22
状态:已完成
父规范:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`
## 目标与判据
一句话目标:AGC 应用内不再存在任何斜杠命令语义,终端 swarm chat CLI 入口(`--swarm-chat`)连同其命令层整体退役,且不再残留只服务这两者的实现、测试、文案与构建期门禁;行为上等同于它们从未存在。
验收判据:
- AGC 应用内(`apps/ai-game-creator-shell/src/**`)除路径/路由字面量外,不存在形如 `'/xxx'` 的命令字面量,也不存在对 `/` 开头输入的命令分支。
- 斜杠命令专属模块与其导出全部消失,`projectSummary` 桶文件只保留现役导出。
- 权威文档不再承诺「聊天输入 `/<cmd>`」能力,终端章节点也不再把控制命令列为入口。
- 终端侧不再存在 swarm chat 入口与其命令层;`--agent-run`、`--agent-enqueue`、`--agent-steer`、`--agent-resume`、`--agent-context-compact`、`--preview-serve` 等运维类控制命令保持现役。
- 依赖终端入口的真实 E2E 与脚本(`user-input`、`supervisor-autonomous-playable-lane-defense`、三个 supervisor-swarm 混合套件、`agc:test`、`agc:test:chat*`、`agc:chat`、`agc:swarm`)与交互式 CLI harness 管道一并消失。
- 不新增任何守卫测试或 check 脚本条目。
## 范围
范围内(删除):
- `features/project-summary/projectSummaryConstants.ts` 的 `chatCommandHelp` 与全部命令清单常量。
- `features/project-summary/chatCommandMetadata.ts`(斜杠命令参数校验文案)。
- Direct 聊天的 `/history` 精确匹配分支与 `reloadHistory`。
- `chatPromptPolish` 的 `/` 前缀绕过分支。
- `memoryCommands.ts` 的 `parseRememberInput`(`/remember` 参数解析器)。
- 只服务退役 Supervisor 摘要面板、零外部调用的 `project-summary/*Summaries.ts` 与 `agentTrace.ts`。
- `/sync-canvas-project`、`/read <path>`、`/trace` 的草稿回填死链(前端 `agentPresentation.ts` + Rust `suggested_canvas_tool_call`)。
- 无人调用的 Tauri 命令 `get_game_creation_agent_capabilities`、`get_limited_local_commands`。
- 钉住上述字符串的构建期门禁条目与专属测试。
- 终端 swarm chat 入口与整个命令层:`cli.rs` 的 `SwarmChat` 变体、`--swarm-chat` 解析与派发、`src/swarm_cli.rs` 与 `src/swarm_cli/` 整个目录(输入解析与帮助输出、`/agents`、`/status` 打印器、`/goal` 引擎、`/compact`、`/resume`、`/quit` 退出分支、观察器、报告、turn 派发与等待)、`SwarmChatFlow`、`SwarmTurnObservation`、`SwarmTurnOutcome::Quit`、`SwarmConfirmationResolution::Quit`、`SWARM_TURN_*_ERROR`,以及 `swarm_cli/input.rs` 中那份只为终端命令存在的 `agent.compact` / `agent.resume` / `agent.run_status` 权限校验(同名权限 id 在现役 Tauri 命令与 Runtime 生命周期上的门禁保持不动)。
- 只服务终端交互内核的 `agent/interaction.rs` 整层:`AgentInteractionAction`、tool registry、`game_creator_agent_uses_interaction_kernel`、`decide_game_creator_agent_interaction_turn_for_session_at`、`AgentInteractionProviderStreamSink`;其上的自然语言 steer 决策 LLM 路径 `decide_game_creator_agent_runtime_steer_at` 在收尾复查后一并删除,见「收尾清理」。
- 只能由命令触达的打印器:`print_runtime_response_stream_status` 及其专属测试。
- 依赖终端入口的脚本与套件:`scripts/agent-swarm-test-chat.mjs`、`scripts/agent-runtime-deterministic-playable-e2e.mjs`、`scripts/deterministic-lane-defense-provider.mjs`,套件 `user-input`、`supervisor-autonomous-playable-lane-defense`、`supervisor-swarm-autonomous-chat`、`supervisor-swarm-static-isolated-autonomous-chat`、`supervisor-swarm-collaboration-policy-mixed-recovery`,以及 harness 里的交互式 CLI 管道(`startInteractiveCli`、`writeInteractiveCliLine`、`waitForInteractiveCli*`、`closeInteractiveCli`、`activeInteractiveCliSessions`、`answerRemainingInteractiveQuestions` 等)与只服务这些套件的混合套件分支、状态字段、sentinel 与 npm 脚本。 同时清掉只被这些套件调用的 harness 残件:`collaboration-assertions.mjs` 的混合/静态隔离断言族(`supervisorSwarmMixed*`、`observeSupervisorSwarmStaticIsolatedProviderOverlap`)、`repair-recovery.mjs` 的静态隔离观测调用点、evidence 模板与校验里的 `mixed*` / `staticIsolated*` / `initialBatchRecovery*` 占位字段,以及 `self-test.mjs` 中对应的合成用例与汇总字段。
范围外(保留):
- 命令 id 注册表 `GAME_CREATION_APP_COMMANDS` 与 `GameCreationAppPermission`(项目权限策略词汇表;App 前端只用 `GameCreationAppCommandDescriptor` 类型表达权限判定与审计粒度,数组本体由 Rust 策略路径消费)。
- `needsInitializedChatProject` 与项目权限策略链路。
- Rust 侧路径/路由的 `/` 前缀校验。
- `--agent-run`、`--agent-enqueue`、`--agent-task`、`--agent-steer`、`--agent-retry`、`--agent-cancel`、`--agent-confirm`、`--agent-resume`、`--agent-runtime-status`、`--agent-goal-*`、`--agent-context-compact`、`--runner-status`、`--runner-shutdown-if-idle`、`--llm-status`、`--preview-serve`、`--environment-check`、`--direct-codex-chat` 等运维与开发 CLI 控制命令。
- 保留的真实 E2E 套件:`supervisor-swarm`、`supervisor-swarm-transient-retry`、`supervisor-swarm-final-reply-transient-retry`、`supervisor-swarm-tool-plan-handoff-runner-kill`、`goal-runtime`、`response-stream`、`web-search`、`context-compaction`、`scoped-agents`、`project-skill`、`parallel-read`、`steer-runner-kill`、`process-session`。
## 检查点
1. 斜杠语义层:Direct `/history`、润色绕过、`chatCommandHelp`/`chatCommandMetadata`、`parseRememberInput`、桶文件与门禁、专属测试。
2. 死链与死模块:草稿回填链(前端 + Rust + 断言)、零调用摘要模块、`agentTrace.ts`。
3. 无人调用的能力清单 Tauri 命令。
4. 文档收口:AGC 主专题命令承诺、ADR、`decision-log.md`、`CONTEXT.md`。
5. 终端 swarm chat 命令层与只服务它的权限门禁、打印器、专属测试。
6. 终端 swarm chat 入口本体、交互内核、`--swarm-chat` 派发与其专属 Rust 测试。
7. 依赖终端入口的 e2e 套件、deterministic wrapper/provider、npm 脚本、构建期门禁条目、harness 交互式 CLI 管道、混合套件分支,以及随之失效的混合/静态隔离断言族与 evidence 占位字段。
8. 文档收口:终端入口退役后主实施计划、Runtime 文档、ADR、decision-log、pitfalls、里程碑计划的最终口径。
## 验证
- `npm run --workspace apps/ai-game-creator-shell typecheck`(含 `skill-pack:check` 与 `check-config.mjs`):通过。
- 定向用例 `npx vitest run tests/appSurface.test.ts`:211 tests(202 passed / 9 skipped);`chatPromptPolish`、`rememberCommand`、`ChatMarkdownMessage`、`agentRuntimeModel` 等相关用例集通过。
- `cargo check --tests`:0 error;删改文件无新增 `dead_code` 告警(按「父提交 vs 本次」引用数逐条比对告警标识符确认)。
- `npm run check:encoding`、`npm run check:doc-index`、`git diff --check`:通过。
- 真实 E2E harness 自检 `node scripts/agent-runtime-real-e2e.mjs --self-test`:`status: PASS`;harness 相对与具名 import 全部可解析。
- 收尾清理(2026-09-23,LLM steer 判定链):`cargo check --tests` 0 error,告警与改动前基线一致(无新增 `dead_code`);`npm --workspace apps/ai-game-creator-shell run typecheck`、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check` 通过;幂等复核 `steer_decision` / `interrupt_for_steer_decision` / `decide_game_creator_agent_runtime_steer_at` 在 `apps/`、`packages/` 源码中零命中。
## 文档收口
- `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`:删除全部「聊天输入 `/<cmd>`」能力条目,保留并改写其中的非命令事实;`/compact`、`/mcp`、`/goal`、`/resume` 的终端承诺改为对应 `--agent-*` 入口。
- `docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`:删除终端 swarm chat 入口整节,控制面统一记为 `--agent-*` CLI(`/goal` → `--agent-goal-*`、`/compact` → `--agent-context-compact`、`/mcp` → 开发配置面板与真实 E2E 核验);终端不再承担任何交互式聊天职责。
- `docs/adr/【ADR】退役AGC项目对话斜杠命令与终端swarm chat入口-2026-09-22.md`:本次决策与影响边界。
- `docs/project-memory/shared-memory/decision-log.md`:新增 2026-09-22 决策条目,并清理已被本次退役取代的历史命令条目。
- `docs/project-memory/shared-memory/pitfalls.md`、`docs/project-memory/plans/【里程碑】DirectProject聊天真相源收敛-2026-09-16.md`:移除 `/history` 入口表述,改为「显式重新加载对话」。
- `CONTEXT.md`:新增「项目对话输入」「项目命令 id」术语,把「斜杠命令」标为已退役说法;不再出现 `/history` 重读入口。
- `docs/README.md`:登记新 ADR。
## 收尾清理
复查(2026-09-23)发现退役残留与随之产生的零引用代码,按同一口径清理:
- 配置向导 `scripts/game-creator-config-wizard.mjs`:删除指向已删脚本的 `npm run test:chat` 调用、失去含义的 `--configure-only` 开关,以及只服务该分支的 `askYesNo` 与 `npmCommand`。
- 用户文案:App 内 `请先用 /project 设置本地项目。` 改为 `请先打开本地项目。`。
- 注释与 prompt:`chatPromptPolish` 头部注释的 `/` 命令表述、`harness/process.mjs` 与 `scripts/check-config.mjs` 中指向已退役入口的说明,以及 `interaction.json` 中只服务已删交互内核的 7 个键(`execute_description`、`resume_description`、`project_location_description`、`protocol`、`system`、`user`、`user_with_context`)。
- harness 死导出:`runtime-state.mjs` 的 4 个 schema 常量、`assertions/runtime.mjs`、`harness/project.mjs`、`collaboration-policy.mjs`、`persistence.mjs` 中因调用方被删而零引用的函数,连同因此失去用途的 import。
- 无前端调用方的 Tauri 命令 `start_game_creator_agent_runtime_task` 与 `steer_game_creator_agent_runtime_task` 全部删除(`main.rs` 注册与 `check-config.mjs` 条目同步移除)。删掉 steer 命令后其唯一非测试入口的整条 LLM steer 判定链一并退役:`agent/interaction.rs`(`runtime_steer_decision` 工具、判定请求构建、响应解析、`decide_game_creator_agent_runtime_steer_at`)、`runtime.interrupt_for_steer_decision` RPC 与派发分支、持久 decision 记录(`agent.runtime.steer_decision`)、`AgentRuntimeProviderInterrupt::applied_steer_cursor`、`AgentRuntimeSteerResult` 的 `assistantReply / interruptDecision / decisionReason` 字段、`steer_decision_*` prompt 键,以及只测该链条的 provider / runner / runtime_state 用例。
## 未做与边界
- 不为退役概念新增守卫测试或断言残留字符串的 check 条目。
- 不为终端入口与已删套件补充替代实现、兼容别名或迁移提示。
@@ -25,6 +25,7 @@
- [ ] 未单独配置快照目标时仍使用 agc-dev,只复用资源存储凭据;显式快照目标保持有效,不迁移现存对象。
- [ ] 后台列表按部署渠道查询,显示项目名/ID、用户(昵称 + 陶泥号)、同步时间、文件数、体积和完整性,支持刷新与游标分页(每页 20/50/100 + 上一页/下一页)。
- [ ] ZIP 按清单还原相对路径;不含对象存储摘要目录;空文件可上传与导出。
- [ ] `.agent` 承载项目身份与 Agent 状态,整目录随快照上传,并在后台 ZIP 中按原相对路径还原;归档因此可用于还原项目身份与 AGC 对话历史。凭据、版本库与构建产物仍不进快照。
- [ ] 清单名称/完整性变化在无内容差异时也提交,partial 可恢复 ready,历史缺字段不冒充 ready。
- [ ] 缺失、损坏、越界路径和非完整清单失败关闭;历史未声明完整性的清单明确标记,允许导出已有文件但不称为完整工程。
- [ ] 无后台权限不能读取项目或 ZIP;不泄漏凭据;ZIP 构建有体积、并发和临时文件清理边界。
@@ -39,6 +40,10 @@
| 层次 | 结果 |
| --- | --- |
| 客户端 Rust `project_snapshot` | 22 通过、1 忽略(写入式真实上传 smoke 未运行);含排队退出等待回归 |
| `.agent` 全量上传口径(2026-09-22 修订) | `project_snapshot` 定向 24 通过、1 忽略;新增扫描纳入 `.agent` 与策略单测,覆盖 `.agent` 内凭据、版本库、`.env*` 继续排除,以及项目索引 / checkpoint 口径不变 |
| `.agent` 真实样本复核 | 对 15 个本地 AppData 项目的 487 个 `.agent` 普通文件按新规则复算,0 个仍落在排除集;该复算是脚本复刻规则,不是 Rust 运行时证据 |
| `.agent` 客户端链路运行时 smoke(2026-09-22) | 用真实项目副本(`.agent` 164 个文件 / 5.61 MiB,项目 `gameagent-agentsmoke1`)走真实差异引擎 → 本地 api-server → 真实 OSS:首轮 `synced`、`uploaded=179` / `18,991,590` 字节、跳过与失败均为 0;紧接着第二轮 `no-op` / 上传 0 个文件。`uploaded=179` 恰好等于原口径的 15 个项目文件加 164 个 `.agent` 文件 |
| `.agent` 存储与后台归档(2026-09-22) | 只读 GET 真实 OSS 清单对象(`agc/project-snapshots/v1/<user>/gameagent-agentsmoke1/manifest.json`,26,356 字节):`files=179`、其中 `.agent/**` 164 条、`pendingFiles=0`、合计 18,991,590 字节;按清单里的 `大小-摘要` 构造对象键 HEAD 命中 `.agent/manifest.json`(18,319)、`.agent/agent.db`(9,353)、`.agent/conversations/project.jsonl`(1,422,226) 与 `game/package.json`(227)。后台 `download` 返回的 ZIP 解压出 179 条目、164 条在 `.agent/` 下,抽样 6 个文件(含 `agent.db`、会话、运行态文件)与本地 SHA-256 完全一致;正在运行的 dev api-server 列表显示同一项目 `files=179 status=ready` |
| 客户端前端生命周期与启动器 | 3 文件、10 测试通过;完整 AGC typecheck、skill-pack、check-config 通过 |
| 后台页面、API client、路由与样式 | 44 测试通过;admin-web typecheck/build 通过 |
| 后端快照与权限 | 14 定向测试通过;未认证路由矩阵与页签映射 2 测试通过 |
@@ -53,3 +58,5 @@
`npm run dev:api-server -- --api-port 4198 --bgfilter-worker-port 4199 --api-timeout-seconds 600` 编译成功,但当前工作区配置的本地数据库 `xushi-p4wfr` 在 `127.0.0.1:3101` 返回 404,启动认证投影无法完成,故 `/healthz` 及完整 HTTP smoke 未通过。仅本任务启动的 API/worker 已停止;没有清库、迁移数据库、改 `.env` 或替换其它项目的验证目标。
尚未替换安装版、构建新客户端发布包或部署;提交推送按用户本轮授权执行。当前条款已有上述自动化与只读存储证据,最终验收仍等待当前工作区数据库就绪后的 HTTP 联调,以及新客户端的隔离实机自动上传验证;完成后再关闭里程碑并清理两份计划。
`.agent` 全量口径已在上表取得真实上传、真实 OSS 清单与后台 ZIP 三层运行时证据。两点边界仍需记住:① 本次 smoke 用的是真实项目的可丢弃副本,其 `projectId` 改成 `gameagent-agentsmoke1`、走 dev 专用账号上传,历史真实项目在下一个同步周期前仍是旧口径清单(不含 `.agent`);② 本地 `api-server.exe` 构建于 2026-09-21 14:06,早于渠道分区提交 `4951b71d7`(2026-09-21 16:57),因此本次对象键仍是 `agc/project-snapshots/v1/` 布局、`GET /admin/api/project-snapshots/channels` 在本地返回 404;升级到 v2 键布局需要重建并重启本地 api-server。
File diff suppressed because it is too large Load Diff
@@ -92,10 +92,24 @@ SpacetimeDB 任务统一先读取 `.codex/skills/genarrative-spacetimedb/SKILL.m
## Jenkins 定时版本调度
定时与版本比较只保留在 `Genarrative-Scheduled-Revision-Trigger` 一处:每小时用 `git ls-remote` 解析 `SOURCE_BRANCH` 远端 HEAD,与上一次触发过的 revision 比较,变化时才把同一个 `COMMIT_HASH` 传给 `Genarrative-Full-Build-And-Deploy`,并先经 `Genarrative-Agc-Global-Version-Issue` 发号、再把同一个总版本号透传给 `Genarrative-Agc-Windows-Build` 与 `Genarrative-Agc-MacOS-Build`,保证两个客户端的平台分区发布同一个版本。这三个下游 Job 都不得自带 `triggers` / `cron`,也不得在管线内再做一套版本去重;`npm run check:production-ops` 会拦住这两类回退。macOS 节点是日常办公机,调度触发它时置 `SKIP_IF_SUPERSEDED=true`:节点离线期间排队的旧构建在恢复后会自行让位,不发布过期版本。调度状态与生效步骤见 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
定时与版本比较收口到两条调度器:`Genarrative-Scheduled-Revision-Trigger` 每小时处理 dev 渠道,变化时触发 Full Build、AGC Windows/macOS dev,并让两个客户端平台共用同一个总版本号;`Genarrative-Scheduled-Release-Trigger` 每天 04:00 按服务端与客户端两条独立 scope 处理 release,服务端变化时用 `DEPLOY_TARGET=release` 触发正式 Full Build,客户端变化时经 `Genarrative-Agc-Global-Version-Issue` 发同一个总号并触发 AGC Windows/macOS release。两条调度都等待并汇总下游,按成功 lane 推进 revision;失败 lane 下一轮单独补发。各下游 Job 都不得自带 `triggers` / `cron`,也不得在管线内再做一套版本去重;`npm run check:production-ops` 会拦住回退。macOS 节点是日常办公机,两条调度触发它时都置 `SKIP_IF_SUPERSEDED=true`:节点离线期间排队的旧构建在恢复后会自行让位,不发布过期版本。调度状态与生效步骤见 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## Gitea CI 依赖闭合
AGC Rust 两条 lane、crates、smoke、Backend 和桌面壳测试使用镜像内可信 sccache 对象快照;Native shell release step 显式清空双 wrapper,前端/repository checks 不启用。维护者通过 `scripts/build-gitea-rust-cache.sh` 从远端 master 在限额、无宿主挂载的临时容器中按实际 cwd/profile/目标预热全部测试组,仅编译、不执行测试/应用;后端 workspace 与 spacetime-module 保持独立,AGC 的三个 cwd 入口之间清理预热 target,防止 fresh 判断漏产缓存键。最终镜像只追加 sccache、对象和来源元数据,不包含源码或 target。容量上限 4 GiB,不替代宿主旧镜像/归档清理。PR 只写当前容器层、不回传,不开放 Docker API/发布权限;继续禁用 incremental。`ci-rust-cache.sh` 在快照缺失、工具链不符或 wrapper 探测失败时直接编译,并隔离远程缓存配置和 daemon。分片日志记录编译耗时,收尾输出命中统计;两个 lane 的测试和前置检查不同,耗时差不是严格 A/B。线上存在活跃 CI 时不得重启 runner 或切换标签;全组启用前须刷新完整快照并逐组验证,详见开发运维文档。
缓存维护下载必须核对 artifact 元数据大小与实际响应,并立即检查 ZIP 完整性;有长度上限不等于能检测短读。网络或归档损坏最多重试 3 次且重新取签名链接。sccache 同 key 的不同 ZIP 成员排列会改变整个对象 SHA;新增对象去重仅在成员内容 SHA、权限和 ZIP 元数据均一致时接受排列差异,真实内容及继承对象冲突仍拒绝。禁止通过任取一份冲突对象绕过完整性契约。
Buildx 0.30.1 的 `inspect` 不支持 `--format`,builder 驱动校验读取普通输出的 `Driver:` 字段。相关命令须在宿主真实插件上验证;测试替身应拒绝不支持的参数,避免把模拟命令成功误当兼容性证据。
BuildKit 会把 Dockerfile 中的裸 `FROM sha256:<Image ID>` 当作远程镜像名;快照组装须在维护锁内把可信基础 Image ID 绑定到专用临时 tag,并显式使用能读取宿主镜像的 `default` Docker builder。成功或失败后去 tag,元数据和 runner 仍固定完整 Image ID;不要复用受管 `base_tag` 给历史基础镜像打别名,以免改变自动清理归属。
Gitea 基础镜像通过专用 `genarrative-ci-images` Buildx builder 持久复用 Cargo/npm 下载缓存;稳定 cache mount 与 commit、lock 哈希无关,以 `sharing=locked` 隔离并发写入,仅供可信宿主构建、不开放给 PR。最终镜像显式物化当前依赖下载快照,仍不包含 node_modules/target 或上一版 sccache 层。首次可用 `seed-downloads` 从可信完整 Image ID 提取包缓存,操作账号须与维护服务一致;部署要求及 builder GC 空间目标见 `deploy/container/README.md`。构建上下文必须覆盖 AGC vendor 与编辑器 bridge 的全部本地 path manifest,普通源码变化不应使依赖层失效。维护 journal 提供阶段耗时和失败 build.log 定位。
Gitea Rust 缓存自动维护由宿主 `genarrative-ci-cache.timer` 收集同一 master push run 六个 Rust job 的原生 V4 缓存产物,不重复执行 Cargo 预热。只传本轮新 key,命中对象只传使用时间;宿主与真实来源镜像对象合并、去重、按新近使用时间裁剪到 4 GiB,从无对象缓存基础镜像重新组装。源 run 不要求全绿,但取消、缺组、旧 attempt、未完成上传或混用来源镜像不得采用。网关暂停新 FetchTask、在途领取结束、持久化任务账本清空且内层活动容器为空才切换,不打断运行中的 CI。首次接入/升级网关需空闲窗口;Token 只需普通仓库 `write:repository`,不查管理员 API。候选装载后清理已收集 artifact,遗留项保留 7 天;真实 master CI 验证后才清理旧镜像,保留当前、一个回滚版、基础镜像及容器引用。部署入口见 `deploy/container/README.md`,合并代码不等于服务启用。
修改 Gitea workflow 的 job 显示名称、ID 或缓存导出组时,必须同步维护器的 `JOBS` / `RUST_JOB_IDS`;`test_gitea_cache_maintenance.py` 直接对照实际 workflow 检查全集和导出映射,避免自动刷新或镜像验收因名单漂移长期等待。维护器 `Api.request` 的 `method` 是必填关键字参数,GET 也必须显式指定,不根据 body 推断请求方法。
Gitea 缓存部署必须区分网络:runner 的 RPC 走 `gitea-runner-fetch-gate:8080`;内层 job 的 checkout/上传走映射到 `172.30.0.3` 的 `http://genarrative-station/git`;宿主专用 clone 走 `http://127.0.0.1:3003`。不要把 runner 可达的 `gitea:3000` 配给 job。内层 Docker 使用 `10.240.0.0/16`、每 job `/24` 的默认地址池,避开外层 `172.30/172.31` 网段;恢复领取前必须在真实 job 网络里验证 checkout 与 Gitea API,不能只验证 FetchTask。具体配置与遗留空网络处理见 `deploy/container/README.md`。
AGC Rust 两条 lane、crates、smoke、Backend 和桌面壳测试使用镜像内可信 sccache 对象快照;Native shell release step 显式清空双 wrapper,前端/repository checks 不启用。仅首次人工 bootstrap 时,维护者通过 `scripts/build-gitea-rust-cache.sh` 从远端 master 在限额、无宿主挂载的临时容器中按实际 cwd/profile/目标预热全部测试组,仅编译、不执行测试/应用;后端 workspace 与 spacetime-module 保持独立,AGC 的三个 cwd 入口之间清理预热 target,防止 fresh 判断漏产缓存键。最终镜像只追加 sccache、对象和来源元数据,不包含源码或 target。容量上限 4 GiB,不替代宿主旧镜像/归档清理。PR 只写当前容器层、不回传,不开放 Docker API/发布权限;继续禁用 incremental。`ci-rust-cache.sh` 在快照缺失、工具链不符或 wrapper 探测失败时直接编译,并隔离远程缓存配置和 daemon。分片日志记录编译耗时,收尾输出命中统计;两个 lane 的测试和前置检查不同,耗时差不是严格 A/B。线上存在活跃 CI 时不得重启 runner 或切换标签;全组启用前须刷新完整快照并逐组验证,详见开发运维文档。
`.gitea/workflows/project-ci.yml` 的客户端门禁拆成 lane 与功能 job,每个 job 只预热自己会构建的那几份依赖:`AI game creator shell Rust lane 1/2`、`lane 2/2` 各自预取一次 AGC 壳 manifest,并顺序运行两片 Rust bin 单测;`AI game creator shell Rust smoke` 同样只预取 AGC 壳 manifest(`agent-run` smoke 会用 `src-tauri/Cargo.toml` spawn `cargo run`),`AI game creator shell Rust crates` 预取 `server-rs/Cargo.toml` 与独立 crate,`Native shell tests` 预取桌面壳与 AGC 壳 manifest,`AI game creator shell web tests` 不触碰 Cargo,不预热。两条 Rust lane、smoke job 与 crates job 只用 cargo 与 node 内建模块,因此不执行 `npm ci`。两个被 `server-rs/Cargo.toml` 排除、且没有提交 `Cargo.lock` 的独立 crate(`agent-runtime-core`、`agent-runtime-orchestration`)只能在 `AI game creator shell Rust crates` 里用不带锁标志的 fetch。AGC 壳的 bin target 单测(约 2466 条)由 `apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs` 编译后按 `--list` 名单分 4 片:每次分片调用用 `--shard-index=<i>` 只跑自己那片,片内保持 `--test-threads=1` 并使用独立 `TMPDIR`;两条 lane 之间并发,lane 内顺序运行两片,避免重复依赖预热和同一容器内多进程争抢。不要改回「一个 job 内多进程并行这几片」——同一容器里它们会争抢共享 `HOME`、target 目录与固定临时路径,实测比整套串行还慢。每个分片调用都会自校验「片并集等于全集且互斥」,因此改分片规则不会静默漏跑。Backend host workspace tests 使用 `cargo test --locked --workspace --exclude spacetime-module --no-fail-fast`,避免 `spacetime-module` 的 `spacetime-types` feature 统一污染普通领域 crate 的 host 测试;随后单独执行 `cargo test --locked -p spacetime-module --no-fail-fast`,由 `spacetime-module/src/active.rs` 在 host 测试构建期间提供仅测试期的 SpacetimeDB ABI 链接支持,使该 crate 的纯单元测试也纳入 Backend 门禁。`spacetime-module` 的 reducer / procedure 运行时行为仍必须通过真实 SpacetimeDB runtime/integration harness 验证,host 链接支持不得被当作运行时替身。Backend 另外执行 `cargo check --locked -p spacetime-module` 验证模块源码。AGC 壳检查还会运行 `platform-llm` 与 `shared-contracts` 的 server-rs workspace 测试,这些命令以及 AGC 壳测试必须带 `--locked`,避免在测试阶段重新解析 registry index;锁文件发生变化时应先更新受信任 CI 镜像缓存,再重跑门禁。
File diff suppressed because it is too large Load Diff
@@ -4,7 +4,7 @@
## 一句话定位
Genarrative / 陶泥儿当前主站聚焦图片画布创作、编辑器项目与素材管理,以及账号、钱包、公共设置、后台管理和小程序 WebView 外壳。AI Game Creator(AGC)是独立的桌面创作 App,按 DirectProject Runtime 合同执行,不复用主站旧玩法入口。
Genarrative / 陶泥儿当前主站聚焦图片画布创作、编辑器项目与素材管理,以及账号、钱包、公共设置、后台管理和小程序 WebView 外壳。AI Game Creator(AGC)是独立的桌面创作 App,按 DirectProject Runtime 合同执行,与主站创作入口、后端契约和运行态保持独立。
产品 IP / 产品形象统一为 `public/branding/taonier-product-ip.png`:陶罐中探出的橙色耳朵形象。
@@ -70,9 +70,9 @@ SpacetimeDB crate、SDK、CLI / standalone 与生成 bindings 按 `2.8.3` 对齐
- `ui-prototype`(设计图片)与 UI 编辑器 `UI` JSON 是不同资源。白名单 `ui.workflow.run` 按页面执行 `prepare → recognize → status → finalize`,由 provider-backed 识别、合并和组件绑定持久化 State/revision,并把 `reference-ready → structure-ready → merge-ready → binding-ready → application-ready → completed` 投影到 manifest。Provider 缺失、请求失败、工具缺失、结果不匹配或仍有待审节点时保留真实阶段并返回 blocker,不得用 deterministic seed 伪造完成。
- UI workflow 的资源桥接与 Runtime 边界以 `docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md` 和 AGC 实施计划的 2026-08-24 覆盖段为准;只生成图片、登记空 JSON 或进入普通图片画布都不构成 workflow 完成。
## 退役边界
## 当前产品边界
全部旧创作模板及其创作、生成、发布、公开业务详情与专属运行态已于 `2026-07-17` 退役。历史表和迁移仅按最小 schema 数据壳保留;旧前端、API、worker、procedure / reducer 和纯业务 crate 不属于当前编译或运行能力,不能重新作为兼容目标。
当前主站只提供图片画布创作、编辑器项目与素材、账号与钱包、公共设置、后台管理及小程序 WebView 外壳;AGC 独立承载桌面端游戏创作。不在当前路由树、Cargo workspace 和 SpacetimeDB schema / bindings 中的入口与数据对象,不属于当前产品能力。
## 当前文档入口
@@ -43,6 +43,8 @@
- 画布卡片类型与信息角标共用 `CanvasCardCornerActions`;菜单收纳共用 `OverflowActions`,宿主决定展示数量和资源命令。AGC 选中菜单前 5 项直显,Web 默认不折叠;浮层 portal 继续接入现有画布关闭与滚轮归属判据。
- 修改范围保持聚焦;优先扩展现有系统、页面、组件、DTO 和脚本。
- Agent 可见内容直接描述当前任务、输入和成功条件,细节按调用需要提供。
- AGC 外置智能体提示词只写模型必须遵守的指令与契约(工具名与参数、调用顺序、禁止项、失败处理、产物要求),不写客户端/宿主怎么实现:内部预算字段与限额、内部状态机与枚举、执行器锁与沙箱技术栈、进程/插件部署、注入与投影管线都属于宿主实现,一律改写成对应行为要求或删除。
- 修改提示词文本时保留原有 `{...}`/`{{...}}` 占位符;确需增删占位符必须同步改对应 Rust 调用点与断言该文本的测试。
- UI 开发优先复用现有公共组件;跨页面或跨端重复的视觉/交互模式应沉淀到 `packages/shared`,由现有页面迁移使用。共享组件承载通用表现与交互,领域规则、后端副作用和正式业务状态由后端负责。
- AGC 当前 Agent 与策划 Agent 的消息层级共用 `packages/shared` 的 `AgentMessageContent`:正文使用 `body`,思考、中间输出与工具调用使用 `process`;过程字号和颜色由共享组件统一定义,错误状态保留语义色。
- 后端遵循 `module-*`、`spacetime-module`、`spacetime-client`、`api-server`、`platform-*`、`shared-contracts` 的现役边界。