From 678804ff5201fab4296b133af9457b7120739b68 Mon Sep 17 00:00:00 2001 From: JavaZero <71128095+JavaZeroo@users.noreply.github.com> Date: Tue, 27 Oct 2026 13:04:21 +0800 Subject: [PATCH] research: verify same-source ConPTY native margin repair Add isolated source pair probes and bounded identity/lifecycle checks. Preserve the original 175-byte workload, native oracle and deadlines. Record the two-grid red-to-green comparison and VT-off controls, with full product and cross-contract limits remaining open. Related #56, #41. Reviewed local tree 6cc9ca72c616ba085d650aff8f3ad1c958cad65a. --- CMakeLists.txt | 14 + docs/EVIDENCE.md | 3 +- docs/FEATURES.md | 2 + docs/research/conpty-native-margin-path.md | 10 +- docs/research/conpty-source-margin-runtime.md | 68 ++ experiments/conpty/backend.hpp | 55 +- experiments/conpty/backend_probe.cpp | 23 +- experiments/conpty/margin_probe.cpp | 10 +- experiments/conpty/margin_run.py | 9 +- .../build-summary.json | 294 +++++ .../validation.json | 1035 +++++++++++++++++ experiments/conpty/session.hpp | 2 +- experiments/conpty/source_backend_check.py | 117 ++ experiments/conpty/source_margin_run.py | 153 +++ experiments/conpty/test_backend.py | 4 +- 15 files changed, 1773 insertions(+), 26 deletions(-) create mode 100644 docs/research/conpty-source-margin-runtime.md create mode 100644 experiments/conpty/results/2026-10-27-source-margin-runtime/build-summary.json create mode 100644 experiments/conpty/results/2026-10-27-source-margin-runtime/validation.json create mode 100644 experiments/conpty/source_backend_check.py create mode 100644 experiments/conpty/source_margin_run.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 846f1e13..010bb7db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,11 @@ cmake_minimum_required(VERSION 3.20) project(termforge_experiments VERSION 0.1.0 LANGUAGES CXX) option(TERMFORGE_BUILD_CANDIDATE_ADAPTER "Build the Rust candidate C++ adapter checks" OFF) +option(TERMFORGE_BUILD_SOURCE_CONPTY_EXPERIMENT "Build isolated fixed-source ConPTY research probes" OFF) set(TERMFORGE_CONPTY_BACKEND_DIR "" CACHE PATH "Explicit reviewed x64 ConPTY pair; empty uses the system API") +if(TERMFORGE_BUILD_SOURCE_CONPTY_EXPERIMENT AND (NOT WIN32 OR TERMFORGE_CONPTY_BACKEND_DIR)) + message(FATAL_ERROR "Source ConPTY probes require Windows and a separate build without a product backend directory") +endif() if(TERMFORGE_CONPTY_BACKEND_DIR AND NOT WIN32) message(FATAL_ERROR "Side-by-side ConPTY is a Windows-only experiment") endif() @@ -134,6 +138,16 @@ if(WIN32) add_executable(conpty_backend_probe experiments/conpty/backend_probe.cpp) termforge_warnings(conpty_backend_probe) target_compile_definitions(conpty_backend_probe PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0A00) + if(TERMFORGE_BUILD_SOURCE_CONPTY_EXPERIMENT) + add_executable(conpty_source_margin_probe experiments/conpty/margin_probe.cpp) + add_executable(conpty_source_backend_probe experiments/conpty/backend_probe.cpp) + foreach(target conpty_source_margin_probe conpty_source_backend_probe) + termforge_warnings(${target}) + target_compile_definitions(${target} PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0A00 + TERMFORGE_CONPTY_SOURCE_EXPERIMENT=1) + target_link_libraries(${target} PRIVATE user32 bcrypt) + endforeach() + endif() add_executable(conpty_resize_native_probe experiments/conpty/resize_native_probe.cpp) termforge_warnings(conpty_resize_native_probe) target_compile_definitions(conpty_resize_native_probe PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0A00) diff --git a/docs/EVIDENCE.md b/docs/EVIDENCE.md index 083a9af6..11c12d3f 100644 --- a/docs/EVIDENCE.md +++ b/docs/EVIDENCE.md @@ -50,7 +50,8 @@ R1 固定总出口的源码为 `ce9b24d`,早期 `2dc8d6d` / `9b16acc` 仍分 | controller B 正常结束后 cmd 退出未验收(#44) | [原失败脱敏记录](../experiments/shell/results/2026-10-26-controller-exit/validation.json)、[新退出诊断及有界重放](../experiments/shell/results/2026-10-26-controller-exit-observation/validation.json)、[关闭合同](https://github.com/JavaZeroo/termforge/issues/44) | 原 child 收完 32B、退出 0/join/Job0;cmd 只留下 close 后 0xC000013A,close 前未知。新观察分列输入、wait、pre-close 与清理;多轮未复现不构成历史修复,继续阻塞 #38。 | | 原 10 秒纯夹具超时的阶段保留(#40 未关闭) | [限定合同](research/inventory-timeout-checkpoints.md)、[正常与受控超时对照](../research/ime/results/2026-10-26-inventory-timeout-checkpoints/validation.json) | 保持原 10 秒期限;超时后保留有界阶段、原异常和回收。受控挂起对照已通过,但历史 CI 的阶段/根因仍未知,#40 不因此关闭。 | | 纯 AST 夹具的生命周期记录 | [#40](https://github.com/JavaZeroo/termforge/issues/40)、[计时、进度与回收验证](../research/ime/results/2026-10-26-inventory-lifecycle/validation.json)、[专用 CI 40253727745](https://github.com/JavaZeroo/termforge/actions/runs/40253727745) | 旧 10 秒 AST 夹具及专用 CI 记录启动、AST 和 Job0;原超时根因未确定。诊断增强见 #40 阶段保留,不由一次 CI 成功结项。 | -| 原生 margin 逐字段合同与修复候选(#56 仍开放) | [合同、源码与路径](research/conpty-native-margin-path.md)、[独立 oracle](../experiments/conpty/margin_contract.py)、[固定运行](../experiments/conpty/results/2026-10-26-native-margin-path/validation.json) | 四阶段文字/格属性/光标独立判据;系统与官方原生 final 仍红,官方80列两次原5秒生命周期失败保留。共享单 margin 源码支持小 MIT fork 候选;patch 仅可应用,未编译、未运行、不计修复。 | +| 原生 margin 逐字段合同与历史修复候选(#56 仍开放) | [合同、源码与路径](research/conpty-native-margin-path.md)、[独立 oracle](../experiments/conpty/margin_contract.py)、[2026-10-26固定运行](../experiments/conpty/results/2026-10-26-native-margin-path/validation.json) | 当时系统与官方原生 final 仍红,官方80列两次原5秒生命周期失败保留;补丁当时仅可应用。后续真实同源构建/运行另列,不改写旧失败。 | +| 固定源码 ConPTY margin 运行对照(#56 限定实验) | [构建身份、运行与复验](research/conpty-source-margin-runtime.md)、[白名单验证](../experiments/conpty/results/2026-10-27-source-margin-runtime/validation.json) | 同源baseline与patched,原175B两尺寸native red→green;四阶段文本/格属性/光标、末端文本和VT-off反例独立检查,共8次自然退出/EOF/Job0。15个API/身份场景及8个清单拒绝通过;显式样式、历史、resize、完整产品未验,未选择后端。 | | ConPTY 系统/固定官方后端的同源 margin A/B(#41、#47 未关闭) | [合同与第一方来源](research/conpty-margin-backends.md)、[固定 175B 与 16 case 结果](../experiments/conpty/results/2026-10-26-margin-backends/validation.json) | 相同 175B、两网格/两 mode/两 fresh:系统末端 margin 错,固定官方后端的末端正确;CHAR_INFO 仍错。禁用 VT 由窄屏反例拒绝;仅诊断可行性,不证明 PR #50 或 main 联合整合通过。 | | 实验输入 worker 持块与停止(#62) | [sender fence 合同](research/sender-fence.md)、[固定源码与实际字段](../experiments/shell/results/2026-10-26-sender-fence/validation.json) | `519a3f3` 的系统/官方各两进程、每次 stop/cancel/竞争,共 12 条实际 worker 记录,均 join/Job0;另有各一次原 focus 单测和各 2/2 纯 CTest。明确修正旧 never-read/output-first 不能保证 16KiB 的夹具假设,同 EXE 旧顺序仍红。实现仅在 PR #50 实验整合,main 未包含;probe 未链接候选 VT,纯 EOF 为模拟,#53 UI/3MB/768B/八会话及完整官方矩阵未验收。 | | 实验 UI 未完成粘贴(#53) | [机制与边界](research/ui-paste-fence.md)、[旧固定记录](../experiments/shell/results/2026-10-26-ui-paste-fence/validation.json)、[组合修复](research/resize-replay-cost.md)、[最终同源码记录](../experiments/shell/results/2026-10-26-replay-cost-and-close/validation.json) | `eae4b6a` 单窗口两轮、真实EOF/无GO负例及两轮原八会话data-only通过;行键复用及关闭期间不回复分别有诊断/因果回归,原completed/stage45/退出错误保留。完整CTest54/56仍有Vim59、resize43、main7/ui52与shell180秒期限;未计真实CF/IME、完整后端或性能通过。 | diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 681b95ec..b6bc7f8a 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -16,6 +16,8 @@ | 显式候选 main:`termforge.4` + 系统 ConPTY | `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON`;按 [ADR-0003](adr/0003-core-reference-and-reuse.md)保留为可评估实验,正式核心选型待用户确认 | [固定来源](../experiments/candidates/alacritty/PROVENANCE.md)、[适配边界](../experiments/core/CANDIDATE.md)与 [#39 字素合同](research/streaming-graphemes.md);候选正确性通过不关闭系统 ConPTY 或 R2 父项 | | 实验分支:`termforge.5` + 固定官方 ConPTY | [PR #50(Draft)](https://github.com/JavaZeroo/termforge/pull/50),`.4` 联合源码 `3e5b96d` 的[真实基线](research/joint-conpty-baseline.md)保留,尚未合并 main 或通过产品整体验收 | [#51 原生 viewport 修复](research/native-viewport-resize.md)的原 resize/Unicode 及原生样本、[#88 回显完成判据](research/workspace-restore-transport-oracle.md)的原保存/恢复/钳制分别通过;[#87 完整记录检查点](research/workflow-record-checkpoint.md)有受控旧红/新绿及最终同源码两轮原八会话 data-only。最新官方完整回归 56/57、8 项内部 skip,[Vim #52](https://github.com/JavaZeroo/termforge/issues/52)仍红,[MSYS输入边界](research/vim-msys-input-boundary.md)已定位捕获样本的Ctrl-O丢失位置,后续[隔离运行库补丁](research/vim-msys-runtime-prototype.md)在重建基点red后原方法四轮通过;正式固定运行库验收未完成,[#63 工作区残尾](research/workspace-native-viewport.md)已由同EXE单变量与运输回放确认现有viewport修复覆盖,原UI/主区三轮重复两批及完整shell通过;更早缺运输样本保留未知。[#53 UI fence](research/ui-paste-fence.md)与[行键复用/关闭回复](research/resize-replay-cost.md)、[#62 worker 夹具](research/sender-fence.md)各自限定通过。终端 margin 与原生状态分列,#47 及完整后端仍开放;旧失败和历史运输缺口保留 | +另有默认关闭的[源码 ConPTY margin 探针实验](research/conpty-source-margin-runtime.md):固定同源 baseline/patched 对照在原175B两尺寸取得原生四阶段 red→green,VT-off反例保持一致。它只构建独立探针,尚未成为产品后端;样式、历史、resize及完整合同仍由 #56/#41 跟踪。 + 以下能力默认指已合并实现;某个配置的通过结果不能转用于另一个配置。R1 的真实系统 CF/拼音验收属于固定自研核心配置,不外推到候选 `.4` 或实验后端联合配置。 ## 基础文字交互与本地终端 diff --git a/docs/research/conpty-native-margin-path.md b/docs/research/conpty-native-margin-path.md index f144898f..667473fc 100644 --- a/docs/research/conpty-native-margin-path.md +++ b/docs/research/conpty-native-margin-path.md @@ -1,8 +1,8 @@ # 保留原生与末端 margin 合同的实现路径 -[#56](https://github.com/JavaZeroo/termforge/issues/56) 的当前交付是可执行的逐字段原生 oracle、固定上游的补丁候选及下一次构建入口。 -**原生缺口仍存在,尚无经过真实运行证明的完整修复路径。** 补丁仅检查可应用,没有编译或替换任何后端。 -这保留 [#41](https://github.com/JavaZeroo/termforge/issues/41) 的原合同与开放状态,不改变 #34 产品决定。 +本文保留 2026-10-26 的逐字段原生 oracle、固定上游补丁候选与当时结果;其中“未编译/未运行”仅指该历史批次。 +后续[同源源码构建与运行对照](conpty-source-margin-runtime.md)已取得原175B两尺寸的 native red→green,VT-off反例保持一致。 +样式、历史、resize交叉及完整产品仍未验收,[#56](https://github.com/JavaZeroo/termforge/issues/56)与[#41](https://github.com/JavaZeroo/termforge/issues/41)继续开放;不改变 #34 产品决定或下文原失败。 ## 逐字段合同与测量边界 @@ -35,7 +35,7 @@ Microsoft [每缓冲区 scrolling margins 声明](https://learn.microsoft.com/en oracle 同时比较两个原生读入口的文字、完整格/属性和光标。初始颜色由原夹具外部默认决定,因此只验证其跨阶段保持, 不把某次默认颜色推为所有宿主的硬合同。历史、resize、改变样式和末端各阶段光标仍明确列为未测。 -## 本批固定运行与失败保留 +## 2026-10-26 固定运行与失败保留 [白名单证据](../../experiments/conpty/results/2026-10-26-native-margin-path/validation.json) 固定 main `3d6d44d` 的未修改原生探针、 单独冻结的候选 `.4` trace、官方同包 DLL/EXE 双 SHA、源码、flags、期限与生命周期;原件只留 `local/`。 @@ -68,7 +68,7 @@ oracle 同时比较两个原生读入口的文字、完整格/属性和光标。 这是固定源码和运行现象共同支持的待证伪原因,尚缺仅改变该状态归属的真实 red→green。 Microsoft 文档仍声明 per-buffer;目前没有上游确认允许把这种差异当成设计豁免,也没有证据证明其他上游版本已修复。 -## 路径比较与具体下一步 +## 2026-10-26 路径比较与后续入口 | 路径 | 当前证据与边界 | 维护和采用条件 | |---|---|---| diff --git a/docs/research/conpty-source-margin-runtime.md b/docs/research/conpty-source-margin-runtime.md new file mode 100644 index 00000000..9b6e7f95 --- /dev/null +++ b/docs/research/conpty-source-margin-runtime.md @@ -0,0 +1,68 @@ +# 同源 ConPTY 原生 margin 运行对照 + +固定 Microsoft Terminal `dc8ae365847d41f62d1ebca93815bb00959bc12f` 的两组重建产物,在原始 175 字节、两种网格和原期限下完成一次预定对照:未修补版复现原生 final 错误,应用 margin 状态补丁后四阶段原生文字、逐格字符/属性和光标全部符合原独立预期。实际运输经候选核心回放,两版末端画面均正确;VT-off 反例在两版之间保持一致。 + +这提供了原用例的同源因果证据。它没有覆盖样式变化、历史、resize 交叉及完整产品合同,不能关闭 [#56](https://github.com/JavaZeroo/termforge/issues/56) 或 [#41](https://github.com/JavaZeroo/termforge/issues/41),也不决定 #34 的产品后端。原系统/官方包失败与新源码构建结果分开保存。 + +## 来源与构建身份 + +[PR #96](https://github.com/JavaZeroo/termforge/pull/96) 的 [v2 构建 40449292015](https://github.com/JavaZeroo/termforge/actions/runs/40449292015) 使用同一固定源码、依赖和工具链: + +- Terminal 提交如上,vcpkg baseline 为 `927f62e4b8838bd7e441e9c45103a16ffd75007e`;原 NuGet/vcpkg 版本和 feature 不变。 +- 实际 CI checkout 为 `ad857d448dfe64c0c11d10254043a8b893e7546a`,VS18 / MSVC 14.51.36231 / SDK 26100;详细工具和文件身份见[构建摘要](../../experiments/conpty/results/2026-10-27-source-margin-runtime/build-summary.json)。 +- 补丁 SHA-256 为 `dc27377e399bd0217a0b76e16cc89f087c1b781929e4c958ad30c3a475322b81`。仅测试补丁时 focused 测试明确失败一次、退出 1;随后保存 baseline EXE/DLL,再应用两个生产文件的补丁,focused 1/1、完整 adapter 412/412、零跳过,最后保存 patched EXE/DLL。 +- 两组都有独立 `variant.json`,绑定生产补丁是否应用、测试状态、双文件 SHA 与大小;共享 summary 的完整 adapter 通过只属于 patched。baseline 没有执行完整 adapter。 + +| 文件 | baseline SHA-256 / 字节 | patched SHA-256 / 字节 | +|---|---|---| +| OpenConsole.exe | `fabdc934d9755c405fe848251905e8c0e70a2bf7702b8ed7ccd104eec6591588` / 1146880 | `1c8f071f6e4779ec297fb041af4eaea39be29b6920cef625ab91d1cbc20c79cb` / 1147392 | +| conpty.dll | `9d165745cce4b29a5818a45c23f68e962b3a3140c2e00566bd17fee970fdd9d1` / 108544 | 与 baseline 逐字相同 | + +原始 summary SHA 为 `8132695b32162ff22e61d2de166a31fad508f0b63cbb46de8d1612bd197e1ded`,两个 pair 和 summary artifact 中相同。artifact `12710186210`(summary)、`12710186222`(baseline)、`12710146241`(patched)的 ZIP SHA、来源和内部文件均已核对,记录在[验证清单](../../experiments/conpty/results/2026-10-27-source-margin-runtime/validation.json)。入库构建摘要是规范化 UTF-8/LF 的解析内容;原始摘要字节 SHA 不冒充入库文件 SHA。 + +这些是带 Microsoft MIT LICENSE/NOTICE 的短期实验产物,不是正式包。没有把二进制、私人路径、原始提取或 WindTerm 文件加入源码库。artifact 保留期为 14 天;之后可按固定构建入口重建,但新文件身份须重新审核,不宣称逐字节可复现。 + +## 隔离入口与身份拒绝 + +`TERMFORGE_BUILD_SOURCE_CONPTY_EXPERIMENT` 默认 OFF,仅建立 `conpty_source_margin_probe` 与 `conpty_source_backend_probe`,不能与产品 `TERMFORGE_CONPTY_BACKEND_DIR` 同时启用。只有这两个目标编译新的 source factory;默认及官方 factory 仍严格接受各自原身份。产品 bundle、默认后端和 PR #50 的固定官方配置均未切换。 + +source factory 只允许表中两个 SHA 对,分别报告 `source-dc8-r56-baseline-run40449292015` / `source-dc8-r56-patched-run40449292015`。Python 入口另核对原始 summary、patch、精确 variant 字段与类型、文件大小和 SHA;布尔值不能以 0/1 代替。DLL 与 EXE 在加载期间保持只读共享句柄;create/resize/close 使用同一模块,模块存活至会话和子进程回收之后。 + +独立 API/身份验证实际执行 15 个原生场景及 8 个清单拒绝检查:两版 identity、create/resize/自然退出/EOF/卸载;官方入口拒绝两版 source,source 拒绝官方、相对路径、缺失和未知文件;分别篡改 DLL/EXE;交换 variant、数字冒充布尔、修改 summary 或文件字节均拒绝。两版 DLL 本来相同,不虚构“混用相同 DLL”的可识别差异。另有系统 API 生命周期、原官方/系统 loader 14/14 和原 margin/oracle/cleanup 13/13 回归。 + +13/13 与 14/14 的计数和耗时来自当时命令输出,未单独保存 stdout 日志;验证清单明确标记这一可复核限制。8个margin样本和15个API/身份场景的原始记录另外完整保存,不借用两项会话输出代替。 + +## 原始 175B 的运行结果 + +本机 Windows 11 x64 build 26200,独立探针使用 VS17 / MSVC 19.42.34433 构建;候选 trace 为 `termforge.5`、Rust 1.92.0。运行时代码为 `fdee94fe94d48e83f5a61f191bddfc812d43a2f8`、工作树干净;探针、trace、参与源文件的 SHA 保存在验证清单。API/身份检查脚本字节绑定后续提交 `08ec4708afa347d87b584def73a3dba5021f7870`,不把它写成前一轮运行时源码。 + +源 SHA 为 `01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59`,175 字节与四阶段保持原样;原生预期来自[独立 oracle](../../experiments/conpty/margin_contract.py),没有通过候选 parser 生成。每种 variant × 网格 × VT mode 各一次,共 8 次,没有自动重试或失败后延长预算。 + +| 网格与模式 | baseline | patched | 生命周期 | +|---|---|---|---| +| 80×24、VT-on | before/alt/restored 全部正确;final 文字/格错误、光标正确,出现 `REGION_CIXED` | 四阶段文字、格/属性、光标全部正确;主屏区域恢复 | 均自然退出 37、EOF 109、Job0,无 force | +| 12×7、VT-on | 同一 final 错误;保留结束标记换行 | 四阶段全部正确,结束标记仍分两行 | 同上 | +| 80×24、VT-off | 两版完整原生格 SHA、文本、光标和末端文本一致 | 不把 VT-off 当成一般修复方案 | 同上 | +| 12×7、VT-off | 同上,原窄屏反例保留 | 同上 | 同上 | + +VT-on 两版的实际运输经同一候选 trace 回放后,主屏末端文本均正确、alternate=false;说明 baseline 原生错误不能由末端正确掩盖。这里没有测末端逐阶段光标。原生属性检查仅覆盖原 source 未设置 SGR 时的初始属性保持,不外推为显式样式恢复。 + +所有 8 次保留原 5 秒 child wait、15 秒外部期限与共享 5 秒 cleanup;creation flags 为 0,外 Job 最终 active0/total4/terminated0,未强制回收。另一个 API 生命周期探针使用原 20 秒外部期限,source flags24 的 create/resize/close 检查不能与 flags0 margin 拼成完整产品结果。 + +原件保存在 `local/r56-native-runtime/original-matrix-1` 和 `backend-check-2`;公开清单只保留合成结果与审核字段。首轮 margin 测试调用漏设 `TERMFORGE_BIN_DIR` 导致两个 setup error,修正调用后原 13 项通过;首次新增身份检查误要求 Job total=1,实际拒绝样本 total2/active0,已改为保留实际总数并严格检查回收。两项失败均保留,不修改原用例或期限。 + +## 复验与后续边界 + +先按[构建入口](../../experiments/conpty/upstream/README.md)取得并审核上述固定产物,在全新输出目录执行: + +```powershell +cmake -S . -B local/source-margin-build -A x64 -DTERMFORGE_BUILD_SOURCE_CONPTY_EXPERIMENT=ON -DTERMFORGE_BUILD_CANDIDATE_ADAPTER=OFF -DTERMFORGE_CONPTY_BACKEND_DIR= -DBUILD_TESTING=OFF +cmake --build local/source-margin-build --config Release --target conpty_backend_probe conpty_source_backend_probe conpty_source_margin_probe --parallel 2 +cargo +1.92.0 build --manifest-path experiments/candidates/alacritty/Cargo.toml --locked --release --bin termforge-alacritty-trace +python -m experiments.conpty.source_margin_run --baseline local/source-baseline --patched local/source-patched --probe local/source-margin-build/bin/Release/conpty_source_margin_probe.exe --candidate-trace experiments/candidates/alacritty/target/release/termforge-alacritty-trace.exe --output local/source-margin-new +python -m experiments.conpty.source_backend_check --baseline local/source-baseline --patched local/source-patched --official local/official-conpty-1.25 --probe-directory local/source-margin-build/bin/Release --output local/source-backend-new +``` + +待补无备用屏、重复 h/l、默认 margin、非零光标和显式样式、历史隔离、resize、查询及 API 写入的真实交叉;上游 focused 测试虽覆盖重复 h/l,不能代替这些原生运行。之后仍需实际产品联合源码、支持平台、资源与维护/升级验证,并由 #34 提出正式方案供用户确认。 + +独立的 [#97 candidate-scroll 退出超时](https://github.com/JavaZeroo/termforge/issues/97)发生于 parent `5c073e4` 的 candidate/system push CI:正确末帧已经产生,但原 20 秒期限未自然结束。它不是本 175B 输入,同 HEAD 的其他 CI 成功不能覆盖该失败;此处的源构建修补也不构成其因果修复。原官方 80 列 5 秒失败、Vim #52、完整 R2、WindTerm 行为等价、性能与实际呈现均没有由本实验验收。 diff --git a/experiments/conpty/backend.hpp b/experiments/conpty/backend.hpp index 3195dcea..74cd8733 100644 --- a/experiments/conpty/backend.hpp +++ b/experiments/conpty/backend.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,7 @@ class Backend { private: struct Api { HMODULE module = nullptr; + const char* identity = "system"; decltype(&::CreatePseudoConsole) create = ::CreatePseudoConsole; decltype(&::ResizePseudoConsole) resize = ::ResizePseudoConsole; decltype(&::ClosePseudoConsole) close = ::ClosePseudoConsole; @@ -75,22 +77,26 @@ class Backend { for (const auto byte : bytes) { value += hex[byte >> 4]; value += hex[byte & 15]; } return value; } -public: - static Backend system() { - static const auto api = std::make_shared(); - return Backend(api); - } - static Backend reviewed_side_by_side(const std::filesystem::path& directory, - GlyphWidth width = GlyphWidth::Default) { + struct ReviewedPair { + const char* dll_sha256; + const char* host_sha256; + const char* identity; + }; + static Backend load_reviewed_pair(const std::filesystem::path& directory, GlyphWidth width, + std::initializer_list pairs) { if (sizeof(void*) != 8 || !directory.is_absolute()) throw std::runtime_error("ConPTY reviewed backend requires x64 and an absolute directory"); const auto canonical = std::filesystem::canonical(directory); const auto dll = canonical / L"conpty.dll"; File dll_file(dll), host_file(canonical / L"OpenConsole.exe"); - if (digest(dll_file.get()) != "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a" || - digest(host_file.get()) != "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586") - throw std::runtime_error("ConPTY reviewed backend identity mismatch"); + const auto dll_hash = digest(dll_file.get()), host_hash = digest(host_file.get()); + const char* identity = nullptr; + for (const auto& pair : pairs) { + if (dll_hash == pair.dll_sha256 && host_hash == pair.host_sha256) identity = pair.identity; + } + if (!identity) throw std::runtime_error("ConPTY reviewed backend identity mismatch"); auto api = std::make_shared(); + api->identity = identity; api->module = LoadLibraryExW(dll.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); if (!api->module) throw std::runtime_error("ConPTY reviewed backend load failed"); @@ -105,10 +111,36 @@ class Backend { throw std::runtime_error("ConPTY reviewed backend module identity differs"); if (width != GlyphWidth::Default && width != GlyphWidth::Console) throw std::runtime_error("ConPTY reviewed glyph width policy invalid"); - // Fixed official 1.25 conpty.h: GLYPH_WIDTH_CONSOLE=0x18. This is + // Fixed official 1.25 and source dc8 conpty.h: GLYPH_WIDTH_CONSOLE=0x18. This is // a console API compatibility policy, not the removed passthrough. return Backend(std::move(api), width == GlyphWidth::Console ? 0x18 : 0); } +public: + static Backend system() { + static const auto api = std::make_shared(); + return Backend(api); + } + static Backend reviewed_side_by_side(const std::filesystem::path& directory, + GlyphWidth width = GlyphWidth::Default) { + return load_reviewed_pair(directory, width, {{ + "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586", + "reviewed-side-by-side-1.25.260930003"}}); + } +#ifdef TERMFORGE_CONPTY_SOURCE_EXPERIMENT + // Available only to separately compiled research probes. The official + // factory above continues to accept exactly its original matching pair. + static Backend reviewed_source_side_by_side(const std::filesystem::path& directory, + GlyphWidth width = GlyphWidth::Default) { + return load_reviewed_pair(directory, width, { + {"9d165745cce4b29a5818a45c23f68e962b3a3140c2e00566bd17fee970fdd9d1", + "fabdc934d9755c405fe848251905e8c0e70a2bf7702b8ed7ccd104eec6591588", + "source-dc8-r56-baseline-run40449292015"}, + {"9d165745cce4b29a5818a45c23f68e962b3a3140c2e00566bd17fee970fdd9d1", + "1c8f071f6e4779ec297fb041af4eaea39be29b6920cef625ab91d1cbc20c79cb", + "source-dc8-r56-patched-run40449292015"}}); + } +#endif static Backend configured() { #ifdef TERMFORGE_CONPTY_SIDEBYSIDE wchar_t image[32768]{}; @@ -127,6 +159,7 @@ class Backend { HRESULT resize(HPCON console, COORD size) const { return api_->resize(console, size); } void close(HPCON console) const { api_->close(console); } bool side_by_side() const { return api_->module != nullptr; } + const char* identity() const { return api_->identity; } DWORD creation_flags() const { return creation_flags_; } }; } // namespace tf_conpty diff --git a/experiments/conpty/backend_probe.cpp b/experiments/conpty/backend_probe.cpp index 137564e8..4db92665 100644 --- a/experiments/conpty/backend_probe.cpp +++ b/experiments/conpty/backend_probe.cpp @@ -62,21 +62,36 @@ int wmain(int argc, wchar_t** argv) { require(GetModuleHandleW(L"conpty.dll") == nullptr, "backend unexpectedly loaded before selection"); const bool side_by_side = std::wstring(argv[2]) != L"system"; const auto select = [&] { +#ifdef TERMFORGE_CONPTY_SOURCE_EXPERIMENT + return side_by_side ? tf_conpty::Backend::reviewed_source_side_by_side(argv[2], tf_conpty::Backend::GlyphWidth::Console) + : tf_conpty::Backend::system(); +#else return side_by_side ? tf_conpty::Backend::reviewed_side_by_side(argv[2], tf_conpty::Backend::GlyphWidth::Console) : tf_conpty::Backend::system(); +#endif }; if (std::wstring(argv[1]) == L"--identity") { auto backend = select(); - std::cout << "{\"identity_accepted\":true}\n"; + std::cout << "{\"identity_accepted\":true"; +#ifdef TERMFORGE_CONPTY_SOURCE_EXPERIMENT + std::cout << ",\"backend_identity\":\"" << backend.identity() << '"'; +#endif + std::cout << "}\n"; return 0; } bool loaded_during_session = false, loaded_after_close = false; DWORD exit_code = STILL_ACTIVE, active_after = MAXDWORD, read_error = 0; bool cleanup_complete = false, forced_reader = false, forced_process = false; +#ifdef TERMFORGE_CONPTY_SOURCE_EXPERIMENT + std::string selected_identity; +#endif { // The selected temporary is destroyed at this semicolon. The // Session's shared API ownership must retain the DLL thereafter. tf_conpty::Session session(select()); +#ifdef TERMFORGE_CONPTY_SOURCE_EXPERIMENT + selected_identity = session.backend_identity(); +#endif loaded_during_session = GetModuleHandleW(L"conpty.dll") != nullptr; session.start(self_path(), L"--client"); require(session.wait_text("TF_BACKEND_READY_80x24", 5000), "backend initial marker missing"); @@ -100,7 +115,11 @@ int wmain(int argc, wchar_t** argv) { << ",\"module_unloaded_after_session\":" << module_unloaded << ",\"exit_code\":" << exit_code << ",\"active_children_after\":" << active_after << ",\"read_error\":" << read_error << ",\"cleanup_complete\":" << cleanup_complete << ",\"forced_reader_cancel\":" << forced_reader - << ",\"forced_process_cancel\":" << forced_process << "}\n"; + << ",\"forced_process_cancel\":" << forced_process; +#ifdef TERMFORGE_CONPTY_SOURCE_EXPERIMENT + std::cout << ",\"backend_identity\":\"" << selected_identity << '"'; +#endif + std::cout << "}\n"; return valid ? 0 : 1; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 2; } } diff --git a/experiments/conpty/margin_probe.cpp b/experiments/conpty/margin_probe.cpp index cfdea50b..e02ba705 100644 --- a/experiments/conpty/margin_probe.cpp +++ b/experiments/conpty/margin_probe.cpp @@ -68,7 +68,11 @@ int wmain(int argc, wchar_t** argv) { if (std::wstring(argv[1]) != L"system") { const std::filesystem::path dll(argv[1]); require(dll.is_absolute() && dll.filename() == L"conpty.dll", "absolute conpty.dll path required"); +#ifdef TERMFORGE_CONPTY_SOURCE_EXPERIMENT + backend = tf_conpty::Backend::reviewed_source_side_by_side(dll.parent_path()); +#else backend = tf_conpty::Backend::reviewed_side_by_side(dll.parent_path()); +#endif } wchar_t self[32768]{}; const DWORD length = GetModuleFileNameW(nullptr, self, 32768); @@ -90,7 +94,11 @@ int wmain(int argc, wchar_t** argv) { << ",\"forced_reader_cancel\":" << session.forced_reader_cancel << ",\"forced_process_cancel\":" << session.forced_process_cancel << ",\"cleanup_complete\":" << session.cleanup_complete - << ",\"active_children_after\":" << session.active_after << "}\n"; + << ",\"active_children_after\":" << session.active_after; +#ifdef TERMFORGE_CONPTY_SOURCE_EXPERIMENT + std::cout << ",\"backend_identity\":\"" << session.backend_identity() << '"'; +#endif + std::cout << "}\n"; return waited && session.exit_code == 37 && session.cleanup_complete ? 0 : 1; } catch (const std::exception& e) { std::cerr << e.what() << '\n'; return 2; } } diff --git a/experiments/conpty/margin_run.py b/experiments/conpty/margin_run.py index ef1264cd..da0d98cb 100644 --- a/experiments/conpty/margin_run.py +++ b/experiments/conpty/margin_run.py @@ -49,13 +49,15 @@ def backend_path(value): return str(path) -def validate_lifecycle(native, side_by_side=False): +def validate_lifecycle(native, side_by_side=False, *, backend_identity=None): expected = { "waited": True, "exit_code": 37, "read_error": 109, "capture_overflow": False, "forced_reader_cancel": False, "forced_process_cancel": False, "cleanup_complete": True, "active_children_after": 0, "side_by_side": side_by_side, "creation_flags": 0, } + if backend_identity is not None: + expected["backend_identity"] = backend_identity if (type(native) is not dict or native.keys() != expected.keys() or any(type(native[key]) is not type(value) or native[key] != value for key, value in expected.items())): @@ -109,7 +111,8 @@ def inspect_case(out, trace, mode, columns, rows): } -def execute(executable, backend, trace, out, mode, columns, rows, *, gate=b"GO\n", timeout=15): +def execute(executable, backend, trace, out, mode, columns, rows, *, gate=b"GO\n", timeout=15, + backend_identity=None): if mode not in ("vt-on", "vt-off") or (columns, rows) not in ((80,24), (12,7)): raise ValueError("fixed workload required") out.mkdir(parents=True, exist_ok=False) @@ -146,7 +149,7 @@ def cleanup_fault(code): raise ValueError("native probe failed") native = json.loads(stdout) stage = "native_lifecycle" - validate_lifecycle(native, side_by_side=backend != "system") + validate_lifecycle(native, side_by_side=backend != "system", backend_identity=backend_identity) stage = "case_inspection" result.update(inspect_case(out, trace, mode, columns, rows), native=native, status="completed") except BaseException: diff --git a/experiments/conpty/results/2026-10-27-source-margin-runtime/build-summary.json b/experiments/conpty/results/2026-10-27-source-margin-runtime/build-summary.json new file mode 100644 index 00000000..3e0b091a --- /dev/null +++ b/experiments/conpty/results/2026-10-27-source-margin-runtime/build-summary.json @@ -0,0 +1,294 @@ +{ + "schema": "termforge-upstream-conpty-build-v2", + "purpose": "isolated source-build and adapter experiment; not an official ConPTY package", + "status": "adapter-verified-runtime-unverified", + "termforge_source": "ad857d448dfe64c0c11d10254043a8b893e7546a", + "upstream_source": "dc8ae365847d41f62d1ebca93815bb00959bc12f", + "vcpkg_source": "927f62e4b8838bd7e441e9c45103a16ffd75007e", + "image_os": "win25-vs2026", + "image_version": "20261021.278.1", + "workflow_run": "40449292015", + "workflow_attempt": "1", + "configuration": "Release/x64/v145/WindowsSDK10.0.26100.0", + "stages": [ + { + "name": "vcpkg-tool-download", + "exit_code": 0, + "elapsed_seconds": 0.7866937, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "vcpkg-version", + "exit_code": 0, + "elapsed_seconds": 0.0399271, + "log_sha256": "fea4ead8f40a8aeca48fc22406d6102a7f1e07373fc0d6a5decb129457a43161" + }, + { + "name": "nuget-restore", + "exit_code": 0, + "elapsed_seconds": 13.0426646, + "log_sha256": "5e70d22a3fe292a819ec1459a71a5db932927a6a5c61df3c4761c6dfc9a85bb9" + }, + { + "name": "baseline-patch-check", + "exit_code": 0, + "elapsed_seconds": 0.0436494, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "baseline-test-patch", + "exit_code": 0, + "elapsed_seconds": 0.0362571, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "baseline-proxy-build", + "exit_code": 0, + "elapsed_seconds": 71.6812146, + "log_sha256": "e5911ff82f7de99e1ed420bbf32afe56748651db63db74406b21451459d69954" + }, + { + "name": "baseline-types-build", + "exit_code": 0, + "elapsed_seconds": 7.5849581, + "log_sha256": "2517edeadee0c2ece0eb57d76fc9b638eb4c24dcb36bff67d7aff6bce2c01fd4" + }, + { + "name": "baseline-buffer-build", + "exit_code": 0, + "elapsed_seconds": 8.0015929, + "log_sha256": "5e0cd5be3d1fa74b0db5bd0ca246b75cad536912d1264cb2a04ce5243fe483cc" + }, + { + "name": "baseline-render-base-build", + "exit_code": 0, + "elapsed_seconds": 5.39903, + "log_sha256": "6a80b6b730498b86e451d25f08194d592438d4f02dca945aa6dd7ad4c67919c4" + }, + { + "name": "baseline-render-gdi-build", + "exit_code": 0, + "elapsed_seconds": 5.6435044, + "log_sha256": "5ffba2be38849e5bedbc3f852b4a416fbc44eb25b859c0ad59115626ece82867" + }, + { + "name": "baseline-adapter-build", + "exit_code": 0, + "elapsed_seconds": 83.908652, + "log_sha256": "27de5599e32a701c602340f27ae0c9ca1cdf9c42ec2b9c9ab5f169a2c204f631" + }, + { + "name": "baseline-regression", + "exit_code": 1, + "elapsed_seconds": 0.2718681, + "log_sha256": "d1907fb1f5e1dd008eef1b1e5611d6533d37538df0a2a6cba0871784cb8d4988" + }, + { + "name": "baseline-regression-contract", + "exit_code": 0, + "elapsed_seconds": 0.0513568, + "log_sha256": "b7aa5de14c43240cceac182a506bd51937189c7b22b4a3998c8ec37bba6f1896" + }, + { + "name": "baseline-propsheet-build", + "exit_code": 0, + "elapsed_seconds": 10.929838, + "log_sha256": "b0ca024bae4050c4acbc7bea2ba6435710a8d7ea4b8f70fe351d79dba20f8eaf" + }, + { + "name": "baseline-host-build", + "exit_code": 0, + "elapsed_seconds": 22.6881976, + "log_sha256": "3186d0fc9afea5dd466b68042787b259cf722a84d8a429850309d5a1d4e50bb8" + }, + { + "name": "baseline-dll-build", + "exit_code": 0, + "elapsed_seconds": 8.9063766, + "log_sha256": "1dbb9fc9a551831d8d69ad67a7142e3247c28f92982a04bf42784be75ea8c650" + }, + { + "name": "production-patch-check", + "exit_code": 0, + "elapsed_seconds": 0.0368057, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "production-patch", + "exit_code": 0, + "elapsed_seconds": 0.035935, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "patched-adapter-build", + "exit_code": 0, + "elapsed_seconds": 7.9965399, + "log_sha256": "fd4e8df0088e935f74ac60407a659bfc69638f0329e12777fa9ab33cef1d57f1" + }, + { + "name": "patched-regression", + "exit_code": 0, + "elapsed_seconds": 0.2678428, + "log_sha256": "b0413a44a181bbd07e81dcf612d18a00423baa01da4a28d0759004dc2603873c" + }, + { + "name": "patched-regression-contract", + "exit_code": 0, + "elapsed_seconds": 0.0473823, + "log_sha256": "da281146aa232fafccb57afd0100781005fc28c4625a5a782e0513e12912573d" + }, + { + "name": "patched-adapter", + "exit_code": 0, + "elapsed_seconds": 1.9669664, + "log_sha256": "343a699ea0b2bf861cf9af70d8a93e5660f1086a73d59c3b9ab48971acd66704" + }, + { + "name": "patched-adapter-contract", + "exit_code": 0, + "elapsed_seconds": 0.0649075, + "log_sha256": "332b578d7b8603574df41dea702bfb7ef14c7e201ff24b194c57a0bf61c2ff4f" + }, + { + "name": "patched-propsheet-build", + "exit_code": 0, + "elapsed_seconds": 0.8511354, + "log_sha256": "fd638f0969daef7e599fa8151210c63fc249df769639a208d10d9925becef4ad" + }, + { + "name": "patched-host-build", + "exit_code": 0, + "elapsed_seconds": 6.3205807, + "log_sha256": "a50332e3893eea02a86feb72af5673dccf0757f299e8343192ec028cbdb60b81" + }, + { + "name": "patched-dll-build", + "exit_code": 0, + "elapsed_seconds": 0.7732798, + "log_sha256": "568584e4abd446efe5e74f0f66c1aa921f5f337201ac5910122a66aa73b3a156" + } + ], + "test_binaries": [ + { + "stage": "baseline-regression", + "test_dll_sha256": "0e5e32fa345b104a669337fb26488e0d80812500bfc7a7f3e665f7e4b4c6320f", + "taef_sha256": "b729c665a51a1e36dc24152ecde4e4a7efb618b75dc7387d8c6edb103ef952c1" + }, + { + "stage": "patched-regression", + "test_dll_sha256": "a46029f198ad998d52d65df9b94902ed7ddb545bbd46fce587eabe23b5eab1b0", + "taef_sha256": "b729c665a51a1e36dc24152ecde4e4a7efb618b75dc7387d8c6edb103ef952c1" + }, + { + "stage": "patched-adapter", + "test_dll_sha256": "a46029f198ad998d52d65df9b94902ed7ddb545bbd46fce587eabe23b5eab1b0", + "taef_sha256": "b729c665a51a1e36dc24152ecde4e4a7efb618b75dc7387d8c6edb103ef952c1" + } + ], + "adapter_tests_verified": true, + "native_runtime_verified": false, + "full_r2_contract_verified": false, + "product_backend_selected": false, + "vcpkg_shallow": "false", + "patch_sha256": "dc27377e399bd0217a0b76e16cc89f087c1b781929e4c958ad30c3a475322b81", + "source_hashes_verified": true, + "tools": { + "visual_studio": "18.0", + "msvc": "14.51.36231", + "sdk": "10.0.26100.0\\", + "compiler_sha256": "b0a1818ebae6da7f18230a84e76ce839212584ac4c2ab55b97a0d5268085da57", + "msbuild_sha256": "fc55d2743dbbb20708b2ca6084fc5b036a2ce72291ff989eae94ad8220ae2f74" + }, + "vcpkg_exe_sha256": "f1edbf3a39de350e2bb065214fdc057111aa87a2e2ed9a7dcb8ddc86e17751b9", + "baseline_files": { + "OpenConsole.exe": { + "sha256": "fabdc934d9755c405fe848251905e8c0e70a2bf7702b8ed7ccd104eec6591588", + "bytes": 1146880 + }, + "conpty.dll": { + "sha256": "9d165745cce4b29a5818a45c23f68e962b3a3140c2e00566bd17fee970fdd9d1", + "bytes": 108544 + } + }, + "files": { + "OpenConsole.exe": { + "sha256": "1c8f071f6e4779ec297fb041af4eaea39be29b6920cef625ab91d1cbc20c79cb", + "bytes": 1147392 + }, + "conpty.dll": { + "sha256": "9d165745cce4b29a5818a45c23f68e962b3a3140c2e00566bd17fee970fdd9d1", + "bytes": 108544 + } + }, + "nuget_packages": [ + { + "name": "Appium.WebDriver.3.0.0.2.nupkg", + "sha256": "c67adece6bd5730036812af29614a7e5699f048b8e0d261841f47d2007358702" + }, + { + "name": "Castle.Core.4.1.1.nupkg", + "sha256": "c3f130d751103ec89a4ec24ec4f3c8bfa02c4d7ca70a865b68c79eddae5dea45" + }, + { + "name": "Microsoft.Internal.PGO-Helpers.Cpp.0.2.34.nupkg", + "sha256": "92aca6465e2cbe04b312d8255d15d7af2e5cfceee59fc91194c1228283bfc453" + }, + { + "name": "Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.8.250811004.nupkg", + "sha256": "c70c34c5ed0b937a23285402396c3f23523496dca59ede2aaddc2914f6ea339d" + }, + { + "name": "Microsoft.MSBuildCache.AzurePipelines.0.1.283-preview.nupkg", + "sha256": "9a5a7815a175000b8c608d94727758f0c22a8e6d5f318515169cf9d003b36f82" + }, + { + "name": "Microsoft.MSBuildCache.Local.0.1.283-preview.nupkg", + "sha256": "732767cf82dc8d4e83790d9421c4c7bdbb7b5bbc6e9ca3bb2d3e79cb92dbed79" + }, + { + "name": "Microsoft.MSBuildCache.SharedCompilation.0.1.283-preview.nupkg", + "sha256": "40d947d33e2b31341870831b320de4db2926026e2e0dbdcb71099fae1a876f34" + }, + { + "name": "Microsoft.Taef.10.100.251104001.nupkg", + "sha256": "46b1290acaebc7cbcd3c4ac2522d9526cd7c4dad3a618759754619a052396a4b" + }, + { + "name": "Microsoft.UI.Xaml.2.8.4.nupkg", + "sha256": "486dbe1f271b31057ec01ecb7ce4df3eedad11efdc26569170fb128ee4a4e964" + }, + { + "name": "Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262.nupkg", + "sha256": "43c2d1c5694a846d04d5f1cb3c3a8269ebad3b0432954da2948e53b33c5ed5fb" + }, + { + "name": "Microsoft.Web.WebView2.1.0.1661.34.nupkg", + "sha256": "bad4c011c20ab68526c4c1448da05a6297586806123019ac89387fc62f46c3ce" + }, + { + "name": "Microsoft.Windows.CppWinRT.2.0.250303.1.nupkg", + "sha256": "955e3051b35db1c00177ed14ab6b7b995c3b32a53fdb6c184ea53b1dba66e439" + }, + { + "name": "Microsoft.Windows.ImplementationLibrary.1.0.250325.1.nupkg", + "sha256": "b3e3ac9077c4f5f96bd8f90785bcae47bac6ba9688d896cd04422c8972ec5b2b" + }, + { + "name": "Microsoft.WindowsPackageManager.ComInterop.1.8.1911.nupkg", + "sha256": "9b8a8a5186a554e56fcb7997b6ea48f3432813c0945a2d9ba60930d1241cfe2b" + }, + { + "name": "Newtonsoft.Json.13.0.1.nupkg", + "sha256": "2b6b52556e27e1b7913f33eedeb95568110c746bd64afff74357f1683878323a" + }, + { + "name": "Selenium.Support.3.5.0.nupkg", + "sha256": "0716656ae84c72cc31595ee2f06bf1f59311b96f813f9ada3d31d7f7f72ffb82" + }, + { + "name": "Selenium.WebDriver.3.5.0.nupkg", + "sha256": "79c41eb770a8728a15ddf27c7ac41ca433597983ac37e4db7746a52e16cec876" + } + ], + "vcpkg_status_sha256": "9957c37c4d2da3b71509a3180374a6b5ddb4126f9e1a6cffe46a9ea0c307337c" +} diff --git a/experiments/conpty/results/2026-10-27-source-margin-runtime/validation.json b/experiments/conpty/results/2026-10-27-source-margin-runtime/validation.json new file mode 100644 index 00000000..0930b308 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-source-margin-runtime/validation.json @@ -0,0 +1,1035 @@ +{ + "schema": "termforge-source-conpty-margin-v1", + "generated_utc": "2026-10-27T04:36:45.581770+00:00", + "upstream_source": "dc8ae365847d41f62d1ebca93815bb00959bc12f", + "build_run": "40449292015", + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "termforge_source": "fdee94fe94d48e83f5a61f191bddfc812d43a2f8", + "working_tree_dirty": false, + "source_files": { + "CMakeLists.txt": "44f3b6bb99d1836b9fd5597213080e55dfe6b5d09ce13ca4afe6d9be7f6fa46a", + "experiments/conpty/backend.hpp": "af672718af8c05e90a26576e317b6ac9295fbee76f3f0298371060a9d167f5bb", + "experiments/conpty/session.hpp": "242ac8aa13f3fd56675b2ddefaaf207e5f4c8d3ef8e703260aaed2f0c41677aa", + "experiments/conpty/margin_probe.cpp": "2316c237785a367772c5adcd98e09b38e31bd9eef0bdc154a204287070244335", + "experiments/conpty/margin_run.py": "99e6caf18224ce7b41307355066db70eacb810bc0626b15d3af09c9a89439ed2", + "experiments/conpty/margin_contract.py": "f6ff82099b4675d7f64c9bbebcd226b9c3d5da1998a20c8477945956eadd3561", + "experiments/conpty/source_margin_run.py": "26d07c9f912b1bb68b89d33a989ee80189230002869df83a572a22e7b2f6f657" + }, + "platform": "Windows-11-10.0.26200-SP0", + "probe_sha256": "9b2debc8060f1addb2e348f28e3468a99851ca35484039163becc3b588d009ba", + "trace_sha256": "5941d4569d0ae146eb005f12ecfb15014bc06c18ee48d1c9460824ab65df93d3", + "pair_receipts": { + "baseline": { + "schema": "termforge-upstream-conpty-variant-v1", + "upstream_source": "dc8ae365847d41f62d1ebca93815bb00959bc12f", + "workflow_run": "40449292015", + "production_patch_applied": false, + "focused_adapter_result": "expected-failure", + "full_adapter_verified": false, + "patch_sha256": "dc27377e399bd0217a0b76e16cc89f087c1b781929e4c958ad30c3a475322b81", + "files": { + "OpenConsole.exe": { + "sha256": "fabdc934d9755c405fe848251905e8c0e70a2bf7702b8ed7ccd104eec6591588", + "bytes": 1146880 + }, + "conpty.dll": { + "sha256": "9d165745cce4b29a5818a45c23f68e962b3a3140c2e00566bd17fee970fdd9d1", + "bytes": 108544 + } + } + }, + "patched": { + "schema": "termforge-upstream-conpty-variant-v1", + "upstream_source": "dc8ae365847d41f62d1ebca93815bb00959bc12f", + "workflow_run": "40449292015", + "production_patch_applied": true, + "focused_adapter_result": "passed", + "full_adapter_verified": true, + "patch_sha256": "dc27377e399bd0217a0b76e16cc89f087c1b781929e4c958ad30c3a475322b81", + "files": { + "OpenConsole.exe": { + "sha256": "1c8f071f6e4779ec297fb041af4eaea39be29b6920cef625ab91d1cbc20c79cb", + "bytes": 1147392 + }, + "conpty.dll": { + "sha256": "9d165745cce4b29a5818a45c23f68e962b3a3140c2e00566bd17fee970fdd9d1", + "bytes": 108544 + } + } + } + }, + "comparisons": [ + { + "columns": 80, + "rows": 24, + "baseline_expected_native_failure": true, + "patched_all_native_stages_passed": true, + "both_terminal_finals_passed": true, + "vt_off_unchanged": true, + "passed": true + }, + { + "columns": 12, + "rows": 7, + "baseline_expected_native_failure": true, + "patched_all_native_stages_passed": true, + "both_terminal_finals_passed": true, + "vt_off_unchanged": true, + "passed": true + } + ], + "passed": true, + "full_r2_contract_verified": false, + "product_backend_selected": false, + "presented_done": null, + "unmeasured": [ + "styled_source", + "history", + "resize_cross_margin", + "terminal_stage_cursors", + "full_product_workflows", + "performance", + "WindTerm_equivalence" + ], + "samples": { + "baseline-80x24-vt-on": { + "status": "completed", + "mode": "vt-on", + "columns": 80, + "rows": 24, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 218, + "transport_sha256": "d793c84c56624d6c647e562ef7ba35e21d8998baa43371cb36f95b2add684246", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native": { + "waited": true, + "side_by_side": true, + "creation_flags": 0, + "exit_code": 37, + "read_error": 109, + "capture_overflow": false, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "cleanup_complete": true, + "active_children_after": 0, + "backend_identity": "source-dc8-r56-baseline-run40449292015" + }, + "native_contract": { + "schema": "termforge-native-margin-contract-v1", + "columns": 80, + "rows": 24, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "initial_native_attribute": 7, + "stages": { + "before": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + }, + "alt": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + }, + "restored": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + }, + "final": { + "text": false, + "cell_text": false, + "cells_and_attributes": false, + "cursor": true, + "passed": false + } + }, + "all_native_stages_passed": false, + "unmeasured": [ + "styled_source", + "history", + "resize", + "terminal_stage_cursors" + ], + "full_r2_contract_verified": false + }, + "native_cell_sha256": { + "before": "fe9016196e03b264cce22f628f67b778fe1e6a3aeb160ed53effc153b1ff6651", + "alt": "0fd582bf6115b38b1dbe57084658f686392c74b4d6b1340f945cfcbca4ff7635", + "restored": "fe9016196e03b264cce22f628f67b778fe1e6a3aeb160ed53effc153b1ff6651", + "final": "00896cdf02c29564c82f4b2f7b118e6c1ac5cb8a0b8f8f5a0d91be3556a94421" + }, + "native_main_margins_restored": false, + "terminal_main_margins_restored": true, + "terminal_alternate": false, + "replay_passed": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [] + }, + "patched-80x24-vt-on": { + "status": "completed", + "mode": "vt-on", + "columns": 80, + "rows": 24, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 218, + "transport_sha256": "d793c84c56624d6c647e562ef7ba35e21d8998baa43371cb36f95b2add684246", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native": { + "waited": true, + "side_by_side": true, + "creation_flags": 0, + "exit_code": 37, + "read_error": 109, + "capture_overflow": false, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "cleanup_complete": true, + "active_children_after": 0, + "backend_identity": "source-dc8-r56-patched-run40449292015" + }, + "native_contract": { + "schema": "termforge-native-margin-contract-v1", + "columns": 80, + "rows": 24, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "initial_native_attribute": 7, + "stages": { + "before": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + }, + "alt": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + }, + "restored": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + }, + "final": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + } + }, + "all_native_stages_passed": true, + "unmeasured": [ + "styled_source", + "history", + "resize", + "terminal_stage_cursors" + ], + "full_r2_contract_verified": false + }, + "native_cell_sha256": { + "before": "fe9016196e03b264cce22f628f67b778fe1e6a3aeb160ed53effc153b1ff6651", + "alt": "0fd582bf6115b38b1dbe57084658f686392c74b4d6b1340f945cfcbca4ff7635", + "restored": "fe9016196e03b264cce22f628f67b778fe1e6a3aeb160ed53effc153b1ff6651", + "final": "1dc413a54e0b457e9a301fb6dc2eb9d40733314e5fb589a60c6a8951edd29800" + }, + "native_main_margins_restored": true, + "terminal_main_margins_restored": true, + "terminal_alternate": false, + "replay_passed": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [] + }, + "baseline-80x24-vt-off": { + "status": "completed", + "mode": "vt-off", + "columns": 80, + "rows": 24, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 218, + "transport_sha256": "d793c84c56624d6c647e562ef7ba35e21d8998baa43371cb36f95b2add684246", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native": { + "waited": true, + "side_by_side": true, + "creation_flags": 0, + "exit_code": 37, + "read_error": 109, + "capture_overflow": false, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "cleanup_complete": true, + "active_children_after": 0, + "backend_identity": "source-dc8-r56-baseline-run40449292015" + }, + "native_cell_sha256": { + "before": "d1c4bf6e7fe26f05575ef2c2b14b99774a1396ecc521b71e25937f302302f57d", + "alt": "29e73cec4d64d55aaac58b0278ba0baeaacacb6751d33c65eb2e0e80c1eadd28", + "restored": "6ac38720c2b51b66f34cb786408f6a2967b5a8023ca5546044c91f7ed4f2e154", + "final": "5374bda9fdcf2000436f66d7d6a9acce59149362247208987ea7935e3f462766" + }, + "native_main_margins_restored": false, + "terminal_main_margins_restored": true, + "terminal_alternate": false, + "replay_passed": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [] + }, + "patched-80x24-vt-off": { + "status": "completed", + "mode": "vt-off", + "columns": 80, + "rows": 24, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 218, + "transport_sha256": "d793c84c56624d6c647e562ef7ba35e21d8998baa43371cb36f95b2add684246", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native": { + "waited": true, + "side_by_side": true, + "creation_flags": 0, + "exit_code": 37, + "read_error": 109, + "capture_overflow": false, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "cleanup_complete": true, + "active_children_after": 0, + "backend_identity": "source-dc8-r56-patched-run40449292015" + }, + "native_cell_sha256": { + "before": "d1c4bf6e7fe26f05575ef2c2b14b99774a1396ecc521b71e25937f302302f57d", + "alt": "29e73cec4d64d55aaac58b0278ba0baeaacacb6751d33c65eb2e0e80c1eadd28", + "restored": "6ac38720c2b51b66f34cb786408f6a2967b5a8023ca5546044c91f7ed4f2e154", + "final": "5374bda9fdcf2000436f66d7d6a9acce59149362247208987ea7935e3f462766" + }, + "native_main_margins_restored": false, + "terminal_main_margins_restored": true, + "terminal_alternate": false, + "replay_passed": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [] + }, + "baseline-12x7-vt-on": { + "status": "completed", + "mode": "vt-on", + "columns": 12, + "rows": 7, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 218, + "transport_sha256": "d793c84c56624d6c647e562ef7ba35e21d8998baa43371cb36f95b2add684246", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native": { + "waited": true, + "side_by_side": true, + "creation_flags": 0, + "exit_code": 37, + "read_error": 109, + "capture_overflow": false, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "cleanup_complete": true, + "active_children_after": 0, + "backend_identity": "source-dc8-r56-baseline-run40449292015" + }, + "native_contract": { + "schema": "termforge-native-margin-contract-v1", + "columns": 12, + "rows": 7, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "initial_native_attribute": 7, + "stages": { + "before": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + }, + "alt": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + }, + "restored": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + }, + "final": { + "text": false, + "cell_text": false, + "cells_and_attributes": false, + "cursor": true, + "passed": false + } + }, + "all_native_stages_passed": false, + "unmeasured": [ + "styled_source", + "history", + "resize", + "terminal_stage_cursors" + ], + "full_r2_contract_verified": false + }, + "native_cell_sha256": { + "before": "323107891dd2f32c2be1fecd7ae03379c96f8fe38eff938ece8f19eeebe6dbf3", + "alt": "c7b9b5456a0566673db0a45765f1de63ac3ae76501da1be8cc3f0aadf848b3b7", + "restored": "323107891dd2f32c2be1fecd7ae03379c96f8fe38eff938ece8f19eeebe6dbf3", + "final": "86cdf8cc46178d0eff6f3b801d3ce2eab0bb714db9fedd98e018ee46538489ea" + }, + "native_main_margins_restored": false, + "terminal_main_margins_restored": true, + "terminal_alternate": false, + "replay_passed": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [] + }, + "patched-12x7-vt-on": { + "status": "completed", + "mode": "vt-on", + "columns": 12, + "rows": 7, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 218, + "transport_sha256": "d793c84c56624d6c647e562ef7ba35e21d8998baa43371cb36f95b2add684246", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native": { + "waited": true, + "side_by_side": true, + "creation_flags": 0, + "exit_code": 37, + "read_error": 109, + "capture_overflow": false, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "cleanup_complete": true, + "active_children_after": 0, + "backend_identity": "source-dc8-r56-patched-run40449292015" + }, + "native_contract": { + "schema": "termforge-native-margin-contract-v1", + "columns": 12, + "rows": 7, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "initial_native_attribute": 7, + "stages": { + "before": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + }, + "alt": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + }, + "restored": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + }, + "final": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "passed": true + } + }, + "all_native_stages_passed": true, + "unmeasured": [ + "styled_source", + "history", + "resize", + "terminal_stage_cursors" + ], + "full_r2_contract_verified": false + }, + "native_cell_sha256": { + "before": "323107891dd2f32c2be1fecd7ae03379c96f8fe38eff938ece8f19eeebe6dbf3", + "alt": "c7b9b5456a0566673db0a45765f1de63ac3ae76501da1be8cc3f0aadf848b3b7", + "restored": "323107891dd2f32c2be1fecd7ae03379c96f8fe38eff938ece8f19eeebe6dbf3", + "final": "9f777d4471731af76d13ae779cc060c1920a8e4b0e5a41aa38858bb14bc41440" + }, + "native_main_margins_restored": true, + "terminal_main_margins_restored": true, + "terminal_alternate": false, + "replay_passed": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [] + }, + "baseline-12x7-vt-off": { + "status": "completed", + "mode": "vt-off", + "columns": 12, + "rows": 7, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 222, + "transport_sha256": "01064e034cc1cd3c34eae65a2f656ae940936cf381e60371b8e17428ec00617d", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native": { + "waited": true, + "side_by_side": true, + "creation_flags": 0, + "exit_code": 37, + "read_error": 109, + "capture_overflow": false, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "cleanup_complete": true, + "active_children_after": 0, + "backend_identity": "source-dc8-r56-baseline-run40449292015" + }, + "native_cell_sha256": { + "before": "fff7ff68ab09ddf930d4f85b4d59f50336bcaca7e891b36c7171607d74d693ef", + "alt": "feb6fabd4a576f1f15af2cdf03adf188f7269eb61d676684d81113e3661c4bc0", + "restored": "2f8d6538d3f8570df2eca932c4a8ad9292b35cd231d367c9a75a571723f00ea9", + "final": "b19f6d442b71d2bb273dd991216132b25b0d753572084cc5761bbced02a9128f" + }, + "native_main_margins_restored": false, + "terminal_main_margins_restored": true, + "terminal_alternate": false, + "replay_passed": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [] + }, + "patched-12x7-vt-off": { + "status": "completed", + "mode": "vt-off", + "columns": 12, + "rows": 7, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 222, + "transport_sha256": "01064e034cc1cd3c34eae65a2f656ae940936cf381e60371b8e17428ec00617d", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native": { + "waited": true, + "side_by_side": true, + "creation_flags": 0, + "exit_code": 37, + "read_error": 109, + "capture_overflow": false, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "cleanup_complete": true, + "active_children_after": 0, + "backend_identity": "source-dc8-r56-patched-run40449292015" + }, + "native_cell_sha256": { + "before": "fff7ff68ab09ddf930d4f85b4d59f50336bcaca7e891b36c7171607d74d693ef", + "alt": "feb6fabd4a576f1f15af2cdf03adf188f7269eb61d676684d81113e3661c4bc0", + "restored": "2f8d6538d3f8570df2eca932c4a8ad9292b35cd231d367c9a75a571723f00ea9", + "final": "b19f6d442b71d2bb273dd991216132b25b0d753572084cc5761bbced02a9128f" + }, + "native_main_margins_restored": false, + "terminal_main_margins_restored": true, + "terminal_alternate": false, + "replay_passed": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [] + } + }, + "same_source_build": { + "head": "5c073e49ecef6722f997cdc9a7d3b8dd97e594ee", + "actual_checkout": "ad857d448dfe64c0c11d10254043a8b893e7546a", + "run_id": 40449292015, + "job_id": 122007975668, + "summary_sha256": "8132695b32162ff22e61d2de166a31fad508f0b63cbb46de8d1612bd197e1ded", + "artifacts": { + "summary": { + "id": 12710186210, + "sha256": "681f2337cbb6e666110230871671057ab5c8468b1013914051e5c1e0132ee2fe" + }, + "baseline": { + "id": 12710186222, + "sha256": "770a1a1bb77adca8039fef6e6dc777e187afee18db5f9e7a46f1e685958e1c7b" + }, + "patched": { + "id": 12710146241, + "sha256": "7efed8379ac0e66a0a7cd0720d2f342c72d446b4a7f8bfb22548e787ad9b08df" + } + } + }, + "backend_checks": { + "schema": "termforge-source-conpty-backend-check-v1", + "build_run": "40449292015", + "probe_sha256": { + "conpty_source_backend_probe.exe": "604c4815bb9329bdae1700137a5ee57b87c494a6bc93e0e1cf4124586ea85003", + "conpty_backend_probe.exe": "2d921cc7d82f912ab87a9a6d0c73d9e6e7acfcde00d0d88020d3997d5732d1c7" + }, + "native_cases": { + "baseline-identity": { + "status": "completed", + "failure_code": null, + "returncode": 0, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "identity_accepted": true, + "backend_identity": "source-dc8-r56-baseline-run40449292015" + }, + "error": null + }, + "baseline-lifecycle": { + "status": "completed", + "failure_code": null, + "returncode": 0, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "valid": true, + "side_by_side": true, + "creation_flags": 24, + "loaded_during_session": true, + "loaded_after_close": true, + "module_unloaded_after_session": true, + "exit_code": 37, + "active_children_after": 0, + "read_error": 109, + "cleanup_complete": true, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "backend_identity": "source-dc8-r56-baseline-run40449292015" + }, + "error": null + }, + "official-rejects-baseline": { + "status": "completed", + "failure_code": null, + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": null, + "error": "identity_rejected" + }, + "baseline-rejects-conpty.dll-tamper": { + "status": "completed", + "failure_code": null, + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": null, + "error": "identity_rejected" + }, + "baseline-rejects-OpenConsole.exe-tamper": { + "status": "completed", + "failure_code": null, + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": null, + "error": "identity_rejected" + }, + "patched-identity": { + "status": "completed", + "failure_code": null, + "returncode": 0, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "identity_accepted": true, + "backend_identity": "source-dc8-r56-patched-run40449292015" + }, + "error": null + }, + "patched-lifecycle": { + "status": "completed", + "failure_code": null, + "returncode": 0, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "valid": true, + "side_by_side": true, + "creation_flags": 24, + "loaded_during_session": true, + "loaded_after_close": true, + "module_unloaded_after_session": true, + "exit_code": 37, + "active_children_after": 0, + "read_error": 109, + "cleanup_complete": true, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "backend_identity": "source-dc8-r56-patched-run40449292015" + }, + "error": null + }, + "official-rejects-patched": { + "status": "completed", + "failure_code": null, + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": null, + "error": "identity_rejected" + }, + "patched-rejects-conpty.dll-tamper": { + "status": "completed", + "failure_code": null, + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": null, + "error": "identity_rejected" + }, + "patched-rejects-OpenConsole.exe-tamper": { + "status": "completed", + "failure_code": null, + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": null, + "error": "identity_rejected" + }, + "source-rejects-official": { + "status": "completed", + "failure_code": null, + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": null, + "error": "identity_rejected" + }, + "source-rejects-relative": { + "status": "completed", + "failure_code": null, + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": null, + "error": "identity_rejected" + }, + "source-rejects-missing": { + "status": "completed", + "failure_code": null, + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": null, + "error": "identity_rejected" + }, + "source-rejects-unknown": { + "status": "completed", + "failure_code": null, + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": null, + "error": "identity_rejected" + }, + "system-lifecycle": { + "status": "completed", + "failure_code": null, + "returncode": 0, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "outer_job_after_forced_cleanup": null, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "valid": true, + "side_by_side": false, + "creation_flags": 0, + "loaded_during_session": false, + "loaded_after_close": false, + "module_unloaded_after_session": true, + "exit_code": 37, + "active_children_after": 0, + "read_error": 109, + "cleanup_complete": true, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "backend_identity": "system" + }, + "error": null + } + }, + "receipt_rejections": [ + "baseline-wrong-variant", + "baseline-numeric-boolean", + "baseline-summary-bytes", + "baseline-binary-bytes", + "patched-wrong-variant", + "patched-numeric-boolean", + "patched-summary-bytes", + "patched-binary-bytes" + ], + "passed": true, + "product_backend_selected": false, + "full_r2_contract_verified": false + }, + "backend_check_source": { + "commit": "08ec4708afa347d87b584def73a3dba5021f7870", + "script_sha256": "4ea7a8feded7caa5ea5d2aedd4db335f8aee5a2a9d0b827a6bfa0b1de539bd0f" + }, + "preserved_failures": [ + { + "scope": "first local margin test invocation", + "result": "2 setup errors: explicit build directory omitted; no native invocation in these two tests", + "resolution": "set TERMFORGE_BIN_DIR to the actual new build; original 13 tests passed without source or deadline change" + }, + { + "scope": "backend-check-1", + "result": "new checker incorrectly required total Job members=1; known official rejection and source rejection both recorded total2/active0", + "resolution": "retain observed total, require active0/terminated0 and no force; corrected checker passed 15 native cases and 8 receipt rejections; original backend contracts unchanged" + }, + { + "scope": "push candidate/system CI 40449286764", + "result": "candidate-scroll original20s timeout after correct final frame; parent build-only source did not alter this path", + "issue": 97, + "causal_fix": false + } + ], + "validation": { + "original_margin_and_cleanup_tests": { + "passed": 13, + "seconds": 0.377, + "evidence_kind": "session-command-output-only", + "standalone_stdout_log_saved": false + }, + "existing_system_and_official_loader_checks": { + "passed": 14, + "seconds": 1.551, + "evidence_kind": "session-command-output-only", + "standalone_stdout_log_saved": false + }, + "source_native_cases": 15, + "source_receipt_rejections": 8, + "source_matrix_executions": 8, + "native_matrix_deadlines": { + "child_wait_seconds": 5, + "outer_seconds": 15, + "cleanup_seconds": 5 + }, + "api_lifetime_probe_outer_seconds": 20 + } +} diff --git a/experiments/conpty/session.hpp b/experiments/conpty/session.hpp index d44f3666..f52a4419 100644 --- a/experiments/conpty/session.hpp +++ b/experiments/conpty/session.hpp @@ -62,7 +62,7 @@ class Session { Session& operator=(const Session&) = delete; ~Session() { close(); } const char* backend_identity() const { - return backend_.side_by_side() ? "reviewed-side-by-side-1.25.260930003" : "system"; + return backend_.identity(); } DWORD backend_creation_flags() const { return backend_.creation_flags(); } diff --git a/experiments/conpty/source_backend_check.py b/experiments/conpty/source_backend_check.py new file mode 100644 index 00000000..08582f73 --- /dev/null +++ b/experiments/conpty/source_backend_check.py @@ -0,0 +1,117 @@ +"""Actual identity, API lifetime and rejection checks for fixed source pairs.""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import shutil + +from experiments.conpty.margin_run import backend_path, sha256 +from experiments.conpty.source_margin_run import reviewed_pair, identity, RUN +from experiments.conpty.test_backend import BackendTests + + +def run(baseline, patched, official, probe_directory, output): + pairs = {name: reviewed_pair(folder, name)[0] for name, folder in + (("baseline", baseline), ("patched", patched))} + official = Path(backend_path(str(Path(official).resolve() / "conpty.dll"))).parent + probes = Path(probe_directory).resolve(strict=True) + source_probe = probes / "conpty_source_backend_probe.exe" + official_probe = probes / "conpty_backend_probe.exe" + output = Path(output).resolve() + output.mkdir(parents=True, exist_ok=False) + subject = BackendTests() + subject.out, subject.serial = output / "_private", 0 + result = {"schema": "termforge-source-conpty-backend-check-v1", "build_run": RUN, + "probe_sha256": {p.name: sha256(p) for p in (source_probe, official_probe)}, + "native_cases": {}, "receipt_rejections": [], "passed": False, + "product_backend_selected": False, "full_r2_contract_verified": False} + + def invoke(name, directory, accepted, *, variant=None, lifecycle=False, executable=source_probe): + row = subject.invoke(directory, "--host" if lifecycle else "--identity", executable=executable) + result["native_cases"][name] = row + if not accepted: + subject.assertEqual(row["returncode"], 2) + subject.assertIsNone(row["native"]) + # Job totals can include the hidden console host; retain the + # observed count rather than infer child creation from it. + subject.assertEqual(row["outer_job_after"]["active"], 0) + subject.assertEqual(row["outer_job_after"]["terminated"], 0) + subject.assertGreaterEqual(row["outer_job_after"]["total"], 1) + return + expected_identity = "system" if variant is None else identity(variant) + subject.assertEqual(row["native"]["backend_identity"], expected_identity) + if lifecycle: + checked = dict(row, native={k: v for k, v in row["native"].items() if k != "backend_identity"}) + subject.check_lifecycle(checked, variant is not None) + else: + subject.assertEqual(row["returncode"], 0) + subject.assertEqual(row["native"], {"identity_accepted": True, "backend_identity": expected_identity}) + + def reject_receipt(name, directory, variant): + try: + reviewed_pair(directory, variant) + except ValueError: + result["receipt_rejections"].append(name) + else: + raise AssertionError("changed receipt accepted") + + try: + for variant, directory in pairs.items(): + invoke(f"{variant}-identity", directory, True, variant=variant) + invoke(f"{variant}-lifecycle", directory, True, variant=variant, lifecycle=True) + invoke(f"official-rejects-{variant}", directory, False, executable=official_probe) + for name in ("conpty.dll", "OpenConsole.exe"): + changed = output / "_private" / f"{variant}-tamper-{name}" + changed.mkdir(parents=True) + for member in ("conpty.dll", "OpenConsole.exe"): + shutil.copyfile(directory / member, changed / member) + payload = bytearray((changed / name).read_bytes()) + payload[-1] ^= 1 + (changed / name).write_bytes(payload) + invoke(f"{variant}-rejects-{name}-tamper", changed, False) + reject_receipt(f"{variant}-wrong-variant", directory, + "patched" if variant == "baseline" else "baseline") + changed = output / "_private" / f"{variant}-receipt" + shutil.copytree(directory, changed) + receipt = json.loads((changed / "variant.json").read_text(encoding="utf-8-sig")) + receipt["production_patch_applied"] = int(receipt["production_patch_applied"]) + (changed / "variant.json").write_text(json.dumps(receipt), encoding="utf-8") + reject_receipt(f"{variant}-numeric-boolean", changed, variant) + shutil.copyfile(directory / "variant.json", changed / "variant.json") + with (changed / "summary.json").open("ab") as file: + file.write(b" ") + reject_receipt(f"{variant}-summary-bytes", changed, variant) + shutil.copyfile(directory / "summary.json", changed / "summary.json") + payload = bytearray((changed / "OpenConsole.exe").read_bytes()) + payload[-1] ^= 1 + (changed / "OpenConsole.exe").write_bytes(payload) + reject_receipt(f"{variant}-binary-bytes", changed, variant) + invoke("source-rejects-official", official, False) + invoke("source-rejects-relative", "relative-directory", False) + missing = output / "_private" / "empty" + missing.mkdir() + invoke("source-rejects-missing", missing, False) + for name in ("conpty.dll", "OpenConsole.exe"): + (missing / name).write_bytes(b"unreviewed") + invoke("source-rejects-unknown", missing, False) + invoke("system-lifecycle", "system", True, lifecycle=True) + result["passed"] = True + finally: + (output / "summary.json").write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8") + return result + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + for name in ("baseline", "patched", "official", "probe-directory", "output"): + parser.add_argument("--" + name, type=Path, required=True) + args = parser.parse_args() + result = run(args.baseline, args.patched, args.official, args.probe_directory, args.output) + print(json.dumps({"passed": result["passed"], "native_cases": len(result["native_cases"]), + "receipt_rejections": len(result["receipt_rejections"])})) + return int(not result["passed"]) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/conpty/source_margin_run.py b/experiments/conpty/source_margin_run.py new file mode 100644 index 00000000..679eb059 --- /dev/null +++ b/experiments/conpty/source_margin_run.py @@ -0,0 +1,153 @@ +"""Bounded same-source native margin comparison; no product backend selection.""" +from __future__ import annotations + +import argparse +from datetime import datetime, timezone +import json +from pathlib import Path +import platform +import subprocess + +from experiments.conpty.margin_contract import inspect_native +from experiments.conpty.margin_run import execute, sha256, SOURCE_SHA256 + +ROOT = Path(__file__).resolve().parents[2] +RUN = "40449292015" +UPSTREAM = "dc8ae365847d41f62d1ebca93815bb00959bc12f" +PATCH = "dc27377e399bd0217a0b76e16cc89f087c1b781929e4c958ad30c3a475322b81" +SUMMARY = "8132695b32162ff22e61d2de166a31fad508f0b63cbb46de8d1612bd197e1ded" +DLL = "9d165745cce4b29a5818a45c23f68e962b3a3140c2e00566bd17fee970fdd9d1" +HOSTS = { + "baseline": ("fabdc934d9755c405fe848251905e8c0e70a2bf7702b8ed7ccd104eec6591588", 1146880), + "patched": ("1c8f071f6e4779ec297fb041af4eaea39be29b6920cef625ab91d1cbc20c79cb", 1147392), +} + + +def identity(variant): + if variant not in HOSTS: + raise ValueError("unknown source variant") + return f"source-dc8-r56-{variant}-run{RUN}" + + +def reviewed_pair(directory, variant): + """Bind both binary bytes and their audited build receipt before launch.""" + directory = Path(directory).resolve(strict=True) + host_hash, host_bytes = HOSTS[variant] + files = {"OpenConsole.exe": {"sha256": host_hash, "bytes": host_bytes}, + "conpty.dll": {"sha256": DLL, "bytes": 108544}} + expected = {"schema": "termforge-upstream-conpty-variant-v1", "upstream_source": UPSTREAM, + "workflow_run": RUN, "production_patch_applied": variant == "patched", + "focused_adapter_result": "passed" if variant == "patched" else "expected-failure", + "full_adapter_verified": variant == "patched", "patch_sha256": PATCH, "files": files} + receipt = json.loads((directory / "variant.json").read_text(encoding="utf-8-sig")) + # JSON comparison distinguishes booleans from numbers and rejects extra fields. + if json.dumps(receipt, sort_keys=True) != json.dumps(expected, sort_keys=True): + raise ValueError("source variant receipt differs") + if sha256(directory / "summary.json") != SUMMARY: + raise ValueError("source build receipt differs") + if sha256(directory / "dc8-native-margin-candidate.patch") != PATCH: + raise ValueError("source patch differs") + for name, record in files.items(): + file = directory / name + if file.stat().st_size != record["bytes"] or sha256(file) != record["sha256"]: + raise ValueError("source pair identity differs") + return directory, expected + + +def expected_baseline_failure(sample): + if sample.get("status") != "completed": + return False + stages = sample["native_contract"]["stages"] + return (all(stages[name]["passed"] for name in ("before", "alt", "restored")) and + stages["final"] == {"text": False, "cell_text": False, "cells_and_attributes": False, + "cursor": True, "passed": False} and + sample["native_lines"]["final"][:5] == + ["TOP_FIXED", "REGION_A", "REGION_B", "REGION_CIXED", "REGION_D"]) + + +def compare(samples): + results = [] + for columns, rows in ((80, 24), (12, 7)): + get = lambda variant, mode: samples[f"{variant}-{columns}x{rows}-{mode}"] + baseline, patched = get("baseline", "vt-on"), get("patched", "vt-on") + off_baseline, off_patched = get("baseline", "vt-off"), get("patched", "vt-off") + native_red = expected_baseline_failure(baseline) + native_green = (patched.get("status") == "completed" and + patched["native_contract"]["all_native_stages_passed"]) + terminal_green = all(s.get("status") == "completed" and + s["terminal_main_margins_restored"] and not s["terminal_alternate"] + for s in (baseline, patched)) + off_equal = (all(s.get("status") == "completed" for s in (off_baseline, off_patched)) and + all(off_baseline[key] == off_patched[key] for key in + ("native_lines", "native_cursor", "native_cell_sha256", "terminal_lines"))) + results.append({"columns": columns, "rows": rows, "baseline_expected_native_failure": native_red, + "patched_all_native_stages_passed": native_green, + "both_terminal_finals_passed": terminal_green, "vt_off_unchanged": off_equal, + "passed": native_red and native_green and terminal_green and off_equal}) + return results + + +def run(baseline, patched, probe, trace, output): + pairs = {name: reviewed_pair(folder, name) for name, folder in + (("baseline", baseline), ("patched", patched))} + probe, trace = Path(probe).resolve(strict=True), Path(trace).resolve(strict=True) + output = Path(output).resolve() + output.mkdir(parents=True, exist_ok=False) + sources = ["CMakeLists.txt", "experiments/conpty/backend.hpp", "experiments/conpty/session.hpp", + "experiments/conpty/margin_probe.cpp", "experiments/conpty/margin_run.py", + "experiments/conpty/margin_contract.py", "experiments/conpty/source_margin_run.py"] + summary = {"schema": "termforge-source-conpty-margin-v1", "generated_utc": datetime.now(timezone.utc).isoformat(), + "upstream_source": UPSTREAM, "build_run": RUN, "source_sha256": SOURCE_SHA256, + "termforge_source": subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip(), + "working_tree_dirty": bool(subprocess.check_output(["git", "status", "--porcelain"], cwd=ROOT)), + "source_files": {name: sha256(ROOT / name) for name in sources}, + "platform": platform.platform(), "probe_sha256": sha256(probe), "trace_sha256": sha256(trace), + "pair_receipts": {name: receipt for name, (_, receipt) in pairs.items()}, + "samples": {}, "comparisons": [], "passed": False, + "full_r2_contract_verified": False, "product_backend_selected": False, + "presented_done": None, + "unmeasured": ["styled_source", "history", "resize_cross_margin", "terminal_stage_cursors", + "full_product_workflows", "performance", "WindTerm_equivalence"]} + try: + # One planned execution per variant/grid/mode. Failures remain distinct; + # there is no retry, altered input, deadline extension or fallback. + for columns, rows in ((80, 24), (12, 7)): + for mode in ("vt-on", "vt-off"): + for variant, (directory, _) in pairs.items(): + name = f"{variant}-{columns}x{rows}-{mode}" + case = output / "_private" / name + try: + sample = execute(probe, str(directory / "conpty.dll"), trace, case, + mode, columns, rows, backend_identity=identity(variant)) + sample["native_cell_sha256"] = {stage: sha256(case / f"{stage}.cells.bin") + for stage in ("before", "alt", "restored", "final")} + if mode == "vt-on": + sample["native_contract"] = inspect_native(case, columns, rows) + except Exception as error: + diagnostic = case / "diagnostic.json" + sample = json.loads(diagnostic.read_text()) if diagnostic.exists() else {} + sample.update(status="failed", error_type=type(error).__name__) + summary["samples"][name] = sample + (output / "summary.json").write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8") + summary["comparisons"] = compare(summary["samples"]) + summary["passed"] = all(row["passed"] for row in summary["comparisons"]) + finally: + (output / "summary.json").write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8") + return summary + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--baseline", type=Path, required=True) + parser.add_argument("--patched", type=Path, required=True) + parser.add_argument("--probe", type=Path, required=True) + parser.add_argument("--candidate-trace", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + result = run(args.baseline, args.patched, args.probe, args.candidate_trace, args.output) + print(json.dumps({"passed": result["passed"], "comparisons": result["comparisons"]})) + return int(not result["passed"]) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/conpty/test_backend.py b/experiments/conpty/test_backend.py index 14dca4b4..47b64cdd 100644 --- a/experiments/conpty/test_backend.py +++ b/experiments/conpty/test_backend.py @@ -21,7 +21,7 @@ def setUp(self): self.out = Path(tempfile.mkdtemp(prefix="run-", dir=base)) self.serial = 0 - def invoke(self, directory, mode="--host"): + def invoke(self, directory, mode="--host", *, executable=None): job = process = None stdout = stderr = b"" result = {"status": "failed", "failure_code": None, "returncode": None, @@ -42,7 +42,7 @@ def cleanup_fault(code): try: job = OwnedJob() stage = "process_start" - process = subprocess.Popen([str(binary("conpty_backend_probe")), mode, str(directory)], + process = subprocess.Popen([str(executable or binary("conpty_backend_probe")), mode, str(directory)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW) stage = "job_assign"