diff --git a/CMakeLists.txt b/CMakeLists.txt index 779a9998..a227e991 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,6 +324,7 @@ if(BUILD_TESTING) if(WIN32) termforge_add_python_test(research_guards -m unittest discover -s research/tests -v) termforge_add_python_test(windterm_cmd_contract -m unittest benchmarks.tools.test_windterm_cmd -v) + termforge_add_python_test(windterm_c0_settings_contract -m unittest benchmarks.tools.test_windterm_c0_settings -v) termforge_add_python_test(receive -m unittest experiments.receive.test_receive -v) termforge_add_python_test(conpty -m unittest experiments.conpty.test_conpty -v) termforge_add_python_test(conpty_ctrl_c -m unittest experiments.conpty.test_ctrl_c -v) diff --git a/benchmarks/tools/WINDTERM-C0-SETTINGS.md b/benchmarks/tools/WINDTERM-C0-SETTINGS.md new file mode 100644 index 00000000..56d32926 --- /dev/null +++ b/benchmarks/tools/WINDTERM-C0-SETTINGS.md @@ -0,0 +1,54 @@ +# #133:本地 cmd 实际网格与设置读取诊断 + +状态:生产源 `28f293a5d2c01954dc8b418a02231603128955a6`、准备 HEAD `b22293f` 经复核后,由 root 监督一次独立实际执行,CLI 已结束、Job 已清理。[结果](results/2026-10-29-c0-settings-reference/README.md)为 partial:producer console 和原版 view 的窗口均读到 120×40,其他 C0 设置仍有未知。[原 false proposal](results/2026-10-29-c0-settings-proposal/plan.json)保持不变;另建的[授权计划](results/2026-10-29-c0-settings-reference/plan.json)与新原件分开绑定,不改旧 #119 证据。 + +[#133](https://github.com/JavaZeroo/termforge/issues/133) 的父项为 #3,真实前置 #119 已关闭。沿用其 14 行/536B ASCII 全文 oracle、原版本地 cmd→producer、OwnedJob 和非输入 desktop 边界;本项只调查请求配置是否有实际读取路径,不取得完整 C0、历史淘汰或性能验收。 + +## 静态线索、请求与实际值 + +下表的默认值来自固定官方资源,是静态线索,均不填入 actual: + +| 配置 key | 静态值 | 本次请求 | 请求写入位置 | +|---|---|---|---| +| `window.autoAdjustRowsAndColumns` | true | false | 独立 synthetic `user.sessions` | +| `window.columns` / `window.rows` | 80 / 24 | 120 / 40 | 同上 | +| `window.scrollbackLimit` | 999999 | 10000 | 同上 | +| `text.fontFamily1` | Roboto Mono | Cascadia Mono | 自有副本 `app/global/wind.config` | +| `text.fontSize` | 10 | 12 | 同上 | +| `terminal.autoComplete.enabled` | true | false | 同上 | + +官方 `terminal/configs/statusbar.config` 对当前 view 调用 `terminal.activeView()` / `terminal.view()`,并读取 `view.windowRows/windowColumns`。`terminal/configs/session.config` 和 `global/wind.config` 提供上述 key;这不能证明 profile 请求已消费或当前网格为 120×40。 + +实际取证分开保留:producer 的 console window/buffer 行列与模式;原版 view 的 windowRows/windowColumns;document 的 screenLineCapacity;QWidget 的 geometry、QFont family/pointSizeF/pixelSize。capacity 不替代 PTY rows,QWidget 字体不证明自绘 glyph、真实 fallback 或字体文件身份。Cascadia Mono 缺失时只记录可读返回或 unknown,不安装字体、不调整用户 DPI。 + +## 受控配置差异 + +[新 runner](windterm_c0_settings.py)先调用固定 #119 的 `prepare_run()`:完整核对原 4,047 文件清单后才修改自有副本。仅 `global/wind.config` 的三个声明 key 允许变化,其他原发行文件必须逐一保持 SHA 相同;synthetic session 只追加上述四个请求字段。 + +该 global 文件含 UTF-8 BOM、CRLF 和内联 `//` 注释,不能当严格 JSON 重写。离线准备发现并保留了这个格式事实。补丁仅替换三条唯一匹配行的标量值 token,保留 BOM、注释、空白及其他所有字节。计划固定修改前 SHA 与预计修改后 SHA;执行时再记录实际前后 SHA、逐 key 的带类型旧/请求值、原/配置后完整 manifest SHA 和唯一差异文件集合。离线摘要重新从原始字节计算补丁,核对输出精确相同。 + +因此身份是**固定二进制及原发行物基线 + 明确的自有配置差异**,不称整个包未改。对其他字段的改动、缺失/重复 key 或格式不符均在启动前拒绝。原始配置文件和副本只留 local,不提交资源内容。 + +## 独立源与公共 helper + +旧 `windterm_cmd.py`、`qt_cmd_diagnostic.js` 保持逐字节不变,SHA 分别为 `e517f8cab36d55e5d4bdda3fc14d4641e5ce6256991af04762dbf43c25961297`、`9ed0d6ee6bd6e82bae6d9a6802690b3b0b83f437c7b20f0333008b5f6c214756`。新 runner 只调用旧工具的公开 workload/producer、全文 oracle、设置投影和清理 helper,不改 base 全局变量或伪装旧 run。 + +[新原生启动边界](../../research/ui/cmd_owned_launcher.py)仅负责独立 desktop、suspended→Job→resume、公共 REA CLI 等待及最终清理。新 probe 由固定旧 Qt helper 前缀(`function state(){` 之前)与[新 driver](../../research/ui/qt_cmd_settings.js)组成,不复制旧完整 runner。九个实际 source SHA、两个模板 SHA、旧前缀 SHA、组合模板 SHA 均在计划中;运行后另记替换 run_id/thread/目录参数后的完整注入脚本 SHA,离线精确重建比较。 + +Oracle 输入和 run_id 在计划中预先固定。另建授权计划时可用 `--run-id` 保留已 review 的 nonce 与全部 536B 输入;必须使用新计划文件和唯一输出,不能修改旧 false proposal。producer 仍先发送固定清屏序列后写完整 payload;producer 的计数器不作为终端消费或呈现端点。 + +## 有界只读发现与出口 + +新 probe 仅调用已有静态依据的无参 current-view 读取,优先 `activeView()`、再 `view()`;窗口指标只读属性,不调用未知 getter 方法。设置发现限定四个 owner × 七个固定 key,共 28 项属性读取;另对每个 owner 保留最多 128 项外显遍历范围内的 13 名白名单命中。发现函数名不会执行 `getSetting()`、`setting()` 或 `value()`。此限制不保证 QJSEngine 内部枚举耗时,因此没有观测开销结论。 + +每条实际读取保留 status/type/value。owner 缺失、undefined、非标量、读取异常、类型不符、非有限数值或不可公开字符串均为 null 并保留原因。未知值不替换成请求或静态默认。实际读到其他行列/字体,也按实际返回记录,不改窗口、ROI 或等待预算追求请求值。 + +最多一次原版执行,REA capture=30 秒、CLI wait=45 秒。原版更早启动并另等 2 秒,准备及回收另计,整程上限为 null。禁止用户输入桌面、剪贴板、个人会话或显示配置操作。执行者需等待 CLI 真正结束及 Job active=0 后再审计。结构/身份失败保留原件和停止,不重跑;配置只部分可读时,合法摘要明确为 `partial-settings-diagnostic`。 + +公开 CLI 在全部校验完成后才创建输出,只保留完整文本校验结果、合成输入、白名单标量与哈希;原日志、像素、PID、路径、profile 和原资源留 local。producer 按 run_id/负载关联,不声称已独立核验进程祖先。consumer_done/presented_done=null,C0 可见且无遮挡、完整 C0、10000 行淘汰、UTF-8/其他开关全量验证、字体文件/fallback、扰动和性能基线均未知,不关闭 #3/#4。 + +## 离线验证 + +[公共测试](test_windterm_c0_settings.py)覆盖严格 14 行 oracle、请求/实际/未知分离、全 manifest 先验、仅三 token 的 BOM/注释字节保留、配置和发现白名单、普通/`-O` CLI 拒绝 false plan 且不创建输出、cmd 元字符提前拒绝,以及合成 WinAPI 边界的 Job-before-resume 和 CLI 异常回收。[合成 QJSEngine 边界](../../research/ui/test_cmd_settings_probe.js)执行实际新读值逻辑,验证有限枚举、异常值抑制和不调用未知方法。 + +离线新工具普通及 `python -O` 各 14 项通过;旧工具 12 项保持通过,Debug 配置 CTest `windterm_cmd_contract` / `windterm_c0_settings_contract` 2/2 通过;组合注入脚本另经 Node 语法检查。这些工具测试没有启动原版。其后唯一实际执行及独立离线重算见结果;九个生产源、false proposal、正常/优化导出及全部原件保持不变。本项仍不提供完整 C0 或性能通过事实。 diff --git a/benchmarks/tools/results/2026-10-29-c0-settings-proposal/plan.json b/benchmarks/tools/results/2026-10-29-c0-settings-proposal/plan.json new file mode 100644 index 00000000..4e9ffe07 --- /dev/null +++ b/benchmarks/tools/results/2026-10-29-c0-settings-proposal/plan.json @@ -0,0 +1,93 @@ +{ + "schema": "termforge.windterm-c0-settings.v1", + "issue": 133, + "execution_authorized": false, + "maximum_original_runs": 1, + "source_base_commit": "28f293a5d2c01954dc8b418a02231603128955a6", + "run_id": "e736ad1ca79c4cfc85d468fdd5e5899e", + "rea_capture_seconds": 30, + "cli_wait_timeout_seconds": 45, + "whole_lifecycle_bound_seconds": null, + "module_sha256": { + "WindTerm.exe": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "Qt5Core.dll": "8d2ff4ce9096ddccc4f4cd62c2e41fc854cfd1b0d6e8d296645a7f5fd4ae565a", + "Qt5Gui.dll": "5e7d2d41b8b92a880e83b8cc0ca173f5da61218604186196787ee1600956be1e", + "Qt5Widgets.dll": "3788c669d4b645e5a576de9fc77fca776bf516d43c89143dc2ca28291ba14358", + "Qt5Qml.dll": "30035484c81590976627f8face9507caa8581a7dc7630cccf6a8d6de65cab707" + }, + "baseline_files_manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203", + "requested_session": { + "window.autoAdjustRowsAndColumns": false, + "window.columns": 120, + "window.rows": 40, + "window.scrollbackLimit": 10000 + }, + "requested_global": { + "text.fontFamily1": "Cascadia Mono", + "text.fontSize": 12, + "terminal.autoComplete.enabled": false + }, + "static_resource_sha256": { + "global/wind.config": "0a4f02250b855e9209960f934892f30e3b4b800c0c4c80cea3acd8af527ca177", + "terminal/configs/session.config": "e97009e1c57ba17709bc52738c964df18341cfc0c24895f76821b9a6141803cd", + "terminal/configs/statusbar.config": "a4f7df8f9f215eeba227714438c0ee15070cc85921ab1ebd88d991ad4efef25d" + }, + "static_config_hints": { + "evidence_kind": "static-only", + "global": { + "text.fontFamily1": "Roboto Mono", + "text.fontSize": 10, + "terminal.autoComplete.enabled": true + }, + "session": { + "window.autoAdjustRowsAndColumns": true, + "window.columns": 80, + "window.rows": 24, + "window.scrollbackLimit": 999999 + } + }, + "expected_configured_global_sha256": "01a5465e6b4dec6567ac4cf3d21df360f09b737f77c0861ec3f250400806081d", + "source_files_sha256": { + "benchmarks/tools/windterm_c0_settings.py": "609614b51f20fd76ba8da0a5cc82bd47c45b8f3715c906050a258e557ed50970", + "research/ui/qt_cmd_settings.js": "b0c008afd2318ae60cdf3a8f3f3cc87cc042db9bb03013c09246ba1a498f866b", + "research/ui/cmd_owned_launcher.py": "20f38736fa14600b5c659d9e14bfb150932639af1326d2bbe04fcfe6cd32c357", + "benchmarks/tools/windterm_cmd.py": "e517f8cab36d55e5d4bdda3fc14d4641e5ce6256991af04762dbf43c25961297", + "research/ui/qt_cmd_diagnostic.js": "9ed0d6ee6bd6e82bae6d9a6802690b3b0b83f437c7b20f0333008b5f6c214756", + "research/ui/launch.py": "6970e0d4f2fe5e7ec4827df7aa1026cd0e74aeb828cdc9b7a0b42b3c887eb4e6", + "research/ui/r1_launch.py": "54fb41872c6abcc48242a0938044184854b016785cc13659c66b1bf404da0d8a", + "benchmarks/tools/play.py": "1c83f400d45f94f4d7100c177c7cf42f98d0add037c5d31f232092f4dd6b9fea", + "experiments/conpty/run.py": "c8eef1e36b6d200b2d508cef91eaa5ca79a91fa87c5daf6265b2475c31dce329" + }, + "composed_template_sha256": "314e7320146bb3422e4488a41b9af1bc22b52c920b6b73e078d2c0e0761017fc", + "helper_prefix_sha256": "ca8057c1979d700957eceb829e12b2d866c64eff7d3ae04daa9c95abea11299e", + "workload": { + "lines": [ + "TF-CMD-BEGIN e736ad1ca79c4cfc85d468fdd5e5899e", + "TF-CMD-000 35f0debf71dd0844cfac4f9d", + "TF-CMD-001 247605d13990f0ed8aec7035", + "TF-CMD-002 06b01e9778fea56e4ef334f1", + "TF-CMD-003 a05831d81fe13dac18746dbb", + "TF-CMD-004 1e2f6b1e2d6156984fd38e68", + "TF-CMD-005 8f66fae705ec7fc7c0a8b766", + "TF-CMD-006 54bf94fa52a55e12489c166b", + "TF-CMD-007 740d7c69a480613408434292", + "TF-CMD-008 febc0660156a697069d422b9", + "TF-CMD-009 e658d25af22aefd3ada53833", + "TF-CMD-010 6e781bafc07a68895a2674b4", + "TF-CMD-011 8cb1e7d5def3ce44b479e072", + "TF-CMD-END e736ad1ca79c4cfc85d468fdd5e5899e" + ], + "payload_hex": "54462d434d442d424547494e2065373336616431636137396334636663383564343638666464356535383939650d0a54462d434d442d303030203335663064656266373164643038343463666163346639640d0a54462d434d442d303031203234373630356431333939306630656438616563373033350d0a54462d434d442d303032203036623031653937373866656135366534656633333466310d0a54462d434d442d303033206130353833316438316665313364616331383734366462620d0a54462d434d442d303034203165326636623165326436313536393834666433386536380d0a54462d434d442d303035203866363666616537303565633766633763306138623736360d0a54462d434d442d303036203534626639346661353261353565313234383963313636620d0a54462d434d442d303037203734306437633639613438303631333430383433343239320d0a54462d434d442d303038206665626330363630313536613639373036396434323262390d0a54462d434d442d303039206536353864323561663232616566643361646135333833330d0a54462d434d442d303130203665373831626166633037613638383935613236373462340d0a54462d434d442d303131203863623165376435646566336365343462343739653037320d0a54462d434d442d454e442065373336616431636137396334636663383564343638666464356535383939650d0a", + "payload_bytes": 536, + "sha256": "2bf0d9bc3e5b296fb7484c8cca86491d6c307f054b43cd37b81845d8b612b7d8" + }, + "scope": "Non-input-desktop small-cmd settings read diagnostic, not C0 visibility, history eviction, terminal timing or performance acceptance", + "actual_original_runs": 0, + "actual_settings": null, + "consumer_done": null, + "presented_done": null, + "c0_verified": false, + "history_eviction_verified": false, + "performance_gate": "not_evaluated", + "private_plan_sha256": "e8813242dd87c001b03b63dc42690c2996ae5d127ac7ed399394abeb6c51cb18" +} diff --git a/benchmarks/tools/results/2026-10-29-c0-settings-reference/README.md b/benchmarks/tools/results/2026-10-29-c0-settings-reference/README.md new file mode 100644 index 00000000..547a3e47 --- /dev/null +++ b/benchmarks/tools/results/2026-10-29-c0-settings-reference/README.md @@ -0,0 +1,48 @@ +# #133:原版本地 cmd 网格已读取,C0 配置仍为 partial + +固定 WindTerm 2.7.0 的一次有界本地 cmd 诊断中,producer 的实际 console window 为 **120×40**,原版 `terminal.activeView()` 返回对象的 `windowColumns/windowRows` 也为 **120/40**。14 行/536B oracle 全文及 1 条空尾行匹配。document 的 `screenLineCapacity=23` 单独记录,不能替代 PTY 或 view 的行数。 + +这只支持本次两个窗口读取点及全文正确性。QWidget 元数据为 Roboto Mono 9pt / pixelSize -1;它不证明终端实际 glyph 使用该字体,也不能据此断言 Cascadia Mono 12pt 请求失效。实际 glyph/fallback/字体文件身份、10000 行历史上限的有效性、自动补全关闭是否生效、完整 C0、消费/呈现和性能仍未知。 + +Refs [#133](https://github.com/JavaZeroo/termforge/issues/133)、[#3](https://github.com/JavaZeroo/termforge/issues/3)、[#4](https://github.com/JavaZeroo/termforge/issues/4)。本记录不自动关闭这些任务,不改变产品路线或验收容差。 + +## 固定执行与配置差异 + +- 生产源 `28f293a5d2c01954dc8b418a02231603128955a6`,执行准备 HEAD `b22293f7fbc78c282fa01b12d28e53e5e2ff00c9`;[工具及方案](../../WINDTERM-C0-SETTINGS.md)。九个实际 source 文件及两个 Qt 模板、旧 helper 前缀、组合模板和完整注入脚本分别绑定哈希。旧 #119 runner/probe 没有修改,没有 monkeypatch base 全局或伪装旧 run。 +- [新授权计划](plan.json)的私有原件 SHA 为 `d1678750af02c27e60dec373b38ca49cca8cedd254a0c3708ea9e1b841494afc`,使用原 review nonce `e736ad1ca79c4cfc85d468fdd5e5899e` 和完整 536B 输入。[原 false proposal](../2026-10-29-c0-settings-proposal/plan.json)保持原样;实际只执行一次,没有补跑。 +- 固定官方 EXE SHA 为 `7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`。原 4047 文件清单 SHA 为 `e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203`;五个实际加载模块的完整文件 SHA 和模块路径守卫已核对。 +- 自有副本只修改 `global/wind.config` 三个唯一标量 token:`text.fontFamily1="Cascadia Mono"`、`text.fontSize=12`、`terminal.autoComplete.enabled=false`。BOM、CRLF、注释及所有其他字节保留;实际修改后 SHA 为 `01a5465e6b4dec6567ac4cf3d21df360f09b737f77c0861ec3f250400806081d`。其余 4046 个原发行文件逐一未变,受控差异的 key/前后 SHA、配置后完整清单 SHA 见[原样白名单出口](validation.json)。身份是固定二进制/原发行物基线加受控配置差异,不称整个包未改。 +- synthetic `user.sessions` 请求 `window.autoAdjustRowsAndColumns=false`、columns=120、rows=40、scrollbackLimit=10000。该会话文件与请求身份一致,但写入字段不等于设置已被消费;静态默认与实际读数仍分开。 + +## 实际读值 + +| 读取点 | 结果 | 解释边界 | +|---|---|---| +| producer console window | columns=120,rows=40 | 实际 console API 返回 | +| producer console buffer | columns=120,rows=3000 | 不是 WindTerm 保留历史上限 | +| producer mode | 原 mode=3,VT 启用成功 | 不表示完整终端语义或性能通过 | +| `terminal.activeView()` | 实际返回 object;windowRows=40、windowColumns=120 | getter 属性读值,未用默认值补造 | +| document | screenLineCapacity=23 | 独立文档容量读点,不能当 PTY rows | +| QWidget | geometry=`[0,0,905,428]`;QFont family=Roboto Mono、pointSizeF=9、pixelSize=-1 | 控件元数据,不是终端绘制字体或 glyph/fallback 证据 | +| 28 条 dot-key 配置读取 | 21 条 unavailable、7 条 owner-unavailable;值均 null | 未读出有效配置,不填请求或静态默认 | +| 有限名字枚举 | 前 3 个 owner 各保留 128 项范围并截断;仅 active_text/active_view 命中 screenLineCapacity/windowColumns/windowRows,terminal 无命中;global_settings owner 不可用 | 不能据截断名单认定其他 API 不存在,也没有执行名字命中的未知方法 | + +没有将 console buffer 3000 解释为 scrollbackLimit,也没有把 QWidget 的 9pt 与配置请求 12pt 当作终端 glyph 的直接比较。字体请求是否作用于终端绘制仍未知,不安装个人宿主字体或改变 DPI。 + +## 全文、CLI 与回收 + +producer 按固定 run_id 和 payload SHA 关联,独立重建 BEGIN、12 条编号 token、END 共 14 行和 CRLF 536B。原版一次 `settings_complete` 的 15 条 `lineText` 恰为完整 14 行及空尾行,没有 trim、跳过前缀或只检查 END marker。consumer_done/presented_done 均为 null。 + +REA Evidence ID 为 `ev_68b4214467f4886a31fd9f0a8292c26b83cbaa4801bc63a4109b4a0d84fce6ac`,无脚本错误、截断、清理错误或 stderr。root 监督实际 CLI session 至 exit=0 后才交离线收尾;没有活 CLI 交接。OwnedJob 为 active=0、total=14、terminated=0,run_id/原版 target PID 绑定一致,profile 仍只有一个合成会话。启动器主动结束原版,Job=0 不证明自然 GUI 退出,producer 祖先关系未独立观测。 + +capture=30 秒、CLI wait timeout=45 秒;目标更早启动并另等 2 秒,准备与回收另计,整程上限为 null。原版运行在非输入 desktop,不能满足 C0 可见且无遮挡条件;这是插桩正确性/配置读取诊断,没有观测开销或性能结论。 + +## 独立离线审计与发布身份 + +[独立重算摘要](audit.json)在不调用生产 summarizer 作为断言依据的条件下,分别核对了准备提交及当前九源、运行时五模块、组合及完整注入 JS、完整 oracle、4047 文件清单、三项 literal token byte patch、session 请求文件、原始实际读取、13 份 raw 哈希和清理等式。没有再次运行原版,也未修改计划、source 或任何原件。 + +原普通/`python -O` 出口逐字节相同,SHA 为 `2e1e2dbfffb3f2f3ed914c032b1eb0c0d859734f54f6f216215e6be29c22e3d4`;公开 [validation.json](validation.json)直接保留普通出口原字节,未添加或改写字段。计划与额外审计分别放在独立文件。源码随后合并 main `5f7373f`,九个生产源仍与准备 HEAD 相同;发布源码不反填为执行源码。 + +工具公共边界测试仍分列:新工具普通/优化解释器各 14 项、旧工具 12 项;Debug CTest 两项通过。它们使用合成 Qt/WinAPI、CLI 拒绝和文本 oracle,不计作额外原版样本。raw 日志、PID、路径、原资源、profile 与注入原件留 local;公开只有合成输入、白名单标量及 hash。 + +下一最小取证点是先静态确认当前 view/document 的有效配置读取接口,以及能绑定合成文本的绘制字体读取点,再做独立有界观测。未经核实的方法不能靠枚举名执行;历史 10000 淘汰另需完整编号负载与淘汰 oracle。当前不扩展此批,不以新增读取点消除完整 C0、glyph、autocomplete、历史或性能的未知。 diff --git a/benchmarks/tools/results/2026-10-29-c0-settings-reference/audit.json b/benchmarks/tools/results/2026-10-29-c0-settings-reference/audit.json new file mode 100644 index 00000000..0958af45 --- /dev/null +++ b/benchmarks/tools/results/2026-10-29-c0-settings-reference/audit.json @@ -0,0 +1,75 @@ +{ + "schema": "termforge-c0-settings-independent-audit-v1", + "issue": 133, + "execution_preparation_commit": "b22293f7fbc78c282fa01b12d28e53e5e2ff00c9", + "production_source_commit": "28f293a5d2c01954dc8b418a02231603128955a6", + "actual_original_runs": 1, + "additional_original_runs": 0, + "plan_sha256": "d1678750af02c27e60dec373b38ca49cca8cedd254a0c3708ea9e1b841494afc", + "original_normal_export_sha256": "2e1e2dbfffb3f2f3ed914c032b1eb0c0d859734f54f6f216215e6be29c22e3d4", + "normal_optimized_byte_identical": true, + "evidence_id": "ev_68b4214467f4886a31fd9f0a8292c26b83cbaa4801bc63a4109b4a0d84fce6ac", + "checks": { + "nine_sources_and_prepared_commit": true, + "full_runtime_module_hashes": true, + "prefix_and_two_templates": true, + "exact_injected_script": true, + "independent_full_text_oracle": true, + "whole_original_manifest": true, + "exact_three_token_global_byte_patch": true, + "synthetic_session_requests": true, + "actual_readouts_equal_raw": true, + "owned_cleanup_and_profile": true, + "all_public_raw_hashes": true, + "public_scalars_only": true + }, + "distribution": { + "baseline_files": 4047, + "changed_original_files": [ + "global/wind.config" + ], + "unchanged_original_files": 4046, + "actual_global_after_sha256": "01a5465e6b4dec6567ac4cf3d21df360f09b737f77c0861ec3f250400806081d" + }, + "text": { + "payload_bytes": 536, + "payload_lines": 14, + "observed_lines": 15, + "empty_tail_lines": 1 + }, + "actual": { + "producer_window": [ + 120, + 40 + ], + "producer_buffer": [ + 120, + 3000 + ], + "view_window": [ + 120, + 40 + ], + "document_capacity": 23, + "widget_font_metadata": { + "family": "Roboto Mono", + "point_size": 9, + "pixel_size": -1 + }, + "dot_key_statuses": { + "unavailable": 21, + "owner-unavailable": 7 + }, + "truncated_discovery_owners": 3 + }, + "limits": { + "widget_metadata_is_glyph_identity": false, + "c0_verified": false, + "history_eviction_verified": false, + "auto_complete_effectiveness_verified": false, + "actual_glyph_or_font_file_verified": false, + "consumer_done": null, + "presented_done": null, + "performance_gate": "not_evaluated" + } +} diff --git a/benchmarks/tools/results/2026-10-29-c0-settings-reference/plan.json b/benchmarks/tools/results/2026-10-29-c0-settings-reference/plan.json new file mode 100644 index 00000000..d21be2a6 --- /dev/null +++ b/benchmarks/tools/results/2026-10-29-c0-settings-reference/plan.json @@ -0,0 +1,88 @@ +{ + "schema": "termforge.windterm-c0-settings.v1", + "issue": 133, + "execution_authorized": true, + "maximum_original_runs": 1, + "source_base_commit": "b22293f7fbc78c282fa01b12d28e53e5e2ff00c9", + "run_id": "e736ad1ca79c4cfc85d468fdd5e5899e", + "rea_capture_seconds": 30, + "cli_wait_timeout_seconds": 45, + "whole_lifecycle_bound_seconds": null, + "module_sha256": { + "WindTerm.exe": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "Qt5Core.dll": "8d2ff4ce9096ddccc4f4cd62c2e41fc854cfd1b0d6e8d296645a7f5fd4ae565a", + "Qt5Gui.dll": "5e7d2d41b8b92a880e83b8cc0ca173f5da61218604186196787ee1600956be1e", + "Qt5Widgets.dll": "3788c669d4b645e5a576de9fc77fca776bf516d43c89143dc2ca28291ba14358", + "Qt5Qml.dll": "30035484c81590976627f8face9507caa8581a7dc7630cccf6a8d6de65cab707" + }, + "baseline_files_manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203", + "requested_session": { + "window.autoAdjustRowsAndColumns": false, + "window.columns": 120, + "window.rows": 40, + "window.scrollbackLimit": 10000 + }, + "requested_global": { + "text.fontFamily1": "Cascadia Mono", + "text.fontSize": 12, + "terminal.autoComplete.enabled": false + }, + "static_resource_sha256": { + "global/wind.config": "0a4f02250b855e9209960f934892f30e3b4b800c0c4c80cea3acd8af527ca177", + "terminal/configs/session.config": "e97009e1c57ba17709bc52738c964df18341cfc0c24895f76821b9a6141803cd", + "terminal/configs/statusbar.config": "a4f7df8f9f215eeba227714438c0ee15070cc85921ab1ebd88d991ad4efef25d" + }, + "static_config_hints": { + "evidence_kind": "static-only", + "global": { + "text.fontFamily1": "Roboto Mono", + "text.fontSize": 10, + "terminal.autoComplete.enabled": true + }, + "session": { + "window.autoAdjustRowsAndColumns": true, + "window.columns": 80, + "window.rows": 24, + "window.scrollbackLimit": 999999 + } + }, + "expected_configured_global_sha256": "01a5465e6b4dec6567ac4cf3d21df360f09b737f77c0861ec3f250400806081d", + "source_files_sha256": { + "benchmarks/tools/windterm_c0_settings.py": "609614b51f20fd76ba8da0a5cc82bd47c45b8f3715c906050a258e557ed50970", + "research/ui/qt_cmd_settings.js": "b0c008afd2318ae60cdf3a8f3f3cc87cc042db9bb03013c09246ba1a498f866b", + "research/ui/cmd_owned_launcher.py": "20f38736fa14600b5c659d9e14bfb150932639af1326d2bbe04fcfe6cd32c357", + "benchmarks/tools/windterm_cmd.py": "e517f8cab36d55e5d4bdda3fc14d4641e5ce6256991af04762dbf43c25961297", + "research/ui/qt_cmd_diagnostic.js": "9ed0d6ee6bd6e82bae6d9a6802690b3b0b83f437c7b20f0333008b5f6c214756", + "research/ui/launch.py": "6970e0d4f2fe5e7ec4827df7aa1026cd0e74aeb828cdc9b7a0b42b3c887eb4e6", + "research/ui/r1_launch.py": "54fb41872c6abcc48242a0938044184854b016785cc13659c66b1bf404da0d8a", + "benchmarks/tools/play.py": "1c83f400d45f94f4d7100c177c7cf42f98d0add037c5d31f232092f4dd6b9fea", + "experiments/conpty/run.py": "c8eef1e36b6d200b2d508cef91eaa5ca79a91fa87c5daf6265b2475c31dce329" + }, + "composed_template_sha256": "314e7320146bb3422e4488a41b9af1bc22b52c920b6b73e078d2c0e0761017fc", + "helper_prefix_sha256": "ca8057c1979d700957eceb829e12b2d866c64eff7d3ae04daa9c95abea11299e", + "workload": { + "lines": [ + "TF-CMD-BEGIN e736ad1ca79c4cfc85d468fdd5e5899e", + "TF-CMD-000 35f0debf71dd0844cfac4f9d", + "TF-CMD-001 247605d13990f0ed8aec7035", + "TF-CMD-002 06b01e9778fea56e4ef334f1", + "TF-CMD-003 a05831d81fe13dac18746dbb", + "TF-CMD-004 1e2f6b1e2d6156984fd38e68", + "TF-CMD-005 8f66fae705ec7fc7c0a8b766", + "TF-CMD-006 54bf94fa52a55e12489c166b", + "TF-CMD-007 740d7c69a480613408434292", + "TF-CMD-008 febc0660156a697069d422b9", + "TF-CMD-009 e658d25af22aefd3ada53833", + "TF-CMD-010 6e781bafc07a68895a2674b4", + "TF-CMD-011 8cb1e7d5def3ce44b479e072", + "TF-CMD-END e736ad1ca79c4cfc85d468fdd5e5899e" + ], + "payload_hex": "54462d434d442d424547494e2065373336616431636137396334636663383564343638666464356535383939650d0a54462d434d442d303030203335663064656266373164643038343463666163346639640d0a54462d434d442d303031203234373630356431333939306630656438616563373033350d0a54462d434d442d303032203036623031653937373866656135366534656633333466310d0a54462d434d442d303033206130353833316438316665313364616331383734366462620d0a54462d434d442d303034203165326636623165326436313536393834666433386536380d0a54462d434d442d303035203866363666616537303565633766633763306138623736360d0a54462d434d442d303036203534626639346661353261353565313234383963313636620d0a54462d434d442d303037203734306437633639613438303631333430383433343239320d0a54462d434d442d303038206665626330363630313536613639373036396434323262390d0a54462d434d442d303039206536353864323561663232616566643361646135333833330d0a54462d434d442d303130203665373831626166633037613638383935613236373462340d0a54462d434d442d303131203863623165376435646566336365343462343739653037320d0a54462d434d442d454e442065373336616431636137396334636663383564343638666464356535383939650d0a", + "payload_bytes": 536, + "sha256": "2bf0d9bc3e5b296fb7484c8cca86491d6c307f054b43cd37b81845d8b612b7d8" + }, + "scope": "Non-input-desktop small-cmd settings read diagnostic, not C0 visibility, history eviction, terminal timing or performance acceptance", + "production_source_commit": "28f293a5d2c01954dc8b418a02231603128955a6", + "actual_original_runs": 1, + "private_plan_sha256": "d1678750af02c27e60dec373b38ca49cca8cedd254a0c3708ea9e1b841494afc" +} diff --git a/benchmarks/tools/results/2026-10-29-c0-settings-reference/validation.json b/benchmarks/tools/results/2026-10-29-c0-settings-reference/validation.json new file mode 100644 index 00000000..96142a6c --- /dev/null +++ b/benchmarks/tools/results/2026-10-29-c0-settings-reference/validation.json @@ -0,0 +1,469 @@ +{ + "schema": "termforge.windterm-c0-settings.v1", + "issue": 133, + "status": "partial-settings-diagnostic", + "correctness": { + "status": "passed", + "method": "all-bounded-logical-lines-exact", + "payload_lines": 14, + "observed_lines": 15, + "terminal_text_sha256": "ac71f0e23b1af5092a568d8dd58bbb8b4748394056de82b4746c69bfbace2e27" + }, + "requested": { + "session": { + "window.autoAdjustRowsAndColumns": false, + "window.columns": 120, + "window.rows": 40, + "window.scrollbackLimit": 10000 + }, + "global": { + "text.fontFamily1": "Cascadia Mono", + "text.fontSize": 12, + "terminal.autoComplete.enabled": false + } + }, + "actual_settings": { + "producer_console": { + "vt_enable_succeeded": true, + "original_mode": 3, + "columns": 120, + "rows": 40, + "buffer_columns": 120, + "buffer_rows": 3000 + }, + "view_metrics": { + "window_rows": { + "status": "read", + "type": "number", + "value": 40 + }, + "window_columns": { + "status": "read", + "type": "number", + "value": 120 + } + }, + "document_screen_line_capacity": 23, + "widget_geometry": [ + 0, + 0, + 905, + 428 + ], + "widget_font": { + "family": "Roboto Mono", + "point_size": 9, + "pixel_size": -1 + }, + "view_lookup": [ + { + "method": "activeView", + "type": "object" + } + ], + "settings_reads": [ + { + "owner": "active_text", + "key": "window.autoAdjustRowsAndColumns", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_text", + "key": "window.columns", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_text", + "key": "window.rows", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_text", + "key": "window.scrollbackLimit", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_text", + "key": "text.fontFamily1", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_text", + "key": "text.fontSize", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_text", + "key": "terminal.autoComplete.enabled", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_view", + "key": "window.autoAdjustRowsAndColumns", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_view", + "key": "window.columns", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_view", + "key": "window.rows", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_view", + "key": "window.scrollbackLimit", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_view", + "key": "text.fontFamily1", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_view", + "key": "text.fontSize", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "active_view", + "key": "terminal.autoComplete.enabled", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "terminal", + "key": "window.autoAdjustRowsAndColumns", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "terminal", + "key": "window.columns", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "terminal", + "key": "window.rows", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "terminal", + "key": "window.scrollbackLimit", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "terminal", + "key": "text.fontFamily1", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "terminal", + "key": "text.fontSize", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "terminal", + "key": "terminal.autoComplete.enabled", + "result": { + "status": "unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "global_settings", + "key": "window.autoAdjustRowsAndColumns", + "result": { + "status": "owner-unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "global_settings", + "key": "window.columns", + "result": { + "status": "owner-unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "global_settings", + "key": "window.rows", + "result": { + "status": "owner-unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "global_settings", + "key": "window.scrollbackLimit", + "result": { + "status": "owner-unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "global_settings", + "key": "text.fontFamily1", + "result": { + "status": "owner-unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "global_settings", + "key": "text.fontSize", + "result": { + "status": "owner-unavailable", + "type": "undefined", + "value": null + } + }, + { + "owner": "global_settings", + "key": "terminal.autoComplete.enabled", + "result": { + "status": "owner-unavailable", + "type": "undefined", + "value": null + } + } + ], + "discovery": [ + { + "owner": "active_text", + "result": { + "status": "enumerated-names-only", + "scanned": 128, + "truncated": true, + "hits": [ + "screenLineCapacity", + "windowColumns", + "windowRows" + ] + } + }, + { + "owner": "active_view", + "result": { + "status": "enumerated-names-only", + "scanned": 128, + "truncated": true, + "hits": [ + "screenLineCapacity", + "windowColumns", + "windowRows" + ] + } + }, + { + "owner": "terminal", + "result": { + "status": "enumerated-names-only", + "scanned": 128, + "truncated": true, + "hits": [] + } + }, + { + "owner": "global_settings", + "result": { + "status": "owner-unavailable", + "scanned": 0, + "truncated": false, + "hits": [] + } + } + ] + }, + "run_id": "e736ad1ca79c4cfc85d468fdd5e5899e", + "source_base_commit": "b22293f7fbc78c282fa01b12d28e53e5e2ff00c9", + "source_files_sha256": { + "benchmarks/tools/windterm_c0_settings.py": "609614b51f20fd76ba8da0a5cc82bd47c45b8f3715c906050a258e557ed50970", + "research/ui/qt_cmd_settings.js": "b0c008afd2318ae60cdf3a8f3f3cc87cc042db9bb03013c09246ba1a498f866b", + "research/ui/cmd_owned_launcher.py": "20f38736fa14600b5c659d9e14bfb150932639af1326d2bbe04fcfe6cd32c357", + "benchmarks/tools/windterm_cmd.py": "e517f8cab36d55e5d4bdda3fc14d4641e5ce6256991af04762dbf43c25961297", + "research/ui/qt_cmd_diagnostic.js": "9ed0d6ee6bd6e82bae6d9a6802690b3b0b83f437c7b20f0333008b5f6c214756", + "research/ui/launch.py": "6970e0d4f2fe5e7ec4827df7aa1026cd0e74aeb828cdc9b7a0b42b3c887eb4e6", + "research/ui/r1_launch.py": "54fb41872c6abcc48242a0938044184854b016785cc13659c66b1bf404da0d8a", + "benchmarks/tools/play.py": "1c83f400d45f94f4d7100c177c7cf42f98d0add037c5d31f232092f4dd6b9fea", + "experiments/conpty/run.py": "c8eef1e36b6d200b2d508cef91eaa5ca79a91fa87c5daf6265b2475c31dce329" + }, + "plan_sha256": "d1678750af02c27e60dec373b38ca49cca8cedd254a0c3708ea9e1b841494afc", + "module_sha256": { + "WindTerm.exe": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "Qt5Core.dll": "8d2ff4ce9096ddccc4f4cd62c2e41fc854cfd1b0d6e8d296645a7f5fd4ae565a", + "Qt5Gui.dll": "5e7d2d41b8b92a880e83b8cc0ca173f5da61218604186196787ee1600956be1e", + "Qt5Widgets.dll": "3788c669d4b645e5a576de9fc77fca776bf516d43c89143dc2ca28291ba14358", + "Qt5Qml.dll": "30035484c81590976627f8face9507caa8581a7dc7630cccf6a8d6de65cab707" + }, + "composed_template_sha256": "314e7320146bb3422e4488a41b9af1bc22b52c920b6b73e078d2c0e0761017fc", + "static_config_hints": { + "evidence_kind": "static-only", + "global": { + "text.fontFamily1": "Roboto Mono", + "text.fontSize": 10, + "terminal.autoComplete.enabled": true + }, + "session": { + "window.autoAdjustRowsAndColumns": true, + "window.columns": 80, + "window.rows": 24, + "window.scrollbackLimit": 999999 + } + }, + "controlled_configuration_delta": { + "baseline_manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203", + "configured_manifest_sha256": "e1038e447b7c77777f96cc353f408dedb3fafc6a6859fb49c2ae4dedfc176e6f", + "changed_distribution_files": [ + "global/wind.config" + ], + "global_before_sha256": "0a4f02250b855e9209960f934892f30e3b4b800c0c4c80cea3acd8af527ca177", + "global_after_sha256": "01a5465e6b4dec6567ac4cf3d21df360f09b737f77c0861ec3f250400806081d", + "keys": { + "text.fontFamily1": { + "before": "Roboto Mono", + "requested": "Cascadia Mono" + }, + "text.fontSize": { + "before": 10, + "requested": 12 + }, + "terminal.autoComplete.enabled": { + "before": true, + "requested": false + } + }, + "all_other_global_bytes_preserved": true, + "session_requests": { + "window.autoAdjustRowsAndColumns": false, + "window.columns": 120, + "window.rows": 40, + "window.scrollbackLimit": 10000 + } + }, + "raw_records_sha256": { + "run.json": "5fa5d07d10f76fc7f0716853e4054ace7fa005e9780319d8bfc08e51d44fd000", + "workload.json": "5d4a44b49a46568a2056b1b6a8d55b9e97f7e1d395fc7a5751f33d70c242fdc4", + "payload.bin": "2bf0d9bc3e5b296fb7484c8cca86491d6c307f054b43cd37b81845d8b612b7d8", + "producer.json": "f9f8ccb88a173449f47fd681fb0a37d25929c4a078653fed8ac379f51de05cfa", + "evidence.json": "0621b2b04f0f6e4b958a3ad3953933014e361fd3a636b56fcc98c6f413ec70e7", + "cleanup.json": "6b6ed2141ddb2a9d2fc1fdc197d6e0c03814041dfeed881eebfd240107a74005", + "profile-check.json": "5e522a64e98197722ccd084639b1ce50258b238e03be2f016e81c8af023b5ee1", + "config-delta.json": "6c1406c21ab83c88156be3fd79db37005fa7468085188c9915c4cfa1fea7029a", + "configured-files.json": "eb6c5e9d579e384da960758b690a2a20234ea609d8d95caddf88b6ca60b17027", + "files.json": "a25c87cba935294d5bfd3bbc76c650d31a8473809564d798ad5a889e4aea00e8", + "global-config-before.bin": "0a4f02250b855e9209960f934892f30e3b4b800c0c4c80cea3acd8af527ca177", + "probe.js": "fc46ec24d5e99126ca9f78c28efedfbd225fbc7238b9a73a9d48363df9a4df5f", + "stderr.txt": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "actual_original_runs": 1, + "rea_capture_seconds": 30, + "cli_wait_timeout_seconds": 45, + "whole_lifecycle_bound_seconds": null, + "consumer_done": null, + "presented_done": null, + "c0_verified": false, + "font_file_identity_verified": false, + "glyph_fallback_verified": false, + "history_eviction_verified": false, + "performance_gate": "not_evaluated", + "cleanup": { + "owned_active": 0, + "only_synthetic_session": true + }, + "limitations": [ + "Non-input desktop cannot establish visible and unobstructed C0.", + "Requested configuration and static hints are separate from actual readouts.", + "Screen capacity is not PTY rows; console/window, view and widget font remain separate.", + "Name enumeration caps retained entries, not internal engine time.", + "Producer uses run_id/payload identity; process ancestry was not independently observed.", + "No font installation, glyph/fallback identity, history eviction, terminal endpoints, observer calibration or performance acceptance." + ] +} diff --git a/benchmarks/tools/test_windterm_c0_settings.py b/benchmarks/tools/test_windterm_c0_settings.py new file mode 100644 index 00000000..a5f43df1 --- /dev/null +++ b/benchmarks/tools/test_windterm_c0_settings.py @@ -0,0 +1,164 @@ +"""Public plan/profile/text/launcher seams; never launch an original target.""" +from __future__ import annotations +import json +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import Mock,patch + +from benchmarks.tools import windterm_c0_settings as settings +from research.ui.launch import manifest_digest + + +def missing():return {'status':'unavailable','type':'undefined','value':None} + + +class SettingsTests(unittest.TestCase): + def test_fixed_public_proposal_has_no_actual_values_or_execution_authorization(self): + plan=json.loads((settings.ROOT/'benchmarks/tools/results/2026-10-29-c0-settings-proposal/plan.json').read_text()) + self.assertFalse(plan['execution_authorized']);self.assertEqual(plan['actual_original_runs'],0) + self.assertIsNone(plan['actual_settings']);self.assertFalse(plan['c0_verified']) + self.assertEqual(len(plan['workload']['lines']),14);self.assertEqual(plan['workload']['payload_bytes'],536) + self.assertEqual(plan['requested_session']['window.rows'],40);self.assertEqual(plan['static_config_hints']['session']['window.rows'],24) + self.assertEqual(plan['source_files_sha256'],{name:settings.sha(settings.ROOT/name) for name in settings.SOURCE_FILES}) + + def snapshot(self): + return {'screen_line_capacity':23,'widget_geometry':[0,0,905,428],'widget_font':{'family':'Synthetic Fallback','point_size':9,'pixel_size':-1}, + 'view_lookup':[{'method':'activeView','type':'object'}], + 'view_metrics':{'window_rows':{'status':'read','type':'number','value':24},'window_columns':{'status':'read','type':'number','value':80}}, + 'settings_reads':[{'owner':owner,'key':key,'result':missing()} for owner in settings.OWNERS for key in settings.SETTING_KEYS], + 'discovery':[{'owner':owner,'result':{'status':'owner-unavailable','scanned':0,'truncated':False,'hits':[]}} for owner in settings.OWNERS]} + + def test_actual_readouts_and_unknowns_do_not_inherit_requested_defaults(self): + actual=settings.settings_snapshot(self.snapshot()) + self.assertEqual(actual['view_metrics']['window_rows']['value'],24) + self.assertEqual(actual['view_metrics']['window_columns']['value'],80) + self.assertEqual(actual['document_screen_line_capacity'],23) + self.assertEqual(actual['widget_font']['family'],'Synthetic Fallback') + self.assertEqual(actual['widget_font']['point_size'],9) + self.assertTrue(all(row['result']['value'] is None for row in actual['settings_reads'])) + self.assertEqual(settings.SESSION_REQUESTS['window.rows'],40) + + def test_only_declared_global_keys_change_after_whole_manifest_validation(self): + with tempfile.TemporaryDirectory() as folder: + out=Path(folder);app=out/'app';(app/'global').mkdir(parents=True) + original={'text.fontFamily1':'Synthetic Original','text.fontSize':10,'terminal.autoComplete.enabled':True,'unrelated':{'keep':[1,2,3]}} + (app/'global/wind.config').write_text(json.dumps(original,indent=2));(app/'other.bin').write_bytes(b'unchanged') + manifest={name:settings.sha(app/name) for name in ('global/wind.config','other.bin')} + (out/'files.json').write_text(json.dumps(manifest)) + session=out/'profile/.wind/profiles/default.v10/terminal/user.sessions';session.parent.mkdir(parents=True);session.write_text(json.dumps([{'session.label':'synthetic','keep':True}])) + result=settings.configure_copy(out,manifest_digest(manifest)) + self.assertEqual(result['changed_distribution_files'],['global/wind.config']) + self.assertEqual(json.loads((app/'global/wind.config').read_text()),{**original,**settings.GLOBAL_REQUESTS}) + self.assertEqual((app/'other.bin').read_bytes(),b'unchanged') + self.assertEqual(json.loads(session.read_text()),[{'session.label':'synthetic','keep':True,**settings.SESSION_REQUESTS}]) + + def test_bom_comments_and_unrelated_bytes_survive_exact_key_token_patch(self): + with tempfile.TemporaryDirectory() as folder: + out=Path(folder);app=out/'app';(app/'global').mkdir(parents=True) + original=b'\xef\xbb\xbf{\r\n "text.fontFamily1": "Synthetic Original",\r\n "text.fontSize": 10,\r\n "terminal.autoComplete.enabled": true,\r\n "unrelated": 1 // synthetic comment\r\n}\r\n' + (app/'global/wind.config').write_bytes(original);manifest={'global/wind.config':settings.sha(app/'global/wind.config')};(out/'files.json').write_text(json.dumps(manifest)) + session=out/'profile/.wind/profiles/default.v10/terminal/user.sessions';session.parent.mkdir(parents=True);session.write_text('[{}]') + result=settings.configure_copy(out,manifest_digest(manifest)) + expected=original.replace(b'"Synthetic Original"',b'"Cascadia Mono"').replace(b'"text.fontSize": 10',b'"text.fontSize": 12').replace(b'"terminal.autoComplete.enabled": true',b'"terminal.autoComplete.enabled": false') + self.assertEqual((app/'global/wind.config').read_bytes(),expected) + self.assertTrue(result['all_other_global_bytes_preserved']) + + def test_manifest_failure_precedes_any_configuration_write(self): + with tempfile.TemporaryDirectory() as folder: + out=Path(folder);(out/'files.json').write_text('{}') + with self.assertRaisesRegex(ValueError,'Baseline manifest'):settings.configure_copy(out) + self.assertEqual([path.name for path in out.iterdir()],['files.json']) + + def test_whole_fourteen_line_oracle_is_reused_without_marker_shortcut(self): + expected,payload=settings.base.workload('0'*32) + self.assertEqual(len(expected),14);self.assertEqual(len(payload),536) + self.assertEqual(settings.base.check_text({'lines':expected+[''],'line_count':15},expected)['status'],'passed') + with self.assertRaises(ValueError):settings.base.check_text({'lines':[expected[-1]],'line_count':1},expected) + + def test_unreadable_statuses_keep_null_and_invalid_private_values_reject(self): + for status in ('invalid-type','invalid-scalar','read-threw','non-scalar','owner-unavailable'): + record={'status':status,'type':'string','value':None} + self.assertEqual(settings.scalar_read(record,'text.fontFamily1'),record) + for record,key in (({'status':'read','type':'number','value':float('nan')},'window_rows'),({'status':'read','type':'string','value':'C:/private/font'},'text.fontFamily1'),({'status':'read','type':'boolean','value':True},'window_rows')): + with self.subTest(key=key),self.assertRaises(ValueError):settings.scalar_read(record,key) + + def test_settings_inventory_and_discovery_are_whitelisted(self): + for change in (lambda row:row['settings_reads'].pop(),lambda row:row['discovery'][0]['result'].update(hits=['privatePath']),lambda row:row['view_metrics'].update(privatePath=missing())): + snapshot=self.snapshot();change(snapshot) + with self.assertRaises(ValueError):settings.settings_snapshot(snapshot) + + @unittest.skipUnless(shutil.which('node'),'Node required for external QJSEngine boundary') + def test_probe_known_calls_and_bounded_name_scalar_reading(self): + result=subprocess.run(['node',str(settings.ROOT/'research/ui/test_cmd_settings_probe.js')],capture_output=True,text=True,timeout=10) + self.assertEqual(result.returncode,0,result.stderr) + + def test_false_plan_public_cli_refuses_before_native_launcher(self): + for optimized in (False,True): + with self.subTest(optimized=optimized),tempfile.TemporaryDirectory() as folder: + root=Path(folder);plan=root/'false.json';plan.write_text(json.dumps({'schema':settings.SCHEMA,'issue':133,'maximum_original_runs':1,'execution_authorized':False})) + result=subprocess.run([sys.executable,*(['-O'] if optimized else []),str(Path(settings.__file__).resolve()),'run','--plan',str(plan)],capture_output=True,text=True,timeout=10) + self.assertNotEqual(result.returncode,0);self.assertIn('execution not authorized',result.stderr) + self.assertEqual([path.name for path in root.iterdir()],['false.json']) + + def test_public_export_false_identity_creates_no_public_output_under_optimization(self): + for optimized in (False,True): + with self.subTest(optimized=optimized),tempfile.TemporaryDirectory() as folder: + root=Path(folder);out=root/'raw';out.mkdir();plan=root/'plan.json';destination=root/'public/result.json' + plan.write_text(json.dumps({'schema':settings.SCHEMA,'issue':133,'maximum_original_runs':1,'execution_authorized':False})) + for name,value in (('run.json',{'schema':settings.SCHEMA}),('cleanup.json',{}),('profile-check.json',{})):(out/name).write_text(json.dumps(value)) + result=subprocess.run([sys.executable,*(['-O'] if optimized else []),str(Path(settings.__file__).resolve()),'summarize','--out',str(out),'--plan',str(plan),'--destination',str(destination)],capture_output=True,text=True,timeout=10) + self.assertNotEqual(result.returncode,0);self.assertIn('unauthorized diagnostic',result.stderr) + self.assertFalse(destination.exists());self.assertFalse(destination.parent.exists()) + + def test_public_prepare_rejects_cmd_meta_path_before_source_read_or_plan_output(self): + for optimized in (False,True): + suffix='optimized' if optimized else 'normal';out=settings.ROOT/f'local/settings&REVIEW_MARKER-{suffix}';plan=settings.ROOT/f'local/settings-rejected-{suffix}.json' + self.assertFalse(out.exists());self.assertFalse(plan.exists()) + result=subprocess.run([sys.executable,*(['-O'] if optimized else []),str(Path(settings.__file__).resolve()),'prepare','--source',str(settings.ROOT/'local/missing-original'),'--out',str(out),'--rea',sys.executable,'--plan',str(plan)],capture_output=True,text=True,timeout=10) + self.assertNotEqual(result.returncode,0);self.assertIn('Unsupported cmd path',result.stderr);self.assertNotIn('FileNotFoundError',result.stderr) + self.assertFalse(out.exists());self.assertFalse(plan.exists()) + + def test_composed_probe_and_fixed_old_templates_have_separate_identity(self): + old=settings.OLD_PROBE.read_text(encoding='utf-8');new=settings.PROBE.read_text(encoding='utf-8') + self.assertEqual(settings.probe_template(),old.split('function state(){')[0]+new) + self.assertEqual(settings.sha(Path(settings.base.__file__)),'e517f8cab36d55e5d4bdda3fc14d4641e5ce6256991af04762dbf43c25961297') + self.assertEqual(settings.sha(settings.OLD_PROBE),'9ed0d6ee6bd6e82bae6d9a6802690b3b0b83f437c7b20f0333008b5f6c214756') + + @unittest.skipUnless(sys.platform=='win32','Synthetic WinAPI boundary') + def test_native_boundary_assigns_before_resume_and_records_cleanup_on_cli_failure(self): + from research.ui import cmd_owned_launcher as launcher + for cli_failure in (False,True): + with self.subTest(cli_failure=cli_failure),tempfile.TemporaryDirectory() as folder: + out=Path(folder);app=out/'app';app.mkdir();(app/'WindTerm.exe').write_bytes(b'synthetic-native-boundary-only') + session=out/'profile/.wind/profiles/default.v10/terminal/user.sessions';session.parent.mkdir(parents=True);session.write_text(json.dumps([{'session.protocol':'Shell','session.label':'TF_UI_CMD','session.uuid':'00000000-0000-4000-8000-000000000001'}])) + events=[];kernel,user=Mock(),Mock();user.CreateDesktopW.return_value=10;user.CloseDesktop.return_value=1;kernel.WaitForSingleObject.return_value=0 + def create(*args): + info=args[-1]._obj;info.process,info.thread,info.pid,info.tid=111,222,333,444;events.append('created-suspended');return 1 + kernel.CreateProcessW.side_effect=create;kernel.ResumeThread.side_effect=lambda handle:events.append('resumed') or 1 + class FakeJob: + @staticmethod + def check(value): + if not value:raise RuntimeError('synthetic native call failed') + def assign(self,pid):events.append('assigned') + def terminate(self):events.append('terminated-job') + def drain(self):return {'active':0,'total':1,'terminated':0} + def close(self):events.append('closed-job') + def cli_call(arguments,directory,job,timeout): + self.assertEqual(timeout,45);self.assertIn('30000',arguments);events.append('cli-wait') + if cli_failure:raise TimeoutError('synthetic CLI timeout') + return 0 + template='__UI_THREAD_ID__ __RUN_ID_JSON__ __RUN_DIRECTORY_JSON__ __DESKTOP_JSON__ __TIMEOUT_MS__' + with patch.object(launcher.c,'WinDLL',side_effect=lambda name,**kw:user if name=='user32' else kernel),patch.object(launcher,'OwnedJob',FakeJob),patch.object(launcher,'isolated_environment',return_value={'SYNTHETIC':'1'}),patch.object(launcher,'cli_command',return_value=[sys.executable]),patch.object(launcher,'run_cli',side_effect=cli_call),patch.object(launcher.time,'sleep'): + if cli_failure: + with self.assertRaises(TimeoutError):launcher.launch_owned(app,out,Path(sys.executable),'0'*32,template,{'schema':'synthetic-boundary'}) + else:launcher.launch_owned(app,out,Path(sys.executable),'0'*32,template,{'schema':'synthetic-boundary'}) + self.assertLess(events.index('assigned'),events.index('resumed'));self.assertIn('terminated-job',events);self.assertIn('closed-job',events) + self.assertEqual(json.loads((out/'cleanup.json').read_text())['active'],0) + self.assertEqual(json.loads((out/'profile-check.json').read_text()),{'only_synthetic_session':True,'session_count':1}) + + +if __name__=='__main__':unittest.main() diff --git a/benchmarks/tools/windterm_c0_settings.py b/benchmarks/tools/windterm_c0_settings.py new file mode 100644 index 00000000..12eb9d8d --- /dev/null +++ b/benchmarks/tools/windterm_c0_settings.py @@ -0,0 +1,196 @@ +"""Independent #133 requested/actual settings diagnostic; false plans never launch.""" +from __future__ import annotations +import argparse +import copy +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys +import uuid + +ROOT=Path(__file__).resolve().parents[2] +sys.path.insert(0,str(ROOT)) +from benchmarks.tools import windterm_cmd as base +from research.ui.launch import EXPECTED_FILES,manifest_digest + +SCHEMA='termforge.windterm-c0-settings.v1' +PROBE=ROOT/'research/ui/qt_cmd_settings.js' +OLD_PROBE=ROOT/'research/ui/qt_cmd_diagnostic.js' +GLOBAL_REQUESTS={'text.fontFamily1':'Cascadia Mono','text.fontSize':12,'terminal.autoComplete.enabled':False} +SESSION_REQUESTS={'window.autoAdjustRowsAndColumns':False,'window.columns':120,'window.rows':40,'window.scrollbackLimit':10000} +SETTING_KEYS=tuple(SESSION_REQUESTS)+tuple(GLOBAL_REQUESTS) +OWNERS=('active_text','active_view','terminal','global_settings') +NAMES=('windowRows','windowColumns','screenLineCapacity','settings','config','configuration','getSetting','setting','value','scrollbackLimit','fontFamily1','fontSize','autoCompleteEnabled') +MODULES=('WindTerm.exe','Qt5Core.dll','Qt5Gui.dll','Qt5Widgets.dll','Qt5Qml.dll') +SOURCE_FILES=('benchmarks/tools/windterm_c0_settings.py','research/ui/qt_cmd_settings.js','research/ui/cmd_owned_launcher.py','benchmarks/tools/windterm_cmd.py','research/ui/qt_cmd_diagnostic.js','research/ui/launch.py','research/ui/r1_launch.py','benchmarks/tools/play.py','experiments/conpty/run.py') +require,sha,write_json=base.require,base.digest,base.write_json + + +def probe_template(): + old=OLD_PROBE.read_text(encoding='utf-8');marker='function state(){' + require(old.count(marker)==1,'Fixed Qt helper boundary changed') + return old.split(marker,1)[0]+PROBE.read_text(encoding='utf-8') + + +def validate_cmd_paths(out): + for original in (out,base.__file__,sys.executable): + path=Path(original) + for text in (str(path),str(path.resolve())): + require(not any(char in base.CMD_PATH_META or ord(char)<32 for char in text),'Unsupported cmd path characters') + + +def patch_global(raw): + """Replace three unique scalar tokens, preserving vendor JSON-like syntax byte-for-byte.""" + spans=[];original={} + for key,requested in GLOBAL_REQUESTS.items(): + pattern=re.compile(rb'(?m)^[ \t]*"'+re.escape(key.encode('ascii'))+rb'"[ \t]*:[ \t]*(?P"(?:[^"\\\r\n]|\\.)*"|true|false|-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?)[ \t]*,?[ \t]*(?://[^\r\n]*)?\r?$') + matches=list(pattern.finditer(raw));require(len(matches)==1,'Declared global key must occur once on a scalar line') + match=matches[0];value=json.loads(match['value']);require(type(value) is type(requested),'Static global scalar type differs') + original[key]=value;spans.append((match.start('value'),match.end('value'),json.dumps(requested).encode('ascii'))) + patched=raw + for begin,end,replacement in sorted(spans,reverse=True):patched=patched[:begin]+replacement+patched[end:] + return patched,original + + +def configure_copy(out,expected_manifest=EXPECTED_FILES): + """Verify the entire baseline, then apply only declared keys in an owned copy.""" + app=out/'app';manifest=json.loads((out/'files.json').read_text()) + require(manifest_digest(manifest)==expected_manifest,'Baseline manifest identity differs') + measured={name:sha(app/name) for name in manifest} + require(measured==manifest,'Prepared baseline files changed') + path=app/'global/wind.config';before=path.read_bytes();changed,original=patch_global(before) + (out/'global-config-before.bin').write_bytes(before) + path.write_bytes(changed) + configured={name:sha(app/name) for name in manifest} + require([name for name in manifest if manifest[name]!=configured[name]]==['global/wind.config'],'Unexpected distribution file delta') + session=out/'profile/.wind/profiles/default.v10/terminal/user.sessions';rows=json.loads(session.read_text()) + require(len(rows)==1,'One synthetic session required');rows[0].update(SESSION_REQUESTS) + session.write_text(json.dumps(rows,indent=2)+'\n',encoding='utf-8',newline='\n') + delta={'baseline_manifest_sha256':expected_manifest,'configured_manifest_sha256':manifest_digest(configured),'changed_distribution_files':['global/wind.config'], + 'global_before_sha256':hashlib.sha256(before).hexdigest(),'global_after_sha256':sha(path),'keys':{key:{'before':original[key],'requested':value} for key,value in GLOBAL_REQUESTS.items()}, + 'all_other_global_bytes_preserved':True, + 'session_requests':SESSION_REQUESTS,'synthetic_session_sha256':sha(session)} + write_json(out/'configured-files.json',configured);write_json(out/'config-delta.json',delta) + return delta + + +def prepare_plan(source,out,rea,destination,execution_authorized=False,run_id=None): + source,out,rea,destination=[Path(path).resolve() for path in (source,out,rea,destination)] + require(not out.exists() and not destination.exists(),'Plan/output must be new') + require(out.is_relative_to(ROOT/'local') and destination.is_relative_to(ROOT/'local'),'Plan/output must be worktree-local') + validate_cmd_paths(out) + require(rea.is_file(),'Existing REA CLI missing') + package=rea.parent.parent/'rea-agents/package.json';require(json.loads(package.read_text())['version']=='6.7.0','REA version differs') + hashes={name:sha(source/name) for name in MODULES};require(hashes['WindTerm.exe']==base.EXPECTED,'Pinned executable differs') + run_id=run_id or uuid.uuid4().hex;lines,payload=base.workload(run_id) + plan={'schema':SCHEMA,'issue':133,'execution_authorized':execution_authorized,'maximum_original_runs':1,'source_base_commit':subprocess.check_output(['git','rev-parse','HEAD'],text=True,cwd=ROOT).strip(), + 'source':str(source),'out':str(out),'rea':str(rea),'run_id':run_id,'rea_capture_seconds':30,'cli_wait_timeout_seconds':45,'whole_lifecycle_bound_seconds':None, + 'module_sha256':hashes,'baseline_files_manifest_sha256':EXPECTED_FILES,'requested_session':SESSION_REQUESTS,'requested_global':GLOBAL_REQUESTS, + 'static_resource_sha256':{name:sha(source/name) for name in ('global/wind.config','terminal/configs/session.config','terminal/configs/statusbar.config')}, + 'static_config_hints':{'evidence_kind':'static-only','global':patch_global((source/'global/wind.config').read_bytes())[1],'session':{key:json.loads((source/'terminal/configs/session.config').read_bytes())[key] for key in SESSION_REQUESTS}}, + 'expected_configured_global_sha256':hashlib.sha256(patch_global((source/'global/wind.config').read_bytes())[0]).hexdigest(), + 'source_files_sha256':{name:sha(ROOT/name) for name in SOURCE_FILES},'composed_template_sha256':hashlib.sha256(probe_template().encode()).hexdigest(), + 'helper_prefix_sha256':hashlib.sha256(OLD_PROBE.read_text(encoding='utf-8').split('function state(){',1)[0].encode()).hexdigest(), + 'workload':{'lines':lines,'payload_hex':payload.hex(),'payload_bytes':len(payload),'sha256':hashlib.sha256(payload).hexdigest()}, + 'scope':'Non-input-desktop small-cmd settings read diagnostic, not C0 visibility, history eviction, terminal timing or performance acceptance'} + destination.parent.mkdir(parents=True,exist_ok=True);write_json(destination,plan) + return plan + + +def scalar_read(record,key): + require(isinstance(record,dict) and set(record)=={'status','type','value'},'Invalid scalar read shape') + require(record['status'] in ('owner-unavailable','unavailable','non-scalar','read-threw','read','invalid-type','invalid-scalar'),'Unknown read status') + require(record['type'] in ('undefined','null','unknown','object','function','number','boolean','string'),'Unknown read type') + value=record['value'] + if record['status']!='read':require(value is None,'Unreadable value must remain null') + elif record['type']=='number':require(key not in ('text.fontFamily1','window.autoAdjustRowsAndColumns','terminal.autoComplete.enabled') and type(value) in (int,float) and value==value and abs(value)<=10000000,'Invalid numeric setting') + elif record['type']=='boolean':require(key in ('window.autoAdjustRowsAndColumns','terminal.autoComplete.enabled') and type(value) is bool,'Invalid boolean setting') + elif record['type']=='string':require(key=='text.fontFamily1' and isinstance(value,str) and len(value)<=128 and not any(char in value for char in '\\/\r\n'),'Unexpected private/string setting') + else:require(record['type']=='null' and value is None,'Unexpected scalar type') + return copy.deepcopy(record) + + +def settings_snapshot(snapshot): + terminal=base.terminal_settings(snapshot) + metrics=snapshot['view_metrics'];require(set(metrics)=={'window_rows','window_columns'},'View metrics differ') + selected={key:scalar_read(metrics[key],key) for key in metrics} + reads=snapshot['settings_reads'];require(len(reads)==len(OWNERS)*len(SETTING_KEYS),'Settings read inventory differs') + require([(row['owner'],row['key']) for row in reads]==[(owner,key) for owner in OWNERS for key in SETTING_KEYS],'Settings read identity differs') + selected_reads=[{'owner':row['owner'],'key':row['key'],'result':scalar_read(row['result'],row['key'])} for row in reads] + lookup=snapshot['view_lookup'];require(1<=len(lookup)<=2 and [row['method'] for row in lookup]==['activeView','view'][:len(lookup)] and all(set(row)=={'method','type'} and row['type'] in ('object','null','unavailable','threw','undefined','function','number','boolean','string') for row in lookup),'View lookup inventory differs') + discovery=snapshot['discovery'];require([row['owner'] for row in discovery]==list(OWNERS),'Discovery owner differs') + for row in discovery: + value=row['result'];require(set(value)=={'status','scanned','truncated','hits'} and value['status'] in ('owner-unavailable','enumerated-names-only','enumeration-threw'),'Discovery shape differs') + require(type(value['scanned']) is int and 0<=value['scanned']<=128 and type(value['truncated']) is bool and isinstance(value['hits'],list) and len(value['hits'])<=len(NAMES) and len(set(value['hits']))==len(value['hits']) and all(name in NAMES for name in value['hits']),'Unbounded/unlisted discovery') + return {'producer_console':None,'view_metrics':selected,'document_screen_line_capacity':terminal['screen_line_capacity'],'widget_geometry':terminal['widget_geometry'],'widget_font':terminal['widget_font'],'view_lookup':lookup,'settings_reads':selected_reads,'discovery':discovery} + + +def summarize(out,plan_path): + out,plan_path=Path(out),Path(plan_path);plan=json.loads(plan_path.read_text());read=lambda name:json.loads((out/name).read_text()) + identity,cleanup,profile=read('run.json'),read('cleanup.json'),read('profile-check.json') + require(plan['schema']==identity['schema']==SCHEMA and plan['issue']==133 and plan['maximum_original_runs']==1 and plan['execution_authorized'] is True,'Wrong or unauthorized diagnostic identity') + require(plan['source_files_sha256']==identity['source_files_sha256']=={name:sha(ROOT/name) for name in SOURCE_FILES},'Actual source inventory changed') + require(identity['plan_sha256']==sha(plan_path) and identity['run_id']==plan['run_id'] and identity['sha256']==plan['module_sha256']['WindTerm.exe']==base.EXPECTED,'Run/plan/module identity differs') + require({name:sha(out/'app'/name) for name in MODULES}==plan['module_sha256'],'Full module hash differs') + require(identity['desktop']=='owned-non-input' and identity['profile']=='synthetic-local-only' and cleanup['run_id']==identity['run_id'] and cleanup['target_pid']==identity['pid'] and cleanup['active']==0 and profile=={'only_synthetic_session':True,'session_count':1},'Owned cleanup/profile identity differs') + require(identity['rea_capture_seconds']==30 and identity['cli_wait_timeout_seconds']==45 and identity['whole_lifecycle_bound_seconds'] is None,'Capture lifecycle bound differs') + from research.ui.cmd_owned_launcher import render_template + require(identity['composed_template_sha256']==plan['composed_template_sha256']==hashlib.sha256(probe_template().encode()).hexdigest(),'Composed template identity differs') + require((out/'probe.js').read_text(encoding='utf-8')==render_template(probe_template(),out.resolve(),plan['run_id'],identity['tid'],'TermForgeCmdSettings-'+plan['run_id']),'Exact injected source differs') + lines,payload=base.workload(plan['run_id']);work=read('workload.json') + require(work=={'run_id':plan['run_id'],'lines':lines,'payload_bytes':len(payload),'sha256':hashlib.sha256(payload).hexdigest()} and (out/'payload.bin').read_bytes()==payload,'Full oracle identity differs') + require(plan['workload']=={'lines':lines,'payload_hex':payload.hex(),'payload_bytes':len(payload),'sha256':hashlib.sha256(payload).hexdigest()},'Planned oracle differs') + delta=read('config-delta.json');configured=read('configured-files.json');baseline=read('files.json') + require(plan['requested_session']==SESSION_REQUESTS and plan['requested_global']==GLOBAL_REQUESTS and delta['baseline_manifest_sha256']==EXPECTED_FILES,'Request/baseline metadata differs') + require(set(configured)==set(baseline) and manifest_digest(baseline)==EXPECTED_FILES and manifest_digest(configured)==delta['configured_manifest_sha256'] and {name:sha(out/'app'/name) for name in configured}==configured,'Configured package identity differs') + expected_global,before=patch_global((out/'global-config-before.bin').read_bytes()) + require([name for name in baseline if baseline[name]!=configured[name]]==delta['changed_distribution_files']==['global/wind.config'] and delta['session_requests']==SESSION_REQUESTS and delta['all_other_global_bytes_preserved'] is True and delta['keys']=={key:{'before':before[key],'requested':value} for key,value in GLOBAL_REQUESTS.items()},'Controlled config delta differs') + require(sha(out/'global-config-before.bin')==delta['global_before_sha256']==plan['static_resource_sha256']['global/wind.config'] and sha(out/'app/global/wind.config')==delta['global_after_sha256']==plan['expected_configured_global_sha256'],'Config resource SHA differs') + require((out/'app/global/wind.config').read_bytes()==expected_global,'Unrequested global bytes changed') + require(plan['static_config_hints']=={'evidence_kind':'static-only','global':before,'session':{key:json.loads((out/'app/terminal/configs/session.config').read_bytes())[key] for key in SESSION_REQUESTS}},'Static hints differ from fixed resources') + produced=read('producer.json');require(produced['run_id']==plan['run_id'] and produced['status']=='complete' and produced['error'] is None and produced['sha256']==work['sha256'] and produced['payload_bytes']==len(payload) and produced['consumer_done'] is None and produced['presented_done'] is None,'Producer identity differs') + console={key:produced['console'].get(key) for key in ('vt_enable_succeeded','original_mode','columns','rows','buffer_columns','buffer_rows')};require(console['vt_enable_succeeded'] is True and all(value is None or type(value) is int and 0<=value<=0xffffffff for key,value in console.items() if key!='vt_enable_succeeded'),'Invalid producer console') + payloads=base.evidence_payloads(identity,read('evidence.json'),sha(out/'probe.js'));finished=[row for row in payloads if row.get('kind')=='settings_complete'];require(len(finished)==1,'One complete settings snapshot required') + snapshot=finished[0];require(snapshot['module_guards']==[{'name':name,'path_guard_passed':True} for name in MODULES],'Runtime module path guard differs') + correctness=base.check_text(snapshot,lines);actual=settings_snapshot(snapshot);actual['producer_console']=console + raw_names=('run.json','workload.json','payload.bin','producer.json','evidence.json','cleanup.json','profile-check.json','config-delta.json','configured-files.json','files.json','global-config-before.bin','probe.js','stderr.txt') + return {'schema':SCHEMA,'issue':133,'status':'partial-settings-diagnostic','correctness':correctness,'requested':{'session':SESSION_REQUESTS,'global':GLOBAL_REQUESTS},'actual_settings':actual, + 'run_id':plan['run_id'],'source_base_commit':plan['source_base_commit'],'source_files_sha256':plan['source_files_sha256'],'plan_sha256':sha(plan_path),'module_sha256':plan['module_sha256'], + 'composed_template_sha256':plan['composed_template_sha256'],'static_config_hints':plan['static_config_hints'],'controlled_configuration_delta':{key:value for key,value in delta.items() if key!='synthetic_session_sha256'},'raw_records_sha256':{name:sha(out/name) for name in raw_names}, + 'actual_original_runs':1,'rea_capture_seconds':30,'cli_wait_timeout_seconds':45,'whole_lifecycle_bound_seconds':None,'consumer_done':None,'presented_done':None,'c0_verified':False,'font_file_identity_verified':False,'glyph_fallback_verified':False,'history_eviction_verified':False,'performance_gate':'not_evaluated','cleanup':{'owned_active':0,'only_synthetic_session':True}, + 'limitations':['Non-input desktop cannot establish visible and unobstructed C0.','Requested configuration and static hints are separate from actual readouts.','Screen capacity is not PTY rows; console/window, view and widget font remain separate.','Name enumeration caps retained entries, not internal engine time.','Producer uses run_id/payload identity; process ancestry was not independently observed.','No font installation, glyph/fallback identity, history eviction, terminal endpoints, observer calibration or performance acceptance.']} + + +def run_plan(plan_path): + plan_path=Path(plan_path).resolve();plan=json.loads(plan_path.read_text()) + require(plan.get('schema')==SCHEMA and plan.get('issue')==133 and plan.get('maximum_original_runs')==1,'Unexpected diagnostic plan') + require(plan.get('execution_authorized') is True,'Offline proposal only; execution not authorized') + require(plan['source_base_commit']==subprocess.check_output(['git','rev-parse','HEAD'],cwd=ROOT,text=True).strip() and plan['source_files_sha256']=={name:sha(ROOT/name) for name in SOURCE_FILES},'Planned source changed') + require(plan['requested_session']==SESSION_REQUESTS and plan['requested_global']==GLOBAL_REQUESTS and plan['rea_capture_seconds']==30 and plan['cli_wait_timeout_seconds']==45 and plan['whole_lifecycle_bound_seconds'] is None,'Planned scope differs') + require(plan['composed_template_sha256']==hashlib.sha256(probe_template().encode()).hexdigest(),'Composed probe changed') + out,source,rea=[Path(plan[key]) for key in ('out','source','rea')] + validate_cmd_paths(out) + require({name:sha(source/name) for name in MODULES}==plan['module_sha256'],'Pinned modules changed') + require(not out.exists(),'Output must be new') + with plan_path.with_suffix('.attempted').open('x') as marker:marker.write('One attempt; never retry.\n') + app,_=base.prepare_run(source,out,plan['run_id']);configure_copy(out) + from research.ui.cmd_owned_launcher import launch_owned + launch_owned(app,out,rea,plan['run_id'],probe_template(),{'schema':SCHEMA,'source_files_sha256':plan['source_files_sha256'],'plan_sha256':sha(plan_path),'composed_template_sha256':plan['composed_template_sha256']}) + + +if __name__=='__main__': + parser=argparse.ArgumentParser();commands=parser.add_subparsers(dest='command',required=True) + prepare=commands.add_parser('prepare') + for key in ('source','out','rea','plan'):prepare.add_argument('--'+key,type=Path,required=True) + prepare.add_argument('--authorize-execution',action='store_true',help='Only after separate explicit authorization; create a fresh plan, never edit an old proposal') + prepare.add_argument('--run-id',help='Reuse the reviewed synthetic nonce when creating the separate authorized plan') + run=commands.add_parser('run');run.add_argument('--plan',type=Path,required=True) + export=commands.add_parser('summarize') + for key in ('out','plan','destination'):export.add_argument('--'+key,type=Path,required=True) + args=parser.parse_args() + if args.command=='prepare':prepare_plan(args.source,args.out,args.rea,args.plan,args.authorize_execution,args.run_id);print(json.dumps({'prepared':True,'execution_authorized':args.authorize_execution})) + elif args.command=='run':run_plan(args.plan) + else: + result=summarize(args.out,args.plan);args.destination.parent.mkdir(parents=True,exist_ok=True);write_json(args.destination,result);print(json.dumps({'exported':True,'status':result['status']})) diff --git a/docs/EVIDENCE.md b/docs/EVIDENCE.md index 030eb596..18b8172c 100644 --- a/docs/EVIDENCE.md +++ b/docs/EVIDENCE.md @@ -22,6 +22,8 @@ R1 固定总出口的源码为 `ce9b24d`,早期 `2dc8d6d` / `9b16acc` 仍分 原版输出自动化见 [cmd 正确性工具](../benchmarks/tools/WINDTERM-CMD.md)及[固定结果](../benchmarks/tools/results/2026-10-28-windterm-cmd/README.md):小 ASCII 全文、生产事件和 Job 回收已有无人值守闭环,路径元字符修复及各执行源码分列。它没有取得 C0、完整历史、消费/真实呈现端点或正式性能基线。 +[#133 设置读取诊断](../benchmarks/tools/results/2026-10-29-c0-settings-reference/README.md)在独立受控配置差异下核对 536B 全文,并分别读到 producer console / 原版 view 窗口 120×40、document capacity 23;QWidget 字体仅为元数据,28 条 dot-key 设置仍不可读。完整 C0、glyph/字体请求有效性、10000 行历史、autocomplete、呈现及性能继续未知。 + 显式官方后端的旧 `.3` 实现及独立生命期/双 SHA 拒绝记录见[实验合同](https://github.com/JavaZeroo/termforge/blob/e0b4d67e50bda6fe08cc02b43a475137133310b3/docs/research/experimental-conpty-backend.md)与[冻结验证](https://github.com/JavaZeroo/termforge/blob/e0b4d67e50bda6fe08cc02b43a475137133310b3/experiments/conpty/results/2026-10-26-reviewed-backend/validation.json)。联合源码 `3e5b96d` 的[三配置清单](../experiments/conpty/results/2026-10-26-joint-backend/validation.json)分别记录默认43/45、候选/system 48/48、显式官方46/48(shell套件超时,未完成项仍未知);原resize/Vim各一红一绿、粘贴两红。新恢复/worker/工作区失败由 #61–#63 承接,旧失败不因其他轮成功变成通过。 后续合并推送的两个独立失败见[原件审计](../experiments/shell/results/2026-10-26-postmerge-ci/validation.json):main `098f9ab` 工作区第二轮保存 stage2 [#72](https://github.com/JavaZeroo/termforge/issues/72),实验分支源码 `af563ba` 的 681 高度选择场景 [#73](https://github.com/JavaZeroo/termforge/issues/73)。两次 CI 实际均为默认自研核心/system;return2、Job0、desktop 已删除,原生退出/EOF 与逐运行 EXE 绑定仍未知。它们不等同已修 #61/#66,另一批 PR 成功不能覆盖这些失败。 diff --git a/research/ui/cmd_owned_launcher.py b/research/ui/cmd_owned_launcher.py new file mode 100644 index 00000000..c5f8586f --- /dev/null +++ b/research/ui/cmd_owned_launcher.py @@ -0,0 +1,64 @@ +"""Small owned-native boundary for independently identified cmd diagnostics.""" +from __future__ import annotations +import ctypes as c +from ctypes import wintypes as w +import json +from pathlib import Path +import platform +import re +import sys +import time + +from research.ui.launch import StartupInfo,ProcessInfo,OwnedJob,isolated_environment,digest +from research.ui.r1_launch import cli_command,run_cli +from benchmarks.tools.windterm_cmd import cleanup_owned,write_json,require + + +def render_template(template,out,run_id,thread_id,desktop_name): + require(re.fullmatch('[0-9a-f]{32}',run_id) and type(thread_id) is int and thread_id>0,'Invalid owned script identity') + replacements={'__UI_THREAD_ID__':str(thread_id),'__RUN_ID_JSON__':json.dumps(run_id),'__RUN_DIRECTORY_JSON__':json.dumps(out.as_posix()),'__DESKTOP_JSON__':json.dumps(desktop_name),'__TIMEOUT_MS__':'27000'} + for key,value in replacements.items(): + require(template.count(key)==1,'Fixed script placeholder differs') + template=template.replace(key,value) + return template + + +def launch_owned(app,out,rea,run_id,template,identity): + """Contain before resume, wait for CLI completion, and always drain owned Job.""" + require(sys.platform=='win32','Windows is required') + cli=cli_command(Path(rea));require(Path(cli[0]).is_file(),'Existing REA CLI unavailable') + environment=isolated_environment(out) + block=c.create_unicode_buffer('\0'.join(f'{key}={value}' for key,value in sorted(environment.items()))+'\0') + user,kernel=c.WinDLL('user32',use_last_error=True),c.WinDLL('kernel32',use_last_error=True) + user.CreateDesktopW.argtypes,user.CreateDesktopW.restype=[w.LPCWSTR,w.LPCWSTR,c.c_void_p,w.DWORD,w.DWORD,c.c_void_p],w.HANDLE + user.CloseDesktop.argtypes=[w.HANDLE] + kernel.CreateProcessW.argtypes=[w.LPCWSTR,w.LPWSTR,c.c_void_p,c.c_void_p,w.BOOL,w.DWORD,c.c_void_p,w.LPCWSTR,c.POINTER(StartupInfo),c.POINTER(ProcessInfo)] + kernel.ResumeThread.argtypes,kernel.ResumeThread.restype=[w.HANDLE],w.DWORD + kernel.WaitForSingleObject.argtypes,kernel.WaitForSingleObject.restype=[w.HANDLE,w.DWORD],w.DWORD + kernel.TerminateProcess.argtypes,kernel.CloseHandle.argtypes=[w.HANDLE,w.UINT],[w.HANDLE] + check,info,job=OwnedJob.check,ProcessInfo(),None + name='TermForgeCmdSettings-'+run_id;desktop=user.CreateDesktopW(name,None,None,0,0x1ff,None);check(desktop) + try: + job=OwnedJob();start=StartupInfo();start.cb,start.desktop=c.sizeof(start),name + binary=app/'WindTerm.exe';command=c.create_unicode_buffer('"'+str(binary)+'"') + check(kernel.CreateProcessW(str(binary),command,None,None,False,4|0x400,block,str(app),c.byref(start),c.byref(info))) + job.assign(info.pid) + script=render_template(template,out,run_id,info.tid,name) + (out/'probe.js').write_text(script,encoding='utf-8',newline='\n') + write_json(out/'run.json',{**identity,'run_id':run_id,'pid':info.pid,'tid':info.tid,'sha256':digest(binary),'script_sha256':digest(out/'probe.js'), + 'profile':'synthetic-local-only','desktop':'owned-non-input','rea_capture_seconds':30,'cli_wait_timeout_seconds':45,'whole_lifecycle_bound_seconds':None, + 'environment':{'os':platform.platform(),'python':platform.python_version()},'consumer_done':None,'presented_done':None}) + if kernel.ResumeThread(info.thread)==0xffffffff:raise c.WinError(c.get_last_error()) + time.sleep(2) + code=run_cli(cli+['instrument-with-frida','attach',str(info.pid),'--script-file',str(out/'probe.js'),'--duration-ms','30000','--format','json','--full-output'],out,job,45) + require(code==0,'REA invocation failed') + except BaseException as error: + if not (out/'launcher-failure.json').exists():write_json(out/'launcher-failure.json',{'reason':'settings-'+type(error).__name__}) + raise + finally: + try:cleanup_owned(kernel,info,job,out,run_id) + finally: + if job:job.close() + if info.thread:kernel.CloseHandle(info.thread) + if info.process:kernel.CloseHandle(info.process) + check(user.CloseDesktop(desktop)) diff --git a/research/ui/qt_cmd_settings.js b/research/ui/qt_cmd_settings.js new file mode 100644 index 00000000..b8165e14 --- /dev/null +++ b/research/ui/qt_cmd_settings.js @@ -0,0 +1,60 @@ +// New #133 driver appended to the fixed #119 helper prefix; never patch its globals. +function scalar(object,key){ + try{if(object===null||object===undefined)return {status:'owner-unavailable',type:'undefined',value:null};var value=object[key],type=value===null?'null':typeof value; + if(type==='undefined')return {status:'unavailable',type:type,value:null}; + var expected=key==='text.fontFamily1'?'string':key==='window.autoAdjustRowsAndColumns'||key==='terminal.autoComplete.enabled'?'boolean':'number'; + if(type==='null')return {status:'read',type:type,value:null}; + if(type==='number'||type==='boolean'||type==='string'){ + if(type!==expected)return {status:'invalid-type',type:type,value:null}; + if(type==='number'&&(!isFinite(value)||Math.abs(value)>10000000))return {status:'invalid-scalar',type:type,value:null}; + if(type==='string'&&(value.length>128||/[\\/\r\n]/.test(value)))return {status:'invalid-scalar',type:type,value:null}; + return {status:'read',type:type,value:value}; + } + return {status:'non-scalar',type:type,value:null}; + }catch(error){return {status:'read-threw',type:'unknown',value:null};} +} +function discover(object){ + var whitelist=['windowRows','windowColumns','screenLineCapacity','settings','config','configuration','getSetting','setting','value','scrollbackLimit','fontFamily1','fontSize','autoCompleteEnabled']; + var hits=[],count=0,truncated=false; + try{if(object===null||object===undefined)return {status:'owner-unavailable',scanned:0,truncated:false,hits:[]}; + for(var key in object){if(++count>128){truncated=true;count=128;break;}if(whitelist.indexOf(key)>=0)hits.push(key);} + return {status:'enumerated-names-only',scanned:count,truncated:truncated,hits:hits}; + }catch(error){return {status:'enumeration-threw',scanned:count,truncated:truncated,hits:hits};} +} +function state(){ + const value=evaluate(`JSON.stringify((function(){var t=window.activeText;if(!t)return null;var n=t.visibleLineCount;if(typeof n==='function')n=n.call(t);if(typeof n!=='number'||n<0||n>128)throw Error('logical line count bound');var rows=[];for(var i=0;i{if(!finished)postThreadMessage(uiThread,0,0,0);},100); +emit({kind:'probe_ready',desktop_verified:true,clipboard_access:false,module_guards:moduleGuards}); +Interceptor.attach(user.getExportByName('DispatchMessageW'),{onEnter(){ + if(busy||finished||Process.getCurrentThreadId()!==uiThread||Date.now()timeoutMs)throw Error('bounded diagnostic timeout'); + if(stage===0){scan();resize(main,1360,860);stage=1;} + else if(stage===1){const invalid=Memory.alloc(32);indexCtor(invalid);const ready=engine!==null&&!treeModel(tree).isNull()&&hasIndex(treeModel(tree),0,0,invalid);modelReady=ready?modelReady+1:0;if(modelReady>=2){expandAll(tree);focus(tree,7);key(tree,0x1000011);stage=2;}} + else if(stage===2){key(tree,0x1000004);emit({kind:'local_session_open_requested'});stage=3;due=Date.now()+1500;return;} + else if(stage===3){scan();if(editor!==null)stage=4;} + else if(stage===4){ + let producer=null;try{producer=JSON.parse(File.readAllText(directory+'/producer.json'));}catch(error){due=Date.now()+200;return;} + if(producer.run_id!==runId||producer.status!=='complete')throw Error('producer identity or completion'); + const snapshot=state();if(snapshot===null||!Array.isArray(snapshot.lines)||!snapshot.lines.includes('TF-CMD-END '+runId)){due=Date.now()+200;return;} + const rectangle=geometry(editor),face=font(editor); + emit({kind:'settings_complete',...snapshot,module_guards:moduleGuards,widget_geometry:[rectangle.readS32(),rectangle.add(4).readS32(),rectangle.add(8).readS32(),rectangle.add(12).readS32()],widget_font:{family:str(fontFamily,face),point_size:pointSize(face),pixel_size:pixelSize(face)}});finished=true; + } + due=Date.now()+250; + }catch(error){finished=true;emit({error:'bounded-settings-observation-failed',stage});}finally{busy=false;} +}}); diff --git a/research/ui/test_cmd_settings_probe.js b/research/ui/test_cmd_settings_probe.js new file mode 100644 index 00000000..e2bb9411 --- /dev/null +++ b/research/ui/test_cmd_settings_probe.js @@ -0,0 +1,21 @@ +'use strict'; +const fs=require('fs'),vm=require('vm'),assert=require('assert'),path=require('path'); +const source=fs.readFileSync(path.join(__dirname,'qt_cmd_settings.js'),'utf8'); +const context=vm.createContext({JSON,Number,Math,isFinite}); +vm.runInContext(source.slice(0,source.indexOf('function modulePathMatches')),context); +assert.strictEqual(context.scalar({windowRows:NaN},'windowRows').status,'invalid-scalar'); +assert.strictEqual(context.scalar({windowRows:Infinity},'windowRows').status,'invalid-scalar'); +assert.strictEqual(context.scalar({windowRows:true},'windowRows').status,'invalid-type'); +assert.strictEqual(context.scalar({'text.fontFamily1':'X'.repeat(129)},'text.fontFamily1').value,null); +assert.strictEqual(context.scalar({'text.fontFamily1':'C:/private/font'},'text.fontFamily1').status,'invalid-scalar'); +assert.strictEqual(context.scalar({value(){throw Error('must not call');}},'value').status,'non-scalar'); +const object={windowRows:24};for(let index=0;index<200;index++)object['unlisted'+index]=index; +const discovery=context.discover(object);assert.strictEqual(discovery.scanned,128);assert.strictEqual(discovery.truncated,true);assert.deepStrictEqual(JSON.parse(JSON.stringify(discovery.hits)),['windowRows']); +let activeCalls=0,fallbackCalls=0; +const t={visibleLineCount:1,lineText(){return 'synthetic';},screenLineCapacity:23,getSetting(){throw Error('unknown methods must not be called');}}; +context.evaluate=script=>vm.runInNewContext(script,{window:{activeText:t},terminal:{activeView(){activeCalls++;return {windowRows:24,windowColumns:80};},view(){fallbackCalls++;return {};}}}); +const state=context.state();assert.strictEqual(activeCalls,1);assert.strictEqual(fallbackCalls,0); +assert.strictEqual(state.view_metrics.window_rows.value,24);assert.strictEqual(state.view_metrics.window_columns.value,80); +assert.strictEqual(state.settings_reads.length,28);assert(state.settings_reads.every(row=>row.result.status==='unavailable'||row.result.status==='owner-unavailable')); +assert.strictEqual(state.discovery.length,4);assert(state.discovery[0].result.hits.includes('getSetting')); +process.stdout.write('bounded scalar/name reads and known current-view calls passed\n');