# 验证手册(怎么确认"真的改对了") 3.2.15 的本地交付已完成:冻结产物 31 套 401 例回归与独立视觉复核通过,主 CM6 实装版额外完成 15 项关键交互验收。完整包身份、安装/卸载、便携版、备份及清理收据见 [3.2.15 验收记录](LOG-3.2.15.md)。源码回归与实装复验分别记录,保留中断及失败原记录。 这个项目改一行都可能踩坑,所以每处行为都有**可复跑的判据**。这份文档说明这套验证怎么跑, 以及**哪些取证方式不可信**(这部分比"怎么跑"更值钱)。 --- ## 一、测试挂点(产品内置,不用另外打补丁) 应用支持几个环境变量与启动参数,专为自动化取证准备: | 名称 | 作用 | |---|---| | `--remote-debugging-port=<端口>` | 开 CDP,探针用 Playwright 连上去做真实键鼠输入 | | `--user-data-dir=<目录>` | 独立配置目录:**并行跑多个实例**、互不干扰(务必每次都换新目录) | | `MP_DEBUG=1` | 把壳层 trace 写到 `userData/main-debug.log`(保存/窗口/会话等一手事实) | | `MP_TEST_NO_DIALOG=1` | 关掉原生对话框(**不设它,遇到保存框会把探针挂死**) | | `MP_TEST_SAVE_AS=<路径>` | 预先指定"另存为"的目标,等价于用户在弹框里选好 | | `MP_TEST_PICK_IMAGES=<路径>` | 同上,用于插入图片 | | `MP_EXPORT_PDF=<路径>` | 预先指定 PDF 导出目标 | | `MP_KEEP_STAGING=1` | 导出后保留临时 staging 目录(排查导出问题时看它) | 引擎侧另有一批**只读取证口**(`window.mpEngine.*`),例如: | 取数口 | 回答的问题 | |---|---| | `_view()` | 拿 CodeMirror 的 `EditorView`(选区、文档、行盒坐标…) | | `__inlineMarks()` | 行内标记清单(含 `hides` 隐藏区间、`uInner` 等;排"某类标记没投影") | | `__mermaid()` | 图表块清单 + 三层缓存的规模/命中(排"图没渲染/改了没更新/变慢") | | `imgCopyDiag()` / `cellCopyDiag()` | 复制路径的分支与次数(排"复制没带上图/表格") | | `prefs()` / `setPref()` / `applyPrefs()` | 外观偏好的读写(改设置后的观感回归) | > **加取数口的纪律**:只读、不改状态;名字以 `__` 开头;并在同一次改动里把"用它排障的判据"写上。 --- ## 二、探针怎么跑 `tools/probes/` 下是手写探针。**顶层那批开箱即用**(已做冒烟普查:124 支里 109 支在公开仓里 就能跑出结果 ✓);`tools/probes/dev-only/` 里那 15 支需要开发期的夹具/输入文件 (内部工作区 `.mp-verify/` 未随源码公开),**照抄思路可以、直接跑会缺文件** —— 该目录里有清单写明各缺什么。 典型形态: ```bash node tools/probes/probe-u-accept.cjs "path/to/MarkPilot.exe" ``` 约定: 1. **第一个参数=被测产物路径**(默认指向打包出来的 exe)。探针会在开头打一张**尺子卡** (产物 sha16、文档 sha、node 版本)—— 换产物之后读数不可比,这条卡就是证据。 2. 探针自己起应用、连 CDP、结束后 `taskkill` 收摊;**不需要**你先手动开应用。 3. 输入一律走真实通道(CDP 的 `Input.dispatchKeyEvent` / `dispatchMouseEvent` / `insertText`), **不允许**用 JS 造 DOM 事件代替 —— 合成事件测不出 keymap/layout 这类真问题。 4. 读数落到探针自己的目录(`result.json` 或日志),按**实际字段名**取,不要凭猜写过滤条件。 两个外部依赖(路径按你的机器改): - **Playwright-core**:探针里 `require('C:/Windows/System32/node_modules/playwright-core')` 之类写法是为了在无 `node_modules` 的目录里也能跑;换成你本地安装位置即可。 - **Chromium**:用应用自带的 Electron,无需另装浏览器。 --- ## 三、取证纪律(这一节是踩出来的,不是抄的) ### 1. 任何尺子先做体检 拿一个**已知答案**的输入走同一条通路,确认尺子读得出已知答案。 **实测教训**:我们曾用自写的 PNG 解码器和本机 `pdftoppm` 去判"导出的 PDF 是不是白纸", 两者都报"整页全黑" —— 拿一份已知白底的 PDF 一测,它们**同样报黑**,才知道是尺子坏了。 结论:**自写的解码/解析器默认不可信**;外部工具也会坏;至少两条独立口径一致才落账。 ### 2. "读数一字未变" = 这段没执行 改完代码后如果读数与上一版**逐字相同**,第一嫌疑是**新分支根本没进去**(守卫条件恒假、 或者被上游 `return` 挡死),而不是参数不对。做法:在分支入口埋一行自证 (`window.__xx = (window.__xx || 0) + 1`),复验时**先读它**再读行为。 ### 3. 结果比对只适合"我是唯一写入者" 同族模块共用同一个键时(表格、列表、代码块都处理 Tab),用"文档有没有变"判"我的处理器有没有生效" **必然误判**。要**分层**:先判"谁接管了"(自证计数),再判"接管者做得对不对"(用户可见性质)。 ### 4. 判据文本不许内嵌期望值的字面量 写"把 62 写进了所有项"这种判据,一旦数值规则变了,判据还在说 62, 读报告的人会得出**相反**的结论。判据应由数据生成("实测 X ∈ [min,max]")。 ### 5. 突变验证 "修好了"的结论,除了同一支探针复验,还要**把修复注掉再复跑**:判据必须转红; 注掉不变红 ⇒ 要么探针没鉴别力,要么你修的不是那个地方。 突变脚本要有**双向幂等**开关(`on|off`),验完立刻还原并复跑,最后用 `git status` 确认工作树干净。 ### 6. 夹具要与判据同源 - 光标**必须**放在"不触发源码态"的行上 —— 否则会把产品**既有契约**(光标在标记区间内就显示源码) 读成"没渲染"; - 文档形态一变,**所有坐标算式都要重算**; - 每臂开头**同时复位文档与光标**,并把复位结果打出来(上一臂的脏数据会连造几处假红)。 ### 7. 不要用"看起来像"当判据 搜"像哈希的 16 进制串"去替换,会命中源码里的占位符; 在打包产物里搜中文字面量永远 0 命中(构建会把非 ASCII 转义掉); 正则里 `.` 不匹配换行这件事,会让"挖 HTML 容器"只挖到开头。 **判"某段文本长什么样"就先把它打印出来看**。 --- ## 四、各族报告(`docs/verification/`) 这些是历次改动的验收报告:判据怎么写的、实测读数是多少、哪些臂没有鉴别力。 按文件名找族即可,常见几类: | 前缀 | 覆盖 | |---|---| | `long-doc-navigation` / `outline-modes` | 大文档导航、大纲面板 | | `table-daily-*` | 表格:按键、粘贴、选区、剪贴板 | | `math-daily-*` | 公式:点击定位、浮框、块公式折叠 | | `code-fence-*` | 代码围栏:行类、粘贴、剪贴板通道 | | `settings-truth-*` / `corrupt-config-*` | 设置的真源与脏数据 | | `path-edge-*` / `file-ops-*` | 文件路径边界、另存为、外部改名 | | `control-chars-*` / `rich-paste-*` | 控制字符嗅探、富文本粘贴 | | `theme-switch-*` / `reading-split-*` | 主题切换、阅读/分栏模式 | `docs/DEBUG-DB.md` 是开发期的**总台账**:每处改动的原因、判据、实测读数、 以及走错的路(那部分通常比结论更有用)。 --- ## 五、这份文档本身的来源 它整理自开发期的内部工作区(保存了每处改动的判据、读数、以及**踩过的坑**)。 与源码一起公开,是为了让别人**不用重走一遍**:想做二次开发的话, 把上面的取证纪律当成默认习惯,能省掉相当多的来回。 ## 六、3.2.5 可移植检查入口与误报纠正 运行方法见 [tools/quality](../tools/quality/README.md),本轮结果见 [审计记录](LOG-3.2.5-AUDIT.md)与[证据索引](verification/quality-3.2.5/README.md)。预检只解析历史 probe,不代表已执行它们。critical 当前保留真实产品失败,不应通过删判据把它改绿。 新的启动器自动独占 app-slot、创建独立 profile 并验证发布文件 SHA;不要再包一层同一把锁。旧 probe 可用 `scripts/acceptance/app-slot.cjs -- node tools/probes/.cjs ...`。产物、profile、模型会话放 `.mp-verify/`,公开证据工具只接收清单内哈希一致且限额的 PNG。 本轮加入两个尺子约束:Windows 重做用 Ctrl+Y,不能把 Ctrl+Shift+Z 未重做误报为产品缺陷;没有先约定输入中“再次按格式键”的含义,不能把每种格式都记成 bug。空包裹立即取消和已选中文本去格式则有明确判据。 关窗保护使用原生菜单回调 `BrowserWindow.close()`,同时跑“未保存 A 单独关窗仍在”的正向对照;不能用 CDP page.close 证明 beforeunload 有效。PDF 先校验当前文档独有标记与另一文档禁用标记,再打开最终 PDF 页面;文本提取的重复/交错应保留原文,不能偷偷归一化后判绿。 ## 七、CM6 日常操作链与执行成本(3.2.12) 以“定位/选中 → 编辑或复制剪切粘贴 → 撤销 → 重做 → 立即继续输入 → 保存并回读”为基本单位。孤立按钮通过不能证明链条通过;在重做与下一次输入之间插入等待、读数或强制 focus,会掩盖真实的焦点交接问题。 | 编辑表面 | 优先交接与边界 | 独立判据 | | --- | --- | --- | | 普通文字 | 上下翻页、正反选区、跨页拖选、查找返回 | 完整源码、anchor/head、真实焦点、原位续写、磁盘字节 | | 行内/块公式 | 渲染/源码切换、引用前缀、单击与拖拽分界、按住滚轮后松开 | 源码坐标映射;拖选抬起前后的范围;复制内容与相邻正文 | | 表格活动格 | 转义竖线、中文/emoji、原生菜单历史、搜索及换文件 | 可见格文字与 DOM 方向选区;提交边界的 CM 范围;同一文档历史 | | 查找与其他输入框 | 编辑控件与正文之间的历史归属 | 控件自己的值/焦点;正文及其待插入任务是否保持 | | 图片插入 | 保存/未保存、路径特殊字符、迟到响应、撤销取消、比例预设 | 实际图片字节先落盘;可解析地址、解码结果、源码与重开结果 | 每条高风险链至少区分键盘、鼠标、修饰键组合、键鼠切换、按下移动松开。先选用户常用与历史失败的交接,补相邻正对照;不把所有维度的笛卡尔积变成重复跑数。点击坐标先用当前真实布局标定,再用被动事件证明命中对象。实际命中后功能没有交接成功,应记产品失败;未成功启动或没有命中目标才记入口未确认。 格覆盖层的 DOM 选区可以领先 CM,输入与提交边界才要求映射一致。测试不得每步替产品 dispatch、修复选区或抢回焦点。来源是 CDP 的 trusted 键鼠事件,也不等于操作系统的物理输入;合成 compositionend、普通 Unicode insertText 均不能证明系统输入法候选流程。 执行顺序为:小夹具确认旧产物失败与控制臂 → 同一复核会话看原失败和方案 → 按独立根因合并修复 → 定向回归 → 一次冻结全量回归 → 每根因删除机制变红并逐字节恢复变绿 → 新会话核验真实图片与五通道 → 打包及实际安装边界。单个根因的尺子或输入条件改变,只重跑受影响臂;产品或冻结输入改变,重新建立对应完整门禁。用例总数和重复安装复验不是覆盖率。 突变操作只在拥有的一次性副本中执行,核对原始完整 ASAR、运行文件、资源、源码、探针和夹具身份;不在用户已有修改的工作树上要求“恢复干净”,也不使用 checkout/stash/clean 清掉用户工作。GUI 与性能期间独占 app-slot 和剪贴板,不并行构建、复制大目录或启动另一批 GUI。模型助手可并行做纯文本审查。 公开入口在 [tools/quality](../tools/quality/README.md),3.2.12 证据在 [quality-3.2.12](verification/quality-3.2.12/source-suites.json)。例如复验公式手势(输出目录应为新的空目录): ```powershell $verifyExe = "C:\path\to\MarkPilot.exe" $verifySha = (Get-FileHash -LiteralPath $verifyExe -Algorithm SHA256).Hash.ToLowerInvariant() node tools/quality/math-gesture-workflows.cjs --exe "$verifyExe" --expected-sha256 "$verifySha" --out ".mp-verify/gesture-recheck" ``` 新启动器内部持锁,不再套外层锁。大型中间产物、用户原文/配置/注册信息、原始剪贴板保持私有;公开合成夹具的探针、简化结果、根因对照和限额图片。性能分开记录首笔与稳态分位数,以及正文文末、公式内部、活动格等实际编辑位置;不同位置、不同指标的数字不混用。