From 747d792789201e5c1ecd6161692613f0fa01ab7c Mon Sep 17 00:00:00 2001 From: fhgffy <102001626+fhgffy@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:20:21 +0800 Subject: [PATCH] test: cover IDE 2.5.5 permission forms and upgrade checks --- .../self-improvement/references/lessons.md | 3 + README.md | 2 + docs/ide-compatibility.md | 20 + tests/fixtures/uia/agent-error-retry.json | 422 +++++++++++++++ tests/fixtures/uia/mcp-one-time.json | 483 ++++++++++++++++++ tests/fixtures/uia/read-url-one-time.json | 483 ++++++++++++++++++ tests/scanner-replay.test.ps1 | 37 +- 7 files changed, 1447 insertions(+), 3 deletions(-) create mode 100644 docs/ide-compatibility.md create mode 100644 tests/fixtures/uia/agent-error-retry.json create mode 100644 tests/fixtures/uia/mcp-one-time.json create mode 100644 tests/fixtures/uia/read-url-one-time.json diff --git a/.agent/skills/self-improvement/references/lessons.md b/.agent/skills/self-improvement/references/lessons.md index af9262f..91db182 100644 --- a/.agent/skills/self-improvement/references/lessons.md +++ b/.agent/skills/self-improvement/references/lessons.md @@ -6,6 +6,8 @@ - [原生桌面验收](../../native-desktop-acceptance/SKILL.md):独占配置、进程身份、真实卡片、输入保护、窗口接管、资源/延迟采样及白名单收尾。 2026-10-24:子进程模块路径、共享日志、激活版本及查询视图的核对规则见该技能。 - 每张成功事件应对应窗口、完整卡片身份和精确目标;通用按钮文案、模型自述和空白截图不足以证明真实工具输出。 +- 2026-10-26:独立服务的 ready 回执只证明当时就绪;审批动作和取内容前重新核对其持有进程及端点。服务达到本轮预算并退出后产生的拒连,单列为夹具失效,不当作插件审批失败或成功取内容。 +- 2026-10-26:CIM 的映像路径可能带 DOS 长路径前缀,而持有 Process 返回普通绝对路径;只规范已确认等价的盘符绝对路径前缀,保留完整映像、父代及创建时间检查,异盘符、异文件与 UNC 负例仍须拒绝。 - 最小化 UIA 树与旧状态栏文字可能失真;恢复后的新树、真实日志和互斥状态分层核对。 - GC 堆稳定不代表私有内存稳定;按进程创建身份分段,不拼接刷新/重启前后的数据。性能探针也要量化自身开销。 - 查询耗时下降与实际单核 CPU 分开验收;固定布局做匹配对照,剩余开销按原函数拆分,不从只读探针的百分比外推产品改善。 @@ -51,6 +53,7 @@ - 无 BOM 中文脚本用显式 UTF-8/AST 引导;记录实际引擎版本,工具 shell 字段不是引擎版本证明。Python 文本验证器在 Windows 使用 `-X utf8`,不要为默认 GBK 误报转换项目文件。 - PowerShell ScriptProperty 异常可能表现为 null;先读取一次并拒绝 null/0,再转换或据缺失 key 清状态。 - 有序字典按键访问或先 JSON 回读为对象;显示投影为空不代表原始回执丢失。 +- 2026-10-26:Windows PowerShell 5.1 与 PowerShell 7 的 Sort-Object 可对相同文件名给出不同次序;严格文件集合比较时,预期集合与实际集合交给同一引擎排序后逐字相等,保留精确文件名和数量门,不改成宽泛包含检查。 - Framework冷NGEN候选须限定实际子进程环境并清除Windows大小写重复键,父/其它引擎原样;纯Node传递门不能代替CLR机制实测,后代继承和后续冷点迁移必须分别保留。2026-10-23:托管CI与普通/自托管选项分别验证,单对映射变化不等于历史超时定因;完整原链保留默认CLR与原期限。 - 2026-10-23:恢复时重算ZIP摘要不代表解压目录未变;语义读回直接核原ZIP唯一成员、SHA/CRC与来源,或逐成员对照。API调用端点在任何旁证写入前冻结,日志源码echo不能当实际版本。 diff --git a/README.md b/README.md index 43bb122..e0a57c1 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ Current maintenance: **fhgffy**, with **Codex** assisting fixes and tests. See [ Window process and cross-architecture verification: [docs/verification-2026-10-07-process.md](docs/verification-2026-10-07-process.md). Latest user-input interference verification: [docs/verification-2026-10-07-input.md](docs/verification-2026-10-07-input.md). Multi-window and CI verification: [docs/verification-2026-10-07.md](docs/verification-2026-10-07.md). Earlier runtime notes remain in [docs/verification-2026-10-05.md](docs/verification-2026-10-05.md). A result on one IDE version does not establish compatibility with every permission type or future release. +IDE upgrade self-check and approval-type evidence boundaries / IDE 升级自检与审批类型证据边界:[Compatibility self-check](docs/ide-compatibility.md). + --- ## ✨ How It Works | 工作原理 diff --git a/docs/ide-compatibility.md b/docs/ide-compatibility.md new file mode 100644 index 0000000..79b29ed --- /dev/null +++ b/docs/ide-compatibility.md @@ -0,0 +1,20 @@ +# IDE 升级后的兼容性自检 + +2026-10-26:审批界面会随 IDE 版本、账号能力和权限设置变化。已有版本的回放或成功日志不能替代升级后的原生验收。 + +1. 在 F 盘建立本轮独立配置、扩展目录和测试工作区,记录实际 IDE/扩展版本、激活扩展路径与权限设置。不要更改日常使用配置、信任设置或已有持久允许规则。 +2. 用自有无害目标分别触发终端命令、Read URL、浏览器域名访问、浏览器 JavaScript、工作区外文件读/编辑和 MCP 工具。每类分别记录是否真的出现审批;Read URL、浏览器域名与 JavaScript 分开计数。没有弹卡的成功工具调用不能证明该类审批已经覆盖。 +3. 扫描器停止时保存原始 UIA 树和卡片:完整父链、卡片/目标 RuntimeId、精确目标值、一次允许选项及 Submit 或 Allow Once。开始扫描后,只接受同卡片的本次允许,核对精确目标及其 UTF-8 SHA-256、唯一成功日志和工具原始输出;模型复述、通用 Submit 文案或文件存在本身都不能代替这条证据链。 +4. 升级后运行仓库回归、Windows PowerShell 5.1/PowerShell 7 对应脚本及打包校验。真实卡片发生变化时,先保留原始失败与快照,再加入注明来源的脱敏回放;结构推演样本必须继续标作 `derived-regression`。 +5. 保留失败日志、原始工具输出及输入/前台影响记录。正常关闭本轮独立窗口,再按确切路径清理自建目录;不要据一次成功扩大到其他权限类型、远程环境或未来版本。 + +`Proceed`、`Continue`、`Retry`、持久允许规则、登录认证和工作区信任属于需要人工判断的语义,默认排除。不要为通过自检加入通用按钮白名单,也不要绕过拒绝规则、企业策略或沙箱限制。 + +本轮样本的解释边界: + +- `mcp-one-time.json` 和 `read-url-one-time.json` 分别来自真实 MCP 与 Read URL 一次允许卡片。两者都使用统一权限表单,所以回放中的 `terminal=true` 表示该表单判定路径,不能据此把 MCP、Read URL 计成终端执行。 +- MCP `compiler-fs/write_file` 卡片已有一次批准记录,但下游工具拒绝了测试路径,不能计作文件写入成功。已测配置下,原生工作区外读取和编辑确实成功,却没有触发文件审批;这与 MCP 卡片是不同证据。 +- 本轮 JavaScript 尝试受到 `browser_subagent` 报告的官方 API 地区限制,未取得 JavaScript 审批卡及原始执行输出,不能记作验证通过。Read URL 回放也不能补足这一项。 +- `agent-error-retry.json` 保留原始 `Retry` 的 `offscreen=true`。回归另直接调用生产名称判定验证其排除,不依赖不可见性冒充语义覆盖。 + +回放入口为 [scanner-replay.test.ps1](../tests/scanner-replay.test.ps1),由现有扫描器回归调用;它验证生产判定函数,不执行桌面审批。 diff --git a/tests/fixtures/uia/agent-error-retry.json b/tests/fixtures/uia/agent-error-retry.json new file mode 100644 index 0000000..3c27d97 --- /dev/null +++ b/tests/fixtures/uia/agent-error-retry.json @@ -0,0 +1,422 @@ +{ + "provenance": "native-sanitized", + "basis": "2026-10-26:源自独立 IDE 配置的原生 UIA ControlView 快照 A-g1-terminal-card.uia.json。提取 Agent 错误信息、Retry 按钮及其已捕获父链,保留真实控件类型和可见性,Retry 原本为 offscreen=true;仅替换临时 RuntimeId 与错误编号。直接名称断言另行验证 Retry 不属于审批,不能靠屏外状态代替语义排除。", + "source": { + "capture": "A-g1-terminal-card.uia.json", + "sha256": "1c8f6421554872d1b015053b27358d58dbd85ad1538e45c1502f5e72c5d2d8e2" + }, + "card": "22", + "nodes": [ + { + "key": "1", + "parent": null, + "name": "workspace - Antigravity IDE", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 1 + ] + }, + { + "key": "2", + "parent": "1", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 2 + ] + }, + { + "key": "3", + "parent": "2", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 3 + ] + }, + { + "key": "4", + "parent": "3", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 4 + ] + }, + { + "key": "5", + "parent": "4", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 5 + ] + }, + { + "key": "6", + "parent": "5", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 6 + ] + }, + { + "key": "7", + "parent": "6", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 7 + ] + }, + { + "key": "8", + "parent": "7", + "name": "workspace - Antigravity IDE", + "automationId": "RootWebArea", + "type": "Document", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 8 + ] + }, + { + "key": "9", + "parent": "8", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 9 + ] + }, + { + "key": "10", + "parent": "9", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 10 + ] + }, + { + "key": "11", + "parent": "10", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 11 + ] + }, + { + "key": "12", + "parent": "11", + "name": "Agent Conversation", + "automationId": "conversation", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 12 + ] + }, + { + "key": "13", + "parent": "12", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 13 + ] + }, + { + "key": "14", + "parent": "13", + "name": "Message history", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 14 + ] + }, + { + "key": "15", + "parent": "14", + "name": "Agent response", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 15 + ] + }, + { + "key": "16", + "parent": "15", + "name": "Worked for 1s", + "automationId": "", + "type": "Button", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 16 + ] + }, + { + "key": "17", + "parent": "16", + "name": "", + "automationId": "", + "type": "Image", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 17 + ] + }, + { + "key": "18", + "parent": "15", + "name": "Error Agent execution terminated due to error.", + "automationId": "", + "type": "Button", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 18 + ] + }, + { + "key": "19", + "parent": "18", + "name": "", + "automationId": "", + "type": "Image", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 19 + ] + }, + { + "key": "20", + "parent": "15", + "name": "Error ID: ", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 20 + ] + }, + { + "key": "21", + "parent": "15", + "name": "fixture-error-id", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 21 + ] + }, + { + "key": "22", + "parent": "12", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 22 + ] + }, + { + "key": "23", + "parent": "22", + "name": "Agent terminated due to error", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": true, + "value": null, + "runtimeId": [ + 42, + 23 + ] + }, + { + "key": "24", + "parent": "23", + "name": "", + "automationId": "", + "type": "Image", + "enabled": true, + "offscreen": true, + "value": null, + "runtimeId": [ + 42, + 24 + ] + }, + { + "key": "25", + "parent": "22", + "name": "You can prompt the model to try again or start a new conversation if the error persists.", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": true, + "value": null, + "runtimeId": [ + 42, + 25 + ] + }, + { + "key": "26", + "parent": "22", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": true, + "value": null, + "runtimeId": [ + 42, + 26 + ] + }, + { + "key": "27", + "parent": "26", + "name": "Dismiss", + "automationId": "", + "type": "Button", + "enabled": true, + "offscreen": true, + "value": null, + "runtimeId": [ + 42, + 27 + ] + }, + { + "key": "28", + "parent": "26", + "name": "Copy debug info", + "automationId": "", + "type": "Button", + "enabled": true, + "offscreen": true, + "value": null, + "runtimeId": [ + 42, + 28 + ] + }, + { + "key": "29", + "parent": "26", + "name": "Retry", + "automationId": "", + "type": "Button", + "enabled": true, + "offscreen": true, + "value": null, + "runtimeId": [ + 42, + 29 + ] + } + ], + "expected": { + "terminal": false, + "browser": false, + "generic": [] + } +} diff --git a/tests/fixtures/uia/mcp-one-time.json b/tests/fixtures/uia/mcp-one-time.json new file mode 100644 index 0000000..d805488 --- /dev/null +++ b/tests/fixtures/uia/mcp-one-time.json @@ -0,0 +1,483 @@ +{ + "provenance": "native-sanitized", + "basis": "2026-10-26:源自独立 IDE 配置的原生 UIA ControlView 快照 A-g1-file-write-result.uia.json。提取完整 MCP 一次允许卡片及已捕获父链,保留控件类型、名称、可见性与 compiler-fs/write_file 目标值;仅替换临时 RuntimeId 和 ask-opt 问题编号。批准记录不证明 MCP 下游文件写入成功。", + "source": { + "capture": "A-g1-file-write-result.uia.json", + "sha256": "00a8c8e37c5fdf03c1c619c3b68500792a4b866443f28da7667b3a6e22e1346e" + }, + "card": "14", + "nodes": [ + { + "key": "1", + "parent": null, + "name": "workspace - Antigravity IDE", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 1 + ] + }, + { + "key": "2", + "parent": "1", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 2 + ] + }, + { + "key": "3", + "parent": "2", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 3 + ] + }, + { + "key": "4", + "parent": "3", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 4 + ] + }, + { + "key": "5", + "parent": "4", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 5 + ] + }, + { + "key": "6", + "parent": "5", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 6 + ] + }, + { + "key": "7", + "parent": "6", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 7 + ] + }, + { + "key": "8", + "parent": "7", + "name": "workspace - Antigravity IDE", + "automationId": "RootWebArea", + "type": "Document", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 8 + ] + }, + { + "key": "9", + "parent": "8", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 9 + ] + }, + { + "key": "10", + "parent": "9", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 10 + ] + }, + { + "key": "11", + "parent": "10", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 11 + ] + }, + { + "key": "12", + "parent": "11", + "name": "Agent Conversation", + "automationId": "conversation", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 12 + ] + }, + { + "key": "13", + "parent": "12", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 13 + ] + }, + { + "key": "14", + "parent": "13", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 14 + ] + }, + { + "key": "15", + "parent": "14", + "name": "", + "automationId": "", + "type": "Image", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 15 + ] + }, + { + "key": "16", + "parent": "14", + "name": "Allow using this MCP tool?", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 16 + ] + }, + { + "key": "17", + "parent": "14", + "name": "Edit permission target", + "automationId": "", + "type": "Edit", + "enabled": true, + "offscreen": false, + "value": "compiler-fs/write_file", + "runtimeId": [ + 42, + 17 + ] + }, + { + "key": "18", + "parent": "14", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 18 + ] + }, + { + "key": "19", + "parent": "18", + "name": "1 Yes, allow this time", + "automationId": "ask-opt-fixture-1", + "type": "RadioButton", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 19 + ] + }, + { + "key": "20", + "parent": "18", + "name": "1", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 20 + ] + }, + { + "key": "21", + "parent": "18", + "name": "Yes, allow this time", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 21 + ] + }, + { + "key": "22", + "parent": "18", + "name": "2 Yes, and always allow in this conversation", + "automationId": "ask-opt-fixture-2", + "type": "RadioButton", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 22 + ] + }, + { + "key": "23", + "parent": "18", + "name": "2", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 23 + ] + }, + { + "key": "24", + "parent": "18", + "name": "Yes, and always allow in this conversation", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 24 + ] + }, + { + "key": "25", + "parent": "18", + "name": "3 Yes, and always allow", + "automationId": "ask-opt-fixture-3", + "type": "RadioButton", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 25 + ] + }, + { + "key": "26", + "parent": "18", + "name": "3", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 26 + ] + }, + { + "key": "27", + "parent": "18", + "name": "Yes, and always allow", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 27 + ] + }, + { + "key": "28", + "parent": "18", + "name": "4 No (tell the agent what to do instead)", + "automationId": "ask-opt-fixture-__write_in__", + "type": "RadioButton", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 28 + ] + }, + { + "key": "29", + "parent": "18", + "name": "4", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 29 + ] + }, + { + "key": "30", + "parent": "18", + "name": "No", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 30 + ] + }, + { + "key": "31", + "parent": "18", + "name": "(tell the agent what to do instead)", + "automationId": "", + "type": "Edit", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 31 + ] + }, + { + "key": "32", + "parent": "14", + "name": "Skip", + "automationId": "", + "type": "Button", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 32 + ] + }, + { + "key": "33", + "parent": "14", + "name": "Submit ↵", + "automationId": "", + "type": "Button", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 33 + ] + } + ], + "expected": { + "terminal": true, + "browser": false, + "generic": [] + }, + "expectedTarget": { + "key": "17", + "id": "42.17", + "value": "compiler-fs/write_file" + } +} diff --git a/tests/fixtures/uia/read-url-one-time.json b/tests/fixtures/uia/read-url-one-time.json new file mode 100644 index 0000000..2a0deb1 --- /dev/null +++ b/tests/fixtures/uia/read-url-one-time.json @@ -0,0 +1,483 @@ +{ + "provenance": "native-sanitized", + "basis": "2026-10-26:源自独立 IDE 配置的原生 UIA ControlView 快照 A-g1-read-url-result.uia.json。提取 Allow reading this URL? 一次允许卡片及已捕获父链,保留完整 radio/Submit、控件类型、名称、可见性与 127.0.0.1 精确目标;仅替换临时 RuntimeId 和 ask-opt 问题编号。Read URL 与浏览器域名访问、JavaScript 执行分别验收。", + "source": { + "capture": "A-g1-read-url-result.uia.json", + "sha256": "db2a59f98635b47a18bfe26e34650e587c089223d058bab9e44a19d9e4283ae2" + }, + "card": "14", + "nodes": [ + { + "key": "1", + "parent": null, + "name": "workspace - Antigravity IDE - sentinel.txt", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 1 + ] + }, + { + "key": "2", + "parent": "1", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 2 + ] + }, + { + "key": "3", + "parent": "2", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 3 + ] + }, + { + "key": "4", + "parent": "3", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 4 + ] + }, + { + "key": "5", + "parent": "4", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 5 + ] + }, + { + "key": "6", + "parent": "5", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 6 + ] + }, + { + "key": "7", + "parent": "6", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 7 + ] + }, + { + "key": "8", + "parent": "7", + "name": "workspace - Antigravity IDE - sentinel.txt", + "automationId": "RootWebArea", + "type": "Document", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 8 + ] + }, + { + "key": "9", + "parent": "8", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 9 + ] + }, + { + "key": "10", + "parent": "9", + "name": "", + "automationId": "", + "type": "Pane", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 10 + ] + }, + { + "key": "11", + "parent": "10", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 11 + ] + }, + { + "key": "12", + "parent": "11", + "name": "Agent Conversation", + "automationId": "conversation", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 12 + ] + }, + { + "key": "13", + "parent": "12", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 13 + ] + }, + { + "key": "14", + "parent": "13", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 14 + ] + }, + { + "key": "15", + "parent": "14", + "name": "", + "automationId": "", + "type": "Image", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 15 + ] + }, + { + "key": "16", + "parent": "14", + "name": "Allow reading this URL?", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 16 + ] + }, + { + "key": "17", + "parent": "14", + "name": "Edit permission target", + "automationId": "", + "type": "Edit", + "enabled": true, + "offscreen": false, + "value": "127.0.0.1", + "runtimeId": [ + 42, + 17 + ] + }, + { + "key": "18", + "parent": "14", + "name": "", + "automationId": "", + "type": "Group", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 18 + ] + }, + { + "key": "19", + "parent": "18", + "name": "1 Yes, allow this time", + "automationId": "ask-opt-fixture-1", + "type": "RadioButton", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 19 + ] + }, + { + "key": "20", + "parent": "18", + "name": "1", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 20 + ] + }, + { + "key": "21", + "parent": "18", + "name": "Yes, allow this time", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 21 + ] + }, + { + "key": "22", + "parent": "18", + "name": "2 Yes, and always allow in this conversation", + "automationId": "ask-opt-fixture-2", + "type": "RadioButton", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 22 + ] + }, + { + "key": "23", + "parent": "18", + "name": "2", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 23 + ] + }, + { + "key": "24", + "parent": "18", + "name": "Yes, and always allow in this conversation", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 24 + ] + }, + { + "key": "25", + "parent": "18", + "name": "3 Yes, and always allow", + "automationId": "ask-opt-fixture-3", + "type": "RadioButton", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 25 + ] + }, + { + "key": "26", + "parent": "18", + "name": "3", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 26 + ] + }, + { + "key": "27", + "parent": "18", + "name": "Yes, and always allow", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 27 + ] + }, + { + "key": "28", + "parent": "18", + "name": "4 No (tell the agent what to do instead)", + "automationId": "ask-opt-fixture-__write_in__", + "type": "RadioButton", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 28 + ] + }, + { + "key": "29", + "parent": "18", + "name": "4", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 29 + ] + }, + { + "key": "30", + "parent": "18", + "name": "No", + "automationId": "", + "type": "Text", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 30 + ] + }, + { + "key": "31", + "parent": "18", + "name": "(tell the agent what to do instead)", + "automationId": "", + "type": "Edit", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 31 + ] + }, + { + "key": "32", + "parent": "14", + "name": "Skip", + "automationId": "", + "type": "Button", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 32 + ] + }, + { + "key": "33", + "parent": "14", + "name": "Submit ↵", + "automationId": "", + "type": "Button", + "enabled": true, + "offscreen": false, + "value": null, + "runtimeId": [ + 42, + 33 + ] + } + ], + "expected": { + "terminal": true, + "browser": false, + "generic": [] + }, + "expectedTarget": { + "key": "17", + "id": "42.17", + "value": "127.0.0.1" + } +} diff --git a/tests/scanner-replay.test.ps1 b/tests/scanner-replay.test.ps1 index 5f37c05..57e28ea 100644 --- a/tests/scanner-replay.test.ps1 +++ b/tests/scanner-replay.test.ps1 @@ -40,15 +40,23 @@ function Test-ReplayConversation($Node){ } return $false } +# 2026-10-26:逐个冻结九种布局,新增、漏掉或替换任何原夹具都必须失败。 +$fixturePaths=@(Get-ChildItem -LiteralPath (Join-Path $PSScriptRoot 'fixtures\uia') -Filter '*.json' | Sort-Object Name) +$expectedFixtureNames=@('agent-error-retry.json','browser-narrow.json','browser-wide.json','excluded.json','generic.json','mcp-one-time.json','read-url-one-time.json','terminal-outside-conversation.json','terminal.json') +if((@($fixturePaths|ForEach-Object{$_.Name})-join',')-cne(@($expectedFixtureNames|Sort-Object)-join',')){throw 'Incomplete nine-layout replay set'} $checks=0 -foreach($fixturePath in @(Get-ChildItem -LiteralPath (Join-Path $PSScriptRoot 'fixtures\uia') -Filter '*.json' | Sort-Object Name)){ +$targetChecks=0;$retryChecks=0 # 2026-10-26:单独核对动作前目标复验和错误按钮排除的覆盖数量。 +foreach($fixturePath in $fixturePaths){ $fixture=Get-Content -LiteralPath $fixturePath.FullName -Raw -Encoding UTF8|ConvertFrom-Json if($fixture.provenance-notin@('native-sanitized','derived-regression')){throw 'Replay provenance missing'} $script:replayNodes=@{} foreach($row in $fixture.nodes){ if($script:replayNodes.ContainsKey($row.key)){throw 'Duplicate replay node'} - $node=[pscustomobject]@{Key=[string]$row.key;ParentKey=[string]$row.parent;Current=[pscustomobject]@{Name=$row.name;AutomationId=$row.automationId;ControlType=[Windows.Automation.ControlType]::($row.type);IsEnabled=$row.enabled;IsOffscreen=$row.offscreen}} + # 2026-10-26:补齐真实目标复验需要的两个 UIA 查询接口,值与身份都来自脱敏图。 + $node=[pscustomobject]@{Key=[string]$row.key;ParentKey=[string]$row.parent;RuntimeId=[int[]]@($row.runtimeId);Value=$row.value;Current=[pscustomobject]@{Name=$row.name;AutomationId=$row.automationId;ControlType=[Windows.Automation.ControlType]::($row.type);IsEnabled=$row.enabled;IsOffscreen=$row.offscreen}} $node|Add-Member ScriptMethod FindAll {param($scope,$condition) return @(Get-ReplayDescendants $this.Key $scope $condition)} + $node|Add-Member ScriptMethod GetRuntimeId {return $this.RuntimeId} + $node|Add-Member ScriptMethod GetCurrentPattern {param($pattern) if($pattern-ne[Windows.Automation.ValuePattern]::Pattern){throw 'Unsupported replay pattern'};return [pscustomobject]@{Current=[pscustomobject]@{Value=$this.Value}}} $script:replayNodes[$row.key]=$node } foreach($node in $script:replayNodes.Values){if($node.ParentKey-and-not$script:replayNodes.ContainsKey($node.ParentKey)){throw 'Missing replay parent'}} @@ -57,10 +65,33 @@ foreach($fixturePath in @(Get-ChildItem -LiteralPath (Join-Path $PSScriptRoot 'f $browser=($null-ne$card)-and(Test-ReplayConversation $card)-and(Test-LiveBrowserPermissionCard $card) $generic=@($script:replayNodes.Values|Where-Object{$_.Current.ControlType-eq[Windows.Automation.ControlType]::Button-and$_.Current.IsEnabled-and-not$_.Current.IsOffscreen-and(Test-ButtonMatch $_.Current.Name)}|ForEach-Object{$_.Key}|Sort-Object) if($terminal-ne$fixture.expected.terminal-or$browser-ne$fixture.expected.browser-or($generic-join',')-cne(@($fixture.expected.generic|Sort-Object)-join',')){throw ('Original live decision differs from literal fixture expectation: '+$fixturePath.Name+' terminal='+$terminal+' browser='+$browser+' generic='+($generic-join','))} + # 2026-10-26:复用生产动作前复验;完整卡片不变时,目标值或控件身份变化都应拒绝旧批准。 + if($fixturePath.Name-in@('mcp-one-time.json','read-url-one-time.json')){ + $targetKind=if($fixturePath.Name-ceq'mcp-one-time.json'){'MCP'}else{'Read URL'} + $expectedTarget=@{Id=[string]$fixture.expectedTarget.id;Value=[string]$fixture.expectedTarget.value} + $target=$script:replayNodes[[string]$fixture.expectedTarget.key] + if(-not(Test-LiveApprovalCard $card $expectedTarget)){throw ($targetKind+' approval rejected original target')} + $targetChecks++ + $originalValue=$target.Value + try{$target.Value=if($targetKind-ceq'MCP'){'compiler-fs/other_tool'}else{'127.0.0.2'};if(Test-LiveApprovalCard $card $expectedTarget){throw ($targetKind+' approval accepted changed target value')}}finally{$target.Value=$originalValue} + $targetChecks++ + $originalRuntimeId=$target.RuntimeId + try{$target.RuntimeId=[int[]]@(42,2147483647);if(Test-LiveApprovalCard $card $expectedTarget){throw ($targetKind+' approval accepted changed target RuntimeId')}}finally{$target.RuntimeId=$originalRuntimeId} + $targetChecks++ + Write-Output ('PASS '+$targetKind+' target revalidation: original, changed value, changed RuntimeId') + } + # 2026-10-26:保留原生 Retry 的屏外状态,另直接调用名称判定,避免靠不可见性掩盖误匹配。 + if($fixturePath.Name-ceq'agent-error-retry.json'){ + $retry=@($script:replayNodes.Values|Where-Object{$_.Current.ControlType-eq[Windows.Automation.ControlType]::Button-and$_.Current.Name-ceq'Retry'}) + if($retry.Count-ne1-or-not$retry[0].Current.IsOffscreen){throw 'Native Retry fixture visibility changed'} + if(Test-ButtonMatch $retry[0].Current.Name){throw 'Native Retry name matched an approval'} + $retryChecks++;Write-Output 'PASS native Retry name exclusion independent of visibility' + } $snapshot=[pscustomobject]@{ButtonNames=[string[]]@($script:replayNodes.Values|Where-Object{$_.Current.ControlType-eq[Windows.Automation.ControlType]::Button}|ForEach-Object{$_.Current.Name});HasRadio=@($script:replayNodes.Values|Where-Object{$_.Current.ControlType-eq[Windows.Automation.ControlType]::RadioButton}).Count-gt0} $flags=Get-DiscoveryFlags $snapshot if(($terminal-and-not$flags.Terminal)-or($browser-and-not$flags.Browser)-or($generic.Count-gt0-and-not$flags.Generic)){throw ('Discovery dropped a live candidate: '+$fixturePath.Name)} $checks++;Write-Output ('PASS UIA decision replay: '+$fixturePath.BaseName+' provenance='+$fixture.provenance) } -if($checks-ne6){throw 'Incomplete six-layout replay set'} +if($checks-ne9){throw 'Incomplete nine-layout replay set'} +if($targetChecks-ne6-or$retryChecks-ne1){throw 'Incomplete target and Retry replay checks'} # 2026-10-26:新增行为断言不能因夹具分支遗漏而静默跳过。 Write-Output "___SCANNER_REPLAY_TEST_DONE___:passed=$checks"