docs: design N15 selector report hardening

This commit is contained in:
石头
2026-07-09 17:04:19 +08:00
parent 83585d6696
commit 2609b2028d

View File

@@ -0,0 +1,266 @@
# N15 Selector Report Hardening Design
日期2026-07-09
## 1. 背景
N14 已在 `main` 上合并并推送,提供了离线 UIA selector 报告能力:
- `internal/uiaselector/report.go`报告模型、catalog report generator、JSON/Markdown renderer。
- `cmd/uia-selector-report/main.go`:离线 CLI只接受 dump 文件路径和输出报告路径。
- `scripts/verify-n14-uia-selector-report.ps1`:运行测试、生成报告并做基础安全扫描。
N14 最终 review 留下两个非阻塞建议:
1. `StrictExitAmbiguous` 分支缺少显式单测。
2. verifier 的输出安全检查仍以字符串扫描为主,并且运行 CLI 前没有清理旧输出文件。
N15 的目标是把这些 review 建议收口为一个小型加固节点,不扩展产品能力,不接触 live UI不新增 helper op不新增 MCP tool不扩展 live action surface。
## 2. 目标
N15 只做报告安全与验证加固:
1. 增加 `StrictExitAmbiguous` 显式单测,锁定 `ambiguous_count > 0` 时严格模式返回 `3`
2. 加固 `scripts/verify-n14-uia-selector-report.ps1`,避免旧报告掩盖 CLI 输出回归。
3. 增加输出文件检查:必须存在、必须是文件、大小必须大于 0。
4. 增加 JSON 字段 allowlist 检查,确认 `selectors[].matches[]` 只包含允许的结构字段。
5. 保持 N14 报告格式和 CLI 参数不变。
## 3. 非目标
N15 不做以下事项:
```text
新增 selector
修改 selector catalog
修改报告 JSON/Markdown 格式
新增 CLI 参数
新增 helper op
新增 MCP tool
连接内网 iSphere
live dump 当前窗口
使用 hwnd/process 参数
点击控件
输入文本
打开会话
搜索联系人
发送消息
发送文件
接收文件
读取聊天内容
导出完整 visible text
```
N15 也不清理本地分支。分支清理可以在 N15 合并后单独执行。
## 4. 推荐方案
采用“小范围加固”方案,只修改两个文件:
```text
internal/uiaselector/report_test.go
scripts/verify-n14-uia-selector-report.ps1
```
不修改:
```text
native/ISphereWinHelper/Program.cs
internal/tools/winhelper.go
cmd/isphere-mcp/main.go
cmd/uia-selector-report/main.go
internal/uiaselector/report.go
```
理由N14 的实现逻辑已经通过最终验证和 review。N15 只需要补测试和 verifier 防回归能力,不需要改变生产代码路径。
## 5. Ambiguous strict-mode 测试设计
`internal/uiaselector/report_test.go` 中新增测试:
```text
TestStrictModeClassifiesAmbiguousMatches
```
测试构造一个最小 UIA tree
```text
root Window frmMain
child Pane duplicate
child Pane duplicate
```
测试 catalog 只包含一个 read-only selector
```text
id: duplicate_panel
allowed_use: read_only_locate
required:
automation_id: duplicate
control_type: Pane
framework_id: WinForm
```
期望:
```text
report.OK == true
report.CatalogSize == 1
report.MatchedCount == 1
report.UnmatchedCount == 0
report.AmbiguousCount == 1
report.Selectors[0].MatchCount == 2
StrictExitCode(report) == StrictExitAmbiguous
```
该测试只使用内存构造的 `Node`,不依赖 N12R fixture也不涉及 visible text。
## 6. Verifier 加固设计
### 6.1 清理旧输出
`go run ./cmd/uia-selector-report` 之前删除旧报告:
```powershell
Remove-Item -LiteralPath $jsonPath, $mdPath -Force -ErrorAction SilentlyContinue
```
目的:如果未来 CLI 回归为“退出 0 但未写新文件”,旧报告不会让后续存在性检查误通过。
### 6.2 文件存在和非空检查
替换当前 `Test-Path` 检查为更严格的文件检查:
```powershell
$jsonItem = Get-Item -LiteralPath $jsonPath -ErrorAction Stop
if (-not $jsonItem.PSIsContainer -eq $false) { ... }
if ($jsonItem.Length -le 0) { ... }
```
实际实现应避免 PowerShell 运算优先级歧义,推荐使用清晰表达:
```powershell
if ($jsonItem.PSIsContainer) { throw "JSON report path is not a file: $jsonPath" }
if ($jsonItem.Length -le 0) { throw "JSON report is empty: $jsonPath" }
```
Markdown 报告同样检查。
### 6.3 JSON match 字段 allowlist
解析 JSON 后,对每个 `selectors[].matches[]` 对象检查字段集合,只允许:
```text
path
control_type
automation_id
class_name
framework_id
has_name
name_length
```
禁止出现:
```text
name
value
text
visible_text
```
实现策略:
1. 遍历 `$report.selectors`
2. 遍历 `$selector.matches`
3. 对每个 match 的 `$match.PSObject.Properties.Name` 做集合比较。
4. 遇到不在 allowlist 中的字段立即失败。
5. 如果字段名是 `name``value`,错误信息明确指出这是 visible text 字段泄漏。
这比单纯扫描字符串更稳,因为它检查的是 JSON 对象结构。
### 6.4 保留字符串扫描
保留 N14 已有字符串扫描,用于发现:
```text
"name"
"value"
国网甘肃省电力公司
send_after_approval
send_file_after_approval
receive_file_after_approval
open_conversation
InvokePattern
ValuePattern.SetValue
```
字段 allowlist 和字符串扫描同时存在:
- allowlist 负责结构化字段泄漏。
- 字符串扫描负责已知敏感文本和动作词防线。
## 7. 测试与验证策略
N15 implementation plan 必须采用 TDD。
最小验证命令:
```powershell
go test ./internal/uiaselector -run "TestStrictModeClassifiesAmbiguousMatches" -count=1
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-n14-uia-selector-report.ps1
go test ./...
```
最终验证命令:
```powershell
go test ./internal/uiaselector ./cmd/uia-selector-report -count=1
go test ./...
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-n14-uia-selector-report.ps1
git diff --name-only main..HEAD -- native/ISphereWinHelper/Program.cs internal/tools/winhelper.go cmd/isphere-mcp/main.go
```
最后一条命令期望无输出。
## 8. 风险与缓解
### 风险 1PowerShell 对单元素数组展开造成遍历差异
缓解:实现遍历时使用 `@($report.selectors)``@($selector.matches)`,确保单元素和多元素都按数组处理。
### 风险 2allowlist 检查误报 PowerShell 内置属性
缓解:只读取 `$match.PSObject.Properties.Name`,这返回 JSON 对象自身属性,不读取方法或扩展成员。
### 风险 3旧输出清理误删非目标文件
缓解:只删除两个明确计算出的文件路径:
```text
runs/n14-selector-report/n12r-selector-report.json
runs/n14-selector-report/n12r-selector-report.md
```
不递归删除目录。
## 9. 验收条件
N15 可接受条件:
1. `StrictExitAmbiguous` 有显式单测。
2. verifier 运行 CLI 前删除旧 JSON/Markdown 输出文件。
3. verifier 检查 JSON/Markdown 输出存在、是文件、非空。
4. verifier 对 JSON `matches[]` 执行字段 allowlist 检查。
5. verifier 继续扫描 `"name"``"value"`、已知 visible text 和动作词。
6. `go test ./...` 通过。
7. `scripts\verify-n14-uia-selector-report.ps1` 通过。
8. 不修改 helper/MCP/live action 文件。
## 10. 推荐下一步
下一步写 N15 implementation plan拆分为两个小任务
1. TDD 增加 ambiguous strict-mode 单测。
2. 加固 verifier 的旧输出清理、文件检查和 JSON 字段 allowlist。