修复AGC合并后的CI门禁失败
Project CI / Repository checks (pull_request) Successful in 1m4s
Project CI / Backend tests (pull_request) Successful in 2m57s
Project CI / Native shell tests (pull_request) Successful in 10m3s
Project CI / Frontend tests (pull_request) Successful in 2m55s

清理项目开发页测试的重复导入
放宽自动预览轮询测试的确定性等待窗口
同步自动预览CI时序约束文档
This commit is contained in:
2026-07-31 14:55:03 +08:00
parent 66cf0bcfc8
commit dc5bab9453
3 changed files with 7 additions and 16 deletions
@@ -2,20 +2,6 @@ import type {
ProjectResourceCanvasLayout,
ProjectResourceCanvasPosition,
} from '../../../../packages/shared/src/contracts/gameCreationApp';
import { consumeInitialGameChatMessage } from '../../src/App';
import type {
AgentRuntimeEventRecord,
AgentRuntimeState,
} from '../../src/app/types';
import {
collectGameChatResultImages,
collectGameChatRuntimeEvents,
SupervisorChatOnlyView,
} from '../../src/features/project-workspace/SupervisorChatOnlyView';
import {
RESOURCE_CANVAS_CARD_WIDTH,
RESOURCE_CANVAS_COLUMN_GAP,
} from '../../src/view/project-development/resourceCanvasLayoutModel';
import {
consumeInitialGameChatMessage,
latestGameChatPlayableRevision,
@@ -30,6 +16,10 @@ import {
collectGameChatRuntimeEvents,
SupervisorChatOnlyView,
} from '../../src/features/project-workspace/SupervisorChatOnlyView';
import {
RESOURCE_CANVAS_CARD_WIDTH,
RESOURCE_CANVAS_COLUMN_GAP,
} from '../../src/view/project-development/resourceCanvasLayoutModel';
import {
act,
agentRuntimeUserInputRequest,
@@ -4241,7 +4231,7 @@ export function registerProjectSupervisorSurfaceTests() {
([command]) => command === 'get_local_game_preview_status',
).length,
).toBeGreaterThan(previewStatusReads);
});
}, { timeout: 3000 });
expect(driver.readAuthorization()?.authorizationId).toBe(
steerAuthorizationId,
);
@@ -3864,6 +3864,7 @@
- 根因:旧实现调用 `start_local_game_preview` 前就把“项目 + parent run”的授权加入 attempted 集合并清空;首版完成投影与后续专业任务仍在写项目时,启动恰逢项目写锁竞争,catch 只显示错误却无法重试。
- 约束:一次性语义应按“成功或确定性终态”消费,不按“函数调用次数”消费。项目写锁竞争保留同一授权并轮询重试;成功、显式 deny 与非瞬时失败才清除。授权需持久化项目路径和 accepted runId,重启恢复时仍必须逐项匹配,切换项目不得继承。
- 回归:AppSurface 模拟第一次 `start_local_game_preview` 返回 `项目正在被其他写操作占用`、第二次成功,断言最终渲染游戏区域且启动调用恰为两次;完整 AppSurface 仍需覆盖显式 deny、停止隐藏与项目切换隔离。
- CI 时序:生产预览状态每 `1000ms` 轮询一次,回归若也使用 `waitFor` 默认 `1000ms` 上限,会在 CI 负载下于首次 interval 回调附近竞争超时。验证“授权保留期间仍继续轮询”应使用明确 `3000ms` 上限,不改生产轮询周期。合并长测试文件后还要运行全量 ESLint;单纯 autofix 只会排序、不会消除两个分支同时引入的重复 import。
## 跨窗口 CAS 锁不能用 mtime stale 删除模拟系统互斥(2026-07-30)
@@ -43,7 +43,7 @@
- 固定试玩契约:`generic-v1` 初始状态必须为 `ready``level > 0`;点击 start 后 sequence 必须推进、phase 必须进入 `playing`,并先持续观察 2 秒、取得至少 8 个实际样本,期间保持 `playing`,以确认玩家获得正常操作机会。随后必须点击唯一可见、启用且真实可交互的 `data-playtest-id="primary-action"` 控件;该控件必须映射游戏的真实主要玩法操作,并以 sequence 相对点击前严格推进证明操作已被接受。玩家获得这次正常操作机会之前进入 `won | lost` 属于过早结束并失败;操作被接受后的单次 `lost` 是合法游戏结局,但不能成为所有受控尝试的唯一结果;若主要操作后仍为 `playing`,则继续观察 3 秒并取得至少 12 个实际样本,`won` 可提前证明非失败推进。点击 restart 后 sequence 必须再次推进并恢复到 `ready | playing`,随后持续观察 3 秒且取得至少 12 个实际样本。若首轮结果为 `lost`,重开稳定后必须再执行一次必要的 start、2 秒 / 8 样本操作机会和真实 primary-action;第二次必须进入或保持 `playing`(再观察 3 秒 / 12 样本且不得转为 `lost`)或进入 `won`,两次都固定 `lost` 代表无法正常推进的恶性 bug,必须失败。各观察窗口内 sequence 不得回退,restart 窗口只能保持 `ready | playing`;样本数门槛不能替代时长门槛,窗口末端必须强制再读取一次有效状态,不能只在前段快速取得足够样本后提前通过。控件 selector、观察时长、最少样本数、终态边界、非失败推进、末端覆盖、sequence 单调 / 严格推进规则及完整 required assertions 都进入 scenario fingerprint。读取旧 fingerprint 回执和检查 plan liveness 时,把合同升级造成的 fingerprint 不匹配视为 stale missing,允许同一 run 重新执行 `preview.validate` 自愈;身份、路径、digest 或内容完整性篡改仍失败关闭。最终完成门每次按当前合同重算 fingerprint,并严格拒绝旧 fingerprint、旧 assertion 集或仅保存历史 `passed=true` 的证据。
- 试玩证据展示:game-chat 的进度卡、可玩 revision 和自动预览只接受结构化 `preview.validate` detail 同时满足 `passed=true``playtestPassed=true`;工具 summary 中的 `ok` 不能作为兜底。`image.inspect``status=ok` 只代表工具执行成功,不代表视觉验收通过;结构化 `passed=null` 或缺少布尔结论时,UI 必须以中性“截图分析完成”展示,只有显式 `passed=true` 才能显示“截图检查通过”。
- 一次性自动预览授权:用户在该入口成功提交本轮自主生成需求,即视为对“当前项目 + 当前 Supervisor 父 run”的一次 `preview.start` 授权。授权以仅含项目路径与 accepted parent runId 的客户端本地记录持久化,App / WebView 重启后仍可恢复,但项目或 run 身份不匹配时不得使用。只有当前 accepted parent run 成功完成 `preview.validate` 且给出有效 revision 后,客户端才可消费授权,由 Tauri 首次启动并自动展示该 revision 的用户可见预览;一次授权最多成功启动一个 Tauri preview server,并必须继续走现有权限、项目写锁、审计和客户端 `PreviewRegistry` 链路。项目或 Agent 策略的显式 deny 始终优先,不得被此授权绕过。启动成功、显式 deny、非瞬时失败、父 run 在首版验证前终止或切换项目后授权失效;`preview.start` 恰逢项目写锁竞争属于瞬时失败,不消费授权,释放写锁后由同一轮询链路重试。
- 增量预览刷新:Tauri 客户端记录当前 iframe 已展示的 validated revision;同一当前 run 后续成功 `preview.validate` 的 revision 严格高于已展示 revision 时,只在原 Tauri preview server 和原 loopback origin 上刷新 iframe,不得再次调用 `preview.start`、新增 server 或切换到 Runner registry。相同或更低 revision 不触发刷新。preview HTTP server 对 HTML、脚本、样式、资源和错误响应统一发送 `Cache-Control: no-store`iframe 刷新必须读取新 revision,不能继续命中 WebView 缓存中的旧版本。
- 增量预览刷新:Tauri 客户端记录当前 iframe 已展示的 validated revision;同一当前 run 后续成功 `preview.validate` 的 revision 严格高于已展示 revision 时,只在原 Tauri preview server 和原 loopback origin 上刷新 iframe,不得再次调用 `preview.start`、新增 server 或切换到 Runner registry。相同或更低 revision 不触发刷新。preview HTTP server 对 HTML、脚本、样式、资源和错误响应统一发送 `Cache-Control: no-store`iframe 刷新必须读取新 revision,不能继续命中 WebView 缓存中的旧版本。自动预览轮询回归的等待上限必须严格大于生产 `1000ms` 轮询间隔,不得使用同为 `1000ms` 的默认上限制造 CI 边界竞争。
- 退出与 Runner:独立 game-chat release 正常退出 Tauri 事件循环时调用仅供该 flavor 使用的认证 `runner.shutdown_for_client_exit`。Runner 先进入 draining、拒绝新的 Runtime 写请求,再无条件请求结束本 boot;已有 durable task / handoff / retry / reconciliation 状态不得伪装为 completed 或被删除,下次启动按既有 reconciliation 合同恢复。Windows game-chat 客户端以 `CREATE_SUSPENDED` 创建 Runner,在其执行用户代码前立即加入由客户端持有的 `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` Job,严格复核唯一主线程后再恢复运行;分配或恢复失败必须 kill + wait 并令客户端启动失败。Runner 的 command、MCP、Git、PTY 及其它未 breakaway 后代随客户端异常退出或 Job handle 关闭一起终止;Runner 正常退出前仍先执行既有进程会话收束。普通 dev / release 与 CLI 继续使用 `runner.shutdown_if_idle`,不改变共享 Runner 的原生命周期。
- Windows 后台进程可见性:所有不需要交互控制台的 `command.exec / project.verify`、STDIO MCP、Repository Context Git、`git.inspect / project.git_commit` 和清理用 `taskkill` 必须使用 `CREATE_NO_WINDOW`;需要独立终止边界的命令可同时使用 `CREATE_NEW_PROCESS_GROUP`,不得使用会增加孤儿风险的 `DETACHED_PROCESS`。game-chat release 禁止再打开 launcher、workspace 或其它平行 Tauri 页面。
- 真实浏览器回归:显式运行 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml real_chrome_generic_playtest_ --features game-chat-release -- --ignored --nocapture --test-threads=1`,同时用真实 Chrome / Chromium / Edge 证明尚未接受真实 `primary-action` 就瞬时进入 `lost`、以及两次受控尝试都固定 `lost` 的页面必须判失败;首轮合法 `lost`、restart 恢复后第二轮进入 `playing | won` 的页面可以判通过。