From 6d8969a6faf4525cb8af3ed86720634b1c102997 Mon Sep 17 00:00:00 2001 From: Linghong Date: Tue, 29 Sep 2026 15:04:32 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=B8=85=E7=90=86=E8=BF=87=E6=9C=9F?= =?UTF-8?q?=E5=85=B1=E4=BA=AB=E8=AE=B0=E5=BF=86=E4=B8=8E=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E6=96=87=E6=A1=88=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除运维和原生壳对记忆文案的固定措辞检查,保留实际契约与权限验证 清理过期决策和踩坑流水账,保留当前兼容约束与未完成验证边界 明确共享记忆维护规则,并同步修正游戏发行计划的现行入口说明 --- docs/project-memory/README.md | 2 + ...ž施计划】游戏分发阶段A领域合同-2026-09-19.md | 10 +- .../shared-memory/decision-log.md | 700 +----------------- docs/project-memory/shared-memory/pitfalls.md | 200 +---- scripts/check-native-shells.mjs | 98 --- scripts/check-production-ops-guardrails.mjs | 124 ---- 6 files changed, 47 insertions(+), 1087 deletions(-) diff --git a/docs/project-memory/README.md b/docs/project-memory/README.md index 397ea7af6..c43f93294 100644 --- a/docs/project-memory/README.md +++ b/docs/project-memory/README.md @@ -27,6 +27,8 @@ docs/project-memory/ - `plans/` 只保存正在执行、具有明确剩余项和验收门禁的计划;完成或作废后立即删除,稳定结论融合进当前专题或共享记忆。 - `todos/` 只保存真实开放、有人接手即可执行的事项;每项应写明状态、下一决策点和关闭条件。已退役对象不保留未来 TODO。 - 分支、提交、测试轮次和阶段流水账不进入长期记忆;需要追溯时使用 Git 历史。 +- `decision-log.md` 与 `pitfalls.md` 可以直接修改、合并和删除旧条目:被覆盖的决定、退役对象专属说明和重复记录不继续保留;仍有效的风险、兼容约束和未完成事项应保留或融合到当前专题。 +- 自动验证检查实际代码、配置和行为,不要求决策记录或踩坑记录包含固定措辞,也不强制把同一规则复制到多个记忆文件。 - 若本目录与代码或最新 `docs/` 冲突,以代码和最新专题为准,并在同次变更中修正记忆。 - 禁止写入个人配置、API Key、Token、Cookie、会话记录、认证文件、本地私密路径、构建产物、日志、缓存和数据库 dump。 diff --git a/docs/project-memory/plans/【实施计划】游戏分发阶段A领域合同-2026-09-19.md b/docs/project-memory/plans/【实施计划】游戏分发阶段A领域合同-2026-09-19.md index 45f548fd6..8ef32b3d3 100644 --- a/docs/project-memory/plans/【实施计划】游戏分发阶段A领域合同-2026-09-19.md +++ b/docs/project-memory/plans/【实施计划】游戏分发阶段A领域合同-2026-09-19.md @@ -121,15 +121,15 @@ - 发布灰度改为**默认关闭**并修掉客户端“看得到点不动”:`is_game_distribution_publish_enabled_for_user` 现在要求 gate 行存在且 `enabled=true`(未登录、无行、`enabled=false` 一律 false),因此没配灰度时 `gameDistributionPublishEnabled=false`,AGC 不再渲染「发布到游戏广场」按钮、网页入口也不出现;AGC 侧新增 `announcePublishMessage`,把「已构建并打包试玩包」「先打开一个项目再发布」等提示通过 DirectProject 聊天容器的 `announce` 出口回话(普通项目不渲染工作台状态行,之前只写 workspaceStatus 才会表现为点击无反应)。后台「灰度发布配置」新增「可配置开关」列表:预设开关在未创建行时也可见并可一键配置(不再需要先猜 gate key)。 -## 2026-09-23 口径更新:发行入口改为服务端派生 +## 发行入口现行口径:服务端派生平台同源路径 -- 管理员不再填写 `entryUrl`:审核通过时 `api-server` 读版本取 gameId,按部署模板 `GENARRATIVE_GAME_DISTRIBUTION_RELEASE_ENTRY_TEMPLATE`(生产形如 `https://{gameId}.games.<发行域名>/`,必须含 `{gameId}` 占位符)派生每游戏独立来源地址,再走原有 HTTPS / 无凭据 / 无 query / 无 fragment 校验;后台审核 DTO 与页面已删除该输入框。 -- 上文「已完成证据」中描述「管理员填写 / 要求 HTTPS 发行入口」的条目是当时的交付事实,当前口径以主规范《平台入口与玩法链路》《本地开发验证与生产运维》与 `shared-memory/decision-log.md` 的 2026-09-23 条目为准。 -- 非生产环境未配置模板时仍回落到本地发行网关回环地址(用于免 TLS 验证内嵌游玩);生产未配置模板、模板缺 `{gameId}`、gameId 非主机安全字符或派生结果非法时,审核通过直接失败。 +- 管理员不再填写 `entryUrl`:审核通过时 `api-server` 读版本取 gameId,派生 `/games/{gameId}/` 写入公开投影。dev、release 与预览环境统一使用平台同源路径,不再配置发行域名、通配 TLS 或部署模板变量。 +- 上文「已完成证据」中的管理员手填地址、每游戏独立来源及旧模板门禁属于当时的验证过程;现行合同以[平台入口与玩法链路](../../【玩法创作】平台入口与玩法链路-2026-05-15.md)、[本地开发验证与生产运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)及决策记录的 2026-09-24 同源发行路径条目为准。 +- 边缘将 `/games/{gameId}/` 及其资源路径转发到发行网关并清空 Cookie;网页通过 `sandbox="allow-scripts"` iframe 隔离游戏。历史绝对 HTTPS 入口继续按现行兼容规则读取,不据此恢复模板配置。 ## 尚未完成 -- 真实独立发行域名、通配 TLS 与 CDN 仍属部署侧:边缘模板与门禁已就绪,本地已用真实 nginx 验证按主机映射、Cookie 403 与命名空间隔离,但仍需在真实域名/证书下跑一次“审核通过 → 游玩 → 换版 → 下架”并确认 CDN TTL 不超过 60 秒窗口。 +- 仍需在真实平台域名下按同源发行路径复验“审核通过 → 游玩 → 换版 → 下架”,并确认 CDN TTL 不超过 60 秒窗口;独立发行域名和通配 TLS 已不属于部署或验收要求。 - 版本回读、撤回与管理员安全下架已实现;主规范 HTTP 表中不再有待落地路由。容量与限额边界、发行包 PUT 重试已有真实栈证据;仍未做的是 CDN purge 失败行为、回滚演练与清理策略(不删除仍被公开版本引用的对象)的上线验收。 - 生产调用依赖已初始化的 editor generation runtime service identity;未初始化时 procedure 拒绝写入,不会退回 API 进程内存状态。 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index b854f9184..ae1c7f542 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -1,5 +1,8 @@ # 决策记录 +> 用途:只记录当前仍有效、会影响后续开发的长期技术、产品与协作结论;同一事实只保留一处当前口径。 +> 维护:阶段过程、分支合并和当轮测试数字由 Git 追溯;实现依据以当前代码和最新专题文档为准。 + ## 2026-09-29 外壳状态栏退役:`status` 一行改由浮层承载 - 背景:首页输入框下方、项目组页面头部那一行由 `WorkspaceLauncher` 的 `status` 承接,写入的内容很杂——进行中进度(`正在创建工作区`、`正在选择项目`)、“已取消 / 已创建项目 / 已打开项目目录”这类回显、失败结论(`创建未完成,请重试`、工作区看门狗文案、Provider 报错)以及项目组页面的 `正在检查项目状态`。这一行常驻占页面,用户明确要求整行改成浮层提示,而不是只把「已取消」挑出来。 @@ -265,7 +268,7 @@ ## 策划 V1/V2 退役的现行边界 - 旧策划 V1 和 Runtime V2 均已删除,当前策划入口统一使用独立 Design Agent。V1 被 V2 接替只描述历史过程,不表示 V2 仍在使用。 -- 下文旧策划版本的阶段审批、`plan.submit_gdd`、planning session binding、exact planning lifecycle v3、专属身份白名单、IPC 和测试约束均为历史记录,不能作为恢复代码或保留孤立实现的理由。不新增旧版本兼容别名、双跑或回退链路。 +- 旧策划版本的阶段审批、`plan.submit_gdd`、planning session binding、exact planning lifecycle v3、专属身份白名单、IPC 和测试约束均为历史记录,不能作为恢复代码或保留孤立实现的理由。不新增旧版本兼容别名、双跑或回退链路。 - 通用项目锁、权限、持久化和当前 Design Agent 能力按实际调用保留;清理未用参数不扩大为删除调用方的持锁范围或锁归属校验。 - 当前事实源:[策划 Agent 生产迁移与工作区浏览](../../technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md)。 @@ -382,18 +385,6 @@ - 按技术负责人要求,列表改为 `(event_time, event_id)` 倒序,历史补传按发生时间归位。入库时间仍用于固定分页快照,翻页期间新入库的事件在刷新后显示。 - 分页游标改存发生时间;旧游标刷新后重新取得,不迁移持久表、不新增索引、不改变采集与上传行为。 -## 2026-09-22 埋点分支同步发布入口与工作台更新 - -- 合并 master `079466b29`,同时保留项目离开埋点与工作台运行通知、后台埋点查询与游戏发布审核入口;不扩大采集范围。 -- 本次冲突位于工作台相邻回调、后台 API 测试导入及本文件新增记录,均保留双方现役内容;Direct 聊天的认证重试埋点与 master 回合状态调整继续共存。 - -## 2026-09-22 埋点分支合并 DirectProject 聊天重构 - -- 保留 master 的独立 DirectProject 聊天控制器及 canonical `userItem` 合同;旧 Supervisor、独立 prompt/attachments IPC 参数和已退役删除命令不恢复。 -- 原有 Direct run 埋点从 App 聊天链迁至 `useDirectProjectChatController.runTurn`,认证重试仍逐次生成 attempt ID,并仅确认最后一次;账号代次变化时丢弃确认。项目进入/离开及用户预览的原有接线继续保留,不扩大采集范围。 -- 过期测试按现役聊天入口调整,新增实际 UI→controller 认证重试用例验证最终尝试确认。上传、数据库和后台合同不变。 -- 合并验证:Rust analytics 56 项通过(真实服务桥接用例维持 ignored),App 界面 203 项通过 / 13 项既有跳过;预览激活、版本切换、工作台清单及客户端埋点辅助测试通过,客户端 TypeScript、原生契约、编码、文档索引和 diff 检查通过。本次未重跑上传真实服务 smoke 或完整 GUI/Provider 创作。 - ## 2026-09-21 客户端埋点方案进入团队共享文档 - 本期验收完成:原始需求与已确认口径的 12 类事件入口已核对,同一 writer/session/project/goal 的宿主组件链路通过真实文件、HTTP、checkpoint 与 JSONL 关联验证;最终 51 项 Rust、46 项前端测试和类型/格式/文档检查通过。仅测试辅助模拟创建投递、run 结果和构建产物,不宣称完整 GUI/Provider 端到端验证;未提交或发布。禁止把全部资源操作逐项接线重新当作本期必做范围。 @@ -430,16 +421,6 @@ - 影响范围:`jenkins/Jenkinsfile.scheduled-release-trigger`、`jenkins/scheduled-release-trigger-job-config.xml`、`scripts/check-production-ops-guardrails.mjs`、开发运维文档与共享开发工作流。 - 验证方式:生产运维门禁检查 Full Build release 参数、双 scope、`wait: true` 结果聚合、三路分支和成功后才写 lane revision;并用 Jenkins 具体构建验证 Full Build/AGC 的 build number 与结果能回传到调度 Job。 -## 2026-09-22 AGC release 增加每日调度,dev 调度保持双平台 - -> 已被上一条决策取代:release 调度现已纳入正式服务端 Full Build。 - -- 背景:原有 `Genarrative-Scheduled-Revision-Trigger` 每小时跟随 revision 发布 dev 客户端,但没有对应的 release 渠道自动入口;Mac 节点此前已纳入小时 dev 调度,需要避免新增 release 调度时再退回 Windows-only。 -- 决策:新增 `Genarrative-Scheduled-Release-Trigger`,每天 04:00 检查 `SOURCE_BRANCH`,使用独立的 `.jenkins-last-release-revision` 与客户端相关路径白名单;上一轮 release 调度后有客户端变更时,先经 `Genarrative-Agc-Global-Version-Issue` 发统一总号,再以同一固定 `COMMIT_HASH` 触发 `Genarrative-Agc-Windows-Build` 与 `Genarrative-Agc-MacOS-Build` 的 `AGC_UPDATE_CHANNEL=release` 分区,Mac 继续带 `SKIP_IF_SUPERSEDED=true`。小时 dev 调度保持同时触发 Windows 与 macOS dev。 -- 原因:release 与 dev 是不同渠道和发布节奏,不能靠同一个小时 Job 隐式切换;独立 Job 能分别去重、记录状态和审计发号。release 调度不触发 Full Build,避免把桌面客户端发布与线上全栈部署绑定。 -- 影响范围:`jenkins/Jenkinsfile.scheduled-release-trigger`、`jenkins/scheduled-release-trigger-job-config.xml`、`jenkins/Jenkinsfile.agc-global-version-issue`、`scripts/check-production-ops-guardrails.mjs`、开发运维文档、共享开发工作流与 AGC 总版本号技术方案。 -- 验证方式:`npm run check:production-ops` 校验 release Job 的 cron、发号、双平台 release 参数、Mac 让位和 Copy Artifact 授权;`npm run check:encoding` 与 `git diff --check` 校验文件与补丁。 - ## 2026-09-22 项目快照 `.agent` 全量上传:远端工程包要能还原项目身份与 Agent 历史 - 背景:快照上传此前复用 `should_skip_project_snapshot_path`,把整个 `.agent`(`manifest.json`、`agent.db`、conversations、logs、runtime、checkpoint、workbench、`project.lock`)排除在上传集合之外。后台“项目工程”的「下载完整工程」是按清单逐文件打包的,于是导出的 ZIP 里没有项目身份与对话历史,用户在 AGC 里恢复不出同一个项目。 @@ -450,14 +431,6 @@ - 验证方式:`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml project_snapshot`(24 passed、0 failed、1 ignored),新增用例覆盖 `.agent` 整目录进入候选集、`.agent` 内凭据与 `.env*` 仍被排除、项目索引与 checkpoint 口径不变;另用同一份规则复算 15 个本机 AppData 项目的 487 个 `.agent` 普通文件,0 个仍落在排除集。 - 运行时证据(2026-09-22):真实项目副本(`.agent` 164 文件 / 5.61 MiB,`projectId=gameagent-agentsmoke1`)经真实差异引擎 → 本地 api-server → 真实 OSS `agc-dev`:`uploaded=179`(= 原口径 15 个项目文件 + 164 个 `.agent` 文件)/18,991,590 字节、`synced` 后第二轮 `no-op`;只读 GET 远端 `agc/project-snapshots/v1//gameagent-agentsmoke1/manifest.json` 得 `files=179`(`.agent/**` 164 条)、HEAD 命中 `agent.db` 等对象;后台 download 的 ZIP 含 179 条目(164 条 `.agent/`),抽样文件 SHA-256 与本地一致。注意该 bucket 仍是 `v1/` 键布局:本地 `api-server.exe`(2026-09-21 14:06)早于渠道分区提交 `4951b71d7`(16:57),重建重启后才会写 `v2/{channel}/`。 -## 2026-09-21 合并 origin/master:Supervisor 永久退役,策划 V1V2 退役落到当前两条产品路径 - -- 背景:`refactor/split-direct-project`(DirectProject 独立聊天容器)与 `origin/master`(#355 退役策划 Agent V1/V2)在 2026-09-18 之后各走一条线:本分支删掉 Supervisor 前端链路、把立项策划收敛到 `view/project-development/planning/`,master 删掉整套策划 V1/V2(前端会话 / 审批卡 / 适配器 / 类型与 Rust `planning_*_v2` 命令、`planning_gdd_model.rs`、`planning_policy_v2.rs`、`planning_session_v2.rs`)只保留 Design Agent。两边都在删 Supervisor,冲突集中在 `App.tsx`、聊天视图(`PlanningChatView`、`DirectProjectTurn`、`ToolCallGroup`)、Direct composer / 引用输入区、`styles.css`、Rust direct user item 与 appSurface 用例。 -- 决策:按「Supervisor 永久退役」的当前口径合并,保留 DirectProject 独立容器 + Design Agent 两条产品路径,master 的策划 V1/V2 删除整体生效(GDD 审批卡、策划输入卡、`planningLane`、`planningSessionV2`、`planningSessionContract` 与对应 Rust 模块、`planning_*_v2` 命令与前端契约全部删除,不保留兼容别名或双跑路径)。两边的 patch 目的若有独立价值,就落到当前结构上而不是恢复 Supervisor:① 思考折叠入口收成一个共享表现 `chat/components/AgentReasoning/AgentReasoning.tsx`(折叠态单行纯文本预览 + 箭头、展开态安全 Markdown),DirectProject 回合与策划回合共用;② 输入盒的模型 / 推理档控件(`ConversationModelSelect`、`ComposerReasoningEffortSelect`)按 ADR「行为中立的设置表现可复用」接到策划输入盒,写回仍走客户端配置通道。Rust 侧保留本分支的 canonical→wire 投影、无审计回合与 direct user item 严格校验,master 的 `prepare_new_web_project_at` 前置复核并入当前回合入口。 -- 原因:Supervisor 结构(含它的 composer 控件、消息标签与运行态)是已退役对象,冲突里出现它只是两侧删除的落点不同;但「策划入口也要能选模型 / 推理档」和「折叠思考显示单行预览」是产品行为,属于 master 那边的独立目的,丢掉就是功能回退。把它们挂到当前两条路径上,既满足退役口径也不让 master 的行为丢失。 -- 代价与取舍:master 用例里钉住 `.project-supervisor-composer-controls`、`项目总控消息` 的断言改为当前类名 `.project-chat-composer-controls` 与「立项策划消息」;本分支 CSS 的 `project-supervisor-* → project-chat-*` 改名对 master 新增规则同样生效。`workspaceProjectKind` 在两条读它的路径(壳层 Cocos 插件门禁、Supervisor 提交路由)都退役后删除,插件可用性改由插件宿主自判。`.env` 的本地私有改动不进入本次合并。 -- 验证方式:`apps/ai-game-creator-shell` `npm run typecheck` 与仓库 `npm run typecheck`、`npx vitest run apps/ai-game-creator-shell/tests`(168 个文件,除 5 个 jsdom `localStorage` 环境失败文件与 1 条本分支既有的 `resourceTagStatsRefresh` 失败外全绿,`appSurface.test.ts` 198 passed / 13 skipped / 0 failed)、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`、改动文件 eslint 0 error。Rust 侧定向 `cargo test --bin genarrative-ai-game-creator-shell -- direct_codex_user_item | skill_pack:: | sessions::developer_project_file_and_memory_writes_advance_project_revision` 全过;整套 Rust 分片在本容器有 55 条环境性失败(`/sbin -> usr/bin` 让 `command.exec` 沙箱的 merged-usr 预检失败,报 `command.exec sandbox unavailable`),与本次合并无关:这些用例所在模块与 master 逐字相同。 - ## 2026-09-21 合并后两处回归:预览激活回接「运行」入口,聊天 `@` 清单按需重读 - 背景:合并 `origin/master` 后 CI 报两处回归。① `scripts/check-native-shells.mjs` 报 `AI game creator client preview activation drifted: missing await invoke('activate_local_game_preview'`:Supervisor 前端链路退役时(`0224ef7b9` 删 `pendingCommand` 死链那一步)把 `activate_local_game_preview` 的前端调用方一起带走,而这条守卫与 Rust `preview.rs` 的实现都还在。② `resourceTagStatsRefresh` 用例失败:聊天 `@` 选择器的标签统计与候选读 `useDirectProjectManifest` 自己那份清单,资源画布的标签写入既不发 `game-creator-manifest-invalidated`(Rust 只在 agent 驱动的写入上发),壳也不再往聊天推清单快照,于是用户改完标签打开 `@` 仍看到旧标签。 @@ -623,9 +596,6 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和不确定执行回执合同,编辑器实现留在 `plugins/agc-godot-editor`。用户选择 DLL 原件随 AGC 安装资源分发,并确认按编辑器实例在 AGC 私有缓存准备临时加载副本,以满足 Godot Windows 加载器的同目录 `~DLL` 写入要求;项目内不复制 DLL,只用受管 `.gdextension` 引导。Godot 自动 UID 伴生文件必须记录归属并在确认卸载后按内容匹配清理。工作区根不迁移到 Godot 子目录,原始项目配置与场景只通过明确编辑操作修改。完整合同及验证范围见 [Godot 编辑器插件接入](<../../technical/【技术方案】AGC Godot编辑器插件接入-2026-09-20.md>)。 -> 用途:记录已经确认、会影响后续开发的长期技术/产品/协作决策。短期讨论不要写在这里。 -> 当前口径(2026-09-18):历史条目的旧路径、旧版本和已退役对象只用于追溯,不构成现行实现依据。策划 Agent V1/V2 的 Runtime、专用命令、审批卡、展示适配和旧测试已删除;当前策划入口统一使用 Design Agent。如与当前代码或 `docs/README.md` 冲突,以当前代码和最新专题文档为准。 - ## 2026-09-20 DirectProject 工具并行与交付收敛 - 所有工具具备有界并行调度能力,MCP 每入口在途上限 8;独立图片在客户端进程内最多 2 个。只保留同资源冲突、编辑器实例、canonical 美术包和短提交事务的必要串行边界。同一付费动作必须在容量排队前取得原 durable 槽锁,不重写幂等算法。 @@ -956,18 +926,6 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和 - 影响范围:策划入口、会话与工具实现、资源打包、文件浏览;迁移已完成。当前入口统一使用新 Design Agent,旧 Planning V2 会话不再继续运行。 - 关联文档:[策划 Agent 生产迁移与工作区浏览](../../technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md)。 -## 记录格式 - -```md -## YYYY-MM-DD 决策标题 - -- 背景:为什么需要这个决策 -- 决策:最终决定是什么 -- 影响范围:涉及哪些模块/文档/流程 -- 验证方式:如何确认决策仍有效 -- 关联文档:相关 PRD、技术文档、提交或 Issue -``` - ## 2026-09-13 AGC 资源替换补「会话内血缘标注」:只保留当前有效的一条 A→B,不假装持久 - 背景:用户验收指出「当前版本使用素材替换后,一个标为当前版本素材一个不是,替换关系不明」。核实后的边界:①光环(`currentVersionBindingIds` → DOM `is-current-version` / `data-used-by-current-version`)本身同源、不会两处打架,替换后光环从源素材 A 移到替换素材 B 是**设计内**的既定变化;②替换关系在客户端**任何一层都不存在** —— 宿主只消费替换结果的 `result.versionId` 与 `result.committedProjectRevision`,`ProjectVersionResourceReplacement` 里的 `sourceResourceId` / `replacementResourceId` / `warning` 无人消费,审计 `asset.version_binding.replace` 只有写侧;③manifest 的 `.previous` 不能当数据源 —— 它只是原子安装的崩溃兜底副本,装盘成功即删,正常项目里根本不存在。 @@ -1011,20 +969,6 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和 - 验证方式:本条目为**待查项,无代码改动**。修复时建议的断言:同一次冷启动首屏流程里,同一个 identity 的 `read_local_project_media_preview` **只允许发一次**(除非确实发生过 LRU 驱逐或显式重试)。 - 关联:`apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCardPreviews.ts`(`observePreview` 的注册回声扫描、`cancelQueuedVisiblePrefetches`、`RESOURCE_PREVIEW_VISIBLE_SWEEP_DELAYS_MS` 多延迟点兜底扫描)、`apps/ai-game-creator-shell/tests/useProjectResourceCardPreviews.test.ts`(`冷启动首屏的放行范围与放行顺序` 两条用例的夹具可直接复用:20 张卡 + `eagerPreviewLimit: 12`)。 -## 2026-09-11 资源筛选改为按需弹出的独立浮层:区域与画布栏目共用一份状态,状态字段不做 - -- 背景:资源画布此前只有搜索框一条收窄路径。`bb4834520` 按用户要求移除了画布上的**常驻分类筛选条**(含画布标签 chip),随它一起摘掉的还有 `resourceCanvasCategoryFilters` / `resourceCanvasActiveTags` / `resourceTagLibrary` 三个只为常驻条存在的筛选轴,注释落在 `index.tsx` 的 `visibleResources` 上方。用户随后明确要的是**关键词 / 所在区域 / 状态 / 自定义标签**四个维度的检索,并给出原型:一个独立的「筛选」浮层、四字段竖向排布、右上角 `×` 关闭。因此本轮交付的是**按需弹出的浮层**,与已被移除的常驻条不是同一个东西;用户点名「切换区域的时候你也跟着切换区域,然后 filter 就好做了,只在当前区域 filter」。 -- 决策:**筛选固定做成独立浮层**(`PlatformFilterPanel` 只装外壳:`role="dialog"` + 标题 + 右上 `×` + 竖向字段容器,**不自带 position**,定位由宿主 `className` 注入),**不重新引入常驻筛选条**,`bb4834520` 的移除**无需撤销**。字段固定三个: - 1. **查找素材**:与画布搜索浮层**共用同一份 `searchText` 状态**,两个入口读写同一个关键词,不新建第二套搜索;判据沿用既有四字段(label / path / mediaType / taskTitle)与大小写不敏感口径。 - 2. **所在区域**:取值 = 「全部区域」+ `PROJECT_RESOURCE_CANVAS_SECTIONS` 的 6 类资产分类与末尾独立「项目版本」栏;**区域不新增第二份 state** —— 下拉 `value` 由 `resourceBookState.view === 'child' ? resourceBookState.category : 'all'` 派生,`onChange` **只调既有 `openResourceBookChild`**(与栏目切换完全同一条路径,含 viewport 拟合)。因此滚轮 / 总览卡片 / 下一页切换栏目时区域显示自动跟随,**不需要对账 effect,也不可能双写漂移**。「全部区域」复用既有 `RESOURCE_BOOK_ALL_TARGET` 的「所有资源」分组网格,8 个取值全走同一个函数、**无特例分支**;又因为 `visibleResourcesByCategory` 由筛选后的 `visibleResources` 按 `category` 派生,「区域=具体栏目时只筛该栏目」与「区域=全部时筛全量」**共用同一份过滤实现**,不为前者另写一套裁集合逻辑。 - 3. **自定义标签**:从 manifest `assets[].tags` 派生,**多选 AND**(与既有 `assetTagsMatchSelection` 一致,PRD 无条款故不偏离);标签库只统计当前区域内资源,切区域后不残留别的区域的标签。 - **不提供「状态」字段**。三条证据:① `gameCreationApp.ts` 的 `GameCreationAppAssetManifestEntry` 字段集里**没有 status**;② `resourceProjectionModel.ts` 的 `ProjectResource` 字段集里**也没有 status**;③ 唯二候选都不是资源状态且恒单值 —— `GameCreationAppTaskStatus` 是**任务**状态、而投影只让 `status === 'completed'` 的任务产出资源;`ProjectAttachmentResult.status` 是**附件导入**状态、而投影先滤掉非 `imported` 的才产出资源。硬拿它们派生,取值永远只有一个,是**假筛选维度**。按用户原话「如果没有这个东西就删掉」与仓库「四不写」,**不实现、不留占位下拉**。 -- 影响范围:`packages/shared/src/components/PlatformFilterPanel.tsx`(新增,仅表现)、`apps/ai-game-creator-shell/src/view/project-development/resourceCanvasFilterModel.ts`(新增纯函数:区域判据做恒等比较、关键词四字段、标签 AND、区域选项与显示名派生、标签库派生、筛选生效判据)、`apps/ai-game-creator-shell/src/view/project-development/ResourceFilterPanel.tsx`(新增 AGC 薄接线,领域规则留在这一层)、`apps/ai-game-creator-shell/src/styles.css`(`.game-resource-filter-panel` 右下角锚点,与搜索浮层同一 `bottom` / `z-index` 口径)。**`PlatformResourceFilterBar` 一字未改、其默认行为逐字不变**:它是**横向常驻条**且**正被主站 `src/components/image-editor/ImageCanvasProjectAssetPickerDialog.tsx` 在役使用**(参考图弹窗),把形态改成竖向会波及不在本任务范围的主站生成面,加 variant 又等于让一个组件背两种骨架,故新建外壳而非扩展它。区域显示名复用 `@` 面板那份唯一中文权威 `resourceReferenceCategoryLabel`,只为不在该 6 类表内的 `version` 单列固定显示名,不另建译名表。**跨端契约、sidecar schema、manifest 字段、后端接口均不变。** -- 未落地(截至本条记录时):`index.tsx` 的 3 处接线(①`visibleResources` 内联过滤换成调用纯函数;②新增 `resourceFilterOpen` state + Dock 筛选按钮 + 浮层挂载,并把区域接到 `openResourceBookChild`;③新增 `activeTags` state)**尚未提交**——当时该文件正被另一条线(美术画布「生成动画」接线)占用。已落地的部分(外壳、纯函数、AGC 侧组件与样式)各自独立提交,接线完成后本条应补记。 -- 验证方式:`packages/shared/src/components/PlatformFilterPanel.test.tsx` 覆盖 `dialog` 语义与关闭转发、字段逐个渲染、外壳不自带「状态」占位字段、`controlId` 字段名关联、宿主定位类注入且组件不自带 `absolute` / `fixed`;`apps/ai-game-creator-shell/tests/resourceCanvasFilterModel.test.ts` 覆盖区域选项顺序与逐个中文显示名、空筛选不重排、区域恒等比较、关键词四字段与空白归一、标签 AND 语义、无标签事实源资源的口径、三维度叠加、标签库计数与 `zh-CN` 排序、筛选生效判据;`apps/ai-game-creator-shell/tests/resourceFilterPanel.test.tsx` 覆盖三字段渲染且无「状态」占位、区域选项取值与显示名、关键词回显与回调、区域切换只回调、标签受控多选与 `aria-pressed`、空标签库隐藏该字段、关闭键 / `Escape` / 点外部 / 点触发按钮四种关闭口径、`Escape` 后焦点回触发按钮。**变异验证实测**:把区域判据改成恒 `true` → `4 failed`;去掉关键词判据 → `3 failed`;去掉标签判据 → `4 failed`;三处还原后复跑 `13 passed`。既有断言零放宽(`PlatformResourceFilterBar.test.tsx` 与主站弹窗测试一字未改,同跑 `9 passed`)。 -- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`(§5.2.4 资源筛选浮层条款)、本条上一条 2026-09-11 决策、`docs/project-memory/shared-memory/pitfalls.md`(常驻筛选条移除那条)。 -- 后续(2026-09-13):画布搜索浮层合并进本浮层——Dock 只留放大镜按钮、`Ctrl/Cmd+F` 与它叫出同一个面板,上面那条「两个入口读写同一个关键词」收敛为**唯一入口**,纯搜索浮层及其 state / ref / CSS 与两处手写互斥一并删除。三个字段、区域派生、「状态」字段不做、关闭与焦点口径(Esc 关闭不清条件、焦点回触发按钮)均未变,见本文件 2026-09-13 一条。 - ## 2026-09-11 资源画布 sidecar 的读时归并保持写回,但不再静默,并公开丢弃口径 - 背景:2026-09-10「旧栏目坐标读时归并」那条决策在文档里写成「不写迁移脚本、不删除、不重置」,字面为真、事实上是**读时原地重写**——`useProjectResourceCanvasLayout.ts` 读盘后若 `reconcileLayout(...).changed` 为真就立刻走 `update_local_project_resource_canvas_layout` 做一次 CAS 写盘(`revision` +1)。真机量到的规模(36 份 sidecar / 300 条坐标):**112 条 legacy 分区坐标**(`art` 36×2 + `code` 20×2)会在第一次打开项目时被静默改写;另有 **82 条(27%)坐标被丢弃**,其中 **66 条**因为 `resourceId` 在 manifest 里查不到、**16 条**因为持久化分区与资源分类不匹配,丢弃点是 `resourceCanvasLayoutModel.ts` 的 `if (!normalized) return false;`,而这次丢弃同样被上面那次写回**固化**。旧文档口径与读路径真实行为冲突:不改则下一个排障的人会往「是不是有迁移脚本」的方向找。审计给出的两个候选是 A(保留重写、补可见提示 + 改文档)和 B(把丢弃/重写改成只在拖动时发生);用户拍板 **A**,因为 B 会改变布局语义、影响两次 sidecar 的既有行为。 @@ -1068,14 +1012,6 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和 - 验证方式:`resourceCanvasSectionMapping.test.ts` 覆盖旧值 × 目标栏目矩阵(坐标与 `manuallyPlaced` 原样保留、`section` 改写、独有旧值改写后必然判定为变化)与回落栏目属于目标集合;`projectResourceProjectionModel.test.ts` 覆盖 6 类分区投影、项目版本独立成栏和只登记游戏代码的项目落在「待归类」;Rust 侧覆盖旧 section JSON 反序列化、未知值拒绝与既有旧 sidecar 文件读取。 - 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。 -## 2026-09-05 Planning V2 一次性写路径对齐 V1 的项目锁等待窗口 - -- 背景:V2 审批修改意见后立即 `continue_planning_session_v2`。审批、回合启动、策略落盘和 hydrate 原先无等待取锁,和 GUI 重灌或其它写操作撞车就返回 `项目正在被其他写操作占用`,前端再映射成总控失败。V1 已用完整/短窗口处理同一形状。 -- 决策:V2 审批、回合启动、策略落盘、失败投影和 GDD 认领使用完整等待窗口;V2 hydrate 使用短窗口。不引入可重入项目锁,不放宽失效回收。 -- 影响范围:`planning_policy_v2.rs`、`planning_session_v2.rs`、V2 hydrate 前端瞬时争用处理。 -- 验证方式:Rust 定向测试覆盖短暂占用下的审批、修订续跑和 hydrate。 -- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`docs/project-memory/shared-memory/pitfalls.md`。 - ## 2026-09-05 本进程新建 Windows 私有对象不因继承 DACL 自动 UAC - 背景:#211 要求 sidecar 满足当前用户独占、禁止继承的 DACL。新建文件会先继承父目录 ACE,生产路径把这种短暂不合格送进 UAC;`project.lock` 还在独占句柄上 harden。含空格项目路径上提权 ArgumentList 被拆开,修复以 exit 1 失败。GDD 审批改意见因此弹权限,V1 锁创建不会。 @@ -1085,81 +1021,6 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和 - 验证方式:Windows 定向测试覆盖 `Genarrative GameAgent\gameagent-*` 取锁与私有 DACL,以及带空格路径的 quoted ArgumentList。 - 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/pitfalls.md`。 -## 2026-09-05 Planning V2 的 3 轮策略与 8 个问题门禁有意不对称 - -- 决策:模型提示最多提问 3 轮,并在达到 3 后要求出稿;Runtime `question_limit` 默认 8,对偏离模型策略的合法问题保留接收空间,达到 8 才拒绝新 question。前者是模型行为指令,后者是运行时接收边界,数值有意不同,不是缺陷或配置不一致。 -- 评审口径:Session/UI 的 8 是容量,不是必须问满的配额;正常路径在 3 轮或更早出稿符合设计。不得仅因数值不同,把模型提示改为按 `question_limit` 出稿、把 3 改成可继续到 8 的软目标,或把 Runtime 门禁收紧为 3。 -- 影响范围:仅补充文档解释,现有提示词、运行逻辑、校验和测试保持不变。 -- 关联文档:[策划会话 Runtime V2 接入与旧链路退役方案 §1.3](../../technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md#13-问询策略与-runtime-门禁的不对称设计)。 - -## 2026-09-04 Planning V2 将决定 ID 从 Provider 输入移回 Runtime - -- 背景:`plan_submit_gdd` 原先要求模型生成 `decisions[].id` 及原型验证项引用 ID。该字段既不是方案内容,又容易出现 `initial_request`、`initialRequest` 或错误层级,导致合法 GDD 在 Runtime 事后校验阶段失败。 -- 决策:Provider-facing `plan_submit_gdd` schema 和入参删除决定/原型验证项 ID。Runtime 按决定数组顺序生成首项 `initial-request`、后续 `decision-{序号}`,并按 `prototype_pending` 决定顺序给原型验证项绑定同一 ID。最终持久化 `plan-gdd.v2` 仍保留 ID,供审批、引用和 fingerprint 使用。V2 尚未上线,不为旧 Provider 输入或历史 V2 artifact 增加兼容转换;不符合新契约的历史数据按现有失败策略处理。 -- 影响范围:`planning_policy_v2.rs` 的工具 schema、Provider 入参解析、Runtime 产物构建与定向测试;Planning V2 技术方案。 -- 验证方式:定向 Rust 测试确认 schema 不含 ID、无 ID 输入可生成 Runtime ID;并运行 `cargo fmt --check`、`npm run check:encoding`、`git diff --check`。 -- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_policy_v2.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_session_v2.rs`。 - -## 2026-09-04 Planning V2 持久化每次 Provider 尝试的诊断产物 - -- 背景:Provider 已成功返回但策略解析失败时,原 V2 只保留最终错误,无法核对实际请求、原始工具参数和单次重试结果。 -- 决策:在 `.agent/planning-v2/debug//` 保存每次尝试的 request、response 和分类事件;诊断文件不进入会话上下文,不参与恢复、重试或 GDD 业务判断,写入失败不改变主流程结果。 -- 影响范围:`planning_session_v2.rs` 的 Provider 调用外围和 Planning V2 技术方案持久化目录说明。 -- 验证方式:通过 Provider 请求/响应产物可还原每次尝试及 `toolCalls.arguments`,并确认主流程仍按原有解析、重试和状态转换执行。 -- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_session_v2.rs`。 - -## 2026-09-04 Planning V2 把 `gdd.vN.json` 创建成功当作提交点 - -- 背景:V2 persist 先 create-only 写入不可变 GDD,再更新 index、Markdown、conversation 和 session。后续任一步失败会把 session 标成 `provider_failed`,但不回滚已创建文件;重试会重新生成 UUID/时间戳并撞上“已存在且内容不同”,hydrate 又只信 `current_artifact_version`,项目会卡死。 -- 决策:`gdd.v{N}.json` 创建成功即提交点,禁止回滚不可变文件。persist / hydrate / 回合启动若发现 session 指针的下一个连续版本已在磁盘,必须读取既有 GDD 补投影,不得用新的 LLM 入参重建身份。session 指针写成功前的投影失败仍可返回 persist 错误,但恢复路径必须认领该版本。 -- 影响范围:`planning_policy_v2.rs` persist/认领、`planning_session_v2.rs` hydrate 与回合启动;V2 技术方案。 -- 验证方式:Rust 测试覆盖孤儿 GDD 重试认领、hydrate 认领、成功提交后仍分配下一版本;`cargo fmt --check`、`npm run check:encoding`、`git diff --check`。 -- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`。 - -## 2026-09-04 PlanningSessionRuntime V2 用协议工具输出问询和 GDD - -- 背景:原型已验证 `plan_ask_question` / `plan_submit_gdd` 两个协议工具、深层 schema、提示词只留策略、`tool_choice=auto` 可跑通;生产 V2 仍解析正文 `{kind,question|gdd}` JSON,并把形状骨架写在 system prompt 里。浅 schema + 正文 JSON 会误导模型把 GDD 写成普通文本;`tool_choice=required` 与 DeepSeek thinking 不能同时使用。 -- 决策:V2 Provider 请求固定挂这两个协议工具,`tool_choice=auto`,`strict=false`。模型必须恰好调用其中一个;Runtime 解析 `toolCalls` 归一为 Question/Artifact,正文 JSON 视为非法。system prompt 只保留问询/出稿策略和当前问询进度,不再附 JSON 骨架或数量清单。入参不再要求模型回声 `schemaVersion`,落盘 GDD 仍由 Runtime 写入 `plan-gdd.v2`。既有结构门禁(含 `initial-request` 首项、`validate_plan_game` 数量/字数)不变,失败仍回灌一次。不把协议工具写入 `capabilities.tools`,不执行 MCP/Skill,不把 `tool_call`/`tool_result` 写入会话消息。 -- 影响范围:`planning_session_v2.rs` 请求构造、重试文案与 Provider 结果投影;`planning_policy_v2.rs` 工具 schema、解析和入参 `schemaVersion`;V2 技术方案。 -- 验证方式:Planning V2 定向 Rust 测试覆盖工具解析、缺 `schemaVersion` 的合法 GDD、正文 JSON 拒收、工具 schema 含嵌套 `game` 字段、提示词不再含骨架;`cargo fmt --check`、`npm run check:encoding`、`git diff --check`。 -- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`。 - -## 2026-09-03 新建策划会话采用 PlanningSessionRuntime V2,旧 Supervisor 链路直接退役 - -- 背景:现有“做方案”依赖 `project-supervisor-plan` 根 Run、`project-planning` 子 Run、静态委派、delivery、Acceptance Graph 和审批前 evidence。新策划 Agent 只需要单 Agent 会话、问询、GDD 和审批;继续在旧 Runtime 上逐条放宽会保留身份/编排耦合。未来策划 Agent 可能支持无限多轮、MCP 和 Skill,需要避免把当前 8 题/GDD/no-tools 固化为 Runtime 根结构。 -- 决策:新增独立 `PlanningSessionRuntime`,复用 Provider/流式、会话持久化、项目锁、原子写和基础错误恢复;当前启用 `mode=gdd`、最多展示 8 个有效问题、GDD 审批和用户修改。新建“做方案”会话不创建 Supervisor root、planning child、delegation 或 acceptance evidence。V2 使用独立 `.agent/planning-v2/` 与 V2 schema,继续输出 `game/fast_gdd.md`;不自动转换旧会话。 -- 兼容性:Session 保存 `mode`、可空 `questionLimit`、`capabilities.tools/skills`;完整会话记录与 Provider 请求上下文分离;消息模型预留 tool/skill 事件类型但本期不执行 MCP/Skill。无限问询、上下文摘要、多产物和能力执行以后作为策略/能力层扩展,不重新引入 Supervisor 身份模型。 -- 当前进度:P0 合同冻结、P1 会话内核和 P2 GDD/审批核心已落地;P3 正式入口/UI 接入已开始,P5 旧链路退役尚未开始。 -- 退役:V2 切换时旧链路直接封存;所有未完成旧会话投影为 `legacy_retired` 失败,禁止继续问询、审批、恢复或 continuation。旧 GDD、approval、conversation 和 `.agent/planning` 文件只读保留;旧入口 caller 关闭,但不删除旧代码、旧测试或旧数据。 -- 影响范围:AGC 做方案入口、Rust/Tauri planning session/Provider adapter、GDD/审批 V2、前端 planning lane、阶段任务与 BDD 验收;做游戏/做素材 DirectProject 不变。 -- 验证方式:按 `docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md` 的 P0~P5 阶段验收执行;至少覆盖第 8 个问题、上限后 question 抑制、Provider 失败、非法输出、批准/修改/退回、重启恢复、旧会话切换强制失败、迟到 Provider 结果丢弃和当前空能力快照。 -- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`。 - -## 2026-09-03 PlanningSessionRuntime V2 统一 Agent 推断语义 - -- 背景:旧 V1 使用 `default_pending` / `answerSource=default` 表示未提问时由 Agent 按默认建议补齐的字段;该语义会让 V2 的 Agent 推断看起来像产品默认值,也会造成原型与生产字段不一致。 -- 决策:V2 只使用 `confirmed`、`assumption_pending`、`prototype_pending` 三种决定状态;`assumption_pending` 的来源统一为 `agent_inferred`。`answerSource` 仍不是独立阻断项,缺失或不一致时按状态归一为 `user_freeform`、`user_option` 或 `agent_inferred`。V1 的 `default_pending` / `default` 校验和历史数据保持不动,不作为 V2 合同的一部分。 -- 问询策略:V2 出稿前必须确认玩家核心行为、单局目标/核心循环、MVP 制作边界;其中任一仅由 Agent 推断时继续问一个关键问题。`questionLimit` 是 Runtime 对已展示问题数的硬上限,提示词中的“默认最多三轮”只是策略偏好,不要求与硬上限数值一致。 -- 影响范围:V2 GDD 输入/产物、Provider system prompt、前端 V2 类型与决定状态展示;旧 Supervisor/V1 存储、校验和历史产物不变。 -- 验证方式:V2 解析 `assumption_pending` 不报错并落盘为 `assumption_pending/agent_inferred`;`default_pending` 不作为 V2 合法状态;核心三项未确认时提示词要求继续问询;相关 Rust/TS 定向测试、类型和编码检查通过。 -- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_policy_v2.rs`。 - -## 2026-09-04 PlanningSessionRuntime V2 将既有输出阻断原因回灌给 Provider - -- 背景:原型已将会导致输出拒收的字段、类型、数量和长度契约写入提示词,并在校验失败重试时回灌具体原因;生产 V2 仍只有简要 GDD 形状提示,模型可能重复犯同一结构错误。 -- 决策:生产 V2 只同步当前已经存在的 question/GDD 校验契约到 Provider system prompt,并在现有一次重试中明确列出本次阻断原因、要求逐项修复;不扩大校验范围、不新增门禁、不增加重试次数,也不把 `answerSource` 变成阻断条件。 -- 影响范围:`planning_session_v2.rs` 的 Provider prompt 与现有非法输出重试提示;`planning_policy_v2.rs` 校验逻辑、问询上限和持久化契约不变。 -- 验证方式:运行 Planning V2 定向 Rust 测试、`cargo fmt --check`、`git diff --check`,确认提示词构造和现有校验路径通过;不改变既有校验结果。 -- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_session_v2.rs`。 - -## 2026-09-04 PlanningSessionRuntime V2 提示词只保留形状和策略 - -- 背景:把逐字段长度、数量、类型清单写入 system prompt 后,提示词与 JSON 骨架、Rust 校验器三重叠,模型也难以消化长清单;精确数字已由校验失败的一次重试回灌。 -- 决策:V2 system prompt 只保留问询/GDD 骨架、`game` 与 `decisions` / `prototypeValidationItems` 同级边界、状态枚举、首条 `initial-request` 约束,以及一行易错数量范围(options / keywords / pillars / coreLoop / mvpSystems / outOfScope / oneLiner)。不把逐字段长度、控制字符、label 去重等校验细则写入 prompt;校验范围、门禁和重试次数不变。 -- 影响范围:`planning_session_v2.rs` 的 Provider system prompt;`planning_policy_v2.rs` 校验逻辑与非法输出重试路径不变。 -- 验证方式:提示词含骨架与同级边界、不含逐字段长度清单;现有 Planning V2 定向测试、编码和 diff 检查通过。 -- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_session_v2.rs`。 - ## 2026-09-03 AGC 登录 route event 使用 handler 已验证主体归属 - 背景:登录请求进入时尚未拥有 `AuthenticatedAccessToken`,通用 tracking middleware 无法从响应 extensions 归属登录成功用户;将 AGC marker 直接写入按用户/业务日幂等的 `daily_login` 又会受到不同来源登录顺序影响。 @@ -1259,22 +1120,6 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和 - 验证方式:非游戏 conformance 覆盖并行分支、汇合、repair closure、AgentCatalog 和非法图失败关闭;`platform-agent` 锁定种子 DAG 与现役波次/返工顺序,并验证环拒绝和 catalog 注入。根检查脚本必须执行新 crate 测试。 - 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md` V1.54。 -## 2026-08-27 `plan.submit_gdd` 拒绝无审批决定的 `user_revision` - -- 背景:结构校验允许 `round=0 + user_revision + confirmed`,提交闸原先只做结构、身份和 Session CAS。Provider 可在首次 collecting、澄清续跑或提交前质量返工里把未确认项标成用户审批修改,审批卡显示「已确认」。 -- 决策:新版本 create 时,payload 含 `user_revision` 则当前 session 的 `lastDecisionRef.action` 必须是 `revise` 或 `reject`;否则 `PLAN_INVALID_REQUEST`。同 `submissionId` replay 不重判。不恢复 session 前缀逐项相等,不把 `user_revision` 与审批意见正文对齐,也不在这次处理 `round≥1` 的 `user_option` 伪造。 -- 影响范围:`planning_submit.rs` 提交闸;Fast GDD 技术方案第 5.1 / 8.2 / 12 节。 -- 验证方式:首次 collecting 带 invented-confirmation 必须拒绝且不落 GDD;reject continuation 再交 `user_revision` 的 v2 仍成功。 -- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`。 - -## 2026-08-28 planning continuation 必须沿当前 delivery 游标推进 - -- 背景:`lastDecisionRef.action=revise/reject` 在用户修订后的质量返工中必须继续有效,但仅凭该历史指针无法证明当前 `agent.delegate` 选择的是本次 planning session 的当前分支。 -- 决策:不新增用户修订授权字段,也不在 `plan.submit_gdd` 重复遍历 approval receipt/GDD lineage。已有 planning session 创建新 child 时,`repairOfDelegationId` 必须直接等于旧 session 的 `latestDelegationId`;不一致即在 Provider 启动前以 `PLAN_NEEDS_RECONCILIATION` 拒绝。合法用户修订及其后质量返工继续保留 `lastDecisionRef`,成功提交新的 GDD 后仍由 submit successor 清理该指针。 -- 影响范围:`planning_coordinator.rs` continuation 投影门;Fast GDD 技术方案第 8.2 节和提交步骤;不改变静态委派通用返工合同或 `PlanSessionV1` schema。 -- 验证方式:新增当前游标 continuation 正向/旧 delivery 负向回归;CI 继续验证首次伪造 `user_revision` 拒绝、用户修订后质量返工提交成功及现有澄清/返工 lineage。 -- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_coordinator.rs`。 - ## 2026-08-27 退款 emergency spool 容量溢出保持可恢复 ## 2026-08-27 退款 emergency spool 容量溢出保持可恢复 @@ -1309,15 +1154,6 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和 - 验证方式:核对 CLI 版本和 commit,重新生成 Rust bindings,运行 `npm run check:spacetime-schema`、相关 Cargo check / tests、server provision 工具测试、dev 调度测试、encoding 和 diff 门禁。 - 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 -## 2026-08-26 Fast GDD 修订后先取证再允许再次委派 - -- **现象**:GDD v1 经用户选择“修改”后,策划子 Agent 正确提交 v2,但 plan 根 Supervisor 的 `Delegated` 阶段仍同时广告 `agent.delegate` 与审批前置工具;模型可能在 Acceptance Graph 重新取证前重复创建修订 delivery,随后被 `PLAN_PROVIDER_USAGE_DEFERRED` 拦停。 -- **决策**:plan 根阶段增加轻量的 `AwaitingAcceptanceEvidence` 状态。当前根最新 GDD 无 approval receipt/pending、session `latestSubmittedRef` 精确指向该提交、delivery 已由根认领且 Acceptance Graph 返回 `NeedsEvidence` 时,只广告 `file.read`、`agent.acceptance_update`、`agent.run_status`;只有用户真正对最新审批卡选择修改/退回后,才恢复 `agent.delegate`。 -- **边界**:不放宽 Provider usage 门禁,不重构 delegation/repair lineage,不自动生成证据或审批 pending;审批 pending 仍只由既有 acceptance gate 在 `agent.acceptance_update` 成功后创建。 -- **验证**:新增一条阶段工具面回归,并通过 15 条 M1C-2a acceptance gate 定向测试、plan root 原生工具目录测试、`cargo check --all-targets`、格式与 diff 检查。 -- **锁边界修正(2026-08-27)**:阶段判定拆为 `plan_root_supervisor_stage_at_locked` 与负责取得一次项目锁的外层入口;Provider tool-plan builder 已持有项目锁时直接复用 locked 入口。Acceptance Evidence 判据和阶段工具面不变,禁止在持锁调用链中再次获取 `.agent/project.lock`。 -- **回归验证**:planning submit 定向测试 68 passed、Provider request builder 定向测试 17 passed、Tauri `cargo check` 与 `cargo fmt --check` 通过。 - ## 2026-08-24 AGC Direct 媒体能力只通过客户端语义工具开放 - 背景:资源页已经补齐视频、角色动画、音效和背景音乐的 create/derive 能力,但 Direct Codex 只能准备标准美术包,无法查询已登记源资源或表达新增媒体意图。直接开放 Tauri invoke 会把项目路径、revision、operation、幂等键、登录态和事务权力交给模型。 @@ -1339,144 +1175,6 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和 - 输入边界:`merge_ui` 继续直接接收 `State`,不修改 Tauri/frontend IPC 参数;进入 Rust 后、发起 LLM 前按每棵源树独立限制 `512` 节点 / `32` 层,不跨树求和,并限制 `2 MiB` 序列化投影。UI 设计参考图只设单张 `5 MiB` 上限,不设批次合计或像素数上限;元数据检查、有限读取和 base64 编码进入 blocking worker,不新增命令超时。 - 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。 -## 2026-08-19 M1 审查后四项可靠性修复 - -- **审批 stale 收口**:`status` 或 `phase` 为 `needs-reconciliation` 的策划根不再满足审批所需的 active 身份;审批命令返回 `PLAN_STALE_APPROVAL`,并且不得创建 approval receipt。 -- **rollout capability**:AppData 配置增加 `planning.capabilityEnabled`,默认 `true`。关闭后拒绝新的 plan 根 run、`plan.submit_gdd`、approval pending 和 decision mutation;hydrate 仅返回已有 sidecar 的只读视图,不执行恢复写入。 -- **profile/source 边界**:durable run-profile binding 集中拒绝 `project-supervisor-plan + autonomous-game-build`;自主构建的 scheduler 与 completion consumer 使用不含 plan 的精确 source matcher,普通 plan `standard` 仍属于通用 trusted matcher。 -- **边界**:不新增既有项目首次进入策划入口;不改做游戏、做素材路径。回归覆盖 capability、stale receipt、错项目 sidecar 写前失败、非法 binding 不落盘与新项目 ID 形状。 - -## 2026-08-19 更正 M1D-2 首页入口映射 - -- **更正**:`bf2185fba` 把“做游戏”误接为 `standard + project-supervisor-plan`,并额外增加“直接开建”按钮;这与既有产品决定“做方案入口独立成链,不动做游戏路径”冲突。 -- **当前入口合同**:仅首页“做方案”新建项目以 `standard + project-supervisor-plan` 进入立项策划;“做游戏”和“做素材”保持 `autonomous-game-build` 直接开建。目录提交与 Enter 自动创建使用同一映射。 -- **边界**:项目页新建、打开既有项目和 Godot 导入不新增策划入口;已有 planning sidecar 或 active plan lineage 只恢复其原有链路。 -- **回归**:覆盖做游戏目录提交、做方案目录提交、做方案 Enter 自动创建,以及既有做素材 Enter 自动创建,分别断言首个 Supervisor run 的 profile/source。 - -## 2026-08-18 M1D 收口:design 组展示名改完 - -- **背景更正(先于结论)**:此前把这条的严重度建立在「新项目默认主路径上『立项策划』与『策划 Agent』同屏共存」上,**该说法未经验证且不成立**。用 appSurface harness 实测策划路径:`子 Agent 状态栏` 根本不渲染,`策划 Agent` 出现 0 次、`立项策划` 出现 2 次。结构上二者确在 `launcherView === 'project-development'` 分支的同一棵树里(`ProjectDevelopmentView` 的 dock + 作为 `supervisor` 传入的 `ProjectSupervisorView`),但未能把用例驱动到该分支,故不作为事实主张。若真会撞,也是**批准后进入完整制作**那一段(此时 `state=approved`,阶段进度卡守卫仍放行),比原描述窄得多。 -- **仍然要改的理由**:与撞不撞名无关。`bf2185fba` 只把 `taskGroupLabels.design` 改成「设计实现组」,另两本同概念字典没动,于是同一个 design 组在开发者面板/文本汇总里叫「设计实现组」、在工作台状态栏里叫「策划 Agent」——**这个语义不一致是该提交引进来的**。第 18.2 节要求本就是「design 组用户名称改为设计实现组」,与新阶段区分只是其动机之一。改完比回退便宜(回退还需挑拣 `c6a08ef98` 里混着的按钮改名与 prettier 重排,并反改第 18.2 节)。 -- **口径选择**:采最小方案,保持兄弟项的「X Agent」体系(美术 Agent / 程序 Agent / …),design 取「设计实现 Agent」。**不**把 `taskGroupLabels` 直接塞进 `groupConfigs`——两本字典命名体系不同(「X组」对「X Agent」,且音乐组/音频 Agent、运营组/发布 Agent 连词都不一样),直接替换会连带改掉另外五个分组名。消灭重复字典属视觉改版,单独立项。 -- **改动**:`agentPresentation.ts` 的 `groupConfigs`、`view/project-development/index.tsx` 的 `summarizeAgent` 默认名与同文件分组头像字(`策`→`设`)、`model.ts` 中 `agentId.includes('design')` 的个体名兜底(`design-director` 走这里;`design-foundation` 的「玩法策划 Agent」单列在前,不变)。内部 agent id 与 `design` 分组键均未动。 -- **测试**:原估「3 处断言」严重低估。实跑发现 `project-development.suite.ts` 有 16 处派生断言需跟改——「X 文本回执」由 `App.tsx` 的 `${candidate.label} 文本回执` 拼出、「历史成果 · X」由 `resourceProjectionModel.ts` 拼出、dock 的 article accessible name 亦然。替换时用后行否定守住 `玩法策划 Agent`(它含 `策划 Agent` 子串),替换前后该串恒为 6 处。`agentRuntimeModel.test.ts` 与 `projectResourceProjectionModel.test.ts` 里剩余 4 处是测试自造的输入 fixture、不由字典派生,保持不动。 -- **验证**:`appSurface.test.ts` **383 passed / 0 failed**;`agentRuntimeModel` / `agentTraceSummary` / `projectResourceProjectionModel` / `projectResourceLiveUpdateModel` 合计 39 passed;`agc:typecheck`、ESLint `--max-warnings 0`、`check:encoding`、`git diff --check` 通过。 - -## 2026-08-18 M1D 审查修复补充:锁错误脱敏与澄清轮次口径 - -- **锁错误回传绝对路径**:`acquire_project_write_lock` 的 Err 内嵌 `.agent/project.lock` 真实绝对路径,违反第 18.3 节「返回值不包含绝对路径……或内部诊断」。补 `redact_agent_runtime_project_paths` 的三处是前端审批卡真正会显示的那条链:`reconcile_plan_gdd_approval_projections_at`(hydrate 在取自己的锁之前调它)、hydrate 自己的锁、`decide_plan_gdd_at`(其错误与 hydrate 的错误渲染在同一个错误区)。planning 另有 14 个取锁点沿用未脱敏写法,属 M1B/M1C 既有模式,本次不扩面。脱敏不破坏 `项目正在被其他写操作占用:` 前缀,`project_gates.rs` / `provider_recovery.rs` 两处按前缀分类的判据不受影响。 -- **澄清轮次差一格**:`clarificationRound` 与 `awaitingAnswerFor.round` 都由 `static_delegate_lineage_counters` 派生,该函数排除目标自身,是 0-indexed 的「已答轮数」;后端判上限用的是 `current_round + 1`。阶段进度原样渲染成「轮次 X/3」整体差一格,问最后一轮时显示「轮次 2/3」,字面暗示还剩一轮。**只改前端文案,不动 DTO 语义**:等待回答时显示「第 N+1 轮 / 共 3 轮」(此时 `latestDelegationId` 就是当前 delivery,+1 恰好等于后端校验用的轮次),其余状态退回「已完成 N/3 轮澄清」,不猜当前轮。 -- **顺带**:`planning_hydrate.rs` 里 `reconcile` 的错误原本用同一 code 把 `to_string()` 当 detail 重包一层,而 `PlanningStorageError` 的 Display 已是 `"{code}: {detail}"`,渲染出 `CODE: CODE: detail`;code 与 detail 均无变化,改为直接 `?` 传播,并把「不重复拼 code」钉进回归。 -- **测试陷阱(值得记)**:写「占住项目锁」的 fixture 时必须给锁 JSON 填**真实** `createdAt`。失效锁回收的年龄判定读的是该 JSON 字段而**不是**文件 mtime(`project_write_lock_age_seconds`),填 0 会让锁显得约 1.7e9 秒老、越过 600 秒阈值被当场回收删除,hydrate 反而成功。第一版 fixture 正是这样自证失败的。 -- **验证**:Rust `planning_` 组 **155 passed / 0 failed**(原 154 + 本次 1 条);`appSurface.test.ts` **383 passed / 0 failed**(378 原有 + 5 条新增);三条新回归均经变异验证,逆转对应修复即变红。`cargo fmt --check`、`agc:typecheck`、ESLint `--max-warnings 0`、`check:encoding`、`git diff --check` 通过。 -- **撤回一条此前的审查发现**:曾判定 hydrate 读 manifest 缺符号链接判定(因其走裸 `root.join` 而非 `resolve_local_project_path`)。复核后**不成立**:`read_manifest` 自身在 `metadata.file_type().is_symlink()` 处即拒(`manifest.rs`),防护在另一层;`.agent` 目录本身为符号链接的残差也无窗口,紧随其后的 `resolve_planning_path` 同样逐段判定。未据此改动代码。 -- **仍未修**:① hydrate 在校验 GDD/session 的 projectId 与 manifest 一致之前已执行落盘投影修复,违反第 18.3 节固定顺序。**已裁决为不修**:它唯一有后果的前提是 projectId 变成每项目唯一,而该常量方案不属本工作包管辖;单独为一个不受控的假设改动权威读取路径不划算。触发后的实际后果也已复核为可忽略——命令仍正确返回 `PLAN_PROJECT_ID_MISMATCH`,写入的 pending/session/index 均为幂等或可重建投影,`session.previous.json` 是改名而非删除且只在 primary 缺失时发生。裁决与改动前提(含「不能把 `reconcile` 直接挪到 hydrate 取锁之后」这个非重入锁陷阱)已作为注释写在 `planning_hydrate.rs` 调用点旁,使提醒与会坏掉的代码同处,而不是只留在本文档里;② design 组展示名仍有 `agentPresentation.ts` 的 `groupConfigs` 与 `view/project-development/index.tsx` 的 `summarizeAgent` 两处硬编码「策划 Agent」,注意该两处与 `taskGroupLabels` **命名体系不同**(「X Agent」对「X组」),直接替换会连带改掉另外五个分组名,需先定命名口径。 -- **新记一条既有问题(非 M1D 引入)**:`seedManifest.projectId` 是常量 `local-project-draft`,App 的 5 个 init/import 调用点全传它,因此**本机所有项目 projectId 相同**。第 18.3 节第 1 步依赖的「manifest 与 projectId 校验」因此分辨不出任意两个项目——把 A 项目的 `.agent/planning/**` 整体拷入 B 项目仍会通过。该门当前近乎恒真,须单独立项处置。 - -## 2026-08-18 M1D 审查修复:GDD 审批决定失败路径与恢复期弹层门控 - -- **审查范围与基线**:对 `14c00017c..bf2185fba` 的 M1D-1/M1D-2 全量改动做规格对照审查(技术方案第 13、18 节)。审查完成后分支又前进了 `4624fd795`(文档同步)与 `c6a08ef98`(前端文案回归断言修复)两条,二者都不改 `src/**` 生产代码,审查结论不受影响。 -- **修复一:决定失败也必须重灌权威状态**。`decidePlanGdd` 原来只在成功分支 hydrate,`catch` 只写错误后 rethrow。后端 `decide_plan_gdd_at` 有多条真实 `PLAN_STALE_APPROVAL` 分支(GDD 已不在当前 lineage、identity 不符、版本被更新版本取代、pending 丢失或不一致),命中后卡片停在已失效的 pending 身份上、三个决定按钮仍可点,且 `recoveryPending` 永不翻真导致「重试恢复」入口不渲染,卡内没有任何恢复路径。现在失败分支同样 hydrate,落实第 18.3 节「approval decision 返回后调用 hydrate」(该句不区分成功与失败)。**两句顺序已被回归钉死**:`hydratePlanGddState` 入口会 `setPlanGddError(null)`,必须先 hydrate 再写决定错误,写反会把这条错误擦掉。 -- **修复二:responseId 复用键纳入 comment**。原键为 `approvalRequestId:action`,不含 comment,违反第 13.2 节「用户改变 action/comment 后必须生成新 responseId」。在第 14 节恢复矩阵承认的「receipt 已提交但 command response 丢失」构造下,用户改写修改意见后重提会带着旧 responseId,命中后端「同 responseId 的审批意图不一致」硬拒,改写后的原因永远落不了盘。现在键挂在 `approvalRequestId` 上并比对 `{action, comment}` 完整意图。**判据方向为宁可多换不可少换**:receipt 已存在时多换的最坏后果是 `replayed` 降级成 `already-decided`(两者都是 Ok,且 already-decided 正是第 18.2 节要求的刷新态),少换则是硬错误。 -- **修复三:`recoveryPending` 必须挡住已经打开的评论弹层**。第 18.2 节要求恢复期只允许重试同一 ID、不允许提交决定;原实现只把 `canDecide` 接到三个触发按钮上,而弹层是打开之后才可能被后台 hydrate 翻掉决定资格的,其「提交决定」按钮只看 `busy || !comment.trim()`,仍可提交。现在 `submitComment` 与该按钮都判 `canDecide`,并在弹层内说明原因。**刻意不自动关弹层**,否则会丢掉用户已经写好的修改意见。 -- **测试**:appSurface harness 新增 `hydrate_game_creator_plan_gdd_state` / `decide_game_creator_plan_gdd` 两个分发分支与 `createPlanGddStateView` fixture;未配置策划状态时 hydrate 与接入前一样抛出,既有用例行为不变。新增 `tests/appSurface/plan-gdd.suite.ts` 三条回归,并逐条做过变异验证——把对应修复单独逆转后三条各自以自己的断言变红(修复二的变异是**部分逆转**:保留新 Map 结构、只删掉 comment 比对,因此该用例钉住的是 comment 这一维本身而非那次重构)。 -- **验证**:`appSurface.test.ts` **381 passed / 0 failed**(378 既有 + 3 新增);`agentTraceSummary` 与 `rememberCommand`(另两个 import `src/App` 的用例文件)13 passed;`agc:typecheck` 通过;6 个改动/新增文件 ESLint `--max-warnings 0` 通过;`check:encoding` 5409 文件通过。不改 Rust——三条全在前端,后端语义已经正确。 -- **对既有记录的更正**:M1D-1 与 M1D-2 两条记录分别称「Shell TypeScript typecheck 仍被仓库既有依赖缺失阻断」「appSurface UI suite 受仓库现有缺失 Tauri plugin 依赖阻断,未把该基线失败归因于本包」,在原分支主工作树上都不成立:`agc:typecheck` 干净退出,appSurface 378 条全绿;两道门分别位于 CI 的 `check:native-shells`(且 typecheck 排在 cargo test 之前)与 Frontend tests 内,一直是活的。实际情况与记录相反——`bf2185fba` 改名 `taskGroupLabels.design` 后,appSurface 有 8 个用例文件的断言变红,随后由 `c6a08ef98` 修复;把该套件记为「基线阻断、不归因本包」正是让这条自带回归合入的原因。**隔离工作树的依赖缺失不能作为跳过门禁的依据,须回原工作树复跑后再下结论。** -- **未修的审查发现(本次不并入,单列后续)**:① hydrate 在校验 GDD/session 的 projectId 与 manifest 一致之前,已执行 `reconcile_plan_gdd_approval_projections_at`、session previous 提升与 index 重建等落盘修复,违反第 18.3 节固定顺序,其中 `session.previous.json` 的提升+删除不可逆(触发需外部篡改 `.agent/`,App 自身流程造不出该分歧);② 项目写锁竞争时 `acquire_project_write_lock` 的错误原文内嵌项目绝对路径,被原样回传前端,违反第 18.3 节「返回值不包含绝对路径」,常态可达;③ design 组展示名只改了 `taskGroupLabels` 一本字典,`agentPresentation.ts` 的 `groupConfigs` 与 `view/project-development/index.tsx` 的 `summarizeAgent` 仍硬编码「策划 Agent」,与新阶段「立项策划」同屏共存,违反第 18.2 节;④ 阶段进度「轮次 X/3」直接透传 0-indexed 的 `clarificationRound` 未 +1(后端自己用的是 `current_round + 1`),最后一轮显示「轮次 2/3」,字面暗示还剩一轮。 - -## 2026-08-18 M1E 隔离工作树:Fast GDD submit 有界拒绝与覆盖审计 - -- **范围与结论**:在 `codex/genarrative-isolated` 上按 M1E 只接受具备完整触发链的缺陷。确认 Provider 连续输出不合法 `plan.submit_gdd` 时,Runtime 原有「rejected observation → 同 child run 续跑」链没有次数上限,模型可反复请求 tool-plan 并累积历史 observation;这是可达的 prompt 膨胀与资源消耗链。 -- **有界收束**:为 Runtime state 新增 durable `planSubmitGddRejectionCount`。仅本次 Provider input / 候选 GDD 触发的 `PLAN_INVALID_REQUEST`、`PLAN_SIZE_LIMIT` 计数;前四次维持既有 batch abort、observation 与 same-run continuation,第五次仍先完整落 rejected observation,再将该 planning child 终态失败并记录专用失败 audit,不发起第六次 Provider tool-plan。字段以 serde default 向后兼容,进程重启不会重置;新 child run 才从零开始,普通工具 observation 不计入。 -- **自审修复**:`PLAN_SIZE_LIMIT` 也可能来自读取既有不可变 GDD/receipt,而非 Provider 输入;`PLAN_VERSION_LIMIT_REACHED` 则由既有 lineage 已达 128 版或其读取异常决定。若只按 error code 分类,会把 durable authority 异常误当可纠正模型输出,最多多跑五次。现将这些读取/版本边界转为 `PLAN_NEEDS_RECONCILIATION`;Provider input/候选 GDD 的大小限制仍可按上项重试,不扩大改变其它 authority 错误语义。 -- **覆盖审计**:第 21 节要求的澄清三轮/回答绑定/continuation、submit→receipt 的 replay 与投影恢复、审批后修订、hydrate 空态与恢复均已有真实 Runtime/存储回归。未发现能低成本构成完整新断链的前端或跨层缺陷,因而未为拼接既有单测新增大而脆的 E2E。 -- **自审收口**:复核第五次 rejected observation 已持久化、但终态失败写入前进程中断的窗口:原先恢复会再次进入 Provider loop,形成第六次请求。恢复入口现读取 durable counter,达到 5 时直接复用同一终态失败与 audit 收束,不依赖内存 continuation;该修复与原有计数边界完全同域。未发现其它具备完整触发链、且可在 M1E 或此前范围内明确修复的问题;不扩展至 M2 的 `approvedGddRef` 或完整构建绑定。 -- **已验证**:新增“第五次终止”“第五次后恢复仍终止”“超限既有 GDD 转 reconciliation”及“lineage 版本上限不作 Provider feedback”四条回归;随后 Rust `planning_` 定向组 **157 passed / 0 failed**,`cargo check --offline --all-targets`、`cargo fmt --check`、`npm run check:encoding`(6608 files)和 `git diff --check` 均通过。仓库既有 Rust warnings 未在本包扩修。M1E 完成。 - -## 2026-08-18 M1D-2 隔离工作树实现:入口分流与阶段进度 - -- **隔离范围**:在 `codex/genarrative-isolated`、基线 `5b11a0530` 上开工;只接入口分流、阶段进度和实际项目总控页面的现有审批卡挂载,不接 M2 `approvedGddRef` 构建绑定、完整构建按钮或 M1E 端到端故障注入。 -- **入口合同(已被 2026-08-19 更正)**:本条原将游戏新项目默认接为 `standard + project-supervisor-plan`,现改为仅首页“做方案”进入该路径;做游戏/做素材保持既有直接构建。项目打开/项目页新建不注入 start mode,保持老项目行为。 -- **页面接线**:`ProjectSupervisorView` 现在复用 M1D-1 的 hydrate、GDD 审批卡和阶段进度;阶段进度只消费 `plan-gdd-state-view.v1`,显示 `轮次 x/3`、当前版本与状态徽章。`project-planning` 显示为“立项策划 Agent”,设计组展示名改为“设计实现组”。 -- **Runtime 路由**:项目总控聊天提交在规划入口或已读到 `project-supervisor-plan` 时继续使用 `standard + project-supervisor-plan`;规划 Run 尚未终态时不另起通用聊天 Run,要求先完成当前策划步骤。直接开建及非规划入口保留原提交 profile/source。 -- **自审边界与验证**:只接受有完整触发链路的问题;本包不改变 Runtime source 门禁、审批命令、构建准入或老项目 direct-build 语义。改动文件 ESLint、Shell TypeScript 两套 typecheck、`agentRuntimeModel.test.ts`(29 passed)、编码检查与 `git diff --check` 通过;appSurface UI suite 受仓库现有缺失 Tauri plugin 依赖阻断,未把该基线失败归因于本包。自审未发现测试失败或修复边界明确的 M1D-2 之外缺陷;M2 approved-GDD 构建绑定与 M1E 跨层故障注入保留。 -- **合入状态**:M1D-2 以 `bf2185fba`(`完成M1D-2入口分流与阶段进度`)fast-forward 合入 `feat/five_min_design`;自审中发现 `WorkspaceLauncher` 漏解构/传递新增的 `createHomeDraftDirectBuild`,会使 Shell typecheck 直接失败,已在同一提交修复。 - -## 2026-08-18 M1D-1 隔离工作树实现:hydrate/read model 与 GDD 审批卡 - -- **隔离基线与范围**:在 `codex/genarrative-isolated`、基线 `14c00017c` 上开工;只实现 M1D-1 的前端 hydrate/read model 与 GDD 审批卡,不接 M1D-2 入口分流、完整构建按钮、构建准入或 M1E 下游链路。 -- **Runtime hydrate**:新增 `hydrate_game_creator_plan_gdd_state` 与 `plan-gdd-state-view.v1`。command 通过严格 `deny_unknown_fields` 的 `{projectPath}` JSON 输入并经过项目权限校验;Rust 只读取 canonical GDD/receipt/session/pending/index authority,空 planning 项目返回 `not_started` 且不创建目录,页面不扫描 sidecar/Markdown/index。 -- **审批卡链路**:工作台在项目打开、Runtime 状态推进、窗口恢复时 hydrate;待审 GDD 由 hydrate 提供,审批决定调用既有 `decide_game_creator_plan_gdd`,按 `(approvalRequestId, action)` 复用 `gdd-response-`,决定返回后再次 hydrate。卡片提供 approve/revise/reject,后两者必须填写原因;正文通过独立详情弹层展示,不在卡片下无限堆叠。 -- **恢复与安全边界**:`recoveryPending` 时卡片只显示恢复重试,禁止决定;审批 pending 只有验收门已落下的 `gdd-approval` sidecar 才能成为可操作事实,不能把 `awaiting_gdd_approval` session 误当验收通过。未审批 GDD 缺失或错绑 session successor 时返回 `PLAN_SESSION_RECOVERY_REQUIRED`,pending/receipt 身份不一致 fail-closed;hydrate 响应使用序列号丢弃过期并发结果。 -- **必要回归与验证**:保留一条必要 Rust 回归,验证已初始化但无 planning 目录 hydrate 返回完整空 view 且不创建存储;该测试通过。`cargo check --offline --all-targets --target-dir target-m1d1`、`cargo fmt --check`、`git diff --check` 通过。前端 TypeScript 复用原工作树依赖 junction 做检查,新增代码无类型错误;仓库现有缺少 `@tauri-apps/api/event`、`@tauri-apps/plugin-http`、`@tauri-apps/plugin-clipboard-manager`、`@tauri-apps/plugin-opener` 依赖的问题仍保留。 -- **自审保留项与合入状态**:真实验收门等待期间 pending 尚未建立时不显示可操作审批卡;M1D-1 不新增完整跨层故障注入或构建链测试,留给 M1E。所有检查以完整触发链路为准,未发现需要扩大到 M1D-2/M1E 的问题。实现已由 `0052a80da` 合入 `feat/five_min_design`,其后 ESLint 修正为 `5b11a0530`。 - -## 2026-08-18 `M1C-2c` 隔离工作树开工:A/B 决策卡语义与信封合同收口 - -- **隔离基线**:在 `codex/genarrative-isolated` 上从 `0199fb6e4` 开工;`M1C-2b` 已合回 `feat/five_min_design`,本包不回改其三轮上限、continuation 幂等、答案绑定或预算折叠。 -- **本包范围**:把 planning 决策卡从“同一推荐的三种采纳程度”改为 A/B 平行方案 + 固定“需要原型验证”;Runtime 按 label 形状校验并确定性映射 `state` / `answerSource` / `answerSummary`,同步更新 planning role brief、final-reply 收束提示与定向回归。 -- **冻结映射**:A、B → `confirmed / user_option`;固定第三项 → `prototype_pending / user_option`;自由填写 → `confirmed / user_freeform`;`default_pending / default / round=0` 只允许由未提问默认项产生。`answerSummary` 必须逐字等于用户选中的 label 或自由填写原文。 -- **信封合同**:每张 planning 卡必须恰好三项;第一项 label 以 `A` + `·`/`:`/`-` 开头,第二项同形以 `B` 开头,第三项逐字为“需要原型验证”;形状不符 fail-closed,不建立 pending,不改变 session。 -- **提示词纪律**:B 必须是真实、形状不同且说明代价的平行路线;第三项 description 要给出可执行的 30~90 分钟微型原型验证;平台事实和 MVP 已排除项不提问;改口保留用户原文并标注被哪一轮推翻。 -- **纠正旧记录**:此前 M1C-2b 条目把“第四轮”写成正常进入 reconciliation;代码核查确认正常路径在 `agent.delegate` 边界硬拒并返回 failed observation,`planning_coordinator` 的超三轮 reconciliation 仅是损坏血缘的纵深防御,后续文档收口时一并更正。 -- **当前进度**:Runtime 映射、role brief、Supervisor playbook/final-reply 提示、A/B/自由填写/非法信封回归已落地;`planning_clarification_*` **13 passed / 0 failed**,`project_planning` prompt 定向回归 **5 passed / 0 failed**,`planning_submit` 定向回归通过,prompt bundle、`cargo fmt --check`、离线 `cargo check --offline --all-targets --target-dir target-m1c2c`、`npm run check:encoding`(5406 files)及 `git diff --check` 均通过。实现已由提交 `6e4bd9703` 合回 `feat/five_min_design`。 - -## 2026-08-17 M1C-2b 隔离工作树实现完成:策划澄清中转、链路派生与预算注入 - -- **当前基线**:`M1C-1`、`M1C-2a` 已合回 `feat/five_min_design`;本隔离分支开工后又以 merge commit `9f12d8467` 合入原分支截至 `a8215a599` 的全部已提交改动,包含 P4 的 Fast GDD 识别顺序修复与 P5 的委派栅栏 detail 等价性锁定。原工作树未提交的 `planning_approval.rs` 不属于该合并且未触碰。M1C-2a 的固定 Goal Contract、验收图与审批前置门作为既有前置,不在本包回改。 -- **本包范围**:只把已发布的 `AGC_NEEDS_USER_INPUT_V1` 子 Agent → Supervisor 中转链接入 Fast GDD 的 planning session:回答以 `(requestId, answersSha256)` 原子绑定原 delivery 后,严格派生唯一 continuation identity、更新 `appliedAnswers` / session phase / active run 投影,并让 planning 子 Agent 的后续 Provider 请求获得当前澄清轮次与累计活跃时间的受控上下文。 -- **编码前裁决**:现役 answer sidecar 只有题目、原始回答及 transport hash,不能提供 session schema 对 `decisionsSummary` / `prototypeValidationItems` 的必需字段;若等 continuation Provider 补字段,该 Provider 又会先被 `appliedAnswers.length != clarification_round` 拒绝。故冻结 Runtime 的确定性派生:从固定问题前缀提取 topic,按三个固定选项/自由填写映射 state、answerSource、answerSummary;“需要原型验证”同步生成固定四字段 30~90 分钟微型原型项。派生失败或已有同 ID 内容不一致均失败关闭,不请求 Provider 猜测。 -- **开工验证计划**:先以现有澄清中转回归为基础,补 planning 专属的三轮边界、答案冲突、重复 wake/continuation 幂等、session 链与预算注入断言;随后运行对应 Rust 定向测试、格式/编码/diff 门禁。实现和验证结论在本条持续补充。 -- **当前实现**:新增 planning coordinator,在首个 `project-planning` child 落 revision 1 initial-request session;`NeedsUserInput` delivery 投影为 `awaiting_user_input`;回答绑定且 continuation child durable 后,在同一项目写锁内严格派生 `appliedAnswers`、`decisionsSummary`、`prototypeValidationItems`、`collecting + activeRunId`。固定三选项及自由填写均按技术方案映射,问题必须是单题、`第N轮·关键决定`、固定三选项且 N 为 1~3;第四轮在建立 Supervisor pending 前失败关闭。 -- **恢复与锁序**:本条只约束 **M1C-2b 新增的 planning 澄清写投影路径**,不把结论扩大到整个 Agent Runtime。Supervisor 直接回答先以只读候选判别是否为 planning 澄清,再按 `project write lock → execution lock` 重取并在双锁内重读 pending;`answer-prepared` 恢复先释放旧 execution lock,再按同一顺序重取,期间旧候选若已被并发回答、替换或清理,只按 obsolete candidate 让路,不把合法前滚误标为 reconciliation;planning parent-wake 先把父 run 持久化为 `waiting-for-delegate-receipts`,待主循环返回并释放 execution lane 后,再在 lane 外按 `project → execution` 创建唯一澄清 pending,lane 忙时只 deferred、重放不增加 session revision 或 action。恢复仍在 planning child 进入 Provider 路径前补 session 投影;已精确投影的 retry/provider handoff 只恢复冻结请求,不因 usage fold 的 `Deferred` 误进 reconciliation。**边界说明**:`main_loop.rs` 既有通用 completion blocker 仍存在 execution lane 内调用 project-lock wrapper 的路径,它不是 M1C-2b 新增逻辑,也不在本包重构范围;因此本包不得表述为“项目写锁始终先于全部 Session lane / execution lock”。 -- **预算事实**:Provider 真实 future 的 completed / failed / interrupted 活跃区间以 requestId create-only fact 写入 Agent DB;同 ID 内容冲突失败关闭。下一次新的 planning request 在项目锁内、重建 request 前折叠合法 facts 到 `accumulatedAgentMillis`,因此冻结 binding 不会在 Provider 返回处漂移;项目锁、请求构造、用户/审批等待、retry backoff、handoff、工具执行与停机时间均不计入。fold 发现当前 run 仍有 ready / executing lifecycle 时返回 `Deferred`,不擅自改写 session。末次 `plan.submit_gdd` 的 usage fact 会被 v4 submit batch 暂时挡住;receipt 已完成 session 投影且精确消费 standalone/v4 anchors 后,同一项目锁内再 fold,确保直接 approve 而无下一次 planning request 时该区间也计入 session;任何 deferred/identity/I/O 异常只留 `recoveryPending`,不强写。 -- **本轮已修的明确缺陷**:plan 回答读取曾把 opaque `taskId` 误与 Supervisor `agentId` 比较,会令第一轮 continuation 必然失败。现改为读取同一 parent run 的 Supervisor root task,并精确核对 taskId、agentId、sessionId、runId、source、requestId、questionsSha256 与 answersSha256;回答 sidecar 的共用 payload 校验保持完整,不降低普通 user-input 的身份校验。 -- **终审修复**:完成至少一轮澄清后,审批 `revise/reject` 会保留 `appliedAnswers`,但新修订 delivery 的身份不再等于最后回答 continuation;旧纯 session 判据会把合法修订 successor 固定拒成 `PLAN_IDENTITY_CONFLICT`。现把独立 schema 校验收窄为“不得回退到已消费问题 delivery”,并在 session 新值、已有 primary/previous、发布后回读及普通读取边界读取真实 static-delivery 谱系:从 latest 回到最后回答 continuation 的**每一条边**都必须由父 delivery 的 `UserRevisionRequested` 状态授权,且 root/agent/session 身份一致、无 Unknown、缺节点或循环;质量返工边不得借路径中其它用户修订继续保留旧回答。正向回归同时覆盖 `revise/reject` 后 continuation、轮次/回答/决定保留与 Provider 注入;负向回归证明混入质量返工边时,即使重算合法 session fingerprint 仍失败关闭。 -- **门禁中修复的测试缺陷**:并发整组首次复跑时,锁序测试把“回答线程开始”误当成“已得到调度”,180ms 内未观察到 project lock 竞争而失败;同用例精确复跑通过。测试只将调度观察窗口放宽到 2 秒,断言仍要求真实 project lock 竞争发生后才释放被占用的 execution lane,未改变生产锁序或放松结果判据。 -- **纠正旧观察**:第四轮澄清委派在 `agent.delegate` 工具边界就按持久化血缘轮次上限硬拒,返回普通 failed observation,正常路径不会进入 reconciliation;`planning_coordinator` 的 `clarification_round > 3` 分支仅是损坏/篡改血缘的纵深防御。Supervisor 可据失败 observation 改走 `plan.submit_gdd`,不需要在 M1C-2b 另加自动 submit 状态机。 -- **最终门禁证据**:`planning_clarification_*` **11 passed / 0 failed**(原 9 条之外新增真实 main-loop 释放 execution lane 后 parent-wake 回归,以及已回答后 `revise/reject` 修订回归);`tests::collaboration::static_deliveries::*` **44 passed**;planning storage **13 passed**(含重新计算 fingerprint 的混合质量返工谱系负例);`planning_submit` **53 passed**;`planning_provider_usage` **4 passed**;真实末次 submit usage receipt 回归 **1 passed**;`barrier_detail_*` **3 passed**。`cargo fmt --check`、`cargo check --offline --all-targets --target-dir target-m1c2b`、`npm run check:encoding`(7810 files)及整个工作树 `git diff --check` 均通过。**M1C-2b 本包实现及门禁已完成,并已快进合回 `feat/five_min_design`;审批 UI、hydrate、构建准入和下游完整构建仍后置。** - -## 2026-08-17 立项策划决策卡改为 A/B 平行方案:`default_pending` 收回为未提问默认项唯一来源,改口不改合同,立包 `M1C-2c` - -- **裁决**:决策卡三选项从「接受推荐 / 暂按推荐 / 需要原型验证」(同一条推荐的三种采纳程度)改为「方案 A(推荐)/ 方案 B(真实可行、形状不同的平行备选)/ 固定『需要原型验证』」,仍恒定三项,第 3 项的 description 须给出这一题可执行的验证方式。A、B 均记 `confirmed / user_option`;`需要原型验证` 仍记 `prototype_pending` 并要求同 ID 微型原型项;自由填写仍记 `confirmed / user_freeform`。`default_pending` 不再由任何选项产生,只表示**未提问、由子 Agent 按默认建议填写**的字段(`answerSource=default, round=0`),与 `plan.submit_gdd` 校验「前缀之后只允许追加 `default_pending` 默认决定」完全一致——三个决定状态各只有一个来源。状态映射按 label(A/B 前缀 + 第 3 项固定文案)而非位置,Runtime 校验信封形状;`answerSummary` 直接落所选 label,台账自描述。 -- **依据**:以 DeepSeek v4-flash 做的本地原型多轮实测(原型不入库、只用于开发调试):选 1 与选 2 产出的 GDD 一字不差,差别只是一个不进任何机制的标签,却消耗一轮问询名额(上限 3);台账只落「接受推荐」三个字,用户下一轮改口推翻上一轮已确认决定时,Supervisor 转述与子 Agent 出稿只能靠改写文本消化。改为 A/B 后两轮冒烟:B 均为带独立代价说明的真实岔路;模型一次擅自把第 3 项换成自定义方案 C,被信封形状校验拒回并自行改正。(首版曾允许第 3 项按问题性质省略,产品拍板改回恒定三项。) -- **改口(已确认决定被后续自由填写推翻)**:M1 内不改合同——台账前缀不可变,两条 `confirmed` 并存;Supervisor 转述时必须在被推翻的那条后注明「已被第 N 轮回答推翻,以后者为准」且用户答案原文逐字保留(不得改写、拆分或搬轮次),子 Agent 按后者出稿并在新决定 topic 中写明推翻关系。实测三次改口 Supervisor 均能消化,但一次靠改写用户原文(转述保真审计报「内容缺失」),因此该规则必须写进 Supervisor prompt。`supersedes` 字段进 `plan-gdd.v1` 列为 M2 候选。 -- **提问纪律补一条**:平台事实已定的事(含移动/桌面优先级)与 MVP 规则已排除的事(多人/联机/商城/服务器)不作为问题;A/B 格式会诱使模型问"天然二选一"但无价值的问题,实测一轮 3 张卡 2 张如此。 -- **与 `M1C-2b` 的关系**:拟定本条时 `M1C-2b` 尚在隔离工作树,其合入门禁(3 轮上限、continuation 重放幂等、答案绑定冲突被拒)与选项文案无关,故按当时的 §5.2 映射表实现,并已于同日快进合回(见上一条)——它把「单题、`第N轮·关键决定`、固定三选项」的校验与「三个固定选项/自由填写 → state、answerSource、answerSummary」的确定性派生冻结在 planning coordinator 里。映射翻转、信封形状校验与 prompt 文案由新立的 **`M1C-2c`**(依赖 `M1C-2b`,现在即可开工)承接。**`M1D-1` 前端决策卡直接按新语义实现**(label 动态渲染、默认焦点 A、Other 槽不变),避免做两遍。§5.2 正文、§5.1 prompt 段与 §23.6「仍冻结:固定选项」一句由 `M1C-2c` 一并改写;本轮只在 §5.2 顶部加了指向注、新增 §23.9 与 §23.8 表 `M1C-2c` 行。 -- **顺带核对项(归 `M1E`)**:`plan.submit_gdd` 连续校验失败必须有次数上限。本地实测无界时模型对大载荷序列化出错后连续 40 余次重试、每次重放全部历史、单次 prompt 涨到 15 万 token;240/300 秒预算注入兜不住「硬超时后仍连续校验失败」。若 §12 retry 状态机没有该上限,补一个(原型取 5 次)。 -- **不改的部分**:`user.input_request` strict input 2~3 项区间、Other 槽 placeholder、`prototype_pending` 同 ID 验证项规则、第 12 节提交校验、§23.7 用户修订不计 `repair_depth`(`M1C-0`/`M1C-1` 已落地)均不变;生产代码本轮零改动。 -- **关联**:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 5.2 节指向注、第 23.8 节 `M1C-2c` / `M1D-1` 行、第 23.9 节。 - -## 2026-08-15 M1C-2a 隔离工作树实现:固定 Goal Contract 与审批前置门 - -- **本轮范围**:只实现 Supervisor 根 run 的 Goal Contract / Acceptance Graph 与 Fast GDD 审批前置门;不接 `M1C-2b` 澄清中转、审批 UI、构建准入或下游完整构建。当前变更仍在隔离 worktree,尚未合回原分支。 -- **固定合同**:`project-supervisor-plan / standard` 的首轮与格式修复请求都必须且只能调用一次 `agent.goal_contract`。按项目变化的字段只有 `outcome / nonNegotiables / forbiddenAssumptions / openQuestions`;`preferences` 固定空数组,`acceptanceNodes` 固定为唯一 `fast-gdd-serves-intent` 节点,required evidence 固定 `tool:file.read`。其它 source 保持动态合同,即使使用同名 criterionId 也不套 Fast GDD 特例。 -- **证据合同**:Fast GDD evidence 只接受当前 Supervisor 根 run 自己读取 `game/fast_gdd.md` 的成功 action receipt。receipt 保存规范化路径、完整内容 SHA-256 与行覆盖摘要;多页必须从 `startLine=1` 无缺口、无重叠覆盖到 EOF,全部页同 hash/同总行数,`agent.acceptance_update.evidence` 必须列出所有分页 actionId。普通 Graph 读取只验 durable receipt 形状与完整覆盖;当前 Markdown hash 只在审批前且无 exact pending/receipt 时复核,审批后的状态投影不会让旧 Graph 损坏。 -- **取证与返工三态**:planning delivery 先由同一根 run 的 `agent.run_status` durable 认领。Graph 缺失、not-observed、project revision/hash 过期或证据不完整属于 `NeedsEvidence`,下一步是 `file.read`,不得提前返工;只有当前完整证据支撑的显式 failed 属 `RepairRequired`,才返回原 delivery 的 `repairOfDelegationId`;passed 才幂等创建 `gdd-approval` pending。`run_status` 在持有项目锁时调用 locked gate,并在 Ready 数为零时仍重放,封住旧 action 已认领但 gate 尚未落盘的崩溃窗口;GDD create 到 child/delivery 完成前保持惰性,不抢断 M1B-2 恢复。 -- **恢复与完成门**:acceptance update、delivery claim、Runner recovery、completion 与 finalization 都消费同一 gate。恢复不重新执行 Provider 或 `file.read` 动作,只复核 durable Graph、动作回执与当前 Markdown hash;missing pending 按原 approvalRequestId/identity 补建,exact pending 与 receipt 优先于 Graph/hash 复核,冲突则失败关闭。当前根没有自己的 GDD 时,上一根遗留 Markdown/Graph 不能绕过完成门。 - -## 2026-08-15 M1C-1 隔离工作树收口:审批核心与专用完成门已落地,生产前置门保持后置 - -- **本轮落地**:在 `planning_storage.rs` 增加 `plan-gdd-approval.v1` receipt、`plan-gdd-approval-pending.v1` projection、comment/decision/receipt/pending 的 typed fingerprint 与 strict canonical 校验;审批 observation 固定校验 tool/status、版本摘要、detail 前缀和规范化 comment。`planning_approval.rs` 增加 receipt create-only、三动作幂等(`committed / replayed / already-decided`)、版本/指纹竞态防护、receipt 后 index/Markdown/audit/terminal observation/session 投影与恢复,以及只读的 plan 根专用 completion blocker;`commands.rs` 暴露 `decide_game_creator_plan_gdd`。 -- **恢复边界**:generic `plan.submit_gdd` 的 v5 standalone pending 与 v4 batch 仍是独立恢复锚点。receipt 投影只在 exact planning-submit batch(v4、单 action、cursor 已到 1、completed、observation 与 receipt 逐字相等)时清理残留;pending 已缺失但 terminal observation 存在时仍校验/清理该 batch,形状不 exact 则保持 `recoveryPending`,不猜测删除。无 receipt 的 GDD 不由本包自行重建审批 pending。 -- **明确后置**:技术方案第 13.0 节要求的 acceptance-gate 取证成功后才创建生产 `gdd-approval` pending;当前创建 helper 仅由定向测试调用,真实 submit/recovery caller 留给 `M1C-2a` 的验收前置门接线。审批 UI、验收图接线和构建准入仍未完成,不能把当前隔离 WIP 宣称为完整产品交付。 -- **验证**:专用 target `target-m1c1-current` 下 `cargo test --all-targets planning_submit --no-fail-fast`(37 passed,含 completion blocker 正向/阻塞/作用域/identity 回归)、`cargo test --all-targets planning_storage --no-fail-fast`(11 passed)、`cargo check --all-targets` 通过;`npm run check:encoding`(7848 files)和 `git diff --check` 通过。编译仍有仓库既有 warnings,不作为本包缺陷。 -- **关联**:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 13、14、23.6、23.8 节;本条只记录隔离工作树状态,合回原分支前仍需按既定合并流程复核。 - -## 2026-08-15 CI 五条失败归并为三个根因:修位置,不修症状 - -栈溢出修复后的全量跑出 5 条失败(`2051 passed / 5 failed`,**零栈溢出**,栈修复站住)。逐条定位后归并为 3 个根因,**全部先于本轮工作**:与栈修复、`M1C-0b`、以及刚合入的 master 都无关,master(`9f5c84ee7`)自身全绿,`deae1e08c`(栈修复前、`M1C-0b` 前)已全挂。三者形状相同——**新增的检查被放到了链路更靠前的位置,改变的是作用域而非严格程度**,详见 pitfalls 同日条。 - -- **归属**:`tool_plan_handoff_rejects_out_of_order_entries` + 两条 `tests::goal`(`provider_action_batch_goal_resume_never_rewinds_newer_steer_cursor`、`agent_goal_paused_edit_replans_old_confirmation_in_same_run`)→ `M1B-2`(`27c3eb847`)的 `validate_next_entry` 判据上提;`response_stream::structured_plan_finalization_without_readable_runtime_state_needs_reconciliation` → 同一提交新增的 `missing_plan_submit_anchor_candidate_at` 强读;`immutable_writer_rejects_symlink_and_hardlink_targets` → `M1B-1`(`f453c2ca2`)起就没绿过的错误码分类,Windows 上编译都不参与故一直不可见。 -- **证据不是推测**:失败用例遗留的临时项目目录里,`.agent/agent.db` 记着 `failureKind=tool-plan-integrity`、`errorChars=55`、`errorSha256=0750f609…`,与 `M1B-2` 新增那句「`tool-plan 成功响应交接 entry 的 Provider/session binding 链身份冲突`」的 SHA-256 与字符数逐位相同;`requestSlot` 由 `loop-1-repair-0` 走到 `loop-2-repair-0`,直接指认是跨 loop 那一跳被误判。 -- **裁决一:撤回上提,而不是放宽判据本身。** `same_tool_plan_repair_chain` 含 steer cursor、goal revision/快照与 planning session binding 这些本轮量,进入新 loop 本就意味着它们前进;这条判据只在同 loop 的 repair 之间成立。**撤回不留缺口**:跨 loop 的 durable 身份由 `same_durable_tool_plan_run` 守,binding 漂移由 `provider_retry.rs` 的 `..._drift_fields`(含 `planningSessionBinding`)在**每个请求**层面守,`ledger.rs` 的 `is_later_repair_identity` 一直就把这条判据限定在同 loop——上提是模块内唯一的例外。**未采纳**「保留跨 loop 检查但只比 durable 子集」:`gdd_id` 在策划子 run 内会从 `None` 变 `Some`、`goal_id` 亦非绝对不变,凭空发明一条无测试支撑的新不变量,对一个管 Provider 计费与身份的安全屏障不划算。上提本身也**没有任何测试**(`M1B-2` 在该文件只机械补了一行 `planning_session_binding: None`)。 -- **裁决二:探测器不得对自己的前置条件 fail-fast。** `missing_plan_submit_anchor_candidate_at` 是机会性修复,不是门。state 读不出来就不可能匹配它要找的形状,改为 `Ok(None)`;不可读 state 的处置权归下游 `resume_game_creator_agent_finalization_at`(从 task record 重建并 fail-closed 到 `needs-reconciliation`)。**不算掩盖**:紧随其后的那一步照样会读同一个文件并留下 `reason=runtime-state-missing` 的记录。**未采纳**「把探测器挪到 finalization 之后」——那会让 `Recovered`/`Blocked` 分支的 `continue` 直接跳过探测,改动的是语义而不是位置。 -- **裁决三:链接一律按不可信路径分类,且模块内统一。** 新增 `resolve_planning_path`:先用模块自己的 `planning_metadata_is_link_or_reparse` 逐组件判链接/重解析点(比通用解析器的 `is_symlink()` 多覆盖 Windows reparse point),命中返回 `PLAN_UNTRUSTED_PATH`,其余仍交通用解析器并保持 `PLAN_INVALID_PATH`。模块内 13 处解析全部改走它。**未采纳**「改测试去迁就现状」——同模块 `ensure_planning_parent`、`verify_regular_planning_file` 都把链接判为 `PLAN_UNTRUSTED_PATH`,测试写的才是既定语义。已确认无生产代码或其它测试对这两个码做分支(全仓库仅这两处断言)。 -- **回归钉边界,不只钉拒绝**:新增 `tool_plan_handoff_accepts_new_loop_after_steer_and_goal_revision_advance`——跨 loop 且 steer cursor / goal revision 已前进必须被**接受**。原有用例只钉「什么该拒」,所以判据作用域被放大时无人报警。 -- **验证**:4 条可在 Windows 复现的用例全绿(含新增回归);Linux-only 那条在 WSL 上跑通。同轮曾出现 3 条 `response_stream` 超时失败,空载单独复跑 3 passed / 0 failed,且三者走 `final-reply` 路径、不经过 `validate_next_entry`,与本次改动无因果——判为负载抖动。 -- **未做**:不追查这 5 条各自的完整历史绿/红轨迹(引入点已锁定到单个提交,继续二分无增量);不动 `M1B-2` 的功能面。 -- 关联:`apps/ai-game-creator-shell/src-tauri/src/tool_plan_handoff/{identity_order_validation.rs,tests.rs}`、`.../agent/runtime_driver/recovery_scan.rs`、`.../agent/runtime_protocol/planning_storage.rs`;pitfalls.md 同日「把校验往链路前面挪」条。 - ## 2026-08-15 恢复重启栈溢出:主循环专用 worker 从「枚举入口」改为不变量 CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 `tokio-rt-worker` 栈溢出并 SIGABRT。属于本文件 pitfalls「Runtime 后台执行不能让大型 async frame 共用默认 worker 栈」的同一失败类,但暴露出该条目的判据形式本身有缺陷。**Windows 上可直接复现,不必去 Linux/WSL**。 @@ -1492,18 +1190,6 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - **需回流 master**:master 同样存在「恢复重启走默认栈」与「`drain_next_*` 余量偏低」,只是尚未触发;与 `manifest.rs` 的 Windows 构建修复同理。 - 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/{task_queue.rs,recovery_scan.rs}`;pitfalls.md 同名条目已按不变量重写。 -## 2026-08-15 M1C-1 开工前三条裁决:barrier 独立计数、正向校验的上限、前向兼容粒度另拆 `M1C-0b` - -对本文件 2026-08-14「`M1C-0` 合入复核」留下的三条前置逐条裁决,全部读实代码后定稿。**其中第三条订正了 08-14 自己的表述**:原文写的「二选一:给枚举加单条容错,或接受回滚锁死」是伪二选一——「单条跳过并告警」这个选项对 delivery 不安全,已作废。 - -- **裁决一:补 barrier,且必须是独立的第六个计数 `user_revision_pending_count`。** 不补的后果是 `M1C-1` 写入方一落地,Supervisor 就能在用户修订尚未派出时收束用户任务(收束门 `static_delegate_completion_blocker_at_locked`)。不复用现有两个计数的理由是硬的:`repair_required_count` 带 `repair_of_delegation_id.is_none()`,只算原始委派——因 depth≤1,返工的返工本就不该阻塞;而**用户第 2 次修订的父节点自身就是 repair 节点**,并进去会让第 2 次及以后的修订全部不阻塞,恰好在最需要处失效。`user_input_required_count` 的语义是「等用户回答」,与「用户已发话、等 Supervisor 派发」不同,`detail()` 文案会说反。判据形状照 `user_input_required_count`(`ClaimedByParent` + 该 status + 无活跃 child,**不带** `repair_of.is_none()`)——它正是仓库里「轮次无上限」概念的既有先例。**连带**:除 `is_clear()` 与 `detail()` 外,另有四处调用点逐字段读 barrier 而不走 `is_clear()`(`autonomous_policy.rs` 两处、`runtime_tools/delivery.rs` 两处),加字段不会自动传播,每处单独裁决。这是 `M1C-0` 那个失败模式的同构版本,载体从 enum 变体换成 struct 字段,编译器同样沉默。 -- **裁决二:正向一致性锁死在「只能从 `EvidenceReady` 改写而来」,不得更严。** 复用 `EvidenceReady` 的三条客观证据约束(终态 `completed`、无缺失产物、`verification_required` 时 `verified_revision` 存在);产物覆盖校验与「不得携带 `user_input_questions`」已对所有状态生效,不需改。**重点是实现形状**:把 `validate_static_delegate_structured_result` 里按 `contract_status` 的 if/else-if 链改成穷尽 `match`、不留 `_`——同一失败形状在本仓库已出现两次,靠纪律没拦住。**反向风险**:该函数不是入口过滤器,`read_static_delegate_delivery_at` → `validate_static_delegate_delivery_record` 让它每次读取都跑;过严等于把写入方的一个 bug 变成「该 delivery 永久读不出来」,直接触发裁决三的锁死,故不得再加 `EvidenceReady` 自己都没有的条款(例如要求 `error` 为 `None`)。**另订正 08-14 前置二可能引起的误读**:它不是既有漏洞——专业 Agent 自证「用户要求修订」今天不可达,唯一派生点 `build_static_delegate_structured_result_at` 只能产出三个旧变体,claim 回执的 `structuredResult` 从 delivery 拷贝,均为 Runtime 侧;本条约束的是 `M1C-1` 引入的**第二个写入方**(审批命令)。 -- **裁决三:「单条跳过并告警」作废,走第三条路,并另开 `M1C-0b`。** 单条跳过不安全的原因很具体:`list_static_delegate_deliveries_at` 喂的是 barrier 的**计数**,跳过一条损坏的 `Dispatched` 记录会让 `waiting_count` 少 1、barrier 变 clear,Supervisor 在仍有未完成委派时收束——把可用性故障换成了正确性故障,比锁死更糟。对照组说明危险面很精确:谱系重放不受影响,缺节点走「上游缺失」出口返回 `(u32::MAX, u32::MAX)` 哨兵,本就 fail closed。**第三条路是把「解析失败」拆两类**:损坏(截断/非 UTF-8/超限)状态未知,维持整体锁死不动;前向不兼容(结构良好、只有一个 enum 值不认识)是已知的未知,解析进显式 `Unknown` 但最大化阻塞(进 barrier、返工门无条件拒、谱系按最保守的「其它」分类,只会拒不会放)。不违反 `M1C-0` 条里「不得静默降级为 `NeedsRepair`」——那条禁的是把记录当正常记录继续走流程,`Unknown` 是显式隔离。半径从「整个项目静态委派面不可用(做游戏链路一起挂)」降到「只冻结携带该状态的那条 lineage」。 -- **两条比 08-14 记录更糟的事实。** 锁死半径是**整个项目目录**——`list_static_delegate_deliveries_at(root)` 先读全目录再按 parent run 过滤,任何一条无关 run 的损坏记录毒化所有 run 的 barrier;`.json.previous` 备份救不了——`read_agent_runtime_json_sidecar_with_max_bytes` 只在 primary **NotFound** 时才回退,损坏但存在的 primary 不回退。 -- **实现坑。** `#[serde(other)]` 用不了:它只允许在 internally/adjacently tagged 枚举上,而 `StaticDelegateContractStatus` 是序列化成纯字符串的 unit-variant 枚举;需自定义 `Deserialize`,且 `Serialize` 必须原样回写原始字符串,否则旧版本任何一次读-改-写都会把未知值抹掉。 -- **拆包与门禁。** ①② 进 `M1C-1`(它们约束的正是 `M1C-1` 新增的写入方);③ 拆 `M1C-0b`,与 `M1C-0` 同形状——无写入方、纯读路径、对既有记录零行为变化可证;塞进审批闭环的 diff 就是重犯第 23.8 节「拆包纪律」自己写的错。`M1C-1` 门禁因此为二选一:`M1C-0b` 先落,或直接上线并在 release note 明写「用过策划审批后回滚旧版本会让该项目静态委派面不可用」;**建议前者**,多机/版本不一致不需要用户主动回滚就会发生。`WP1` 的 depth≤1 结论仍未被推翻。 -- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 23.7 节「落地约束」裁决一/二/三与「回归必须覆盖」、第 23.8 节 `M1C-0b` / `M1C-1` 行与「拆包纪律」。 - ## 2026-08-15 M1C-0b:静态委派 durable status 前向兼容实现完成 - **落地**:`StaticDelegateContractStatus` 采用手写 serde。四个已知 durable 值保持原有字符串;结构良好的未知字符串解析为 `Unknown(raw)`,并在再次序列化及读-改-写时原样保留 raw;非字符串输入仍拒绝。该包只改读路径,不新增状态写入方。 @@ -1537,39 +1223,6 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 安全边界:已建立结构化计划后的 `completed / failed` 单调与不可改写语义保持不变,普通失败继续 fail-closed;不新增 smoke receipt 特判恢复通道,不放宽项目 revision、static smoke、desktop/mobile 试玩、身份绑定或最终完成门。 - 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。 -## 2026-08-14 M1C-0 合入复核:三条 M1C-1 前置、一条文档订正、一条已排除假设 - -合入 `M1C-0`(见本文件同日条)后对该包做整组复核。**结论:包本身可合,「今天行为零变化」成立且可证**;但新变体在 `M1C-1` 写入之日会同时点亮三个默认值,而这三个默认值都不是裁决出来的,是「给用 `==` 比较(而非穷尽 `match`)的 durable enum 加变体,编译器不报,新变体静默落到作者没想过的那一侧」的结果——与本文件「修复 M1A-2 引入的回归」条同源,是同一失败模式的第二次出现。(复核完成后 `M1B-2` 已合入;其对 `delegation.rs` 的改动是 rustfmt 换行、无语义变化,本条结论不受影响。) - -- **不可达性已实证,不是靠注释。** `contract_status` 的生产赋值点只有两处(终态回执构造与 `build_static_delegate_structured_result_at`),都从客观事实派生(终态、缺失产物、verification、问题数),模型选不了;`StaticDelegateClaimRecord` 全仓库**唯一**构造点在 `delegation.rs` 的 claim 准备路径,是运行时构造而非 agent 提交的 JSON,所以「claim payload 自带 `contractStatus`」这条路不存在;durable sidecar 位于 `.agent/runtime/delegation-deliveries/`,被 `is_agent_runtime_private_control_path` 覆盖,`file_ops.rs` 与 `project/filesystem.rs` 各三处拒绝写入;前端无 `contractStatus` 消费者。哨兵侧同样成立:`static_delegate_lineage_counters` 的三个 fail-closed 出口(成环、超长、上游缺失)**全部**返回 `(u32::MAX, u32::MAX)`,两个分量都是 MAX,新分支的 `== u32::MAX` 检查全接得住;合法累加上限 31,不会误触。 -- **前置一:新变体不进任何 barrier。** `repair_required_count` 的判据是 `== NeedsRepair || (== NeedsUserInput && 有澄清答案)`,`user_input_required_count` 要求 `== NeedsUserInput`,`UserRevisionRequested` 两边都不落,因此 `barrier.is_clear()` 可能为真——`M1C-1` 写入方一落地就意味着 **Supervisor run 可以在用户修订尚未派出时完成**。同源的还有 `swarm_cli/terminal_classification.rs` 的 `static_delegate_delivery_has_repairable_contract`(`is_none_or(|r| == NeedsRepair)` → 新变体判为不可返工,影响面小但同样是默认选的)。`M1C-1` 须显式裁决补或不补。 -- **前置二:`validate_static_delegate_structured_result` 缺正向一致性分支。** `EvidenceReady` 有(终态/产物/verification 对账),`NeedsUserInput` 有(问题数 + 指纹),`UserRevisionRequested` 只继承了「不得携带 `user_input_questions`」这条否定约束,于是 `UserRevisionRequested + terminal_status="failed" + 缺产物` 能通过校验落盘。`M1C-0` 新增的 real-gate 回归恰好依赖这一点才能构造 fixture。 -- **前置三:前向兼容失败的粒度是整个子系统,不是单条记录。** `list_static_delegate_deliveries_at` 对每条 sidecar 用 `?` 上抛,一条解析失败即整个目录枚举失败,barrier、lineage、返工校验、run status 投影同时不可用。`M1C-0` 当时有意不 bump `STATIC_DELEGATE_DELIVERY_SCHEMA_VERSION`,并在自身范围内把「未知状态 serde 直接报错」记为安全属性;**该前向兼容语义现由 `M1C-0b` 改为显式 `Unknown`,损坏输入仍整体锁死**。注意 bump 版本号救不了损坏输入:版本号校验排在 serde parse 之后。三条均已写进技术方案第 23.7 节「落地约束」与第 23.8 节 `M1C-1` 行。 -- **文档订正:`STATIC_DELEGATE_LINEAGE_MAX_HOPS = 32` 限的是链上节点数,不是跳数。** 判据 `chain.len() >= MAX_HOPS` 排在入链之前,故链最多 32 个节点 = **31 跳**。第 23.7 节原写「真实上限就是 32 跳」,已订正为 31。产品阈值 16 远低于两者,不受影响;`M1C-0` 自己的测试注释(「32 条 delivery(31 跳)」)一直是对的。之所以要订正:`M1C-0` 之前 `depth=1` 才是主刹车,用户修订路径上现在只剩这个哨兵,写错的数字从此是承重的。 -- **已排除的假设,后续不要重走。** 怀疑过「派一个子委派 → 抑制 → 再派一个」可绕开兄弟检查、在用户修订父节点下无限扩宽度。打不通:`suppress_static_delegate_delivery_at` 对 `ClaimedByParent` 直接原样返回、拒绝抑制,且生产侧唯一调用者是 `suppress_static_delegate_deliveries_for_parent_terminal_at`(父 run 终态清理),彼时同一 `parent_run_id` 已不能再派委派。另确认新分支**没有**跳过兄弟检查:if/else-if 链在前、兄弟检查在后且无提前返回,扇出仍是一条。 -- **测试侧本次一并处置。** ① `static_delegate_user_revision_preserves_existing_clarification_round` 名不副实:计数循环只遍历 `chain[..len-1]`(父节点集合),目标自身 status 从不参与判定,而该用例把 `UserRevisionRequested` 放在了**目标**位置,新分支根本没被执行。已改名为 `static_delegate_user_revision_parent_hop_preserves_depth_and_clarification_round`,补一跳真正以用户修订为父的续跑,并加反证(同一条链只把该父节点改回 `NeedsRepair`,结果必须变成 `(2, 0)`);原断言保留为对照组并注明其性质。② `user_revision_continuation_passes_real_gate_at_depth_one_but_stays_single_child`(原 `static_delegate_user_revision_requested_continuation_...`)补兄弟检查断言。③ 新增 `concurrent_user_revision_dispatch_creates_exactly_one_delivery`,与既有 `project_supervisor_concurrent_repair_dispatch_creates_exactly_one_delivery` 同构但父节点为 `UserRevisionRequested` 且链上 depth 已为 1,两侧同时钉住:并发下恰好放行一条,且不是零条(新分支被删则两条都会被 depth 门拒,用例变红)。 -- 教训(与 `M1A-2` 回归条合看):**给用 `==`/`!=` 比较的 durable enum 加变体,等于在每一个比较点上替作者做了一次没人复核的裁决。** 这类 PR 即使「无写入方、行为零变化」也必须逐个枚举比较点并写下每处落点,否则这些默认值会在写入方落地的那个 PR 里一次性生效,而那个 PR 的复核者只会看它自己的 diff。 - -## 2026-08-14 M1B-2 实现合同收口:Provider binding、结构化注入与提交恢复边界 - -- **状态与基线**:`M1B-1` 已通过门禁并合入,作为 `.agent/planning/` storage 基线;`M1B-2` 工作包已通过本包门禁并以 `27c3eb847` 合入本分支。当前包接 `plan.submit_gdd`、exact planning Provider 请求身份、提交点和恢复;`gdd-approval` planning pending、审批等待、receipt、审批命令与 UI 继续属于 `M1C-1` 及之后。本状态只表示 M1B-2 工作包完成,不表示完整产品可交付。 -- **binding 是有自指纹的 exact v1,不是可扩展 map**:`plan-provider-session-binding.v1` 在 `runId` 后固定包含 `rootAgentId`,并在 `requestContextFingerprint` 后以 required typed `fingerprint` 收尾;typed value 排除自身 fingerprint 但覆盖 `rootAgentId`,base Provider request ID value 同样在 `runId` 后覆盖 `rootAgentId`。`rootAgentId` 与末尾 fingerprint 是本次实现对委派根身份和 binding 自完整性的有意加固,不再称“额外字段”;缺失、重排、未知字段、重算不等或从当前状态补默认值都失败关闭。无 Goal 的唯一合法三元组是 `goalId=null / goalRevision=0 / goalSnapshotFingerprint=""`;Agent DB validator 不能先用通用非空 identity 门把这个合法空 fingerprint 拒掉。 -- **四类请求共用同一 captured context**:exact planning 的 `tool-plan | final-reply | context-compaction | final-reply-context-compaction` 全部写 `game-creator-provider-request-lifecycle.v3`、同一 required binding 和同一 structured injection;只有 `tool-plan` 能产生 action、`game-creator-provider-action-batch.v4` 与 `plan.submit_gdd`,其余三类无 action batch。planning idle context compaction 因没有 active run/session captured context 而明确不支持、fail-closed。request-context 的 `composition` 与 `sourceKind` 都固定为现役 Prompt Bundle 值 `runtime`,不得把 durable source `agent-delegate` 复制进 `sourceKind`;MCP 固定为空且 planning builder 不读取项目 MCP catalog,避免“先读后清空”制造额外依赖或漂移。 -- **structured injection 的 wire 冻结**:`plan-provider-structured-injections.v1` 顶层顺序为 `schemaVersion, clarificationRound, accumulatedAgentMillis, session, platformFacts, approvalObservation`;session 顺序为 `phase, decisionsSummary, prototypeValidationItems, latestSubmittedRef, lastDecisionRef`;平台事实复用固定 `PlanPlatformFacts`;approval observation 只能为 `null` 或现役 `tool, status, summary, detail` 四字段 strict 对象。canonical compact JSON 最大 64 KiB,以 dedicated user message 真正进入最终 `LlmRunRequest`:第一行 `AGC_PLAN_PROVIDER_STRUCTURED_INJECTIONS_V1`,第二行 JSON,无第三行与尾换行。同一第二行 JSON bytes 同时生成 `structuredInjections.wireBytes/wireSha256`,禁止重建另一份“语义相同”对象再摘要。 -- **submit 的包内 policy 与 session 真相**:`plan.submit_gdd` 配为 `confirm` 时按 deny 失败关闭,不创建 M1B-2 无法消费的 generic confirmation;显式 deny 同样拒绝。输入的 decisions 先逐项严格匹配 source session 的完整前缀,之后只允许追加 `default_pending/default/round=0` 的默认决定,不能把未提问项伪造成用户已确认。 -- **提交点不等于审批等待**:M1B-2 在 create-only GDD 提交点之后只重建 index、`game/fast_gdd.md` 与 session successor,再终止策划子 run/delivery;不创建 planning pending 或 waiting 投影。child terminal ensure 按原 action identity 可重入,task/event/delivery/Agent DB audit 各自幂等;delivery 必须发布后 exact 回读,未 durable 前不能先写 `recoveryPending=false` 的 committed audit。原 submit 在提交前已经建立的 generic `game-creator-pending-action.v5` standalone pending 与 v4 action batch 继续保留,供 M1C-1 receipt/terminal observation 按同 action identity 消费。standalone pending 保存 `providerBatchPlanUpdate`,使 pending-only 能精确重建完整 plan/planUpdate/batchId;batch-only 则补回同 identity pending。两枚 anchor 都在时严格对账;恰缺一枚且另一枚与 immutable GDD/frozen binding 严格匹配时确定性重建缺失投影;两枚都缺失、任一损坏或 identity 漂移时进入 reconciliation,不能只凭 GDD 猜完整 action/batch wire。双缺扫描覆盖 GDD commit 后、child finish 前且 Runtime `pendingToolAction=null` 的真实窗口,重复扫描不重复写 audit;已提交事实优先于其后的 repository/steer 漂移,同 action 只收口原投影、不生成新版本。 -- **本包门禁结果**:四类 request 的 binding/lifecycle/wire、structured DTO 字段与 64 KiB 边界、`composition/sourceKind=runtime`、idle compaction 拒绝、submit 业务/身份拒绝、提交点前后恢复、同 submission replay 不增版本、index/Markdown/session/child terminal 断点,以及 generic anchors 双在/单缺/双缺/漂移矩阵均已有定向证据;范围匹配的 Rust 门禁、`cargo check --offline`、`cargo fmt --check`、`npm run check:encoding` 与 `git diff --check` 已通过。本包已合入本分支;审批 pending/receipt/UI/构建准入仍不在本包。 -- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 3、8.1、9、12、21、23.6、23.8 节。 - -## 2026-08-14 M1C-0:用户修订状态进入静态委派 lineage 分类 - -- 落地:`StaticDelegateContractStatus` 新增 `UserRevisionRequested`(serde durable 值为 `user-revision-requested`)。`static_delegate_lineage_counters` 现在按三类传播:`NeedsUserInput` 只增加 `clarification_round`;`UserRevisionRequested` 原样继承 `repair_depth` 与 `clarification_round`;其它状态继续按质量返工增加 `repair_depth` 并重置 `clarification_round`。 -- 门禁:父 delivery 为 `UserRevisionRequested` 时,后续同链续跑不再被 `repair_depth=1` 的质量返工门误拒,因此连续用户修订可以继续通过;链上 `(u32::MAX, u32::MAX)` fail-closed 哨兵仍拒绝,不得借用户修订分支绕过 32-hop、成环或缺失上游保护。普通做游戏链路没有该状态,`repair_depth≤1` 结论未被推翻。 -- 范围:本包没有任何审批状态写入方、`gdd-approval` pending、receipt 或前端状态;`UserRevisionRequested` 仍由后续 `M1C-1` 的审批命令与 receipt 同步写入。本包不 bump `STATIC_DELEGATE_DELIVERY_SCHEMA_VERSION`,也不处理未知 durable status;禁止将未知值静默降级为 `NeedsRepair`,其显式 `Unknown` 前向兼容由后续 `M1C-0b` 收口。 -- 回归:新增连续用户修订、保留已有澄清轮次、普通 depth=1 拒绝、32-hop fail-closed 与 `user-revision-requested` serde round-trip;未知 durable variant 的显式 `Unknown` 前向兼容与损坏输入锁死由 `M1C-0b` 单独覆盖,无新状态的历史记录继续按原质量返工分类。 -- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 23.7、23.8 节;后续审批写入依赖 `M1B-2`、`M1C-1`。 -- 合入说明:本包在隔离 worktree 上以 `09c7d7af8`(`M1A-2` 收口)为基线开发,未包含 `M1A-4`、M1A 残余收口、`M1A-2` 回归修复与 `M1B-1`。合回时代码零冲突(本包改的是顶层 `src-tauri/src/delegation.rs`,`M1A-4` 改的是 `src-tauri/src/agent/runtime_tools/delegation.rs`,同名不同文件),仅两份文档的状态句冲突:合并时以原分支为准保留 `M1A-4` / `M1B-1` 的已落地事实,删去本包基线上「`.agent/planning` 存储仍未实现」「`M1B-1` 及之后仍未开始」两句已被 `M1B-1` 推翻的表述。 - ## 2026-08-14 修复 M1A-2 引入的回归:未知工具名不是身份违规 - 症状:`background_agent_runtime_persists_receipts_for_rejected_actions` 在 feature 分支恒失败(3/3),`master` 通过。首个 tool-plan 请求能收到,动作被拒绝后**第二次 Provider follow-up 请求不再发出**,测试等待超时。**不是已知的 mock-LLM 本机 flake**,是确定性回归。 @@ -1579,151 +1232,6 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - planning 侧约束未放松:planning + 未知工具、planning + 越权工具仍判身份拒绝,回归 `planning_identity_still_rejects_unknown_and_out_of_scope_tools` 钉死;`unknown_tool_on_ordinary_agent_is_not_an_identity_rejection` 钉死普通 Agent 侧。 - 教训:给已有主循环插「命中即中断」的门时,判据必须是**专门表达该门语义**的谓词。复用一个名字听起来正确、但对多数输入退化成别的含义的通用函数,会在没人测到的分支上改变现役语义。 -## 2026-08-14 M1A 复核残余收口:retry 强判据前置、身份哨兵改为编译期约束 - -对 `M1A-1`~`M1A-4` 做整组复核后,除已由 `M1A-4` 解决的一项外,另有两处代码残余与三处文档残余。本条记录代码两处的处置,文档三处随本次提交一并订正。 - -- **retry 强判据必须排在全部分支之前。** `resolve_game_creator_agent_runtime_retry_configuration_at` 原先把 plan 分支放在 `delegated` 与 `autonomous-game-build` 之后,两支都能绕开 `reject_supervisor_plan_root_retry_without_identity`:① 「plan source + 伪造 parent」落 `delegated` 支,直接返回 `agent-delegate-retry`,强判据根本不执行;② 「`binding.source` 是 plan + autonomous profile」落 autonomous 支,因 plan 在可信集合内而被原样取回,**复活启动路径 `reject_supervisor_plan_autonomous_profile` 明令禁止的组合**。两者都要 durable 状态先畸变才可达,但强判据存在的意义正是对畸变状态 fail closed。处置:把守卫提到函数开头无条件执行(合法 plan 根 run 对它恒真),plan 分支内不再重复读 durable 状态;autonomous 支内另加 `reject_supervisor_plan_autonomous_profile(&binding.source, &run_profile)`,兜住「`task.source` 已损坏但 `binding.source` 是 plan」这一种顶部守卫按 `task.source` 判定所挡不住的情形。回归 `plan_root_retry_identity_guard_precedes_delegated_and_autonomous_branches`,已用变异测试确认去掉任一守卫即变红。 -- **`"__all_agents__"` 身份哨兵改为编译期约束。** 四个不带 `agentId` 的 wrapper(`build_agent_runtime_native_function_tools`、`parse_agent_runtime_native_tool_calls`、`parse_game_creator_agent_tool_plan_llm_response` 及其 `_with_catalog` / `_with_catalog_classified` 两层)会以哨兵跳过按身份的工具面收窄与原始工具 identity 复核。`M1A-2` 之后它们的全部调用点都只剩测试,但「将来新增生产调用点忘记改用 `_for_agent`」是**静默拿到全量目录**而非编译失败。处置:四个 wrapper 与 `runtime_actions.rs` 的对应 re-export 一并加 `#[cfg(test)]`,漏改即编译期报错。注意该哨兵当时已扩散到两个文件(`agent_native_tools.rs`、`tool_plan_protocol.rs`),属于正在复制的模式而非单点遗留。 -- 复核中另外三条按「记录不改」处置,理由见各自条目:`agent-background-task` 空 source 兜底(见本文件 `M1A-3` 条订正段)、plan 根强判据尚缺 planning pending 一维(`M1B-1` / `M1C-1` 回补)、两条写成终态的验收句(属执行稿口径,不影响实现)。 - -## 2026-08-14 M1A-4:plan 根 run 的子 Agent 创建面收窄,并冻结三条已知残留 - -- **补的是 `M1A-2` 的反方向。** `M1A-2` 只做了「目标是 `project-planning` → 要求父是 plan 根」这一半;反过来「父是 plan 根 → 目标必须是 `project-planning`」当时没做,也没记为 deferred。后果是 plan 根 run 可以委派任意专业 Agent,而被委派者拿的是常规 `standard` 工具面(能写文件、跑命令),第 24 节「策划全程零构建」当时只由 Prompt 兜底、不是机制保证。 -- 落地:`observe_agent_runtime_agent_delegate` 补对称分支;`observe_agent_runtime_agent_spawn_isolated` 对 plan 根 run 一律拒绝(**它是第二条造子 Agent 的通道,只堵 delegate 等于留后门**)。两条通道共用 typed `kind=plan-root-child-target-unsupported`。Supervisor 根 run 的 prompt 在 plan source 下不拼 `supervisorIntro` 与 `$visualContract` 两段;不改 `.md` 内容、不新增 composition key(第 4.2 节)。 -- **强弱判据分工与 `M1A-3` 一致,不得合并**:弱判据(`task.source` 自称是 plan,经 task journal 读取而非 binding)决定「本约束是否管辖这条 run」;强判据 `validate_project_supervisor_plan_root_binding_at` 决定「它是否合法」。强判据 `Err` **永远落进拒绝分支**,绝不可写成 `else if validate(..).is_ok() { 拒 }`——那会把「binding 损坏」误归为「不是 plan 根」,恰好在 durable 状态损坏时放行任意子 Agent 创建。 -- 回归:`plan_root_delegate_rejects_non_planning_targets_but_keeps_planning_path`(含 `project-planning` 正向路径仍通过,避免把功能整个焊死)、`plan_root_delegate_and_spawn_isolated_fail_closed_when_binding_missing_or_corrupted`、`gui_root_delegate_and_spawn_isolated_are_unaffected_by_plan_root_symmetry`、`plan_root_supervisor_prompt_drops_intro_and_visual_contract_sections`、`non_plan_supervisor_prompt_stays_byte_identical_to_the_original_composition`。 - -**以下三条是经复核后有意保留的取舍,不是待办。后续 PR 不得在未重新裁决的情况下「顺手修掉」。** - -1. **`$isolatedAgentTemplates` 仍会向 plan 根 run 列出全部专业角色名。** `art-director` / `design-foundation` / `art-asset-plan` / `code-prototype` 等名字来自 `$base`(`RUNTIME_PROMPT_RUNTIME_COMPOSITION` 的 `$isolatedAgentTemplates` 段,由 `GAME_CREATOR_AGENT_GROUP_DEFINITIONS` 运行时渲染),用途是广告 `agent.spawn_isolated` 的合法模板 id,**不在本次裁掉的两段之内**。裁掉它要动 `$base` 与隔离模板目录,波及全部 Agent。保留的依据是:`A2` 已对 plan 根 run 硬拒 `spawn_isolated`,该目录对 plan 根 run 是**死文本**,不构成可利用面。**结论:上下文层的收窄边界到此为止;「plan 根 run 的上下文里不出现其它 Agent 名」这一目标 M1 不成立,不要据此写验收句。** -2. **上下文层回归是弱断言。** `plan_root_supervisor_prompt_drops_intro_and_visual_contract_sections` 用「不含某几条独有短句」断言,不是对照组那种字节级 `assert_eq`。已实测非永真。已知漏报场景:若将来 `$visualContract` / `supervisorIntro` 被替换成措辞不同但仍暗示专业组扇出的新文本,这条不会报警。**执行层的 `A1`/`A2` 是该场景的唯一保障**——这也是本包把硬门排在裁段之前的原因。 - -- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 4.3、22、23.8、24 节。 - -## 2026-08-14 M1B-1:planning storage 基础与只挡写隔离完成(待合入) - -- 范围:在 `M1A-2` 的 planning 子 Agent 工具边界之上,先落地 Runtime-owned `.agent/planning/**` 的 typed storage 基础,不注册、不广告、不执行 `plan.submit_gdd`(该工具仍属于 `M1B-2`)。当前实现位于隔离 worktree 的 `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs`,由 `runtime_protocol.rs` 注册。 -- strict 合同:实现 `plan-gdd.v1`、`plan-gdd-index.v1`、`plan-session.v1` 与 `plan.submit_gdd` input 的 `deny_unknown_fields` 结构校验,并复用统一的文本、ID、时间、枚举、数量/字节上限和 `basis=null` 约束。canonical storage bytes 固定为 Rust struct 声明顺序的 compact UTF-8 JSON;BOM、尾换行/空白、重复键、字段重排和语义等价但非 canonical 的 bytes 均拒绝。typed 指纹固定使用 `sha256-serde-json-v2:<64 位小写 hex>` 与 domain separation,GDD fingerprint 排除外层自身字段。 -- durable 边界:GDD/index 等不可变事实使用项目锁 + 同目录临时文件 + `sync_all` + 回读 + OS no-replace 发布;相同 canonical bytes 只返回 replay,其它同路径内容返回 identity conflict。session 使用原子 replace 与单份 `.session.json.previous`,按 `revision + 1` / `previousFingerprint` 链校验;primary 损坏不得静默被 previous 覆盖,只有 primary 缺失且 previous 唯一有效时才允许持锁提升。 -- 写入隔离:`.agent/planning/**` 保持 `file.read` / `file.list` 可读,但通用 `file.write`、`file.patch`、`file.delete`、`project.patchset` 和 checkpoint restore 只挡写;`game/fast_gdd.md` 作为 Runtime renderer 的人读投影同样禁止通用写入。专用 writer 另做 `project-planning + agent-delegate + standard + project-supervisor` identity 校验,失败关闭。 -- 当前验证:第 9.1 节 golden vector 已逐字节复核(3857 bytes,指纹 `sha256-serde-json-v2:a59856de7ef134cf2f49c4dedd2ba10ae4ab2340a9634d402eb792b6ee5458f0`),planning storage 定向测试 11/11 通过,writer 已按目标 schema 重解析并核对文件名/版本,index 与权威 GDD 逐项对账,新增 index recovery API 在锁内从严格 GDD 链重建缺失、损坏或陈旧 index,GDD 文件枚举/连续链读取、session request/decision/phase 约束及 recovery 分叉矩阵均有回归覆盖;`cargo check --offline`、`npm run check:encoding` 与 `git diff --check` 均通过。实现已提交于隔离分支 `f453c2ca2`,待合回原分支。 -- index 的 `statusCache` 在 M1B-1 仍是无 approval receipt 的预审批投影:多版本只把最新版本标为 `ready_for_approval`,旧版本标为 `superseded`。M1B-1 尚无 approval receipt schema;M1C-1 接入 receipt 后必须重建真实的 `approved` / `revise` / `reject` / `superseded` 状态。`clarification_round` 与完整 root/session identity 绑定留给后续 `M1B-2` / `M1C-2b` 接线。 -- 边界:本条不代表 GDD 提交点、approval pending/receipt、审批 UI、`game/fast_gdd.md` renderer 或构建 `approvedGddRef` 已可用;这些仍按 `M1B-2` 及后续 `M1C~M1E` 交付。上述边界不影响 M1B-1 存储层本身已完成。 -- 关联:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 8.3~10.2、23.6、23.8 节;`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs`;`apps/ai-game-creator-shell/src-tauri/src/project/filesystem.rs`。 - -## 2026-08-13 M1A-2:planning 子 Agent 两层工具面与角色 brief 注入 - -- 落地范围:在 `M1A-1` 的 `project-supervisor-plan` source 基础上,收口两层工具面。Supervisor 根 run 继续使用 `standard` 的现役工具面;`project-planning` 只接受 `source=agent-delegate`、`profile=standard`、父 Agent 为 `project-supervisor` 的静态委派身份。**(2026-08-14 订正:「Supervisor 根 run 继续使用现役工具面」这句读起来像决定,实际是要求丢失——本包工作项原本还要求「按 source 收窄 Supervisor 侧委派面:做方案链路只应产生一条指向 `project-planning` 的委派」,该半未实现也未记为 deferred。已由 `M1A-4` 补齐,见本文件同名条。)** -- planning 子 Agent 的当前 native action exact allowlist 只有 `file.read`、`file.list`;`update_agent_plan` / `respond_to_user` 是协议控制函数,不计入 action capability。MCP catalog 强制为空,`webSearchEnabled=false`,`collaborationPolicy=null`。`plan.submit_gdd` 刻意未注册、未广告、未执行,留给后续 `M1B-2`,因此本条不代表 GDD 提交、版本存储或审批闭环已完成。 -- Prompt:Prompt Bundle 新增并登记 `project-planning` role brief,standard planning child 的初始请求与 repair/rebuild 请求均注入同一 brief;Supervisor 和其它 Agent 不注入该 section。brief 只描述 Fast GDD 澄清、终态 `AGC_NEEDS_USER_INPUT_V1`、三轮边界、平台事实与低幻觉约束,不授予任何写入、命令、MCP、预览、生成或审批能力。 -- 纵深拒绝:广告层不再向 planning child 暴露 `user.input_request`;Provider parser、action batch/pending、并行只读、执行层和状态恢复均按原始 tool identity 再校验。伪造写入/命令/MCP、`project.search` 等映射为 `file.read` 的 alias、`user.input_request` 都 fail-closed;恢复旧快照不得把 planning 工具面扩回全量目录。委派子 Agent 原有 `validate_user_input_action_owner` 执行层拒绝继续保留。 -- 回归与边界:覆盖 planning 函数目录精确集合、brief 只注入 planning、MCP/web search/collaboration 收窄、原始工具身份拒绝及状态归一化不扩权;Supervisor 根 run 的 standard 工具面保持既有行为。M1A-3 的 source 保留、强判据与 retry 语义不改;`M1B-1` 的 planning 存储、strict schema、typed 指纹和写入隔离已完成,`plan.submit_gdd` 提交仍留给 `M1B-2`。 -- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 4.3、6、19.2、23.6、23.8 节。 - -## 2026-08-13 M1A-3:plan 根 run 强判据与 retry 保源 - -- 落地:新增 `supervisor_plan_root_identity_holds_at`。必须核 durable run-profile binding(含 project/fingerprint 校验),并与 task 的 `agentId/source/profile/parent/delegation/root*` 以及「存在且 runId 相同」的 runtime、尚存 provider action batch 逐项相等。**不得只比较内存 `runtime.source`。** `agent_runtime_supervisor_source_is_plan` 仍只用于拒绝(steer),本函数只用于授予 retry 保源。 -- retry:`resolve_game_creator_agent_runtime_retry_configuration_at` 在 generic `agent-background-task` 兜底**之前**插入弱候选分支——`task.source == project-supervisor-plan` 时先走强判据;通过则写出 `project-supervisor-plan`,失败返回 `kind=plan-root-retry-identity-unsupported`,**不得降级**。`delegated` / `autonomous-game-build` 两支未改。 -- 强弱分工:steer 继续用弱判据。不得把 steer 改成强判据——binding 缺失时会判不成 plan,反而 fail-open。 -- 本包不做:`.agent/planning/`、`gddId`、plan session revision+1、按 `gdd-approval` kind 禁 retry(现役已拒 `waiting-for-user-input`)。conversation `sessionId` 复用走现役 retry 入队。 -- ~~同族 source 重建复核(`rg` 生产路径,测试除外):字面量 `agent-background-task` 的**唯一**构造点仍是本函数兜底分支。`start_game_creator_agent_background_task_with_link_in_session_lane_at` 接受调用方 source、自身不改写;resume / pending_recovery / recovery_scan 续跑既有 `task.source`,不另造 source。~~ **← 本条于 2026-08-14 订正,结论有误,后续 PR 不得沿用。** 生产路径实际还有两处同形状的静默兜底,而且恰好就是原文点名「不改写 / 不另造」的那两个:`task_start.rs` 的 `start_game_creator_agent_background_task_with_link_in_session_lane_at` 内 `if source.trim().is_empty() { "agent-background-task" }`;`recovery_scan.rs` 内 `if task.source.trim().is_empty()` 同样兜底。两处在 source 非空时都保源,因此今天不可利用;但它们是 `M1A-3` 所堵漏洞的同一类另外两扇门,**且 `recovery_scan` 那条完全不经过 plan 根强判据**。经复核后**有意不改**:空 source 兜底是全 Agent 通用的历史默认,改成 fail closed 会波及现役全部后台任务启动与恢复路径,超出立项策划范围;若将来要收,须单列工作项并对照现役 resume/recovery 回归。 -- 对照:`project-supervisor-gui` + `standard` 仍降级为 `agent-background-task`;`delegated=true` 仍为 `agent-delegate-retry`;autonomous 仍从 binding 取回可信 source。 -- 回归:`plan_root_identity_requires_durable_binding_not_runtime_source`、`plan_root_retry_rejects_identity_mismatch_instead_of_degrading`、`plan_root_retry_keeps_plan_source_and_goal_contract_authority`、`gui_and_delegate_retry_sources_stay_on_existing_fallback`;既有 `autonomous_supervisor_retry_restores_trusted_source_from_run_profile_binding` 继续绿。 -- 关联文档:技术方案第 4.1 节第 5、7 段(本包只落地 source 保源与强判据,不提前实现第 7 段里依赖 M1B/M1C 的 session/gdd 合同)、第 22 节 `plan retry` 行、第 23.8 节 `M1A-3`。 - -## 2026-08-13 订正 `M1A-1` 的 retry 复核结论;plan 根 run retry 保源单列为 `M1A-3` - -- **被订正的是同日 `M1A-1` 条第三项第三类中的 `lifecycle_control.rs` `resolve_game_creator_agent_runtime_retry_configuration_at`。** 原结论「不适用且已被 profile 挡住,本包不改函数」**只对了一半**:该处确实不会让 plan 误得 autonomous 语义(`autonomous-game-build` 分支先判 profile),但 `M1A-1` 的复核模板只问了「plan 进 matcher 后会不会**误得**不该有的语义」,没有问「plan 落到通用兜底后会不会**丢掉**该有的语义」。retry 这个调用点既是判据也是 run 构造器,两个方向都要问。 -- **实际缺陷**:plan 根 run 是 `standard` + 顶层无 parent,两个特例分支都不命中,落入 `agent-background-task` 字面量兜底。`run_profile` 由 `agent_runtime_run_profile_identity_at` 原样返回、不受影响,**丢的只有 source**。 -- **为什么无声**:retry 走 `start_game_creator_agent_background_task_with_link_in_session_lane_at`,该启动路径对 source 无门禁;而 supervisor 正规启动路径 `start_game_creator_supervisor_background_task_for_session_at` 有 trusted 检查。`validate_agent_runtime_run_profile_binding_record` 也只在 `profile == autonomous-game-build` 时要求 trusted source,`standard` 档照写。于是 retry 造出一个**正规启动路径造不出来的状态**:`agentId=project-supervisor` + root binding + `standard` + `source=agent-background-task`,全程零告警。 -- **后果分两类**。放行类:`reject_supervisor_plan_root_steer` 只认精确 source 字符串,重试后不再命中,**steer 重新放开**,直接违反第 4.1 / 23.1 节裁决。死路类:`root_control_authority` 判 binding.source 是否 trusted,变 `false` 后广告层删掉 `agent.goal_contract` 与 `agent.acceptance_update`、`root_goal_contract_required` 变 `false`、`validate_goal_contract_record` 也拒绝建约——重试后的根 run **建不出 Goal Contract**,第 13.0 节审批前置门要的验收取证永远收敛不了,且**用户会走到审批那一步才撞墙**。`agent.delegate` 不受 `root_control_authority` 影响,委派仍可发出,所以故障不会在 retry 当场暴露。 -- **处置**:不回改已合入的 `M1A-1`,新列 `M1A-3`(见技术方案第 23.8 节)一并交付第 4.1 节要求的两件事——① 所有 plan 根 run 例外共用的**强判据函数**(须核 durable binding 并逐项相等,不得只比较内存中的 `runtime.source`;`M1A-1` 交付的 `agent_runtime_supervisor_source_is_plan` 是纯字符串比较,用于**拒绝** steer 是安全的,但不足以**授予** retry 的 source 保留);② generic standard fallback **之前**的 exact plan root 分支。`M1A-3` 必须早于 `M1C-2a` 合入。 -- **修法边界**:`project-supervisor-gui` / `-cli` 根 run 重试同样降级为 `agent-background-task`,这是现役行为,不在本次范围。只能在兜底分支**之前**插精确分支,不得改兜底默认值(第 4.1 节「不扩大现役 retry 的破坏面」)。 -- **前瞻风险**:M1 将实现「每个项目同一时刻最多一条非终态 plan lineage」。若该判据按 plan source 判定,重试产物会**隐身**——不占 lineage 名额却实际在跑,用户此时能并发开出第二条策划链路。`M1A-3` 修好 source 保留即消解。 -- 关联文档:技术方案第 4.1 节(第 5、7 段)、第 22 节证据表 `plan retry` 行、第 23.8 节 `M1A-3`。 - -## 2026-08-13 M1A-1:`project-supervisor-plan` 进可信 matcher,steer 独立否决 - -- 落地:新增 `AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE = "project-supervisor-plan"` 并加入 `agent_runtime_supervisor_source_is_trusted`。启动路径 `start_game_creator_supervisor_background_task_for_session_at` / Tauri command 接受该 source,`standard` 放行,`plan + autonomous-game-build` 返回 `kind=plan-autonomous-profile-unsupported`。旧字面 `project-supervisor-plan-chat` 仍不在 matcher 内。 -- steer:独立函数 `reject_supervisor_plan_root_steer` 只认精确 plan source,**不咨询 matcher**。Tauri command 在 trusted 检查之前调用;`steer_game_creator_agent_runtime_task_for_profile_at` 读到 task/runtime source 即拒;`goal_contract_root_steer_task_at` 同样先拒再走 trusted。typed 错误 `kind=plan-root-steer-unsupported`。回归覆盖「plan 在 matcher 内仍拒」与「否决不依赖 matcher、不误伤 gui/forged/已作废 plan-chat」。 -- 消费点复核(`rg agent_runtime_supervisor_source_is_trusted`,测试除外): - - **适用(plan 进 matcher 后语义正确)**:`commands.rs` 启动门、`task_start.rs` 启动门、`goal_contract.rs` 的 `validate_goal_contract_record` / `create_game_creator_agent_runtime_goal_contract_at`、`acceptance_graph.rs` 的 `update_game_creator_agent_runtime_acceptance_graph_at` / `goal_contract_acceptance_completion_blocker_at_locked`、`provider_request_builders.rs` 的 `root_control_authority`、`run_configuration.rs` 根 binding 写入(autonomous 组合另由启动门拒绝)。 - - **不适用 → 独立否决**:`commands.rs` `steer_game_creator_agent_runtime_task`、`steering.rs` `goal_contract_root_steer_task_at`(及 `steer_..._for_profile_at` 入口)。不得用「不进 matcher」实现。 - - **不适用且已被 profile 挡住,本包不改函数**:`task_start.rs` `current_autonomous_game_build_root_task_at`(先要求 `run_profile == autonomous-game-build`);~~`lifecycle_control.rs` `resolve_game_creator_agent_runtime_retry_configuration_at`(autonomous 分支才读 trusted source,standard 走 `agent-background-task`)~~ **← 本条已于同日订正,见本文件上方「订正 `M1A-1` 的 retry 复核结论」条:结论只对了「不会误得 autonomous 语义」这一半,漏了「会丢掉 plan 语义」这一半;该函数改由 `M1A-3` 处理,后续 PR 不得沿用此处的「本包不改」结论**;`project_gates.rs` `ensure_current_autonomous_ready_child_mutation_at_locked`(`profile != autonomous` 即 `Ok(())`);`autonomous_completion.rs` 的 `autonomous_game_build_root_run_active_at` / `validate_autonomous_completion_contract` / `ensure_autonomous_completion_contract_for_task_at` 均先看 autonomous profile;`failed_terminal_autonomous_root_contract_before_task_at` 由后者以及 `autonomous_effective_root_task_at` 调用,后者先要求已存在完成合同(完成合同只由 autonomous 根写入)。 -- 本包不做:工具面、brief、`plan.submit_gdd`、planning sidecar、审批、前端入口。 -- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 23.8 节 `M1A-1`。 - -## 2026-08-13 checkpoint handoff 私有持久化随 D10 作废;M1 入口前置决策归零 - -- 处置:原待裁决项「checkpoint handoff 私有持久化」**无需裁决,随 D10 一并作废**。它待的是 `plan-decision-checkpoint` 这份 Provider 响应的专用 handoff schema / path / requestSlot / ledger 排序语义;该请求 kind 是 D10「Runtime 直投」的组成部分(策划节点持续存活于同一 run,用户回答后在同一 run 内再发一次专用请求形成设计解释)。D11 下策划子 Agent 以终态信封退出来提问、该 run 随即结束,解释与下一步由 continuation 子 Agent 的第一个普通 tool-plan turn 完成,专用请求 kind 不存在,其专用 handoff 也就不需要。 -- 核实依据:① 现役 `tool_plan_handoff::lookup_at(root, agent_id, run_id, &response_identity)`(`agent/runtime_actions/provider_tool_plan.rs:367`)按 `(agentId, runId)` 寻址、与 agent 身份无关,任何 Agent 的普通 tool-plan 响应都已被覆盖,continuation 子 Agent 首轮不需要新机制;② 技术方案第 8.6 节为该方案预留的 `supersededCheckpointHandoffs` 全仓库零代码引用,纯设计构想,作废无迁移成本。 -- 不受影响:通用 handoff 安全边界(任何 Provider 成功响应必须先过 storage 的大小/控制字符/敏感键/绝对路径/容量/durable identity 门并落盘才可消费;门拒绝或无法 durable 提交时禁止保存不安全正文、补 lifecycle completed 或自动重发)是现役机制,策划链路照用。 -- 结果:**M1 入口前置决策归零**。剩余全部是待执行项:该可信 matcher 约 19 处消费点逐点复核(steer 门须实现为独立显式否决)、为 standard 下 plan 根 run 补「不得自行提问」的机制兜底。 - -## 2026-08-13 plan source 进可信 matcher;做方案验收图取自固定 Fast GDD 合格标准 - -- 裁决一:`project-supervisor-plan` **进** `agent_runtime_supervisor_source_is_trusted`(`agent/runtime_driver.rs:113`),做方案链路正常参与 Goal Contract 协议,不做豁免。2026-08-12 条记录的硬门「裁决冻结前,依赖 plan source 可信身份的 M1 代码不得合入」随本条解除。 -- 原阻塞理由已失效:2026-08-12 排除该方向的依据是「策划 Agent 按设计一项证据工具都不该有,必然留下永不 passed 的节点」。该推理写于 D6/D9 拓扑(当时 plan run 就是策划 Agent 本身)。D11 拆成两层后两个前提都不成立:① 策划子 Agent 的 exact allowlist 恰好含 `file.read`/`file.list`,均在 `agent_runtime_acceptance_evidence_tools()` 白名单内;② 更根本的是证据不必由它出——`validate_acceptance_evidence_identity_at`(`agent/runtime_protocol/acceptance_graph.rs:152`)对证据来源只要求 `binding.root_agent_id/root_run_id` 等于合同的根,**不要求是根 Agent 自己的回执**,而委派子 Agent 的 binding 根就是 Supervisor。 -- 入口门不是阻塞,只是时序:合同不存在时本轮必须且只能是一个 `agent.goal_contract`,故 turn 1 冻结合同、turn 2 才发委派,代价是多一次 Provider 调用。 -- 落地前必须补的功课:该 matcher 约 19 处生产消费点,同时承担 run 启动门、steer 门、Goal Contract 创建权限、根控制面工具授权与验收图完成门等多种语义。本裁决只确定「plan 进入 matcher」,不等于每个消费点对 plan 语义都正确,M1 落地前须逐点复核并对不适用者单独收窄。steer 门已由同日裁决单独否决,且要求实现为独立于本 matcher 的显式否决。 -- 裁决二:做方案链路的验收图**取自固定的 Fast GDD 合格标准,不由 Supervisor 每轮自由发挥**。理由:GDD 是否合格与它描述的是什么游戏无关——变的是游戏概念,不变的是字段与字段约束。因此「验收标准必须在产物不存在的 turn 1 冻结且不可改」不构成矛盾。分工:`plan.submit_gdd` 的 strict schema 承担机器可判的字段存在性与约束(硬校验);Goal Contract 验收图承担「产物确实服务了用户这次的意图」,由 Supervisor 以 `tool:file.read` 对 `game/fast_gdd.md` 取证后确认。Goal Contract 中按项目变化的只有 `outcome`/`nonNegotiables`/`forbiddenAssumptions`/`openQuestions` 四项,`acceptanceNodes` 近乎固定——这不算入口门 Prompt 所禁的「照抄固定信号代替理解」,因为「什么算一份合格 GDD」本就不是本轮要理解的东西。 -- 本条不改变 D1(游戏支柱按项目生成 2~4 条)与 D8(GDD 不含引擎字段)已冻结的口径。 -- 同批更正一处失真表述:技术方案第 4.3 节此前称「Supervisor 不得自行发起策划性提问」这一条「靠机制保证」。经核实不成立——做该校验的 `static_delegate_clarification_pending_matches_delivery_at` 全仓库唯一生产调用点(`agent/runtime_driver/pending_recovery.rs:791`)外层套着 `run_profile == autonomous-game-build`,而做方案链路跑 `standard`,校验不触发。该链路上 Supervisor 既可自行提问也可改写子 Agent 问题原文,Runtime 都不拦。这是产品约束不是机制约束;要变成机制约束须为 standard 下的 plan 根 run 单独接一道等价校验,属 M1 范围。 -- M1 入口前置决策至此剩一项:checkpoint handoff 私有持久化。 - -## 2026-08-13 立项策划 plan 根 run 不允许 steer - -- 裁决:plan 根 run(`source=project-supervisor-plan`)**不接受 steer 替换协议**。原「plan run 是否允许 steer」待裁决项就此关闭。 -- 理由不是「交互未验证」,而是确定性的能力损失:D11 把策划的问询轮次预算挂在委派链上——`clarification_round` 沿 `repair_of_delegation_id` 上溯推断,且每一跳强制 `parent_run_id` 等于当前根 run。steer 会终止旧根、另起 replacement root run,换根后 `parent_run_id` 改变,旧链的 continuation 被跨 run 隔离判据直接拒绝,**该策划链路剩余问询轮次全部作废,用户已回答的内容也无法续接**。 -- 实现约束(关键,不得靠副作用实现):`goal_contract_root_steer_replacement_run_id`(`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/steering.rs:763`)现在的资格判据是 `agent_runtime_supervisor_source_is_trusted(&task.source)`,而该 matcher **同时**是 Goal Contract 创建权限与根控制面工具授权的判据。因此**不得**用「不把 `project-supervisor-plan` 加进该 matcher」来实现本裁决——那会连带否掉 Goal Contract,正好撞上仍未裁决的「plan source 与 Goal Contract 协议的关系」。本裁决必须是一条**独立于可信 source 判定的显式否决**:steer 入口识别出 plan 根 run 即拒绝并返回 typed 错误,无论该 source 是否在可信 matcher 内。回归须覆盖「在 matcher 内」与「不在 matcher 内」两种情形下 steer 均被拒。 -- 产品侧替代路径:本轮问询内回答/自由填写纠偏;GDD 审批卡 `revise` / `reject`;再不行放弃本轮、重开一条 plan lineage(同一时刻只允许一条非终态 lineage,第二条返回 `PLAN_ACTIVE_RUN_EXISTS`)。 -- M1 入口前置决策至此剩两项:checkpoint handoff 私有持久化;plan source 与 Goal Contract 协议的关系(后者带硬门——裁决冻结前,依赖 plan source 可信身份的 M1 代码不得合入;2026-08-13 已合入的 `project-planning` 身份登记不触碰该门,它登记的是子 Agent 的 `agentId`,未引入 `project-supervisor-plan` 这个 source)。 - -## 2026-08-13 立项策划执行计划入档:五步顺序、`WP1`/`WP2` 完成状态与后置清单写进技术方案 - -- 背景:D6→D9→D11 三轮拓扑改写与 `WP1` 拆分都各自记了决策,但「现在做到哪一步、下一步是什么」一直只存在于会话里,没有任何仓库内载体。直接后果是技术方案出现过时陈述——文首状态行与第 1.1 节第 5 条在 `WP1`/`WP2` 已合入本分支后,仍写着「WP1 落地前问询上限仍是 1 轮」「正在另一个 worktree 并行实现」,读文档的人会以为该工作尚未开始。本条把执行计划本身作为需要维护的对象入档。 -- 决策:技术方案新增第 23.5 节(`WP1`/`WP2` 的问题、定稿语义、门禁与完成状态)与第 23.6 节(五步执行计划表 + M1 开工前必须处置的两类事项 + 明确后置清单),并修正上述两处过时陈述。此后每完成一步,须同步更新第 23.6 节的状态列;**设计结论变更与进度状态变更是两件必须分别维护的事,不得只更新前者。** -- `WP1` 的独立立包依据:它改的是 master 已发布的 PR #165 静态委派澄清中转机制,服务对象不止策划链路,因此不并入 M0/M1 门禁,单列第 23.5 节。语义权威定义仍在本文件同日「静态委派返工深度与澄清轮次拆分」相关条目,第 23.5 节只承载问题陈述、门禁与完成状态,不重复展开语义。 -- 明确后置、不阻塞任何一步的四项已记入第 23.6 节:run status 观测暴露派生计数;画布替换授权是否收紧为「只有真实质量返工可替换正式图片」(PR #165 之后就存在的既有行为,与本方案诉求无关);跨 run 全局预算缺口(7 跳是单链界、不是项目生命周期累计界,只要不断开新 run 就能不断获得新配额,本阶段只记录不实现);「做游戏」路径改造(删 `design-director`、收窄 `design-foundation`、调整 16 任务 DAG)。 -- 未纳入:此前一度使用过的 `WP0`/`WP3`/`WP4`/`WP5` 编号从未落进仓库,为避免出现两套不一致的工作包编号,本次只保留 `WP1`/`WP2` 两个真实存在的包名,其余内容按性质分别归入第 23.6 节的「M1 开工前必须处置」与「明确后置」两类,不再引入新编号。 - -## 2026-08-13 `project-planning` 的 agentCatalog 登记机制定稿:与 `supervisor` 平级、不进 `groups`,「做游戏链路一行不动」得以成立 - -- 背景:D11(见下方同日「立项策划 D9 二次作废…」条)继承了 D9「`project-planning` 需登记 agentCatalog」的结论,但登记方式一直是待裁决项——`build.rs` 的 `validate_seed_task_catalog`(`apps/ai-game-creator-shell/src-tauri/build.rs:23-46`)要求编译出的 `(taskId, groupId, role)` 集合与 `shared_contracts::game_creation_app::new_game_creation_app_seed_tasks()` 完全相等,不等即 `panic!`,直接把 `project-planning` 塞进任何一个专业组会破坏这条一致性校验、污染 16 任务种子 DAG。本条只做只读取证与机制定稿,**不落地任何代码**,工作目录 `C:/wtp`(分支 `feat/plan-agent-catalog-registration`),只改文档。 -- 事实基线(详见技术方案 `docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 3.1 节): - - 被 `build.rs` 比对的 `specialist_nodes` 集合只来自 `manifest.agent_catalog.groups[].roles[]`(`build_support/runtime_prompt_bundle.rs:387-398`),不遍历 `agentCatalog` 的其它顶层键。 - - `project-supervisor` 本身就是「catalog 成员但不是种子 DAG 任务」的既有先例:`runtime_adapter.rs` 的 `build_game_creator_runtime_agent_catalog`(`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_adapter.rs:4-38`)先单独 push 一个 supervisor 的 `AgentDescriptor`,再遍历各组角色。 - - `AgentDescriptor::metadata()` 目前零生产调用,`game_creator_runtime_agent_catalog()` 唯一生产调用点(`runtime_state.rs:1491`,即 `normalize_game_creator_runtime_agent_id`)只做 `.get(agent_id).is_some()`;`AgentCatalog::iter()` 也零生产调用——当前没有任何代码枚举 runtime catalog,`project-planning` 登记后不会泄漏进「做游戏」团队清单或路由清单。 - - 但仅做 catalog 登记不足以让 D11 可执行:`prompt.rs` 的 `game_creator_agent_role_definition`(`apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs:661-679`)硬编码「非 supervisor 即 group 角色」二分,`project-planning` 落入 else 分支返回 `None`,两个调用方都会把 `None` 转 `Err` 中断——`provider_request_builders.rs:536-539` 报「未知 Agent 模板:project-planning」,`prompt.rs:416-417` 报「未知 Agent:project-planning」(两处错误文案不同,均已逐行核对代码原文,非同一字符串)——这是本次调研发现的 **blocking** 缺口,不是 catalog 登记本身能解决的。 -- 决策:`project-planning` 在 `prompts/runtime/manifest.json` 的 `agentCatalog` 下登记为与 `supervisor` 平级、**不进 `groups` 数组**的独立条目(`id`/`taskId=project-planning`);descriptor metadata 取值——`groupId="project-planning"`(自引用伪 group id,绝不复用 `"design"`,避免与 design 组中文 label「策划组」语义碰撞)、不设 `groupLabel`(照抄 supervisor 先例)、`roleLabel` 跟随角色定义、`toolId="agent.runtime.project-planning"`(跟随 supervisor 的组外单节点命名族)、`capabilityAuthority="game-creator-tool-policy-snapshot"`(与全部现有条目相同,无需新值)。由此 `specialist_nodes`、16 任务种子 DAG、`new_game_creation_app_seed_tasks()`、`build.rs` 三者均不需要改动,「做游戏链路一行不动」得以成立。composition 复用现役 `runtime` composition,不需要单独配置;`briefPathName` 只是运行时文件名 token,缺文件不报错,但格式与全局唯一性仍受编译期 `validate_file_name`/`validate_agent_catalog` 约束。 -- 编译链路要改的位置(M1 落地范围,本轮不动):`build_support/runtime_prompt_bundle.rs` 的 `struct AgentCatalog` 加 `planning` 字段、`validate_agent_catalog` 镜像 supervisor 的单 role 约束/去重/防冲突集合/显式校验调用、`compile_manifest` 的 `catalog_task_ids` 链入 `planning.roles`、`render_agent_catalog` 镜像 supervisor 专属四个产物;`runtime_adapter.rs:4-38` 镜像 push 一段 `AgentDescriptor`;连带隐藏耦合 `pass_artifacts.rs` 的 `agent_role_memory_relative_path_for_task`(335-347 行)需加第三条 `project-planning` 分支,否则运行时报「未知 Agent 任务」。 -- **本轮范围声明(与 M1 的边界)**:本条只冻结登记机制、更新技术方案文档第 3.1 节与第 23.1 节,**不改 `manifest.json`、不改 `runtime_prompt_bundle.rs`、不改 `runtime_adapter.rs`、不改任何 `.rs` 文件**。理由:`project-planning` 目前没有 prompt、没有任何路径能调用它,现在注册 catalog 而不同步处理上面的 blocking 缺口,等于给发布产物加一个「看似已登记、一调用就硬失败」的死重身份;注册代码应与 M1 的 prompt/source 一起落地。 -- 保留待处置(M1 范围,非本条待裁决):`prompt.rs` 的 `game_creator_agent_role_definition` 角色身份合成缺口(blocking);`task_ops.rs` 的 group/role 误分类兜底(needs_change);`delegation.rs` 的 `agent.spawn_isolated` 放行面扩权(needs_change);`task_start.rs` 的 `collect_game_creator_agent_runtime_agent_ids` 恢复/steer/对账枚举缺口(needs_change,2026-08-13 复核补记);`pass_artifacts.rs` 的内存路径解析缺口(M1 必须同步处理);`runtime_adapter.rs` 的 `game_creator_runtime_agent_catalog_matches_the_existing_role_directory` 测试(91-121 行)断言 catalog 精确等于 `{supervisor} ∪ 16 组角色`,`project-planning` 登记后需同步更新其期望集合,否则 M1 落地当天编译测试即失败。 -- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 3.1 节(新增小节)、第 23.1 节(原「`project-planning` 的编译期 agentCatalog 登记方式与 `build.rs` 一致性校验」待裁决项已降级为「机制已定稿,代码落地属 M1」);关联决策:本文件下方同日「立项策划 D9 二次作废、D10 作废」条(D11、第 1.1 节「D11 新拓扑」第 6 条)。 - -## 2026-08-13 立项策划 D9 二次作废、D10 作废:改为 Supervisor 静态委派子 Agent(D11);静态委派澄清轮次与返工深度拆分定稿(WP1) - -- 背景:产品侧确认「做方案」入口不需要新增编排调度器概念,复用已有静态委派(`agent.delegate`)与 PR #165 已实现的子 Agent 澄清中转链路即可覆盖 D9/D10 试图解决的问题;同时实证发现静态委派返工深度门(`repair_of_delegation_id.is_some()` 即拒绝)对「质量返工」与「澄清 continuation」无差别拒绝,一次澄清会吃掉整条链唯一一次质量返工额度。两条问题合并处置。 -- 事实基线(均已逐处打开源码或测试核对,详见技术方案 `docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 1.1 节「D11 新拓扑」): - - `delegation.rs:1119-1121` 的返工深度门对「质量返工」与「澄清 continuation」无差别拒绝;`apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/static_deliveries.rs` 的 `clarification_continuation_chain_supports_multiple_rounds` 走真实 `observe_agent_runtime_agent_delegate` 生产路径证明:D1→D2 成功、D2→D3 被拒、D3 从未落盘。 - - 委派子 Agent 天然带 `parent_agent_id`/`parent_run_id`,`user.input_request` 被执行层 `validate_user_input_action_owner`(`user_input.rs:367-394`)在落盘 pending 之前直接拒绝;但广告层不拦——`build_agent_runtime_native_function_tools`(`agent_native_tools.rs:279`)无 `agent_id` 参数,`standard` profile 下 `tool_policy_snapshot.rs:144-147` 仍把它列为 auto tool,模型看得见、会去调,只是必失败并转成一次失败的 tool observation。 - - 委派子 Agent 的 `target_session_id` 每轮解析为同一条持久 active session(`resolve_agent_conversation_session_id_at` 传 `None` 时走 `catalog.active_session_id`,`project/conversation.rs:849-852`),prompt history 按 `(agent_id, session_id)` 组装(`context_compaction.rs:453-490`,`run_id` 只用于 observation 覆盖计数),故 continuation 子 Agent 是「新 run、同 session」,不失忆;但用户答案物理落在 Supervisor 会话,子 Agent 只能靠 Supervisor 转述,Runtime 只校验 `questionsSha256`/`answersSha256` 哈希绑定,不校验转述内容与已确认答案的语义一致性。 - - `normalize_static_delegate_expected_artifact`(`delegation.rs:1981-1998`)拒绝首段为 `.agent` 的路径,`.agent/planning/**` 在委派发起阶段天然进不来,不需要文档层自律约束;`game/fast_gdd.md` 不受影响,定稿 `expectedArtifacts=["game/fast_gdd.md"]` + `acceptanceCriteria` 承载质性标准,`verification_required=false`。 - - `project-planning` 目前不在编译期 agentCatalog 里;`build.rs` 的 `validate_seed_task_catalog`(`build.rs:23-46`)要求编译出的 `(taskId, groupId, role)` 集合与 `new_game_creation_app_seed_tasks()` 完全相等,不等即 `panic!`。 -- 决策(D11,取代 D9,连带作废 D10):立项策划节点改为 Project Supervisor 通过 `agent.delegate` 发起的**静态委派子 Agent**(`agentId=project-planning`),不再由 manifest ready-task 调度器启动;问询复用 PR #165 已实现的中转链路:子 Agent 以 `AGC_NEEDS_USER_INPUT_V1` 终态信封退出 → Supervisor 认领 → Supervisor 在自己的 runtime/session 上建 `waiting-for-user-input` pending → 用户在 Supervisor 会话内作答 → 答案经 `questionsSha256`/`answersSha256` 绑回 delivery → Supervisor 发起 continuation 子 Agent 续跑。命名裁决同时冻结:`agentId=project-planning`,Supervisor 侧新可信 source 为 `project-supervisor-plan`(不沿用旧预留字符串 `project-supervisor-plan-chat`)。 -- D9「立项策划是独立 `agentId` 的下游工作流节点」这一结论方向被 D11 继承,但调度机制被推翻——不再由 ready-task 调度器启动。D10「问询改走 Runtime 转发、两路投影」这一方向也被继承,但落地机制不同:D10 设想的是为 D9 拓扑新造的「直投」,D11 复用的是已经上线的 PR #165 链路,两者不是同一套代码。D10「exact allowlist 主动排除 `user.input_request`」这条论证在 D11 下失效:委派子 Agent 的 `user.input_request` 由执行层 `validate_user_input_action_owner` 兜底拒绝,是 Runtime 机制而非产品自律(但广告层仍放行,须与执行层拒绝一并回归钉死,不能只测一半)。 -- **推翻 2026-08-12「子 Agent 澄清回执由 Supervisor 中转(Issue #163)」决策记录中「随后最多创建一次绑定原 `delegationId` 的 continuation child」这一条**(该决策落地为 PR #165,对应本文件本条目下方原文见该日期条目)。实证(`clarification_continuation_chain_supports_multiple_rounds`)证明该限制来自返工深度门的误伤,不是有意设计;该条自本决策起作废,continuation 允许的次数改按下方 WP1 定稿的 `clarification_round` 语义执行,不再是「最多一次」。 -- 决策(WP1,静态委派澄清轮次与返工深度拆分): - - 两个维度独立,且都是**运行时派生值,不落盘**:`repair_depth` 上限维持 1(不放松);`clarification_round` 上限按 source 区分。 - - **分类判据(唯一权威)**:`parent.structured_result.contract_status == NeedsUserInput` ⇔ 这一跳是澄清 continuation;否则是质量返工。 - - **传播规则**:根节点(`repair_of` 为 `None`)`depth=0, round=0`;澄清跳 `round=parent.round+1, depth=parent.depth`(**不重置返工深度**,否则可插一次澄清洗掉返工深度、变成无限返工);返工跳 `depth=parent.depth+1, round=0`(**重置澄清轮次**,因为返工后策划节点重新开工,不能因返工吃掉预设的 3 轮问询预算)。 - - **总跳数上界推导**:`repair_depth` 上限 1 意味着链上有 `repair_depth_max + 1 = 2` 个「深度段」,每段各自最多 `clarification_round_max = 3` 次澄清跳,另加 `repair_depth_max = 1` 次返工跳本身。故总跳数上界 = `repair_depth_max + (repair_depth_max + 1) × clarification_round_max = 1 + 2 × 3 = 7` 跳,加根节点共 **8 条 delivery 记录**。**注意是 7 不是 6**——容易漏算的是「连接两层的返工跳本身也算一跳」,只算两段澄清跳(`2 × 3 = 6`)会漏掉这一跳。 - - **不得新增持久字段**:给 `StaticDelegateDeliveryRecord` 加 `repair_depth` 字段并用 `#[serde(default)]` 兜底,会让磁盘上已有的返工记录读出 `0`,深度门失效,`tests/collaboration/static_deliveries.rs:977-988` 钉的「返工的返工」漏洞原样复活。方向是 **fail-open,不可接受**。必须改用**链上推断**:每次校验时沿 `repair_of_delegation_id` 向上重放整条链现算。链上推断对历史记录是精确而非仅保守的:PR #165 之前不存在 `NeedsUserInput`,老记录天然被正确分类为「非澄清」,不会误判。 - - **最脆弱的回归点**:不是「澄清跳误把 `depth` 清零」(已被设计明确防着),而是**「返工跳漏掉 `depth+1`」**——现有测试要么纯返工链、要么纯澄清链,从未覆盖交替链,若实现写成「有 parent 就继承 `depth`」这种自然默认,`depth` 会永远停在 0,无限乒乓原样重开,而两条现有测试全绿。回归矩阵必须新增一条交替链用例(澄清→返工→澄清→返工…)专门钉死这一路径。 -- M0/M1 影响:D11 依赖 WP1 先落地才能生效——WP1 正在另一个 worktree 并行实现,是 D11 的**强制前置**,不是并行工作包;WP1 落地前,D11 描述的「最多 3 轮问询」实际上限仍是 1 轮,且用掉后连一次质量返工都做不了。 -- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`;关联决策:本文件 2026-08-12「立项策划 D6 作废」条、2026-08-12「子 Agent 澄清回执由 Supervisor 中转(Issue #163)」条(其「最多创建一次 continuation」结论已被本条推翻)。 - ## 2026-08-12 Repository checks 采用 CI 与本地共用的单一门禁入口 - 背景:master run 1037 的 Backend/Frontend 已通过,但 `Repository checks` 因 3 个 `simple-import-sort/imports` 错误失败。原 pre-commit 只运行 Prettier,Prettier 不处理 ESLint import 排序;推送前又未运行完整仓库 lint,因此本地与 CI 的覆盖范围长期存在漂移。 @@ -1738,41 +1246,6 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 未完成恢复项:isolated join 唤醒、isolated child result 发布、manifest terminal projection 和 terminal-unknown reconciliation 在持久化自身失败时仍需要独立 durable marker 与重启扫描协议;这些跨崩溃窗口必须单独设计和验证,不能用 best-effort 事件或日志冒充已恢复。 - 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。 -## 2026-08-12 立项策划 D6 作废:策划改为 Supervisor 下游工作流节点,问询走 Runtime 直投 - -- 背景:产品侧确定「做方案」入口独立成链——不动做游戏路径,最终产物只有策划方案,且保持 Supervisor 顶层、工作流节点与子 Agent 在下游的结构。据此复核代码后,D6「策划是 Project Supervisor 通道的第三 persona、复用同一 `agentId`、不注册新 agentCatalog 身份」的前提逐条不成立。 -- 事实基线(均已逐处打开源码核对):`user.input_request` 在 `autonomous-game-build` 下被广告层(`tool_policy_snapshot.rs:198-215`)与执行层(`main_loop.rs:2604-2616`)两道拦截,**判据只看 run profile、不看 agent 身份,父 Supervisor 自己也被禁**;硬闯的后果是 `pending_execution.rs:1157` 把 `runtime.status` 写成 `failed`,而 `task_start.rs:685-693` 的根活跃判定不认 `failed`,**整条 16 节点工作流永久瘫痪、须人工核对**;`run_configuration.rs:264-266` 明文「子 Run 不能切换父 Run 的 Run Profile」,故不能只给策划节点换 profile;`user_input.rs:367-394` 拒绝任何带 `parent_agent_id / parent_run_id / delegation_id` 的 run 直接提问。 -- 决策(D9,取代 D6):立项策划是独立 `agentId` 的下游工作流节点,由 manifest ready-task 调度器在 Supervisor 下游启动,需登记 agentCatalog;Project Supervisor 以新可信 source 承载「做方案」入口并保持唯一顶层 root;父子 run profile 必须同为 `standard`。「复用 standard」这一结论方向从 D6 继承,但成立理由完全不同。 -- 决策(D10,问询机制):策划节点不得直接调用 `user.input_request`,改走 **Runtime 直投**——策划节点提交结构化决策字段,Runtime 创建 **owner 为 Project Supervisor** 的 pending,用户在 Supervisor 对话里回答,Supervisor 的 Provider 全程不参与提问。这不是新造机制:`AgentRuntimeUserInputRecord` 已经是唯一事实源并两路投影(`user_input.rs:494-530` 投影成会话消息写进 pending owner 的会话文件,`:595-623` 投影成结构化 observation,`:803-807` 有「observation 重算冲突」校验防漂移),直投只是让两路分别落到 Supervisor 与策划节点。 -- 依据的产品约束:一、用户侧只有一个对话对象;二、所有呈现给用户的对话内容必须**物理存在于** Supervisor 的会话文件中,不得由前端把多个 Agent 的会话拼成单一视图。第二条不是靠约定满足,而是靠「会话文件按 `agentId` 分目录(`conversation.rs:42`)、消息归属完全由 pending owner 决定」这个物理事实。 -- 决策卡冻结范围收窄:冻结三个固定选项及顺序(要确定性映射到 decision state 与 answer source)与 question ID 规则(`decisionId` 由它推导);**问题正文措辞不冻结**,作者是策划节点。保真由「Runtime 不经过任何 Provider 搬运」保证,与模板无关。 -- Goal Contract 处置:原四方案 A/B/C/D 随之作废(共同前提是策划 run 自己就是那个 root)。但入口门 `validate_root_goal_contract_control_plan_at` 无 profile 判断,standard root Supervisor 仍受约束,故替换为三条实测约束:Supervisor 第一轮必须且只能提交 `agent.goal_contract`;验收 `requiredEvidence` 锚定 `game/fast_gdd.md` 配 `tool:file.read`,**不得指向 `.agent/planning/**`**(`reject_agent_runtime_private_control_path`不区分读写,加进去会把`file.read` 一并挡死、出口门永久 blocked,planning 的写保护须用只挡写的独立判据);Supervisor 取证必须在 GDD 落盘之后。 -- M0 影响判定:三个代码工作包(`M0A-2` / `M0B-1` / `M0B-2`)**全部不受影响、零回退**——无一行按 D6 编写,全仓库检索 `fast_gdd` / `.agent/planning` / `project-supervisor-plan-chat` / `is_exact_supervisor_plan_run_at` 均零命中。只有 `M0A-1` 交付的文档基线失效,以工作包 `M0A-3` 修订,修订完成前不得声称「M0 全部完成」。**注意 `M0A-2` 虽不受影响,其实现也不可复用**:`autonomous_owner_artifact_validation_available_for_run_at`(`autonomous_completion.rs:416-441`)四重绑死 owner 白名单、profile、source 且要求 `parent_agent_id` 为 Project Supervisor,standard 路径必须另建物理独立实现。 -- 保留待裁决:策划节点 `agentId` / source 命名(是 `M0A-3` 批二的共同阻塞点,身份常量不定则第 3、8、9、12、13 节无法落笔,第 9.1 节 golden vector 的 SHA-256 必然重算);Supervisor 侧新可信 source 是沿用旧字符串还是取新名;checkpoint handoff 私有持久化(原有项,不受影响);plan run 是否允许 steer,以及 Supervisor 被 steer 时下游策划节点如何收束。 - -## 2026-08-12 M0 全部完成,并据动态目标验收图收敛 M2 / M3 设计(M1 裁决保留) - -- 背景:M0-1~M0-4 及 `M0A-1` / `M0A-2` / `M0B-1` / `M0B-2` 四个工作包全部通过门禁并合入 M0 集成分支 `feat/five_min_design`(已推送)。M0 按整体切片交付,不逐工作包直接进 master,因此「未进 master」不作为 M0 未完成的依据。 -- 裁决:**M0 全部完成**。M0 期间该分支两次合入上游 master(`03a441027` 无限画布、`1363b9374` 动态目标验收图、`51e35468a` 自主构建测试锁竞态修复),`origin/master` 已是分支祖先。M0 完成不表示任何策划功能上线;M1 功能实现尚未开始。 -- 触发复审的上游事实:`1363b9374` 引入的 Goal Contract / Acceptance Graph 对**所有可信 root Supervisor**生效且不看 Run Profile,与本方案 M1~M3 的多条前提冲突,故一并收敛下列三条。 -- M2 裁决一:确定性收束与 Runtime 内部产物验证**都不构成验收证据**。`design-director` 确定性化后不产生 Provider 回执,M0-3 的内部 owner 验证同样不产生;而 passed 节点必须引用真实成功动作回执且 `requiredEvidence` 须命中 `agent_runtime_acceptance_evidence_tools()`。涉及 GDD 落地的验收标准要么由根 Supervisor 用允许的证据工具自行取证,要么不写成 required 节点。**不得**为使确定性节点可验收而把内部验证工具暴露给 Provider——那会推翻 M0-3 已冻结的 owner 验证边界。 -- M2 裁决二:planning baseline 不是「一次冻结永久有效」。steer 替换协议会终止旧根树并另起 replacement root run,该 run 必须在自己的锁/CAS 边界内重新冻结与被替换根**完全相同**的 `approvedGddRef`,即使期间已有更新的 approved 版本也不换稿;无法证明同一 ref 时失败关闭,不得降级为 `mode=direct-build`。 -- M2 附带证据:Goal Contract 落在 `.agent/runtime/goal-contracts/{rootAgent}/{rootRun}.json`,按 root run 分文件并绑定 binding fingerprint 与 source SHA-256。这是「自带 root/run 身份」的正面先例,D4 关于 `approvedGddRef` 是否进 manifest 的决策应参照此形态,而非项目级单例。 - -- 背景:M0B-2 遗留一条挂起项——manifest 没有 root/run 绑定,不能安全决定 cached failed manifest 是否应覆盖活跃 main 状态。本条对该项作出处置,使 M0-4 可以收口。 -- 事实基线:`GameCreationAppManifest` / `GameCreationAppTaskState`(TS 与 Rust 双侧)不含任何 run/root/agent 身份字段;manifest 是项目级单例文件、跨轮复用;前端唯一读取入口按 `task.id === 'code-prototype'` 做纯字符串过滤。因此"这份 manifest 属于哪一轮"无法从数据本身判定,这是结构性质而非实现疏漏。 -- 本阶段不加绑定:不给 manifest 或其投影新增 `statusRunId` / `statusSource` 等身份字段,维持 M0B-2「不修改后端 DTO/schema/delivery/route」的范围声明。补字段的方案必须同时覆盖 `update_manifest_task_status_at` 与 `set_task_status` 两条写入路径,否则会制造"校验通过"的假象(详见 pitfalls 2026-08-12 条)。 -- 残余风险(明示保留,不视为回归):当前 main 到达 Runtime 终态 `completed`、而全局 manifest state 尚未被本轮 `manifestInvalidated` 事件刷新时,跨轮残留的 `failed` 仍会被当作本轮结论显示。该窗口实际宽度未量化;三条候选机制(新鲜度门控、root-scoped 永久缓存、manifest 补身份字段)经审查均不可安全落地,故本阶段只记录不实现。 - -- 归档边界:`【Supervisor 阶段记录】` 只有在 root、唯一 main、当前动态美术 children 与 manifest `code-prototype` 全部终态且无冲突/reconciliation 时才可写入。等待期间按完整 `agent/session/run` 身份保存 root-scoped Runtime 快照,避免 current-by-agent map 被新 root 覆盖后把新旧证据串线;消息 ID 固定绑定 root run,重载与 hydration 幂等。 -- 范围:M0B-2 只改前端纯投影、接线、文案与回归,不修改后端 DTO/schema/delivery/route,不迁移历史 manifest,也不提前实现 M1~M3。 - -- 结构性依据:delivery 的 delegationId 由父动作 ID 派生,且 targetRunId 绑定原 child run;通用 retry 铸造的 `retry:{旧run}:{新run}:{纳秒}` 身份在结构上不可能匹配任何现有 delivery。即使只圈住 retry 的写边界,其产出也没有合法消费者。让 retry 继承 lineage 必须引入可变 delivery、换绑或放宽 exact binding,与不可变事实和失败关闭方向冲突,明确不采纳。 -- 既有语义确认:同一 main run 内“每个审计缺口最多委派一次”(不含 `Suppressed`、包含终态失败或取消 delivery)是 2026-08-08 单主编排重构的 master 既有防抖语义,本裁决有意保留,不为失败 child 开豁免。同 run 重来与通用 retry 一样被拒绝,恢复只走下一轮 main 重新审计;这与禁止 retry 继承 lineage 是同一设计哲学。 -- 实现边界:入口守卫落在 `runtime_driver/lifecycle_control.rs` 的 `retry_game_creator_agent_runtime_task_at`,必须使用不要求 child 仍为 running 的结构身份分类;`resolve_game_creator_agent_runtime_retry_configuration_at` 保持不变。纵深防御落在 `runtime_tools/file_ops.rs` 的动态美术分类入口;严格 lineage/Canvas 授权 predicate 本身不放宽。除这两处与对应测试外,不扩展 M0B-1 生产改动面。 -- 验证方式:终态 failed/cancelled 动态美术 child 的 retry 返回类型化错误且不产生新 run;遗留或伪造的 `agent-delegate-retry` 美术 run 对 `file.write`、`project.patchset`、`canvas.asset_generate` 及其它全部可变工具阻断;完整 DAG child、非美术委派和顶层 retry 非回归;失败或取消 child 的回执被认领后,同一 main run 对同一 target 的新 action 仍拒绝且零新 delivery,下一轮 main 重新 `asset.list` 并路由同一缺口后允许新委派,且新 delegationId、targetRunId、parent run、delivery 与 assets-only 授权全链一致。 -- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`。 - ## 2026-08-11 固定 owner 产物验证与可玩验收分离 - 背景:真实新项目初始化后没有 `package.json`,默认 `game/index.html` 只是无 `` 的占位页。`design-foundation`、`balance-seed`、`art-asset-plan`、`audio-asset-plan` 位于 `code-prototype` 上游,只负责策划、数值、美术清单和音频清单;若要求它们执行 `project.verify` 或 `game.static_smoke`,前者没有可执行合同,后者只能检查尚未生成的占位游戏并必然失败。曾在测试中预先写入 `fake_llm_game_draft()` 会把占位入口替换成可玩页面,从而掩盖这条真实新项目死锁。 @@ -1782,18 +1255,6 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 验证方式:使用真实 `init_local_game_project_at` 证明无 `package.json`、占位入口 smoke 失败、owner 产物不齐时阻断、产物齐全后 Runtime 内部验证收束、无 smoke trace 且 `staticSmokeVerifiedRevision` 为空;再覆盖四个 owner 的路径矩阵、再次 mutation 失效、错误 source/run/root/parent/终态拒绝、恢复重验、跨 Agent/run 不可借用、`code-prototype` / `preview-readiness` smoke 非回归,以及 `art-director` 有/无 Key 的条件角色分类。 - 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`。 -## 2026-08-10 立项策划 Agent 使用 Fast GDD 版本审批作为完整构建的可选基线 - -- 背景:当前普通完整构建从简短需求直接进入 autonomous manifest,缺少用户在消耗完整构建成本前确认玩法方向、MVP 范围和原型验证项的正式环节;现有 `design-director` 是只读协调任务,`design-foundation` 又会自行补齐玩法定位,用户意图与实现之间没有可版本化、可审批、可恢复的信任根。 -- 阶段合同基线:durable source 为 `project-supervisor-plan-chat`,Rust 常量为 `AGENT_RUNTIME_SUPERVISOR_PLAN_CHAT_SOURCE`,Prompt composition/source kind 为 `supervisorPlanChat` / `SupervisorPlanChat`,原生工具为 `plan.submit_gdd`;strict submit/回答解释 checkpoint/provider binding 为 `plan-submit-gdd-input.v1` / `plan-decision-checkpoint.v1` / `plan-provider-session-binding.v1`。exact plan Provider lifecycle/action batch 目标写入 `game-creator-provider-request-lifecycle.v3` / `game-creator-provider-action-batch.v4`;普通 tool-plan 只要有 action,即使 sole action也强制 durable 写 v4 batch,user-input/submit 各有唯一 member,checkpoint/final-reply 无 action 才不建 batch。非 plan 继续写 v2/v3,旧 lifecycle v1/v2 与 batch v1/v2/v3 只按非 plan 语义双读,不能补 binding 恢复成 plan。独立 pending kind/schema 为 `gdd-approval` / `plan-gdd-approval-pending.v1`,审批与 hydrate 命令为 `decide_game_creator_plan_gdd` / `hydrate_game_creator_plan_gdd_state`,hydrate view 为 `plan-gdd-state-view.v1`;阶段名为“立项策划”,persona 名为“立项策划 Agent”,现有 design 组用户名称改为“设计实现组”。checkpoint handoff 的私有 schema/path/slot/ledger 排序不在本次非交付检查点选择实现方案,必须在 M1 对应代码合入前另行冻结。 -- 持久化与信任:`.agent/planning/gdd.v{N}.json` 和 `.agent/planning/approvals/v{N}.json` 使用 no-replace create-only 发布,磁盘 JSON 固定 compact UTF-8 且无结尾换行,分别以 GDD durable create 和 receipt durable create 作为提交点;receipt 是构建信任根,index、session 的已提交摘要、Markdown、pending、审计、event 和 observation 都不能反向覆盖不可变事实。Markdown 有有效 approved 时固定投影最高 approved,否则投影最新严格 submitted GDD 并显示推导状态,不能在 revise/reject 后残留未决文案。planning GDD/回答解释 checkpoint/审批 intent/receipt/session/pending/comment 使用 domain-separated typed serde `sha256-serde-json-v2:`;现役 `actionFingerprint`、`runProfileBindingFingerprint`、answer hash 与 handoff response fingerprint 继续使用裸 64 hex,不做全局迁移,两类值不得互相比较。 -- 对话 checkpoint:plan 专用 `user.input_request` 保持现役 questions-only sole action;Runtime 先创建或复用与 v4 batch identity 全等的 pending sidecar,再把完整问题、hash 与 request/action/provider identity 写入 session.activeQuestion,最后展示决策卡。activeQuestion 尚未落时,session 仍等于 batch binding 才安装同一问题;session 已是合法 successor 且尚无 standalone pending/card/answer 时,旧问题未被消费,必须先补 lifecycle completed、supersede 并回读旧 batch、删除 exact pending sidecar并确认 absent,再清理 batch和从 successor 请求新问题,不能把合法 steer 一律送入 reconciliation。activeQuestion durable 后只允许 v4 batch `ready/nextActionIndex=0/唯一 approved member`,standalone pending 从 absent 直接进入 exact `auto + waiting-for-user-input + observation:null`。若在 runtime/card 发布前崩溃,只能按相同身份补齐或按上述 successor 清理顺序前滚;其它 sidecar/member/cursor/pending 状态和无法证明的 session 漂移失败关闭。用户回答先停在 `answer-prepared`;启动 checkpoint 前还必须重验 exact v4/standalone waiting anchor。随后同一 run 发起只广告 plan 专用 strict `update_agent_plan` 的 `plan-decision-checkpoint` Provider turn;Agent 用 `plan-decision-checkpoint.v1` 形成 answerSummary 和需要时的微型原型项。success handoff durable 后,Runtime 以 session revision/fingerprint CAS 追加 decisionsSummary/prototypeValidationItems/appliedAnswers 并增加 roundsUsed;新 session primary durable 是该轮解释的线性化点,此后才发布原 input observation。handoff 与 current session binding 相等时只重放 handoff;若 current 已是仍保留同题/答案的合法 successor,旧 handoff 不能跨 context 应用,replacement binding 必须以 `supersededCheckpointProviderRequestIds` 传递闭包记录旧 request;最终 appliedAnswers 根据完整 handoff 生成并保存对应 `supersededCheckpointHandoffs` 最小摘要后,旧 handoff 才稳定收口为被替换历史并可清理。长期读取只信 sessionFingerprint 保护的摘要,不要求历史 handoff 文件存在;链缺口、分叉或 identity 不同失败关闭。answerResponseId 只在所属 requestId 域内幂等,不同 request 合法复用同一文本值;下一题/submit 使用新 session 的另一普通 tool-plan 请求。原始回答、选项说明或聊天正文单独都不能猜解释,同 ID 同 checkpoint 只补投影,不同 answer/checkpoint identity 失败关闭。 -- 审批与恢复:Runtime 在 GDD 提交前冻结 `approvalRequestId`,UI 为一次 GDD 审批决定生成并在传输重试中复用 `gdd-response-` responseId;它与现役 user-input answer transport 的同名 responseId 属于不同幂等域,不能跨域比较或恢复。approvalResponseId 只在所属 GDD ref/action 域内幂等,decision audit 使用 `(recordType,gddId,version,responseId)` 复合键,不同版本允许复用同一文本值。审批等待只写 `.agent/planning/pending.json`,不升级或重写 `game-creator-pending-action.v5`;该投影可由唯一待审 GDD 重建,receipt 后 observation 可确定性重建。最新版本已有任一 approve/revise/reject receipt 后才允许下一版本;revise/reject 由原 run 继续,approve 后必须由用户显式开始新 plan continuation,旧批准在新版本 approve 前继续有效。receipt 后固定修复 index/Markdown、`agent-runtime-plan-gdd-decided.v1` 专用幂等 decision audit、原 action terminal observation 和 session;原 run durable 消费 observation 后才清理 planning pending。提交点之后的投影失败仍返回成功 outcome,并以 `recoveryPending=true` 表示派生投影未齐,不回滚、覆盖或重编号不可变事实。 -- 安全不变量:plan run 必须通过 durable top-level `project-supervisor + standard + project-supervisor-plan-chat` exact binding,action tool 仅 `file.read`、`file.list`、`user.input_request`、`plan.submit_gdd`,MCP 为空且 `webSearchEnabled=false`;Prompt/tool-plan/checkpoint/action batch/batch recovery/repair/context/completion 都跳过 Supervisor collaboration 合同。submit 是 sole action,并通过 main-loop 专用审批等待分支;checkpoint 无 action batch。每个 Provider request 的 effective model、api kind、stream、当前 apiKind 实际生效的 official fallback/Anthropic strict/OpenAI Chat token budget field、输出 token、reasoning/verbosity、tool choice、messages、结构化注入、实际工具目录、requestContextFingerprint 与 durable session binding 必须来自同一 captured session;不适用于当前 apiKind 的 adapter 字段固定为 null,除明确排除的 timeout/retry/backoff/log/URL/header/秘密外,任一实际请求语义变化都产生新 context fingerprint 与 base ID。Provider request ID、handoff ID 和 superseded 控制元数据只进 binding/base identity,禁止进入 messages/tools/structured injection;同 session protocol repair 原样继承 superseded 数组。协议无效且已通过 handoff storage 安全/容量门的 Provider 成功响应仍先持久化 raw response handoff 并补 lifecycle completed;恢复只重放验证并进入同一 repair,不重发原 request,而该 raw handoff 未通过 checkpoint validator,不能追加到 superseded 数组。raw handoff 因超限、敏感键、绝对路径、容量、identity 或 durable write 失败而无法安全提交时,不保存正文、不补 completed、不自动 repair/retry,只写安全诊断并进入 reconciliation。同 session/context 的下一 attempt 也必须先把旧 attempt durable 闭合为已知 failure 的 failed,或在 owner/lease/boot 证明物理请求已终止后闭合为 interrupted;合法 successor 的 replacement 同样只能在 handler 已取得并丢弃旧 response,或证明旧调用终止并回读 interrupted 后启动。任何旧终态回读前都不得新建 started。started 后只有在不存在后续业务消费证明时,合法 session successor 才会 interrupt 旧请求、supersede 尚未执行的 ready batch,或为 checkpoint 使用 `decision-round-{N}-session-{revision}-repair-0` 与新 base attempt 0 自动前滚;同 submission GDD、精确 activeQuestion 和精确 appliedAnswers 是优先于 stale 的三类消费证明,`started + ready batch` 先补真实 completed,binding 损坏则只进入 reconciliation。Runtime 在 session revision 1 固定写入不可由 Provider 改写的 `initial-request`,后续决定和最多 3 个原型项逐项匹配 session 真相。plan 无 command、smoke、preview、canvas、任务图、委派或通用文件写能力;retry 只在旧 run 已终态且无 pending 时保留原 plan source/profile,不能降级为 background。`game/fast_gdd.md` 是 Runtime 内部投影,不推进代码 mutation revision。前端只经 hydrate command 从严格 GDD/receipt/session 推导 `not_started|draft|ready_for_approval|revision_requested|approved|rejected`;receipt 隐藏 stale pending,合法投影未齐只返回 `recoveryPending=true`。完整构建仍由用户动作启动,直接开建必须显式声明,不能因 ref 缺失静默降级。 -- 影响范围:AI 游戏创作客户端、Project Supervisor、Agent Runtime、Prompt Bundle、本地项目 sidecar、项目开发工作台和后续完整构建准入。 -- 验证方式:M0 验证 tracked 技术方案、索引、注册表、golden 指纹、提交/恢复合同和决策记录自包含一致;M1~M3 分别按关联技术方案的阶段门禁执行,不能以文档合入冒充功能完成。 -- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`。 - ## 2026-08-10 资源管理评审阻塞项按第二轮正式合同修复 - 背景:资源管理第一轮实现后,人工验证继续暴露 WebView 默认缩放、预览队列饥饿、过滤后媒体残留播放、外层滚动串 scope、超深依赖坐标越过 Rust 上限和暂时错误无法重试等问题。部分 PRD / 技术方案仍描述第一轮的中央媒体预览、单全局 Overlay 和统一 section scope,已经与第二轮代码及验收结论冲突。 @@ -2664,30 +2125,6 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 验证方式:运行 `cargo test -p api-server editor_bgfilter_cross_check --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_canvas_screen_background_generation_uses_bgfilter_postprocess --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_character_animation_frames_use_three_stage_matting_fallback --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。 - 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。 -## 2026-07-11 SpacetimeDB 工具链统一升级到 2.6.0 - -- 背景:生产数据副本验证已使用 2.6.0 standalone,而仓库 Rust crate、本地 CLI、生成 bindings、容器与 server provision 仍锁定 2.5.0 或更早版本,继续混用会增加 BSATN / procedure 返回值与发布产物错配风险。 -- 决策:`server-rs/Cargo.toml` 的 `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 精确锁定 2.6.0;本地 CLI / standalone、Rust bindings、worker smoke、容器压测镜像和生产 provision 下载根同步对齐 2.6.0。其它 crate 恰好出现的 2.4.1 / 2.5.0 不随本决策机械替换。 -- 影响范围:Rust workspace lockfile、SpacetimeDB bindings、本地 dev 版本门禁、容器 smoke / loadtest、server provision Jenkins 与项目 SpacetimeDB skills / 文档。 -- 验证方式:核对 `spacetime --version`,运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo check` / 定向测试、`npm run test -- scripts/dev.test.ts`、server provision 工具测试、production ops / encoding / diff 门禁。 -- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 - -## 2026-07-17 SpacetimeDB 工具链统一升级到 2.6.1 - -- 背景:SpacetimeDB 2.6.1 修复 procedure context 中调用者 `Identity` / `ConnectionId` 丢失问题,并修正 TypeScript 生成代码中 `Option` 字段的可选键语义;继续运行 2.6.0 会保留已知 procedure 身份回归。 -- 决策:`server-rs/Cargo.toml` 的 `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 精确锁定 2.6.1;本地 CLI / standalone、Rust bindings、worker smoke、容器压测镜像和生产 provision 下载根同步对齐 2.6.1。 -- 影响范围:Rust workspace lockfile、SpacetimeDB bindings、本地 dev 版本门禁、容器 smoke / loadtest、server provision Jenkins 与项目 SpacetimeDB skills / 文档。 -- 验证方式:核对 `spacetime --version`,运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo check` / 定向测试、server provision 工具测试、encoding / diff 门禁。 -- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 - -## 2026-07-23 SpacetimeDB 工具链统一升级到 2.7.0 - -- 背景:SpacetimeDB 2.7.0 增加满足数据约束时的 unique / primary-key 非破坏迁移、Rust SDK capability traits、standalone MCP endpoint、SQL JSON 输出和更多连接/视图/内存指标,并修复旧 procedural-view backing table 的自动迁移。官方当前发行资产位于 `v2.7.0-hotfix3` 标签,二进制和 Rust crates 版本仍为 2.7.0。 -- 决策:`server-rs/Cargo.toml` 的 `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 精确锁定 2.7.0;本地 CLI / standalone 与 Rust bindings 使用官方 2.7.0 hotfix3 构建,worker smoke 本地镜像按运行版本标记 2.7.0,官方容器和生产 provision 下载根固定到 `v2.7.0-hotfix3`。provision 从 hotfix 资产标签解析运行版本时必须得到 2.7.0,并同时核对 CLI commit 为 `d220349a...`;裸 tag `a08663c7...` 不得因版本号相同而被复用,下载 / 安装结果也必须通过同一 commit 门禁。 -- 影响范围:Rust workspace lockfile、SpacetimeDB bindings、本地 dev 版本门禁、容器 smoke / loadtest、server provision Jenkins 与项目 SpacetimeDB skills / 文档;现役 module 没有 procedural view,本次不修改 schema 或 migration。 -- 验证方式:核对 CLI 版本和 commit,重新生成 Rust bindings,运行 `npm run check:spacetime-schema`、相关 Cargo check、server provision 工具测试、容器配置、Rust 1.93 兼容检查、standalone `/v1/ping`、encoding 和 diff 门禁。 -- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 - ## 2026-07-10 外部生成任务只持久化轻量媒体引用并独立维护摘要投影 - 背景:编辑器 worker 化后直接把同步接口 payload 序列化进 `external_generation_job.request_payload_json`;前端又把已有 OSS `objectKey` 下载成 Data URL 再提交,导致单个任务 JSON 膨胀到数 MB,正式任务列表读取 20 条任务时同时搬运约 65 MB payload,并放大为 SpacetimeDB 与 api-server 的瞬时内存峰值。此前“禁止 Data URL 持久化”只覆盖工程、素材、图层和元数据,遗漏了正式生成任务表。 @@ -3464,16 +2901,6 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 验证方式:微信小程序首点登录仍打开原生登录页;小程序支付仍跳转 `/pages/wechat-pay/index` 并保留 hash 回灌确认;订阅授权仍跳转 `/pages/subscribe-message/index` 且返回不阻断生成;普通浏览器分享、H5 支付和 Native 二维码支付不受影响。前端验证运行 HostBridge、auth、payment、分享、订阅和个人中心充值相关定向测试,并执行 `npm run typecheck`、`npm run check:encoding`。 - 关联文档:`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`。 -## 2026-06-15 SpacetimeDB 本地 skills 范围(已由 2026-08-27 决策覆盖) - -> 2026-08-27 覆盖说明:本节记录的“三个本地 skill”方案已收敛为单一项目适配层;当前口径见下方“SpacetimeDB 项目 skill 与官方插件职责收敛”。 - -- 背景:本仓库的 SpacetimeDB 接入已固定为 `server-rs + Axum + SpacetimeDB`,本地 skill 需要从上游 SpacetimeDB `skills/` 更新到 2.5 口径,同时避免继续维护当前项目不使用的 TypeScript server/client、C# 和 Unity 专用 skill。 -- 决策:当时仅在仓库内维护与当前后端路线相关的 SpacetimeDB skill,通用 SDK/CLI 内容按上游资料核对;该历史范围已由 2026-08-27 的项目适配层方案替代。 -- 影响范围:当时的 `AGENTS.md` SpacetimeDB skill 清单和本地 skill 维护范围;当前范围以新的项目适配层及官方插件路由为准。 -- 验证方式:保留当时的上游 skill 对照、本地 skill 校验、删除引用扫描、diff 和编码检查记录。 -- 关联文档:`AGENTS.md`、`.codex/skills/genarrative-spacetimedb/SKILL.md`、`docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`。 - ## 2026-06-13 图片大图预览统一为黑底全屏查看器 - 背景:`CreativeImageInputPanel` 的参考图 / 主图预览曾使用白底 `UnifiedModal` 工具弹窗,移动端会透出原页面背景,且不能全屏查看、缩放或拖拽细节。 @@ -7528,14 +6955,6 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 影响范围:`scripts/deploy/production-api-deploy.sh`、`scripts/check-production-api-deploy.mjs`、`jenkins/Jenkinsfile.production-api-deploy`、`jenkins/Jenkinsfile.production-full-build-and-deploy`、`scripts/check-production-ops-guardrails.mjs`、生产运维文档和 worker systemd 发布契约。 - 验证方式:`bash -n scripts/deploy/production-api-deploy.sh`、`node --check scripts/check-production-api-deploy.mjs`、`npm run check:production-api-deploy`、`npm run check:production-ops`、`npm run check:encoding`、`git diff --check`。 -## 2026-07-23 Gitea CI 镜像刷新到 SpacetimeDB 2.7.0 锁 - -- 背景:`server-rs/Cargo.lock` 已从镜像预热时的 SpacetimeDB 2.6.1 前移到 2.7.0,runtime 校验因此报告 `server_rust_cache_lock=partial`。受控 Cargo egress proxy 连续返回 CONNECT tunnel 502 时,Backend job 在 `check:module-runtime-artifact` 依赖解析阶段失败,尚未进入 workspace tests。 -- 决策:刷新默认镜像 tag 为 `genarrative/gitea-project-ci:20260723.1`,完整 Image ID 为 `sha256:c04b114b1f145072c9df7842c4c974e1bb2eaaf391d95d84c9212a460546b7d5`,并将 Runner `genarrative-ci` label 映射到该精确 ID。镜像内 server-rs lock SHA-256 为 `ab1e07479b5716a98ddab9824bf95664121935aea30ab719f76f0705e1f96bbb`,包含 `spacetimedb`、`spacetimedb-lib` 和 `spacetimedb-sdk` 2.7.0 缓存。 -- 构建边界:两个 `cargo fetch --locked` 在 Cargo 自身网络重试外再执行最多 5 次整命令级重试,处理 registry index / config TLS 握手直接失败;版本解析仍受 lockfile 固定,构建末尾继续以 `CARGO_NET_OFFLINE=true cargo fetch --locked` 证明缓存闭合。 -- 运维边界:镜像归档、SHA-256 sidecar、切换前 Runner config 与注册文件备份只保存到仓库外受控目录。切换前连续确认 Gitea 无 `in_progress` run 且内层 Docker 无容器,切换后等待 rootless Docker socket 恢复,再验证 Image ID、label 注册、bwrap 与 Chrome canary;旧镜像在真实 CI 通过前保留。 -- 验证方式:新镜像在 `--network none` 下按当前锁完成 `cargo fetch --offline`,并完成 `module-runtime`、`platform-auth`、`platform-wechat` 构建;宿主与 runner 内层 Image ID 一致。真实 master CI 还必须确认 cache lock 命中并完成原 Backend workspace tests。 - ## 2026-07-22 陶泥儿精选改为顺序循环分列 Masonry - 背景:CSS multi-column 会按纵向高度平衡卡片,少量素材或活动卡与普通素材高度差较大时,桌面首行会只放一到两张,后续卡片提前从左侧下一段开始,无法满足“每行填满三张再换行”。 @@ -8856,39 +8275,6 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 生成文件名保留可读清洗前缀,并追加 asset ID 的 SHA-256 摘要前缀以避免不同 ID 碰撞;不迁移既有旧路径,调用方需在采用新命名后使用新返回路径。 - Radial90 的前端预览与 Rust 导出统一使用角点映射和顺时针起始角规则,顺时针填充从角点前一条边开始,避免两端渲染偏移。 -## 2026-09-03 策划会话 Runtime V2 P1 内核落地 - -- 新增 `PlanningSessionRuntime V2` 内核入口:`start_planning_session_v2`、`continue_planning_session_v2`、`hydrate_planning_session_v2`。P1 只负责单 Agent 会话生命周期、Provider 流式/普通调用、消息持久化、回合幂等、单项目单活跃回合、耗时和失败恢复,不接入 GDD 解析、审批或旧 Supervisor。 -- V2 会话快照落在 `.agent/planning-v2/session.json`,完整消息落在 `.agent/planning-v2/conversation.jsonl`。同一 `clientTurnId` 已有成功 assistant 记录时直接重放;若只有 error 记录则允许沿用同一用户意图重试,不重复追加用户消息。进程退出后 hydrate 发现 `planning` 会投影为 `provider_failed/RECOVERY_REQUIRED`,不伪造成功或自动重试。 -- Provider 调用复用现有 `platform-llm` 的 API-kind、流式解析、超时和重试配置;当前策略快照固定 `mode=gdd`、`questionLimit=8`、`tools=[]`、`skills=[]`。GDD 业务规则留给 P2,生产 UI 接入留给 P3。 - -## 2026-09-03 策划会话 Runtime V2 P2 产物闭环 - -- V2 新增 `GddPlanningPolicy`:Provider 输出只接受合法 question 或完整 GDD JSON;question 在 `questionCount < questionLimit` 时保存并展示,第 8 个问题仍可展示,第 8 个之后再次返回 question 时只允许一次内部强制出稿重试,额外 question 不落盘、不进入用户等待态。 -- V2 GDD 使用独立 `plan-gdd.v2`,只保留项目身份、版本、游戏内容、决定、原型验证项和指纹,不带旧 Supervisor/Run/delegation 字段。每个版本写入 `.agent/planning-v2/gdd.vN.json`,同步更新 V2 index 和 `game/fast_gdd.md`;旧版本不可覆盖。 -- 新增 `decide_planning_artifact_v2` 与 `plan-approval.v2`。批准、修改、退回均绑定当前 Session、artifact、version 和 fingerprint;修改/退回必须有意见,修改后 Session 回到 `revision_requested`,下一轮由同一 V2 Session 继续。`answerSource` 缺失或未知值回退,不成为单独阻断项。 - -## 2026-09-03 策划会话 Runtime V2 P3 入口与 UI 接入开始 - -- 正式 AGC“做方案”入口在 `planningStartMode` 下直接调用 `start_planning_session_v2`、`continue_planning_session_v2` 和 `decide_planning_artifact_v2`,不再为新策划回合创建 Supervisor root、child Run 或 delegation。 -- 前端以适配层复用现有聊天区、澄清输入卡、GDD 审批卡和阶段进度条;V2 hydrate 返回 `conversation` 消息,用于页面刷新和重启后恢复可见历史。 -- V2 会话使用独立 `planning-session-v2-stream` 事件,旧 Supervisor Runtime 轮询、专业 Agent 轮询和旧 Runtime 事件不会介入 V2 会话。 -- 没有 V2 authority 的项目仍按旧读取路径打开;旧链路封存、未完成旧会话强制失败和旧入口彻底关闭仍留在 P5。 - -## 2026-09-05 策划会话 Runtime V2 P4 收口 - -- Planning V2 的 P4 灰度与回归验收已通过人工校验:正常提问/回答/GDD/批准链路、GDD 修改后再次批准链路、Provider 失败恢复、非法输出失败边界、重启恢复、前端工作台展示以及编码/差异门禁均完成验证。 -- P4 收口不代表旧链路退役;旧 `project-supervisor-plan` / `project-planning` 源码和入口仍保留,旧活跃会话封存、迟到结果隔离、旧入口关闭和只读历史展示统一留在后续 P5。 -- P5 收缩为最小退役:旧 `project-supervisor-plan` caller 统一立即返回退役错误,不启动旧 Runtime/Provider;不扩展 V1 `PlanSessionV1` schema,不做旧数据迁移,旧文件继续保留只读,V2 只读取 `.agent/planning-v2`。 - -## 2026-09-05 修正 Planning V2 流式交互契约 - -- Planning V2 不要求把 Provider 的文本 stream delta 逐条投影为用户可见对话。V2 的用户交互是结构化工具调用结果:`plan_ask_question` 渲染澄清选项卡,`plan_submit_gdd` 渲染 GDD 输出和审批卡;中间纯文本不是用户对话内容。 -- `planning-session-v2-stream` 若继续存在,只能作为内部状态/兼容事件能力,不构成实时逐 delta 的功能契约;Provider 是否使用流式传输不影响 V2 的业务验收。 -- 文档措辞约束:凡出现“流式响应”“流式事件”或 `text_delta`,均须注明其属于 Provider adapter/Runtime 内部实现能力;不得将其描述为前端必须逐条接收的用户可见消息。V2 的唯一用户交互结果是 `plan_ask_question` 和 `plan_submit_gdd` 的结构化工具结果,Provider 完成前是否产生多个 delta 不参与验收。 - -## 2026-08-29 AGC 官方 LLM 代理与 Windows 私有路径修复 - ## 2026-08-29 AGC 官方 LLM 代理与 Windows 私有路径修复 - AGC 正式发行版不再让用户配置 Provider、Base URL 或 API Key。客户端只携带登录 access token 调用 `api-server`;`api-server` 按 access token 的 owner 查询 `llm_router_account`,解密服务端密文后调用固定 `https://router.genarrative.world/v1`。模型目录由后台 owner 管理并持久化到 `agc_model_catalog`,客户端仅显示别名,在对话框右下角选择稳定标识,服务端映射实际模型名;设置页不承载模型选择或方案管理。真实 Router Key 不进入聊天、manifest、trace、日志、项目文件、Codex argv/环境变量或普通 IPC payload。 @@ -8951,25 +8337,6 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 搜索结果始终是不可信外部输入,只可作为资料;工具 schema、参数、客户端权限、Agent 身份、系统规则和工具协议不得由网页内容修改。搜索结果与状态投影不携带 API Key、请求头、宿主绝对路径或 Provider 原始错误正文。 - 验证锁定:`configuration`、`direct_tools_mcp`、`direct_tool_bridge`、`codex_app_server` 定向 Rust 测试,以及前端状态格式化 / AppSurface 测试;真实 Provider 登录态与真实公网搜索仍需单独现场 smoke。 -## 2026-09-08 策划 V1 链路源码整体退役(四不写) - -- 策划会话 Runtime V2 全量接管“做方案”入口后,旧 V1 链路(`project-supervisor-plan` 根 Run、`project-planning` 子 Agent、`plan.submit_gdd` 工具、Fast GDD 审批门禁与恢复车道)按四不写原则整体删除源码,不保留兼容实现、墓碑注释或防御性测试。 -- 删除范围:`runtime_protocol` 六个 V1 模块与四条 V1 提示词、prompt manifest planning 目录与生成常量、`capabilities.planning` 配置开关、CLI `--swarm-chat --plan` 与 `--plan-gdd-status/--plan-gdd-decide`、provider_retry 的 planning session binding、provider_action_batch 的 V1 提交批次校验、main_loop/pending_recovery/recovery_scan/acceptance_graph 的 plan 根特判、agent_db 三条 V1 专用持久车道(`agent.runtime.plan.provider_usage`、`agent.runtime.plan.gdd_decided`、`agent.runtime.plan_submit_gdd.committed`)及预留配额、前端旧 V1 读模型、`scripts/agent-swarm-test-chat.mjs --plan` 与 `test:plan*` 脚本。 -- 保留项:V2 与 V1 共用的 GDD 数据模型收敛到 `runtime_protocol/planning_gdd_model.rs`;`.agent/planning/` 旧文件不迁移、不删除,旧 `fast_gdd.md` 仍可只读打开;前端 `PROJECT_SUPERVISOR_PLAN_SOURCE` 字符串与 `GddApprovalCard.tsx`(`PlanGddSurface`)是 V2 现役的适配/展示面,不属于退役对象。 -- 旧 sidecar 中带已删字段(`clarification`、`continuation`、`requestId`、`turnId`、`pendingApproval` 等)的记录会因 `deny_unknown_fields` 拒绝反序列化,这是退役语义的一部分,不做迁移。 -- 测试基线说明:收尾时测试套件存在 25 个既有失败(mock LLM 时序敏感类,HEAD worktree 对照验证与本次无关),后续清理时不要误记到本次退役头上。 - -## 2026-09-08 Planning V2 正常 run 优先的恢复旁路 - -- 恢复只利用已经落盘的合法 question、GDD 和 approval receipt;不调用 Provider、不要求模型额外输出恢复字段、不设置复杂状态机或自动重试。 -- 正常 run 进行时不读取或写入其 Planning V2 文件,也不增加文件锁或等待;无活跃 run 时恢复只做一次非阻塞锁尝试,竞争即退出,交给既有轮询。 -- question 恢复为成功结果,approval 重放复用 receipt 原始 decisionId。 - -## 2026-09-08 Planning V2 用户输入与异步投影身份 - -- 问题回答携带被回答卡片的 questionId,在已有回合锁内核对 Session 和当前问题;自由文本回答同样绑定问题,已完成回合保留幂等重放。此身份匹配服务于用户提交,不增加恢复门禁或模型输出要求。 -- hydrate 结果(包括空结果)写入前端状态前同时核对请求序列和当前项目路径;过期结果直接丢弃,不重试、不阻塞正常 run。 - ## 2026-09-09 manifest 资源功能分类与自定义标签接入 UI 与写入 - 决策:资源筛选口径收敛为单一权威 `category`。删除 `resourceReferences.ts` 里 mediaType 优先的 7 桶口径与 `resourceReferenceFilterKind`,`@` 面板与资源画布统一改用 `RESOURCE_REFERENCE_FILTERS`(`全部 / UI 交互 / 角色与对象 / 场景与环境 / 音频 / 文档 / 待归类`,即 `all` + 6 个合法分类);筛选与标签文案的中文名只在该常量定义一次。 @@ -9231,49 +8598,21 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 验证:`projectResourceLiveUpdateModel.test.ts` 新增「同 revision 只有 `preview` 变化必须被接受」与「同 revision 资产变化必须仍被拒收」两条;变异回整份 JSON 指纹后前者以 `expected 'revision-conflict' to be 'accepted'` 失败。定向:模型 17 passed、workspaceLauncherManifestMerge 5 passed、appSurface 413 passed、typecheck exit 0、check:encoding 4399 files passed。 - 影响范围:`apps/ai-game-creator-shell/src/view/project-development/projectResourceLiveUpdateModel.ts`、`apps/ai-game-creator-shell/tests/projectResourceLiveUpdateModel.test.ts`、`pitfalls.md` 本条。 -## 2026-09-11 AGC 资源替换按 PRD 恢复为「版本级替换」:改绑定落在追加的新版本上 +## 2026-09-11 AGC 资源替换直接改写指定版本绑定 -- 背景:PRD §3.2(`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md:36-41`)要求「替换版本引用资源时创建下一迭代版本,不原地修改既有版本」,并要求新版本记录 `parentVersionId`、替换前后资源身份与创建原因;§5.3(:356-387)给出 `ProjectVersionResourceReplacement` 与「三项兼容性必须同时为 true 才能创建下一版本」。本文件 2026-09-10 那条(`decision-log.md:8263`)与 Issue #309 的 C1 决策 / 贯穿性决策 6 / 验收总纲当时写的是相反口径:「改 manifest 绑定……**不创建新版本**」「替换功能(候选素材 / 替换关系 / 替换队列 / Agent 审核 / 单点替换)整条取消,不实现」。用户裁决:**按 PRD 来**,实际机制仍是「改 manifest 绑定」。 -- 决策:资源替换 = **改绑定 + 追加下一迭代版本**。新命令 `replace_local_project_version_resource`(写,`asset.register`)在持项目写锁与 `expectedProjectId + expectedProjectRevision` CAS 下,一次 `mutate_manifest_at` 写入里追加 `createdReason='resource-replacement'` 的子版本(`parentVersionId` 指向被替换的源版本),子版本绑定 = 源版本绑定**去掉源素材**并保证**替换素材在集合里**;只读命令 `read_local_project_version_replacement_candidates`(`asset.list`)返回候选与后端权威的三项兼容性。既有版本记录一个字节不改,**继续走默认空放行集合**,不新增/放宽 `mutate_manifest_at_allowing_version_removals`。 -- 恒等绑定口径的硬约束(实测得出):每个版本的 `resourceBindings` 是「该版本使用的素材集合」而不是槽位表,替换素材若在源版本创建时就已登记,它本来就在集合里,因此**不能**把源素材那条槽位改写成替换素材(会撞「资源槽位重复」);正确落盘是「源素材从集合里消失 + 替换素材在集合里」,替换素材是后来才登记时按源素材原位置插回。 -- 三项兼容性判据:`categoryEqual` 用 PRD §5.3 的**读时自愈**口径(为此在 `server-rs/crates/shared-contracts` 新增 `game_creation_app_asset_category_with_read_time_healing`,与 `packages/shared` 的 `gameCreationAppAssetCategory` 逐分支一致并配用例锁定——此前 Rust 反序列化只有「缺失/非法 → 按 kind 派生」,没有自愈,两侧口径并不一致);`subtypeEqual` 比较 canonical `kind`;`sizeSpecEqual` 比较规范化媒体格式 + 已知帧宽高 + 已知时长,任一方有事实的维度必须相等。 -- 已知降级(刻意接受,不得当成完整实现):manifest 资产表没有 `width / height / durationMs`,现役写入侧几乎全写 `imageSequenceFrames: None`,所以 `sizeSpecEqual` 在真实数据上退化为**媒体格式相等**(`png ↔ webp` 会被拒)。要支持跨图片格式替换,必须先给 manifest asset 增尺寸字段并在写入侧回填(跨端契约变更)。 -- 替换前后资源身份:按 PRD §5.4 的版本字段表口径**用推导记录**,不新增 manifest / 跨端契约字段(`父 − 子 = {源素材}`、`子 − 父 = {替换素材}`,配对由 `parentVersionId` + `createdReason` 确定)。已知限制:替换素材在源版本创建时就已登记时 `子 − 父` 为空集,版本记录无法单独反推配对;要无歧义持久化配对须先给 `GameIterationVersion` 增字段。 -- 边界:C6 的候选素材 / 替换关系图 / 替换队列 / Agent 审核 / 批量提交**仍然不做**(#309 该部分不变);§3.2 末条「当前运行中的版本不消费尚未生成的新版本变更」保持——成功后只切记录层当前版本,不自动重载/重启预览,也不做运行时资源重映射。入口只在资源卡选中工具条、且只对「manifest 资产 + 被当前版本绑定」的素材渲染;候选弹窗复用美术画布的 `ImageCanvasProjectAssetPickerDialog`(AGC 首次使用),以可选 prop 扩展且默认值保持网页端行为逐字不变。 -- 影响范围:`server-rs/crates/shared-contracts/src/game_creation_app.rs`、`src-tauri/src/project/version_resource_replacement.rs`(新增)、`commands.rs`、`main.rs`、`tests/version_resource_replacement.rs`(新增)、`src/features/resource-canvas/resourceVersionReplacement{Model,Transport}.ts`(新增)、`view/project-development/index.tsx`、`src/components/image-editor/ImageCanvasProjectAssetPickerDialog.tsx`、`apps/ai-game-creator-shell/src/styles.css`。 -- 验证方式:Rust 定向 8 条 + `shared-contracts` 20 条;变异验证三条(去掉媒体格式维度 → 2 条转红;绕开读时自愈 → 自愈用例转红;改成原地改既有版本 → 4 条转红并被「项目版本记录写入后不可修改、删除或重排」拦下,证明只追加守卫真的在挡)。前端新增 12 条,变异验证两条(放宽入口判据 → 「不给假按钮」转红;失败路径静默关弹窗 → 「保留弹窗显示原因」转红)。AGC 全量 1231 passed / 4 skipped / 0 failed;共享美术画布组件 1385 passed;typecheck / `cargo check --locked --all-targets` / `check:encoding` / `git diff --check` 全绿。跨端契约、SpacetimeDB schema、manifest 结构与布局 sidecar schema 均未改动。 -- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`(§3.2 保持,§5.3 补实现口径与降级声明,§7.8 新增替换验收)、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`(新增同章节)、`docs/technical/【测试用例】AGC资源工作台V3端到端验收-2026-09-11.md`(§7.3 第 1 条已按本次口径改写)、Issue #309。 - -## 2026-09-11 AGC 资源替换改成「直接替换」:只改该版本的绑定,不建新版本 - -- 背景:同日上午按 PRD §3.2 / §5.3 落地的「版本级替换(改绑定 + 追加下一迭代版本)」,当天下午被用户改口径:「**替换这块先做成直接替换**」(同日 DDL:替换先做,运行画面点选、切图集像素规整先不做)。直接替换 = 改 manifest 里该版本的绑定指向另一个已登记资源,**不追加新版本**,也就是 #309 原先那条「更换资源 = 改 manifest 绑定」的轻形态。 -- 决策(实现):删除版本级路径(按「四不写」:不建 `replace-{revision}` 版本、不写 `parentVersionId`、不写 `createdReason=resource-replacement`、不留兼容分支)。直接替换走**新增的第二条、更窄的版本放行通道** `mutate_manifest_allowing_version_binding_rewrites`:不增不删(版本数量必须相等)、不重排(版本 ID 序列逐项相同)、只有显式放行列出的版本允许 `resourceBindings` 不同、其余字段(`versionId / parentVersionId / projectRevision / createdReason / createdAt / editPrompt`)逐字段相等、未放行版本整条相等、放行集合取自**写入前** manifest、与删除放行集合**互斥**。既有 `validate_version_records_are_append_only` 与既有删除放行通道的语义一行未改;唯一结构性改动是把写盘公共体抽成带校验器参数的内部函数(`write_manifest_locked` 变成一行委托),装盘 / 回读 / 原子替换仍只有一份实现。 -- 恒等绑定口径的硬约束(实测得出,两个形态共用):每个版本的 `resourceBindings` 是「该版本使用的素材集合」而不是槽位表,所以绑定改写 = **源素材从集合里消失 + 保证替换素材在集合里**;不能把源素材那条槽位改写成替换素材(替换素材若早已登记就会撞「资源槽位重复」)。 -- 准入与提示:硬门禁只有 `categoryEqual` 与 `subtypeEqual`(拒绝并说明哪一项不等);`sizeSpecEqual` **降级为提示**(「格式与源素材不同」)不再拒绝。理由:它的完整判据今天不存在(manifest 资产表没有 `width / height / durationMs`,现役写入侧几乎全写 `imageSequenceFrames: None`,实际只等于"媒体格式相等"),硬拦会误拒 `png ↔ webp` 这类直接替换里最常见的需求。要变成硬判据须先给 manifest asset 增尺寸字段并在写入侧回填(跨端契约变更)。 -- 存储与留痕:改绑定属于 `versions` 变化,因此写入后**推进一次 project revision**(跨面快照门禁要求 revision 前进),并追加一条 `asset.version_binding.replace` 审计(复用既有 `append_agent_db_record`,字段 `versionId / sourceResourceId / replacementResourceId / projectRevision`)。审计写失败会报错但不回滚,与 `asset.register` 同口径。 -- **代价(用户已确认接受,记账备查):直接替换没有可回溯的替换历史。** 上一轮版本级形态里"父版本绑定"就是那份历史;现在替换前的身份只存在于 ① 那条审计记录 ② manifest 的 `.previous` 恢复副本(只保留上一次写入)。需要"某个版本历史上被换过几次、换成过什么"时必须另立切片(PRD §3.2 / §5.3 保留为未来合同正是为此)。 -- 文档口径:PRD §3.2 的两条与 §5.3 的 `ProjectVersionResourceReplacement` / "三项兼容性必须同时为 true 才能创建下一版本"**保留为未来合同**并就地标注「当前实现为直接替换(2026-09-11),版本级替换暂缓」;PRD 的**存储边界**("只允许追加…有且只有一个例外")改写成当前状态——现在是**两个例外**(删除素材时连带删版本 / 显式放行版本的绑定改写),因为它是"现在写入校验行为"的描述,留未来式会误导。 -- 影响范围:`src-tauri/src/project/manifest.rs`、`src-tauri/src/project/manifest/version_binding_rewrite_tests.rs`(新增)、`src-tauri/src/project/version_resource_replacement.rs`、`src-tauri/src/tests/version_resource_replacement.rs`、`src/features/resource-canvas/resourceVersionReplacement{Model,Transport}.ts`、`view/project-development/index.tsx`、`src/components/image-editor/ImageCanvasProjectAssetPickerDialog.tsx`、`apps/ai-game-creator-shell/src/styles.css`。 -- 验证方式:放行通道定向 7 条 + 替换定向 8 条 + `shared-contracts` 20 条;**变异验证四条**(去掉"未放行版本整条相等"→两周转红;放行集合改成整个版本数组→"未放行版本"转红;去掉长度检查→"不增不删"转红;准入删掉 category→硬门禁与候选两条转红,均已实测并还原)。前端 13 条(模型 9 + 真链路 4)。门禁:AGC 全量 1231 passed / 4 skipped / 0 failed、共享美术画布组件 1385 passed、`ai-game-creator-shell:typecheck`(含 check-config)、`cargo check --locked --all-targets`、`check:encoding`、`git diff --check` 全绿。 -- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`(§3.2 / §5.3 / §5.4 存储边界 / §7.8 验收)、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`(同章节)、`docs/technical/【技术方案】AGC资源派生与非破坏性编辑合同-2026-09-09.md`、`docs/technical/【测试用例】AGC资源工作台V3端到端验收-2026-09-11.md`、Issue #309。 - -## 2026-09-12 改完素材标签聊天侧不刷新:聊天宿主跟随工作台壳的清单快照 - -- 背景:用户报「对素材修改标签之后统计信息不会更新(例如聊天框里的点选)」。核实后的现状:依赖 manifest `assets[].tags` 的派生数据只有三处 —— `@` 选择器的标签 chip 计数与候选(`ResourceReferenceInput` 的 `scopeTagItems` / `pickerReferences` ← `resourceReferences.ts::resourceReferenceTagLibrary` ← 共享 `buildGameCreationAppAssetTagLibrary`)、资源画布筛选浮层的标签库(`resourceCanvasFilterModel.ts::buildResourceFilterTagOptions`)、资源卡与信息浮层的小字(`resourceCanvasInfoModel.ts`)。后两者都由 `ProjectDevelopmentView` 的 `manifest` prop 派生,写入后重读即生效;**只有聊天侧停在旧清单上**。 -- 根因(机制 + 证据):`App`(聊天宿主,由壳以 `ProjectSupervisor` 传入)持有自己的一份 `manifest` state,初值取自 `initialProjectManifest`。壳(`WorkspaceLauncherShell`)在资源命令(改标签 / 改类型 / 重命名 / 删素材)后确实重读一次 manifest 并按 CAS 归并进 `currentProjectContext`,也把归并结果继续以 `initialProjectManifest` 传下来 —— 但那是 `useState` 的初值,**壳里换了新清单不会再进来**:`App` 里该 prop 只出现在两处 `useState(... ?? seedManifest)` 初始化上,没有任何跟随它的同步,`manifest` 只由聊天侧自己的动作(`refreshManifest` / 自身命令回执)改写;而资源画布那条写入既不经过聊天侧,`update_local_project_resource_classification` 也不发 `game-creator-manifest-invalidated`(该事件只由 `agent/runtime_driver/entrypoints.rs` 的 Runtime emitter 发送)。于是 `chatProjectAssets` 每帧都是同一份旧 `manifest.assets` ⇒ `assetReferences` / `scopeReferences` / `scopeTagItems` 逐层命中旧值,标签 chip 与候选都不动。证据:新增用例在资源画布改完标签保存后,聊天 `@` 选择器的标签行为空(`expected [] to deeply equal ['主角1']`),而画布侧同一份清单已是 `tags: ['主角']`。 -- 决策:在 `App` 里给这份壳快照补一条同步(`useEffect` + `manifestRef`):同一 `projectId`、内容确实变了才 `setManifest`;聊天侧自己的写入会被壳原样回传,因此必须按内容短路(只比身份会让两边无意义地互相推一轮)。不新增第二份标签统计口径、不在业务页再造一份清单状态、不靠整页刷新或强制重挂载;壳那份快照始终来自磁盘重读 + CAS 归并,所以不会把聊天侧带到更旧的版本上。 -- 已知未覆盖(**本轮有意不做**):`?supervisor-chat` 独立聊天窗口(`main.tsx` 仅在 `import.meta.env.DEV` 下开)没有壳、拿不到这份 prop,仍只靠 Tauri 失效事件刷新。要覆盖它得在 Rust 写入命令后补发 `game-creator-manifest-invalidated`(另一条因果点,且无法在 vitest 里先红后绿)。 -- 影响范围:`apps/ai-game-creator-shell/src/App.tsx`(新增跟随 effect + `manifestRef`)、`apps/ai-game-creator-shell/tests/resourceTagStatsRefresh.test.tsx`(新增真宿主链路用例)。manifest 契约、Rust、SpacetimeDB 均不动。 -- 验证方式:`npm run test -- apps/ai-game-creator-shell/tests/resourceTagStatsRefresh.test.tsx`(先红:`expected [] to deeply equal ['主角1']`;修复后 2 条绿)+ 11 个资源相关套件 124 条 + `appSurface.test.ts` 415 条 + `npm run typecheck` + `npm --prefix apps/ai-game-creator-shell run typecheck` + `npm run check:encoding` + `git diff --check`。变异验证(已实测并还原):把跟随 effect 改回「不跟随壳快照」→ 新增用例第一条变红(`expected [] to deeply equal ['主角1']`)。 -- 关联文档:无(本轮不涉及跨端契约、PRD 或后端口径变化)。 +- 当前实现:`replace_local_project_version_resource` 在项目写锁和 `expectedProjectId + expectedProjectRevision` CAS 下,直接改写指定版本的 `resourceBindings`,不追加版本、不更改其它版本。绑定是素材集合:移除源素材,并确保替换素材在集合中;替换素材已存在时不插入重复槽位。 +- 写入边界:`mutate_manifest_allowing_version_binding_rewrites` 仅允许写前明确列出的版本改动绑定;版本不得新增、删除或重排,其它字段和未放行版本保持相同。该通道与版本删除放行通道互斥,仍共用 manifest 的原子装盘和回读。 +- 准入:`categoryEqual` 与 `subtypeEqual` 是硬门禁;`sizeSpecEqual` 只提示格式不同。manifest 尚无完整宽、高和时长事实,不能把当前媒体格式比较当成完整尺寸兼容判据。 +- 成功改写后推进一次项目 revision,并写入 `asset.version_binding.replace` 审计,包含版本及替换前后资源身份。审计失败会报错但不回滚已提交的绑定。客户端没有可查询的持久替换历史;`.previous` 仅是装盘期间的崩溃兜底,不能作为血缘数据源。当前画布只在会话内显示一条有效替换关系,见 2026-09-13 条目。 +- PRD §3.2 / §5.3 的追加版本替换仍是未来合同,当前实现以直接替换为准;现役存储边界允许删除素材时连带删版本,以及显式放行版本的绑定改写。完整合同见[工作台 PRD](../../prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md)与[AGC 实施计划](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md)。 ## 2026-09-13 AGC 资源画布「搜索」与「筛选」合并为单一筛选浮层 -- 背景:右下角 Dock 上并排两颗按钮——放大镜开 `.game-resource-search` 纯搜索浮层,滑块开 `.game-resource-filter-panel` 筛选浮层;两者共用同一份 `searchText`(筛选面板的第一个字段就是同一个关键词),却各存一套开合状态,互斥逻辑在两个 `onClick` 里各写一遍且无测试守护。同一个功能两个入口,本身就是两处随时会漂移的状态。 -- 决策:**状态不合并、只收敛 UI 入口。** 删除纯搜索浮层的 JSX、`resourceSearchOpen` / `resourceSearchId` / `resourceSearchRef` / `resourceSearchTriggerRef` / `resourceSearchPopoverRef`、它的共享 dismiss hook 调用与 `.game-resource-search` 两条 CSS;Dock 只留放大镜按钮(`aria-controls` → `resourceFilterId`,`is-active` → `resourceFilterActive`),`Ctrl/Cmd+F` 与它打开同一个面板。面板的「点外部 + Esc、Esc 后焦点还给触发按钮」继续沿用它自己那套手写实现(不改成共享 hook,共享 hook 判据用 `.image-canvas-editor__portal-menu`,会把焦点还错地方)。关键词字段改由 `autoFocus` 承接既有「浮层打开后焦点落到搜索框」,并保留 `type=search` 原生 Esc 清空的 `preventDefault`(关闭 ≠ 清条件)。 -- 原因:`searchText`、`resourceFilterTags` 与派生 region 的语义一行未改,「条件生效只隐藏卡片、不重排坐标」与 Dock 高亮判据(关键词 ∨ 标签,**不含区域**)因此不可能跟着变;真正多余的只是那颗只看关键词的浮层和它那份开合状态。`filterCanvasResources` 的过滤链路与 `resourceCanvasFocusModel` 里 `.game-resource-filter-panel` 的滚轮归属登记一并保留(删了面板内滚轮会带动画布)。 -- 验证:`appSurface.test.ts` 全量 416 passed(含改写后的 4 条:CSS/Dock 锚定几何、DOM 归属并列、两视图关键词过滤、快捷键开合与不清条件/焦点回按钮);`resourceFilterPanel.test.tsx` 8 passed;`projectResourceLiveIntegration.test.tsx` 25 passed;`npm run check:encoding` 与 `git diff --check` 干净。 -- 影响范围:`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`.../ResourceFilterPanel.tsx`、`apps/ai-game-creator-shell/src/styles.css`、`apps/ai-game-creator-shell/tests/appSurface/{harness.ts,project-development.suite.ts}`、`apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx`、PRD §5.2.4、`docs/technical/【测试用例】AGC资源工作台V3端到端验收-2026-09-11.md` 的 S8。 +- 画布只保留一个筛选浮层,Dock 放大镜按钮和 `Ctrl/Cmd+F` 打开同一面板;不恢复常驻分类条或独立的纯搜索浮层。 +- 字段为关键词、所在区域、自定义标签,不提供没有资源事实支撑的“状态”字段。区域直接派生自当前画布栏目,选择区域复用栏目切换;关键词按名称、路径、媒体类型与任务名匹配,标签采用 AND 语义,标签选项从当前区域资源派生。 +- 条件生效只隐藏卡片,不重排坐标;Dock 高亮仅由关键词或标签决定,不因切换区域常亮。关闭浮层不清条件,打开时聚焦关键词;Esc 关闭不触发画布清选中,焦点在面板内时关闭后回到触发按钮。面板滚轮归属保持独立,不能带动画布。 +- 共享 `PlatformFilterPanel` 负责外壳和字段表现,筛选与栏目规则在 AGC 宿主及 `resourceCanvasFilterModel.ts`;`ResourceFilterPanel.tsx` 承接关闭和焦点交互。 +- 现行合同见[工作台 PRD](../../prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md) §5.2.4;浮层遮挡排障见[踩坑记录](./pitfalls.md)的“AGC 资源筛选浮层不能遮住栏目操作”。 ## 2026-09-13 AGC 资源画布替换素材增加「点选替换」入口 @@ -9688,15 +9027,6 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 审计:选择器常量正好是 `.resource-reference-picker, .resource-reference-menu`,指向的就是这两个组件渲染的类;其余类名也逐条与归属组件同名(`ResourceReferenceInput` / `ResourceReferencePicker` / `ResourceReferenceChip`)。规模为 80 条规则(`styles.css` 78 + `resourceCanvasChrome.css` 2)、17 个文件、32 处测试断言。 - 决策:不改名。没有现役调用方、公开契约或样式隔离问题需要靠改名解决,纯改名只增加回归面;等出现新的命名口径时,再与选择器常量、样式表与断言一次性同批改。该条以「接受现状」关闭,其余两条(归一化撞名的候选菜单提示、真机手感验收)仍开放。 -## 2026-09-23 游戏发行入口改为服务端按模板派生:管理员不再手填地址 - -- 背景:游戏审核通过要求管理员手填绝对 HTTPS `entryUrl`,现场出现「不知道该填什么、随手填一个外部站点也能过校验」的风险;而每游戏独立来源本身完全能由 gameId 推出,人工输入没有增加任何判断。 -- 决策(唯一口径):部署侧用 `GENARRATIVE_GAME_DISTRIBUTION_RELEASE_ENTRY_TEMPLATE` 配置带 `{gameId}` 占位符的模板(生产形如 `https://{gameId}.games.<发行域名>/`);审核通过时 `api-server` 读版本取 gameId、替换模板、再走原有绝对 HTTPS / 无凭据 / 无 query / 无 fragment 校验后写入公开投影。后台审核请求 DTO 删除 `entryUrl`,页面不再渲染输入框,也不要求二次确认。 -- 决策(失败关闭):模板缺 `{gameId}`、生产未配置模板、gameId 含非主机安全字符或派生结果非法时,审核通过直接失败,不回落主站、内网或任意外部地址;非生产未配置模板时回落 `http://127.0.0.1:/api/game-distribution/releases/{gameId}/`,保持免 TLS 的本地内嵌游玩验证。 -- 边界:`entryUrl` 仍是公开投影字段,只是改由服务端写入;审核请求摘要不再包含它,表结构与版本回读不变;模板变更只影响之后新通过审核的版本,历史版本已冻结的 `entry_url` 不改写。 -- 影响面:`server-rs/crates/api-server/src/{config.rs,modules/game_distribution.rs}`、`apps/admin-web/src/{api/adminApiTypes.ts,api/adminApiClient.test.ts,pages/AdminGameDistributionReviewPage.tsx,pages/AdminGameDistributionReviewPage.test.tsx}`、`scripts/check-game-distribution-media-e2e.mjs`、`deploy/{nginx,env,container}`、平台与运维主规范、发行里程碑实施计划。 -- 验证:`cargo check -p api-server`、`cargo test -p api-server game_distribution`(31 passed)、admin-web 定向 Vitest(19 passed)与 `apps/admin-web` typecheck、`npm run check:release-origin-config`、`npm run check:doc-index`、`npm run check:encoding`、`git diff --check` 全部通过;真实栈端到端(真实 OSS + SpacetimeDB + 审核通过)未在本轮复跑。 - ## 2026-09-24 游戏发行入口改为平台同源路径:取消发行域名与部署模板变量 - 背景:每游戏独立来源要求 `*.games.<域名>` 通配 DNS 与通配 TLS,一直未在任何环境落地,dev / release 审核通过直接报「发行来源未配置」;同时线上 SPA 白名单缺少 `games` 系列路由,`/games`、`/games/detail`、`/games/play` 在真实域名上全部 404。运行隔离实际由 iframe `sandbox="allow-scripts"` 的不透明来源承担,不需要独立 origin 兜底。 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 422cb3a4c..c9e9f00c3 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -1,5 +1,7 @@ # 踩坑与排障记录 +这里只记录对当前开发仍有用的症状、根因、排查方法和风险边界。同一事实保留一个当前口径;退役对象的专属过程与单轮测试结果由 Git 历史追溯。遇到旧路径或版本时,以现行代码和专题文档为准。 + ## 2026-09-29 逐 delta 脱敏会吃掉段尾换行:DirectProject 汇报的 Markdown 表格整块失效 - **现象**:AGC 项目对话里 agent 汇报正文渲染错位——段落并进同一行、序号项挤成一段、Markdown 表格整块不渲染(表头 `| 素材 | 用途 | 路径 |`、`|---|---|---|` 与数据行以纯文本连在同一行里);同一段文本里的路径还会出现 `gameame.js`、`assetsanvas-generated/...` 这种被切坏的占位符。重开项目读历史时同一段话渲染正常,很容易被当成渲染器偶发或模型写坏了 Markdown。 @@ -94,8 +96,6 @@ - **leader 卡死的兜底**:闸门只有 follower 的有界等待(60s),若提权子进程真的挂死(`Start-Process -Wait` 无超时),`leader_deadline`(5 分钟)之前该 key 一直被占住,之后新调用会接管并按新 leader 执行;被接管后旧 leader 迟到的结果按令牌丢弃,不会覆盖接管者。`clear_game_creator_acl_elevation_denials` 只清「被拒绝」记忆,不清理 running。 - **关联**:`src-tauri/src/acl_repair_gate.rs`、`src-tauri/src/config.rs`、issue #498。 -> 策划历史条目边界:旧策划 V1/V2 已全部退役,当前入口仅使用 Design Agent。下文带日期的旧 Planning V2、Fast GDD、`plan.submit_gdd`、旧 IPC/模块记录仅用于追溯,不能作为恢复旧代码、身份门禁或专属测试的依据;共享问题需在现役调用上核查。现行合同见[策划 Agent 生产迁移与工作区浏览](../../technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md)。 - ## 2026-09-24 模型输出的围栏会粘在正文行里:聊天 Markdown 必须先归一化再解析 - **现象**:AGC 对话里代码块解析错位——引言行被当成代码渲染(`…实现细节(game.js):```js`),或者代码块收不住、把后面的正文一起吞进去(`… return centerOn(projection); }````)。文本本身「看起来没问题」,容易被当成渲染器坏了。 @@ -181,7 +181,7 @@ ## macOS 只出 arm64 单架构,universal 必须失败关闭 -`stage-node-runtime.mjs` 只把**构建宿主的 Node**打成便携运行时(官方发行版是单架构,没有 universal 发行版),而 2026-09-21 之前 `build-macos-ci.mjs` 构建的是 `universal-apple-darwin`:macOS Job #7~#13 因此在 `stageNodeRuntime` 直接抛「Node 运行时不支持发布目标:universal-apple-darwin」。期间出现过一版「按宿主架构放行」的过渡实现(`targetRuntime` 对 universal 返回宿主架构),它能骗过通用包自检(`check-macos-bundle.mjs` 按 `process.arch` 校验),但**Intel Mac 上这份 arm64 侧车不可执行**,等于把坏包发出去。当前决策:macOS 固定只构建 `aarch64-apple-darwin`,清单只登记 `darwin-aarch64`,`targetRuntime('universal-apple-darwin')` 保持失败关闭。恢复 Intel 的正确路径是先在 staging 支持按架构各带一份**同版本**运行时(另下载另一架构官方发行版)并让通用包自检按架构分别校验,再切回 universal 目标、把 `darwin-x86_64` 键登记回去;不得用「只带宿主架构」充数,也不得把 arm64 产物登记成 x86_64 键。 +`stage-node-runtime.mjs` 只将构建宿主的单架构 Node 打成便携运行时。当前 macOS 固定构建 `aarch64-apple-darwin`,清单只登记 `darwin-aarch64`;`targetRuntime('universal-apple-darwin')` 必须失败关闭。若将来恢复 Intel 或 universal,须先在 staging 配齐同版本的双架构 Node、Codex 原生包、code-mode host 和 zsh,按运行切片选择并分别校验,Intel 真机验收后才能登记 `darwin-x86_64`;不能只合并主程序或复用构建宿主的单架构资源。 ## release 冷备空间不足会把生产留在维护态 @@ -253,14 +253,6 @@ Copy Artifact 插件在**非 SYSTEM 认证**下按「认证用户」判权:只 AGC macOS 发布入口一度传入 `--no-sign`(目的是绕过没有 Apple 证书的代码签名),结果 Tauri 打印 `Warn Updater signing is skipped due to --no-sign flag.`,产物只有 `*​.app.tar.gz` 而没有 `.sig`,发布入口按设计在「缺少更新包签名」处失败关闭(2026-09-20 首次 Jenkins 实跑命中)。正确做法是不传 `--no-sign`,改为剥离 `APPLE_*` 凭据让 Tauri 跳过 Apple 签名——minisign 更新包签名与 Apple 代码签名这两个开关在 Tauri 里并不独立。Apple 签名状态要按 `codesign -dv` 实测记录,不能硬编码。 -## 复用 workspace 的构建必须显式清理本次要写的产物 - -Jenkins workspace 跨构建保留:上一轮失败留下的同名 `陶泥儿__universal.dmg` 会让 `hdiutil create` 以「文件已经存在」失败,而上一轮遗留的 `*​.app.tar.gz.sig` 更危险——本轮即使没签出签名,验签门禁也会读到旧签名而误判通过。构建入口必须在构建前删除本次将写出的确切路径(更新包、签名、同版本 DMG 及其校验文件、`latest.json`、`release-notes.txt`),`hdiutil create` 同时用 `-ov`,让「归档里的产物来自本次构建」成为结构性事实而非假设。 - -## AGC macOS 单次构建耗时集中在主 crate 重复编译 - -AGC 主 crate(`genarrative_ai_game_creator_shell`)单架构 codegen 约 15–20 分钟,而每次 Tauri 构建都会重新生成前端 `dist`,`build.rs` 对 `dist` 目录的 `rerun-if-changed` 因此每次都判定变化,导致两个架构各重编一次主 crate。实测:`CARGO_BUILD_JOBS=4` 时首次 Jenkins 构建 78 分钟,提到 6 后为 41–43 分钟且成功;依赖 crate 走 sccache 与 target 缓存,首轮 0 命中属预期。剩余优化空间在「不必要地重建 dist」这一层,需单独设计(例如按内容摘要决定是否重跑前端构建),不要在发布入口里用假缓存换取速度。 - ## Godot C++ 扩展构建与对象生命周期 - 原生引导通过官方 `godot-cpp` 管理 Variant、String 和 Ref,不自行维护 ABI 存储。Godot 类型必须在扩展终止回调内释放,不能依赖 DLL 静态对象析构;桥节点可能已经退出,应按实例 ID 核验存活再回调。 @@ -290,10 +282,6 @@ AGC 主 crate(`genarrative_ai_game_creator_shell`)单架构 codegen 约 15 - **处理(现行口径)**:生成目录一律成对登记 `.prettierignore` + eslint `ignorePatterns`;改 `export_to` 时同步改这两处,并用 `cargo test --locked -p shared-contracts --features ts-bindings export_bindings --manifest-path server-rs/Cargo.toml` 后 `git diff` 为空来验证幂等。 - **易错点**:旧的 `apps/ai-game-creator-shell/src/contracts/generated/` 目录下的同名文件不会自动删除,换目录后必须显式删除旧文件,否则会出现「两个同名 union,改动只落在一个目录」的假绿。 -## universal 主程序必须配套双架构原生依赖 - -AGC macOS 主程序可合并为 universal,但 Codex 原生包的 `codex-package.json`、code-mode host 和 zsh 仍有架构身份。两套包应各自保留上游布局与摘要,放入 `coding-agent/mac-native/darwin-arm64/`、`darwin-x64/`,由正在运行的主程序切片选择;不能只把主程序用 lipo 合并后复用最后一次构建的单架构资源。Tauri universal 两次 Cargo 构建共用 staging,每次都必须 stage 完整的两套资源。发布清单两个平台键同 URL/签名,只在 universal 产物上成立;Rosetta 隔离 smoke 不代替 Intel 真机验收。 - ## 生成草稿与异步展示边界必须按身份隔离 非模态生成浮层切换占位时按 draftId 分实例,卸载保留未提交/失败草稿,成功提交不再复活草稿;旧项目占位不存在时丢弃其保存回调。失败重试保留原请求输入和引用身份,引用失效不能静默过滤;修改已绑定输入须明确另起请求,不伪装成原请求重试。 @@ -497,8 +485,6 @@ AGC 的 Cocos 能力来自随客户端分发的 `agc-cocos-editor` 内置插件 - 同步 Tauri command 内的权限校验、会话目录扫描和锁等待会占用窗口线程。DirectProject 必须跳过专业 Agent 历史;开发入口仍需要的对话读取在 blocking worker 中执行,权限校验保留在同一后台闭包内。 - 排障测量完整 IPC 链路并同步采样原生窗口响应。某命令的调用端耗时可能包含前面的主线程队列等待,不能仅凭调用端耗时认定插件启动或上游请求本身缓慢。 -> 当前口径:本文件保留可复用的排障经验;历史条目的旧路由、旧版本和已删除文档仅作根因背景,不得据此恢复退役入口。当前命令、路由和 schema 以代码与 `docs/README.md` 为准。 - ## 2026-09-12 画布滚轮要按 DOM 归属判定,portal 出去的浮层不能把滚轮让给画布 - **现象**:资源卡「快速编辑」里用 `@` 开出「选择素材」浮层后,在选择器列表上滚鼠标滚轮,列表自己在滚,背后的资源画布也一起平移 / 缩放(用户口语:「滚轮还是回滚到画布上」)。 @@ -521,14 +507,6 @@ AGC 的 Cocos 能力来自随客户端分发的 `agc-cocos-editor` 内置插件 - **排查顺序**:先看 debug 响应里 `output` 是否为空、是否同时有工具调用;不要当成模型拒调工具或策划提示词错误。 - **验证**:`platform-llm` 流式夹具覆盖「空 completed + 增量 item」能拿出可回放 `responses_output`,以及 completed 顶层 `output`。 -## 2026-09-05 Planning V2 审批和续跑必须等过项目锁瞬时争用 - -- **现象**:策划 V2 在 GDD 审批提交修改意见后提示 `项目正在被其他写操作占用:...\\.agent\\project.lock`,聊天区再出现 `项目总控 Agent 执行失败,请稍后重试`。 -- **原因**:V1 `decide_plan_gdd_at` / hydrate 已按完整或短窗口等待项目锁。V2 的审批、回合启动、策略落盘和 hydrate 直接 `acquire_project_write_lock`,与 GUI 重灌、刚结束的审批写盘或后台扫描撞车就立刻失败。修订后续跑走 `continue_planning_session_v2`,失败被前端写进总控错误位。这不是锁没释放,也不是 UAC。 -- **处理**:一次性用户意图(审批、回合启动、策略落盘、失败投影、GDD 认领)走完整等待窗口;V2 hydrate 走短窗口。前端 V2 hydrate 对锁争用保持上一份状态,不把瞬时占用画进审批卡。 -- **排查顺序**:先看错误是否点名 `project.lock` 且发生在提交修改意见或立刻续跑;不要当成总控 Runtime 或 Provider 失败。锁文件在失败后通常已被 Drop 删掉,现场缺文件不否定争用。 -- **验证**:Rust 定向覆盖 V2 审批、修订续跑和 hydrate 等过短暂占用的项目锁。 - ## 2026-09-05 新建项目锁不要把继承 DACL 当成 UAC 事件 - **现象**:策划 V2 在 GDD 审批提交修改意见时弹出权限窗口,目标是 `Documents\Genarrative GameAgent\gameagent-*\.agent\project.lock`,随后 `AGC ACL 提权修复未成功(exit code Some(1))`。 @@ -537,19 +515,6 @@ AGC 的 Cocos 能力来自随客户端分发的 `agc-cocos-editor` 内置插件 - **排查顺序**:先看错误是否点名 `project.lock` 且含 `禁止继承` / `exit code Some(1)`;不要当成策划 V2 或 Provider 鉴权问题。含空格的 `Genarrative GameAgent` 项目根是复现条件,不是业务失败。 - **验证**:Windows 定向覆盖含空格项目根取锁、新锁已满足私有 DACL、Drop 删除,以及提权参数把带空格路径保留为一个 quoted token。 -## 2026-09-04 Planning V2 不可变 GDD 创建后不能当没提交 - -- **现象**:`gdd.vN.json` 已 create-only 落盘,但 index / Markdown / conversation / session 任一步失败后,session 停在 `provider_failed` 且 `current_artifact_version` 仍指向旧版本。重试会用新 UUID/时间戳再写同一版本号,命中“已存在且内容不同”。 -- **处理**:把该文件当作提交点。恢复时只认领 session 指针的下一个连续版本并补投影,不要删文件,也不要重建 GDD 身份。hydrate 和同一回合重试都必须走这条认领路径。 -- **排查顺序**:先看 `.agent/planning-v2/gdd.vN.json` 是否已存在、再看 `session.json` 的 `currentArtifactVersion` 是否落后;不要为了重试去覆盖不可变文件。 -- **验证**:孤儿文件重试后仍是同一 `gddId`/vN,hydrate 能看到当前产物。 - -## 2026-09-04 DeepSeek thinking 不能与 tool_choice=required 同时使用 - -- **现象**:DeepSeek V4(默认 thinking)对 `tool_choice=required` 或指定函数返回 HTTP 400:`Thinking mode does not support this tool_choice`。 -- **处理**:策划 V2 协议工具固定 `tool_choice=auto`,由 Runtime 校验必须恰好调用 `plan_ask_question` 或 `plan_submit_gdd`。不要按模型名分支,也不要用 required 强行出稿。 -- **验证**:请求体含 `tools` 且 `tool_choice=auto`;无工具调用时走既有非法输出重试。 - ## 2026-09-09 常用设置跨文件保存失败 主配置与 local overlay 的单文件原子写入不能保证整体成功;覆盖层写入失败会留下混合配置。保存前先序列化全部变更,多文件保存保留原内容,错误时逆序恢复并报告回滚失败;单文件保持原写入路径,成功后不回读、不触发外部诊断。此回滚仅处理可捕获错误,不承诺进程崩溃下的事务恢复。 @@ -622,21 +587,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - **处理**:项目 `.agent`、Agent DB 和公共 event 继续只写安全摘要;额外在应用私有配置目录的 `diagnostics/provider-reconciliation//.json` 保存本次响应、tool calls 和校验错误,供本机人工排障。该文件不参与恢复/重试、不复制到项目、不进入 Git,单文件限制 1 MiB,写入失败不改变 reconciliation 语义。 - **排查顺序**:先读 Runtime 状态里的 `localDiagnostic` 相对引用,再在应用私有目录读取诊断,核对 requestId、requestSlot、tool name 和失败 pointer;不要为了取得原文而放宽 handoff 的安全门。 -## 2026-08-27 阶段判定不能在持锁的 Provider builder 中再次获取项目锁 - -- **现象**:GDD 修订取证阶段新增后,重新启动策划时前两步表面成功,但父 Supervisor 在收到 `project-planning` 回执、生成下一轮工具计划时失败:`项目正在被其他写操作占用:$PROJECT_ROOT\\.agent\\project.lock`。 -- **原因**:`provider_tool_plan` 在构建请求前已持有 `.agent/project.lock`;`plan_root_supervisor_stage_at` 又调用会自行取锁的 Acceptance Evidence 包装入口。同一进程的文件锁不可重入,持锁调用被误判为外部竞争,等待约 10 秒后失败。问题与 Provider、代理端口或 GDD 内容无关。 -- **处理**:所有需要一致快照的状态读取保留在项目锁内;阶段判定提供明确的 `*_locked` 内部入口,外层入口仅供未持锁调用方取得一次锁。Provider builder 显式接收并校验当前锁后调用 locked 阶段判定,不引入可重入锁,也不移除 Acceptance Evidence 门禁。 -- **排查顺序**:先看失败 Run 的事件顺序是否为 `delegate receipt ready → 生成工具计划 → 阶段判定项目锁失败`,再检查调用方是否已持有 Provider plan project lock;不要因为错误文案包含“其他写操作”就先扩大锁等待或放宽 Provider usage。 -- **验证**:`cargo check`、`cargo fmt --check`、planning submit 68 passed、Provider request builder 17 passed;阶段测试同时覆盖未持锁包装入口和持锁 locked 入口。 - -## 2026-08-26 GDD 新版本提交后不能沿用“已有委派”工具面 - -- **现象**:`plan_root_supervisor_stage_at` 只按是否存在 delivery 判定 `Delegated`。用户修订产生的新 GDD 仍未完成当前根 Run 的 `file.read → agent.acceptance_update` 取证时,模型会看到 `agent.delegate`,可能重复派发同一条策划链。 -- **原因**:自然语言 playbook 已规定“证据不足先取证、用户修改后才返工”,但阶段工具白名单没有把这条 durable 状态固化。 -- **处理**:阶段判定复用现有 acceptance gate 的 GDD/session/delivery/graph identity 检查,增加无副作用的 `AwaitingAcceptanceEvidence` 阶段;`PLAN_PROVIDER_USAGE_DEFERRED` 保持 fail-closed,不通过放宽 Provider 使用量门禁解决。 -- **排查顺序**:先看最新 `gdd.vN.json`、`session.latestSubmittedRef`、delivery 是否 `ClaimedByParent`,再看 Acceptance Graph 是否 `NeedsEvidence`;若仍可见 `agent.delegate`,优先检查 plan root 阶段快照,而不是修改 acceptance gate 或 Provider 门禁。 - ## 2026-08-15 把校验往链路前面挪,改的不是严格程度而是作用域 - 现象:CI 全量 5 条失败,看上去毫不相干(两条 Goal 续跑停在 `needs-reconciliation`、一条交接用例断言错误文案、一条恢复用例把不可读 state 的错误抛了出来、一条 Linux-only 用例错误码对不上),实际只有 3 个根因,且三者是**同一个形状**:新增或既有的检查被放在了链路更靠前的位置,于是它的语义作用域被悄悄放大或提前,而不是「变严」。 @@ -657,14 +607,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 验证:改后 `cargo check --offline --all-targets` 通过、`cargo fmt --check` 通过、`project::manifest` 与 godot 相关定向测试 65 passed / 0 failed。判断「是不是本次合并引入」的通用手法:`git diff origin/master -- ` 为空即说明该文件就是 master 原样,问题不在合并。 - 关联:`apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs`;`rust-toolchain.toml`;master 提交 `578f8019f`。 -## 2026-08-14 planning sidecar 必须区分“可读”与“可写”,canonical bytes 也不等于 typed 指纹 - -- 现象:如果为了保护 Runtime-owned 事实,直接把 `.agent/planning/**` 加进现有 private-control **读**门,planning 子 Agent 的 `file.read` / `file.list` 会一起失败;反过来若只依赖工具面约束,通用 `file.write`、`file.patch`、`file.delete`、`project.patchset` 或 checkpoint restore 仍可能覆盖 GDD/session。另一个常见误判是把“能反序列化且语义相同”的 JSON 当成已提交文件,导致尾换行、字段重排或重复键绕过不可变事实的字节身份。 -- 原因:`.agent/planning/**` 是 Runtime 专用 durable sidecar,但 planning Agent 需要只读观察;`game/fast_gdd.md` 又是 Runtime renderer 的人读投影,二者都不能复用“读写一体”的旧 private path 判据。存储 bytes 与 typed fingerprint 是两个门:前者约束磁盘 canonical serialization(Rust struct 字段顺序、compact UTF-8、无 BOM/尾空白、无重复键),后者约束 domain-separated 业务 payload 的完整性;只过其中一门都不能视为 authoritative。 -- 处理:保持现有 private-control **读**门不含 planning,新增只挡写 predicate;所有通用 mutation 与 restore 路径在推进 revision 前先拒绝 `.agent/planning/**` / `game/fast_gdd.md`,专用 writer 再校验 `project-planning + agent-delegate + standard + project-supervisor` 身份。GDD/index 等不可变文件走项目锁、同目录临时文件、`sync_all`、回读与 no-replace 发布;相同 canonical bytes 才是 replay,任何其它内容都是 identity conflict。session 只保留一个 `.session.json.previous`,primary 损坏时 fail closed,不得拿 previous 猜测新旧。 -- 验证:先用 planning Agent 的只读 action 验证 sidecar 可列出/读取,再逐项证明 `file.write`、`file.patch`、`file.delete`、`project.patchset` 与 checkpoint restore 均拒绝;storage 测试应覆盖 duplicate key、BOM/尾空白、字段顺序、symlink/目录/硬链接、create-only replay/conflict、session 缺 primary 提升、primary 损坏和合法 successor。第 9.1 节 golden vector 当前为 3857 bytes / `sha256-serde-json-v2:a59856de7ef134cf2f49c4dedd2ba10ae4ab2340a9634d402eb792b6ee5458f0`。 -- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs`、`apps/ai-game-creator-shell/src-tauri/src/project/filesystem.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/file_ops.rs`、`apps/ai-game-creator-shell/src-tauri/src/patchset.rs`、`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 8.3~10.2 节。 - ## 2026-08-12 在 autonomous-game-build 下试图向用户提问,会让整条工作流永久瘫痪 - 现象:给自主构建链路加「问用户一句」的需求时,最自然的两个想法——让 DAG 节点自己问、或让父 Supervisor 代问——**都不成立**,而且第二个的失败方式是灾难性的。 @@ -721,7 +663,7 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 身份与恢复:只允许完整 GUI / CLI 16 任务 DAG 的 `agent-ready-task-scheduler` 确定性直接 child、当前活跃根和正确 parent/binding;错误 source、delegated run、历史/终态根、非当前 root、跨 Agent/run 一律失败关闭。owner 再次 mutation 必须令旧凭证失效;相同身份恢复时可按当前磁盘事实确定性重验。 - 并发恢复补充:自主根任务 journal 写入后建立或重建 completion contract 时,初始 manifest reset 与 continuation reconciliation reset 不能重新使用 fail-fast 项目锁。异步 child finalization 可以合法插入两次取锁之间,使已入 journal 的新根被误记为 `completion-contract-failed`。这两条 reset 必须使用现有有界等待项目锁,超时仍失败关闭;只验证 scheduler 合同的测试应预占 child Runtime lane,不能真实启动后台 worker 后再手工改 manifest。确定性回归要显式持锁,分别证明初始合同与 continuation 合同等待释放后成功落盘。 - 验证:夹具必须从 `init_local_game_project_at` 开始,先断言无 `package.json` 且占位入口 smoke 失败,再证明产物不齐阻断、齐全后内部验证通过、无 smoke trace、再次 mutation 失效;另覆盖四个 owner 路径矩阵、错误身份、恢复、跨 run 凭证、`art-director` 有/无 Key、动态美术借凭证拒绝、`code-prototype` project.verify-only 阻断和试玩 executor 身份。 -- 关联:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`。 +- 关联:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。 ## UI 设计 State 的 strict JSON round-trip 不能混用两种浮点序列化表示(2026-08-19) @@ -2054,38 +1996,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 验证:浏览器创作 Tab 中每张开放态卡都应显示标题、描述和后台契约 `mudPointCost` 数量经前端格式化后的泥点消耗文案;旧契约缺字段时兜底显示 `10泥点数`;`npm test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx -t "creation start card renders reference-aligned banner and template metadata"` 应通过。 - 关联:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/index.css`、`src/components/custom-world-home/CustomWorldCreationHub.test.tsx`。 -## 创作首屏开放态卡片不要再显示左上状态标签 - -- 现象:创作 Tab 的开放态玩法卡左上角会重复显示“可创建”或“可创作”,视觉上比其它状态更吵,还会和封面图抢注意力。 -- 原因:卡片渲染层默认把 `badge` 当成所有状态都要展示的左上角标签,没有区分开放态与非开放态。 -- 处理:开放态卡片不渲染左上标签,仅保留标题、描述和右下角消耗信息;`敬请期待`、`即将开放` 等非开放态标签继续保留。 -- 验证:创作首屏 HTML 中不应包含 `可创建` / `可创作`,但仍应包含 `即将开放` 等非开放态状态。 -- 关联:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 - -## 发现 / 创作 / 草稿页不要把根内容区再包成全局卡片壳 - -- 现象:发现页、创作页或草稿页根区一旦套回 `platform-page-stage`,页面边缘会立刻变得更厚,频道标签、列表和模板卡的横向空间都被挤窄,看起来像回到了旧全局卡片壳。 -- 原因:`platform-page-stage` 本身是全局内容卡片壳,适合推荐页、我的页和其它页面,但这三页已经有自己的视觉结构;草稿页顶部筛选若继续用旧 `platform-tab`,还会和发现页频道标签不一致。 -- 处理:这三页的根内容区只保留 `platform-remap-surface`,不要再加 `platform-page-stage`;草稿页顶部筛选复用发现页的 `platform-mobile-home-channel` 与 `platform-mobile-home-channel--active`。 -- 验证:浏览器里这三页的根区应仍保留 `platform-remap-surface`,但不再出现 `platform-page-stage`;草稿页顶部筛选样式应和发现页频道标签一致。 -- 关联:`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/custom-world-home/CustomWorldWorkTabs.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/index.css`。 - -## 统一创作壳现在自己负责页面滚动和四条入口外壳 - -- 现象:统一创作页最初只包住拼图、抓大鹅和敲木鱼的工作台内容,跳一跳仍然保留独立工作台壳,页面级滚动职责也散落在平台入口 motion wrapper 里,导致移动端不同入口的可见外壳不一致。 -- 原因:`UnifiedCreationPage` 只做了标题和隐藏契约,入口壳还在各自工作台里保留 `platform-remap-surface` / `overflow-y-auto`,`jump-hop` 也没进入统一 spec。 -- 处理:把 `jump-hop` 纳入 `unifiedCreationSpec`,让 `UnifiedCreationPage` 自己承担页面级滚动与统一标题栏;`JumpHopCreationWorkspace`、`WoodenFishCreationWorkspace` 补 `unifiedChrome` / `showBackButton`,平台壳不再给这几条统一入口套额外滚动壳。 -- 验证:`npm run test -- src/components/unified-creation/unifiedCreationSpecs.test.ts src/components/unified-creation/UnifiedCreationPage.test.tsx src/components/unified-creation/UnifiedGenerationPage.test.tsx src/components/unified-creation/workspaces/JumpHopCreationWorkspace.test.tsx src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx` 通过后,`/creation/puzzle`、`/creation/match3d`、`/creation/jump-hop`、`/creation/wooden-fish` 都应由同一套统一创作页外壳承载。 -- 关联:`src/components/unified-creation/UnifiedCreationPage.tsx`、`src/components/unified-creation/unifiedCreationSpecs.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`。 - -## 统一创作编排层不要再让平台壳直挂旧工作台 - -- 现象:平台入口壳已经切到统一创作外壳,但源码里仍直接 lazy import 并渲染四个旧工作台分支,看起来还是四套入口编排。 -- 原因:统一创作页只收口了可见外壳,入口层没有再抽一层统一创作编排组件,导致平台壳依旧要认识各玩法旧工作台。 -- 处理:新增 `UnifiedCreationWorkspace`,由它内部按 `playId` 选择真实工作台;平台壳只依赖这一层,不再直接挂旧工作台分支。旧工作台已迁入 `src/components/unified-creation/workspaces/`,不再是入口编排事实源。 -- 验证:`PlatformEntryFlowShellImpl.tsx` 中不应再出现四个旧工作台的入口渲染分支,创作 Tab 与 `/creation/` 仍能正常进入对应工作台。 -- 关联:`src/components/unified-creation/UnifiedCreationWorkspace.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 - ## Jenkinsfile 开头不能带 UTF-8 BOM - 现象:`Genarrative-Stdb-Module-Publish` 在 `Pipeline script from SCM` 读取 `jenkins/Jenkinsfile.production-stdb-module-publish` 后,流水线还未进入任何 stage 就失败,报 `java.lang.NoSuchMethodError: No such DSL method 'pipeline'`,堆栈位置是 `WorkflowScript.run(WorkflowScript:1)`。 @@ -2485,13 +2395,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 验证:`cargo test -p module-auth projection --manifest-path server-rs/Cargo.toml`、`cargo test -p module-auth phone --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server phone_login_reuses_existing_user_for_same_phone_number --manifest-path server-rs/Cargo.toml`。 - 关联:`server-rs/crates/module-auth/src/lib.rs`、`server-rs/crates/spacetime-module/src/auth/procedures.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。 -## 认证快照表和旧 procedure 已删除 - -- 现象:有些旧代码和生成 bindings 里还会残留 `get_auth_store_snapshot`、`upsert_auth_store_snapshot`、`import_auth_store_snapshot`、`import_auth_store_snapshot_json`、`export_auth_store_snapshot_from_tables`,或者把 `auth-store.json` 误当成认证恢复源。 -- 原因:认证恢复已经彻底收口到 SpacetimeDB 正式表和 `module-auth` typed projection;本地文件持久化或 JSON 快照会和正式表投影打架,SpacetimeDB 不可用时还可能把旧快照回灌到用户表。 -- 处理:先用 `npm run spacetime:generate` 刷新 bindings,确认 `server-rs/crates/spacetime-client/src/module_bindings.rs` 里已没有旧 snapshot table / procedure 导出;`module-auth` 只保留内存态和 projection view,不再写本地快照文件。 -- 验证:`cargo check -p module-auth --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`、`npm run check:encoding`。 - ## 抓大鹅生成页只显示服务暂不可用先查 reason 和外部服务配置 - 现象:点击生成抓大鹅草稿后,页面只提示“服务暂不可用”,或者本地 `npm run dev:api-server` 看似启动但生成接口不可用。 @@ -3037,14 +2940,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 验证:前端测试先点开模板确认面板,再 rerender 到另一 session,断言确认面板消失。 - 关联:`src/components/creative-agent/CreativeAgentWorkspace.tsx`、`src/components/creative-agent/CreativeAgentWorkspace.test.tsx`。 -## 创作 Tab 语义迁移后,旧“新建作品”测试要改看智能创作首页 - -- 现象:把 `create` 从旧创作中心切到 `CreativeAgentHome` 后,旧测试仍尝试在创作页找“新建作品”类型卡,导致用例失败或定位不到元素。 -- 原因:产品语义已经变成“创作 = 智能创作首页,草稿 = 旧作品架”,但测试夹具和 helper 还沿用旧入口。 -- 处理:把这类测试改成验证智能创作首页、快捷胶囊、抽屉与草稿 Tab;同时给 `useRpgEntryLibraryDetail` 这类恢复路径补上 `setPlatformTabToDraft`。 -- 验证:定向 `vitest`、`eslint`、`typecheck`、`check:encoding` 都通过。 -- 关联:`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`src/components/rpg-entry/useRpgEntryAgentDraftRestore.test.tsx`、`src/components/rpg-entry/useRpgEntryLibraryDetail.ts`。 - ## server-rs 默认 cargo build 不能等同于构建 SpacetimeDB 模块 - 现象:在 `server-rs` 下无参数 `cargo build` 期望同时构建 `spacetime-module`,导致链接或构建范围误判。 @@ -3637,14 +3532,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 验证:移动端视口检查视频 `rect` 应覆盖整个视口,`paused` 应最终变为 `false`,`currentTime` 应持续前进。 - 关联:`src/components/GenerationProgressHero.tsx`、`docs/【玩法创作】生成页圆环布局口径-2026-05-23.md`。 -## 跳一跳结果页直达时不要把恢复面板当成空白页 - -- 现象:浏览器直接打开 `/creation/jump-hop/result`,如果没有 `sessionId`、`profileId`、`draftId` 或 `workId`,页面以前会看起来像空白,容易误判成结果页坏了。 -- 原因:跳一跳结果页恢复原先只盯 `jumpHopSession.draft`,没有把“缺恢复信息”明确兜成可见恢复面板;直达结果页时也没有优先用 `profileId -> getWorkDetail` 补回完整作品。 -- 处理:`PlatformEntryFlowShellImpl` 的跳一跳恢复逻辑改成先尝试 `profileId -> getWorkDetail`,再尝试 `sessionId -> getSession`;两者都没有时显示 `跳一跳草稿未恢复` 和 `返回创作`,不再留空白页。 -- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct jump hop result route"`,并手测 `/creation/jump-hop/result` 与 `/creation/jump-hop/result?profileId=` 两种情况。 -- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 - 2026-05-24 补充:`GenerationPageBackdrop` 不要通过 portal 挂到 `document.body`。body 级 fixed 背景会逃离生成页自己的 stacking context,即使业务内容有局部 `z-10`,真实浏览器里也可能把整页 UI 压住。背景视频应作为生成页根容器子节点保留 `fixed inset-0 z-0`,生成页内容保持 `relative z-10`;相关测试应同时断言背景容器低层级、生成页根容器高层级,以及视频节点仍在生成页 DOM 内部。视觉调整时还要记住:空心圆环的中心块要抽掉,时间卡与总进度标题都应缩小,不要让生成页再回到“纯色底 + 大字号说明卡”的状态。顶部返回和右上状态也不能沿用 `text-lg` / `sm:text-2xl` 这类展示级字号;当前步骤名、步骤状态和底部玩法信息标题要维持普通 UI 字号档位,优先保持 `text-xs` 到 `text-sm` 区间。 2026-05-24 补充:生成页“预计等待 / 已耗时”卡片本身已经有标签,传给 `GenerationProgressHero` 的值只能是纯时间,例如 `4 分钟`、`1 分 15 秒`,不要再拼接“预计还需”或“已耗时”;两张时间卡也要和当前步骤卡一样保持半透明。拼图总进度初始帧必须允许显示 `0%`,不要再用 `Math.max(1, nextProgress)` 之类的保护把启动态抬到 `1%`。 @@ -3735,14 +3622,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 验证:`cargo test -p module-auth --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml work_author`、`npm run test -- scripts/rebind-orphan-work-owners.test.ts`。 - 关联:`server-rs/crates/api-server/src/work_author.rs`、`server-rs/crates/module-auth/src/domain.rs`、`scripts/rebind-orphan-work-owners.mjs`。 -## 访客推荐页上下滑不要绑定登录态 - -- 现象:访客模式进入移动端推荐页后,推荐内容可展示和点击底部“下一个”,但在作品信息区域上下滑不会切换推荐作品,表现为推荐页不能上下滑动。 -- 原因:推荐页滑动切换逻辑 `beginRecommendDrag(...)` 误把 `isAuthenticated` 作为启用条件;访客态虽然允许浏览和通过底部按钮切换,却无法触发同一套拖拽切换。 -- 处理:推荐页拖拽只校验当前是否有作品、多作品可切换以及是否正在提交动画,不再要求登录;登录态相关操作仍由点赞、改造等按钮自身权限控制。 -- 验证:`npx vitest run src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 覆盖访客态纵向滑动不弹登录且触发下一条推荐。 -- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。 - ## Windows junction worktree 下 Vitest 定向路径失败先切真实路径 - 现象:在 Windows junction 或映射 worktree 中运行前端测试时,Vitest 可能把同一文件解析为另一盘符路径,误报文件不存在。 @@ -5433,12 +5312,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 部分旧包补充:rollback 的规范图/背景图必须保存旧字节与旧 manifest entry,不能把这两项缺失隐式当成空内容;显式 `regenerate` 因此只在这两项可信可回滚时开放。历史主图集、私有回执、公开清单或 canonical 切片可以缺失,但八个严格路径与受管顶层 asset identity 必须逐项冻结其真实 `Present/Some` 或 `Missing/None` 状态,补偿也必须恢复相同存在性。不要因为旧美术包缺切片而阻断重生成,也不要把本轮新建的严格文件误记成旧文件。 - 对话扫描与 claim 补充:历史中出现 `User A / User B / Assistant B` 时,B 已回答不代表 A 已回答,扫描必须继续寻找 A。成功 Direct 回复在 Rust 返回前已经落盘,前端冗余 append 失败不能据此重跑;普通错误回复的显式落盘失败时,恢复 claim 要保持到 React fallback writer 的同一 messageId append 明确收敛。writer 成功或明确失败后才释放;失败路径要停止该消息的自动迟到重试,再由显式重新加载对话复用原 stable turn。终态后及时删除 claim,避免 Set 无界增长。 -## GDD 历史审批回执误触发当前恢复提示(2026-08-27) - -- 现象:修改 GDD 后新版本标题和内容已正确落盘,但审批卡一直显示“审批状态正在恢复”。 -- 原因:`approval pending` 是当前 lineage 最新 GDD 的单例投影;恢复扫描却让每个历史 receipt 都拿它做 identity 比对。旧 receipt 与新 pending 不同并不表示损坏。 -- 处理:历史 receipt 只修复自身投影;只有最新 GDD 的 receipt 才能校验、更新或清理当前 approval pending。不要在前端隐藏 `recoveryPending`,也不要取消最新版本的 identity fail-closed 检查。 - ## Native shell CI 不能在测试阶段重新解析 Cargo registry(2026-08-26) - 现象:原生壳 job 的依赖预取成功后,AGC 检查仍在 `platform-llm` 测试阶段重新更新 registry index,并因 `symphonia` 下载的 TLS EOF 失败。 @@ -5648,23 +5521,12 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - 守卫三层:Rust `asset_category_mapping_covers_every_kind` 穷举断言(旧的 `"UI" → unclassified` 断言正是这条 bug 的反向加固,已删);`ui_editor/resource_bridge.rs` 的 `bridge_is_idempotent_and_installs_source_image` 走真实生产函数 → 真实写入 → 断言落盘 `category`;TS `parseGameCreationAppAssetKind` / `console.warn` 用例钉住"非 canonical 值收口 + 留痕原值"。 - 关联:`server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs`、`packages/shared/src/contracts/gameCreationApp.ts`、`apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs`、`apps/ai-game-creator-shell/tests/assetKind.test.ts`。 -## AGC 资源搜索栏改成「临时叫出」的浮层,工具条带与它的下移逻辑一并撤掉(2026-09-11) +## AGC 资源筛选浮层不能遮住栏目操作 -- 现象:资源搜索框与栏目标题栏在真机上仍然重合;分页态标题栏右端的「资源总览」(回到资源总览)按钮**点不动**。 -- 成因(两层,同一处):常驻工具条带 `.game-resource-book-tools` 是 `position: absolute; top: 0; left/right: 0; z-index: 40` 的**横跨整行**盒子——只有搜索框、筛选条是不透明内容,其余透明区域照样接收点击;它正好盖在栏目标题栏(`z-index: 30`)那条带上,把「资源总览」按钮的命中区域整段吃掉。而管理区 `padding-top: var(--game-resource-book-tools-height)` 与画本场景按同一变量 `inset` 下移,两处都只为"给搜索框腾一条带"存在,带子本身仍与标题栏争同一条水平带。 -- 处理(用户指定方案):搜索条不常驻。常态只在右下角缩放 Dock 末尾留一颗搜索按钮(`aria-label="搜索资源"`),`Ctrl/Cmd+F`(macOS `Cmd+F`)或点它把浮层叫出来;关闭复用美术画布的 `useImageCanvasFloatingOptionDismiss`(Esc / 点外部,Escape 在 document 上截断,不会连带清画布选中)。随后撤掉工具条带本身:管理区 `padding-top`、场景 `inset` 变量、`resourceBookToolsRef` 与 ResizeObserver 测高 effect、`resourceCanvasSceneSize()`、缩放与滚轮锚点里减掉工具条带的两处算式全部删除,`resourceBookSceneSize` 重新等于管理区盒子(场景盒子 = 可排布盒子,更自洽)。 -- 几何口径(改这些数字前先读):搜索浮层 `position: absolute; right: 14px; bottom: 58px; width: min(320px, calc(100% - 28px))`——`58px` = Dock 的 `14px` + Dock 高度 `34px` + 10px 间隙,且**刻意不写 `top`**,所以它永远落在管理区底部、不可能压到标题栏那条带;状态提示与附件失败条的浮层容器 `.game-resource-book-notices` 用 `top: 58px`(= 标题栏 `min-height: 42px` + 16px)落在标题栏之下。 -- 浮层必须钉住的两条:① 提示层容器整层 `pointer-events: none`、只有提示条自己 `pointer-events: auto`(横跨整行的透明盒子吃点击正是上面那条 bug);② 搜索条件不随浮层收起而清除(`searchText` 是画布状态,`type=search` 的原生 Esc 清空已被 `preventDefault` 挡掉),"当前有搜索"由 Dock 按钮的 `.is-active` 高亮表达。 -- 门禁为什么抓不到:jsdom 不做布局命中判定,`getByLabelText` 也不校验可见性;可见性与重叠只能钉声明(`tests/appSurface/project-development.suite.ts` 的 `styleRuleBody` 口径),入口另用真实 DOM 用例覆盖——`tests/appSurface/harness.ts` 的 `openResourceSearch()` 先按产品入口打开浮层,浮层没打开时 `getByLabelText` 直接抛错,堵掉"元素在但点不到"的假绿。 -- 关联:`apps/ai-game-creator-shell/src/styles.css`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/tests/appSurface/harness.ts`、`src/components/image-editor/useImageCanvasFloatingOptionDismiss.ts`。 -- 后续(2026-09-13):搜索浮层与筛选浮层合并成唯一的 `.game-resource-filter-panel`(关键词 / 所在区域 / 自定义标签),Dock 只留放大镜按钮、`.game-resource-search` 已删除,关闭改用面板自己的「点外部 + Esc」;条目里的 `right: 14px` + `bottom: 58px`(= Dock 14px + 34px + 10px 间隙、刻意不写 `top`)与「条件不随浮层收起而清除」两条口径原样沿用,harness 的 `openResourceSearch()` 现名 `openResourceFilterPanel()`,见 `decision-log.md` 2026-09-13 一条。 - -## AGC 画布侧分类筛选条已按用户要求移除,共享筛选条组件保留(2026-09-11) - -- 用户口径:「这个筛选功能有点鸡肋(悬浮文字这几个),给他去掉吧」——指资源画布顶部那排分类 chip(全部 / UI 交互 / 角色与对象 / 场景与环境 / 音频 / 文档 / 待归类)与同一排的画布标签 chip。 -- 处理:只移除画布上的这一处 `PlatformResourceFilterBar` 用法,**共享组件本身保留**(`@` 素材面板 `ImageCanvasProjectAssetPickerDialog` 与参考图弹窗仍在用)。随它一起删掉只为它存在的画布筛选轴:`resourceCanvasCategoryFilters` / `showResourceCategoryFilter` / `resourceCanvasActiveTags` / `resourceTagLibrary`,以及 `visibleResources` 里两个此后恒真的条件——画布可见资源只由搜索框收窄。 -- 不要顺手改分区:栏目大纲、栏目标题栏、`categoryOrder`、`projectResourcesByCategory` 与「资源总览」入口是另一回事,本次一行未动。将来若要恢复分类筛选,应重新设计入口,而不是把这排横跨画布顶部的 chip 行加回来。 -- 关联:`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`packages/shared/src/components/PlatformResourceFilterBar.tsx`。 +- **现象与原因**:横跨画布的透明浮层仍会接收点击,可能让栏目标题栏的「资源总览」看得见却点不动;jsdom 不做真实布局命中判定,单靠元素存在性测试抓不到重叠。 +- **现行口径**:右下角 Dock 打开唯一的 `.game-resource-filter-panel`,提供关键词、所在区域和自定义标签筛选;面板贴 Dock 向上展开(`right: 14px; bottom: 58px`,不写 `top`),关闭时保留筛选条件。状态提示容器 `.game-resource-book-notices` 整层 `pointer-events: none`,仅提示条恢复点击,并位于标题栏下方。关闭面板用自身的点外部与 Esc 逻辑。 +- **排查**:同时核对浮层的 CSS 位置和点击命中;测试入口使用 `openResourceFilterPanel()` 实际打开面板,再验证内部控件。 +- **关联**:`apps/ai-game-creator-shell/src/styles.css`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/tests/appSurface/harness.ts`。 ## AGC 项目对话的输入区必须留在对话框内部(2026-09-11) @@ -5891,12 +5753,10 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - **排障注意**:Release GUI 没有 panic hook、也没有 stderr,panic 文案不会落盘;`diagnostics/application.log` 只会停在崩溃前最后一行(本次停在 `agent.codex_app_server.remote_control disabled` 之后),所以「应用日志没有异常」不能当作「没有 Rust panic」,要结合 WER 事件、minidump 的 fail-fast 参数与调用线程上下文判断。 - **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs`、`apps/ai-game-creator-shell/src-tauri/src/main.rs`(`install_agent_runtime_async_runtime_with_deep_stack`)、`apps/ai-game-creator-shell/src-tauri/src/commands.rs`(`cancel_direct_codex_turn`)。 -## 2026-09-24 Windows 本机跑 server-rs 全量 workspace:两类已知失败,其余全绿 +## Windows 本机跑 server-rs 全量测试时先区分环境失败 -- **命令与结果**(2026-09-24 实跑,镜像 CI):`cargo test --locked --workspace --exclude spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml` → 2 个 target 失败,其余 crate 全绿:`api-server --bin api-server` **1124 passed / 11 failed / 8 ignored**,`server-manager-panel --lib` **5 passed / 1 failed**。 -- **一、`api-server::wallet_refund_outbox::tests` 全部 11 条本机失败**:panic 是 `Io(Os { code: 5, kind: PermissionDenied })`,来自 outbox 用 `fs::hard_link` 在 `%TEMP%` 下做原子发布(`wallet_refund_outbox.rs` 201/449/468 行)。这是本机环境的既有基线——decision-log 多条验证记录早就写着「`wallet_refund_outbox` 本机环境失败,与基线一致」(当时是 3 条,模块长到 12 条测试后失败数同步变大)。不要为了让它变绿改断言或放宽发布语义;生产与 CI 在 Linux 上跑。 -- **二、`server-manager-panel::fonts::tests::finds_existing_system_cjk_font` 失败**:`find_cjk_font_candidate()` 依赖 fontconfig 的 `fc-match`,Windows 本机没有该命令,因此断言「本机至少有一个 CJK 字体」必红。这是 Linux 专属用例,同样不要改成「找不到也算过」。 -- **口径**:server-rs 的完整测试口径以 Linux(CI 的 `Server-rs tests` job)为准;本机跑全量时只把上面两类失败视为环境噪声,其余任何失败都要当真实信号处理。 +- `wallet_refund_outbox` 用 `fs::hard_link` 在 `%TEMP%` 下原子发布;若 Windows 返回 `PermissionDenied`(OS error 5),先核对本机硬链接权限与 Linux CI 结果,不要为本机限制放宽正式发布语义。 +- `server-manager-panel::fonts::tests::finds_existing_system_cjk_font` 依赖 fontconfig 的 `fc-match`,Windows 本机没有该命令时不能用它判断字体逻辑是否回归。完整 workspace 测试以 Linux CI 为准;其它失败仍需单独排查。 ## 2026-09-16 AGC 壳跑过 `cargo test` 后前端 typecheck 必红:ts-rs 导出把 `generated/*.ts` 重写成另一种形状 @@ -6178,13 +6038,13 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - **根因**:这个 harness 用桩 `systemctl` 驱动巡检脚本,Windows 上 `spawn systemctl` 直接 `ENOENT`,服务态检查不可能通过;它验证的是 Linux + systemd 的生产形态。 - **做法**:本机只跑 `npm run check:production-health-patrol-env`(本轮 OK)确认巡检变量口径;`check:production-health-patrol` 留给服务器/CI 复核,别据此判定巡检脚本本身坏了。 -## 2026-09-28 线上 dev-mac 的更新包是「上一版 + 另一个渠道身份」:清单扫描选中了构建目录残留产物 +## 按目录扫描更新产物时须核对包内版本与渠道身份 -- **现象**:线上 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/dev-mac/latest.json`(`version=0.1.142`、`commit=c07c10c0c`)把更新包指向 `陶泥儿 Release.app.tar.gz`;下载解开看 `Contents/Info.plist`:`CFBundleShortVersionString=0.1.139`、`CFBundleIdentifier=world.genarrative.ai-game-creator.release`、`CFBundleName=陶泥儿 Release`。同一份清单的首装 DMG 却是 `陶泥儿开发版_0.1.142_aarch64.dmg`。也就是说签名是真的、对象也在,但**版本与渠道身份都是错的**。 -- **根因**:`build-macos-ci.mjs` 以前只 `rmSync` 「本轮要写的确切文件名」,构建目录里上一轮(或其它渠道身份)留下的 `*.app.tar.gz` 不会被清;而 `generateUpdateManifest()` 是**扫描构建目录、按优先级挑产物**(同名优先级再按字典序),于是挑走了残留的 release 身份包。mac 构建机是复用 workspace 的,这类残留会长期存在。 -- **判据**:只读核对要**打开产物看身份**,不能只看「地址存在 + 签名匹配」。`npm run check:agc-update-channel-manifests`(`AGC_UPDATE_VERIFY_DOWNLOAD=1`)现在会解出 mac 包的 `Info.plist`、以及 Windows 安装包的 PE `RT_VERSION`(`scripts/pe-version-info.mjs`),断言「产物内版本 == 清单版本」且「产物内产品名/identifier == 本渠道身份」,并额外断言**不同渠道的更新包不得字节相同**;本轮对线上取样得到三条 FAIL,正是这个缺陷。字节级佐证:`dev-mac/0.1.142/陶泥儿 Release.app.tar.gz` 与 `release-mac/0.1.139/陶泥儿 Release.app.tar.gz` 的 sha256 完全相同(`1dfc9deb79f7…`),说明 dev 分区里放的就是 release 那一次构建的产物。 -- **处理(2026-09-28 已修)**:构建前按后缀清空 `macos/` 下的 `*.app.tar.gz`、`*.app.tar.gz.sig`、`*.dmg`、`*.dmg.sha256`;构建后读 `.app/Contents/Info.plist` 断言版本/identifier/产品名;生成清单后再断言 `release.artifact` 就是本轮那一个。守卫在 `apps/ai-game-creator-shell/scripts/macos-release-identity.mjs`,回归用例直接用线上那份 0.1.139 release 身份包(`node --test` 59 passed)。 -- **教训**:凡是「按目录扫描挑产物」的发布步骤,都要么先清空同类产物、要么按本轮预期路径断言;只删「本轮要写的名字」等于把上一轮的坏包留在候选集里。现在共享入口 `generateUpdateManifest()` 也补了与平台无关的 `assertArtifactVersionMatches()`(文件名带 `_0.1.153_` 这类版本段时必须是本轮版本),所以即使将来某个流水线不再 `git clean -fdx`,旧安装包也会被拒绝而不是被发出去。还有一条更一般的:核对线上清单时,先看 decision-log 的现行口径(这里 macOS 已是 arm64 单架构),别拿过期里程碑文字当契约。 +- **风险**:复用构建工作区里残留的其它版本或渠道更新包,会被扫描式清单生成器选中;对象存在且签名有效仍可能发错包。 +- **现行口径**:macOS 构建前清理同类 `*.app.tar.gz`、签名与 DMG 产物,构建后检查 `Info.plist` 的版本、identifier、产品名,并断言清单引用的是本轮包。核对已发布清单时设置 `AGC_UPDATE_VERIFY_DOWNLOAD=1` 后运行 `npm run check:agc-update-channel-manifests`:检查 macOS `Info.plist`、Windows PE 版本资源与清单一致,以及渠道间包体隔离。文件名的版本段也须与本轮版本一致。 +- **排查**:遇到「清单签名正常但客户端更新到错误版本」时,先打开包核对身份,再查扫描目录的残留;不要只看 URL 和签名。 +- **上线边界**:构建侧修复不代表已发布对象已修正,线上包仍须按上述清单核验重新确认。 +- **关联**:`apps/ai-game-creator-shell/scripts/build-macos-ci.mjs`、`apps/ai-game-creator-shell/scripts/macos-release-identity.mjs`、`apps/ai-game-creator-shell/scripts/build-release.mjs`。 ## 2026-09-28 DirectProject 空历史注入被新版 app-server 拒绝:新项目第一条消息直接「执行通道中断」 @@ -6210,12 +6070,9 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` ## 2026-09-29 Vite dev 冷启动会让 web E2E 的首个 goto 超时,别当成页面回归 -- **现象**:`npm run dev` 刚起(Vite 冷缓存)时跑 `check:game-distribution-web-e2e`,三轮 API 断言(管理员登录、作者注册、真实游戏发布上传→送审→审核)全过,但 `page.goto('http://127.0.0.1:3000/games', { waitUntil: 'domcontentloaded' })` 以 `Timeout 30000ms exceeded` 失败——看起来像"网页打不开"。 -- **实测**:冷启动时 `Invoke-WebRequest /games` 花了 **20,171 ms**(`/` 约 2,146 ms),随后连续两次 4,208 ms / 10,119 ms,预热完成后 **14 ms**。原因是 Vite dev 按需编译该路由的模块图,首个请求最贵。 -- **处理(2026-09-29 当天补正:`fetch` 预热不够)**:第一版只在导航前 `fetch` 预热 `/games`、`/games/detail?id=…`、`/games/play?id=…` 并把首个 `goto` 超时提到 120s。但 `fetch` 只拿到 SPA 外壳(HTML 里没有路由模块),**并没有真的让 Vite 编译那些模块**,所以冷启动下断言阶段照样假失败:实测第一次卡在 `button.game-card:visible` 的 20 秒等待,第二次(目录已热)又推进到游玩页 `getByRole('button', { name: '开始游戏' })` 的 15 秒等待——每次都停在「该路由第一次被**浏览器**加载」的那一步。 - 现在改为**浏览器级预热**:起浏览器后用一次性 context 真的把三条路由走一遍(`page.goto(url, { waitUntil: 'domcontentloaded', timeout: 120_000 })` + 2 秒 settle,失败只告警),让 Vite 先把模块编译落缓存,再建断言用的 context;首个 `goto` 的 120s 超时保留。另外三个 web 脚本原本就用 `waitUntil: 'commit', timeout: 120_000` + 60s 等待,属同一类防护,别再去掉。 -- **复验**:重启 `npm run dev`(Vite 冷缓存)后跑修复版 → **24/24 PASS**;同一次冷启动下修复前的脚本会在上面两处各失败一次;四条 web E2E(`web` / `publish` / `publish-recovery` / `a11y`)本次全部复跑通过。 -- **判据**:遇到"某一步等元素超时 + 前面的 API 断言全过",且这一步正好是该路由第一次在浏览器里加载,先怀疑 Vite 按需编译。`curl`/`Invoke-WebRequest` 量到的 20s 级冷启动只能解释导航慢;**纯 HTTP 预热不算预热**,要预热就得用浏览器真的走一遍。 +- **现象**:`npm run dev` 冷启动后,发行 web E2E 的 API 断言通过,浏览器首次进入 `/games`、详情或游玩页却在导航或元素等待处超时。 +- **原因与处理**:Vite 会按需编译浏览器实际加载的路由模块;单纯 `fetch` 路由 URL 只拿到 SPA HTML,不能完成预热。用一次性浏览器 context 实际走过相关路由,再创建断言用的 context;冷导航保留足够的超时。 +- **排查**:若失败恰在该路由首次浏览器加载处,先核对冷编译耗时;不要把 API 已通过、浏览器首次超时直接判为页面逻辑回归。 ## 2026-09-29 客户端"本地导出上限"不等于"平台发布上限" @@ -6225,11 +6082,10 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` ## 2026-09-29 AGC 发布类 Job 变红或卡住,先看构建节点在线状态(不是构建脚本) -- **现象**:`Genarrative-Scheduled-Release-Trigger` 连续 FAILURE,日志尾部是 `Cancelling nested steps due to timeout` + `Genarrative-Agc-MacOS-Build 等待失败: Build of Genarrative-Agc-MacOS-Build was cancelled`(等满 2 hr);`Genarrative-Agc-MacOS-Build` 自己的日志是 ABORTED,且停在 `// node` 之前、只有 `Still waiting to schedule task` / `'genarrative-agc-macos-01' is offline`。两者是同一条根因的不同表现,别去翻构建脚本或产物验证逻辑。 +- **现象**:定时发布 Job 因等待 macOS 构建超时而失败,子 Job 停在 `// node` 前,日志显示 `Still waiting to schedule task` 或节点 offline。此时先查节点调度与连接状态。 - **先查这个**:`GET https://jenkins.genarrative.world/jenkins/computer/api/json?tree=computer[displayName,offline,offlineCauseReason]`(注意实例挂在 **`/jenkins` 上下文路径**下,用根路径会 302/403)。`offlineCause` 为 `OfflineCause$ChannelTermination` 表示 agent 连接断开(机器关机/休眠/agent 进程退出/网络中断),此时 `GET /jenkins/computer/<节点名>/config.xml` 里的 `launcher` 决定能不能远程拉起。 -- **不能远程拉起的形态**:`genarrative-agc-macos-01` 是 `JNLPLauncher`(inbound WebSocket,`remoteFS=/Users/suzmii/Library/Jenkins/agents/genarrative-agc-macos-local`),只能在那台 Mac 本机把 agent 起回来;Jenkins 侧没有可用入口。所以「macOS 渠道一直没有新版本」这类问题的第一问是:那台 Mac 是否开机、agent 是否在跑。(该节点 2026-09-24 19:54:51 +08:00 起因 `ChannelTermination` 离线,`dev-mac` 的 `latest.json` 因此一直停在 0.1.142。) +- **不能远程拉起的形态**:macOS 节点采用 `JNLPLauncher` inbound WebSocket,需要在 Mac 本机恢复 agent;Jenkins 控制器不能直接启动它。macOS 渠道迟迟没有新版本时,先确认机器和 agent 在线,再排查构建脚本。 - **离线期间不会产生半成品**:mac 构建在 `// node` 之前就被中止,Post Action 明确「未走到归档阶段时不会有任何产物,也不会写 OSS」;`Jenkinsfile.scheduled-release-trigger` 里给 mac 分支传 `SKIP_IF_SUPERSEDED=true`,节点回来后排队中的旧构建会自行让位。 -- **同时在查的东西**:`dev-mac/0.1.142` 清单指向的是 `0.1.139` 的 release 身份包(构建目录里上一轮的 `<产品名>.app.tar.gz` 残留被扫描式产物选择器挑走)。修复已在 `build-macos-ci.mjs` + `macos-release-identity.mjs` 落地;`npm run check:agc-update-channel-manifests` 现在会自动报出 `bundle=0.1.139 manifest=0.1.142`、身份 release 以及「渠道隔离」下的 `dev-mac = release-mac`(同一 sha256),不需要人工比对。 ## 2026-09-29 共享组件样式表加载顺序变了,同优先级的页面覆盖会静默失效 @@ -6242,11 +6098,5 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` ## 2026-09-29 用夹具直接验证「已安装渠道包里的随包 Codex 能不能跑」(不用开 GUI) -- **做法**:仓库夹具支持把被测对象换成任意 exe,所以可以直接审问某个已安装的渠道包: - `npm run check:agc-direct-execution-fixture -- --agc-exe "<%LOCALAPPDATA%\<产品名>\genarrative-ai-game-creator-shell.exe>" --cases completed` - 它只在临时目录里造项目、起 loopback Provider,跑的是 CLI/执行链路,不改被装的包、不碰 GUI。 -- **判读口径**(把「包坏了」和「客户端逻辑旧了」分开): - - stderr 里出现 `Codex app-server JSON-RPC 失败:…` → 随包 sidecar **已经起来并回了错**,问题在请求体/客户端逻辑,不在随包依赖;`agent.codex_app_server.remote_control disabled reason=provider-proxy-auth` 是夹具本地模式的预期行,不是故障。 - - 安装目录 `coding-agent/win-x64/manifest.json` 的 6 个 SHA-256 用来证明载荷本身没坏(Windows 侧还有 `codex-package.json` 声明 `codex-cli` 版本)。 - - 只有在 sidecar 根本起不来时,才会看到缺组件/版本不匹配类报错。 -- **实例**:2026-09-29 用这招查出本机已装 `陶泥儿开发版 0.1.154`(源码 `76cdb96c5`)会在第一条 direct 回合报 `items must not be empty`——那是 `7ec984d0d` 已修、`0.1.158`(`e1dacccec5`)才包含的缺陷;同一条 `completed` 用例在当前 master 的调试构建上通过,因此结论是「装着的版本旧」而不是「打包少东西」。 +- **做法**:`npm run check:agc-direct-execution-fixture -- --agc-exe "<已安装渠道包的主程序绝对路径>" --cases completed`。夹具在临时项目和 loopback Provider 上跑 CLI 执行链路,可直接核对已安装包的随包 Codex。 +- **判读**:若报 `Codex app-server JSON-RPC 失败`,sidecar 已启动并返回协议错误,应先检查请求体及已安装包的源码版本;若根本无法启动,再查安装目录 `coding-agent/win-x64/manifest.json` 的组件哈希、`codex-package.json` 的版本和缺失组件。夹具模式中的 `remote_control disabled reason=provider-proxy-auth` 是预期诊断行。 diff --git a/scripts/check-native-shells.mjs b/scripts/check-native-shells.mjs index 03a4d6d69..1c08b8741 100644 --- a/scripts/check-native-shells.mjs +++ b/scripts/check-native-shells.mjs @@ -10,9 +10,6 @@ const nativeShellPlanPath = 'docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md'; const hostBridgeProtocolDocPath = 'docs/【前端架构】宿主壳能力统一协议-2026-06-17.md'; -const developmentWorkflowDocPath = - 'docs/project-memory/shared-memory/development-workflow.md'; -const decisionLogDocPath = 'docs/project-memory/shared-memory/decision-log.md'; const rootPackageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); const mobileShellConfigCheckSource = fs.readFileSync( 'apps/mobile-shell/scripts/check-config.mjs', @@ -3774,107 +3771,12 @@ function extractDocumentMethodTable(source) { ); } -function assertNativeShellScaffoldScanWording(source, label) { - if ( - source.includes('三端生产壳临时替身词扫描') || - source.includes('三端壳生产源码') || - source.includes('壳生产源码禁替身') || - !source.includes('H5 HostBridge 真实调用链的临时替身词扫描') - ) { - throw new Error( - `${label} must document production scaffold scanning for the H5 HostBridge call chain`, - ); - } -} - function assertNativeShellCapabilityPlan() { const planSource = fs.readFileSync(nativeShellPlanPath, 'utf8'); const hostBridgeProtocolDocSource = fs.readFileSync( hostBridgeProtocolDocPath, 'utf8', ); - const developmentWorkflowDocSource = fs.readFileSync( - developmentWorkflowDocPath, - 'utf8', - ); - const decisionLogDocSource = fs.readFileSync(decisionLogDocPath, 'utf8'); - if ( - planSource.includes('permissions` 必须只包含 `core:default`') || - planSource.includes( - 'permissions=["core:default","allow-host-bridge-request"]', - ) - ) { - throw new Error( - 'native shell plan must not document core:default as a desktop capability permission', - ); - } - if ( - !planSource.includes('主窗口 capability 只授予 `allow-host-bridge-request`') - ) { - throw new Error( - 'native shell plan must document the minimal desktop capability permission', - ); - } - for (const staleShareWording of [ - '深链、系统分享、即时本地通知', - '重复执行支付、登录、系统分享、文件导入导出', - '发布分享弹窗只有声明 `share.open` 时才显示“系统分享”', - '发布分享弹窗只有在宿主声明 `share.open` 时才提供“系统分享”动作', - ]) { - if ( - planSource.includes(staleShareWording) || - hostBridgeProtocolDocSource.includes(staleShareWording) || - decisionLogDocSource.includes(staleShareWording) - ) { - throw new Error( - `native shell docs must describe share.open as a host-specific controlled share action: ${staleShareWording}`, - ); - } - } - for (const requiredShareWording of [ - '深链、受控分享动作、即时本地通知', - '重复 `id` 不得重复执行支付、登录、受控分享动作、文件导入导出', - '发布分享弹窗在 Expo 移动壳声明 `share.open` 时提供“系统分享”动作', - '发布分享弹窗在 Tauri 桌面壳中展示“复制分享文案 / 已复制 / 复制失败”', - '并按 `hostShell` 区分 Expo 系统分享面板和 Tauri 剪贴板复制表达', - ]) { - if ( - !planSource.includes(requiredShareWording) && - !hostBridgeProtocolDocSource.includes(requiredShareWording) && - !decisionLogDocSource.includes(requiredShareWording) - ) { - throw new Error( - `native shell docs missing host-specific share.open wording: ${requiredShareWording}`, - ); - } - } - for (const staleMobilePermissionText of [ - 'Android `permissions` 不手写显式权限', - '最终 Expo public config 只允许扫码能力由 `expo-camera` plugin 带入 `android.permission.CAMERA`', - '移动拍摄不请求麦克风权限', - ]) { - if ( - planSource.includes(staleMobilePermissionText) || - hostBridgeProtocolDocSource.includes(staleMobilePermissionText) - ) { - throw new Error( - `native shell docs must not keep stale mobile permission wording: ${staleMobilePermissionText}`, - ); - } - } - assertNativeShellScaffoldScanWording(planSource, 'native shell plan'); - assertNativeShellScaffoldScanWording( - hostBridgeProtocolDocSource, - 'HostBridge protocol document', - ); - assertNativeShellScaffoldScanWording( - developmentWorkflowDocSource, - 'development workflow document', - ); - assertNativeShellScaffoldScanWording( - decisionLogDocSource, - 'decision log document', - ); assertShellLayerLayoutDocumented(planSource, 'native shell plan'); assertShellLayerLayoutDocumented( hostBridgeProtocolDocSource, diff --git a/scripts/check-production-ops-guardrails.mjs b/scripts/check-production-ops-guardrails.mjs index 01116d90c..f78ea7eba 100644 --- a/scripts/check-production-ops-guardrails.mjs +++ b/scripts/check-production-ops-guardrails.mjs @@ -2055,12 +2055,6 @@ const checks = [ reason: 'Pingora 试点文档必须记录 realpath canary 的 Nginx log_format 加载顺序踩坑。', }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - includes: 'Pingora realpath canary include 要晚于 log_format', - reason: - '团队共享踩坑必须记录 realpath canary 独立 server 在 conf.d 中的加载顺序要求。', - }, { file: 'scripts/jenkins-server-provision.sh', includes: 'genarrative-health-patrol.timer', @@ -3349,16 +3343,6 @@ const checks = [ includes: 'manifest.files` 视为闭集', reason: '生产运维文档必须说明证据验真默认闭集归档口径。', }, - { - file: 'docs/project-memory/shared-memory/decision-log.md', - includes: 'manifest.files` 视为闭集', - reason: '团队共享决策必须记录 Pingora 证据验真闭集归档口径。', - }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - includes: 'manifest.files` 视为闭集', - reason: '团队共享踩坑必须记录 Pingora 证据目录不能夹带未登记条目。', - }, { file: 'docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md', includes: 'pingora-gateway-env-shadow-switch.mjs --apply', @@ -3371,18 +3355,6 @@ const checks = [ reason: '生产运维文档必须记录 rollback apply 前用随包脚本恢复 Pingora shadow env。', }, - { - file: 'docs/project-memory/shared-memory/decision-log.md', - includes: 'pingora-gateway-env-shadow-switch.mjs --apply', - reason: - '团队共享决策必须记录 rollback apply 前用随包脚本恢复 Pingora shadow env。', - }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - includes: 'pingora-gateway-env-shadow-switch.mjs --apply', - reason: - '团队共享踩坑必须记录 rollback apply 前用随包脚本恢复 Pingora shadow env。', - }, { file: 'deploy/nginx/README.md', includes: 'scripts/deploy/pingora-gateway-env-shadow-switch.mjs', @@ -6772,39 +6744,6 @@ const checks = [ reason: 'Nginx README 必须说明 direct live 静态响应头摘要缺关键证据时会阻断证据包。', }, - { - file: 'docs/project-memory/shared-memory/decision-log.md', - includes: 'API / WSS 检查不落原始响应头', - reason: '团队共享决策必须记录 direct live 只归档静态白名单响应头。', - }, - { - file: 'docs/project-memory/shared-memory/decision-log.md', - includes: 'directLiveStaticHeaders', - reason: - '团队共享决策必须记录切流 manifest 提升 direct live 静态响应头摘要。', - }, - { - file: 'docs/project-memory/shared-memory/decision-log.md', - includes: - '静态头摘要缺少缓存头、校验头、Range `206 + Content-Range`、ETag 304 / Last-Modified 304 证据时整包记为 `CRITICAL`', - reason: '团队共享决策必须记录静态响应头摘要不完整会阻断证据包。', - }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - includes: '不要把 `direct-live.json` 当成只有状态码的摘要', - reason: '团队共享踩坑必须提醒 direct live JSON 需要可复盘静态响应头证据。', - }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - includes: 'manifest.summary.directLiveStaticHeaders', - reason: '团队共享踩坑必须提醒切流 manifest 需要可快速复盘静态响应头摘要。', - }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - includes: - '摘要缺少缓存头、校验头、Range `206 + Content-Range`、ETag 304 或 Last-Modified 304 证据,证据包会直接记为 `CRITICAL`', - reason: '团队共享踩坑必须提醒静态响应头摘要不完整会阻断证据包。', - }, { file: 'scripts/ops/pingora-cutover-evidence-bundle.mjs', includes: 'directLiveAccessLog', @@ -7032,11 +6971,6 @@ const checks = [ excludes: '补齐 Brotli 取舍', reason: 'Pingora 试点文档不能继续把 Brotli 取舍留成待办。', }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - includes: 'Pingora Brotli 不能只看 Content-Encoding', - reason: '团队共享踩坑必须记录 Pingora Brotli 端到端解压风险。', - }, { file: 'deploy/pingora/pingora-gateway.env.example', includes: 'GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL=5', @@ -7137,17 +7071,6 @@ const checks = [ '--direct-pingora-access-log /var/log/genarrative/pingora-gateway.access.log', reason: '生产运维文档必须展示 Pingora 直连 access log 落盘校验参数。', }, - { - file: 'docs/project-memory/shared-memory/decision-log.md', - includes: '直连日志证据补充', - reason: '团队共享决策必须记录 Pingora 直连 access log 证据门禁。', - }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - includes: - '--direct-pingora-access-log /var/log/genarrative/pingora-gateway.access.log', - reason: '团队共享踩坑必须记录 Pingora 直连 access log 参数。', - }, { file: 'docs/【开发运维】本地开发验证与生产运维-2026-05-15.md', includes: @@ -7192,26 +7115,6 @@ const checks = [ includes: '不要继续用固定 `http://127.0.0.1/healthz` 与 `"ok":true`', reason: 'Nginx README 必须防止回退 smoke 示例回到旧 healthz 口径。', }, - { - file: 'docs/project-memory/shared-memory/decision-log.md', - includes: '不再默认假定 `/healthz` 会返回 `"ok":true`', - reason: '团队共享决策必须记录回退 smoke 的真实 Nginx 入口要求。', - }, - { - file: 'docs/project-memory/shared-memory/decision-log.md', - includes: '启用后复核应看到端口由 Pingora 占用而不是空闲', - reason: '团队共享决策必须记录启用后 --require-direct 不再检查端口空闲。', - }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - includes: '不要继续用固定 `/healthz` 与 `"ok":true`', - reason: '团队共享踩坑必须提醒回退 smoke 不能沿用旧 healthz 片段。', - }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - includes: '启用后 `--require-direct` 复核不再要求端口空闲', - reason: '团队共享踩坑必须提醒端口空闲只属于切流前门禁。', - }, { file: 'deploy/nginx/README.md', excludes: @@ -7234,12 +7137,6 @@ const checks = [ excludes: 'rollback-nginx-smoke-expect-body="ok":true', reason: 'Pingora 试点 runbook 不应再默认展示 ok:true 作为回退响应体片段。', }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - excludes: - '--nginx-smoke-url http://127.0.0.1/healthz --nginx-smoke-host <域名>', - reason: '团队共享踩坑不应再把固定本机 healthz 当成可执行回退示例。', - }, { file: 'deploy/pingora/pingora-gateway.env.example', includes: 'GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED', @@ -7272,27 +7169,6 @@ const checks = [ reason: '生产运维文档必须明确多实例 Pingora 与进程内保护的启动 / preflight 边界。', }, - { - file: 'docs/project-memory/shared-memory/decision-log.md', - includes: 'GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT>1', - reason: '团队共享决策必须记录 Pingora 多实例接流保护边界。', - }, - { - file: 'docs/project-memory/shared-memory/decision-log.md', - includes: '禁止继续执行部署工作区根部脚本', - reason: '团队共享决策必须记录 API Deploy 不再执行 workspace 根部脚本。', - }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - includes: 'API deploy 脚本本身也不能继续用部署工作区根部', - reason: - '团队共享踩坑必须记录 workspace 根部 deploy 脚本会掩盖发布包布局问题。', - }, - { - file: 'docs/project-memory/shared-memory/pitfalls.md', - includes: '备份脚本、健康巡检脚本和 env 示例目录同样不能从部署工作区兜底', - reason: '团队共享踩坑必须记录备份/巡检脚本也不能使用 workspace fallback。', - }, { file: 'deploy/nginx/README.md', includes: 'Pingora 影子网关已通过', From 68ba4c47d8b84d9c8b4f1d6d9d887821f8a701d7 Mon Sep 17 00:00:00 2001 From: Linghong Date: Tue, 29 Sep 2026 15:05:14 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=B0=86=E5=86=B3=E7=AD=96=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E6=A8=A1=E6=9D=BF=E7=A7=BB=E8=87=B3=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E8=AE=B0=E5=BF=86=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在项目记忆 README 的使用原则后恢复决策记录格式模板 说明模板按需填写,简单决策无需补齐全部字段 在决策记录文件顶部增加指向模板的链接 --- docs/project-memory/README.md | 14 ++++++++++++++ docs/project-memory/shared-memory/decision-log.md | 1 + 2 files changed, 15 insertions(+) diff --git a/docs/project-memory/README.md b/docs/project-memory/README.md index c43f93294..b96f4975b 100644 --- a/docs/project-memory/README.md +++ b/docs/project-memory/README.md @@ -32,6 +32,20 @@ docs/project-memory/ - 若本目录与代码或最新 `docs/` 冲突,以代码和最新专题为准,并在同次变更中修正记忆。 - 禁止写入个人配置、API Key、Token、Cookie、会话记录、认证文件、本地私密路径、构建产物、日志、缓存和数据库 dump。 +## 决策记录格式 + +以下格式按需使用,简单决策不必凑齐所有字段。 + +```md +## YYYY-MM-DD 决策标题 + +- 背景:为什么需要这个决策 +- 决策:最终决定是什么 +- 影响范围:涉及哪些模块/文档/流程 +- 验证方式:如何确认决策仍有效 +- 关联文档:相关 PRD、技术文档、提交或 Issue +``` + ## RAG 索引 本目录是本地 RAG 的高权重索引源,但检索结果只作为候选上下文。索引脚本位于 `scripts/rag/`;运行时依赖和 `.rag/` 数据默认不安装、不提交,启用前需先征得用户确认。 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index ae1c7f542..986222c99 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -2,6 +2,7 @@ > 用途:只记录当前仍有效、会影响后续开发的长期技术、产品与协作结论;同一事实只保留一处当前口径。 > 维护:阶段过程、分支合并和当轮测试数字由 Git 追溯;实现依据以当前代码和最新专题文档为准。 +> 格式:参见[决策记录格式](../README.md#决策记录格式),按需填写。 ## 2026-09-29 外壳状态栏退役:`status` 一行改由浮层承载