合并主分支
同步主分支资源卡依赖关系与类型分类预览等最新变更 保留并补齐画布快速编辑前后端正向白名单 拒绝图片编辑接口处理单个图标、角色动作、音频、视频及未知类型 修正快速编辑决策记录与现行编辑器文档 补充后端现役类型和未知类型表驱动测试
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# AI 游戏创作项目开发工作台 PRD
|
||||
|
||||
更新时间:`2026-07-28`
|
||||
更新时间:`2026-08-05`(实时 manifest 与资源焦点状态机收口)
|
||||
|
||||
## 1. 产品定位
|
||||
|
||||
@@ -43,10 +43,12 @@
|
||||
### 3.3 资源布局
|
||||
|
||||
- “按依赖”和“按类型”分别保存画布位置。
|
||||
- 切换布局模式后恢复该模式最后一次用户手动拖动结果。
|
||||
- 当前只允许 dependency / type 自动布局与资源卡点击,不提供资源卡手动拖动入口。
|
||||
- 切换布局模式后恢复该模式 sidecar 中已有坐标;历史手动坐标只读恢复,不删除、不重置、不迁移。
|
||||
- 新资源首次进入某个布局时才执行默认不重叠排版;已有坐标不得被自动排序覆盖。
|
||||
- 依赖布局使用资源生成/引用关系;类型布局按资源大类、子类型、尺寸规格排序。
|
||||
- 不同资源分区不可互相拖入。
|
||||
- 资源分区由资源投影固定,前端交互不能改变分类。
|
||||
- 资源卡手动拖动、拖动持久化、拖动性能与冲突后的重新拖动提示全部暂缓,不作为当前产品合同或验收条件。
|
||||
|
||||
### 3.4 数值微调
|
||||
|
||||
@@ -58,14 +60,14 @@
|
||||
|
||||
现有六个专业组为:
|
||||
|
||||
| group | 普通用户名称 | 当前职责 |
|
||||
| --- | --- | --- |
|
||||
| `design` | 策划组 | 玩法规格、界面原型、规则与验收口径 |
|
||||
| `art` | 美术组 | 角色、场景、UI、动画和美术素材 |
|
||||
| `code` | 程序组 | 可运行原型、模块实现和工程验证 |
|
||||
| `balance` | 数值组 | 速度、生命、得分和难度参数 |
|
||||
| `audio` | 音频组 | 背景音乐、音效和音频资源 |
|
||||
| `publishing` | 发布组 | 质量评审、试玩、打包和发布准备 |
|
||||
| group | 普通用户名称 | 当前职责 |
|
||||
| ------------ | ------------ | ---------------------------------- |
|
||||
| `design` | 策划组 | 玩法规格、界面原型、规则与验收口径 |
|
||||
| `art` | 美术组 | 角色、场景、UI、动画和美术素材 |
|
||||
| `code` | 程序组 | 可运行原型、模块实现和工程验证 |
|
||||
| `balance` | 数值组 | 速度、生命、得分和难度参数 |
|
||||
| `audio` | 音频组 | 背景音乐、音效和音频资源 |
|
||||
| `publishing` | 发布组 | 质量评审、试玩、打包和发布准备 |
|
||||
|
||||
- 底栏默认突出策划、美术、程序三组。
|
||||
- 允许在同一底栏展开数值、音频、发布组,不删除既有专业组。
|
||||
@@ -121,9 +123,15 @@ completed -> starting(nextSlice)
|
||||
idle -> focused(document|art|audio|version) -> idle
|
||||
```
|
||||
|
||||
- 文档:在中央画布展开并独立滚动。
|
||||
- 美术/音频:进入对应媒体聚焦状态,工具能力复用现有编辑器。
|
||||
- 版本:高亮版本引用资源;替换动作只创建下一迭代版本。
|
||||
- 文档:合法 Agent 文本回执直接使用对话投影内容;项目文件只允许读取当前 manifest 已登记资产或已完成任务产物中的 Markdown、文本、JSON、YAML、TOML,必须经过 `file.read` auto 权限、相对路径、项目边界、普通文件、符号链接 / 硬链接、读取漂移、2 MiB、UTF-8 与扩展名白名单校验。正文使用不执行 HTML、不加载远程图片、不产生可点击外链的安全 Markdown 渲染,并在中央画布内独立滚动;读取失败显示错误空态。
|
||||
- 美术:PNG、JPEG、WEBP 继续使用图片魔数与像素边界预览;GIF、SVG、AVIF、BMP、MP4、WebM、MOV 通过新增受控媒体读取链路按文件签名校验后在中央画布放大聚焦。SVG 额外拒绝脚本、事件处理器、外部资源引用和实体声明;视频使用内置播放控件。读取失败显示错误空态。
|
||||
- 音频:只读取 manifest 已登记音频或已成功导入且登记到 manifest 的附件,按文件签名接受 MP3、WAV、OGG / Opus、M4A、AAC、FLAC;聚焦态展示实际格式、浏览器解码后的时长以及带播放进度和暂停能力的内置播放器。音频任务声明中的未登记路径继续不得读取或播放。
|
||||
- 版本:只展示 manifest 中正式、不可变的迭代版本记录;版本卡展示项目修订、创建原因与父版本,聚焦态同时展示直接子版本和资源绑定。点击版本卡后高亮仍存在于当前资源投影中的引用资源;缺失历史资源只保留绑定身份,不生成幽灵资源卡。资源替换仍留给后续切片。
|
||||
- mentor 最新决定:资源聚焦不提供工具栏,也不提供工具侧边栏。
|
||||
- 点击资源后,中央主视窗从 `resources.list` 切换为 `resources.focused.document / art / audio / version`,左侧平台导航、右侧 Supervisor 对话和底部 Agent 状态栏保持原位;聚焦容器只包含标题、资源主体、必要元数据与右上角收起按钮,不使用页面级浮层或可拖动标题栏。
|
||||
- 焦点转换以稳定资源 ID 为准。只有从资源列表进入详情或从一个资源 ID 切换到另一个 ID 时聚焦详情 region;同一资源 ID 因 manifest 更新而重新投影时,不得抢走详情内音频 / 视频控件、文档链接或收起按钮的当前焦点。
|
||||
- 显式收起或按 Escape 后恢复进入前的搜索条件、dependency / type 布局模式、资源画布滚动位置和选中资源,并优先把键盘焦点还给原触发资源卡;这些只属于当前前端会话,不写入布局 sidecar。若资源已经被后台删除,必须清理 stale focused / selected ID、关闭详情并把焦点落到“搜索项目资源”,不得落到 `body`。项目切换和进入运行视图必须取消旧项目的焦点恢复意图。
|
||||
- 阶段四只新增上述受控读取与媒体展示;阶段六在同一聚焦容器内补齐正式版本只读展示和引用高亮,但不新增资源聚焦工具栏 / 工具侧边栏,不新增美术编辑、音频编辑 / 替换、资源重新生成、版本替换或运行模块。飞书原需求中“编辑并生成新资源”的条件项仍暂缓,不能只打开画板却缺少回写、`referenceResourceIds` 血缘登记、新资源自动选中与邻近布局的完整闭环。
|
||||
|
||||
### 4.4 历史成果与当前状态
|
||||
|
||||
@@ -153,7 +161,7 @@ P0 中 `approvalMode` 只能有效写入 `strict`;其它值只能作为不可
|
||||
|
||||
### 5.2 资源画布布局(P1)
|
||||
|
||||
实现状态(2026-07-28):本节布局合同已在独立客户端落地,dependency / type 双模式通过项目内 CAS sidecar 独立持久化;关系线、资源替换、缩放 / 平移等其余 P1 能力仍按本文非目标保持未实现。
|
||||
实现状态(2026-08-03):dependency / type 双模式通过项目内 CAS sidecar 独立持久化;dependency 模式由 Tauri Rust 只读构建关系拓扑与确定性依赖深度、前端 SVG 派生几何,图结构和线段均不写入布局 sidecar。依赖图加载完成前设布局初始化屏障,避免以临时 `dependencyDepth=0` 生成并持久化错误坐标。当前用户入口只允许自动布局与资源卡点击;资源卡手动拖动已按 mentor 决定暂缓。历史 sidecar 坐标继续只读恢复,底层布局读写与 CAS 合同保留,但当前没有用户手动布局入口。资源替换、缩放 / 平移等其余 P1 能力仍按本文非目标保持未实现。
|
||||
|
||||
```ts
|
||||
type ProjectResourceCanvasLayout = {
|
||||
@@ -179,7 +187,7 @@ type ProjectResourceCanvasLayout = {
|
||||
- `x / y` 是相对所属 `section` 内容原点的 CSS 像素坐标,落盘前四舍五入为非负整数;坐标不使用 viewport、页面或资源详情浮层坐标系。
|
||||
- `updatedAt` 是持久层生成的 Unix 毫秒时间戳,前端不得自行覆盖。
|
||||
- `revision` 从 `0` 开始;布局文件不存在时读取接口合成 `revision=0 / positions=[]`,首次成功写入返回 `revision=1`,后续每次成功 CAS 写入递增 `1`。JSON / Tauri / TypeScript 全链路合法范围固定为 `0..=9_007_199_254_740_991`(`Number.MAX_SAFE_INTEGER`),读取、返回或提交负数、小数、非有限值与超限整数都必须失败关闭。
|
||||
- 新资源第一次进入某个 mode 时由默认布局写入 `manuallyPlaced=false`;用户完成一次有效拖动后写为 `true`。
|
||||
- 新资源第一次进入某个 mode 时由默认布局写入 `manuallyPlaced=false`。`manuallyPlaced=true` 仅用于兼容历史 sidecar 和保留底层合同;当前界面不会因指针操作新增该值。
|
||||
- 同一份布局中 `resourceId` 必须唯一。持久层允许暂时存在当前资源投影中没有的旧 ID,因为 Agent 文本成果等资源可能晚于 manifest 恢复;前端协调后必须在下一次成功写入中清除已确认失效的坐标。
|
||||
- 单份布局最多保存 `4096` 个位置,序列化文件不得超过 `2 MiB`;`resourceId` 最多 `512` 个 Unicode 字符,`x / y` 取值范围固定为 `0..=1_000_000`。
|
||||
|
||||
@@ -234,21 +242,46 @@ type UpdateProjectResourceCanvasLayoutResult =
|
||||
|
||||
#### 5.2.4 前端布局与协调合同
|
||||
|
||||
- 资源卡改用 Pointer Events 驱动二维拖动;超过统一移动阈值后才进入拖动态,普通点击仍打开唯一资源详情浮层,`pointercancel` 恢复拖动前位置。
|
||||
- 资源只能在原 `section` 内拖动。不同 section 之间既不能通过指针拖入,也不能通过持久 payload 改变当前资源的前端分类事实。
|
||||
- dependency 默认布局按 `dependencyDepth` 形成横向层级,同层资源纵向寻找第一个不重叠位置;type 默认布局固定按“资源子类型 -> 媒体类型 -> 名称 -> 资源 ID”稳定排序,在分区内从左到右、从上到下寻找第一个空位。布局模型的 `subtype` 必填:manifest 资产使用 `asset.kind`,任务产物、导入附件与 Agent 文本成果分别使用稳定的 `task-artifact`、`attachment`、`agent-result`,不得以缺失值或显示文案兜底;资源协调签名必须包含 subtype。卡片尺寸、间距和拖动阈值必须由单一前端布局模型常量维护。
|
||||
- 资源集合变化时保留全部仍存在的坐标,只为新 ID 计算默认位置,并删除已确认失效的旧 ID;无论 `manuallyPlaced` 为何,已经写入的现存坐标都不得因重新排序、模式切换或新增资源被自动改写。
|
||||
- 资源卡是可点击按钮,只负责选择资源并让中央主视窗进入当前唯一资源聚焦状态;不得绑定卡片级 `pointerdown / pointermove / pointerup / pointercancel` 拖动处理器。
|
||||
- 指针移动不得修改卡片 `x / y`、不得产生拖动预览、不得更新依赖线几何,也不得提交手动布局 CAS。卡片 title、cursor、`touch-action` 和 class 不得暗示可拖动。
|
||||
- dependency 默认布局按 `dependencyDepth` 形成横向层级,同层资源纵向寻找第一个不重叠位置;type 默认布局固定按“资源子类型 -> 媒体类型 -> 名称 -> 资源 ID”稳定排序,在分区内从左到右、从上到下寻找第一个空位。布局模型的 `subtype` 必填:manifest 资产使用 `asset.kind`,任务产物、导入附件与 Agent 文本成果分别使用稳定的 `task-artifact`、`attachment`、`agent-result`,不得以缺失值或显示文案兜底;资源协调签名必须包含 subtype。卡片尺寸与间距由单一前端布局模型常量维护。
|
||||
- type 模式资源集合变化时保留全部仍存在的坐标,只为新 ID 计算默认位置,并删除已确认失效的旧 ID。dependency 模式只永久保留 `manuallyPlaced=true` 的用户坐标;`manuallyPlaced=false` 属于可派生自动位置,在 Rust 关系图首次就绪或可信 producer / dependency depth 变化后按最终拓扑确定性重算。自动重算不得移动手动坐标,协调结果与持久布局逐项一致时不得产生 CAS 写入。
|
||||
- 搜索或筛选只隐藏卡片,不删除、压缩或重排其坐标;清空搜索后恢复原位置。
|
||||
- 窗口尺寸变化只改变可视范围和分区滚动边界,不回写、裁切或缩放持久坐标。当前客户端继续以 `1280×800` 横屏合同验收。
|
||||
- 打开项目、切换 mode 或当前 mode 首次出现新资源时执行“读取 -> 协调 -> 必要时 CAS 写入”;项目或 mode 已切换后返回的旧异步结果必须丢弃。
|
||||
- 同一 `projectPath + projectId + mode` 的首次读取与资源集合协调必须分开:资源集合变化不得取消已经发出的读取或保存。同一 scope 内全部手动拖动和资源自动协调写入使用同一 FIFO,任一时刻最多一个 CAS 在途,后一笔必须使用前一笔成功返回的 revision,不能用“最后请求获胜”跳过中间 CAS。切换项目或 mode 后,旧 scope 的在途请求不能阻塞新 scope 队列;前端放弃旧请求槽位并丢弃其迟到响应,后端继续依靠 `expectedProjectId + expectedRevision + 系统锁` 仲裁已发出的请求。
|
||||
- 某笔 CAS 在途期间,同一 scope 内对相同 `resourceId + section` 重复产生但尚未发送的拖动意图必须折叠为最后坐标;已经在途的请求不得取消,不同资源的顺序不得跨越。队列增长必须受当前资源与分区数量约束,不能随连续 pointer 事件无界累积。
|
||||
- 用户拖动结束后先乐观更新,再立即提交一次 CAS。成功后以返回布局更新 revision;普通写入失败时恢复最近可信持久布局并提示“布局保存失败,已恢复上次布局”。
|
||||
- CAS 冲突时直接载入返回的最新布局并提示“布局已在其他窗口更新,请重新拖动”,丢弃所有基于冲突前快照排队的手动拖动,不得自动重放本地旧坐标或静默覆盖另一窗口结果。即使当前在途请求是允许自动重试的资源协调,只要本次冲突实际清除了任何排队手动拖动,也必须按当前 scope 保留重新拖动提示;后续资源协调成功、失败或通用提示定时器都不得静默清除,只有新的手动布局成功保存或切换 scope 才能解除。资源自动协调可以基于冲突返回的新 revision 有界重试,单次资源签名最多追加 `2` 次,持续跨窗口写入时不得无限自旋。
|
||||
- 打开项目、切换 mode 或当前 mode 首次出现新资源时执行“读取 -> 协调 -> 必要时 CAS 写入”;dependency 模式必须先等待与当前 `projectPath + projectId + resource inputs` 匹配的 Rust 图进入 `ready` 或 `failed` 终态,等待期间不得创建 fallback、读取 sidecar、协调资源或入队保存。`failed` 只允许以空图降级初始化一次。项目或 mode 已切换后返回的旧异步结果必须丢弃。
|
||||
- 同一 `projectPath + projectId + mode` 的首次读取与资源集合协调必须分开:资源集合变化不得取消已经发出的读取或保存。当前 scope 内资源自动协调写入使用单写者 FIFO,任一时刻最多一个 CAS 在途,后一笔必须使用前一笔成功返回的 revision。切换项目或 mode 后,旧 scope 的在途请求不能阻塞新 scope 队列;前端放弃旧请求槽位并丢弃其迟到响应,后端继续依靠 `expectedProjectId + expectedRevision + 系统锁` 仲裁已发出的请求。
|
||||
- 自动协调 CAS 冲突时直接载入返回的最新布局;仍需协调时可以基于权威 revision 最多追加 `2` 次重试,持续跨窗口写入时不得无限自旋。当前提示只说明“布局已在其他窗口更新”,不得要求用户重新拖动。
|
||||
- 缺少 Tauri bridge 的浏览器开发态可以保留当前会话内布局用于界面测试,但不得宣称已经持久保存。
|
||||
|
||||
#### 5.2.5 暂缓的手动拖动合同
|
||||
|
||||
- Pointer Events 二维拖动、移动阈值、拖动态 class、乐观手动坐标与结束时单次 CAS 提交暂缓。
|
||||
- 手动意图折叠、冲突后丢弃旧手动坐标、重新拖动提示和 4096 卡片拖动帧预算暂缓,不进入当前验收。
|
||||
- 已有 Hook 手动意图、命令式 SVG preview、sidecar 字段和 Rust CAS 基础设施可以保留为未接线技术资产;当前资源卡不得调用这些入口。
|
||||
|
||||
#### 5.2.6 资源依赖关系图层
|
||||
|
||||
- 阶段五实现状态(2026-08-03):dependency 自动排列同时消费任务 DAG 与精确资源引用。Rust 把可信 producer 的任务深度作为资源深度下限,再对 `asset-reference` 图做迭代式 SCC 压缩与确定性层级传播;被引用资源位于引用资源之前,同一引用环共享稳定深度,环后资源继续递增,没有引用关系的资源保持默认不重叠位置。布局深度通过独立 `dependencyDepths` 返回,不能把 producer assignment 冒充全部资源的布局结果。
|
||||
- `producerMappingTruncated=true` 只关闭依赖有界 Agent DB 审计的 `producerAssignments`、`taskFlows` 和 `cyclicTaskIds`。Rust 返回的 `dependencyDepths` 仍是 manifest / 精确引用 read model 的权威结果,前端必须过滤未知资源、负数、非整数和非安全整数后继续消费;不得因 producer 截断清空全部深度,也不得在前端重算替代深度。`referenceEdges`、connection index 中的 reference 关系、`cyclicResourceIds` 和 unresolved reference 继续保持可信。
|
||||
- 图层只在 dependency 模式挂载;type 模式不得渲染 SVG、连线或 marker。切换 mode、切换项目或卸载工作台时必须销毁旧图层,并清理尺寸观察和窗口事件监听。
|
||||
- 输入固定为当前资源投影的全部卡片身份 / 坐标与 Tauri Rust 返回的 `ProjectResourceGraph` 只读 DTO;Rust 负责资源过滤、去重、迭代式环检测、SCC 压缩后的确定性依赖深度、任务流聚合和一跳连接索引,前端只负责 DTO 防御归一化、浏览器几何与原生 SVG path / marker。SVG 叠加在资源卡底层并设置 `pointer-events: none`,不得引入 D3、React Flow 等图表库,也不得阻断卡片点击。
|
||||
- `asset-reference` 表示精确资源引用,使用明亮橙色实线与连续贝塞尔曲线。`GameCreationAppAssetManifestEntry.source.referenceResourceIds` 中的外部资源 ID 必须先唯一匹配另一项资产的 `source.resourceId`,再映射为当前资源卡 ID;缺失、重复或已删除的目标均不得渲染幽灵连线。
|
||||
- `task-flow` 表示同一资源类型内的任务产物流转,使用灰色圆头虚线;文档、项目版本、美术、音频之间不得绘制跨分区虚线。任务依赖按 `sourceTaskId -> targetTaskId + section` 分区聚合为一条主线,两端只保留同分区资源并绘制平滑曲线分支,不得出现直角折线;禁止对上下游资源生成笛卡尔积连线。任务主线与分支可以使用不同线宽和透明度表达聚合层级,但不能改变端点或方向语义。
|
||||
- 画布资产 producer 只能来自 `agent.runtime.canvas.asset_generate` 的 `assetId -> agentId` 审计且 `agentId` 必须存在于当前 manifest;External Editor `source.taskId` 属于平台生成任务命名空间,禁止当作 manifest task ID。证据缺失、冲突或有界审计读取未覆盖时不生成对应 task flow,不猜测归属。
|
||||
- 图模型必须对资源引用图和完整任务依赖图做迭代式环检测,不得用无界递归遍历;参与环的可见边保留渲染并标记 cyclic,环本身不能造成重复生成或死循环。
|
||||
- 资源自引用的起点与终点为同一张卡片时,必须绘制在卡片外侧的可见闭环并保留箭头,不得让路径穿过卡片后被底层 SVG 层级遮挡。
|
||||
- 搜索只允许为当前可见端点生成几何;任一精确引用端点隐藏时该线隐藏,聚合任务流只保留仍可见的两端分支,任一侧没有可见资源时整条任务流隐藏。
|
||||
- 资源点击只进入中央聚焦并保留当前选中卡片,不改变依赖卡片或连线的颜色、线宽与透明度;关系线始终直接展示,不提供点击后的上下游高亮或无关线弱化。
|
||||
- 资源卡 Pointer Move 不改变基础 positions 或 SVG 几何。连线只随布局读取、资源自动协调、搜索、项目切换或 section origin 变化而更新。
|
||||
- `ResizeObserver` 在单个图层生命周期只允许构造一次。dependency section 额外提供至少 `64px` 右侧视觉 gutter,确保最右侧自环和箭头可完整滚动显示,但不得修改卡片坐标或布局 sidecar。
|
||||
- 阶段五不改变手动位置边界:已有 `manuallyPlaced=true` 坐标原样保留,资源引用新增或变化只允许重新派生 `manuallyPlaced=false` 的自动坐标;任务流继续按任务对与资源分区聚合,禁止为了计算深度或绘线生成资源笛卡尔积。
|
||||
|
||||
### 5.3 资源类型与替换兼容性(P1)
|
||||
|
||||
实现状态(2026-08-03):当前资源投影已收口到固定的“文档 -> 项目版本 -> 美术资源 -> 音乐音效资源”四区。文档只接收 Markdown / 文本 / JSON / YAML 等正式项目文档和合法 Agent 文本回执;项目版本只接收显式 `ProjectVersionResourceSummary` read model,未知任务产物不得兜底为版本;美术接收图片、SVG、动画和视频类产物;音频只接收 manifest 已登记音频资产或已成功导入并登记到 manifest 的音频附件,任务声明中的未登记音频路径不冒充正式音频资源。无法识别的二进制任务产物和附件不进入资源画布。阶段四已为本地文档、安全 SVG / 扩展图片 / 视频和音频补齐受控读取、中央聚焦、失败空态与媒体播放;这些都是只读表现层,不改变资源投影或 manifest 真相。
|
||||
|
||||
资源身份固定使用 manifest asset ID、正式 version ID、Agent ID + run ID 或已导入资源稳定路径;显示标题、来源文案变化不得改变 `resourceId`,从而避免布局、依赖边、选择和聚焦状态因改名失效。
|
||||
|
||||
```ts
|
||||
type ProjectResourceDescriptor = {
|
||||
resourceId: string;
|
||||
@@ -276,19 +309,29 @@ type ProjectVersionResourceReplacement = {
|
||||
|
||||
### 5.4 游戏迭代版本(P1)
|
||||
|
||||
阶段六实现状态(2026-08-03):正式版本业务真相扩展在本地项目 `.agent/manifest.json` 的可选 `versions` 字段中;旧项目字段缺失时等价于空列表,不根据 checkpoint、布局 sidecar、预览记录或 `game-creator-project-revision.v1` 自动伪造版本。版本数组只允许追加,已有记录不得删除、重排或修改;首轮没有版本创建按钮,也不自动把当前编辑态登记为版本。
|
||||
|
||||
```ts
|
||||
type GameIterationVersion = {
|
||||
versionId: string;
|
||||
parentVersionId: string | null;
|
||||
projectRevision: number;
|
||||
resourceBindings: Array<{ slotId: string; resourceId: string }>;
|
||||
parameterSnapshotId: string;
|
||||
createdReason: 'initial' | 'resource-replacement' | 'agent-revision';
|
||||
createdAt: number;
|
||||
};
|
||||
|
||||
type GameCreationAppManifest = {
|
||||
// 既有字段省略
|
||||
versions?: GameIterationVersion[];
|
||||
};
|
||||
```
|
||||
|
||||
版本写入后不可修改。
|
||||
- `versions` 按追加顺序保存。第一条必须是 `initial + parentVersionId=null`;后续记录必须引用数组中更早出现的父版本,创建原因不能再是 `initial`,从而天然排除自引用、悬空父版本和父子环。
|
||||
- `projectRevision` 与 `createdAt` 必须是 JavaScript 安全非负整数;子版本的修订必须严格大于父版本,创建时间不得早于父版本。
|
||||
- 同一版本内 `slotId` 唯一;`resourceId` 固定保存 manifest asset ID,不保存资源卡显示名称、External Editor resource ID、路径或布局 ID。历史资源已不在当前 manifest 时仍保留原绑定,但界面不为其合成资源卡。
|
||||
- Tauri manifest 存储边界在每次写入前校验完整版本图,并与磁盘中的旧 `versions` 前缀逐项比较;只允许追加新记录,已有记录被修改、删除或重排时写入失败且原文件保持不变。
|
||||
- 版本卡标题由稳定追加序号生成,卡片与聚焦态展示 `versionId / projectRevision / createdReason / parentVersionId`;聚焦态额外展示直接子版本和全部 slot 绑定。点击版本卡只高亮当前投影中唯一匹配 `asset:<resourceId>` 的资源卡,不修改版本或资源。
|
||||
|
||||
### 5.5 测试切片与数值参数(P2)
|
||||
|
||||
@@ -339,7 +382,7 @@ type ProjectAgentMudPointAttribution = {
|
||||
|
||||
- 复用现有四区工作台壳。
|
||||
- 资源/运行切换与客户端内 loopback 预览。
|
||||
- 只读资源画布、文档展开、美术/音频聚焦入口。
|
||||
- 固定四类资源投影、只读资源画布与中央主视窗资源聚焦;资源聚焦工具栏 / 工具侧边栏及后续媒体能力暂缓。
|
||||
- Supervisor 正式会话、上传、Runtime 确认与安全错误。
|
||||
- 当前 run 专业状态与项目历史成果分离。
|
||||
- 默认三专业组,并可展开另外三组。
|
||||
@@ -348,9 +391,9 @@ type ProjectAgentMudPointAttribution = {
|
||||
|
||||
### P1
|
||||
|
||||
- 先实施依赖/类型两套坐标持久化、首次默认不重叠布局、跨重启恢复与 CAS 冲突处理。
|
||||
- 已实施依赖/类型两套坐标持久化、首次默认不重叠布局、历史坐标跨重启恢复与自动协调 CAS 冲突处理;资源卡手动拖动暂缓。
|
||||
- 资源关系线在布局持久化验收通过后单独实施,不与本切片捆绑伪造完成。
|
||||
- 版本资源高亮、兼容性判断和不可变下一迭代版本。
|
||||
- 已实施正式版本只读模型、版本卡、父子关系与引用资源高亮;资源兼容性判断和不可变下一迭代版本创建仍待后续切片。
|
||||
- 美术/音频编辑状态接线。
|
||||
|
||||
### P2
|
||||
@@ -371,21 +414,55 @@ type ProjectAgentMudPointAttribution = {
|
||||
4. 底栏默认显示策划、美术、程序,可展开数值、音频、发布;状态与任务来自真实 Runtime/manifest。
|
||||
5. 风险审批和无需审批不能改变运行策略,点击后明确提示尚未开放;严格审批继续使用现有 Runtime 门禁。
|
||||
6. 不显示伪造泥点、伪造资源完成度、伪造图片或外部浏览器成功提示。
|
||||
7. 未识别任务产物不进入“项目版本”,只有正式版本 read model 可以生成版本卡;资源显示名称变化不改变资源身份。
|
||||
8. 点击任一资源后只替换中央主视窗,右侧对话与底部 Agent 状态栏保持原位;收起或按 Escape 退出后恢复原搜索、布局模式、滚动位置、选中资源和触发资源卡键盘焦点。
|
||||
9. 当前 Supervisor 运行期间 manifest 新增资产、任务状态、预览状态和正式版本后,工作台无需重开项目即可同步更新资源列表、依赖图输入、运行入口和版本卡;旧项目迟到回调不得覆盖当前项目。
|
||||
10. 实时 manifest 验收必须捕获真实 App Tauri listener,并让 `get_local_game_manifest` 在非 Supervisor Agent 的 Runtime / manifest 失效事件后返回新快照;测试不得直接调用 `onManifestChange` 冒充数据源。读取合并、旧项目迟到响应和项目切换隔离必须分别有回归证据。
|
||||
11. 音频或视频控件获得焦点后,同一资源 ID 的 manifest 更新不得把焦点移回详情 region;当前资源被删除后详情关闭、focused / selected ID 清理且焦点落到资源搜索框。显式收起与 Escape 的原卡片焦点和滚动恢复继续成立。
|
||||
|
||||
### 7.2 P1 资源画布布局持久化验收
|
||||
|
||||
1. 同一项目在 dependency 与 type mode 分别拖动资源后,关闭并重启客户端,两种 mode 都恢复各自最后一次成功保存的位置。
|
||||
1. 同一项目在 dependency 与 type mode 分别读取并协调布局,关闭并重启客户端后恢复各自 sidecar 坐标;历史 `manuallyPlaced=true` 坐标保持不变。
|
||||
2. 新资源进入任一 mode 时获得不重叠默认位置,现存资源坐标保持逐项不变;删除资源后,下一次成功写入不再包含已确认失效的 ID。
|
||||
3. 资源不能跨 document、version、art、audio 分区;点击、搜索、筛选和资源详情浮层行为不因二维拖动回归。
|
||||
4. 两个窗口基于同一 revision 写入时最多一个成功;失败方收到 `conflict` 与最新完整布局,界面不静默覆盖成功方结果。
|
||||
3. 资源卡 title、cursor、`touch-action` 与 class 只表达可点击;Pointer Down / Move / Up / Cancel 不改变坐标、不产生拖动预览、不提交手动布局 CAS,点击仍打开当前资源详情。
|
||||
4. 两个窗口基于同一 revision 执行资源自动协调写入时最多一个成功;失败方收到 `conflict` 与最新完整布局,界面不静默覆盖成功方结果。
|
||||
5. 布局文件缺失的旧项目可以无迁移打开;损坏、未知 schema、身份冲突、超限和链接文件失败关闭,且原文件不被空布局覆盖。
|
||||
6. 布局读写不改变 manifest、游戏项目 mutation revision、Runtime verification、Agent 权限与预览状态。
|
||||
7. `1280×800` 最小横屏下全部资源可通过分区滚动访问,不出现页面级横向或纵向溢出,右侧对话和底部 Agent 状态栏保持可见。
|
||||
|
||||
### 7.3 P1 资源依赖关系图验收
|
||||
|
||||
1. dependency 模式显示对画布背景至少 `3:1` 对比度的橙色实线资源引用,并只在同一资源类型分区内显示灰色虚线任务流;跨类型不显示虚线,type 模式没有图层或连线。
|
||||
2. 精确引用只接受唯一有效的外部资源 ID 映射,删除或不存在的资源不产生幽灵连线。
|
||||
3. 多资源任务依赖按资源类型分区后,各分区只形成一条聚合主线与 `O(S+T)` 条端点分支,不产生 `S×T` 连线或跨分区虚线。
|
||||
4. 资源引用环和无资源产物参与的任务环都可被有限遍历识别,界面不死循环。
|
||||
5. 搜索触发端点过滤;资源点击不改变上下游卡片或任何连线的视觉状态,资源卡指针移动不更新线段,点击与中央聚焦行为不回归。
|
||||
6. 切换布局模式或项目后旧 SVG、ResizeObserver 与窗口监听全部清理;图层从不写入 layout sidecar、manifest 或其它持久化。
|
||||
7. 4096 资源链式 fixture 继续验证拓扑、聚合复杂度和自动布局性能;拖动局部更新与真实 Chromium 拖动帧预算暂缓,不作为当前验收条件。最右侧自环与箭头仍需完整显示。
|
||||
8. Rust 图读取延迟时,dependency sidecar 在图进入 `ready / failed` 前没有读取或写入;首次布局直接使用 Rust 返回的最终 producer 与 dependency depth。Agent DB 有界读取截断时 producer、task flow 与 `cyclicTaskIds` 失败关闭,精确 manifest 引用及 Rust 返回的合法 `dependencyDepths` 继续到达布局层。重新打开包含深度 `0 / 1 / 2` 自动坐标的旧布局时不得降成全 `0` 或持久化扁平布局;手动位置逐项不变,自动位置按最终拓扑协调且相同结果不增加 revision。
|
||||
9. 依赖 SVG 作为装饰层不可聚焦并对辅助技术隐藏;画布通过关联的视觉隐藏文本逐条说明当前可见资源引用和任务流,搜索过滤或模式切换后文本与可见关系同步变化。
|
||||
|
||||
### 7.4 P1 正式项目版本阶段六验收
|
||||
|
||||
1. manifest 缺少 `versions` 时旧项目正常打开且不显示伪造版本;存在合法记录时,固定“项目版本”分区按追加顺序显示稳定版本卡。
|
||||
2. 根版本、父版本和直接子版本关系在卡片或聚焦态可见;悬空父版本、自引用、重复 ID、非递增修订、倒退时间、重复 slot 和超限数字均失败关闭。
|
||||
3. 点击版本卡后,当前 manifest 中仍存在的绑定资产卡被高亮;历史已删除资产只在版本详情保留 ID,不创建幽灵卡,也不把 External Editor resource ID 猜成 manifest asset ID。
|
||||
4. 版本聚焦态只读展示身份、修订、创建原因、父子关系、创建时间和 slot 绑定,不提供编辑、替换、切换、回滚或运行按钮。
|
||||
5. 任意现有 manifest 写入只能保留磁盘版本前缀并追加新记录;存储边界以跨进程专用锁串行覆盖旧状态读取、前缀校验、安装和回读,修改、删除、重排或并发旧快照覆盖已有版本时写入失败。
|
||||
6. 版本选择和高亮不写 manifest、布局 sidecar 或 project revision;dependency / type 两种布局都可显示绑定高亮,既有依赖关系 SVG 语义不变。
|
||||
|
||||
### 7.5 阶段七完整验收
|
||||
|
||||
1. 对照飞书需求、当前 PRD、技术方案、代码、测试与阶段提交复核阶段零至阶段六;美术编辑生成新资源继续按本 PRD 已确认的闭环条件暂缓,不作为遗漏或伪完成。
|
||||
2. AppSurface 同时覆盖文档、图片、SVG、音频和视频聚焦;视频必须使用原生 `controls` 且 `preload="metadata"`,读取策略失败时中央主视窗显示安全空态,右侧对话和底部 Agent 状态栏继续存在。
|
||||
3. `1280×800` 应用内浏览器实测 `window`、document 与 body 均无页面级横向或纵向溢出。浏览器开发页受真实登录门禁保护,不为验收绕过认证或伪造 Tauri;工作台内部结构由 AppSurface 集成测试与资源布局 CSS 合同测试复核。
|
||||
4. 根目录全量 Vitest、前后端 typecheck / lint / build、Rust workspace test / check、SpacetimeDB schema、原生壳、内容 / 编码、生产运维与部署门禁全部通过后,阶段七才允许提交。
|
||||
5. 本地 `.env`、`.env.local`、密钥、缓存、日志和构建产物不进入阶段七提交;提交前再次执行编码检查和 `git diff --check`。
|
||||
|
||||
## 8. 非目标
|
||||
|
||||
- 资源画布布局持久化切片不实现资源关系线、资源替换、不可变迭代版本、画板编辑状态、测试切片、数值参数或泥点归因。
|
||||
- 本切片不保存资源详情浮层位置、画布缩放 / 平移、搜索条件、筛选条件或当前 mode;这些状态如需持久化必须另行扩展合同,不能塞入 `game-creator-resource-layout.v1`。
|
||||
- 当前收口不实现资源卡手动拖动,也不实现资源聚焦工具栏、资源聚焦工具侧边栏、美术编辑、音频编辑 / 替换、资源重新生成、资源替换、下一迭代版本创建入口、运行版本切换、版本回滚、运行模块扩展、测试切片、运行态消费版本、数值参数或泥点归因。正式版本记录已经成为 manifest 业务真相,但当前只读取、校验和展示已有记录。
|
||||
- 本切片不持久化资源聚焦状态、画布缩放 / 平移、搜索条件、筛选条件或当前 mode;聚焦退出时的列表上下文恢复只限当前前端会话,这些状态如需跨重启保存必须另行扩展合同,不能塞入 `game-creator-resource-layout.v1`。
|
||||
- 不修改 SpacetimeDB schema。
|
||||
- 不开放普通用户 Agent.md/Skill。
|
||||
- 不自动确认 Agent 动作,不自动触发可能扣费的生成。
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -94,6 +94,12 @@ npm run test -- apps/ai-game-creator-shell/tests/agentRuntimeModel.test.ts --run
|
||||
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml autonomous_completion_contract -- --nocapture --test-threads=1
|
||||
```
|
||||
|
||||
修改 manifest invalidation relay、GUI owner attach 或其测试夹具后,所有会读写进程全局事件 sink 的测试统一使用 `manifest_invalidation_sink_isolation_` 前缀,并至少以 2 个 test thread 重复运行该 filter。测试 fixture 的 accept 和 payload 读取都必须使用总 deadline,不能只在 accept 成功后给 `TcpStream` 设置 read timeout;全局 sink 只能在共享 test-only 串行锁内由 RAII guard 配置和清理。
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml manifest_invalidation_sink_isolation_ -- --nocapture --test-threads=2
|
||||
```
|
||||
|
||||
source allowlist、game-chat 单轮完成门和 Canvas spritesheet 引用门禁均须命中实际测试;若完整 Rust suite 受 Windows `os error 32` 既有文件锁竞态影响,应单独复跑新增 filter 并如实记录,不能把锁竞态失败改报为本次改动通过。
|
||||
|
||||
Windows release 的非交互后台命令统一使用 `CREATE_NO_WINDOW`,包括 `command.exec / project.verify`、STDIO MCP、Repository Context Git、`git.inspect / project.git_commit` 和 `taskkill` 清理命令;需要进程组终止时再叠加 `CREATE_NEW_PROCESS_GROUP`,不要使用 `DETACHED_PROCESS`。smoke 时应在实际任务运行期间观察无额外控制台窗口,并在关闭客户端后核对整棵后台进程树为零,再重启确认 reconciliation 可继续。
|
||||
@@ -541,7 +547,7 @@ npm run check:server-rs-ddd
|
||||
|
||||
- 仓库 CI 入口是 `.gitea/workflows/project-ci.yml`,向 `master`、`codex/ai-game-creator-app` 推送和所有 PR 创建、更新时必须运行,也允许手工触发。
|
||||
- CI 固定拆分为 `Repository checks`、`Frontend tests`、`Backend tests`、`Native shell tests` 四个 required job;对应 PR context 完整名称是 `Project CI / Repository checks (pull_request)`、`Project CI / Frontend tests (pull_request)`、`Project CI / Backend tests (pull_request)`、`Project CI / Native shell tests (pull_request)`,首次运行后仍须从 Gitea 最近一周 context 表复核。测试使用独立 job,不能只藏在综合检查 step 中;原生壳验收单独运行以便定位重型构建失败。
|
||||
- 四个 job 共同覆盖 `npm run check`,并追加 `npm run check:server-rs-ddd`、`cargo test --locked --workspace --no-fail-fast --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --all-targets --manifest-path server-rs/Cargo.toml` 和 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`。后端 runner 安装 `ffmpeg`,避免视频抽帧测试因工具缺失提前返回。`codex/ai-game-creator-app` 分支必须用独立 lockfile 安装 AI 游戏创作壳依赖,其原生壳入口还必须覆盖 `npm run ai-game-creator-shell:check`、release build smoke,并检查 AI Tauri `Cargo.lock` 不漂移。原生壳 job 还要通过 Google 官方签名 APT 源安装 `google-chrome-stable`,用于 headless preview 的真实 DOM / canvas smoke;同时安装 `ripgrep`,把 `actions/setup-node` 的完整 Node.js 22 发行目录与 root-owned rustup proxy 映射到 `/usr/local`,供只接受受信任系统命令目录的 `command.exec` 沙箱测试使用,不能放宽生产命令目录白名单。Tauri 的 1132 项级别 suite 固定 `--test-threads=1`,避免共享 Agent Runtime 后台锁与异步终态在 libtest 并行调度下互相干扰。
|
||||
- 四个 job 共同覆盖 `npm run check`,并追加 `npm run check:server-rs-ddd`、`cargo test --locked --workspace --no-fail-fast --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --all-targets --manifest-path server-rs/Cargo.toml` 和 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`。Backend job 必须先用 `cargo fetch --locked` 的 5 次整命令级有界重试准备当前 `server-rs` 锁定依赖,再进入会触发 `cargo build` 的 DDD / module-runtime 产物边界门禁;不得把依赖准备放在该门禁之后,否则镜像缺少锁新增 crate 时会在首次 registry / TLS 抖动处提前失败。后端 runner 安装 `ffmpeg`,避免视频抽帧测试因工具缺失提前返回。`codex/ai-game-creator-app` 分支必须用独立 lockfile 安装 AI 游戏创作壳依赖,其原生壳入口还必须覆盖 `npm run ai-game-creator-shell:check`、release build smoke,并检查 AI Tauri `Cargo.lock` 不漂移。原生壳 job 还要通过 Google 官方签名 APT 源安装 `google-chrome-stable`,用于 headless preview 的真实 DOM / canvas smoke;同时安装 `ripgrep`,把 `actions/setup-node` 的完整 Node.js 22 发行目录与 root-owned rustup proxy 映射到 `/usr/local`,供只接受受信任系统命令目录的 `command.exec` 沙箱测试使用,不能放宽生产命令目录白名单。Tauri 的 1132 项级别 suite 固定 `--test-threads=1`,避免共享 Agent Runtime 后台锁与异步终态在 libtest 并行调度下互相干扰。
|
||||
- checkout 必须使用完整历史。PR 将 base SHA 写入 `SPACETIME_SCHEMA_BASE_REF`,直接推送 `master` 使用 before SHA;事件基线不可解析时直接失败。Gitea 检查的是 PR head 而非预合并 commit,workflow 必须拒绝不包含最新 base commit 的过期 PR,分支保护同时保持“PR 过期禁止合并”。
|
||||
- 普通 PR job 不读取业务 secret,不运行真实 API/SpacetimeDB/OSS/支付/生成/live smoke,也不执行会修改外部状态的维护、迁移、发布或备份命令。
|
||||
- Gitea 至少升级到 `1.26.4` 后才能注册执行 PR job 的 runner;`ubuntu-latest` 标签只映射到固定 digest 的 Ubuntu 24.04 级 Docker/临时隔离镜像,不使用浮动镜像 tag,不映射 host,不向 job 暴露 Docker socket、业务 secret 或不必要内网。runner 能访问 Gitea、GitHub Actions 与 `actions/node-versions`、nodejs.org、npm、Rust 分发、crates.io 和 Google Chrome 的 `dl.google.com` 官方签名 APT 源;workflow 的官方 action 固定完整 commit,若内网禁用 GitHub,先在当前 Gitea 镜像对应 commit 并改用绝对 URL。受控镜像优先预装 rustup。Gitea 1.26 的任务超时由 runner 全局配置控制;首次运行成功后,`master` 分支保护必须要求上述四个 job 全部成功。
|
||||
|
||||
@@ -14,6 +14,30 @@
|
||||
- 关联:相关文件、文档、提交或 Issue
|
||||
```
|
||||
|
||||
## Linux 生产脚本门禁不能假设本地也是 GNU userland
|
||||
|
||||
- 现象:macOS 本地运行维护页、生产 API 部署和 Rust 产物门禁时,依次出现 `mv: illegal option -- T`、`mapfile: command not found`、`/usr/bin/cp` / `/usr/bin/chmod` 不存在,以及 `.rlib` 明明含有 `.o` 却报告“没有可扫描成员”;安全修复计划还会把 `/var/folders` 到 `/private/var/folders` 的系统别名误判为用户符号链接。
|
||||
- 原因:生产机是 Linux/GNU,而本地门禁运行在 BSD userland、Bash 3.2 和 BSD ar;测试桩硬编码 Linux 二进制路径与参数,归档解析器没有去掉 BSD 扩展成员名的尾随 NUL,路径校验也直接比较了未规范化字符串。
|
||||
- 处理:维护 marker 使用同目录临时文件加 POSIX `mv -f`,并在替换前拒绝所有符号链接和目录目标,避免 `mv -f` 跟随目录链接把临时文件移入链接目标;生产部署测试桩在 macOS 忠实模拟 GNU `mv/ln -T` 的“目标不是目录”语义,并按平台选择系统工具;脚本收集服务使用 Bash 3.2 可用的 `while read`;rlib 解析清理 BSD 成员名 NUL;计划文件只规范化系统临时目录别名,仍拒绝其下用户创建的符号链接组件。
|
||||
- 验证:运行 `npm run check:maintenance-page`、`npm run check:production-api-deploy`、`npm run check:server-rs-ddd`、`npm run test -- scripts/spacetime-repair-editor-canvas-resources.test.ts`,并在 Linux CI 保留同一生产脚本语义。
|
||||
- 关联:`scripts/deploy/maintenance-on.sh`、`scripts/check-maintenance-page.mjs`、`scripts/check-production-api-deploy.mjs`、`scripts/deploy/production-api-deploy.sh`、`scripts/check-module-runtime-artifact.mjs`、`scripts/spacetime-repair-editor-canvas-resources.mjs`。
|
||||
|
||||
## External Editor taskId 不能当作本地 manifest taskId
|
||||
|
||||
- 现象:画布资产之间已有橙色精确引用线,但依赖任务之间没有灰色 task flow;测试用 `design-foundation` 之类字符串时正常,真实生成返回 `task-1` 后失败。
|
||||
- 原因:`GameCreationAppAssetSource.taskId` 保存的是 External Editor 生成任务身份,命名空间与本地 `.agent/manifest.json` 的 Agent/task 身份不同;前端用 `taskById.get(source.taskId)` 会让真实画布资产全部失去 producer。
|
||||
- 处理:资源依赖图的 Tauri Rust read model 从有界 `.agent/agent.db` 读取 `agent.runtime.canvas.asset_generate`,以 `assetId -> agentId` 映射 producer,并要求 `agentId` 存在于当前 manifest。记录缺失、多个不同有效 Agent 冲突或读取已截断时失败关闭 producer assignment、task flow 与对应 `cyclicTaskIds`,不回退 `source.taskId`。精确 `asset-reference` 仍只依赖 manifest 中外部 resourceId 的唯一匹配;Rust 独立返回的 `dependencyDepths` 继续作为 manifest / reference read model 权威结果,前端只过滤未知资源、负数、非整数和非安全整数,不得因 producer 截断把它整体清空。
|
||||
- 验证:Rust fixture 把 `source.taskId` 固定为 `task-1 / task-2`,只有审计提供 `art-director / design-foundation` 后才生成 task flow;移除或截断审计后橙色引用保留、灰色任务流消失,合法深度仍为 `asset:spec=0 / asset:ui=1`。AppSurface 使用截断生产数据形状证明深度 `0 / 1 / 2` 真实到达卡片布局,并且不会把已有自动坐标持久化成扁平布局。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project/resource_dependency_graph.rs`、`apps/ai-game-creator-shell/src/view/project-development/resourceDependencyGraphModel.ts`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
|
||||
|
||||
## 依赖图未就绪时不能先初始化资源布局
|
||||
|
||||
- 现象:首次打开 dependency 画布时所有资源短暂按深度 0 排列;Rust 图返回后连线正确,但卡片仍停留在同一列,错误自动坐标还可能已经写入 sidecar。
|
||||
- 原因:资源图和布局读取独立异步启动,布局 Hook 在图未返回时使用空图资源创建 fallback;后续 reconcile 按旧合同保留全部已有坐标,真实 producer 与 dependency depth 无法纠正首次自动位置。
|
||||
- 处理:dependency 模式增加按项目与资源输入隔离的图加载屏障,`ready / failed` 前不启动布局 Hook 的 fallback、读取、协调或保存。Rust read model 返回确定性依赖深度;已有布局只永久保留手动位置,自动位置按最终图重新派生。type 模式不受图加载影响。
|
||||
- 验证:用 deferred graph Promise 断言终态前 Tauri layout read/update 调用均为 0;图就绪后首次坐标直接按最终深度生成,旧 scope 迟到结果无效,手动坐标不变且相同自动布局不增加 revision。
|
||||
- 关联:`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCanvasLayout.ts`、`apps/ai-game-creator-shell/src-tauri/src/project/resource_dependency_graph.rs`。
|
||||
|
||||
## Jenkins 异步备份不能用 nohup 脱离作业
|
||||
|
||||
- 现象:Stdb Publish 成功,上传日志只留下“已获取进程锁 / 上传已有备份 / 目标对象”,没有成功或可捕获错误;本地 tar.gz 和 `uploadStatus=deferred` manifest 每次发布后继续增长。
|
||||
@@ -3707,6 +3731,7 @@
|
||||
- 原因:`libc::stat.st_dev` 跟随平台 `dev_t`,macOS 为有符号整数,而 `std::os::unix::fs::MetadataExt::dev()` 统一返回 `u64`;直接比较会把 Linux 的类型偶合误当成 Unix 通用契约。
|
||||
- 处理:与 Rust 标准库的 Unix `MetadataExt` 实现保持一致,先把 `st_dev / st_ino` 规范为 `u64`,再与 `metadata.dev() / metadata.ino()` 比较;设备号、inode 和文件类型三重检查均必须保留。
|
||||
- macOS 测试夹具:`std::env::temp_dir()` 可能返回 `/var/folders/...`,而 `/var` 是系统兼容符号链接。需要真实项目根的 Runtime 测试应先 canonicalize 已存在的临时根目录,再创建唯一子目录;不得为了让夹具通过而放宽生产 Runtime 的项目根及祖先符号链接拒绝规则。
|
||||
- 异步测试隔离:测试触发后台 continuation 后,必须等待对应 Agent lane 完整释放,再删除项目夹具或安装下一项全局 mock 配置;否则前一项后台任务可能抢占后一项的唯一 mock 响应,形成只在全量顺序执行时出现的跨测试污染。
|
||||
- 验证:macOS 本机运行 `cargo check --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml`,并复跑 Agent DB、project owner 和 tool-plan handoff 的 Unix 相对句柄替换检测;Linux CI 继续覆盖原有安全回归。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project.rs`、`runner.rs`、`tool_plan_handoff.rs`。
|
||||
|
||||
@@ -4085,6 +4110,27 @@
|
||||
- 现象:可选 server 已成功连接,但返回超限 schema、重复 tool identity 或要求未支持 task-mode 时,整个 MCP catalog 和本轮 Agent planning 一起失败。
|
||||
- 处理:连接、tools/list、工具归一化与聚合容量都使用同一 required / optional 边界。optional 将该 server 投影为 `connected=false + error + tool_count=0`,required 保持失败关闭;被包入 `action.input` 的 `$ref` 只重定位当前 document 根的 `#` / `#/...` JSON Pointer,命名 anchor、外部 URI 与带 `$id` 的 schema resource 内 fragment 不得改写。
|
||||
|
||||
## React 异步读取必须在组件卸载时中止并失效(2026-08-04)
|
||||
|
||||
- 现象:单个 Vitest 文件全部通过,全量 CI 却在 jsdom 环境销毁后出现 `ReferenceError: window is not defined`;栈指向请求 Promise 的 `finally` 中调用 React `setState`。
|
||||
- 原因:测试触发了与断言无关的账户读取,较快环境中请求会在用例结束前失败,较慢 CI 中请求延迟到组件和 jsdom 均已销毁后才收束。仅用 revision 丢弃旧请求而不在卸载时推进 revision,最后一个在途请求仍会被误认作当前请求。
|
||||
- 处理:调用方在未认证时不得启动受保护的钱包刷新;可取消的读取要为每轮分配 `AbortController`,新读取先失效并中止旧读取,组件卸载时同时推进 revision、abort 当前请求并清空句柄。所有 `then / catch / finally` 在更新状态前都要检查 signal 与 revision。
|
||||
- 验证:定向测试覆盖卸载后请求 signal 已中止;同时复跑触发钱包刷新回调的画布生成集成测试和完整前端测试,不能以单文件偶然快速收束代替全量验证。
|
||||
|
||||
## 下游 manifest 回调测试不能冒充实时数据源(2026-08-05)
|
||||
|
||||
- 现象:工作台的资源、任务与版本重投影单测保持绿色,但后台 Agent 已更新 `.agent/manifest.json` 后,打开中的工作台仍长期显示旧快照,只有重开项目才更新。
|
||||
- 原因:测试 Supervisor 直接调用 `onManifestChange`,只证明 `App manifest -> WorkspaceLauncher -> ProjectDevelopmentView` 的下游桥接;真实 Runtime event 没有失效字段,监听器也没有重读 manifest。External Runner 又与 GUI 分属不同进程,Runner 内无法使用 GUI `AppHandle`,只补普通 Tauri event 仍不能形成生产链路。
|
||||
- 处理:后台 manifest mutation 收敛到共用 Runtime emitter;GUI 内进程用带 `manifestInvalidated` 的 Runtime update,External Runner 通过 GUI owner attach 登记的受令牌保护 loopback sink 转发专用失效事件。App 对当前项目做 single-flight manifest 重读,并以 mounted、项目路径和 scope version 丢弃迟到结果;WorkspaceLauncher 继续只消费完整 manifest 快照,不新增平行状态或轮询。
|
||||
- 验证:集成测试必须渲染真实 `App + WorkspaceLauncher`、捕获真实 Tauri listener,让 `get_local_game_manifest` 从旧快照切换到新快照,并由非 Supervisor Agent 事件驱动资产、completed 任务、运行入口和版本卡出现;另测项目切换时旧请求迟到。旧的直接 `onManifestChange` 测试只能标记为下游桥接证据。
|
||||
|
||||
## React 资源详情焦点不能依赖重建对象身份(2026-08-05)
|
||||
|
||||
- 现象:音频 / 视频播放器、文档链接或收起按钮正在获得焦点时,后台 manifest 更新会把焦点突然移回详情 region;若当前资源被删除,详情虽然消失,stale focused ID 和焦点可能残留到 `body`。
|
||||
- 原因:资源投影每次生成新对象,`useLayoutEffect([focusedResource])` 把同一资源的内容更新误判为重新进入详情;删除路径没有显式恢复状态和可聚焦 fallback,项目 / 运行视图切换也可能沿用旧 trigger。
|
||||
- 处理:焦点状态机只比较稳定 `resourceId`:`null -> id` 与 `idA -> idB` 聚焦详情,`idA -> idA` 保持当前 active element。显式收起 / Escape 才恢复原卡片与滚动;后台删除清理 focused / matching selected ID 并聚焦搜索框;项目或运行视图切换清空 trigger / restore。媒体预览副作用依赖稳定 ID、路径和类别,不因同 ID 对象重建先卸载控件。
|
||||
- 验证:媒体控件获得焦点后用同 ID 新 manifest 重渲染并断言 active element 不变;删除资源后断言详情关闭、选中清理且搜索框获得焦点;既有收起、Escape、项目切换和运行切换测试继续通过。
|
||||
|
||||
## 不要用自然语言精确 `.replace()` 维护 Runtime Prompt
|
||||
|
||||
- 现象:Prompt 文案稍作改写、增删空格或调整段落后,替换静默失效,代码中出现难以审阅的链式 `.replace()`。
|
||||
@@ -4099,6 +4145,20 @@
|
||||
- 处理:使用完整 JSON Schema validator 校验原始 catalog schema,不手写 required/type 子集;native parser、fingerprint enrichment 与实际 MCP 调用边界复用同一校验器。enrichment 错误必须映射回 classified `arguments-schema` repair,不能以普通字符串直接终止 run;执行点重验用于阻断升级前已经落盘的 schema 外 pending。关闭网络和文件 `$ref` 解析,schema 无法安全编译时不广告或不执行。`serde` 类型错误会包含实际字符串值,catalog miss 也会包含模型提交的 server/tool,因此这两类错误同样只能返回稳定类别,不能拼接原始错误、参数值或 schema 内容。
|
||||
- 验证:覆盖 required、additionalProperties、type、enum、本地 `$defs/$ref`、HTTP/file 外部引用、无效 schema、错误脱敏,证明 legacy wrapper 在注入 fingerprint 前进入 repair,并证明带旧有效 fingerprint 的历史 pending 在实际调用前仍被 schema 拒绝。
|
||||
|
||||
## macOS 安全路径测试必须使用规范化临时目录(2026-08-05)
|
||||
|
||||
- 现象:调用仓库上下文、Runtime context bundle 或 pending recovery 的 Rust 测试在 macOS 报“Repository root and its ancestors must not be symbolic links”,Linux CI 却可能通过;本地 HTTP 恢复夹具在完整串行测试中还可能偶发 `WouldBlock`。
|
||||
- 原因:`tempfile::tempdir()` 默认返回 `/var/folders/...`,而 macOS 的 `/var` 是指向 `/private/var` 的符号链接,生产安全校验会按设计拒绝该祖先;恢复测试的服务端读超时若仅为 2 秒,也会与完整测试负载下约 2 秒的首次请求形成窄竞态。
|
||||
- 处理:凡测试会进入仓库可信路径校验,统一使用 `crate::tests::canonical_test_tempdir(...)`,不得削弱生产符号链接拒绝规则;loopback 夹具保留有界超时,但为完整 CI 负载留足稳定裕量。
|
||||
- 验证:在 macOS 上定向运行 provider request、pending recovery、autonomous continuation 与 generation recovery 用例,再运行完整 `npm run check:native-shells`。
|
||||
|
||||
## Mach-O 文件头校验必须覆盖反字节序魔数(2026-08-05)
|
||||
|
||||
- 现象:macOS arm64 的 Tauri release 已成功构建且 `file` 明确认定为 Mach-O,产物 staging 仍报“must be an executable Mach-O file”。
|
||||
- 原因:脚本用 `Buffer.readUInt32BE(0)` 读取文件头,却只比较 `0xfeedfacf` 等正序数值;arm64 常见头字节是 `cf fa ed fe`,读取结果为 `0xcffaedfe`。
|
||||
- 处理:文件头白名单同时覆盖 32/64 位与 fat Mach-O 的正序和反字节序合法魔数,并由桌面配置门禁同时反查 staging 脚本和根级产物检查,不能改成只按扩展名或构建退出码判断。
|
||||
- 验证:在 macOS 上构建真实 desktop-shell release,运行 `npm run desktop-shell:stage-release-binary`,再由 `npm run check:native-shells` 校验 staged 产物。
|
||||
|
||||
## 托管 MCP 新增公开域名时不能只更新网关路由(2026-08-05)
|
||||
|
||||
- 现象:`https://dev.genarrative.world/api/external/v1/mcp` 的 manifest、OpenAPI 和 Bearer 鉴权都正常,但鉴权后的 `initialize` 返回 `403 FORBIDDEN`;通过 SSH 隧道访问同一 api-server 的 loopback 地址却可以正常列出 tools/resources。
|
||||
@@ -4106,3 +4166,17 @@
|
||||
- 处理:新增公开 MCP 环境时,同批登记对应 Host 与 HTTPS Origin;不要通过客户端伪造 `Host`、关闭防护或改走内部 SpacetimeDB MCP 规避。allowlist 变更属于 api-server 发布内容,必须随正常 API release 部署到目标环境。
|
||||
- 验证:自动测试使用真实公开 Host/Origin 执行 `initialize`;部署后再从公网域名完成带 Key 的 `initialize`、`tools/list`、`resources/list`、Skill resource 读取和至少一个只读业务 tool 调用。loopback 成功只能证明 MCP 实现和 Key 可用,不能替代公网 Host 验收。
|
||||
- 关联:`server-rs/crates/api-server/src/external_mcp.rs`、`docs/【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md`。
|
||||
|
||||
## GUI owner 锁不能替代逐 boot 的事件接收端登记(2026-08-05)
|
||||
|
||||
- 现象:GUI 首次启动后 manifest 事件转发正常,但 Runner 被替换为新 boot 后只剩 owner 锁和 endpoint 可用,后台更新不再到达 GUI;或者 attach 响应只确认 owner,客户端却误记当前 boot 已完整登记,后续 ensure 不再重试。
|
||||
- 原因:把 OS owner 生命周期约束与进程内事件 sink attachment 混成同一状态,或在 `ensure_external_agent_runner` 之外执行一次性 attach;测试若用 actionId 等无关字段代替真实 sink port/token,也无法证明新 boot 重放的是可用接收端。
|
||||
- 处理:GUI 按规范化 AppData 私有登记真实 sink port/token,`ensure_external_agent_runner` 的 endpoint 复用和新 Runner 就绪两条成功路径都按 `bootId` 重放。同 boot 成功后幂等,新 boot 必须重挂;RPC、`attached` 或 `eventSinkAttached` 任一失败或缺失都不得记录成功 boot,并允许同 boot 后续重试。不同 AppData 不共享登记,未登记 CLI 不触发 attach;sink token 不进入日志、错误或公共状态。
|
||||
- 验证:分别覆盖真实 port/token 跨 boot 原样重放、同 boot 幂等、新 boot 重挂、普通 attach 失败、`eventSinkAttached` 缺失与 false 后同 boot 重试、AppData 隔离和未登记 CLI 零副作用。
|
||||
|
||||
## manifest relay 测试不能并行覆盖同一个全局 sink(2026-08-05)
|
||||
|
||||
- 现象:crate 根 relay 测试在配置全局 sink 后阻塞等待 `TcpListener::accept()`,同时 Runner GUI owner attach 测试通过另一条路径覆盖并清空 sink;事件可能被发往另一端口,原 listener 随后永久等待。断言或 `expect` 提前失败时,成功路径末尾的手动 clear 也不会执行。
|
||||
- 原因:两个跨模块测试读写同一进程全局状态,却没有共用隔离边界;只给 accept 后取得的 stream 设置 read timeout 无法约束 accept 本身,payload 读取也缺少总 deadline。
|
||||
- 处理:全部全局 sink 测试共用一把 test-only 串行锁,并由 RAII guard 在 `Drop` 中无条件清空;测试统一使用 `manifest_invalidation_sink_isolation_` 前缀。relay fixture 对 accept 和 payload 分别使用非阻塞轮询与总 deadline,不使用固定 sleep;生产 loopback、token、连接 / 写入超时和 payload 大小校验保持不变。
|
||||
- 验证:用 `--test-threads=2` 重复运行统一 filter,覆盖正常 relay、无事件 accept 超时、不完整 payload 超时、panic 展开清理,以及 GUI owner attach 配置与 guard 清理。
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -524,7 +524,7 @@ GameBridge 禁止:
|
||||
|
||||
2026-06-19 追加:桌面壳 macOS 媒体权限说明进入门禁。Tauri 桌面壳仍不新增摄像头或麦克风 HostBridge method,不把系统媒体能力暴露成桌面命令;同源 H5 页面可继续使用浏览器标准 `getUserMedia` 承接儿童动作热身 Demo 的实时摄像头输入和汪汪声浪正式 runtime 的实时麦克风输入。macOS 分发包必须通过 `bundle.macOS.infoPlist="Info.plist"` 合并受控用途说明:`NSCameraUsageDescription` 只描述同源 H5 实时动作输入,`NSMicrophoneUsageDescription` 只描述同源 H5 实时声音玩法。`apps/desktop-shell/scripts/check-config.mjs` 会校验 plist 路径和两条文案,并把 `Info.plist` 纳入生产壳替身词扫描,防止桌面包缺少系统授权说明、把媒体权限扩写成通用采集能力,或在 macOS 分发配置里留下临时替身文本。
|
||||
|
||||
2026-06-18 追加:桌面壳 release 构建烟测进入统一验收。`npm run check:native-shells` 会在 H5 HostBridge、Expo 壳和 Tauri 单测通过后执行 `npm run desktop-shell:build -- --no-bundle`,确认 Tauri release 入口指向共享公开主站、受控命令白名单、图标和 Rust release 编译可以共同产出桌面二进制;构建后 `desktop-shell:stage-release-binary` 会把当前平台二进制复制到根目录 `build/native/desktop/genarrative-desktop-shell` 或 `build/native/desktop/genarrative-desktop-shell.exe`,该目录沿用根 `build/` 的 gitignore,只作为本机或 CI 可收集产物目录。统一验收必须检查 staged 二进制存在、非空且符合当前平台可执行文件头。`apps/desktop-shell/scripts/check-config.mjs` 会反查根级门禁仍保留 release build smoke、staging 步骤、二进制路径、Linux ELF / macOS Mach-O / Windows PE 文件头和可执行位检查,避免桌面产物验收被改成只看命令退出码。该烟测不生成平台安装包,避免把 Linux 本机缺少的系统打包器误判为 HostBridge 回归。
|
||||
2026-06-18 追加:桌面壳 release 构建烟测进入统一验收。`npm run check:native-shells` 会在 H5 HostBridge、Expo 壳和 Tauri 单测通过后执行 `npm run desktop-shell:build -- --no-bundle`,确认 Tauri release 入口指向共享公开主站、受控命令白名单、图标和 Rust release 编译可以共同产出桌面二进制;构建后 `desktop-shell:stage-release-binary` 会把当前平台二进制复制到根目录 `build/native/desktop/genarrative-desktop-shell` 或 `build/native/desktop/genarrative-desktop-shell.exe`,该目录沿用根 `build/` 的 gitignore,只作为本机或 CI 可收集产物目录。统一验收必须检查 staged 二进制存在、非空且符合当前平台可执行文件头;macOS 校验同时接受 32/64 位与 fat Mach-O 的大端、反字节序合法魔数,不得把 arm64 常见的 `cf fa ed fe` 文件头误拒绝。`apps/desktop-shell/scripts/check-config.mjs` 会反查根级门禁仍保留 release build smoke、staging 步骤、二进制路径、Linux ELF / macOS Mach-O / Windows PE 文件头和可执行位检查,避免桌面产物验收被改成只看命令退出码。该烟测不生成平台安装包,避免把 Linux 本机缺少的系统打包器误判为 HostBridge 回归。
|
||||
|
||||
2026-06-18 追加:移动壳 Expo managed config 烟测进入统一验收。`npm run check:native-shells` 会执行 `npm run mobile-shell:config`,在 `apps/mobile-shell` 目录内调用 `expo config --type public --json`,校验 Expo CLI 实际解析结果中的包名、scheme、深链、ATS / cleartext / backup / 相机与麦克风权限、启动页、adaptive icon、插件配置和 HostBridge 版本没有漂移。`apps/mobile-shell/scripts/check-config.mjs` 会反查根级门禁仍保留 EAS build profile、Expo config 和 Metro export 三个移动分发烟测,避免移动壳验收退回到只看源码类型检查。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 本地开发验证与生产运维
|
||||
|
||||
更新时间:`2026-08-03`
|
||||
更新时间:`2026-08-05`
|
||||
|
||||
## 标准开发流程
|
||||
|
||||
@@ -230,7 +230,7 @@ npm run check
|
||||
|
||||
- `Repository checks`:执行 `npm run lint`、主站与后台生产构建、内容数据检查和提交差异空白检查。
|
||||
- `Frontend tests`:按根 lockfile 与 `apps/ai-game-creator-shell/package-lock.json` 分别执行干净的 `npm ci`,再独立执行根 `npm run test`、`npm run bgfilter-worker:smoke-test`、`npm run check:production-health-patrol`、`npm run check:production-api-release` 和 `npm run check:production-api-deploy`,让 Vitest、Node test smoke harness 及不依赖真实服务的生产巡检 / 发布 / 部署行为 fixture 在 Gitea job 中持续执行;其中 `.test.mjs` 使用 Node test runner,不依赖 Vitest 的 `scripts/**/*.test.ts` 收集规则。
|
||||
- `Backend tests`:执行 `npm run check:server-rs-ddd`、`cargo test --locked --workspace --no-fail-fast`、`api-server --all-targets` 编译和 `spacetime-module` 编译;runner 安装 `ffmpeg`,避免视频抽帧测试因工具缺失提前返回。依赖真实服务或密钥的测试必须显式 `ignored`,不能让普通 PR job访问现场环境。
|
||||
- `Backend tests`:先对 `server-rs/Cargo.lock` 执行带 5 次整命令级有界重试的 `cargo fetch --locked`,再执行 `npm run check:server-rs-ddd`、`cargo test --locked --workspace --no-fail-fast`、`api-server --all-targets` 编译和 `spacetime-module` 编译;依赖准备必须位于会触发 Cargo build 的 DDD / 产物边界门禁之前,避免锁新增依赖未命中镜像缓存时绕过既有下载重试。runner 安装 `ffmpeg`,避免视频抽帧测试因工具缺失提前返回。依赖真实服务或密钥的测试必须显式 `ignored`,不能让普通 PR job访问现场环境。
|
||||
- `Native shell tests`:按根 lockfile 与 AI 游戏创作壳独立 lockfile 安装依赖后执行 `npm run check:native-shells`,覆盖微信壳、Expo 和 Tauri 的完整验收,并确认桌面壳与 AI 游戏创作壳的 `Cargo.lock` 都没有被构建过程改写。`codex/ai-game-creator-app` 分支的同名脚本还会执行 `npm run ai-game-creator-shell:check` 和 AI 游戏创作壳 release build smoke;共享 Agent Runtime 后台锁 suite 固定 `--test-threads=1`,不能用并行偶发失败后的逐项通过替代整套稳定门禁。
|
||||
|
||||
四个 job 合起来覆盖根 `npm run check`,并补齐根检查没有包含的 BgFilter worker smoke harness、无密钥生产巡检 / 发布 / 部署行为 fixture、server-rs DDD、正式 workspace Rust 测试与现役后端编译门禁。普通 PR CI 不注入业务密钥,不启动真实 API、SpacetimeDB、OSS、支付、图片生成或生产 live smoke;需要现场环境、可变外部状态、Docker 编排或发布凭据的 `check:*` 继续按对应专题和 Jenkins 发布流程执行,不能遍历所有同名前缀脚本冒充 PR 门禁。
|
||||
@@ -607,7 +607,7 @@ Nginx 与 Pingora 在维护 marker 存在时对内网来源绕过整站维护闸
|
||||
|
||||
该规则只绕过网关维护响应,不会自动拉起 api-server、SpacetimeDB 或其它已停止的服务。人工执行 `maintenance-on.sh` 且后端仍运行时,可以从内网继续访问整站和修改后台数据;`pause-after-stdb` 会停止旧 API/controller/worker,在 API 被停期间静态页面可能仍可加载,但普通 API 与 `/admin/api/**` 仍不可用。验证使用 `npm run check:nginx-spa-routes`、`npm run check:pingora-route-parity`、`cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml`、`npm run check:pingora-gateway-smoke` 和 `npm run check:production-ops`,不要在 live 机器上为测试临时创建维护 marker。
|
||||
|
||||
版本化默认维护页固定为 `public/maintenance.html`,使用 `public/branding/taonier-maintenance-page.png` 作为品牌视觉,只允许保存无日期、无具体时段的通用文案;正常 Web 构建由 Vite 复制到发布包根目录的 `web/maintenance.html`,并由 `check-maintenance-page.mjs` 在打包前拒绝“今天 / 今晚”、具体日期或 `HH:MM` 等临时公告。计划内停服的临时公告必须放在 release 外文件中,通过 `/opt/genarrative/current/scripts/deploy/maintenance-on.sh --page-file <公告HTML> <维护原因>` 原子安装到 `/var/lib/genarrative/maintenance/page.html`。Nginx 与 Pingora 在该文件存在时优先返回它,缺失时回退当前 Web 制品的默认维护页;同一维护窗口内 Stdb / API 的后续 `maintenance-on.sh` 调用保留已安装公告,`maintenance-off.sh` 同时删除 marker 和运行态公告,避免下次维护复活旧内容。公告启用后同时用 `genarrative.world` 与 `www.genarrative.world` 的真实 HTTPS 响应校验 `503` 和公告正文。
|
||||
版本化默认维护页固定为 `public/maintenance.html`,使用 `public/branding/taonier-maintenance-page.png` 作为品牌视觉,只允许保存无日期、无具体时段的通用文案;正常 Web 构建由 Vite 复制到发布包根目录的 `web/maintenance.html`,并由 `check-maintenance-page.mjs` 在打包前拒绝“今天 / 今晚”、具体日期或 `HH:MM` 等临时公告。计划内停服的临时公告必须放在 release 外文件中,通过 `/opt/genarrative/current/scripts/deploy/maintenance-on.sh --page-file <公告HTML> <维护原因>` 原子安装到 `/var/lib/genarrative/maintenance/page.html`;公告页和 marker 都使用同目录临时文件加 POSIX 兼容 `mv -f` 的原子替换,不得依赖 GNU `mv -T`,确保 Linux 生产机与 macOS/BSD 本地门禁语义一致。Nginx 与 Pingora 在该文件存在时优先返回它,缺失时回退当前 Web 制品的默认维护页;同一维护窗口内 Stdb / API 的后续 `maintenance-on.sh` 调用保留已安装公告,`maintenance-off.sh` 同时删除 marker 和运行态公告,避免下次维护复活旧内容。公告启用后同时用 `genarrative.world` 与 `www.genarrative.world` 的真实 HTTPS 响应校验 `503` 和公告正文。
|
||||
|
||||
生产 Jenkins 的 `Pipeline script from SCM` 由 Jenkins controller 读取 Jenkinsfile。所有生产 Job 的 SCM URL,以及 Jenkinsfile 内部在 Jenkins Built-In Node 执行的源码准备,统一使用 `ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git`,并显式传入 Jenkins SSH 凭据 `genarrative-local-gitea-ssh`;不再配置局域网 IP、`https://git.genarrative.world/...` 公网 fallback 或 `https://git.genarrative.world/git/GenarrativeAI/Genarrative.git`。所有 `GitSCM checkout` 都必须保留单分支 refspec、`shallow=true`、`depth=1`、`noTags=true` 与 `honorRefspec=true`。API / Web / Stdb 发布类流水线不在目标机器 checkout Git,统一执行上游构建归档里的部署脚本;Server-Provision 和数据库导入导出也由带 `linux && genarrative-build` 标签的 Jenkins Built-In Node 先 checkout 并 stash 所需脚本,再交给目标 dev / release agent,避免目标机把 `127.0.0.1` 误解为远端 Gitea 或让产物 commit 与执行脚本漂移。
|
||||
|
||||
@@ -898,3 +898,7 @@ node scripts/rebind-orphan-work-owners.mjs --in <exported-migration.json> --out
|
||||
- `--out`:写回后的迁移 JSON 输出路径。
|
||||
- `--dry-run`:只统计回填行数,不写文件。
|
||||
- `--placeholder-user-id`:需要时可覆盖默认占位账号 ID。
|
||||
|
||||
## 维护页目标文件安全边界(2026-08-05)
|
||||
|
||||
`scripts/deploy/maintenance-on.sh` 只允许把同目录临时普通文件原子替换到普通文件或尚不存在的 `page.html` / `enabled` 目标。目标只要是符号链接(包括指向目录的链接)或目录,脚本必须在替换前失败,不能跟随链接把临时文件移入链接目标,也不能打印“已进入维护模式”。`page_temp` 与 `marker_temp` 必须在 `set -u` 下安全初始化,清理 trap 必须在首次 `mktemp` 前生效;任一失败退出都不得在目标同级遗留 `page.html.tmp.*` 或 `enabled.tmp.*`,成功替换后则清空临时路径并解除 trap,不能误删已安装目标。跨平台实现继续使用 POSIX `mv -f`,安全语义由替换函数的目标类型门禁保证;修改后运行 `bash -n scripts/deploy/maintenance-on.sh` 与 `npm run check:maintenance-page`。
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
- `快速编辑`
|
||||
|
||||
`角色动画生成面板` 同步纳入本次生成类面板交互统一:点击角色图只聚焦图层,不自动弹出底部重绘或角色动画面板;点击 `生成动画` 后像新建图片一样创建 `角色动作` 画布占位,面板跟随占位底部,参考图首行、单文本无边界、参数按钮向上弹出、生成按钮明确展示泥点。
|
||||
`快速编辑` 由选中图片后的浮动工具栏显式打开,图片类素材统一进入框选区域 + 单提示词 + 比例 / 尺寸 + 模型选择的修改面板,不再恢复原来源生成器,也不展示额外参考图。`icon` 与 `icon-spritesheet` 图标类素材不支持快速编辑,`icon-spec` 图标规范仍按普通图片支持快速编辑。
|
||||
`快速编辑` 由选中图片后的浮动工具栏显式打开,图片类素材统一进入框选区域 + 单提示词 + 比例 / 尺寸 + 模型选择的修改面板,不再恢复原来源生成器,也不展示额外参考图。单个拆分 `icon` 不支持快速编辑,完整 `icon-spritesheet` 与 `icon-spec` 图标规范仍按普通图片支持快速编辑。
|
||||
|
||||
## 统一布局
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
- `生成音乐` 选项面板出现在音乐按钮上方,不再固定在底栏中间。
|
||||
- 规范面板比图片生成面板更紧凑,字段间距和输入高度更小,但外层 shell、首行参考图和底部按钮区必须继续对齐生成图片 / 生成角色 / 生成视频。
|
||||
- 生成规范类图片底部展示禁用态参数按钮 `16:9·2K` 和 `gpt-image-2`,视觉对齐可编辑面板的比例 / 尺寸 / 模型按钮;提交参数也固定为这三项,不出现可展开选项。
|
||||
- 图片快速编辑底部左侧展示比例 / 尺寸组合选择,右侧展示模型选择和 `修改` 按钮;原图或红框序号标注图作为 `sourceImageSrc` 直接编辑,不展示额外参考图条。图标与图集素材不展示快速编辑入口,图标规范仍可快速编辑。
|
||||
- 图片快速编辑底部左侧展示比例 / 尺寸组合选择,右侧展示模型选择和 `修改` 按钮;原图或红框序号标注图作为 `sourceImageSrc` 直接编辑,不展示额外参考图条。单个拆分图标不展示快速编辑入口,完整图标图集与图标规范仍可快速编辑。
|
||||
- 快速编辑打开后画布自动缩放平移到原图完整展示,并让面板位于原图下方且不遮挡原图;原图右侧出现竖向矩形 / 椭圆 / 画笔自由框选按钮。进入快速编辑不默认启用框选,点击工具启用并保持高亮,再点同一工具取消;完成框选后画布红色细框显示连续序号,输入框同步追加 `对N号红色圈选框里的内容做以下修改:`。
|
||||
- 快速编辑提交前保留提示词里对原图的 `原图`、`当前图片`、`当前图` 或 `图1` 引用,不再改写成 `图N`。
|
||||
- 快速编辑提交给后端时只把原图或已绘制红框和序号的标注图作为 `sourceImageSrc`,不提交隐藏的 `referenceImageSrcs`。
|
||||
|
||||
Reference in New Issue
Block a user