diff --git a/.gitattributes b/.gitattributes index d80940da..adeb3c3b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,6 @@ *.py text eol=lf *.ps1 text eol=crlf experiments/core/fixtures/*.bin binary +# Preserve upstream license bytes and unified-diff context prefixes. +research/msys-runtime-window-prefix/CYGWIN_LICENSE whitespace=-blank-at-eol,-blank-at-eof +research/msys-runtime-window-prefix/window-prefix.patch whitespace=-blank-at-eol,-space-before-tab diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3dd2f137..0f812e0b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,6 +53,9 @@ jobs: - name: Test env: TERMFORGE_REQUIRE_SYSTEM_CLIPBOARD: ${{ runner.os == 'Windows' && '1' || '0' }} + # [DEBUG-r99] opt-in stage logging for issue #99 bounded observation; + # consumed only by the ui-clipboard-system scenario debug logger. + TERMFORGE_DEBUG_R99: '1' run: ctest --test-dir build -C Release --output-on-failure --output-junit test-results.xml - name: Preserve clipboard and window integration evidence if: always() && runner.os == 'Windows' diff --git a/.github/workflows/conpty-upstream.yml b/.github/workflows/conpty-upstream.yml new file mode 100644 index 00000000..2bc467ea --- /dev/null +++ b/.github/workflows/conpty-upstream.yml @@ -0,0 +1,78 @@ +name: Fixed upstream ConPTY build experiment + +on: + pull_request: + paths: + - '.github/workflows/conpty-upstream.yml' + - 'experiments/conpty/upstream/**' + workflow_dispatch: + +permissions: + contents: read + +jobs: + upstream: + runs-on: windows-2025 + timeout-minutes: 45 + env: + PYTHONUTF8: '1' + NUGET_CLI_LANGUAGE: en-us + VCPKG_DISABLE_METRICS: '1' + VCPKG_MAX_CONCURRENCY: '1' + steps: + - name: Preserve canonical source bytes in disposable checkouts + run: | + git config --global core.autocrlf false + git config --global core.eol lf + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: project + persist-credentials: false + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: microsoft/terminal + ref: dc8ae365847d41f62d1ebca93815bb00959bc12f + path: upstream + persist-credentials: false + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: microsoft/vcpkg + ref: 927f62e4b8838bd7e441e9c45103a16ffd75007e + fetch-depth: 0 + path: vcpkg + persist-credentials: false + - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 + with: + python-version: '3.12' + - name: Test result rejection gates + working-directory: project + run: python -m unittest experiments.conpty.upstream.test_taef_result -v + - name: Build and test fixed upstream variants + shell: pwsh + run: >- + ./project/experiments/conpty/upstream/build_hosted.ps1 + -Source ./upstream -Vcpkg ./vcpkg -Out ./upstream-result + - name: Preserve source and test identity + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: conpty-upstream-summary + retention-days: 14 + if-no-files-found: warn + path: upstream-result/public/ + - name: Preserve experimental pair after adapter verification + if: success() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: conpty-upstream-experimental-pair + retention-days: 14 + if-no-files-found: error + path: upstream-result/pair/ + - name: Preserve same-source baseline for native comparison + if: success() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: conpty-upstream-baseline-pair + retention-days: 14 + if-no-files-found: error + path: upstream-result/baseline-pair/ diff --git a/.github/workflows/core-candidate.yml b/.github/workflows/core-candidate.yml index 0b8d962c..96f18d02 100644 --- a/.github/workflows/core-candidate.yml +++ b/.github/workflows/core-candidate.yml @@ -84,7 +84,9 @@ jobs: - name: Build native measurement run: cmake --build build --config Release --target core_measure - name: Build and test candidate C++ adapter - run: cmake --build build --config Release --target candidate_core_test && ctest --test-dir build -C Release -R candidate_core --output-on-failure + run: cmake --build build --config Release --target candidate_core_test workflow_checkpoint_test && ctest --test-dir build -C Release -R "^(candidate_core|workflow_checkpoint)$" --no-tests=error --output-on-failure + - name: Verify resize trace and actual replay input rejection + run: cmake --build build --config Release --target candidate_resize_replay && ctest --test-dir build -C Release -R "^(resize_trace|candidate_resize_replay)$" --no-tests=error --output-on-failure - name: Build and test candidate shell on Windows if: runner.os == 'Windows' run: cmake --build build --config Release --target shell_test_launcher && ctest --test-dir build -C Release -R candidate_shell_tui --output-on-failure diff --git a/CMakeLists.txt b/CMakeLists.txt index e5b3e38a..010bb7db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,18 @@ 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() + +if(WIN32) + include(cmake/ConptyBackend.cmake) +endif() # Single-config generators use bin/, multi-config generators use bin//. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") @@ -69,6 +81,14 @@ if(TERMFORGE_BUILD_CANDIDATE_ADAPTER) else() target_link_libraries(candidate_core_test PRIVATE pthread dl m) endif() + add_executable(candidate_resize_replay experiments/core/candidate_resize_replay.cpp experiments/core/candidate_terminal.cpp) + target_link_libraries(candidate_resize_replay PRIVATE termforge_core termforge_candidate_core) + termforge_warnings(candidate_resize_replay) + if(WIN32) + target_link_libraries(candidate_resize_replay PRIVATE ws2_32 userenv ntdll) + else() + target_link_libraries(candidate_resize_replay PRIVATE pthread dl m) + endif() endif() add_executable(resize_test experiments/core/resize_test.cpp) @@ -89,6 +109,10 @@ termforge_warnings(search_test) add_executable(paste_test experiments/shell/paste_test.cpp) termforge_warnings(paste_test) +add_executable(sender_fence_test experiments/shell/sender_fence_test.cpp) +termforge_warnings(sender_fence_test) +find_package(Threads REQUIRED) +target_link_libraries(sender_fence_test PRIVATE Threads::Threads) add_executable(clipboard_policy_test experiments/shell/clipboard_policy_test.cpp) termforge_warnings(clipboard_policy_test) add_executable(keyboard_test experiments/shell/keyboard_test.cpp) @@ -111,6 +135,23 @@ if(WIN32) termforge_warnings(conpty_margin_probe) target_compile_definitions(conpty_margin_probe PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0A00) target_link_libraries(conpty_margin_probe PRIVATE user32 bcrypt) + 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) + target_link_libraries(conpty_resize_native_probe PRIVATE bcrypt) add_executable(presentation_probe experiments/presentation/presentation.cpp) termforge_warnings(presentation_probe) target_compile_definitions(presentation_probe PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN UNICODE _UNICODE) @@ -140,6 +181,9 @@ if(WIN32) target_compile_definitions(shell_query_client PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN UNICODE _UNICODE) add_dependencies(termforge_shell shell_query_client) add_executable(shell_queue_probe experiments/shell/queue_probe.cpp) + add_executable(sender_fence_pipe_test experiments/shell/sender_fence_pipe_test.cpp) + termforge_warnings(sender_fence_pipe_test) + target_compile_definitions(sender_fence_pipe_test PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN) add_executable(shell_size_client experiments/shell/size_client.cpp) add_executable(shell_input_client experiments/shell/input_client.cpp) add_executable(shell_text_client experiments/shell/text_client.cpp) @@ -221,6 +265,16 @@ if(BUILD_TESTING) add_test(NAME candidate_title COMMAND candidate_title_test) set_tests_properties(candidate_title PROPERTIES TIMEOUT 35 ENVIRONMENT "TERMFORGE_TEST_ARTIFACTS=${PROJECT_BINARY_DIR}/test-artifacts") + if(TERMFORGE_CONPTY_BACKEND_DIR) + add_executable(candidate_reply_close_test experiments/shell/candidate_reply_close_test.cpp experiments/shell/shell_launch.cpp) + termforge_warnings(candidate_reply_close_test) + target_compile_definitions(candidate_reply_close_test PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN UNICODE _UNICODE) + target_link_libraries(candidate_reply_close_test PRIVATE termforge_core d2d1 dwrite gdi32 user32 imm32 ole32 oleaut32 uuid bcrypt) + termforge_candidate_shell(candidate_reply_close_test) + add_dependencies(candidate_reply_close_test termforge_shell_assets) + add_test(NAME candidate_reply_close COMMAND candidate_reply_close_test) + set_tests_properties(candidate_reply_close PROPERTIES TIMEOUT 30) + endif() endif() add_dependencies(runtime_observation_test termforge_shell_assets) add_test(NAME runtime_observation COMMAND runtime_observation_test) @@ -263,6 +317,13 @@ if(BUILD_TESTING) add_executable(transport_completion_test experiments/shell/transport_completion_test.cpp) termforge_warnings(transport_completion_test) add_test(NAME transport_completion COMMAND transport_completion_test) + add_executable(workflow_checkpoint_test experiments/shell/workflow_checkpoint_test.cpp) + target_link_libraries(workflow_checkpoint_test PRIVATE termforge_core) + termforge_warnings(workflow_checkpoint_test) + if(TERMFORGE_BUILD_CANDIDATE_ADAPTER) + termforge_candidate_shell(workflow_checkpoint_test) + endif() + add_test(NAME workflow_checkpoint COMMAND workflow_checkpoint_test) add_executable(scenario_tick_gate_test experiments/shell/scenario_tick_gate_test.cpp) termforge_warnings(scenario_tick_gate_test) add_test(NAME scenario_tick_gate COMMAND scenario_tick_gate_test) @@ -274,6 +335,12 @@ if(BUILD_TESTING) add_test(NAME selection_core COMMAND selection_test) add_test(NAME search_core COMMAND search_test) add_test(NAME paste COMMAND paste_test) + add_test(NAME sender_fence COMMAND sender_fence_test) + set_tests_properties(sender_fence PROPERTIES TIMEOUT 10) + if(WIN32) + add_test(NAME sender_fence_pipe COMMAND sender_fence_pipe_test) + set_tests_properties(sender_fence_pipe PROPERTIES TIMEOUT 10) + endif() add_test(NAME clipboard_policy COMMAND clipboard_policy_test) add_test(NAME keyboard COMMAND keyboard_test) add_test(NAME text_action_policy COMMAND text_action_policy_test) @@ -285,7 +352,15 @@ if(BUILD_TESTING) WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" TIMEOUT 180 ENVIRONMENT "TERMFORGE_BIN_DIR=$;TERMFORGE_TRACE=$;PYTHONUTF8=1") + if(TERMFORGE_CONPTY_BACKEND_DIR) + set_property(TEST ${name} APPEND PROPERTY ENVIRONMENT + "TERMFORGE_EXPECT_CONPTY_BACKEND=reviewed-side-by-side-1.25.260930003") + endif() endfunction() + if(TERMFORGE_BUILD_CANDIDATE_ADAPTER) + termforge_add_python_test(candidate_resize_replay -m unittest experiments.core.test_candidate_resize_replay -v) + set_tests_properties(candidate_resize_replay PROPERTIES TIMEOUT 30) + endif() if(TERMFORGE_BUILD_CANDIDATE_ADAPTER AND WIN32) termforge_add_python_test(candidate_shell_tui -m unittest experiments.shell.test_candidate_tui -v) set_tests_properties(candidate_shell_tui PROPERTIES TIMEOUT 120) @@ -310,12 +385,15 @@ if(BUILD_TESTING) termforge_add_python_test(workflow_system_runner -m unittest research.ime.test_workflow_system_runner -v) set_tests_properties(workflow_system_runner PROPERTIES TIMEOUT 30) termforge_add_python_test(copy_error_frame -m unittest experiments.shell.test_copy_error_frame -v) + termforge_add_python_test(paste_fence_evidence -m unittest experiments.shell.test_paste_checks -v) + termforge_add_python_test(resize_trace -m unittest experiments.shell.test_resize_trace -v) if(WIN32) termforge_add_python_test(research_guards -m unittest discover -s research/tests -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) termforge_add_python_test(conpty_margin -m unittest experiments.conpty.test_margin_run -v) + termforge_add_python_test(conpty_backend -m unittest experiments.conpty.test_backend -v) termforge_add_python_test(presentation -m unittest experiments.presentation.test_presentation -v) termforge_add_python_test(shell -m unittest experiments.shell.test_shell -v) termforge_add_python_test(selection_bytes -m unittest experiments.shell.test_selection_bytes -v) @@ -327,3 +405,13 @@ if(BUILD_TESTING) termforge_add_python_test(research_guards -m unittest research.tests.test_ui_probe research.tests.test_ui_typography research.tests.test_ui_r1 research.tests.test_ime_evidence -v) endif() endif() + +if(WIN32) + foreach(target termforge_conpty_probe conpty_ctrl_c_probe conpty_margin_probe conpty_backend_probe real_app_capture + termforge_shell workflow_fixture_test shell_queue_probe shell_paste_probe + runtime_observation_test candidate_title_test candidate_reply_close_test raw_console_receiver_test) + if(TARGET ${target}) + termforge_use_conpty_backend(${target}) + endif() + endforeach() +endif() diff --git a/cmake/ConptyBackend.cmake b/cmake/ConptyBackend.cmake new file mode 100644 index 00000000..6b0b9910 --- /dev/null +++ b/cmake/ConptyBackend.cmake @@ -0,0 +1,46 @@ +# Experimental, explicit side-by-side ConPTY selection. No downloads/fallback. +add_library(termforge_conpty_backend INTERFACE) +target_link_libraries(termforge_conpty_backend INTERFACE bcrypt) + +if(TERMFORGE_CONPTY_BACKEND_DIR) + if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8) + message(FATAL_ERROR "The reviewed ConPTY package is Windows x64 only") + endif() + if(NOT TERMFORGE_BUILD_CANDIDATE_ADAPTER) + message(FATAL_ERROR "Experimental side-by-side ConPTY requires TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON") + endif() + if(NOT IS_ABSOLUTE "${TERMFORGE_CONPTY_BACKEND_DIR}") + message(FATAL_ERROR "TERMFORGE_CONPTY_BACKEND_DIR must be an absolute directory") + endif() + foreach(name conpty.dll OpenConsole.exe) + if(NOT EXISTS "${TERMFORGE_CONPTY_BACKEND_DIR}/${name}") + message(FATAL_ERROR "Reviewed ConPTY package is incomplete: ${name}") + endif() + file(SHA256 "${TERMFORGE_CONPTY_BACKEND_DIR}/${name}" actual) + if(name STREQUAL "conpty.dll") + set(expected "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a") + else() + set(expected "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586") + endif() + if(NOT actual STREQUAL expected) + message(FATAL_ERROR "Reviewed ConPTY identity mismatch: ${name}") + endif() + endforeach() + target_compile_definitions(termforge_conpty_backend INTERFACE TERMFORGE_CONPTY_SIDEBYSIDE=1) + add_custom_target(termforge_conpty_bundle + COMMAND "${CMAKE_COMMAND}" -E make_directory "$/conpty-backend" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${TERMFORGE_CONPTY_BACKEND_DIR}/conpty.dll" + "${TERMFORGE_CONPTY_BACKEND_DIR}/OpenConsole.exe" + "${PROJECT_SOURCE_DIR}/third_party/conpty/manifest.json" + "${PROJECT_SOURCE_DIR}/third_party/conpty/LICENSE" + "$/conpty-backend" + VERBATIM) +endif() + +function(termforge_use_conpty_backend target) + target_link_libraries(${target} PRIVATE termforge_conpty_backend) + if(TERMFORGE_CONPTY_BACKEND_DIR) + add_dependencies(${target} termforge_conpty_bundle) + endif() +endfunction() diff --git a/docs/EVIDENCE.md b/docs/EVIDENCE.md index ed6644c6..11c12d3f 100644 --- a/docs/EVIDENCE.md +++ b/docs/EVIDENCE.md @@ -10,7 +10,7 @@ |---|---| | main 默认:自研核心 + 系统 ConPTY | R1 限定总验收和有限 VT/Unicode 子集;默认构建不需要 Rust | | main 显式候选:`termforge.4` + 系统 ConPTY | [固定来源/补丁台账](../experiments/candidates/alacritty/PROVENANCE.md)、[适配边界](../experiments/core/CANDIDATE.md)、[#39 字素合同](research/streaming-graphemes.md);仅按已有合同验收 | -| PR #50 实验:`termforge.4` + 固定官方 ConPTY | [Draft PR](https://github.com/JavaZeroo/termforge/pull/50) / [#47](https://github.com/JavaZeroo/termforge/issues/47);[联合基线](research/joint-conpty-baseline.md)已固定,产品回归未全绿;[resize #51](https://github.com/JavaZeroo/termforge/issues/51)、[Console-width Vim #52](https://github.com/JavaZeroo/termforge/issues/52)、[确定性 sender fence #53](https://github.com/JavaZeroo/termforge/issues/53)仍开放,已有 completed 观察不构成部分发送取消验收 | +| PR #50 实验:`termforge.5` + 固定官方 ConPTY | [Draft PR](https://github.com/JavaZeroo/termforge/pull/50) / [#47](https://github.com/JavaZeroo/termforge/issues/47);旧`.4`的[联合基线](research/joint-conpty-baseline.md)保留。#51/#53/#87/#88各自限定交付见下表;最新完整官方56/57,#52原固定运行库仍失败,MSYS输入边界与隔离运行库修补结果分列;#63已复现工作区路径的因果核验及完整shell通过见下表;更早缺运输样本保留未知。当前能力以[FEATURES](FEATURES.md)为准 | R1 固定总出口的源码为 `ce9b24d`,早期 `2dc8d6d` / `9b16acc` 仍分别只代表原记录。源 VT、原生控制台 CHAR_INFO、transport、末端状态、自有 DIB、绘制提交和真实呈现是不同检查点;未知端点保持未知。 @@ -50,14 +50,23 @@ 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、完整后端或性能通过。 | | Native Vim 捕获目录隔离 | [原因与复验](research/vim-fixture-isolation.md)、[固定原件审计](../experiments/conpty/results/2026-10-26-vim-fixture-isolation/validation.json) | 实验分支同旧 EXE 的兄弟运行一绿一红;修正新目录隔离后完整模块9/9。仅闭合 #52 native 夹具原因;GUI stage59 仍开放,未知与受扰动探针分列。 | +| 工作区尺寸标记残尾(#63) | [原因与复验](research/workspace-native-viewport.md)、[单变量/原生/回放及无插桩验收](../experiments/shell/results/2026-10-27-workspace-native-viewport/validation.json) | 旧原三轮UI/main第三轮各红;同EXE仅切策略分别原红/绿,原生尺寸和标记正确、底部核心残尾。现有#51修复覆盖;无插桩原三轮各重复两批、完整shell37通过/3原skip、93.62秒,#52和完整R2仍未验收。 | +| GUI Vim/MSYS 输入边界(#52) | [运行时边界与限制](research/vim-msys-input-boundary.md)、[白名单事件与身份](../experiments/shell/results/2026-10-27-vim-msys-input/validation.json) | 两轮API级原失败、一轮诊断绿及一轮函数边界原失败:完整93 B到达原生输入,红例MSYS仅交付92 B;resize旧快照清理取走Ctrl-O的调用顺序已捕获。无截断、Job0/desktopgone;未修产品、未替换固定应用,#52继续开放。 | +| GUI Vim/MSYS 隔离运行库补丁(#52) | [实验与限制](research/vim-msys-runtime-prototype.md)、[许可与补丁](../research/msys-runtime-window-prefix/README.md)、[七轮白名单记录](../experiments/shell/results/2026-10-27-vim-msys-runtime-prototype/validation.json) | 原 DLL 两轮一绿一 stage59,未修重建 DLL 首轮 stage59;只调整 resize 前缀消费的重建 DLL 两批共四轮原方法通过,七轮均完整回收。无新 API/加载模块轨迹,未验证鼠标/实际 longjmp 或完整 CTest;正式固定运行库验收、兼容策略及 #52 保持开放。 | +| 原生 viewport resize 与默认策略隔离(#51) | [原因与复现](research/native-viewport-resize.md)、[固定验证及六组合成原件](../experiments/conpty/results/2026-10-27-native-viewport-resize/validation.json) | 原 `.4` GUI stage43 与独立 native/replay 红;`.5` 原 resize/Unicode、历史/空格复制、六组 native 对照通过。默认策略填充误判经实际红绿修复;最终完整回归 54/56、12 项内部 skip,#52/#88 及此前 #87 的失败独立保留,不计完整后端或 WindTerm 等价。 | +| 窄列命令回显与恢复完成(#88) | [独立判据合同](research/workspace-restore-transport-oracle.md)、[原件哈希/红绿/正式两轮验证](../experiments/shell/results/2026-10-27-workspace-echo-cr/validation.json) | 原 262 B cmd 运输中的回显裸 CR 使完成判据提前拒绝;修正仅使该记录保持不可信至真实 LF。原两轮保存/普通恢复/钳制及完整 shell 通过;官方完整回归 55/56、8 项内部 skip,#52 当时仍红,#63/#87 旧因未闭合(#87 后续见下一行)。构建重叠结果明确排除。 | +| 替换会话完整记录检查点(#87) | [机制、因果与边界](research/workflow-record-checkpoint.md)、[固定源码及原两轮验证](../experiments/shell/results/2026-10-27-workflow-record-checkpoint/validation.json) | 旧判据在真实消费半行时采样而红,同 session 后续字节补齐;被动等待方案因输出结束而红。默认关闭的有界 LF 检查点受控通过,最终 c662011 两轮原八会话及关闭 guard 通过;完整官方 56/57、8 项内部 skip,Vim59 仍红。历史两份 native 后续缺口、诊断 trace 截断及未复现分别保留。 | | 工作区几何与交互的固定记录 | [#38](https://github.com/JavaZeroo/termforge/issues/38)、[工作区几何与输出验证](../experiments/shell/results/2026-10-26-workspace-geometry/validation.json)、[resize 限定证据](../experiments/core/results/2026-10-26-resize-replay/validation.json) | 固定 27f376d 的客户区/布局/输出、默认/候选恢复及窄屏记录;其完整回归仍有 #36/#38 红项,不代表当前所有配置或 R2 通过。 | | 短窗口选区夹具与 hosted 四 shell 前置(#38) | [固定锚点回归](../experiments/shell/test_shell.py)、[准备入口](../tools/prepare_hosted_shells.py)、[夹具收尾合同](research/hosted-wsl-fixture-cleanup.md)、[云端结果与剩余阻塞](https://github.com/JavaZeroo/termforge/issues/38) | 短窗口保留相同选择断言;仅指定一次性 hosted VM 准备固定 Ubuntu。夹具强制收尾与产品自然退出分列,未知归属/回收仍失败。 | | 工作区恢复的运输完成端点(#38 夹具修正) | [限定合同](research/workspace-restore-transport-oracle.md)、[原失败及默认/候选验证](../experiments/shell/results/2026-10-26-workspace-transport-oracle/validation.json) | OSC 标题中的重复标记与 column-one CUP 误导旧端点;新判定保留完整 UTF-8、期限、负载及恢复断言。仅修夹具,不宣称修复产品恢复。 | | 工作区恢复的命令编辑前缀(#61) | [BS 因果反例及复验](../experiments/shell/results/2026-10-26-workspace-restore-bs/validation.json)、[运输完成合同](research/workspace-restore-transport-oracle.md) | 原失败中的 PowerShell 命令重绘 BS 污染当前记录;真实 LF 后仍须完整输出与新提示符,回显/拼接/非法封装拒绝保留。仅修完成 oracle,原 30/40 秒与产品断言不变。 | +| 工作区保存的空记录重绘(#72) | [原 122 B 因果回归与三配置结果](../experiments/shell/results/2026-10-27-workspace-empty-cr/validation.json)、[运输完成合同](research/workspace-restore-transport-oracle.md) | 两次 CI 失败记录逐字节相同;空且未编辑的记录在输出前允许裸 CR,仍须完整独立标记与新提示符。默认/system、候选/system、候选/官方各两轮保存/恢复/钳制共 18 次通过;最终完整回归、跳过和未闭合项独立记录。 | | 候选文字观测与回显端点(#38 夹具修正) | [同源码前后对照及反事实](../experiments/shell/results/2026-10-26-native-observation/validation.json)、[标准文本投影](../experiments/core/text_projection.hpp) | 观测改用标准文本投影,回显完成后才全选;原断言和负例保留。修正夹具时序/投影,不宣称产品选择策略已变或 R2 全绿。 | | 拒绝负例的读取前停止(#46 夹具修正) | [原记录、反事实及 fresh 验证](../experiments/shell/results/2026-10-26-receiver-stop/validation.json)、[公开空数据夹具](../experiments/shell/fixtures/rejected-order-before-read.json) | 旧 oracle 错误拒绝“首读前已停止”;同一空数据/零读原件红→绿,正常读取约束保留。限定夹具修正,不解释 #44 的退出。 | | 候选 GUI 合法空标题(#45) | [真实 GUI 前后对照](../experiments/shell/results/2026-10-26-empty-title/validation.json)、[转换入口](../experiments/presentation/render.hpp)、[持续回归](../experiments/shell/candidate_title_test.cpp) | 真实空 OSC 标题使旧 UTF 转换抛异常;修后两 fresh GUI 正常,非法非空反例保留。不回填旧 normal-1 未知异常的根因。 | @@ -136,6 +145,7 @@ python -m unittest experiments.shell.test_workflow_sessions -v | 工作区结构/几何 | [首轮 UI 报告](research/windterm-ui-2026-10-23.md)、[主标签/字体报告](research/windterm-ui-typography-2026-10-23.md)、[探针与准备守卫](../research/ui/README.md) | 发行资源、定向二进制和隔离运行时交叉核对;默认缩放两次及 Qt 1.5 倍一次。旧首启自动导入和错误状态样本已明确排除,不能混作最终纯合成证据 | | 可公开的 UI 精简结果 | [默认样本 1](research/ui-typography/normal-01.json)、[默认样本 2](research/ui-typography/normal-02.json)、[Qt 1.5 倍样本](research/ui-typography/scale-150.json) | 白名单矩形、字体、DPI 和身份;不是完整像素、逐字形 fallback、真实跨屏或性能证明 | | 基础文字交互参考合同 | [行为合同](research/windterm-r1-text-contract.md)、[hosted 事实摘要](research/r1-text/hosted-39960732305.json)、[Actions 39960732305](https://github.com/JavaZeroo/termforge/actions/runs/39960732305) | `c386494` 的 text/menu-wrap 各两轮及 repeat 门槛通过:历史/选择/查找、键盘及实际 Select All/Find/Copy/Paste 菜单、精确 CF 软折行/CRLF/尾空白与严格粘贴基线均有证据;四轮 Job 清空。满足 #11 限定参考合同,不代表产品功能等价或性能验收 | +| 固定 Vim 与原生尺寸/CPR 参考 | [四个有界样本及限制](research/vim-windterm-reference.md)、[白名单记录](../research/ui/results/2026-10-27-vim-native-reference/validation.json) | 原版一次原 Vim 插入、60×12、精确115 B输入、保存/主提示符完成;随后原尺寸门槛未过。独立样本观察 buffer60×3000/viewport60×12,CPR原入口未完成;显式VT输出变体获回复及shell自然退出37。四份分别记录,不能合成完整TUI、兼容修复或产品后端决定 | 主对照为官方 Windows x64 2.7.0:ZIP SHA-256 `b05c3c7371340ff1302967247ffcd3a3ba27b237f72010e9157086143c453e50`,EXE SHA-256 `7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`。最初本机安装的 EXE 身份不同;其历史静态报告不得冒充官方包结果。其他目标平台仍需各自固定包身份。 diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 3a4dbcfc..b6bc7f8a 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -14,7 +14,9 @@ |---|---|---| | 默认 main:自研核心 + 系统 ConPTY | 默认构建与已发布 Alpha 的基础路线,不需要 Rust | 已有 R1 限定验收;VT/Unicode 能力仍为有限子集,不能引用候选核心的字素或 TUI 结果 | | 显式候选 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.4` + 固定官方 ConPTY | [PR #50(Draft)](https://github.com/JavaZeroo/termforge/pull/50),联合源码 `3e5b96d` 已重建并[冻结真实基线](research/joint-conpty-baseline.md),尚未合并或通过产品整体验收 | [#47](https://github.com/JavaZeroo/termforge/issues/47) 跟踪后端整合;终端 margin 与原生状态分列。[resize 残尾 #51](https://github.com/JavaZeroo/termforge/issues/51)、[Console-width Vim #52](https://github.com/JavaZeroo/termforge/issues/52)、[未完成粘贴的确定性取消夹具 #53](https://github.com/JavaZeroo/termforge/issues/53)仍开放,现有 completed 观察不能证明部分发送取消通过或产品 Cancel 故障;worker gate [#62](https://github.com/JavaZeroo/termforge/issues/62) 已有[独立夹具修正与双后端实际 worker 验证](research/sender-fence.md),默认产品路径不启用 fence,也不替代 #53 的 UI 取消/关闭;工作区 [#63](https://github.com/JavaZeroo/termforge/issues/63) 回归仍开放 | +| 实验分支:`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` 或实验后端联合配置。 @@ -35,7 +37,7 @@ | Unicode 与文字显示 | **实现中,未验收**:默认自研保留 Unicode 15。显式候选 `.4` 的 [#39 Unicode 17 流式字素合同](research/streaming-graphemes.md)已限定验收:固定 ZWJ/keycap 的单 lead/两列、光标、SGR/查询、原子选择复制、单列 resize 与有界无损分片;实际 ConPTY/DWrite 核对固定样本的单 glyph、字体和网格 | [#30](https://github.com/JavaZeroo/termforge/issues/30) 仍需完整输入/IME 交叉;所有脚本/整形、通用 fallback、所有原生重放、真彩色/更多属性、Powerline 和真实跨屏未验收。分段 helper 的规范数据通过不代表完整终端 Unicode 正确性;[#54](https://github.com/JavaZeroo/termforge/issues/54) 保留已观察的性能成本 | R2 / R4 | | resize 与 reflow | **限定验收**:默认按 ADR-0005 物理行裁剪;候选在[固定 ConPTY 重放](../experiments/core/results/2026-10-26-resize-replay/validation.json)与连续同步中保持行身份、Find/选区和有界历史,淘汰/清空不复活;候选 `.4` 另覆盖主/备用屏缩到单列 | [#29](https://github.com/JavaZeroo/termforge/issues/29) 的完整 resize 交叉未完成;固定事务不等于完整 reflow 或 WindTerm 等价。系统/实验 ConPTY、备用屏/滚动区、真实 DPI、呈现、性能及 #36/#38 分别验收;默认缩窄不可恢复 | R2 | | 本地 shell 选择 | **实现中,未验收**:`ShellLaunch` 分离可执行文件、参数、工作目录及 shell 命令差异;cmd、Windows PowerShell 5.1、PowerShell 7、显式 Ubuntu WSL 各有[两轮真实启动/目录/尺寸/正常退出证据](../experiments/shell/results/2026-10-24-r2-shell-lifecycle/validation.json),无效程序启动错误可见且可关闭后恢复 cmd | 仅声明上述固定入口和隔离目录;任意 profile/参数、通用分屏/排序、全部运行时错误提示及管理员 shell 未完成 | R2;权限场景 R4 | -| 常用 TUI | **实现中,未验收**:显式候选 + 系统 ConPTY 有固定 Vim 进入、编辑、60×12 resize、退出写盘、主屏恢复及光标回复的[产品证据](../experiments/core/CANDIDATE.md);More 尾部 ECH 与捕获端点有[限定修复](research/more-capture-ech.md) | [#28](https://github.com/JavaZeroo/termforge/issues/28) 的更多应用及完整输入/输出模式未完成;PR #50 实验后端的 GUI Vim [#52](https://github.com/JavaZeroo/termforge/issues/52) 仍有失败;[native 捕获目录复用](research/vim-fixture-isolation.md)已独立定位并修正,不能代替 GUI 或完整 TUI 验收。headless 比较与自有 DIB 不等于真实呈现,`presented_done=null` | R2 | +| 常用 TUI | **实现中,未验收**:显式候选 + 系统 ConPTY 有固定 Vim 进入、编辑、60×12 resize、退出写盘、主屏恢复及光标回复的[产品证据](../experiments/core/CANDIDATE.md);More 尾部 ECH 与捕获端点有[限定修复](research/more-capture-ech.md) | [#28](https://github.com/JavaZeroo/termforge/issues/28) 的更多应用及完整输入/输出模式未完成;PR #50 实验后端的 GUI Vim [#52](https://github.com/JavaZeroo/termforge/issues/52) 仍有失败,[MSYS输入观测](research/vim-msys-input-boundary.md)及[隔离运行库修补实验](research/vim-msys-runtime-prototype.md)不替代原固定运行库验收;[native 捕获目录复用](research/vim-fixture-isolation.md)已独立定位并修正,不能代替 GUI 或完整 TUI 验收。headless 比较与自有 DIB 不等于真实呈现,`presented_done=null` | R2 | ## 工作区与会话 @@ -45,7 +47,7 @@ | 基础窗口操作 | **限定验收**:新建/切换/关闭/重命名、五条分隔线、面板显隐、底部焦点模式 | 完整会话动作/快捷键、命令面板、目标 Focus/SyncInput 行为 | R1 / R4 | | 工作区外观 | **实现中,未验收**:两种逻辑尺寸下部分外框/层级依据直接取证;UI 字体来自独立 OFL 来源,私有加载、实际 GDI face/em/行高与缺失资产失败有自动检查 | 不是像素等价;图标、主题/透明度、多语言及所有状态仍缺。Explorer/Filer/Outline 当前为空区域 | R2 / R3 / R4 | | DPI 与文字缩放 | **限定验收**:每会话 50%–200%,受控 96→144→192→96;[字体/显示合同](milestones/R2-font-display.md)明确 12×26 基准网格。两台真实 96-DPI 显示器间往返两次保持窗口、rename 焦点、隐藏标签和网格,[证据](../experiments/shell/results/2026-10-24-r2-monitor-font/validation.json)明确 `distinct_dpi=false`;v2 会话恢复另覆盖 100%/150%/75% zoom | 不同 DPI 的真实跨屏、实际呈现和真实 IME 跨屏仍未验收;恢复证据为同一 96-DPI 私有 desktop,目标字形/逐字形 fallback 未等价 | R2 | -| 配置与会话持久化 | **限定验收**:有界 v2、原子替换、v1 迁移、空会话与损坏/未知版本保护;[多会话恢复](../experiments/shell/results/2026-10-25-workspace-restore-multi/validation.json)覆盖 shell 类型/目录/标题/zoom/split/窗口,[迁移回退](../experiments/shell/results/2026-10-25-workspace-recovery-guards/validation.json)及[写失败与关闭策略](../experiments/shell/results/2026-10-25-workspace-write-failure/validation.json)均有真实验证。[#61 的合法编辑 BS 前缀](../experiments/shell/results/2026-10-26-workspace-restore-bs/validation.json)已在默认/候选 system 限定修正;目录/版本完成按[独立输出与新提示符合同](research/workspace-restore-transport-oracle.md)核对;只重建白名单 shell,不恢复原进程/历史;保存失败保留窗口,显式放弃保存才跳过 | 仅当前单工作区、正常窗口和白名单本地会话;偏好、分组/搜索、最大化/多窗口与文字标记恢复归后续。真实 DPI、呈现和 #38 等独立回归不因此视为通过,R2 出口未宣告完成 | R2 / R3 / R4 | +| 配置与会话持久化 | **限定验收**:有界 v2、原子替换、v1 迁移、空会话与损坏/未知版本保护;[多会话恢复](../experiments/shell/results/2026-10-25-workspace-restore-multi/validation.json)覆盖 shell 类型/目录/标题/zoom/split/窗口,[迁移回退](../experiments/shell/results/2026-10-25-workspace-recovery-guards/validation.json)及[写失败与关闭策略](../experiments/shell/results/2026-10-25-workspace-write-failure/validation.json)均有真实验证。[#61 的合法编辑 BS 前缀](../experiments/shell/results/2026-10-26-workspace-restore-bs/validation.json)与 [#72 的空记录 CR 重绘](../experiments/shell/results/2026-10-27-workspace-empty-cr/validation.json)已限定修正;后者同源码三配置共 18 次保存/恢复/钳制通过;目录/版本完成按[独立输出与新提示符合同](research/workspace-restore-transport-oracle.md)核对;只重建白名单 shell,不恢复原进程/历史;保存失败保留窗口,显式放弃保存才跳过 | 仅当前单工作区、正常窗口和白名单本地会话;偏好、分组/搜索、最大化/多窗口与文字标记恢复归后续。真实 DPI、呈现和 #38 等独立回归不因此视为通过,R2 出口未宣告完成 | R2 / R3 / R4 | | 文本增强与辅助面板 | **未实现** | 高亮/时间戳/折叠/outline、补全与 Fish 增强、命令历史、Sender/QuickBar、Free Type Mode(输入模式,行为待取证)、vim 本地/远程键位、锁屏、Sysmon | R4 | ## 远程、文件与协议 @@ -68,7 +70,7 @@ | 组合验收基础 | **限定验收**:[固定源码审计](../experiments/shell/results/2026-10-24-r1-workflow-foundation/validation.json)覆盖基础数据与快照;[受控夹具](../experiments/shell/WORKFLOW.md)分离编号日志、ConPTY传输、原始接收与阶段日志;固定输入前缀保持4096 UTF-16容量,旧IME模式仍要求空前缀 | 该基础证据不单独验收八会话或真实Copy→Paste→IME;组合出口见 #18 独立验收 | R1 | | 八会话文字工作流 | **限定验收**:[R1 固定源码整体验收](../experiments/shell/results/2026-10-24-r1-system-workflow/validation.json)覆盖同次 8 会话/拒绝第 9 个、后台有限洪流、历史/Find/实际选择与 CF 复制/确认粘贴/真实拼音、切换缩放、淘汰、取消关闭和重复回收;实际字节、目标隔离、动作与 Job/desktop 回收有独立审计 | 限定 Windows 固定拼音、本地 cmd 与有界负载;这是固定 R1 配置的组合验收,不能替代 R2 候选/新后端组合。实际呈现、常用 TUI、长期稳定及 UI/性能等价仍未验收 | R1 | | 活体资源与滚动端点 | **限定测量**:普通启动默认关闭;[R1 组合审计](../experiments/shell/results/2026-10-24-r1-system-workflow/validation.json)绑定实际接收、state 与同 revision 的 WM_PAINT/BitBlt/GdiFlush 提交,保留跨时钟校准与阶段资源,可离线重算 | launcher-tree 不等于 UI 归因;观察进程成本不等于增量采样成本,短命进程及退出 CPU 可能漏采。短驻留不证明长期无泄漏,`presented_done=null`,不构成性能通过 | R1;扩展 R4 / R5 | -| 自动正确性与构建 | **限定验收**:Windows shell/ConPTY/呈现及 Windows/Linux 核心 CI、差分/恢复/队列/生命周期场景 | 产品打包、干净机器使用、依赖升级、长期回归和更广平台矩阵;#40 的 CI 纯夹具超时,以及后续保存 stage2 #72 / 窄窗口选择 #73 的独立失败仍未闭合 | R2 / R4 | +| 自动正确性与构建 | **限定验收**:Windows shell/ConPTY/呈现及 Windows/Linux 核心 CI、差分/恢复/队列/生命周期场景 | 产品打包、干净机器使用、依赖升级、长期回归和更广平台矩阵;#40 的 CI 纯夹具超时,以及窄窗口选择 #73 等独立失败仍未闭合;#72 保存完成判据的限定修正不代替完整回归 | R2 / R4 | | Windows 产品交付 | **实现中,未验收**:首个 [v0.1.0-alpha.1 预发布](releases/v0.1.0-alpha.1.md)提供 Windows x64、静态 MSVC 运行库、附资源许可和构建身份的免安装 ZIP;未签名,已验环境限 Windows 11 本机与 Server 2025 CI,布局仍写入 LOCALAPPDATA | 这是 R1 体验包,尚非 R2 日常开发版;干净机器与更广平台矩阵、签名/安装、配置升级及持续运行仍待完成。Windows 10 未验收,Server 2022 已知资源门槛未过 | R2 / R3 | | Linux/macOS 与其他架构 | **未实现**客户端;Linux 核心 CI 已有。[官方平台范围台账](../research/windterm-2.7.0-platform-scope.md)已固定 2.7.0 的 Windows x86/x64、macOS x64、Linux x64 资产及声明下限;这不是 TermForge 支持或实测结论 | 最终需真实 Linux/macOS 客户端;平台调查可提前进行,整合及完整验收排在 Windows 11 x64 完整版本之后。其他系统版本、最低版本和架构由用户后续明确;旧系统及 ARM 等保持调查记录,不自动承诺或排除,也不能由核心 CI 推断产品支持 | 持续调查;R5 全平台出口 | | 吞吐、响应、内存与稳定性 | **实现中,未验收**:已有机制实验、连续 CPU 区间校准与合成回归;候选字素改动的受限诊断中 project/interactive 吞吐中位数约下降 16%/9%,由 [#54](https://github.com/JavaZeroo/termforge/issues/54) 跟踪 | 共享宿主、allocator 包装与缺少真实呈现/输入时延端点使该诊断不构成正式预算。仍需同负载/同正确性、无插桩对照、观测开销和长期/多会话公平性;可靠原版基线后提出逐项预算及允许波动范围,**由用户明确确认**。与 WindTerm 的实际差距**待取证** | 当前优先;贯穿 R1–R5 | diff --git a/docs/PROVENANCE.md b/docs/PROVENANCE.md index c99fff4f..ff4846d4 100644 --- a/docs/PROVENANCE.md +++ b/docs/PROVENANCE.md @@ -18,13 +18,15 @@ ## 已使用的第三方数据 +- `research/msys-runtime-window-prefix/`:外部运行库的隔离研究补丁,来源为 `git-for-windows/msys2-runtime` 固定提交 `2e2ef940151485490aff0c7b00c00965ef7a71a4` 的 `winsup/cygwin/fhandler/console.cc`;修改 resize 输入前缀的消费顺序,适用上游 LGPLv3-or-later 及 `CYGWIN_LICENSE` 中的 Linking Exception。完整上游许可、补丁与来源见[目录说明](../research/msys-runtime-window-prefix/README.md)。这不是 WindTerm 源码,不是产品依赖,也不进入默认构建或发布包;不由项目 MIT 覆盖。编译/隔离 Vim 结果及未验边界见[实验记录](research/vim-msys-runtime-prototype.md)。 + - `assets/fonts/RobotoMono-Regular.ttf`:从 Google Fonts 的 RobotoMono 官方仓库固定提交独立取得,未修改;适用 SIL Open Font License 1.1,完整版权与许可证随字体分发。字体源码、固定版本、原始链接、字节数和 SHA-256 见[字体来源](../assets/fonts/README.md)。只在 TermForge 进程内作为工作区 UI 字体加载;不是从 WindTerm 提取的资源,不受项目 MIT 许可证覆盖。 - 差分测试依赖 `pyte 0.8.2`(LGPLv3)和 `wcwidth 0.2.13`(MIT),固定 wheel 哈希并保留原包许可;没有复制其源码或将其作为产品组件。版本、官方来源与适配边界见[差分说明](../experiments/differential/README.md)。原创项目许可证不替代这些依赖的许可证。 - `experiments/core/unicode_width.hpp`:通过原创生成器从 Python 3.12.10 `unicodedata` 所带 Unicode 15.0.0 属性数据生成,含项目自己的宽度/拒绝策略。未复制 CPython 实现源码。Unicode 数据保留[官方许可](../experiments/core/UNICODE-LICENSE.txt)与归属,来源为 [Unicode](https://www.unicode.org/);正常构建使用固定生成文件。此数据不归项目 MIT 许可覆盖。 -- 外部核心候选当前使用 `alacritty_terminal 0.26.1-dev+termforge.4`(Apache-2.0),固定上游 commit `542147067dd016fc5f49120ef0ba605a98a4e3a7` 的 manifest/changelog/许可证与 `src/`,并保留 ED、稳定行身份/淘汰、安全 swap、每缓冲区滚动区域和流式字素补丁;精确传递依赖及校验值由 `Cargo.lock` 固定。未复制上游 46 MiB 行为录制,候选不进入当前产品包;默认 CMake 不依赖 Rust,显式适配器构建使用 staticlib。原始 0.26.0 身份、当前来源、许可证和补丁清单见[候选来源台账](../experiments/candidates/alacritty/PROVENANCE.md)。 +- 外部核心候选当前使用 `alacritty_terminal 0.26.1-dev+termforge.5`(Apache-2.0),固定上游 commit `542147067dd016fc5f49120ef0ba605a98a4e3a7` 的 manifest/changelog/许可证与 `src/`,并保留 ED、稳定行身份/淘汰、安全 swap、每缓冲区滚动区域和流式字素补丁;精确传递依赖及校验值由 `Cargo.lock` 固定。未复制上游 46 MiB 行为录制,候选不进入当前产品包;默认 CMake 不依赖 Rust,显式适配器构建使用 staticlib。原始 0.26.0 身份、当前来源、许可证和补丁清单见[候选来源台账](../experiments/candidates/alacritty/PROVENANCE.md)。 - 候选新增精确锁定 `unicode-segmentation 1.13.3`(Unicode 17,MIT OR Apache-2.0),官方来源为 [unicode-rs](https://github.com/unicode-rs/unicode-segmentation) 与 [crates.io 固定版本](https://crates.io/crates/unicode-segmentation/1.13.3)。保留原依赖许可证;与既有 `unicode-width 0.2.2` 同使用 Unicode 17,默认自研核心仍使用 Unicode 15。版本、包哈希、MSRV、依赖成本和修改边界见[候选来源台账](../experiments/candidates/alacritty/PROVENANCE.md)。 diff --git a/docs/milestones/R2-local-terminal.md b/docs/milestones/R2-local-terminal.md index 4e9dc8bc..07606ed3 100644 --- a/docs/milestones/R2-local-terminal.md +++ b/docs/milestones/R2-local-terminal.md @@ -44,7 +44,7 @@ flowchart LR ## 下一批的任务边界 -1. **在已冻结基线上逐项闭合后端回归。** #55 已交付含红的真实基线;接续 #51–#53 与 #63 的独立失败。#62 的[真实 writer 持块、取消与停止夹具](../research/sender-fence.md)不降低原 4MiB/16KiB/期限,下一步把默认关闭的 fence 接到 #53 原 UI/3MB/八会话入口;这不代表这些 UI 路径已通过。#51–#53 可以用原冻结输入独立定位,最终修复验收须在联合配置重建;一次未复现不自动构成因果修复。 +1. **在已冻结基线上逐项闭合后端回归。** #55 已交付含红的真实基线;接续 #52 的 GUI Vim 失败;[隔离运行库修补实验](../research/vim-msys-runtime-prototype.md)已提供基点red与补丁green,原固定运行库兼容方案及正式验收仍需闭合。#62 的[真实 writer 持块、取消与停止夹具](../research/sender-fence.md)不降低原 4MiB/16KiB/期限,#53 已接入默认关闭的 fence,[单窗口 UI/3MB/768B 与控制故障切片](../research/ui-paste-fence.md)及[同源码两轮原八会话data-only](../research/resize-replay-cost.md)已通过;[#51 原生 viewport 修复](../research/native-viewport-resize.md)的原 resize 与 Unicode、[#88 回显完成判据](../research/workspace-restore-transport-oracle.md)的原保存/恢复/钳制限定回归通过;[#87 完整记录检查点](../research/workflow-record-checkpoint.md)已具有受控因果红绿及同源码两轮原八会话 data-only。[#63 工作区尺寸标记残尾](../research/workspace-native-viewport.md)已核验现有viewport修复覆盖,并通过原三轮重复与180秒完整shell合同。完整官方回归中的 Vim、更早缺运输的工作区失败及历史聚合期限证据分别保留,#87 的历史运输缺口不追溯补造,真实 CF/IME 组合独立验收。各回归可用原冻结输入独立定位,最终修复验收须在联合配置重建;一次未复现不自动构成因果修复。 2. **并行处理原版对照、平台语义与性能。** [原生语义处理路径 #56](https://github.com/JavaZeroo/termforge/issues/56)先明确 #41 原合同需要什么、哪些路径可支持;保留必要的上游或独立实现工作,不用“平台不同”豁免缺口。#3/#4 优先建立原版 Windows 本地终端基线;#54 现在冻结重复方法与完成端点并定位吞吐成本,数值预算作为提案交用户确认,不等到 #34 才补性能。 3. **在主线推进独立行为合同。** #29 核对极窄/极短、软硬行、历史/淘汰、选择/Find、连续 resize 和 child ack;#30 核对 IME 组合/提交/取消与输出/resize/切换、应用键盘/粘贴接收、实际复制及同次八会话。已验收的 #39 只提供首批字素基础,最终配置的交叉验收仍需执行。 4. **补终端模式和应用矩阵。** #27 明确 DECOM/DECAWM、pending-wrap、保存恢复、异常序列与 margin×resize;#28 先固定应用版本/入口/动作及协议字节预期,再逐应用完成进入、操作、resize、退出、主屏恢复。Vim 的一个修复不替代完整 TUI 验收。 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/docs/research/experimental-conpty-backend.md b/docs/research/experimental-conpty-backend.md new file mode 100644 index 00000000..dabdf584 --- /dev/null +++ b/docs/research/experimental-conpty-backend.md @@ -0,0 +1,43 @@ +# 显式 side-by-side ConPTY 实验整合(#47) + +[每屏 margin 调查](conpty-margin-backends.md)确认固定官方新后端可以保留原 DECSTBM;系统后端已丢失的信息不能由末端猜测补回。本切片将这个可行路径接到实际 `Session` 和 GUI,**保留默认系统后端,未完成 R2 后端决定或完整回归**。 + +## 接口、身份和生命期 + +`Backend::system()`、`reviewed_side_by_side(absolute_directory, GlyphWidth)` 和显式构建的 `configured()` 使用同一个对象边界。`Session` 创建、resize 和关闭均调用该对象的同一 API 族;模块由共享所有权保持,Backend 成员在 Session 中最先声明、最后析构,使输出读线程、HPCON 和清理过程结束前 DLL 始终有效。 + +来源/部署见[固定包入口](../../third_party/conpty/README.md)。CMake 不下载;运行时也不探测已安装的 Terminal。不混用 DLL 与系统 create/resize/close,不在缺失或错误文件时静默回退。独立 native 检查实际创建 80×24、由 child API 观察 120×40、正常退出37、EOF109、自有 Job 归零,并确认临时 Backend 销毁后 DLL 仍在、Session 析构后才卸载;两个文件各自篡改都拒绝。 + +## 宽度策略的 A/B + +固定1.25包默认 flags 0 时,真实 `ReadConsoleOutputCharacterW`/`CHAR_INFO` 对本项目四个复杂文字样本产生新的 readback 投影,原严格 Unicode 合同失败,末端仍保留原文。只改官方 `GLYPH_WIDTH_CONSOLE=0x18` 后,四样本回到既有合同承认的原文/非BMP替代投影,原完整 Unicode/glyph 检查两轮通过;没有修改 `unicode_checks.py`。全部原 flags0失败和 flags24结果仍保留。 + +因此显式实验 `configured()` 固定选择 Console width;系统和通用诊断保持默认0。实际 GUI margin 记录还保存 Session 的 backend 和 creation flags,测试端必须显式选择对应合同;不会根据末屏自行挑选较宽松的解释。该固定包头文件及源码来源已经绑定到 manifest;这个策略与旧 `0x08` passthrough 无关,也不构成完整字素/宽度或 native API 等价验收。 + +## 实际窗口和已有回归 + +原175字节 margin source、四阶段 native capture、区域外文字、主历史、光标、style 和 retained/full frame 均保留。新实验合同独立要求末端恢复 `REGION_C`、`REGION_D`、`BOTTOM_FIXED`,同时要求 native 继续显示已复现的 `REGION_CIXED`。源控制台与末端分列,#41 不关闭。默认 system 合同继续检查原始差异,没有删除旧失败或补发 VT。 + +原 ESC resize 场景曾以宿主的 `CSI ?25h` 当 resize 完成信号。新宿主没有这个重绘副作用,原夹具超时。现在由真实 peer `GetConsoleScreenBufferInfo` 确认60×12并发出独立 ack,随后发送原单个 ESC;resize与输入共用原五秒预算,外层期限不变。真实 ESC 两轮通过;Vim/DSR/resize写盘曾在早期flags0阶段通过两轮,不能将它用于证明最终Console-width策略通过。 + +最终C++源码固定在 `5030058`,随后 `0954fea` 只整合 margin Python 诊断的异常/清理修复。[公开验证记录](../../experiments/conpty/results/2026-10-26-reviewed-backend/validation.json)包含15个源码文件的git blob哈希、四项二进制哈希、固定包/完整MIT身份、逐后端margin结果和白名单化的正反结果;未提交原始屏幕、transport或路径。 + +最终Console-width构建的后端生命期/拒绝检查6/6、margin诊断清理检查9/9、既有TUI检查5/6;TUI中ESC、主/备用屏、局部滚动、每屏margin和完整Unicode各两轮通过。基础cmd Unicode/编辑/Ctrl-C/正常退出三轮、四种本地shell/CWD/退出两轮、持续输出关闭三轮通过。More子阶段完整提示→单q→EOF通过,但包含Vim的整套native capture失败,不能将More成功表述成整套通过。 + +Find、选择/zoom、split后台会话以及flags0的Vim/完整native capture是较早源码阶段的限定结果。不同阶段分别保存,不把早期结果回写为最终同源码完整验收;`runtime_observation_test`此次仅构建,未执行。 + +## #50 评审后的测试监督器修订 + +独立 Standards/Spec 评审确认 `test_backend.py` 的异常清理可能覆盖原始超时,并跳过 Job 关闭和记录;目录准备失败也会泄漏提前创建的 Job。修订先准备目录,再取得 Job;assignment 失败只杀仍等待 GO 的自有根进程,不发送 GO。终止、排空、Job 观察、关闭和写盘分别保障,所有清理等待共用原五秒预算;未知观察保留 `null`,首次非零 Job 与强制清理后的观察分列,不能归零后转为通过。 + +原实现的六组纯异常负例先复现失败,修复后再加入证据/summary 写盘失败边界,`BackendCleanupFailureTests` 共8/8通过,且不受 Windows skip 限制。现有真实 `BackendTests` 6/6再跑通过(7次调用,含同包两文件各自篡改拒绝);只复用已构建二进制和固定包。此次只修改测试监督器,不改变产品后端和下列已知红项,也不重写前述固定源码的历史验证记录。 + +## 仍未闭环的真实差异 + +- **连续 GUI resize。** 原 `resize` 场景在stage43等待精确 `TF_SIZE_100x28`,末端实际出现 `TF_SIZE_100x28OW`,并有旧文字/位置及重放后的 Unicode 差异。Console width并未修复。没有删除尾部、替换source或放宽行匹配;需要裁决候选 resize/reflow 与新宿主同步VT/原生API状态的交叉。 +- **Vim兼容。** 最终Console-width构建在原stage59没有得到resize事件文件,命令出现在插入文字中;同批6项TUI检查为5通过、1失败,失败项第二轮未运行。独立native real-app capture也在 `new-buffer` 阶段失败,返回1,editor字节数1999;外层Job仍归零。没有改成睡眠、跳过Vim或降低既有事件文件断言,早期flags0的成功与本次失败分列。 +- **粘贴并发时序。** 原 3MB 请求、768字节 receiver checkpoint和持续输出均保留,但新宿主在GUI看到checkpoint前已经接收全部输入,transfer为completed。这个运行不能证明未完成期的Cancel/关闭行为。不能把completed当cancelled,也不能仅降低原16KiB阈值;需要保持原负载与独立回收断言的确定性sender进度栅栏,再验证实际产品取消。 +- **源 native margin。** 固定新包仍未修复 native readback 的每屏 margin;末端正确不替代原生API或依赖它的应用验证。 +- **出口。** 尚无该实验后端的完整R1同次工作流、实际IME/跨屏、长期资源、性能或WinTerm对照结论;默认Alpha、#34产品决定、#35和R2范围均未改变。 + +这些缺口继续由 #47 / #41 及相应父项保留,实验可选择不等于可靠日常版本。验证仅在自有Job/非输入桌面进行,原始transport、错误原文和图像继续留私有目录;`presented_done=null`。 diff --git a/docs/research/native-viewport-resize.md b/docs/research/native-viewport-resize.md new file mode 100644 index 00000000..20868a13 --- /dev/null +++ b/docs/research/native-viewport-resize.md @@ -0,0 +1,50 @@ +# 显式官方后端的原生 viewport resize + +本项处理 [#51](https://github.com/JavaZeroo/termforge/issues/51) 的连续 GUI resize 残尾。固定原基线为 `e0b4d67`、候选 `termforge.4`、官方 ConPTY `1.25.260930003`、Console glyph width flags `0x18`;修复为同一候选的 `.5`。默认 system 后端和正式核心选择不由此项决定。 + +## 观察与原因 + +原场景末屏出现 `TF_SIZE_100x28OW`,原断言要求整行恰为 `TF_SIZE_100x28`。原生 `ReadConsoleOutputW` 捕获在同一次失败中显示该行正确,候选屏幕却保留 `OW`。原运输与 resize 时序可以逐步重现失败;移除中间两次请求仍出现同样终态,所以不能仅归因于请求合并或遗漏 host resize。 + +候选的通用 reflow 会在增高或增宽合并行时把历史拉回可见区,并可能为了保持光标行而删掉底部行。原生控制台没有终端已经保留的历史;随后由 host 发送的绝对坐标更新仍针对其原生可见区。两套布局先分歧、再接受局部覆盖,造成旧文字残留。六组独立原生样本分别覆盖增高、可见软行合并、光标所在软行合并、历史/可见区续行边界和部分合并后的底行保留。 + +| 初始→目标(列×行) | 关注点 | 原生末屏(省略行尾填充) | 原生光标(0 起算) | +|---|---|---|---| +| 8×3→8×6 | 增高不拉回 H0/H1 | TOP / MID / BOT / 空 / 空 / 空 | 2,3 | +| 8×3→16×3 | 合并后不拉回 H1 | 123456789 / P> / 空 | 1,2 | +| 8×3→16×3 | 光标位于被合并行 | H2 / 123456789 / 空 | 1,9 | +| 8×3→5×3 | 历史续行不能改变原生可见区 | 90123 / 45678 / 90 | 2,2 | +| 8×4→10×4 | 部分合并不能丢 BOTTOM | H1 / ABCDEFGHIJ / KLMNO / BOTTOM | 1,9 | +| 8×3→16×3 | 增宽不跨历史边界合并 | 9012345678901234 / 567890 / 空 | 1,6 | + +这些是 ASCII 合成用例的原生快照,不是由候选算法计算的预期。原生采样会影响时序;微型探针不回答 CPR,固定 host 的有界等待亦影响时序。因此本记录不作为吞吐、交互延迟或原版性能证据。 + +## 实现边界 + +新增显式 `native_viewport` 策略,仅由官方后端的候选窗口选择。历史和可见区分别 reflow,增高/合并产生的空行置于可见区底部;保留光标、底行、历史身份和有界淘汰语义。跨边界续行保留逻辑换行元数据,以独立标记区分布局填充与文本;任何可变单元格访问都会使该标记失效。保留原跨度内的尾空格,保证完整复制及后续 resize 不增删空格。默认 `reflow_history`、系统候选和默认自研核心保持各自合同。 + +本项目独立实现这些修改,保留第三方许可与版本身份;不是恢复出的 WindTerm 源码,也不是确认 WindTerm 运行时一致。完整 resize/选择/Find、备用屏与模式交叉仍归 #29/#27,正式选型归 #34。 + +## 可复现入口 + +先按[显式官方后端说明](experimental-conpty-backend.md)构建候选配置。以下输出目录须不存在,后端目录必须是经过双 SHA 校验的固定包;探针不会下载或回退。 + +```powershell +cmake --build build/candidate --config Release --target conpty_resize_native_probe candidate_resize_replay candidate_core_test +python -m experiments.conpty.resize_native_run --exe build/candidate/bin/Release/conpty_resize_native_probe.exe --replay build/candidate/bin/Release/candidate_resize_replay.exe --backend build/candidate/bin/Release/conpty-backend --out local/resize-native-new +ctest --test-dir build/candidate -C Release -R '^candidate_(core|resize_replay)$' --output-on-failure +``` + +探针先等待监督器 GO 和自有 Job,再启动私有控制台子进程。每组保存源 VT、前后原生 UTF-16、真实 transport、resize plan、两策略末端状态、二进制/源码哈希和清理状态。child 必须确认真实新尺寸、自然退出 37;父级检查 EOF、无强制取消、无溢出和 Job 清空。内层等待最多 5 秒、外层 20 秒,失败不扩大预算。回归中的预期来自上表,默认策略对照的差异单列。 + +## 验证与限制 + +[固定验证记录](../../experiments/conpty/results/2026-10-27-native-viewport-resize/validation.json)区分原 `.4` 真失败、正式 `.5` 与早期临时探针。原 GUI 的精确 marker、Unicode、位置/历史、child ack、Job/desktop 回收断言及原期限保持;未删除 `OW`、改成子串、清屏或关闭 reflow。 + +诊断中有两组结果不进入因果证据:只保留错误字符串的缩减最终删掉全部 resize,不能作为此缺陷的最小复现;另一次用保留旧 mtime 的文件复制恢复源码,增量构建复用了旧修复二进制。后者已按实际二进制哈希排除,重新写入源码、实际编译后恢复了原 stage43 red。公开记录保留这些排除原因。 + +六组合成探针可公开复现;原 GUI 原件保留本地,只发布审核后的状态、哈希和固定断言结果。完整 R1/R2、实际呈现、真实 CF/IME 组合与 WindTerm 对照不从本项推导,`presented_done` 保持 null。其他真实失败由对应 issue 继续跟踪。 + +最终产品源 `92eac12` 的显式官方配置完整 CTest 为 54/56、411.59 秒,另有 12 项内部 skip;失败为 Vim stage59(#52)和第二轮工作区恢复 stage1(#88)。同次八会话工作流通过,但此前两次编号半行失败仍由 #87 跟踪。原 resize、resize-close 和 Unicode 检查通过只验收本项限定修复,不覆盖这些独立失败。 + +后续 [#63 工作区核验](workspace-native-viewport.md)用同EXE单变量策略、原生快照与实际运输回放,确认本策略还覆盖原UI stage52及主区场景stage7的底部残尾;该项的无插桩原三轮重复和完整shell验收另有固定身份,不改写以上历史整套结果。 diff --git a/docs/research/resize-replay-cost.md b/docs/research/resize-replay-cost.md new file mode 100644 index 00000000..9352fbab --- /dev/null +++ b/docs/research/resize-replay-cost.md @@ -0,0 +1,36 @@ +# 八会话的 resize 重放成本与关闭回复 + +接续 [UI sender fence](ui-paste-fence.md) 的 [#53](https://github.com/JavaZeroo/termforge/issues/53)。候选 `.4` × 显式官方 ConPTY `1.25.260930003`、flags24 在源码 `eae4b6a4ef3e044ebfebbbbfeccf9aec74cd7f0d` 上通过两轮原八会话 **data-only** 合同。修复保留原 3MB 汉字、768B checkpoint、16KiB sender 门槛、后台输出量、UI70秒和 fixture55秒期限。[固定身份与最终回归](../../experiments/shell/results/2026-10-26-replay-cost-and-close/validation.json)与[全部诊断样本](../../experiments/shell/results/2026-10-26-replay-cost-and-close/)分列,不把真实剪贴板/IME、其他构建或整个 R2 算作通过。 + +## 成本定位与有限优化 + +旧源 `8ff112e` 的两次真实八会话插桩仍在 stage45 失败,Job0、desktop 已回收。第一次把约64秒定位到 parser feed;第二次进一步分开内部阶段:parser feed 63.987秒、Rust feed 0.560秒、synchronize 63.420秒,其中 cell export 2.031秒、reconcile_replay 60.905秒。计数是主线程累计的嵌套 wall time,不能相加,也不是实际呈现或输入延迟。两次均关闭资源采样,仅作诊断;[完整计时、源码/二进制身份与原始记录哈希](../../experiments/shell/results/2026-10-26-replay-cost-and-close/ui-profiles.json)保留。 + +普通无 resize 的 headless F1 输入未复现高成本:[2,048,000B 的六样本](../../experiments/shell/results/2026-10-26-replay-cost-and-close/headless-ordinary.json)均满足逐行文字、光标和历史预期,未插桩三次 wall 为0.446–0.486秒。缩小到204,800B后,只在计时前加入一次同尺寸 `resize(17,74)`,未插桩两次从无 resize 的0.046/0.044秒变成0.929/0.969秒;六次最终 screen JSON 哈希全部相同。[全部样本与冻结输入](../../experiments/shell/results/2026-10-26-replay-cost-and-close/headless-resize.json)、[诊断驱动及计时边界](../../experiments/shell/results/2026-10-26-replay-cost-and-close/probe/README.md)可审查。 + +观察与源码共同支持一个有限原因:resize 保留了重放起始屏幕,后续每次同步的 `reconcile_replay` 会反复序列化同一行,查找 origin 时还会再次生成行键。`06bf6b5` 让同次同步生成的 expected/actual 键向量保留空行位置,并直接按索引复用;计数仍跳过空键。键内容、重复行处理、匹配顺序、行 ID/version 映射、重放生命期和历史策略保持原合同。缓存只在一次调用内存在。 + +这些样本来自共享 Windows 11 build26200 宿主,8逻辑核,MSVC19.42 Release x64;headless 只输入 producer 字节,没有 ConPTY 变换或 UI。短样本与插桩均有扰动,不据此计算产品提速比例,也不替代 [#54](https://github.com/JavaZeroo/termforge/issues/54) 的预冻结 `.3/.4` 正式 A/B、用户性能预算或 WindTerm 对标。 + +## 关闭期间仍发送回复的独立错误 + +只完成键复用后的原八会话在27.467秒完成后台消费,35.974秒完成粘贴关闭和后续输入,但退出时因 `terminal reply rejected by input queue` 返回2。额外诊断的两轮分别成功与失败;失败现场为 `closing=1, stopping=1, queued=0, failed=0, paste_busy=0`,回复是 `ESC[4;4R`。这些成功/失败均保留在[中间运行记录](../../experiments/shell/results/2026-10-26-replay-cost-and-close/intermediate-workflows.json),一次成功不消除退出错误。 + +原因是关闭已停止输入队列,剩余输出中的光标位置查询仍经 `route_terminal_events` 发送回复。新增真实 ConPTY 回归先将查询留在 OutputQueue,明确要求尚未消费输出,再关闭并确认输入停止,最后才泵窗口消息。旧代码0.19秒即因输入拒绝失败;`eae4b6a` 仅在 `closing_` 时不再发送回复,两次新进程均实际观察查询、消费65B、解析标记文字、零输入拒绝并完成回收。输出仍解析、排空和发布;正常会话的回复失败仍严格报错。 + +[红绿身份](../../experiments/shell/results/2026-10-26-replay-cost-and-close/closing-reply.json)明确记录夹具变化:绿色版本增加了已解析标记与消费字节断言,未放宽旧条件。测试只注册于候选+显式后端组合,不能据此声明默认/system 配置已验收。 + +## 最终结果与剩余边界 + +最终源码两轮八会话包含资源采样,后台完成时间分别为26.268、27.312秒,粘贴关闭后输入验证分别在34.675、35.808秒完成。每轮26个检查点、五轮6↔8会话、拒绝第9个会话、精确接收字节和18个会话关闭均通过,Job0、desktop已删除。两轮 cancel/close 的动作前均为 sent17391 / accepted18414 / held1023;取消接收18414B,关闭恰768B,in_flight0、retained15。close 的实际唤醒为 cancelled,最终状态为 stopped,二者分列。 + +同源码完整 CTest 为54/56,465.08秒:`candidate_shell_tui` 的六项中 Vim stage59 失败,其余五项通过;`shell` 聚合触及原180秒期限,已记录 resize/resize-close43、main第三轮7、ui第三轮52。聚合中单窗口粘贴两轮和 EOF/无GO反例均已完成并通过;负例的产品运行仍返回2、接收恰768B、Job0/desktopgone,未因清理转绿。未结束的 shell 项不能算通过。 + +内部 skip 也保留:可选 Rust 对比/real-app 工具未构建、托管实际 CF/IME 保护、原版失败注入 opt-in 及私有站点 OpenClipboard 权限失败。CTest 条目通过不表示其中被跳过的场景通过。#51/#52/#63、真实系统剪贴板/IME、实际呈现、长期资源、正式性能及产品核心/后端选型仍需各自完成。自动化使用自有非输入桌面,不要求操作用户窗口。 + +```powershell +cmake --build build/candidate --config Release +ctest --test-dir build/candidate -C Release --output-on-failure +``` + +复验前按贡献指南显式选择 `.4` 与固定官方后端,准备固定 Vim 的测试进程 PATH;结果同时记录二进制/依赖哈希。单独复验关闭查询可用 `ctest --test-dir build/candidate -C Release -R '^candidate_reply_close$' --output-on-failure`。完整回归的失败不会因局部修复通过而被忽略。 diff --git a/docs/research/ui-paste-fence.md b/docs/research/ui-paste-fence.md new file mode 100644 index 00000000..704589d5 --- /dev/null +++ b/docs/research/ui-paste-fence.md @@ -0,0 +1,50 @@ +# 未完成 UI 粘贴的确定性取消与关闭 + +对应 [#53](https://github.com/JavaZeroo/termforge/issues/53),属于 [#50](https://github.com/JavaZeroo/termforge/pull/50) 的实验后端整合。实现源 `a15eb7f67315fb53c9d66b9c5a4424e13511a311` 的单窗口两轮取消/关闭及控制故障检查通过,**该源码的完整八会话仍失败**。后续在 `eae4b6a` 上通过两轮原 data-only 合同,见[行键成本与关闭回复修复](resize-replay-cost.md);本页保留修复前证据。[固定源码、二进制、原始记录哈希与逐字段结果](../../experiments/shell/results/2026-10-26-ui-paste-fence/validation.json)分列原 red、针对性验证与整体验证,不能互相替代。 + +## 原失败与修改 + +在 `.4` 候选与官方 ConPTY `1.25.260930003`、Console width flags24 的联合构建上,原场景两次被正确拒绝:一次到动作前已 `sent=accepted=total=3000000`,另一次在 receiver gate 前就 completed。这说明原夹具不能稳定观察未完成期,不能据此认定无栅栏的产品取消失效。 + +`ShellWindow` 现在允许自有测试预配置单次 generation 与绝对期限。真实 `PasteTransfer` 创建后、`InputQueue::begin_paste` 唤醒 writer 前完成 fence 绑定;普通产品请求仍默认关闭。writer 沿原取块、实际发送、写确认和释放路径工作,达到原 16KiB 门槛后只持住一个尚未提交的真实块。 + +先只增加栅栏、保持原 UI 直接 `transfer.cancel()` 时,场景在原 35 秒期限失败;改为真实 Escape 调用 `input_.cancel_paste()` 后,同一测试两轮通过。该入口通知等待中的 writer,允许已领取块完成真实发送;取消前没有成功 GO。关闭仍走既有 cancel、stop、Session 回收路径。 + +原 100 万个汉字、3,000,000B、768B 接收检查点和 flood 不变。只有同时观察到 held、实际文件 checkpoint、实际输出 flood 和另一会话继续响应,才执行动作。单窗口的另一会话在 held 后执行独立 echo;八会话检查另一后台会话在 held 后继续消费输出。两者的响应证据不同,不称为同一种输入延迟测量。 + +## 控制协议和证据 + +测试控制端通过真实匿名 Win32 管道发送 generation/GO、读取应答;两个端点在同一进程,不是外部监督进程断连验证。UI 正例先验证错误 generation 被拒绝且仍 held,随后执行真实 Escape 或关闭。纯协议测试另覆盖未 held、匹配 GO、重复 GO、八条请求/应答积压及第九条拒绝。 + +EOF 负例实际关闭写端,并由 `ReadFile` 确认 `ERROR_BROKEN_PIPE`;无 GO 负例使用单独的 2 秒故障期限。两种真实 UI 场景均保持失败返回 2,writer 释放 payload,receiver 仍恰为 768B;Job0 与 desktop 回收不会转化为成功粘贴。正例原期限仍为 UI 35 秒、外层 40 秒、receiver gate 15 秒;八会话仍为 UI 70 秒和 fixture 55 秒。 + +`CreatePipe` 的请求大小只是建议,因此构造时通过 `GetNamedPipeInfo` 检查实际 incoming server buffer 足以容纳整个 128B 请求/192B 应答预算,不能直接把 4096 请求值当作实测容量。见 Microsoft 的 [CreatePipe](https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe) 与 [GetNamedPipeInfo](https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-getnamedpipeinfo) 合同。 + +关闭可能先由 cancel 唤醒,再由 stop 改变最终状态。现在在 CV 返回时固定实际退出原因,并分别记录 `exit` 与 `final_state`;最新两轮 close 为 `exit=cancelled / final_state=stopped`。旧 `62226af` 记录的 `exit=stopped` 只是最终状态,不作为实际唤醒原因证据。 + +| 动作 | 动作前 sent / accepted / held bytes | 动作后与独立 receiver | +|---|---|---| +| Escape,两轮 | 17391 / 18414 / 1023 | cancelled;sent=accepted=received=18414;in_flight0、retained15 | +| 关闭,两轮 | 17391 / 18414 / 1023 | receiver 恰为768;in_flight0、retained15;已提交块仍按真实计数记录 | + +## 未完成的组合出口 + +两次带采样八会话都完成了上述两项粘贴动作,但后台整体消费完成分别约为 51.158、52.546 秒;close 前分别约为 61.806、63.096 秒。前台 B fixture 已按原 55 秒期限退出,最后一个 `!` 无法收到,UI 在 stage45 期限失败。原始状态、字节和回收结果均保留。 + +同一最终代码关闭资源采样的诊断仍失败:后台完成 50.262 秒、close 前 60.948 秒。因此不能把采样器认定为这一失败的唯一原因;该单变量运行也不替代带采样组合验收。候选适配、消息循环和宿主负载成本仍需分别定位,不延长原期限或减少后台负载。 + +`62226af` 的完整 CTest 为52/55:Vim 不在当时 PATH、八会话 stage45、shell 聚合180秒期限分别保留;shell 中另有 resize43、main7、ui52 的失败,不归因于粘贴修复。最终唤醒原因与容量修正后,3/3 针对性 CTest、两次完整单窗口正例及 EOF/无 GO 负例通过。随后仅在测试进程 PATH 补齐固定 Vim9.1,原入口再次在 stage59 失败,resize 命令进入插入文本;这与环境准备失败分列,#52 继续开放。其他配置、托管系统剪贴板/IME、呈现与 WindTerm 性能等价均未由这些结果验收。 + +## 复验 + +按贡献指南准备依赖,显式选择候选及固定官方后端后: + +```powershell +cmake --build build/candidate --config Release --target termforge_shell shell_test_launcher sender_fence_test sender_fence_pipe_test +ctest --test-dir build/candidate -C Release -R '^(sender_fence|sender_fence_pipe|paste_fence_evidence)$' --output-on-failure +$env:TERMFORGE_BIN_DIR = (Resolve-Path build/candidate/bin/Release).Path +python -m unittest experiments.shell.test_shell.ShellTests.test_paste_confirmation_and_hidden_target_preserve_visible_focus experiments.shell.test_shell.ShellTests.test_paste_control_eof_remains_failure_after_cleanup experiments.shell.test_shell.ShellTests.test_paste_missing_go_remains_failure_after_cleanup -v +python -m unittest experiments.shell.test_workflow_sessions -v +``` + +最后一项保留真实失败出口。测试使用自有非输入桌面,不要求操作用户窗口;完整 R2 与产品后端选择仍由各自合同约束。 diff --git a/docs/research/vim-fixture-isolation.md b/docs/research/vim-fixture-isolation.md index 9766b333..d4136a18 100644 --- a/docs/research/vim-fixture-isolation.md +++ b/docs/research/vim-fixture-isolation.md @@ -24,6 +24,6 @@ python -m unittest discover -s experiments/differential -p test_real_app_matrix. ## 尚未闭合 -原 GUI `candidate-tui` 两轮本批仍为一绿一红;红例在 resize 后没有产生 InsertLeave,`:call writefile(...)` 成为插入内容,stage59 到原期限失败。两轮都回收为 Job0、私有 desktop 消失。目录隔离不影响此入口,尚无根因或修复结论。历史冻结基线、完整 TUI、呈现端点和后端整体回归仍分别保留。 +原 GUI `candidate-tui` 两轮本批仍为一绿一红;红例在 resize 后没有产生 InsertLeave,`:call writefile(...)` 成为插入内容,stage59 到原期限失败。两轮都回收为 Job0、私有 desktop 消失。目录隔离不影响此入口;该批尚无根因或修复结论,后续输入边界见下文链接。历史冻结基线、完整 TUI、呈现端点和后端整体回归仍分别保留。 -有停止条件的 GUI 探针也未形成修复:strace 包装的两次运行在 Vim 启动时触发另一种 `c0000005`,不计原症状;临时观察真实 WriteFile 成功字节的三轮均绿,其中两轮 Ctrl-O 命令先于 CPR,否定了“这个先后关系必然导致红例”的假设。观察代码已移除,重新构建的原入口两轮均绿,事件/写盘/回复原检查保持;这仍不能解释已保存的真实 stage59 红例。探针二进制未在还原前记录哈希,证据明确保留该身份缺口并排除为验收。下一次需定位真实控制台输入到 Vim resize/按键处理的边界,不能用这批绿例关闭 GUI 问题。 +有停止条件的 GUI 探针也未形成修复:strace 包装的两次运行在 Vim 启动时触发另一种 `c0000005`,不计原症状;临时观察真实 WriteFile 成功字节的三轮均绿,其中两轮 Ctrl-O 命令先于 CPR,否定了“这个先后关系必然导致红例”的假设。观察代码已移除,重新构建的原入口两轮均绿,事件/写盘/回复原检查保持;这仍不能解释已保存的真实 stage59 红例。探针二进制未在还原前记录哈希,证据明确保留该身份缺口并排除为验收。该批绿例不能关闭 GUI 问题。后续[真实输入边界观测](vim-msys-input-boundary.md)已捕获 MSYS 消耗 Ctrl-O 的调用顺序,仍没有产品修复验收。 diff --git a/docs/research/vim-msys-input-boundary.md b/docs/research/vim-msys-input-boundary.md new file mode 100644 index 00000000..9df5b4ec --- /dev/null +++ b/docs/research/vim-msys-input-boundary.md @@ -0,0 +1,41 @@ +# Vim resize 后 Ctrl-O 的输入边界 + +[#52](https://github.com/JavaZeroo/termforge/issues/52) 的原固定运行库 GUI stage59 验收仍未完成。本文的有界运行时记录将捕获到的失败定位在 MSYS 控制台事件到 `read(0)` 的转换:TermForge 成功写出完整 93 B,原生输入也包含 Ctrl-O,但 Vim 收到的读结果仅剩其后 92 B。本文记录诊断,不改变固定应用、输入、期限或验收合同;后续[隔离运行库修补实验](vim-msys-runtime-prototype.md)单独记录重建基点 red 与修补四轮 green,不替代原固定目标验收。 + +## 固定身份与观测范围 + +基点 `44ea45b` 的产品代码与 `c662011` 相同;候选 `termforge.5`、官方 ConPTY `1.25.260930003`、flags24。固定 MSYS Vim 9.1 patches 1–532 的 SHA-256 为 `2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a`,MSYS DLL 文件为 `3096d2ba9be20e90be1399edd391bac65d84bc387f5a419b502c9b326081930f`,对应公开源码 `2e2ef940151485490aff0c7b00c00965ef7a71a4`。 + +临时诊断程序 SHA-256 为 `73472864e5aa291704917812ff1848f7a59be6e5f30c41f6a613e3fc18c740d4`。仅在原 VimEnter READY 和 `[New]` 就绪后,从会话自己的 Job 查询唯一固定路径 Vim,附加 REA/Frida;附加完成后继续原 `i` 输入。该握手包含在原25秒场景期限内,外层仍40秒,Vim脚本、尺寸、93 B命令和原精确断言均保持。探针记录实际 API 参数与返回,不改输入字节、应用数据缓冲区或返回值;Frida安装代码钩子,初始调度及观察开销仍会影响运行。 + +首批最多4轮,出现原 stage59 与诊断成功后停止,实际为两红一绿。随后为核对具体调用点,另设最多2轮、首次原失败或其他失败即停止的批次,实际1轮原 stage59。第二批核验加载模块路径、PE时间戳、COFF函数位置及4个入口的实际32 B前缀,然后观察两个函数的进入/返回;它不是新的修复方案。 + +每轮上限512条事件,每次原生调用最多256个记录,每次 `read` 最多512 B;四轮均未截断。全部原件、工具完整 Evidence、源代码补丁与二进制留在本地,公开[白名单时间线及哈希](../../experiments/shell/results/2026-10-27-vim-msys-input/validation.json)。所有运行均原期限内结束、外层 Job active0/terminated0、私有 desktop 已回收。 + +## 直接观察 + +三次红例中,writer 都一次成功写出93 B,包括开头 `0f`。原生 `ReadConsoleInputW` 可看到该字符;之后的 MSYS `read(0)` 只返回 `:call writefile(... )` 对应的92 B。原 InsertCharPre 文件记录冒号及命令进入插入内容,resize事件文件为空,原场景在stage59失败。 + +核对调用点的红例具有以下顺序。线程名称仅为该次运行的别名,序号对应公开JSON;没有将不同进程时钟拼成耗时测量。 + +| 序号 | 实际事件 | +|---|---| +| 15–17 | T1进入 `process_input_message`,Peek得到两个resize事件,进入 `send_winch_maybe` | +| 18 | T3从 `cons_master_thread` 调用 Read,取走两个resize事件 | +| 19 | T1的 `send_winch_maybe` 返回true | +| 20–21 | T1仍在 `process_input_message` 中,Read清理1个记录,实际取走的是新到的Ctrl-O;随后返回 `input_winch=3` | +| 22–27 | 下一次处理从冒号开始,最终 `read(0)` 交给Vim的内容为92 B,没有Ctrl-O | + +原生Read的返回地址由DLL内COFF函数边界独立映射;函数进入/返回记录同时覆盖了这段执行。上述三层丢失假设中,“写入之前或原生输入之前丢失”与“Vim已收到但忽略Ctrl-O”不符合这些红例,丢失发生在已观测的MSYS转换边界。 + +首批绿例中,T1先实际读走第一个resize事件;另一个线程才移除剩下的resize并写回93个键盘记录。随后MSYS完整返回93 B及退出命令,原写盘、resize文件、InsertLeave/Enter、主屏恢复、回复、退出与回收检查均通过。它是带探针的一次原断言成功,不是修复后的两轮验收。 + +## 源码解释与未完成项 + +固定源码的 [`send_winch_maybe`](https://github.com/git-for-windows/msys2-runtime/blob/2e2ef940151485490aff0c7b00c00965ef7a71a4/winsup/cygwin/fhandler/console.cc#L935) 在发送SIGWINCH时临时释放、再取得 `input_mutex`;[`process_input_message`](https://github.com/git-for-windows/msys2-runtime/blob/2e2ef940151485490aff0c7b00c00965ef7a71a4/winsup/cygwin/fhandler/console.cc#L1124) 先Peek并处理旧快照,再按旧数量Read清理。同期 [`cons_master_thread`](https://github.com/git-for-windows/msys2-runtime/blob/2e2ef940151485490aff0c7b00c00965ef7a71a4/winsup/cygwin/fhandler/console.cc#L363) 可以移除resize并写回键盘记录。由此推断旧快照在释放锁期间失效,与实际调用顺序吻合;本诊断批次没有修补后的因果红绿,后续独立实验见上文链接,不混用其运行身份。 + +这个释放锁的代码来自[避免less信号处理longjmp导致死锁的上游修正](https://github.com/git-for-windows/msys2-runtime/commit/76e1edeb27a573a955ebf3ae5a7a7fd1a75b290f)。后续方案必须同时保留这项约束,不能简单删除解锁,也不能补发Ctrl-O、延长等待、换应用或跳过原断言来结项。可继续准备隔离的最小运行库修补与真实输入回归;外部应用或运行库升级的诊断结果不能自动替换原固定验收目标,正式兼容方案另行提出。 + +REA provider本身没有绑定目标文件身份、平台或版本,这些字段及限制原样保留;文件哈希、脚本报告的平台、第二批加载模块与入口字节核对是另外的证据。进入附加前已在执行的调用不在覆盖范围内;原 `i`、完整标记和失败命令边界均已捕获。没有追溯归因未保存原生输入的历史失败,也没有推出默认/system、其他MSYS版本、WindTerm或性能结论。 + +临时C++改动已按基点字节恢复,原工程重建成功,工作树没有产品或测试改动。重建EXE为 `b061934e08561a7bc2e0dc2773412ef3d900e2728267ce1d5c54be5ca6ad8515`,与探针及探针前EXE各自分列,未声称哈希相同或重新完成全量回归。#52、#47和Draft #50继续开放;[native目录复用](vim-fixture-isolation.md)已修的结论保持独立。 diff --git a/docs/research/vim-msys-runtime-prototype.md b/docs/research/vim-msys-runtime-prototype.md new file mode 100644 index 00000000..cdf34780 --- /dev/null +++ b/docs/research/vim-msys-runtime-prototype.md @@ -0,0 +1,50 @@ +# Vim/MSYS resize 输入前缀修补实验 + +固定公开运行库源码的最小补丁,使隔离环境中的原 GUI Vim 方法连续两批、每批两轮通过;同条件未修补的重建 DLL 首轮复现 stage59。这支持此前捕获的“SIGWINCH 解锁期间旧 Peek 前缀失效”解释。**[#52](https://github.com/JavaZeroo/termforge/issues/52) 的原固定运行库验收仍未完成**:本次没有修改 TermForge 产品、测试或正式兼容策略,也没有把实验 DLL 放入产品包。 + +## 固定身份与实验边界 + +TermForge 基点 `ed55a8758423b3273fbd98f07e0bfd77d88e46ac`,产品代码与 `c662011` 相同;候选 `0.26.1-dev+termforge.5`、官方 ConPTY `1.25.260930003`、flags24。所有 GUI 样本使用同一个无插桩 EXE,SHA-256 `7f26395942f107297565f50402568650e1e17c5eb23924bee1bfb02c50c60d16`。本机 Windows 11 x64,批次完成后查询系统版本为 `10.0.26200`;没有发布主机名。 + +固定 Vim 9.1 patches 1–532,EXE SHA-256 `2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a`。Vim 与四个相邻 DLL 从既有安装按哈希复制到隔离目录;各变体使用同一个活动路径,只有 `msys-2.0.dll` 切换,其他四个文件保持原字节。没有复制 Vim runtime 配置目录。每次切换前检查上一方法的自有 Job 已清空、私有桌面已释放;批次后复核既有安装的五个文件未变。 + +| 运行库变体 | SHA-256 | 实际结果 | +|---|---|---| +| 隔离原 DLL | `3096d2ba9be20e90be1399edd391bac65d84bc387f5a419b502c9b326081930f` | 一次原两轮方法:先通过、后 stage59 | +| 固定源码未修补重建 | `d9e490fdbe31c233eab6578168fb4f2bda84f53252be3898d0cdd0e8610db721` | 首轮 stage59,立即停止 | +| 同条件修补重建 | `dd792766fa976b031cbff0540744b6f3d2b3fc0d695ed764a01457b65eab6258` | 两次原两轮方法:四轮通过,达到预定上限后停止 | + +本批未附加 Frida,也未采集实际加载模块路径或新的输入 API 轨迹。文件身份和隔离路径是本批证据;此前的调用边界证据仍属于独立的[输入边界诊断](vim-msys-input-boundary.md),不能混写成这批运行的调用记录。 + +## 原方法、停止条件与结果 + +直接调用现有 `CandidateTuiTests.test_real_fullscreen_editor_and_cursor_reply`,每次方法原有两轮,保留原输入、Vim 脚本、事件文件、精确断言及 25 秒内层 / 40 秒每轮外层期限。先限定原 DLL 一次方法;未修重建 DLL 最多两次方法,首次 stage59 或其他失败立即停止;只有重建基点出现原 stage59 才执行修补变体,最多两次方法,任何失败即停。实际共四次方法、七个 GUI 样本,没有额外 GUI 重试。 + +两个失败均为原 stage59,外层未超时,命令进入插入内容;`candidate-insert-log.txt` 均为相同的 221 B,resize 文件为空。两个失败的 `candidate-input.bin` 也为空、未完成刷新,**不能将其视为完整输入轨迹或“未收到输入”的证据**。原测试输入未改由源码身份保证,实际 API 字节链路只由前一诊断批次证明。 + +四轮修补结果通过原就绪、进入/离开插入模式、编辑、60×12 resize 文件、写盘、退出、主屏恢复、独立 child 尺寸及 CPR 回复检查;`presented_done` 仍为 `null`。原 DLL 的首轮也通过,但不覆盖其后失败。七轮均无外层超时,Job active/terminated 为 0,desktop gone。导出时仅离线重放原断言及验证器,复核保存的结果和文件哈希,没有重跑 GUI。 + +## 修改机制与尚未覆盖的路径 + +此前记录显示:`process_input_message` Peek 到 resize 后调用 `send_winch_maybe`,后者为信号递送临时释放 `input_mutex`;另一线程可以消耗 resize。旧调用恢复后按旧数量清理队列,取走新到的 Ctrl-O。这是已捕获失败的调用顺序,不能追溯填补没有原生输入记录的历史失败。 + +[研究补丁](../../research/msys-runtime-window-prefix/README.md)仅修改 `winsup/cygwin/fhandler/console.cc` 的 `WINDOW_BUFFER_SIZE_EVENT` 分支,12 行新增、6 行删除:在输入锁仍保护旧 Peek 前缀时,先 `ReadConsoleInputW` 消费已处理前缀并调整 `num_processed`;再保留原 `send_winch_maybe` 解锁、SIGWINCH 与再加锁,返回现有 read 循环,下一次重新 Peek。即使尺寸未实际变化也结束当前快照处理,后续输入进入新一轮。 + +没有删除为 less 信号处理器 longjmp 保留的解锁。结构保留不等于已做实际 longjmp 回归;`MOUSE_EVENT` 中的同类信号路径、读取失败注入、其他应用和正式兼容/分发策略尚未验证。补丁沿用既有 `ReadConsoleInputW` 错误处理,未声称修复这些边界。 + +## 构建条件与来源 + +- 运行库:[固定 MSYS 源码](https://github.com/git-for-windows/msys2-runtime/tree/2e2ef940151485490aff0c7b00c00965ef7a71a4),提交 `2e2ef940151485490aff0c7b00c00965ef7a71a4`。未修补基点 tracked 源码干净;修补变体仅上述文件有 diff,公开补丁已做 `git apply --check` 并比对应用后的源码哈希。 +- 独立 Git SDK:提交 `36b102843d99c4993084b29d839dc6f5ef4db2f9`,构建依赖只装入私有 SDK。完整[包版本清单](../../research/msys-runtime-window-prefix/build-packages.txt)随研究记录保存;其中 GCC/G++ 16.2.0,GNU C/C++17,binutils 2.47、make 4.4.1。 +- Win32 头文件:SDK 提交 `eb069c08fe8ef4d2f19dc6b99c4816a4c51fd2ae`(2024-07-10)的 `usr/include/w32api`,包版本 `12.0.0.r0.g819a6ec2e-1`;同时固定裸包含与 `w32api/` 前缀包含路径。不能仅设置顶层 `CPPFLAGS` 后假定子目录采用了旧头文件。 +- 配置保留 `--disable-dependency-tracking --disable-doc --disable-dumper --with-msys2-runtime-commit=2e2ef940151485490aff0c7b00c00965ef7a71a4`,生成构建文件后在目标 `winsup/cygwin` 目录构建 `all`。C 使用 `gcc -std=gnu17 -Wno-error=old-style-definition`;C++ 使用 `g++ -std=gnu++17` 和 `-O2 -g -Wno-error -Wno-narrowing -Wno-use-after-free`。最终 make 显式传入两个旧头文件的 `-isystem` 路径,`-j2`,每次构建上限 900 秒。构建准备失败和编译警告原件保留在本地,不计 GUI 结果。 + +这是新工具链重建实验,不是官方 2024 DLL 的逐字节复建。两个重建变体的 310 项构建对象清单具有相同键集合,六项哈希不同:`fhandler/console.o`、`libdll.a`、`msysdll.a`、`new-msys-2.0.dll`、`version.o`、`winver.o`,包含再生成的版本对象。只能声称一处主动源码修改,不能声称二进制只有一个函数或一个文件不同。 + +公开源码补丁遵循固定上游的 LGPLv3-or-later 与 `CYGWIN_LICENSE` 中的 Linking Exception;完整许可及修改归属见[来源说明](../../research/msys-runtime-window-prefix/README.md),不套用项目 MIT。它是 MSYS/Cygwin 研究材料,不属于 WindTerm 公开或逆向源码。 + +## 证据与结论限制 + +[白名单验证记录](../../experiments/shell/results/2026-10-27-vim-msys-runtime-prototype/validation.json)包含源码、EXE/DLL、补丁/许可、对象差异、全部七轮结果、原文件哈希与回收状态;DLL、原始终端输出、个人路径及完整工具日志仅本地保存。 + +本实验为原诊断补充了重建基点 red 与修补变体四轮 green,但没有重新执行完整 57 项 CTest;该批没有运行 WindTerm,也没有形成性能或普遍可靠性结论。后续[原版有限对照](vim-windterm-reference.md)单独记录原 Vim 编辑、原生尺寸差异及 CPR 模式变体,不能与本批拼成完整通过。原固定运行库下的完整官方 56/57 结果保持不变;#52、#47 和 Draft PR #50 的正式出口继续开放,不能用替换外部应用 DLL 自动关闭原合同。 diff --git a/docs/research/vim-windterm-reference.md b/docs/research/vim-windterm-reference.md new file mode 100644 index 00000000..6da9363f --- /dev/null +++ b/docs/research/vim-windterm-reference.md @@ -0,0 +1,52 @@ +# WindTerm 2.7.0 的 Vim、原生尺寸与 CPR 有界对照 + +固定原版在一次观察中完成了原 Vim 的插入、60×12 resize、原 93 B Ctrl-O 命令、写盘和返回主提示符。后续原生尺寸与 CPR 检查暴露了入口差异:另一份原版样本的缓冲区为 **60×3000**、可见窗口为 **60×12**;未改动的 CPR 入口没有观察到回复,而显式启用 VT 输出的独立变体得到回复并正常退出 shell。 + +四个样本各自有边界,不能合并为一次完整 TUI 通过。没有修改 TermForge 产品/测试源码,没有采用修补 MSYS DLL 或选择 WinPTY 为产品后端。#52 的原固定运行库兼容方案仍未完成。 + +## 身份、隔离与停止条件 + +- 原版:官方 WindTerm 2.7.0 Windows x64,EXE SHA256 `7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`;每次复制的 4,047 文件清单 SHA256 `e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203`,沿用[既有准备守卫](../../research/ui/launch.py)。原版 cmd 横幅记录 Windows `10.0.26200.9457`。 +- Vim:与[隔离运行库实验](vim-msys-runtime-prototype.md)相同的固定 Vim 9.1 patches 1–532,EXE SHA256 `2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a`;使用未修补 MSYS DLL,SHA256 `3096d2ba9be20e90be1399edd391bac65d84bc387f5a419b502c9b326081930f`。配置 PATH 指向冻结的原始五文件副本;本批没有观察实际加载模块路径。 +- 公开自有夹具按源码基点 `b60cd65b9fa6af724279780faa8d78847eb5cef0` 核对。`entry` 只固定文件哈希,不补造当时未记录的 HEAD;其余三份计划明确记录该提交。原 `candidate-vim.vim` 334 B,SHA256 `386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797`;原尺寸/CPR helper EXE 分别为 `a7a45db0976dd1e157871b6a9b50b1362ceb8cb8ea604470e58791920805d408` / `691b440e9c500c76c1c09c448aafbe2316793bf341ffe69bbc2bf70e41be64cc`。 +- 每次只创建一个合成本地 Shell 配置、独立 HOME/cache、非输入 window station 和自有 Job;不操作个人剪贴板、显示设置或 WSL。REA CLI 固定 6.7.0,使用既有 `instrument-with-frida attach` 入口。 +- 每个问题在启动前各声明最多一次运行,合计四次,均已停止。CLI 采集 30 s、外层监督 45 s;第一份入口观察的脚本期限为 28 s,其余为 25 s,另有晚 0.5 s 的脚本兜底。后续样本针对新的尺寸/模式问题,不重跑前一场景取绿。 + +## 四个独立样本 + +| 样本 | 实际结果 | 未覆盖或失败 | +|---|---|---| +| `entry`:入口可行性 | 实际 `winpty_open`、`winpty_spawn`、91×23 `winpty_set_size` 调用;原 Vim 写出 READY、进入插入并逐字接收标记 | Vim 由 `cmd /k` 直接启动;不含 resize、写盘、CPR、正常退出,结束由自有回收完成 | +| `vim`:原输入与真实窗口 resize | 从 cmd 提示符键入原启动命令;READY 和 `[New]` 后插入;原 93 B 命令写出 `60x12`;原 `Ctrl-O:wq` 保存并恢复 `TF>` | 原尺寸门槛未通过,脚本在 stage56 超时;CPR 和 shell exit 尚未运行。shell-handle 观察器存在一次参数类型错误,正常退出没有证据 | +| `native`:独立尺寸/CPR | 原尺寸 helper 输出 `TF_SIZE_60x3000`;独立 `GetConsoleScreenBufferInfo` 同时记录 buffer 60×3000、window `[0,0,59,11]`、viewport 60×12 | 原 `dwSize=60×12` 预期为 false。CPR 文件为空,脚本在 stage52 超时,query 进程退出码未观察;未发送 `exit 37` | +| `mode`:独立 VT 输出变体 | 读到输出模式 3;仅开启 VT 输出后模式为 7,未改动的 CPR helper 返回 0,文件为 `ESC[7;1R`;模式恢复,shell 自然退出 37 | 新增模式包装器属于不同配置,不代表前一原入口已通过;没有运行 Vim,CPR 的实际生产方未跟踪 | + +`vim` 的窗口准备先在 shell 中校准像素尺寸:1360×860/91×23 → 895×563/52×12 → 990×563/60×12,再恢复 1360×860 后启动 Vim。插入标记之后,只请求一次 990×563,观测到实际 WinPTY 60×12 调用成功及 UI capacity 12。该几何和 Qt 事件调度不同于 TermForge,不能用它比较竞态概率、输入延迟或性能。 + +## Vim 文件的独立核对 + +`vim` 样本的输入日志 **115 B** 与以下序列逐字节相同:`i`、`TF_CANDIDATE_TUI`、原 93 B resize 命令、`Ctrl-O:wq\r`。插入事件文件 **58 B** 精确符合 `INSERT`、逐字符、`LEAVE / INSERT / LEAVE`;保存文件 **17 B** 为标记加 LF,resize 文件 **5 B** 为 `60x12`。恢复主提示符来自该样本的 UI 模型观察;实际呈现端点保持 `null`。 + +该样本的输入命名管道成功匹配,但 WriteFile 探针没有记录调用;这不能解释为没有输入。完整 Vim 日志直接证明上述应用接收序列。`winpty_spawn` 的 shell-handle 观察器向 Frida 传入了 Boolean,出现 `expected an integer`,后续独立样本改为整数参数;保留该错误,不补造该样本的进程退出证据。 + +这里只观察到一次完成的 Vim 编辑过程,不能证明原版不会遇到同类竞态,也不追溯归因此前未记录输入的失败。第一份 stage56 样本没有保存实际尺寸 helper 文本;后一份独立的 60×3000 结果支持尺寸区别,不能冒充前一份缺失记录。 + +## 原生尺寸与 VT 模式的解释 + +`shell_size_client` 查询的是 `dwSize`。Windows 将它定义为整个屏幕缓冲区尺寸,`srWindow` 则描述显示窗口,两者不是同一字段。[Microsoft 字段说明](https://learn.microsoft.com/en-us/windows/console/console-screen-buffer-info-str)。本批原版的 60×3000 与 60×12 是运行观察;现有 TermForge 原生缓冲区判据仍未改变,是否调整跨后端合同需明确提案及用户确认。 + +现有 [CPR helper](../../experiments/shell/query_client.cpp)设置输入的 `ENABLE_VIRTUAL_TERMINAL_INPUT`,没有启用输出的 `ENABLE_VIRTUAL_TERMINAL_PROCESSING`。Windows 文档将后者定义为输出 VT 控制序列的解析开关,并说明输入 VT 标志通常与它配合使用。[Microsoft 模式说明](https://learn.microsoft.com/en-us/windows/console/setconsolemode)。 + +`mode` 样本实际输出模式为 **3 → 7 → 3**;输入模式为 **503 → 512 → 503**。包装器只在自有控制台设置输出标志,运行相同 helper 后恢复两种模式。helper 原 5 s `WaitForSingleObject` 保留,父进程额外提供 7 s 监督以约束可能阻塞的 `ReadConsoleW`;本次没有触发监督超时。shell 退出 37 来自复制的真实进程句柄,不是回收产生的退出码。 + +这些事实支持继续核对夹具的模式前提;**前一失败样本未记录模式,不能将本次读到的 3 回填为它的实测值,也不能据此宣称已因果修复该失败**。CPR 可能由原生控制台处理,本批没有证明回复来自 WindTerm 的末端解析器。模式变体成功与原入口 stage52 未完成分别保留。 + +## 回收、公开材料与结论边界 + +四次配置检查均只含一个合成会话;Job 最终 active 均为 0,总进程数分别为 13、14、15、10,原始 terminated 计数均为 0。外层仍明确请求了终止与回收,该计数不等于所有成员自然退出;只有 `mode` 的 shell 单独观察到自然退出 37。 + +[白名单摘要](../../research/ui/results/2026-10-27-vim-native-reference/validation.json)记录每份样本的输入/工具/原件哈希、实际调用、错误、文件检查和限制。导出重新核对四份私有记录及合成文件,未重新启动 GUI。REA provider 的 subject/environment 仍为 `null`、捕获时 state 为 `running`;包身份由本地准备守卫单独补充,不能改写成 provider 自己完成了身份认证。采集结束后的自有回收另列。 + +原件和临时原创驱动保留在 ignored `local/r52-windterm-reference`、`local/r52-windterm-vim-contract`、`local/r52-windterm-native-size`、`local/r52-windterm-cpr-mode`,每处计划/驱动/参数/观察/回收可本地复核。公开摘要不含私有驱动全文或原始插桩输出,因此不是一套公开的一键复现包;不得据此扩大重复性结论。发行物、DLL、资源、绝对路径、PID/句柄及用户配置均不进入公开材料。 + +本批补充原版同 Vim 的有限行为证据,以及原生 buffer/viewport、控制台模式的真实差别。#52/#28/#56 可据此继续调查兼容方案;原固定运行库的官方完整 CTest **56/57、402.66 s、8 项内部 skip**没有重跑,也没有被这些样本替代。#47、Draft #50、完整 R2 和产品架构选择保持各自原出口,不据此采用 WinPTY、替换用户应用运行库或更改验收门槛。 diff --git a/docs/research/workflow-record-checkpoint.md b/docs/research/workflow-record-checkpoint.md new file mode 100644 index 00000000..61ae64c5 --- /dev/null +++ b/docs/research/workflow-record-checkpoint.md @@ -0,0 +1,45 @@ +# 替换会话的完整记录检查点 + +原八会话 data-only 工作流的 `cycle-eight` 快照必须在替换会话仍持续输出时取得,并满足既有精确文字、身份及回收断言。[#87](https://github.com/JavaZeroo/termforge/issues/87) 修正的是合成测试的消费与采样协调;正常产品输出默认不启用检查点,Python oracle、生产输入和期限保持不变。 + +## 失败及因果证据 + +历史两份失败在 F5 第 127 条记录只得到 12 个点,独立 oracle 要求 35 个点;源已有 16384 B,队列还有 8217 B。原件仍可离线重现 `replacement core payload/role mismatch`。那两次没有保存后续 native 运输,不能追溯宣称已证明其完整性。 + +原 stage35 只要求 source≥4096 B 和任意编号前缀,而消费者一次最多取 8192 B。实际 READY 前缀 25 B 与两个 4096 B 源块可以合并为 8217 B;重分片回放在 8192 B 处产生相同的第 127 条半行,余下 25 B 补齐它。这证明了机制,不等于重现历史 GUI 调度。最初四次有界活体观察均未捕获半行,这些未复现结果单列保留。 + +随后用同一诊断 EXE 做受控旧判据/新判据对照:在第一轮 F5 替换输出前保持消费者,源到达 16384 B 后只放行一次真实 8192 B 消费;保存状态,再继续原工作流。没有改生产字节、4096 B 分块、20 ms 间隔、32000 条/2048000 B、五轮替换、精确 oracle 或期限。 + +- 旧判据在第 117 条中途采样,原 oracle 真红。相同 session 后续实际 39 B 补齐该条,运输前缀保持;独立候选核心回放得到完整原文字。 +- 仅被动等待空行的中间方案仍失败:持续消费跳过边界,直到生产结束才采样,原 oracle 报 `replacement output not active`。这不是可接受修复。 +- 新检查点在实际 LF 后持有快照,通过原 oracle;该检查点源为 241664 B,小于 2048000 B,`fixture_running=true`、`output_done=false`。同 session 的实际后续 41 B 补齐受控半行。两次运行各有自己的真实运输,未声称它们逐字节相同。 + +诊断记录上限为 128 KiB/1024 events。新方案的后续 collected 运输超出字节上限,但所需补齐字节在保存的前缀内;held/checkpoint 未溢出。这里只证明该前缀内的补齐,不声称完整后续运输已捕获。受控消费者调度仅用于因果实验,最终原场景回归移除诊断调度和记录。 + +## 最终机制与边界 + +`OutputRecordCheckpoint` 仅由 UI 线程显式启用。已有空行边界时立即保持;否则每次取一个真实队列字节并交给原核心,遇 LF 后保持,最多读取 256 B,并受原场景 70 秒总期限约束。两个替换会话各自就绪后才采集同一检查点,随即释放,恢复原 8192 B 消费块及 65536 B/4 ms tick 总预算。底层队列仍有界。 + +检查点不验证或修补正文:完整的 `BROKEN` 编号行也会就绪,再由原 Python oracle 拒绝。空启动、只有 READY、CR 无 LF、当前半行、只有其他角色等不会被完整记录判据接受。关闭会解除保持,关闭中或无有效 HWND 的对象拒绝重新启用,避免阻碍排空及回收。 + +`workflow_checkpoint_test` 在实际默认核心和候选 native-viewport 核心验证 8192/8217 分片、后续补齐、保持/释放及边界负例;最初红测试和最终测试保留相同触发用例,最终另增边界与状态用例,不能称整个测试源码相同。`runtime_observation` 用自有非输入 desktop 验证默认关闭、显式启用、关闭解除和关闭后拒绝重新启用。普通 cmd 回显不保证运输中有字面 LF,因此一个依赖此假设的临时关闭测试已排除;最终真实关闭测试只宣称关闭时已 armed,未宣称它必为 held。 + +## 正式验证与复现 + +固定源码、EXE/依赖哈希、旧红、新绿、完整回归、内部 skip 和回收结果见[公开白名单记录](../../experiments/shell/results/2026-10-27-workflow-record-checkpoint/validation.json)。原运输、窗口路径和 desktop 身份保留本地。最终原完整八会话测试包含两个新进程、每次五轮替换;它与受控诊断运行分别记录。 + +最终源码 `c662011`、候选 `.5`/固定官方后端 flags24 的完整 CTest 为 **56/57,402.66 秒**,两轮原八会话均通过,Job active=0、desktop 已回收。唯一失败为 Vim stage59,仍由 #52 定位;8 项内部 skip 逐项列出,不算真实 CF/IME 通过。关闭 guard 的实际新用例先红后绿;红例未独立保留 EXE 哈希,该字段明确为 null,源码、相同测试、构建日志和失败日志仍在。更早 `f89b0ac` 的 57/57 只属于关闭 guard 前版本,不能拼入最终源码,也不能解释 Vim 历史失败。候选 CI 另增加该跨平台核心回归;CI 的 system 后端与本机官方配置分列。 + +在依赖和全部 helper 已构建的候选/显式官方配置运行: + +```powershell +ctest --test-dir build/candidate -C Release -R '^(workflow_checkpoint|runtime_observation)$' --output-on-failure +$env:TERMFORGE_BIN_DIR = "$PWD/build/candidate/bin/Release" +$env:TERMFORGE_EXPECT_CONPTY_BACKEND = 'reviewed-side-by-side-1.25.260930003' +$env:TERMFORGE_TEST_ARTIFACTS = "$PWD/local/workflow-record-check-new" +$env:PYTHONUTF8 = '1' +python -m unittest experiments.shell.test_workflow_sessions.WorkflowSessionsTests.test_two_fresh_owned_eight_session_rounds -v +ctest --test-dir build/candidate -C Release --output-on-failure +``` + +完整回归还需固定 Vim 在当前进程 PATH 中。官方后端、system 后端、默认核心和候选核心分别记录身份,不能互相借用结论。data-only 通过不代表真实 CF/IME、实际呈现、性能或完整 R2;#52、#63 及 #47 的其他门槛仍独立处理。 diff --git a/docs/research/workspace-native-viewport.md b/docs/research/workspace-native-viewport.md new file mode 100644 index 00000000..4c4de557 --- /dev/null +++ b/docs/research/workspace-native-viewport.md @@ -0,0 +1,35 @@ +# 工作区尺寸标记残留与原生 viewport + +[#63](https://github.com/JavaZeroo/termforge/issues/63) 的工作区 `stage52` 与主区场景 `stage7`,在保留原布局、输入、期限和精确断言的对照中,均指向底部会话的历史重排:原生尺寸标记已经正确,候选屏幕却在标记后保留旧文字。现有 [#51 的 `native_viewport` 修复](native-viewport-resize.md)覆盖这两条已复现路径;本次补充因果核验与原回归,不增加产品改动。 + +## 固定身份与原失败 + +旧源码 `188228e`、候选 `.4`、官方 ConPTY `1.25.260930003`、Console flags24 的未插桩 EXE 为 `e81d92a8c7d380d4c33639861180b04935e021bc7c1a5179197c8a9e3c83454a`。原 UI 和主区测试各运行一次,每项三轮均为前两轮通过、第三轮窄布局失败,分别停在 `stage52/client=2048x1120` 和 `stage7`。仅保留单次原窄布局 UI 场景也复现同一失败。 + +UI 内层20秒、主区内层30秒、每轮外层40秒保持。原三轮方法和两份场景源码与新联合源一致。旧二进制在本批前后哈希一致;旧失败原件保留,不用随后成功覆盖。更早 `3e5b96d` 等缺少对应运输的历史样本不追溯认定同因。 + +## 单变量对照与直接观察 + +在 `d6b3f79` 的联合源上临时增加有界 resize/feed、核心/可见快照和尺寸 helper 原生屏幕观察。每对运行使用同一个 EXE,只切换已有 `reflow_history` / `native_viewport` 策略;不清屏、不补发标记、不改变输入或等待条件。UI 与主区分别预设最多两轮,先运行旧策略;若不是原失败就停止,否则执行新策略一次,不重试取绿。 + +| 场景与会话 | 实际子进程尺寸与原生标记 | 旧策略核心 | 新策略核心及原场景 | +|---|---|---|---| +| UI 底部会话 | `169×11`,整行 `TF_SIZE_169x11` | 同一行带旧 helper 路径残尾,stage52 | 整行精确匹配,完整场景通过 | +| 主区场景的底部会话 | `169×11`,整行 `TF_SIZE_169x11` | 同样残尾,stage7 | 整行精确匹配,完整场景通过 | +| 主区场景的活动主会话 | `112×24`,整行 `TF_SIZE_112x24` | 精确匹配 | 精确匹配 | + +失败时底部 resize 请求/完成 generation 分别为 UI `6/6`、主区场景 `4/4`,没有在途任务或输入拒绝。尺寸 helper 实际查询、完整写出标记;同次 transport 含完整标记,核心与可见快照一致。因此捕获红例不符合“子进程尺寸尚未应用”或“查询/输出未送达”;主区的等待失败由底部条件触发。 + +用保存的 feed 分块和 parser resize 顺序离线回放,旧策略逐字段重现同次失败核心,新策略得到精确标记;活动主会话在两策略下均正确。这将实际运输和调度之外的策略差异独立出来。原因与 #51 一致:通用 reflow 将历史拉回可见区,与原生控制台绝对坐标布局分歧,后续局部写入留下旧行残尾。 + +UI 与主区各一红一绿的探针 EXE、helper、源码补丁、每轮原件和全部回放保存在本地;公开[白名单记录与哈希](../../experiments/shell/results/2026-10-27-workspace-native-viewport/validation.json)。resize观察上限1024事件/128KiB运输,helper最多读取100000格,各轮未截断。观测读写会扰动调度,离线回放不证明时序或性能等价。原生快照采于 helper 写出后,不等于后续窗口提交或实际呈现。 + +## 无插桩验收与边界 + +临时 C++ 按基点字节恢复后,以 `dd533878538490c2464673218ea0000c6467a495` 重建;产品源码与 `c662011` 相同。实际 shell EXE 为 `7f26395942f107297565f50402568650e1e17c5eb23924bee1bfb02c50c60d16`,与诊断 EXE 分列。原 UI 和主区三轮方法重复两批,共12次运行全部通过,包含窄布局、像素/几何负例、会话隔离、真实尺寸、焦点/关闭和 Job/desktop 回收。 + +同一构建完整 `shell` CTest 在原180秒期限内用时93.62秒完成:40项中37通过、3项按原 hosted clipboard guard 跳过。完整套件内两项工作区方法又各执行原三轮;共18份相关工作区样本均无外层超时,Job active0/terminated0、desktop已回收。其他负例保留原断言,由完整日志单列,不把预期失败记录写成正常运行成功。 + +回归入口为原 `ShellTests.test_workspace_sessions_resize_and_cleanup`、`test_main_and_bottom_sessions_focus_geometry_and_cleanup` 及 `ctest --test-dir build/candidate -C Release -R '^shell$' -V`。现有 native viewport 核心/原生合成回归继续承担该策略的防退化检查。 + +本次只闭合 #63 已复现工作区路径及完整 shell 期限;#52 GUI Vim、完整官方配置与 R2 仍未验收。历史缺少原生/运输的记录、默认/system、候选/system、实际呈现和 WindTerm 对照保持独立,不由本次推断。 diff --git a/docs/research/workspace-restore-transport-oracle.md b/docs/research/workspace-restore-transport-oracle.md index 7aeb7cfd..a06aafc9 100644 --- a/docs/research/workspace-restore-transport-oracle.md +++ b/docs/research/workspace-restore-transport-oracle.md @@ -10,16 +10,23 @@ [#61](https://github.com/JavaZeroo/termforge/issues/61) 的两份默认/system 新失败具有不同原因:PowerShell 命令编辑重绘在实际输出之前带一个裸 BS(`0x08`)。旧 oracle 因它直接拒绝整个运输,即使随后已有真正 CRLF、完整目录/版本输出和新提示符。用原字节独立重放,两个 PowerShell 条件均为 false,而 cmd/底部条件为 true;仅移除前缀 BS 的诊断输入使旧条件转为 true,证实拒绝分支。修正不模拟退格或删除回显,而是把该前缀记录标记为不可信,直到真正 LF 才允许后续独立输出;[限定复验](../../experiments/shell/results/2026-10-26-workspace-restore-bs/validation.json)保留原始失败及各配置身份。这是测试完成端点修正,不是产品输出丢失或恢复功能缺陷的修复。 +[#72](https://github.com/JavaZeroo/termforge/issues/72) 的两次默认/system CI 保存 stage2 失败记录逐字节相同。PowerShell 的 122 B 原运输在真实 LF 后、完整输出之前含 `CSI 63X / CSI 63C / CR / CSI ?25h`;旧 helper 对空记录的裸 CR 永久拒绝。保留完整原字节的 C++ 回归先红后绿,仅移除这一 CR 的诊断对照在旧 helper 下已通过。修正只允许下述受限空记录情形,不删除运输字节或改变产品输出。源码 `4daf992` 下默认/system、候选/system、候选/官方 flags24 各两轮保存、普通恢复、钳制恢复共 18 次真实运行通过;[身份与结果](../../experiments/shell/results/2026-10-27-workspace-empty-cr/validation.json)分别保留因果回归、原始失败及各配置的实际断言与回收。局部通过不替代最终完整回归或 CI。 + +同源码官方配置完整 CTest 为 54/56、449.38 秒;工作区保存/恢复方法也在完整 shell 套件中通过。剩余为 Vim stage59,以及 shell 的 resize/resize-close stage43、main stage7、workspace stage52,分别由 #51/#52/#63 承接。此次 shell 套件实际完成 40 方法,4 error、3 skip,用时 174.65 秒;旧批次的 180 秒聚合超时仍保留。内部可选依赖、hosted 系统输入和私有剪贴板跳过逐项记录,不写成这些能力通过。PR #78 的两条 CI 均通过,其中 Windows 默认/system 为 51/51、273.64 秒;其实际合并 checkout 与本机源码分别列明。 + ## 限定 oracle 合同 +[#88](https://github.com/JavaZeroo/termforge/issues/88) 的官方后端第二轮普通恢复 stage1 原件中,主 cmd 的 262 B 运输包含命令回显的裸 CR/CUP,随后已有独立目录输出和新提示符。相同 helper 重放四个谓词,仅主 cmd 为 false;只移除该回显 CR 后为 true,再去掉真实输出行仍为 false。修正将这种已存在文字的回显记录保持为不可信,等待真实 LF 后重新验证独立输出;不模拟 CR/CUP,也不删除或拼接回显文字。原完整回归失败保留在 [#51 固定记录](../../experiments/conpty/results/2026-10-27-native-viewport-resize/validation.json)。[修正验证](../../experiments/shell/results/2026-10-27-workspace-echo-cr/validation.json)保留相同触发断言的红绿、最终补充的四个用例,以及构建完成后正式执行的原两轮保存/恢复/钳制;六次运行和完整 shell 方法通过。最终官方配置 CTest 55/56、398.08 秒,Vim stage59 仍归 #52;新构建的 trace 使四项可选差分检查实际执行,剩余 8 项内部 skip 单列。构建重叠的早期运行已排除,旧 #63/#87 失败未因本轮未复现而关闭。 + - 独立扫描 7-bit ESC 形式的完整 OSC(BEL 或 ST 终止)和 CSI。OSC 内容不参与正文匹配,UTF-8 的所有高位正文 bytes 原样保留,包括 `目` 中的 `0x9b`。 - 标记必须匹配完整正文记录,前后不可附加命令、其它正文或任意子串。LF 结束记录;显式正行号、第 1 列 CUP/HVP 仅可结束完整标记或提示符,不能清除命令前缀或拼接分散的标记片段。其它非空内容遇 CUP 保持未知,直到真实 LF;SGR 和其它 CSI 不会凭空结束标记。 -- 在首次匹配输出之前,命令编辑中的 BS 只会污染当前记录;CUP 不能解除该污染,也不把退格两侧文字拼起来。真实 LF 后可验证新的完整记录。已经匹配输出后的 BS 仍拒绝,不能借编辑过的提示符证明命令完成;其它裸控制字符的拒绝保持不变。 +- 在首次匹配输出之前,命令编辑中的 BS 只会污染当前记录;CUP 不能解除该污染,也不把退格两侧文字拼起来。真实 LF 后可验证新的完整记录。已经匹配输出后的 BS 仍拒绝,不能借编辑过的提示符证明命令完成。 +- CRLF 的 CR 不改变正文。在首次匹配输出之前,非空或已被编辑的记录遇裸 CR 会保持不可信,直到真实 LF;CUP 不能解除该状态或将其中的标记算作输出。已观察到记录边界、当前为空且未受编辑的记录可以保持为空。裸 CR 不清除文字、不结束记录,也不替代之后的完整标记和新提示符;初始空记录和已匹配输出后的裸 CR,以及其它未知裸控制字符仍拒绝。 - 该记录之后必须出现独立的 `TF>` 或 `TF> ` 记录,才证明 shell 返回;输入命令回显、标题内标记、旧提示符和输出行前缀都不满足条件。 - 完整 CSI 仅按语法封装剥离,不解释或验收其执行语义;其中受限 column-one CUP 的记录边界另按上条处理。不完整或格式错误的封装、未知 ESC 引导形式、DCS 等其它控制字符串保持未证明状态。这只覆盖固定持久化命令已输出和返回提示符的运输证据,不扩展为第二个通用 VT 核心;产品屏幕及恢复断言仍独立保留。 - 保留原有 8192-byte 观测上限、30 秒总期限、真实目录/版本、返回提示符、恢复成员/标题/缩放/分屏/几何及尺寸检查和真实 Job/desktop 回收断言。 -`transport_completion_test` 独立覆盖标题重复遮蔽、BEL/ST、SGR、CUP、UTF-8、命令编辑前缀 BS,以及命令回显、BS 拼接/提示符编辑、OSC/DCS payload、不完整控制、缺失或旧提示符等反例。它修正的是测试 oracle,不构成产品恢复故障修复或完整 R2 验收。 +`transport_completion_test` 独立覆盖标题重复遮蔽、BEL/ST、SGR、CUP、UTF-8、命令编辑前缀 BS、空记录裸 CR、窄列回显裸 CR,以及命令回显、CR/BS 拼接或提示符编辑、OSC/DCS payload、不完整控制、缺失或旧提示符等反例。它修正的是测试 oracle,不构成产品恢复故障修复或完整 R2 验收。 ```powershell ctest --test-dir build/default -C Release -R '^transport_completion$' --output-on-failure diff --git a/experiments/candidates/alacritty/Cargo.lock b/experiments/candidates/alacritty/Cargo.lock index 6501189a..7a002fd0 100644 --- a/experiments/candidates/alacritty/Cargo.lock +++ b/experiments/candidates/alacritty/Cargo.lock @@ -13,7 +13,7 @@ dependencies = [ [[package]] name = "alacritty_terminal" -version = "0.26.1-dev+termforge.4" +version = "0.26.1-dev+termforge.5" dependencies = [ "arrayvec", "base64", diff --git a/experiments/candidates/alacritty/PROVENANCE.md b/experiments/candidates/alacritty/PROVENANCE.md index 3470237b..ef53938f 100644 --- a/experiments/candidates/alacritty/PROVENANCE.md +++ b/experiments/candidates/alacritty/PROVENANCE.md @@ -1,6 +1,6 @@ # 固定候选来源与本地补丁 -当前适配版本为 `alacritty_terminal 0.26.1-dev+termforge.4`,不是 crates.io 原始 `0.26.0`。 +当前适配版本为 `alacritty_terminal 0.26.1-dev+termforge.5`,不是 crates.io 原始 `0.26.0`。 ## 上游身份 @@ -53,6 +53,15 @@ 完整控制/宽度/资源和字体策略见[流式字素合同](../../../docs/research/streaming-graphemes.md)。固定 WindTerm 的运行时字素/宽度仍未知。 +## termforge.5 补丁 + +1. `Term::resize_native_viewport` 与 Grid 的显式策略:增高在可见区底部补空行;增宽合并可见行时移动光标并保留底行;历史和原生可见区分别 reflow,不把历史拉进原生控制台的坐标。 +2. `src/grid/row.rs` 显式标记本策略产生的边界填充,clone/整行移动保留,mutable cell access、reset、拆分/合并和尺寸改变时失效;不从普通 `wrap_columns` 推断填充。保留历史跨边界的软换行及实际文本跨度,后续 reflow 只移除该标记指明的默认填充。C ABI 同步返回填充的未写格/空文本,并保留原文本跨度中的实际尾空格。 +3. 原 `Term::resize`、C ABI 和 C++ 默认策略继续采用 `reflow_history`。只有显式官方后端候选 `ShellWindow` 选择 `native_viewport`;新增 ABI 而不改变原函数签名。未新增第三方依赖。 +4. 独立原生 `ReadConsoleOutputW` 的六组合成样本、公开适配器回归与原 GUI 反馈见[原生 resize 对照](../../../docs/research/native-viewport-resize.md)。修改为本项目依据观察独立编写,未复制 WindTerm 或 Microsoft 私有代码;既有上游 Apache-2.0 归属保留。 + +这是固定后端的兼容实验,不是 WindTerm resize 等价或最终产品策略确认;#29/#34 继续承担完整合同及选型。 + ## 历史基线 #24–#26 的原始候选为 crates.io `0.26.0`,crate SHA-256 `bda177466b9524d59f1b12f0dd30b68696788e9992a7e959021c4a0ed96fcf59`,对应上游 commit `94e7c8874e526b1e67b349d9ba30ddf81669119e`。当时的 90 match + 5 reference_difference、project/interactive 计量和平台/维护审查保持原批次身份,不改写为 termforge.1/2 结果。 diff --git a/experiments/candidates/alacritty/README.md b/experiments/candidates/alacritty/README.md index c6c711a9..a4021b17 100644 --- a/experiments/candidates/alacritty/README.md +++ b/experiments/candidates/alacritty/README.md @@ -6,8 +6,8 @@ | 项目 | 当前固定值 | 来源 | |---|---|---| -| `alacritty_terminal` | `0.26.1-dev+termforge.4`,Apache-2.0 | [上游 commit `5421470`](https://github.com/alacritty/alacritty/commit/542147067dd016fc5f49120ef0ba605a98a4e3a7)与[来源/补丁台账](PROVENANCE.md) | -| 本地补丁 | ED1/ED2、稳定 Row ID/换行元数据/淘汰、安全完整 Row swap、每缓冲区滚动区域、流式字素与明确剪裁 | `vendor/alacritty_terminal/src/`;所有文件哈希进入结果 manifest | +| `alacritty_terminal` | `0.26.1-dev+termforge.5`,Apache-2.0 | [上游 commit `5421470`](https://github.com/alacritty/alacritty/commit/542147067dd016fc5f49120ef0ba605a98a4e3a7)与[来源/补丁台账](PROVENANCE.md) | +| 本地补丁 | ED1/ED2、稳定 Row ID/换行元数据/淘汰、安全完整 Row swap、每缓冲区滚动区域、流式字素与明确剪裁、显式原生 viewport resize | `vendor/alacritty_terminal/src/`;所有文件哈希进入结果 manifest | | 工具链 | Rust/cargo 1.92.0 | 本机与候选 CI 显式选择;当前锁定图最高声明 Rust 1.85 | | 传递依赖 | 59 个注册表包 + 根适配器与固定 vendor 两个 path crate,共 61 个包 | `Cargo.lock` 与 `cargo metadata --locked --format-version 1` | diff --git a/experiments/candidates/alacritty/include/termforge_candidate.h b/experiments/candidates/alacritty/include/termforge_candidate.h index 9ab501e9..70e6a421 100644 --- a/experiments/candidates/alacritty/include/termforge_candidate.h +++ b/experiments/candidates/alacritty/include/termforge_candidate.h @@ -38,6 +38,7 @@ void tf_terminal_free(void* terminal); int tf_terminal_feed(void* terminal, const uint8_t* data, size_t len, uint8_t* error, size_t error_capacity); int tf_terminal_finish(void* terminal, uint8_t* error, size_t error_capacity); int tf_terminal_resize(void* terminal, size_t rows, size_t columns, uint8_t* error, size_t error_capacity); +int tf_terminal_resize_native_viewport(void* terminal, size_t rows, size_t columns, uint8_t* error, size_t error_capacity); int tf_terminal_meta(void* terminal, TfTerminalMeta* output); int tf_terminal_row_meta(void* terminal, size_t index, TfRowMeta* output); int tf_terminal_cell_meta(void* terminal, size_t index, size_t column, TfCellMeta* output); diff --git a/experiments/candidates/alacritty/run.py b/experiments/candidates/alacritty/run.py index 7b7c609e..25bf3584 100644 --- a/experiments/candidates/alacritty/run.py +++ b/experiments/candidates/alacritty/run.py @@ -11,7 +11,7 @@ ROOT = Path(__file__).resolve().parents[3] HERE = Path(__file__).resolve().parent -VERSION = "0.26.1-dev+termforge.4" +VERSION = "0.26.1-dev+termforge.5" UPSTREAM_COMMIT = "542147067dd016fc5f49120ef0ba605a98a4e3a7" BASE_CRATE_SHA256 = "bda177466b9524d59f1b12f0dd30b68696788e9992a7e959021c4a0ed96fcf59" diff --git a/experiments/candidates/alacritty/src/ffi.rs b/experiments/candidates/alacritty/src/ffi.rs index 4f56d33f..1ae5ce4f 100644 --- a/experiments/candidates/alacritty/src/ffi.rs +++ b/experiments/candidates/alacritty/src/ffi.rs @@ -177,17 +177,39 @@ pub unsafe extern "C" fn tf_terminal_resize( columns: usize, error: *mut c_uchar, error_capacity: usize, +) -> c_int { + unsafe { resize_terminal(value, rows, columns, false, error, error_capacity) } +} + +unsafe fn resize_terminal( + value: *mut c_void, rows: usize, columns: usize, preserve_viewport: bool, + error: *mut c_uchar, error_capacity: usize, ) -> c_int { guard(error, error_capacity, || { if rows == 0 || rows > 200 || columns == 0 || columns > 500 { return Err("invalid candidate dimensions".into()); } let value = unsafe { terminal(value)? }; - value.term.resize(Size { columns, rows }); + if preserve_viewport { + value.term.resize_native_viewport(Size { columns, rows }); + } else { + value.term.resize(Size { columns, rows }); + } drain_events(value) }) } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tf_terminal_resize_native_viewport( + value: *mut c_void, + rows: usize, + columns: usize, + error: *mut c_uchar, + error_capacity: usize, +) -> c_int { + unsafe { resize_terminal(value, rows, columns, true, error, error_capacity) } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn tf_terminal_meta(value: *mut c_void, output: *mut TerminalMeta) -> c_int { if output.is_null() { @@ -253,6 +275,28 @@ fn cell_text(cell: &Cell) -> String { text } +fn wrap_padding(row: &alacritty_terminal::grid::Row, column: usize) -> bool { + // A short retained continuation can need physical padding to keep the + // native viewport boundary. Its wrap marker does not turn that padding + // into a written space. Other cell contents or attributes remain visible. + row.native_wrap_padding() && row.wrap_columns() > 0 && row.wrap_columns() < row.len() && column >= row.wrap_columns() + && row.last().is_some_and(|cell| cell.flags.contains(Flags::WRAPLINE)) + && row[Column(column)] == Cell { flags: Flags::WRAPLINE, ..Cell::default() } +} + +fn row_cell_text(row: &alacritty_terminal::grid::Row, column: usize) -> String { + if wrap_padding(row, column) { + return String::new(); + } + let cell = &row[Column(column)]; + // Moving a wrap marker onto padding must not make the original trailing + // spaces unwritten. They still belong to the retained logical span. + if row.native_wrap_padding() && column < row.wrap_columns() && cell.is_empty() { + return " ".into(); + } + cell_text(cell) +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn tf_terminal_cell_meta( value: *mut c_void, @@ -270,7 +314,8 @@ pub unsafe extern "C" fn tf_terminal_cell_meta( } let cell = &row[Column(column)]; let _ = line; - let text = cell_text(cell); + let padding = wrap_padding(row, column); + let text = row_cell_text(row, column); let flags = cell.flags(); let result = CellMeta { width: if flags.contains(Flags::WIDE_CHAR) { @@ -284,8 +329,8 @@ pub unsafe extern "C" fn tf_terminal_cell_meta( bg: color(cell.bg), bold: flags.contains(Flags::BOLD), marks: cell.zerowidth().map_or(0, |marks| marks.len()), - written: !cell.is_empty() || !text.is_empty() || - flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER), + written: !padding && (!cell.is_empty() || !text.is_empty() || + flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER)), text_len: text.len(), clipped: flags.contains(Flags::CLIPPED_GRAPHEME), grapheme_fragment: cell.grapheme_fragment(), @@ -312,7 +357,7 @@ pub unsafe extern "C" fn tf_terminal_cell_text( if column >= value.term.grid().columns() { return 1; } - let text = cell_text(&row[Column(column)]); + let text = row_cell_text(row, column); unsafe { *needed = text.len() }; if capacity < text.len() || (output.is_null() && !text.is_empty()) { return 1; diff --git a/experiments/candidates/alacritty/test_candidate.py b/experiments/candidates/alacritty/test_candidate.py index 096f8e4c..8e3f56a9 100644 --- a/experiments/candidates/alacritty/test_candidate.py +++ b/experiments/candidates/alacritty/test_candidate.py @@ -32,7 +32,7 @@ def test_fixed_streaming_grapheme_samples(self): self.assertFalse(summary["performance_measured"]) def test_identity_and_limitations_are_recorded(self): - self.assertEqual(self.summary["version"], "0.26.1-dev+termforge.4") + self.assertEqual(self.summary["version"], "0.26.1-dev+termforge.5") self.assertEqual(self.summary["license"], "Apache-2.0") self.assertEqual(self.summary["upstream_commit"], "542147067dd016fc5f49120ef0ba605a98a4e3a7") self.assertEqual(len(self.summary["trace_sha256"]), 64) diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml b/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml index 58192a19..0755589b 100644 --- a/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alacritty_terminal" -version = "0.26.1-dev+termforge.4" +version = "0.26.1-dev+termforge.5" authors = ["Christian Duerr ", "Joe Wilm "] license = "Apache-2.0" description = "Library for writing terminal emulators" diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs index aa70c4ec..e1811d9a 100644 --- a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs @@ -15,19 +15,28 @@ impl Grid { where T: ResetDiscriminant, D: PartialEq, + { + self.resize_with_viewport(reflow, lines, columns, false); + } + + pub(crate) fn resize_with_viewport( + &mut self, reflow: bool, lines: usize, columns: usize, preserve_viewport: bool, + ) where + T: ResetDiscriminant, + D: PartialEq, { // Use empty template cell for resetting cells due to resize. let template = mem::take(&mut self.cursor.template); match self.lines.cmp(&lines) { - Ordering::Less => self.grow_lines(lines), + Ordering::Less => self.grow_lines(lines, preserve_viewport), Ordering::Greater => self.shrink_lines(lines), Ordering::Equal => (), } match self.columns.cmp(&columns) { - Ordering::Less => self.grow_columns(reflow, columns), - Ordering::Greater => self.shrink_columns(reflow, columns), + Ordering::Less => self.grow_columns(reflow, columns, preserve_viewport), + Ordering::Greater => self.shrink_columns(reflow, columns, preserve_viewport), Ordering::Equal => (), } @@ -40,11 +49,30 @@ impl Grid { /// Alacritty keeps the cursor at the bottom of the terminal as long as there /// is scrollback available. Once scrollback is exhausted, new lines are /// simply added to the bottom of the screen. - fn grow_lines(&mut self, target: usize) + fn grow_lines(&mut self, target: usize, preserve_viewport: bool) where T: ResetDiscriminant, D: PartialEq, { + if preserve_viewport { + let original_lines = self.lines; + let history_size = self.history_size(); + let added = target - original_lines; + self.raw.grow_visible_lines(target); + self.lines = target; + + // Storage growth exposes preceding history at the top. Move every + // retained row back to its original logical coordinate and append + // empty rows below the viewport without scrolling or eviction. + for row in (-(history_size as i32)..original_lines as i32).map(Line) { + self.raw.swap(row, row + added); + } + for row in (original_lines as i32..target as i32).map(Line) { + self.raw[row].renew_identity(); + self.raw[row].reset(&self.cursor.template); + } + return; + } let lines_added = target - self.lines; // Need to resize before updating buffer. @@ -97,8 +125,27 @@ impl Grid { self.lines = target; } + /// Remove only padding introduced at a retained/native boundary before a + /// later reflow. A cell with actual text or attributes prevents removal. + fn trim_reflow_padding(row: &mut Row) { + let used = row.wrap_columns(); + if !row.native_wrap_padding() || used == 0 || used >= row.len() { + return; + } + let empty = T::default(); + let mut marker = T::default(); + marker.flags_mut().insert(Flags::WRAPLINE); + let last = row.len() - 1; + if row[Column(last)] != marker || (used..last).any(|column| row[Column(column)] != empty) { + return; + } + row[Column(last)].flags_mut().remove(Flags::WRAPLINE); + row.shrink(used); + row[Column(used - 1)].flags_mut().insert(Flags::WRAPLINE); + } + /// Grow number of columns in each row, reflowing if necessary. - fn grow_columns(&mut self, reflow: bool, columns: usize) { + fn grow_columns(&mut self, reflow: bool, columns: usize, preserve_viewport: bool) { // Check if a row needs to be wrapped. let should_reflow = |row: &Row| -> bool { let len = Column(row.len()); @@ -109,6 +156,7 @@ impl Grid { let mut reversed: Vec> = Vec::with_capacity(self.raw.len()); let mut cursor_line_delta = 0; + let mut removed_visible = 0; // Remove the linewrap special case, by moving the cursor outside of the grid. if self.cursor.input_needs_wrap && reflow { @@ -119,9 +167,26 @@ impl Grid { let mut rows = self.raw.take_all(); for (i, mut row) in rows.drain(..).enumerate().rev() { + if preserve_viewport && i >= self.lines { + Self::trim_reflow_padding(&mut row); + } + // A native console cannot reflow text that only the terminal's + // scrollback retains. Preserve the continuation without merging + // the first visible row into its historical predecessor. + if preserve_viewport && i + 1 == self.lines { + if let Some(previous) = reversed.last_mut().filter(|row| should_reflow(row)) { + if let Some(cell) = previous.last_mut() { + cell.flags_mut().remove(Flags::WRAPLINE); + } + previous.set_wrap_columns(previous.len()); + previous.grow(columns); + previous[Column(columns - 1)].flags_mut().insert(Flags::WRAPLINE); + previous.mark_native_wrap_padding(); + } + } // Check if reflowing should be performed. let last_row = match reversed.last_mut() { - Some(last_row) if should_reflow(last_row) => last_row, + Some(last_row) if (!preserve_viewport || i + 1 != self.lines) && should_reflow(last_row) => last_row, _ => { reversed.push(row); continue; @@ -163,6 +228,9 @@ impl Grid { // Add removed cells to previous row and reflow content. last_row.append(&mut cells); + if preserve_viewport { + last_row.set_wrap_columns(last_row.len()); + } let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1; @@ -182,11 +250,17 @@ impl Grid { let line_delta = self.cursor.point.line - target.line; if line_delta != 0 && row.is_clear() { + if preserve_viewport && i < self.lines { + removed_visible += 1; + } continue; } cursor_line_delta += line_delta.0 as usize; } else if row.is_clear() { + if preserve_viewport && i < self.lines { + removed_visible += 1; + } if i < self.display_offset { // Since we removed a line, rotate down the viewport. self.display_offset = self.display_offset.saturating_sub(1); @@ -209,6 +283,13 @@ impl Grid { reversed.push(row); } + // Merged visible rows become empty rows below the viewport. They do + // not create space into which historical rows may be pulled. + if removed_visible != 0 { + reversed.resize_with(reversed.len() + removed_visible, || Row::new(columns)); + self.cursor.point.line = max(self.cursor.point.line - removed_visible, Line(0)); + } + // Make sure we have at least the viewport filled. if reversed.len() < self.lines { let delta = (self.lines - reversed.len()) as i32; @@ -219,11 +300,17 @@ impl Grid { // Pull content down to put cursor in correct position, or move cursor up if there's no // more lines to delete below the cursor. if cursor_line_delta != 0 { - let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1; - let available = min(cursor_buffer_line, reversed.len() - self.lines); - let overflow = cursor_line_delta.saturating_sub(available); - reversed.truncate(reversed.len() + overflow - cursor_line_delta); - self.cursor.point.line = max(self.cursor.point.line - overflow, Line(0)); + if preserve_viewport { + // A partial merge can move the cursor while both rows remain. + // No row below the cursor may be discarded to hold it still. + self.cursor.point.line = max(self.cursor.point.line - cursor_line_delta, Line(0)); + } else { + let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1; + let available = min(cursor_buffer_line, reversed.len() - self.lines); + let overflow = cursor_line_delta.saturating_sub(available); + reversed.truncate(reversed.len() + overflow - cursor_line_delta); + self.cursor.point.line = max(self.cursor.point.line - overflow, Line(0)); + } } // Reverse iterator and fill all rows that are still too short. @@ -242,7 +329,7 @@ impl Grid { } /// Shrink number of columns in each row, reflowing if necessary. - fn shrink_columns(&mut self, reflow: bool, columns: usize) { + fn shrink_columns(&mut self, reflow: bool, columns: usize, preserve_viewport: bool) { self.columns = columns; // Remove the linewrap special case, by moving the cursor outside of the grid. @@ -256,6 +343,9 @@ impl Grid { let mut rows = self.raw.take_all(); for (i, mut row) in rows.drain(..).enumerate().rev() { + if preserve_viewport && i >= self.lines { + Self::trim_reflow_padding(&mut row); + } // Append lines left over from the previous row. if let Some(buffered) = buffered.take() { // Add a column for every cell added before the cursor, if it goes beyond the new @@ -287,6 +377,19 @@ impl Grid { Vec::new() } else { // Since it fits, just push the existing line without any reflow. + if preserve_viewport && row.len() < columns { + let used = row.len(); + let wraps = row.last().is_some_and(|cell| cell.flags().contains(Flags::WRAPLINE)); + if wraps { + row[Column(used - 1)].flags_mut().remove(Flags::WRAPLINE); + } + row.grow(columns); + if wraps { + row[Column(columns - 1)].flags_mut().insert(Flags::WRAPLINE); + row.set_wrap_columns(used); + row.mark_native_wrap_padding(); + } + } new_raw.push(row); break; } @@ -344,11 +447,13 @@ impl Grid { cell.flags_mut().insert(Flags::WRAPLINE); } + let viewport_boundary = preserve_viewport && i == self.lines && wrapped.last() + .is_some_and(|cell| cell.flags().contains(Flags::WRAPLINE)); if wrapped .last() .map(|c| c.flags().contains(Flags::WRAPLINE) && i >= 1) .unwrap_or(false) - && wrapped.len() < columns + && wrapped.len() < columns && !viewport_boundary { // Make sure previous wrap flag doesn't linger around. if let Some(cell) = wrapped.last_mut() { @@ -376,10 +481,24 @@ impl Grid { // Make sure new row is at least as long as new width. let occ = wrapped.len(); + if viewport_boundary && occ < columns { + if let Some(cell) = wrapped.last_mut() { + cell.flags_mut().remove(Flags::WRAPLINE); + } + } if occ < columns { wrapped.resize_with(columns, T::default); } + if viewport_boundary && occ < columns { + wrapped[columns - 1].flags_mut().insert(Flags::WRAPLINE); + } row = Row::from_vec(wrapped, occ); + if viewport_boundary && occ < columns { + // Padding keeps physical columns aligned with the + // native viewport, but is not part of the logical line. + row.set_wrap_columns(occ); + row.mark_native_wrap_padding(); + } if i < self.display_offset { // Since we added a new line, rotate up the viewport. diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/row.rs b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/row.rs index d0e3fbdb..ab304efe 100644 --- a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/row.rs +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/row.rs @@ -37,6 +37,10 @@ pub struct Row { hard_break: bool, #[cfg_attr(feature = "serde", serde(skip))] wrap_columns: usize, + /// Explicit padding at a retained/native viewport boundary. Any mutable + /// cell access invalidates it; ordinary wrap_columns never implies padding. + #[cfg_attr(feature = "serde", serde(skip))] + native_wrap_padding: bool, } impl Default for Row { @@ -55,7 +59,7 @@ impl Row { /// Create a new terminal row. pub fn new(columns: usize) -> Row { let inner = iter::repeat_with(T::default).take(columns).collect(); - Row { inner, occ: 0, id: next_row_id(), hard_break: false, wrap_columns: 0 } + Row { inner, occ: 0, id: next_row_id(), hard_break: false, wrap_columns: 0, native_wrap_padding: false } } /// Increase the number of columns in the row. @@ -65,6 +69,7 @@ impl Row { return; } + self.native_wrap_padding = false; self.inner.resize_with(columns, T::default); } @@ -79,6 +84,7 @@ impl Row { return None; } + self.native_wrap_padding = false; // Split off cells for a new row. let mut new_row = self.inner.split_off(columns); let index = new_row.iter().rposition(|c| !c.is_empty()).map_or(0, |i| i + 1); @@ -112,6 +118,7 @@ impl Row { self.occ = 0; self.hard_break = false; self.wrap_columns = 0; + self.native_wrap_padding = false; } } @@ -119,7 +126,7 @@ impl Row { impl Row { #[inline] pub fn from_vec(vec: Vec, occ: usize) -> Row { - Row { inner: vec, occ, id: next_row_id(), hard_break: false, wrap_columns: 0 } + Row { inner: vec, occ, id: next_row_id(), hard_break: false, wrap_columns: 0, native_wrap_padding: false } } #[inline] @@ -137,6 +144,16 @@ impl Row { self.wrap_columns } + #[inline] + pub fn native_wrap_padding(&self) -> bool { + self.native_wrap_padding + } + + #[inline] + pub(crate) fn mark_native_wrap_padding(&mut self) { + self.native_wrap_padding = true; + } + #[inline] pub(crate) fn renew_identity(&mut self) { self.id = next_row_id(); @@ -144,11 +161,13 @@ impl Row { #[inline] pub(crate) fn set_hard_break(&mut self, value: bool) { + self.native_wrap_padding = false; self.hard_break = value; } #[inline] pub(crate) fn set_wrap_columns(&mut self, value: usize) { + self.native_wrap_padding = false; self.wrap_columns = value; } @@ -164,6 +183,7 @@ impl Row { #[inline] pub fn last_mut(&mut self) -> Option<&mut T> { + self.native_wrap_padding = false; self.occ = self.inner.len(); self.inner.last_mut() } @@ -173,12 +193,14 @@ impl Row { where T: GridCell, { + self.native_wrap_padding = false; self.occ += vec.len(); self.inner.append(vec); } #[inline] pub fn append_front(&mut self, mut vec: Vec) { + self.native_wrap_padding = false; self.occ += vec.len(); vec.append(&mut self.inner); @@ -196,6 +218,7 @@ impl Row { #[inline] pub fn front_split_off(&mut self, at: usize) -> Vec { + self.native_wrap_padding = false; self.occ = self.occ.saturating_sub(at); let mut split = self.inner.split_off(at); @@ -220,6 +243,7 @@ impl<'a, T> IntoIterator for &'a mut Row { #[inline] fn into_iter(self) -> slice::IterMut<'a, T> { + self.native_wrap_padding = false; self.occ = self.len(); self.inner.iter_mut() } @@ -237,6 +261,7 @@ impl Index for Row { impl IndexMut for Row { #[inline] fn index_mut(&mut self, index: Column) -> &mut T { + self.native_wrap_padding = false; self.occ = max(self.occ, *index + 1); &mut self.inner[index.0] } @@ -254,6 +279,7 @@ impl Index> for Row { impl IndexMut> for Row { #[inline] fn index_mut(&mut self, index: Range) -> &mut [T] { + self.native_wrap_padding = false; self.occ = max(self.occ, *index.end); &mut self.inner[(index.start.0)..(index.end.0)] } @@ -271,6 +297,7 @@ impl Index> for Row { impl IndexMut> for Row { #[inline] fn index_mut(&mut self, index: RangeTo) -> &mut [T] { + self.native_wrap_padding = false; self.occ = max(self.occ, *index.end); &mut self.inner[..(index.end.0)] } @@ -288,6 +315,7 @@ impl Index> for Row { impl IndexMut> for Row { #[inline] fn index_mut(&mut self, index: RangeFrom) -> &mut [T] { + self.native_wrap_padding = false; self.occ = self.len(); &mut self.inner[(index.start.0)..] } @@ -305,6 +333,7 @@ impl Index for Row { impl IndexMut for Row { #[inline] fn index_mut(&mut self, _: RangeFull) -> &mut [T] { + self.native_wrap_padding = false; self.occ = self.len(); &mut self.inner[..] } @@ -322,6 +351,7 @@ impl Index> for Row { impl IndexMut> for Row { #[inline] fn index_mut(&mut self, index: RangeToInclusive) -> &mut [T] { + self.native_wrap_padding = false; self.occ = max(self.occ, *index.end + 1); &mut self.inner[..=(index.end.0)] } diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs index e0139580..eead143b 100644 --- a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs @@ -665,6 +665,16 @@ impl Term { /// Resize terminal to new dimensions. pub fn resize(&mut self, size: S) { + self.resize_with_viewport(size, false); + } + + /// Resize against a native console whose viewport has no scrollback. + /// Retain terminal history while keeping it outside native coordinates. + pub fn resize_native_viewport(&mut self, size: S) { + self.resize_with_viewport(size, true); + } + + fn resize_with_viewport(&mut self, size: S, preserve_viewport: bool) { self.seal_grapheme(); let old_cols = self.columns(); let old_lines = self.screen_lines(); @@ -684,11 +694,14 @@ impl Term { let mut delta = num_lines as i32 - old_lines as i32; let min_delta = cmp::min(0, num_lines as i32 - self.grid.cursor.point.line.0 - 1); delta = cmp::min(cmp::max(delta, min_delta), history_size as i32); + if preserve_viewport { + delta = cmp::min(delta, 0); + } self.vi_mode_cursor.point.line += delta; let is_alt = self.mode.contains(TermMode::ALT_SCREEN); - self.grid.resize(!is_alt, num_lines, num_cols); - self.inactive_grid.resize(is_alt, num_lines, num_cols); + self.grid.resize_with_viewport(!is_alt, num_lines, num_cols, preserve_viewport); + self.inactive_grid.resize_with_viewport(is_alt, num_lines, num_cols, preserve_viewport); // Invalidate selection and tabs only when necessary. if old_cols != num_cols { diff --git a/experiments/conpty/backend.hpp b/experiments/conpty/backend.hpp new file mode 100644 index 00000000..74cd8733 --- /dev/null +++ b/experiments/conpty/backend.hpp @@ -0,0 +1,165 @@ +// Explicit ConPTY API-family ownership; original implementation, MIT. +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tf_conpty { +class Backend { +public: + enum class GlyphWidth { Default, Console }; +private: + struct Api { + HMODULE module = nullptr; + const char* identity = "system"; + decltype(&::CreatePseudoConsole) create = ::CreatePseudoConsole; + decltype(&::ResizePseudoConsole) resize = ::ResizePseudoConsole; + decltype(&::ClosePseudoConsole) close = ::ClosePseudoConsole; + ~Api() { if (module) FreeLibrary(module); } + }; + std::shared_ptr api_; + DWORD creation_flags_ = 0; + explicit Backend(std::shared_ptr api, DWORD flags = 0) + : api_(std::move(api)), creation_flags_(flags) {} + + // Keep both files open without write/delete sharing from verification + // through module load, so the checked pair cannot be replaced mid-load. + class File { + HANDLE handle_ = INVALID_HANDLE_VALUE; + public: + explicit File(const std::filesystem::path& path) { + handle_ = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (handle_ == INVALID_HANDLE_VALUE) throw std::runtime_error("ConPTY backend file unavailable"); + } + ~File() { if (handle_ != INVALID_HANDLE_VALUE) CloseHandle(handle_); } + File(const File&) = delete; + File& operator=(const File&) = delete; + HANDLE get() const { return handle_; } + }; + static std::string digest(HANDLE file) { + BCRYPT_ALG_HANDLE algorithm = nullptr; + BCRYPT_HASH_HANDLE hash = nullptr; + struct Cleanup { + BCRYPT_ALG_HANDLE& algorithm; + BCRYPT_HASH_HANDLE& hash; + ~Cleanup() { + if (hash) BCryptDestroyHash(hash); + if (algorithm) BCryptCloseAlgorithmProvider(algorithm, 0); + } + } cleanup{algorithm, hash}; + const auto check = [](NTSTATUS status) { + if (status < 0) throw std::runtime_error("ConPTY backend identity check failed"); + }; + check(BCryptOpenAlgorithmProvider(&algorithm, BCRYPT_SHA256_ALGORITHM, nullptr, 0)); + check(BCryptCreateHash(algorithm, &hash, nullptr, 0, nullptr, 0, 0)); + std::array buffer{}; + DWORD count = 0; + size_t total = 0; + for (;;) { + if (!ReadFile(file, buffer.data(), static_cast(buffer.size()), &count, nullptr)) + throw std::runtime_error("ConPTY backend identity read failed"); + if (!count) break; + total += count; + if (total > 8 * 1024 * 1024) throw std::runtime_error("ConPTY backend file too large"); + check(BCryptHashData(hash, buffer.data(), count, 0)); + } + std::array bytes{}; + check(BCryptFinishHash(hash, bytes.data(), static_cast(bytes.size()), 0)); + static constexpr char hex[] = "0123456789abcdef"; + std::string value; + for (const auto byte : bytes) { value += hex[byte >> 4]; value += hex[byte & 15]; } + return value; + } + 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"); + 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"); + api->create = reinterpret_castcreate)>(GetProcAddress(api->module, "ConptyCreatePseudoConsole")); + api->resize = reinterpret_castresize)>(GetProcAddress(api->module, "ConptyResizePseudoConsole")); + api->close = reinterpret_castclose)>(GetProcAddress(api->module, "ConptyClosePseudoConsole")); + if (!api->create || !api->resize || !api->close) + throw std::runtime_error("ConPTY reviewed backend API missing"); + wchar_t loaded[32768]{}; + const auto count = GetModuleFileNameW(api->module, loaded, 32768); + if (!count || count >= 32768 || !std::filesystem::equivalent(dll, loaded)) + 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 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]{}; + const auto count = GetModuleFileNameW(nullptr, image, 32768); + if (!count || count >= 32768) throw std::runtime_error("ConPTY backend executable location unavailable"); + return reviewed_side_by_side(std::filesystem::path(image).parent_path() / L"conpty-backend", + GlyphWidth::Console); +#else + return system(); +#endif + } + HRESULT create(COORD size, HANDLE input, HANDLE output, DWORD flags, HPCON* console) const { + if (creation_flags_ && (flags & 0x18) && (flags & 0x18) != creation_flags_) return E_INVALIDARG; + return api_->create(size, input, output, flags | creation_flags_, console); + } + 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 new file mode 100644 index 00000000..4db92665 --- /dev/null +++ b/experiments/conpty/backend_probe.cpp @@ -0,0 +1,125 @@ +// Exercise selected API ownership through actual create/resize/drain/close. +#include "session.hpp" +#include +#include + +namespace { +void require(bool ok, const char* message) { if (!ok) throw std::runtime_error(message); } +std::wstring self_path() { + wchar_t path[32768]{}; + const auto size = GetModuleFileNameW(nullptr, path, 32768); + require(size && size < 32768, "self path unavailable"); + return std::wstring(path, size); +} +void line(const char* text) { + DWORD written = 0; + const auto count = static_cast(std::char_traits::length(text)); + require(WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), text, count, &written, nullptr) && written == count, + "backend client write failed"); +} +std::wstring read_line() { + std::wstring value; + for (;;) { + wchar_t unit{}; DWORD count = 0; + require(ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), &unit, 1, &count, nullptr) && count == 1, + "backend client input failed"); + if (unit == L'\n') return value; + if (unit != L'\r') value += unit; + require(value.size() <= 32, "backend client command bound"); + } +} +void size(short columns, short rows) { + CONSOLE_SCREEN_BUFFER_INFO info{}; + require(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info), "backend client dimensions unavailable"); + require(info.dwSize.X == columns && info.dwSize.Y == rows, "backend client dimensions differ"); +} +void gate() { + char value[4]{}; size_t size = 0; + while (size < sizeof(value)) { + DWORD count = 0; + if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), value + size, 1, &count, nullptr) || count != 1) break; + if (value[size++] == '\n') break; + } + require(std::string(value,size) == "GO\n" || std::string(value,size) == "GO\r\n", "GO required"); +} +void owned() { + BOOL in_job = FALSE; + require(IsProcessInJob(GetCurrentProcess(), nullptr, &in_job) && in_job, "owned Job required"); +} +} +int wmain(int argc, wchar_t** argv) { + try { + if (argc == 2 && std::wstring(argv[1]) == L"--client") { + owned(); size(80,24); line("TF_BACKEND_READY_80x24\r\n"); + require(read_line() == L"RESIZE", "backend resize command differs"); + size(120,40); line("TF_BACKEND_RESIZED_120x40\r\n"); + require(read_line() == L"EXIT", "backend exit command differs"); + return 37; + } + require(argc == 3 && (std::wstring(argv[1]) == L"--host" || std::wstring(argv[1]) == L"--identity"), + "fixed backend probe arguments required"); + gate(); owned(); + 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"; +#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"); + session.resize(120,40); session.send("RESIZE\r"); + require(session.wait_text("TF_BACKEND_RESIZED_120x40", 5000), "backend resized marker missing"); + session.send("EXIT\r"); + require(session.wait_exit(5000), "backend client natural exit missing"); + session.close(); + exit_code=session.exit_code; active_after=session.active_after; read_error=session.read_error; + cleanup_complete=session.cleanup_complete; forced_reader=session.forced_reader_cancel; + forced_process=session.forced_process_cancel; + loaded_after_close = GetModuleHandleW(L"conpty.dll") != nullptr; + } + const bool module_unloaded = GetModuleHandleW(L"conpty.dll") == nullptr; + const bool valid = exit_code == 37 && active_after == 0 && read_error == 109 && cleanup_complete && + !forced_reader && !forced_process && module_unloaded && + loaded_during_session == side_by_side && loaded_after_close == side_by_side; + std::cout << std::boolalpha << "{\"valid\":" << valid << ",\"side_by_side\":" << side_by_side + << ",\"creation_flags\":" << (side_by_side ? 0x18 : 0) + << ",\"loaded_during_session\":" << loaded_during_session << ",\"loaded_after_close\":" << loaded_after_close + << ",\"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; +#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 00bb91c6..e02ba705 100644 --- a/experiments/conpty/margin_probe.cpp +++ b/experiments/conpty/margin_probe.cpp @@ -6,22 +6,8 @@ #include #include #include -static decltype(&CreatePseudoConsole) create_pc = CreatePseudoConsole; -static decltype(&ResizePseudoConsole) resize_pc = ResizePseudoConsole; -static decltype(&ClosePseudoConsole) close_pc = ClosePseudoConsole; static COORD selected_size{80,24}; -// Keep the selected API family together. HPCON is opaque: a DLL-created -// pseudoconsole must never be resized or closed by the operating system API. -static HRESULT WINAPI probe_create(COORD, HANDLE input, HANDLE output, DWORD flags, HPCON* console) { - return create_pc(selected_size, input, output, flags, console); -} -#define CreatePseudoConsole probe_create -#define ResizePseudoConsole resize_pc -#define ClosePseudoConsole close_pc #include "session.hpp" -#undef CreatePseudoConsole -#undef ResizePseudoConsole -#undef ClosePseudoConsole #include "../shell/workflow_fixture.hpp" static void require(bool ok, const char* msg) { if (!ok) throw std::runtime_error(msg); } static void save(const std::filesystem::path& p, const std::string& b) { @@ -78,37 +64,41 @@ int wmain(int argc, wchar_t** argv) { require(std::string(go,go_size) == "GO\n" || std::string(go,go_size) == "GO\r\n", "GO required"); BOOL in_job = FALSE; require(IsProcessInJob(GetCurrentProcess(), nullptr, &in_job) && in_job, "owned Job required"); - HMODULE backend = nullptr; + auto backend = tf_conpty::Backend::system(); 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"); - backend = LoadLibraryExW(argv[1], nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); - require(backend != nullptr, "backend load failed"); - create_pc = reinterpret_cast(GetProcAddress(backend, "ConptyCreatePseudoConsole")); - resize_pc = reinterpret_cast(GetProcAddress(backend, "ConptyResizePseudoConsole")); - close_pc = reinterpret_cast(GetProcAddress(backend, "ConptyClosePseudoConsole")); - require(create_pc && resize_pc && close_pc, "backend API absent"); +#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); require(length && length < 32768, "self path failed"); const auto dir = std::filesystem::absolute(argv[3]); std::filesystem::create_directories(dir); - tf_conpty::Session session; - session.start(self, L"--client " + std::wstring(argv[2]) + L" \"" + dir.wstring() + L"\""); + tf_conpty::Session session(backend); + session.start(self, L"--client " + std::wstring(argv[2]) + L" \"" + dir.wstring() + L"\"", {}, {}, selected_size); const bool waited = session.wait_exit(5000); + // Preserve available bytes even if closing an affected backend hangs + // and the outer owner subsequently has to terminate the process. + save(dir / "transport.prefix.bin", session.output()); session.close(); save(dir / "transport.bin", session.output()); - std::cout << std::boolalpha << "{\"waited\":" << waited + std::cout << std::boolalpha << "{\"waited\":" << waited << ",\"side_by_side\":" << backend.side_by_side() + << ",\"creation_flags\":" << session.backend_creation_flags() << ",\"exit_code\":" << session.exit_code << ",\"read_error\":" << session.read_error << ",\"capture_overflow\":" << session.capture_overflow << ",\"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"; - // The Session destructor must run before unloading its selected ABI. - // The short-lived diagnostic process retains the module until exit. - (void)backend; + << ",\"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 afa2e64e..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): +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, + "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) + 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: @@ -261,7 +264,7 @@ def main(): for columns, rows in ((80,24), (12,7)) for mode in ("vt-on", "vt-off")] valid = all(sample["terminal_main_margins_restored"] for sample in samples if sample["mode"] == "vt-on") summary = { - "schema": "termforge-conpty-margin-backend-v1", "generated_utc": datetime.now(timezone.utc).isoformat(), + "schema": "termforge-conpty-margin-backend-v2", "generated_utc": datetime.now(timezone.utc).isoformat(), "backend": "system" if backend == "system" else PACKAGE, "probe_sha256": sha256(executable), "candidate_trace_sha256": sha256(trace), "terminal_main_margins_restored_in_both_sizes": valid, "full_r2_contract_verified": False, diff --git a/experiments/conpty/real_app_capture.cpp b/experiments/conpty/real_app_capture.cpp index b37c4b68..8d7f2791 100644 --- a/experiments/conpty/real_app_capture.cpp +++ b/experiments/conpty/real_app_capture.cpp @@ -70,7 +70,10 @@ int main(int argc, char** argv) { if (!IsProcessInJob(GetCurrentProcess(), nullptr, &contained) || !contained) return 2; const std::filesystem::path out = argv[1], vim = argv[2]; require(std::filesystem::create_directories(out), "output directory already exists"); - const auto root = out.parent_path(); + // Each newly created capture owns its application fixtures as well as + // its traces. Sibling captures must not reuse editor contents or event + // files from an earlier run in their shared parent directory. + const auto root = out; unsigned long long shell_bytes = 0, pager_bytes = 0, editor_bytes = 0; { tf_conpty::Session session; diff --git a/experiments/conpty/resize_native_probe.cpp b/experiments/conpty/resize_native_probe.cpp new file mode 100644 index 00000000..6bcf8db8 --- /dev/null +++ b/experiments/conpty/resize_native_probe.cpp @@ -0,0 +1,146 @@ +// Synthetic native resize observations; original implementation, MIT. +// No CPR reply is sent: the fixed host's bounded query wait affects timing. +#include "session.hpp" +#include +#include +#include + +namespace fs = std::filesystem; +namespace { +struct Case { + short columns, rows, new_columns, new_rows; + const char* text; +}; +constexpr Case cases[] = { + {8,3,8,6,"H0\r\nH1\r\nTOP\r\nMID\r\nBOT"}, + {8,3,16,3,"H0\r\nH1\r\n123456789\r\nP>"}, + {8,3,16,3,"H0\r\nH1\r\nH2\r\n123456789"}, + {8,3,5,3,"H0\r\n123456789012345678901234567890"}, + {8,4,10,4,"H0\r\nH1\r\nABCDEFGHIJKLMNO\r\nBOTTOM\x1b[3;2H"}, + {8,3,16,3,"H0\r\n123456789012345678901234567890"}, +}; + +void require(bool value, const char* message) { + if (!value) throw std::runtime_error(message); +} + +void save(const fs::path& path, const std::string& value) { + std::ofstream out(path, std::ios::binary); + out << value; + require(bool(out), "file write"); +} + +void output(const std::string& value) { + DWORD written = 0; + require(WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), value.data(), + static_cast(value.size()), &written, nullptr) && written == value.size(), + "console write"); +} + +void snapshot(const fs::path& path, const CONSOLE_SCREEN_BUFFER_INFO& info) { + require(info.dwSize.X > 0 && info.dwSize.X <= 100 && info.dwSize.Y > 0 && info.dwSize.Y <= 50, + "snapshot bounds"); + std::vector cells(info.dwSize.X * info.dwSize.Y); + SMALL_RECT region{0, 0, static_cast(info.dwSize.X - 1), static_cast(info.dwSize.Y - 1)}; + require(ReadConsoleOutputW(GetStdHandle(STD_OUTPUT_HANDLE), cells.data(), info.dwSize, {0,0}, ®ion), + "snapshot read"); + require(region.Left == 0 && region.Top == 0 && region.Right == info.dwSize.X - 1 && + region.Bottom == info.dwSize.Y - 1, "snapshot rectangle"); + std::ofstream out(path); + out << "{\"rows\":" << info.dwSize.Y << ",\"columns\":" << info.dwSize.X + << ",\"cursor\":[" << info.dwCursorPosition.Y << ',' << info.dwCursorPosition.X + << "],\"utf16_rows\":["; + for (int row = 0; row < info.dwSize.Y; ++row) { + if (row) out << ','; + out << '['; + for (int column = 0; column < info.dwSize.X; ++column) { + if (column) out << ','; + out << static_cast(cells[row * info.dwSize.X + column].Char.UnicodeChar); + } + out << ']'; + } + out << "]}\n"; + require(bool(out), "snapshot save"); +} + +int child(const fs::path& out, const Case& item) { + const HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD mode = 0; + require(GetConsoleMode(handle, &mode), "output mode"); + require(SetConsoleMode(handle, mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING), + "set output mode"); + const std::string source = std::string("\x1b[2J\x1b[H") + item.text; + save(out / L"source.bin", source); + output(source); + CONSOLE_SCREEN_BUFFER_INFO before{}; + require(GetConsoleScreenBufferInfo(handle, &before), "before info"); + snapshot(out / L"native-before.json", before); + output("\x1b]0;TF_R51_READY\x07"); + const auto deadline = GetTickCount64() + 5000; + while (!fs::exists(out / L"go")) { + require(GetTickCount64() < deadline, "go deadline"); + Sleep(1); + } + CONSOLE_SCREEN_BUFFER_INFO after{}; + for (;;) { + require(GetConsoleScreenBufferInfo(handle, &after), "after info"); + if (after.dwSize.X == item.new_columns && after.dwSize.Y == item.new_rows) break; + require(GetTickCount64() < deadline, "resize ack deadline"); + Sleep(1); + } + snapshot(out / L"native-after.json", after); + output("\x1b]0;TF_R51_DONE\x07"); + return 37; +} +} // namespace + +int wmain(int argc, wchar_t** argv) { + try { + require(argc == 4 || argc == 5, "arguments"); + const std::wstring role = argv[1]; + require((role == L"--parent" && argc == 5) || (role == L"--child" && argc == 4), "role"); + const fs::path out = argv[2]; + const std::wstring index_text = argv[3]; + require(index_text.size() == 1 && index_text[0] >= L'0' && index_text[0] <= L'5', "case range"); + const int index = index_text[0] - L'0'; + const auto& item = cases[index]; + if (role == L"--parent") { + char gate[4]{}; + require(bool(std::cin.getline(gate, sizeof(gate))) && std::string(gate) == "GO", "supervisor gate"); + } + BOOL contained = FALSE; + require(IsProcessInJob(GetCurrentProcess(), nullptr, &contained) && contained, "owned Job required"); + if (role == L"--child") return child(out, item); + require(out.is_absolute() && fs::create_directory(out), "fresh absolute output"); + wchar_t image[32768]{}; + const auto length = GetModuleFileNameW(nullptr, image, 32768); + require(length > 0 && length < 32768, "image path"); + tf_conpty::Session session(tf_conpty::Backend::reviewed_side_by_side( + fs::path(argv[4]), tf_conpty::Backend::GlyphWidth::Console)); + session.start(image, L"--child \"" + out.wstring() + L"\" " + std::to_wstring(index), {}, + out.wstring(), {item.columns, item.rows}); + require(session.wait_text("TF_R51_READY\x07", 5000), "ready output"); + const auto offset = session.output().size(); + session.resize(item.new_columns, item.new_rows); + save(out / L"go", "go"); + require(session.wait_text("TF_R51_DONE\x07", 5000), "done output"); + require(session.wait_exit(5000) && session.exit_code == 37, "child natural exit"); + session.close(); + require(session.cleanup_complete && session.read_error == ERROR_BROKEN_PIPE && + !session.forced_reader_cancel && !session.forced_process_cancel && + !session.capture_overflow && session.active_after == 0, "EOF and cleanup"); + const auto bytes = session.output(); + save(out / L"transport.bin", bytes); + save(out / L"plan.txt", "R " + std::to_string(item.rows) + " " + std::to_string(item.columns) + + "\nF 0 " + std::to_string(offset) + "\nR " + std::to_string(item.new_rows) + " " + + std::to_string(item.new_columns) + "\nF " + std::to_string(offset) + " " + + std::to_string(bytes.size() - offset) + "\n"); + std::cout << "{\"case\":" << index << ",\"child_exit\":37,\"active_children\":" << session.active_after + << ",\"output_eof\":true,\"bytes\":" << bytes.size() << ",\"resize_offset\":" << offset + << ",\"creation_flags\":" << session.backend_creation_flags() << "}\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 2; + } +} diff --git a/experiments/conpty/resize_native_run.py b/experiments/conpty/resize_native_run.py new file mode 100644 index 00000000..c9029ed8 --- /dev/null +++ b/experiments/conpty/resize_native_run.py @@ -0,0 +1,215 @@ +"""Bounded synthetic native resize comparison for the explicit reviewed backend.""" +from __future__ import annotations + +import argparse +from datetime import datetime, timezone +import hashlib +import json +from pathlib import Path +import platform +import subprocess +import time + +from experiments.conpty.margin_run import PACKAGE, backend_path +from experiments.conpty.run import OwnedJob + + +# Literal native observations, independent of candidate reflow. Each row is +# padded to the acknowledged native width before exact comparison. +CASES = ( + ((8, 3), (8, 6), "H0\r\nH1\r\nTOP\r\nMID\r\nBOT", ("TOP", "MID", "BOT", "", "", ""), (2, 3)), + ((8, 3), (16, 3), "H0\r\nH1\r\n123456789\r\nP>", ("123456789", "P>", ""), (1, 2)), + ((8, 3), (16, 3), "H0\r\nH1\r\nH2\r\n123456789", ("H2", "123456789", ""), (1, 9)), + ((8, 3), (5, 3), "H0\r\n123456789012345678901234567890", ("90123", "45678", "90"), (2, 2)), + ((8, 4), (10, 4), "H0\r\nH1\r\nABCDEFGHIJKLMNO\r\nBOTTOM\x1b[3;2H", + ("H1", "ABCDEFGHIJ", "KLMNO", "BOTTOM"), (1, 9)), + ((8, 3), (16, 3), "H0\r\n123456789012345678901234567890", ("9012345678901234", "567890", ""), (1, 6)), +) + + +def sha(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def write_json(path, value): + path.write_text(json.dumps(value, indent=2) + "\n", encoding="utf-8") + + +def capture(exe, backend, out, index, *, gate=b"GO\n", timeout=20): + # The native parent creates this fresh case directory only after GO. + job = OwnedJob() + process = None + assigned = False + stdout = stderr = b"" + report = dict(case=index, exit_code=None, timed_out=False, forced_outer_cleanup=False, + job_after=None, job_after_forced_cleanup=None, process_reaped=False, + streams_drained=False, cleanup_errors=[]) + try: + process = subprocess.Popen([str(exe), "--parent", str(out), str(index), str(backend)], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + creationflags=subprocess.CREATE_NO_WINDOW) + job.assign(process.pid) + assigned = True + try: + stdout, stderr = process.communicate(gate, timeout=timeout) + report["streams_drained"] = True + except subprocess.TimeoutExpired: + report["timed_out"] = True + finally: + deadline = time.monotonic() + 5 + + def fault(code): + report["cleanup_errors"].append(code) + + def running(): + try: + return process.poll() is None + except Exception: + fault("process_poll") + return True + + def collect_process(): + nonlocal stdout, stderr + try: + stdout, stderr = process.communicate(timeout=max(0, deadline - time.monotonic())) + report["streams_drained"] = True + except Exception: + fault("process_communicate") + + try: + if process is not None and running(): + report["forced_outer_cleanup"] = True + try: + if assigned: + job.terminate() + else: + process.kill() # This root is gated before explicit ConPTY creation. + except Exception: + fault("process_terminate") + # A failed termination operation must not skip the wait. + collect_process() + if assigned: + try: + report["job_after"] = job.drain(timeout=max(0, deadline - time.monotonic())) + except Exception: + fault("job_accounting") + if report["job_after"] is not None and report["job_after"]["active"]: + report["forced_outer_cleanup"] = True + try: + job.terminate() + except Exception: + fault("job_terminate") + try: + report["job_after_forced_cleanup"] = job.drain(timeout=max(0, deadline - time.monotonic())) + if report["job_after_forced_cleanup"]["active"]: + fault("job_not_empty") + except Exception: + fault("job_accounting_after_termination") + finally: + try: + job.close() # Kill-on-close fallback, even after a failed terminate/wait. + except Exception: + fault("job_close") + if process is not None: + if running() or not report["streams_drained"]: + collect_process() + try: + report["exit_code"] = process.poll() + report["process_reaped"] = report["exit_code"] is not None + except Exception: + fault("process_final_poll") + try: + out.mkdir(exist_ok=True) + except Exception: + fault("evidence_directory") + for name, data in (("parent.stdout.bin", stdout), ("parent.stderr.bin", stderr)): + try: + (out / name).write_bytes(data) + except Exception: + fault("evidence_stream_write") + try: + write_json(out / "capture.json", report) + except Exception: + fault("evidence_report_write") + if (report["exit_code"] != 0 or report["timed_out"] or report["forced_outer_cleanup"] or + report["cleanup_errors"] or not report["process_reaped"] or not report["streams_drained"] or + report["job_after"] != {"active": 0, "total": 4, "terminated": 0}): + raise ValueError("native_capture_failed") + native = json.loads(stdout) + if (native["case"] != index or native["child_exit"] != 37 or native["active_children"] != 0 or + native["output_eof"] is not True or native["creation_flags"] != 24): + raise ValueError("native_lifecycle_failed") + report["native"] = native + return report + + +def inspect(out, replay, index): + initial, final, source, expected_rows, cursor = CASES[index] + if (out / "source.bin").read_bytes() != ("\x1b[2J\x1b[H" + source).encode("ascii"): + raise ValueError("source_changed") + for stage, dimensions in (("before", initial), ("after", final)): + native = json.loads((out / f"native-{stage}.json").read_text()) + columns, rows = dimensions + values = native["utf16_rows"] + if (native["columns"] != columns or native["rows"] != rows or len(values) != rows or + any(len(row) != columns or any(type(c) is not int or not 0 <= c < 128 for c in row) + for row in values)): + raise ValueError("native_snapshot_shape") + expected = [row.ljust(final[0]) for row in expected_rows] + if ["".join(map(chr, row)) for row in native["utf16_rows"]] != expected or native["cursor"] != list(cursor): + raise ValueError("native_observation_changed") + results = {} + for policy, options in (("reflow_history", []), ("native_viewport", ["--native-viewport"])): + run = subprocess.run([str(replay), str(out / "transport.bin"), str(out / "plan.txt"), *options], + capture_output=True, timeout=5) + (out / f"{policy}.states.jsonl").write_bytes(run.stdout) + (out / f"{policy}.stderr.bin").write_bytes(run.stderr) + if run.returncode != 0: + raise ValueError("candidate_replay_failed") + state = json.loads(run.stdout.splitlines()[-1])["state"] + results[policy] = dict(native_rows_equal=state["lines"] == expected, + native_cursor_equal=[state["row"], state["column"]] == list(cursor)) + return results + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--exe", type=Path, required=True) + parser.add_argument("--replay", type=Path, required=True) + parser.add_argument("--backend", type=Path, required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + exe, replay = args.exe.resolve(strict=True), args.replay.resolve(strict=True) + backend = Path(backend_path(str(args.backend / "conpty.dll"))).parent + out = args.out.resolve() + out.mkdir(parents=True, exist_ok=False) + report = dict(schema=1, started_utc=datetime.now(timezone.utc).isoformat(), + windows=platform.version(), backend=PACKAGE, creation_flags=24, + probe_sha256=sha(exe), replay_sha256=sha(replay), + sources_sha256={name: sha(Path(__file__).with_name(name)) for name in + ("resize_native_run.py", "resize_native_probe.cpp", "session.hpp", "backend.hpp")}, + cpr_replies_sent=False, diagnostic_only=True, presented_done=None, cases=[]) + success = True + for index in range(len(CASES)): + case = out / f"case-{index}" + row = dict(case=index, passed=False) + try: + row.update(capture(exe, backend, case, index)) + row["replay"] = inspect(case, replay, index) + row["passed"] = all(row["replay"]["native_viewport"].values()) + except Exception: + # The owned case retains raw diagnostics; the summary contains no + # exception messages or absolute command paths for publication. + row["failure"] = "capture_or_comparison_failed" + row["files_sha256"] = {p.name: sha(p) for p in case.iterdir() if p.is_file()} if case.exists() else {} + report["cases"].append(row) + write_json(out / "validation.json", report) + print(json.dumps({"case": index, "passed": row["passed"], "replay": row.get("replay")}), flush=True) + if not row["passed"]: + success = False + break + return 0 if success else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/conpty/results/2026-10-26-reviewed-backend/validation.json b/experiments/conpty/results/2026-10-26-reviewed-backend/validation.json new file mode 100644 index 00000000..c272a20c --- /dev/null +++ b/experiments/conpty/results/2026-10-26-reviewed-backend/validation.json @@ -0,0 +1,899 @@ +{ + "schema": "termforge-reviewed-conpty-integration-v1", + "implementation_commit": "5030058115739411bcc3cadc3a9f321580e0bc31", + "cleanup_and_diagnostic_source_commit": "0954fea5c20d5083839a77613d08e949113af242", + "source_hash_basis": "git blobs at cleanup_and_diagnostic_source_commit; working copy checked equal allowing CRLF normalization only", + "source_sha256": { + "CMakeLists.txt": "8672f74ee151aa0ff96c3814b0100915405ac51e5f82512376f6f6fdbe3f7a64", + "cmake/ConptyBackend.cmake": "6a9f9fb69f90a1db7ad5eb970e61d0913ce4ff75a121f267a369468344fffcde", + "experiments/conpty/backend.hpp": "5ee3ab95dc4b9aefce23ff7900cda2ecdd9cd09d1b1044074de2f5e572c31489", + "experiments/conpty/backend_probe.cpp": "c51d53ef508aa19f81dfb89ed030993ae95f06f5a85123560e9cae2de369b042", + "experiments/conpty/margin_probe.cpp": "9fff80b3eb0ace2cc6944ea8a99603c5e5a2b0150b82933d858ed667b0357002", + "experiments/conpty/margin_run.py": "9390bc716579728d6c6d9ee7d69ce2deae11e56fd7de5cec6b3231209ed72704", + "experiments/conpty/session.hpp": "88197d8dd9bda247bb6d7a7b9a3344ad2670b9b0ce49b196ceb24e7005340a8e", + "experiments/conpty/test_backend.py": "dde8745f7512f00e20435d5985c3eb95919483034a64bce96d45e51647c3f209", + "experiments/conpty/test_margin_run.py": "9fcae221bb076c30b83179b619760f1ea597cc2971373033aae9653c24e82f84", + "experiments/shell/margin_checks.py": "cb89329d43d235a05ae0a84204716ff2bc82d963aa7924149fa4e05d9f9f76b6", + "experiments/shell/paste_scenario_state.hpp": "934414929448b30e429f95d84a3e5b7927ea31292d547b1e0ece92efa3b8c0dc", + "experiments/shell/query_client.cpp": "10d982efc87fa6a832fb674a43b515e2d993dcf8b40bcfa845aae5a2e7ae0545", + "experiments/shell/run.py": "45976b828eecb316881d698dedaef09ad304f72d991dfa360f1c552bbcbe9f1d", + "experiments/shell/shell.cpp": "9360e8c7019bd8671ba361562c524103caf5f2214ae3b961df5fa90f65ffa776", + "experiments/shell/test_candidate_tui.py": "9434bb01c99246142d08ea5f4eaeb381ed6e18aaefcea53d685effc6e992bacf" + }, + "binary_sha256": { + "termforge_shell": "b1e7657cb6f83306a0f16d8427dbe69fbf98aa1f304d11e0f3f4c4a5d24e53fb", + "conpty_backend_probe": "6d0ac4fdafa08b968fea15552ba23ec2daa1d6028aa4edf3d5495a37c3be057a", + "conpty_margin_probe": "19fcac40cf43a0ce51e4bc06ae1f8b7235e7480bbc826e4a39498fcd703d22f8", + "real_app_capture": "02e539e5e9c25101a887b2b50916e4aabb69281d3dbd9d85f5331123ff1adfe2" + }, + "environment": { + "platform": "Windows-11-10.0.26200-SP0", + "python": "3.12.10", + "system_conhost": { + "sha256": "e449bce01f275cd08f3d4e64bb73b3b43ae845a0dbdb3e6131426e66537705e5", + "fixed_file_version": "10.0.26100.9444", + "fixed_product_version": "10.0.26100.9444", + "file_version_string": "10.0.26100.8875 (WinBuild.160101.0800)", + "product_version_string": "10.0.26100.8875" + } + }, + "package_manifest": "../../../../third_party/conpty/manifest.json", + "package": { + "schema": "termforge-conpty-package-v1", + "name": "Microsoft.Windows.Console.ConPTY", + "version": "1.25.260930003", + "architecture": "win-x64", + "license": "MIT", + "source_commit": "dc8ae365847d41f62d1ebca93815bb00959bc12f", + "source_tag": "v1.25.2733.0", + "license_source_url": "https://raw.githubusercontent.com/microsoft/terminal/dc8ae365847d41f62d1ebca93815bb00959bc12f/LICENSE", + "license_source_sha256": "5d177f23ecfeb0ea8e050b6a5a16355e1ae9a0b286436ca8f83ed08b3795be6b", + "bundled_license_sha256": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada", + "license_normalization": "CRLF to LF only; complete text verified equal", + "package_url": "https://github.com/microsoft/terminal/releases/download/v1.25.2733.0/Microsoft.Windows.Console.ConPTY.1.25.260930003.nupkg", + "package_bytes": 1750533, + "package_sha256": "6d6f8b008c655d814c498d8205d4012dcd4d0b6dfc1096461d0a185d1e3caf03", + "files": { + "conpty.dll": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "OpenConsole.exe": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "package_members": { + "conpty.dll": "runtimes/win-x64/native/conpty.dll", + "OpenConsole.exe": "build/native/runtimes/x64/OpenConsole.exe" + }, + "experimental": true, + "configured_creation_flags": 24, + "configured_glyph_width": "PSEUDOCONSOLE_GLYPH_WIDTH_CONSOLE", + "default_backend": false, + "supported_client_platforms_verified": false + }, + "binary_policy": { + "configured_backend": "reviewed-side-by-side-1.25.260930003", + "configured_creation_flags": 24, + "generic_reviewed_creation_flags": 0, + "system_creation_flags": 0, + "meaning": "fixed official Console glyph width policy, not passthrough" + }, + "scope": { + "cpp_binary_sources": "5030058; cleanup commit changes only Python margin diagnostic and its tests", + "raw_outputs": "private local artifacts only; screen text, paths, PIDs and traces excluded", + "unrun_built_target": "runtime_observation_test was built, not run" + }, + "focused_tests": [ + { + "command": "python -m unittest experiments.conpty.test_backend -v", + "methods_passed": 6, + "methods_total": 6, + "actual_invocations": 7, + "skipped": 0, + "source_stage": "5030058" + }, + { + "command": "python -m unittest experiments.conpty.test_margin_run -v", + "methods_passed": 9, + "methods_total": 9, + "source_stage": "0954fea" + }, + { + "command": "python -m unittest experiments.shell.test_candidate_tui -v", + "methods_passed": 5, + "methods_total": 6, + "source_stage": "5030058", + "failure": "Vim stage59; one failed run, its second repeat was not reached" + }, + { + "description": "normal cmd editing/Unicode/Ctrl-C/exit, four local shells/CWD/exit, continuous-output close", + "methods_passed": 3, + "methods_total": 3, + "fresh_runs": 8, + "source_stage": "5030058" + } + ], + "backend_native_lifecycle_and_rejection_invocations": [ + { + "returncode": 0, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_cleanup": false, + "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 + }, + "error": null + }, + { + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "forced_cleanup": false, + "native": null, + "error": "identity_rejected" + }, + { + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "forced_cleanup": false, + "native": null, + "error": "identity_rejected" + }, + { + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "forced_cleanup": false, + "native": null, + "error": "identity_rejected" + }, + { + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "forced_cleanup": false, + "native": null, + "error": "identity_rejected" + }, + { + "returncode": 2, + "outer_job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "forced_cleanup": false, + "native": null, + "error": "identity_rejected" + }, + { + "returncode": 0, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_cleanup": false, + "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 + }, + "error": null + } + ], + "post_cleanup_margin_ab": [ + { + "backend": "system", + "creation_flags": 0, + "summary_sha256": "ca578f4b04325efab7844c2463f068c23031adb95279868e8ae4be0efd56cea1", + "require_margins_exit_code": 1, + "terminal_main_margins_restored_in_both_sizes": false, + "cases": [ + { + "mode": "vt-on", + "columns": 80, + "rows": 24, + "status": "completed", + "failure_code": null, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 641, + "transport_sha256": "5c48dc0008ea2a71c457b7b8f6b67093896d1590c924c704bd3f07123816637f", + "original_decstbm_forwarded": false, + "source_equal_transport": false, + "native_main_margins_restored": false, + "terminal_main_margins_restored": false, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "waited": true, + "side_by_side": false, + "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 + } + }, + { + "mode": "vt-off", + "columns": 80, + "rows": 24, + "status": "completed", + "failure_code": null, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 330, + "transport_sha256": "ab8892697fef17f6f2cf69c55859a0523b41dde46e535649ed52a8bfad1a09e6", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native_main_margins_restored": false, + "terminal_main_margins_restored": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "waited": true, + "side_by_side": false, + "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 + } + }, + { + "mode": "vt-on", + "columns": 12, + "rows": 7, + "status": "completed", + "failure_code": null, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 441, + "transport_sha256": "7527d0312dd73db0f006ccaa2391777481a947b0cdb9070003118fa2d69f8746", + "original_decstbm_forwarded": false, + "source_equal_transport": false, + "native_main_margins_restored": false, + "terminal_main_margins_restored": false, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "waited": true, + "side_by_side": false, + "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 + } + }, + { + "mode": "vt-off", + "columns": 12, + "rows": 7, + "status": "completed", + "failure_code": null, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 505, + "transport_sha256": "cd4c2e7c123739cc05df721fd6b9cf86186ba0f0ed211a9877ab5fce5d481a06", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native_main_margins_restored": false, + "terminal_main_margins_restored": false, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "waited": true, + "side_by_side": false, + "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": "official", + "creation_flags": 0, + "summary_sha256": "fec50841dc07a979fababf91d62e9ad1f33d9db25995b21995039aeb14d31f3c", + "require_margins_exit_code": 0, + "terminal_main_margins_restored_in_both_sizes": true, + "cases": [ + { + "mode": "vt-on", + "columns": 80, + "rows": 24, + "status": "completed", + "failure_code": null, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 218, + "transport_sha256": "d793c84c56624d6c647e562ef7ba35e21d8998baa43371cb36f95b2add684246", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native_main_margins_restored": false, + "terminal_main_margins_restored": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "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 + } + }, + { + "mode": "vt-off", + "columns": 80, + "rows": 24, + "status": "completed", + "failure_code": null, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 218, + "transport_sha256": "d793c84c56624d6c647e562ef7ba35e21d8998baa43371cb36f95b2add684246", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native_main_margins_restored": false, + "terminal_main_margins_restored": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "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 + } + }, + { + "mode": "vt-on", + "columns": 12, + "rows": 7, + "status": "completed", + "failure_code": null, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 218, + "transport_sha256": "d793c84c56624d6c647e562ef7ba35e21d8998baa43371cb36f95b2add684246", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native_main_margins_restored": false, + "terminal_main_margins_restored": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "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 + } + }, + { + "mode": "vt-off", + "columns": 12, + "rows": 7, + "status": "completed", + "failure_code": null, + "source_bytes": 175, + "source_sha256": "01878a5d54c343007309abb89faeb90c89262a8b55e420c5f267342ed9b27e59", + "transport_bytes": 222, + "transport_sha256": "01064e034cc1cd3c34eae65a2f656ae940936cf381e60371b8e17428ec00617d", + "original_decstbm_forwarded": true, + "source_equal_transport": false, + "native_main_margins_restored": false, + "terminal_main_margins_restored": true, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_outer_cleanup": false, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "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 + } + } + ] + } + ], + "configured_console_width_gui_runs": [ + { + "mode": "candidate-alt", + "fresh_runs": 2, + "passed": 2, + "failed": 0, + "native_exit_codes": [ + 0, + 0 + ], + "timed_out": [ + false, + false + ], + "outer_job_active_after": [ + 0, + 0 + ], + "outer_job_active_after_cleanup": [ + 0, + 0 + ], + "desktop_gone": [ + true, + true + ], + "failure_stage": null, + "failure_code": null + }, + { + "mode": "candidate-input-escape", + "fresh_runs": 2, + "passed": 2, + "failed": 0, + "native_exit_codes": [ + 0, + 0 + ], + "timed_out": [ + false, + false + ], + "outer_job_active_after": [ + 0, + 0 + ], + "outer_job_active_after_cleanup": [ + 0, + 0 + ], + "desktop_gone": [ + true, + true + ], + "failure_stage": null, + "failure_code": null + }, + { + "mode": "candidate-margins", + "fresh_runs": 2, + "passed": 2, + "failed": 0, + "native_exit_codes": [ + 0, + 0 + ], + "timed_out": [ + false, + false + ], + "outer_job_active_after": [ + 0, + 0 + ], + "outer_job_active_after_cleanup": [ + 0, + 0 + ], + "desktop_gone": [ + true, + true + ], + "failure_stage": null, + "failure_code": null + }, + { + "mode": "candidate-scroll", + "fresh_runs": 2, + "passed": 2, + "failed": 0, + "native_exit_codes": [ + 0, + 0 + ], + "timed_out": [ + false, + false + ], + "outer_job_active_after": [ + 0, + 0 + ], + "outer_job_active_after_cleanup": [ + 0, + 0 + ], + "desktop_gone": [ + true, + true + ], + "failure_stage": null, + "failure_code": null + }, + { + "mode": "candidate-tui", + "fresh_runs": 1, + "passed": 0, + "failed": 1, + "native_exit_codes": [ + 2 + ], + "timed_out": [ + false + ], + "outer_job_active_after": [ + 0 + ], + "outer_job_active_after_cleanup": [ + 0 + ], + "desktop_gone": [ + true + ], + "failure_stage": 59, + "failure_code": "vim_resize_command_remains_in_insert_mode" + }, + { + "mode": "candidate-unicode", + "fresh_runs": 2, + "passed": 2, + "failed": 0, + "native_exit_codes": [ + 0, + 0 + ], + "timed_out": [ + false, + false + ], + "outer_job_active_after": [ + 0, + 0 + ], + "outer_job_active_after_cleanup": [ + 0, + 0 + ], + "desktop_gone": [ + true, + true + ], + "failure_stage": null, + "failure_code": null + }, + { + "mode": "close", + "fresh_runs": 3, + "passed": 3, + "failed": 0, + "native_exit_codes": [ + 0, + 0, + 0 + ], + "timed_out": [ + false, + false, + false + ], + "outer_job_active_after": [ + 0, + 0, + 0 + ], + "outer_job_active_after_cleanup": [ + 0, + 0, + 0 + ], + "desktop_gone": [ + true, + true, + true + ], + "failure_stage": null, + "failure_code": null + }, + { + "mode": "normal", + "fresh_runs": 3, + "passed": 3, + "failed": 0, + "native_exit_codes": [ + 0, + 0, + 0 + ], + "timed_out": [ + false, + false, + false + ], + "outer_job_active_after": [ + 0, + 0, + 0 + ], + "outer_job_active_after_cleanup": [ + 0, + 0, + 0 + ], + "desktop_gone": [ + true, + true, + true + ], + "failure_stage": null, + "failure_code": null + }, + { + "mode": "ui-shells", + "fresh_runs": 2, + "passed": 2, + "failed": 0, + "native_exit_codes": [ + 0, + 0 + ], + "timed_out": [ + false, + false + ], + "outer_job_active_after": [ + 0, + 0 + ], + "outer_job_active_after_cleanup": [ + 0, + 0 + ], + "desktop_gone": [ + true, + true + ], + "failure_stage": null, + "failure_code": null + } + ], + "configured_console_width_known_reds": [ + { + "mode": "resize", + "verdict": "known_failure_reproduced", + "native_exit_code": 2, + "timed_out": false, + "stage": 43, + "error_code": "resize_state_or_stream_difference", + "paste_counts": null, + "outer_job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "outer_job_after_cleanup": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "desktop_gone": true, + "probe_sha256": "b1e7657cb6f83306a0f16d8427dbe69fbf98aa1f304d11e0f3f4c4a5d24e53fb" + }, + { + "mode": "ui-paste", + "verdict": "known_failure_reproduced", + "native_exit_code": 2, + "timed_out": false, + "stage": null, + "error_code": "transfer_completed_before_checkpoint_action", + "paste_counts": { + "checkpoint": "768", + "flood": "1", + "sent": "3000000", + "accepted": "3000000", + "total": "3000000", + "state": "3" + }, + "outer_job_after": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "outer_job_after_cleanup": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "desktop_gone": true, + "probe_sha256": "b1e7657cb6f83306a0f16d8427dbe69fbf98aa1f304d11e0f3f4c4a5d24e53fb" + } + ], + "configured_console_width_real_app_capture": { + "whole_capture_passed": false, + "supervision": { + "schema": "termforge-real-app-capture-supervision-v1", + "timed_out": false, + "returncode": 1, + "job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "cleanup_errors": [] + }, + "failure_code": "vim_editor_new_buffer_not_displayed", + "editor": { + "stage": "new-buffer", + "editor_exit": 91, + "child_signaled": true, + "editor_bytes": 1999, + "active_children": 0, + "cleanup_complete": true, + "capture_overflow": false + }, + "more_substage_passed": true, + "more": { + "schema": "termforge-more-capture-v1", + "valid": true, + "stage": "output-eof", + "prompt_end_offset": 411, + "q_bytes": 1, + "exit_code": 0, + "output_bytes": 508, + "read_error": 109, + "output_eof": true, + "cleanup_complete": true, + "active_children_after": 0, + "forced_reader_cancel": false, + "forced_process_cancel": false + }, + "pager_sha256": "f51912329ea7e8f696057afc48703ceced3d2e8d582f891239ebfcc2b08b1dc0" + }, + "earlier_flags_zero_results": { + "final_console_width_proof": false, + "gui_vim_dsr_resize_write_fresh_passes": 2, + "full_native_shell_more_editor_capture_passed": true, + "unicode_readback_existing_contract_passed": false, + "preserved_raw_evidence": "private backend-r1 and official-real-app-capture folders; not replaced by final results" + }, + "remaining_gaps": [ + "Continuous resize stage43 retains unexpected trailing text/position and Unicode replay differences", + "Console-width Vim GUI stage59 and native editor new-buffer capture fail; prior flags0 pass is a separate observation", + "3MB paste completes before the original 768-byte receiver checkpoint action; cancel-during-transfer contract unverified", + "Native CHAR_INFO main/alternate margins remain incorrect even when terminal VT is correct", + "Full R1 workflow, IME/monitor coverage, long-run resources, performance and WindTerm comparisons remain unverified" + ], + "product_default_changed": false, + "native_api_margin_gap_closed": false, + "full_r2_contract_verified": false, + "performance_claim": false, + "presented_done": null, + "issues_to_keep_open": [ + 41, + 47 + ] +} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/.gitattributes b/experiments/conpty/results/2026-10-27-native-viewport-resize/.gitattributes new file mode 100644 index 00000000..e5497457 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/.gitattributes @@ -0,0 +1,3 @@ +*.bin binary +case-*/*.json -text whitespace=cr-at-eol +case-*/plan.txt -text diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/native-after.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/native-after.json new file mode 100644 index 00000000..0a56c3f3 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/native-after.json @@ -0,0 +1 @@ +{"rows":6,"columns":8,"cursor":[2,3],"utf16_rows":[[84,79,80,32,32,32,32,32],[77,73,68,32,32,32,32,32],[66,79,84,32,32,32,32,32],[32,32,32,32,32,32,32,32],[32,32,32,32,32,32,32,32],[32,32,32,32,32,32,32,32]]} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/native-before.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/native-before.json new file mode 100644 index 00000000..cfe4597b --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/native-before.json @@ -0,0 +1 @@ +{"rows":3,"columns":8,"cursor":[2,3],"utf16_rows":[[84,79,80,32,32,32,32,32],[77,73,68,32,32,32,32,32],[66,79,84,32,32,32,32,32]]} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/plan.txt b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/plan.txt new file mode 100644 index 00000000..17bf5bba --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/plan.txt @@ -0,0 +1,4 @@ +R 3 8 +F 0 68 +R 6 8 +F 68 36 diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/source.bin b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/source.bin new file mode 100644 index 00000000..946f45d7 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/source.bin @@ -0,0 +1,5 @@ +H0 +H1 +TOP +MID +BOT \ No newline at end of file diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/transport.bin b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/transport.bin new file mode 100644 index 00000000..139dc445 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-0/transport.bin @@ -0,0 +1,5 @@ +[?1004h[?9001hH0 +H1 +TOP +MID +BOT]0;TF_R51_READY]0;TF_R51_DONE[?1004l[?9001l \ No newline at end of file diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/native-after.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/native-after.json new file mode 100644 index 00000000..ed9dc581 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/native-after.json @@ -0,0 +1 @@ +{"rows":3,"columns":16,"cursor":[1,2],"utf16_rows":[[49,50,51,52,53,54,55,56,57,32,32,32,32,32,32,32],[80,62,32,32,32,32,32,32,32,32,32,32,32,32,32,32],[32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32]]} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/native-before.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/native-before.json new file mode 100644 index 00000000..fdf085c7 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/native-before.json @@ -0,0 +1 @@ +{"rows":3,"columns":8,"cursor":[2,2],"utf16_rows":[[49,50,51,52,53,54,55,56],[57,32,32,32,32,32,32,32],[80,62,32,32,32,32,32,32]]} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/plan.txt b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/plan.txt new file mode 100644 index 00000000..df3f3127 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/plan.txt @@ -0,0 +1,4 @@ +R 3 8 +F 0 68 +R 3 16 +F 68 36 diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/source.bin b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/source.bin new file mode 100644 index 00000000..9c74f37a --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/source.bin @@ -0,0 +1,4 @@ +H0 +H1 +123456789 +P> \ No newline at end of file diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/transport.bin b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/transport.bin new file mode 100644 index 00000000..8703ae19 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-1/transport.bin @@ -0,0 +1,4 @@ +[?1004h[?9001hH0 +H1 +123456789 +P>]0;TF_R51_READY]0;TF_R51_DONE[?1004l[?9001l \ No newline at end of file diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/native-after.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/native-after.json new file mode 100644 index 00000000..8df00f4a --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/native-after.json @@ -0,0 +1 @@ +{"rows":3,"columns":16,"cursor":[1,9],"utf16_rows":[[72,50,32,32,32,32,32,32,32,32,32,32,32,32,32,32],[49,50,51,52,53,54,55,56,57,32,32,32,32,32,32,32],[32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32]]} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/native-before.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/native-before.json new file mode 100644 index 00000000..d08f7965 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/native-before.json @@ -0,0 +1 @@ +{"rows":3,"columns":8,"cursor":[2,1],"utf16_rows":[[72,50,32,32,32,32,32,32],[49,50,51,52,53,54,55,56],[57,32,32,32,32,32,32,32]]} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/plan.txt b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/plan.txt new file mode 100644 index 00000000..df3f3127 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/plan.txt @@ -0,0 +1,4 @@ +R 3 8 +F 0 68 +R 3 16 +F 68 36 diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/source.bin b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/source.bin new file mode 100644 index 00000000..7c93df51 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/source.bin @@ -0,0 +1,4 @@ +H0 +H1 +H2 +123456789 \ No newline at end of file diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/transport.bin b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/transport.bin new file mode 100644 index 00000000..8ed3278d --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-2/transport.bin @@ -0,0 +1,4 @@ +[?1004h[?9001hH0 +H1 +H2 +123456789]0;TF_R51_READY]0;TF_R51_DONE[?1004l[?9001l \ No newline at end of file diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/native-after.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/native-after.json new file mode 100644 index 00000000..6c5d6af4 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/native-after.json @@ -0,0 +1 @@ +{"rows":3,"columns":5,"cursor":[2,2],"utf16_rows":[[57,48,49,50,51],[52,53,54,55,56],[57,48,32,32,32]]} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/native-before.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/native-before.json new file mode 100644 index 00000000..64495c16 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/native-before.json @@ -0,0 +1 @@ +{"rows":3,"columns":8,"cursor":[2,6],"utf16_rows":[[57,48,49,50,51,52,53,54],[55,56,57,48,49,50,51,52],[53,54,55,56,57,48,32,32]]} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/plan.txt b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/plan.txt new file mode 100644 index 00000000..4cfb2775 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/plan.txt @@ -0,0 +1,4 @@ +R 3 8 +F 0 81 +R 3 5 +F 81 36 diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/source.bin b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/source.bin new file mode 100644 index 00000000..ee823914 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/source.bin @@ -0,0 +1,2 @@ +H0 +123456789012345678901234567890 \ No newline at end of file diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/transport.bin b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/transport.bin new file mode 100644 index 00000000..3af93aa7 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-3/transport.bin @@ -0,0 +1,2 @@ +[?1004h[?9001hH0 +123456789012345678901234567890]0;TF_R51_READY]0;TF_R51_DONE[?1004l[?9001l \ No newline at end of file diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/native-after.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/native-after.json new file mode 100644 index 00000000..ac791e80 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/native-after.json @@ -0,0 +1 @@ +{"rows":4,"columns":10,"cursor":[1,9],"utf16_rows":[[72,49,32,32,32,32,32,32,32,32],[65,66,67,68,69,70,71,72,73,74],[75,76,77,78,79,32,32,32,32,32],[66,79,84,84,79,77,32,32,32,32]]} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/native-before.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/native-before.json new file mode 100644 index 00000000..a64cfd8e --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/native-before.json @@ -0,0 +1 @@ +{"rows":4,"columns":8,"cursor":[2,1],"utf16_rows":[[72,49,32,32,32,32,32,32],[65,66,67,68,69,70,71,72],[73,74,75,76,77,78,79,32],[66,79,84,84,79,77,32,32]]} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/plan.txt b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/plan.txt new file mode 100644 index 00000000..6f4a91ed --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/plan.txt @@ -0,0 +1,4 @@ +R 4 8 +F 0 84 +R 4 10 +F 84 36 diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/source.bin b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/source.bin new file mode 100644 index 00000000..819a1ebc --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/source.bin @@ -0,0 +1,4 @@ +H0 +H1 +ABCDEFGHIJKLMNO +BOTTOM \ No newline at end of file diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/transport.bin b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/transport.bin new file mode 100644 index 00000000..d1e064f7 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-4/transport.bin @@ -0,0 +1,4 @@ +[?1004h[?9001hH0 +H1 +ABCDEFGHIJKLMNO +BOTTOM]0;TF_R51_READY]0;TF_R51_DONE[?1004l[?9001l \ No newline at end of file diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/native-after.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/native-after.json new file mode 100644 index 00000000..9c308e9f --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/native-after.json @@ -0,0 +1 @@ +{"rows":3,"columns":16,"cursor":[1,6],"utf16_rows":[[57,48,49,50,51,52,53,54,55,56,57,48,49,50,51,52],[53,54,55,56,57,48,32,32,32,32,32,32,32,32,32,32],[32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32]]} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/native-before.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/native-before.json new file mode 100644 index 00000000..64495c16 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/native-before.json @@ -0,0 +1 @@ +{"rows":3,"columns":8,"cursor":[2,6],"utf16_rows":[[57,48,49,50,51,52,53,54],[55,56,57,48,49,50,51,52],[53,54,55,56,57,48,32,32]]} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/plan.txt b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/plan.txt new file mode 100644 index 00000000..e05767b9 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/plan.txt @@ -0,0 +1,4 @@ +R 3 8 +F 0 81 +R 3 16 +F 81 36 diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/source.bin b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/source.bin new file mode 100644 index 00000000..ee823914 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/source.bin @@ -0,0 +1,2 @@ +H0 +123456789012345678901234567890 \ No newline at end of file diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/transport.bin b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/transport.bin new file mode 100644 index 00000000..3af93aa7 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/case-5/transport.bin @@ -0,0 +1,2 @@ +[?1004h[?9001hH0 +123456789012345678901234567890]0;TF_R51_READY]0;TF_R51_DONE[?1004l[?9001l \ No newline at end of file diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/native-cases.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/native-cases.json new file mode 100644 index 00000000..d6b2024c --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/native-cases.json @@ -0,0 +1,330 @@ +{ + "schema": 1, + "started_utc": "2026-10-26T20:46:12.976424+00:00", + "windows": "10.0.26200", + "backend": { + "name": "Microsoft.Windows.Console.ConPTY", + "version": "1.25.260930003", + "url": "https://github.com/microsoft/terminal/releases/download/v1.25.2733.0/Microsoft.Windows.Console.ConPTY.1.25.260930003.nupkg", + "bytes": 1750533, + "sha256": "6d6f8b008c655d814c498d8205d4012dcd4d0b6dfc1096461d0a185d1e3caf03", + "files": { + "conpty.dll": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "OpenConsole.exe": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + } + }, + "creation_flags": 24, + "probe_sha256": "a570b55f7ef0f65be01b827112905a223fb32f37a0f645ce121ead1ed6555f59", + "replay_sha256": "c4cf0d9f68bb5bd688e601b87025f3237c8659f4d80e96bd4f67fa463463a13f", + "sources_sha256": { + "resize_native_run.py": "a7fdc198a9fde7cff00ce1f74031b680e83d130e5034a612e576340e037fa1f2", + "resize_native_probe.cpp": "ea0f26b3cfc322929193a89ea72d3e987a2ec9f3fda87241989f508cd431336a", + "session.hpp": "6945e6ffba1d61e66266f867d3cd124eb63c39da37e27662a5eaf544f60ed96b", + "backend.hpp": "5ee3ab95dc4b9aefce23ff7900cda2ecdd9cd09d1b1044074de2f5e572c31489" + }, + "cpr_replies_sent": false, + "diagnostic_only": true, + "presented_done": null, + "cases": [ + { + "case": 0, + "passed": true, + "exit_code": 0, + "timed_out": false, + "forced_outer_cleanup": false, + "job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "job_after_forced_cleanup": null, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "case": 0, + "child_exit": 37, + "active_children": 0, + "output_eof": true, + "bytes": 104, + "resize_offset": 68, + "creation_flags": 24 + }, + "replay": { + "reflow_history": { + "native_rows_equal": false, + "native_cursor_equal": false + }, + "native_viewport": { + "native_rows_equal": true, + "native_cursor_equal": true + } + }, + "files_sha256": { + "capture.json": "e742d9102fcc20cce032abb28ccba4f638bcc1869c0fc0697654e3afaedb9a8c", + "go": "4cd0e21a9a0795a14ec9aa5f0e7d1abff0492565770e43eafdf1e3e8afed1f33", + "native-after.json": "f2a0180d23afbc07ced80b775df853d494f2c2a4dd51c81ed0292d5805cc2bec", + "native-before.json": "5d5d5aad2764de6a86bbc8ae7da966a482ee551d5dd15307d15ad4e9a21dfb46", + "native_viewport.states.jsonl": "a821dce1d169cd0890b914cffadf792855d8e54c0f1da8cc81b864946aee4b95", + "native_viewport.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "parent.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "parent.stdout.bin": "9ee3d5cb19a9b033cd11cfdf85e9412d93b2859a8db3beb90489bab0d97e5739", + "plan.txt": "b168dd8cf0feafe6cdf16f210328cfa2d7345dbf15babe3d14db2a842009c625", + "reflow_history.states.jsonl": "84082bce93257353c0437e76c33d67049855b327875eda74207e51726154f478", + "reflow_history.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source.bin": "eadc97ee8bfe9eeb07261af1dfb07977b1f87b2f7884d67014e87bf31d1a57e4", + "transport.bin": "3312e6a95a1897d575bddb046cb67a63e3dca21e8003ae0885fb993b359ddac6" + } + }, + { + "case": 1, + "passed": true, + "exit_code": 0, + "timed_out": false, + "forced_outer_cleanup": false, + "job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "job_after_forced_cleanup": null, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "case": 1, + "child_exit": 37, + "active_children": 0, + "output_eof": true, + "bytes": 104, + "resize_offset": 68, + "creation_flags": 24 + }, + "replay": { + "reflow_history": { + "native_rows_equal": false, + "native_cursor_equal": false + }, + "native_viewport": { + "native_rows_equal": true, + "native_cursor_equal": true + } + }, + "files_sha256": { + "capture.json": "8ab700b25ab6634943f120d5332266ca7a9e59099797ca4d6e461bce29e5705b", + "go": "4cd0e21a9a0795a14ec9aa5f0e7d1abff0492565770e43eafdf1e3e8afed1f33", + "native-after.json": "2b154f802963e89b5e9f11e8e538880f2630749d30f1f9632cb19f8e7d076da0", + "native-before.json": "60f447bfdf502c98b2ea3f54cb7a33fff2cac4b53d9762d57082ebe191c526c0", + "native_viewport.states.jsonl": "15a5ca914be318e387b5fc2ccf753ce0d70c131134b29eb976047b843ef62544", + "native_viewport.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "parent.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "parent.stdout.bin": "019d2c63c311d523f438bfafbcdd61674c311c22270e8d867cb808b1e786d185", + "plan.txt": "c1bcd96e9c95e3c21cc4b408e9ea40cc5021f13b88a91d743efc22ac70003c8c", + "reflow_history.states.jsonl": "009dd85a20a9b9978de33275cce7eae305d8f7563a7f60bef8916a48f6d29a10", + "reflow_history.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source.bin": "304dcdd68af70924614c1a1bc1aee10bfdc853403af70f8923806dacc297c770", + "transport.bin": "356ea650a7caad777e9b0f3b87771c380e2026c6257402cbb349b94d1d10719f" + } + }, + { + "case": 2, + "passed": true, + "exit_code": 0, + "timed_out": false, + "forced_outer_cleanup": false, + "job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "job_after_forced_cleanup": null, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "case": 2, + "child_exit": 37, + "active_children": 0, + "output_eof": true, + "bytes": 104, + "resize_offset": 68, + "creation_flags": 24 + }, + "replay": { + "reflow_history": { + "native_rows_equal": false, + "native_cursor_equal": false + }, + "native_viewport": { + "native_rows_equal": true, + "native_cursor_equal": true + } + }, + "files_sha256": { + "capture.json": "3fbf908c17f86f3691db9b6a89962c229ce5cb98ebb1ba12b9638692a641fbcd", + "go": "4cd0e21a9a0795a14ec9aa5f0e7d1abff0492565770e43eafdf1e3e8afed1f33", + "native-after.json": "22b0886b647a71cd91f317097748a798905e8ebf1ee43e4105ffb7321e09807a", + "native-before.json": "c99e33188a761a2d3e0368422692bcb3692ebd9c472fa944f5f9e8aff8b162e6", + "native_viewport.states.jsonl": "a2d0e1dd831c097f1e88a7f2aa9be865ae65e016889251fd79d1eb5d35a1720f", + "native_viewport.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "parent.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "parent.stdout.bin": "440e0bd7905556ef498bd333555f4cba948ff7dc92c81531627b5305c3bfd492", + "plan.txt": "c1bcd96e9c95e3c21cc4b408e9ea40cc5021f13b88a91d743efc22ac70003c8c", + "reflow_history.states.jsonl": "9fc4978a7851c6eeb2156f0493b183a6555880d8b5fde3ba0a08135266cae1c0", + "reflow_history.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source.bin": "e218a542b2a6b18a995b9ec2673f9c54d43a2422125e382a07fffa4e7c8c156a", + "transport.bin": "899fa3110b3bb20cea20f9f8b715fc23d48c9f92adbe3438f85b2c7ed2b5017f" + } + }, + { + "case": 3, + "passed": true, + "exit_code": 0, + "timed_out": false, + "forced_outer_cleanup": false, + "job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "job_after_forced_cleanup": null, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "case": 3, + "child_exit": 37, + "active_children": 0, + "output_eof": true, + "bytes": 117, + "resize_offset": 81, + "creation_flags": 24 + }, + "replay": { + "reflow_history": { + "native_rows_equal": false, + "native_cursor_equal": false + }, + "native_viewport": { + "native_rows_equal": true, + "native_cursor_equal": true + } + }, + "files_sha256": { + "capture.json": "e6e425e3a94f0d9eb7d9a78ba23937ef9a1043bfca06ccfc7d6422036a584865", + "go": "4cd0e21a9a0795a14ec9aa5f0e7d1abff0492565770e43eafdf1e3e8afed1f33", + "native-after.json": "84889f050c31419780cdaac335be9e3302de905f53b42d88dc8b41c9425297a6", + "native-before.json": "c3f863c9a6c386ca80398353201857bede3e94e7fc447d56b9496d174bc8eeb3", + "native_viewport.states.jsonl": "1d318b4c79c46ca3bbfcf6b564d7cba4a1beb36519d66c8fb805cddd77fc979e", + "native_viewport.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "parent.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "parent.stdout.bin": "f35d46efc6e4f4151ca25cf94ed01cdab5bc0a09ed53bf1c17e5493b8e081990", + "plan.txt": "e6a9af25ca49a6fb60ef6338fcd7c507f18cdd8ab21abe618045bc535374429a", + "reflow_history.states.jsonl": "dff7e610f39b72e58739ab6c8dd20558d59c7c272883acfbec3a7bdbde3e5c72", + "reflow_history.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source.bin": "9cb572ecac93ddcf978058226140b4ace5fc6c969a71fc793b6904476503eb11", + "transport.bin": "56a2e81cdb18853b817137de53c7abc21b979f2f4605630e3bc10078851effc9" + } + }, + { + "case": 4, + "passed": true, + "exit_code": 0, + "timed_out": false, + "forced_outer_cleanup": false, + "job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "job_after_forced_cleanup": null, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "case": 4, + "child_exit": 37, + "active_children": 0, + "output_eof": true, + "bytes": 120, + "resize_offset": 84, + "creation_flags": 24 + }, + "replay": { + "reflow_history": { + "native_rows_equal": false, + "native_cursor_equal": false + }, + "native_viewport": { + "native_rows_equal": true, + "native_cursor_equal": true + } + }, + "files_sha256": { + "capture.json": "de391fe6ade5415ac487afb28e85927174a56ba1b87184da97aa0fe9cb8bf59c", + "go": "4cd0e21a9a0795a14ec9aa5f0e7d1abff0492565770e43eafdf1e3e8afed1f33", + "native-after.json": "590a730384f52d89474d7270070372991cbca06920cf552b6143b4d5407dc36c", + "native-before.json": "6b06a9e4ba13c89cbf685496c0f366f71c384b75c0da45d11c5864edfb97bc3f", + "native_viewport.states.jsonl": "bf314aeaa11317724d32569d029e891eec0b5fa6292c083a5538ddf4f93ab774", + "native_viewport.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "parent.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "parent.stdout.bin": "12352f6f723177360287bd565fd73b3521a2cd62fd563874328cd6041d3ae9a5", + "plan.txt": "1c386f02ec8ae813e323d66b543f9f014154d66d7f54433611fafd85f0a16311", + "reflow_history.states.jsonl": "889bb57844746676ed1718b600cf1742979d2c31e414c1f0a357b4ed294046bf", + "reflow_history.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source.bin": "7e419e6d600d8ec050531c34e69485ded92323815f19e9c8a76b2ccc0f9303c7", + "transport.bin": "074058e95259ab33d2853655c9a461460d60dedba5a0ea5eb21504af88754641" + } + }, + { + "case": 5, + "passed": true, + "exit_code": 0, + "timed_out": false, + "forced_outer_cleanup": false, + "job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "job_after_forced_cleanup": null, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "case": 5, + "child_exit": 37, + "active_children": 0, + "output_eof": true, + "bytes": 117, + "resize_offset": 81, + "creation_flags": 24 + }, + "replay": { + "reflow_history": { + "native_rows_equal": false, + "native_cursor_equal": false + }, + "native_viewport": { + "native_rows_equal": true, + "native_cursor_equal": true + } + }, + "files_sha256": { + "capture.json": "a11778c24628865e6b02a2ad8e8f4aa387db7d94e3952df170b66492065baaed", + "go": "4cd0e21a9a0795a14ec9aa5f0e7d1abff0492565770e43eafdf1e3e8afed1f33", + "native-after.json": "8bae95467855394186680f29db75b9556daa6888ca71c4237668759a60c11c86", + "native-before.json": "c3f863c9a6c386ca80398353201857bede3e94e7fc447d56b9496d174bc8eeb3", + "native_viewport.states.jsonl": "f9660bdcbb114d4bd713fa814b78e6c274845f82e827ae6da4b39131869e6ab9", + "native_viewport.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "parent.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "parent.stdout.bin": "e4a81e5682d8a7fc7cd841988098ae605dca427ae0e52aa09a90b63d44218304", + "plan.txt": "4cc8c160bc1f563dc62bb127b145b45df5d25f3fdd3b9c260851cc8bb721c49e", + "reflow_history.states.jsonl": "16f7a95da0d135c06dceb9d700b491d99fa339cc4c01c4a003bdb57875050294", + "reflow_history.stderr.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "source.bin": "9cb572ecac93ddcf978058226140b4ace5fc6c969a71fc793b6904476503eb11", + "transport.bin": "56a2e81cdb18853b817137de53c7abc21b979f2f4605630e3bc10078851effc9" + } + } + ] +} diff --git a/experiments/conpty/results/2026-10-27-native-viewport-resize/validation.json b/experiments/conpty/results/2026-10-27-native-viewport-resize/validation.json new file mode 100644 index 00000000..78e9acdc --- /dev/null +++ b/experiments/conpty/results/2026-10-27-native-viewport-resize/validation.json @@ -0,0 +1,1667 @@ +{ + "schema": 1, + "issue": 51, + "source_commit": "92eac12d5cd2868b469e8b9a10e68f5e20ee2cb0", + "base_commit": "e0b4d67e50bda6fe08cc02b43a475137133310b3", + "candidate_version": "0.26.1-dev+termforge.5", + "backend": { + "name": "Microsoft.Windows.Console.ConPTY", + "version": "1.25.260930003", + "url": "https://github.com/microsoft/terminal/releases/download/v1.25.2733.0/Microsoft.Windows.Console.ConPTY.1.25.260930003.nupkg", + "bytes": 1750533, + "sha256": "6d6f8b008c655d814c498d8205d4012dcd4d0b6dfc1096461d0a185d1e3caf03", + "files": { + "conpty.dll": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "OpenConsole.exe": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + } + }, + "creation_flags": 24, + "windows": "10.0.26200", + "resize_policy": "native_viewport only for explicit reviewed-backend candidate ShellWindow", + "sources_sha256": { + "CMakeLists.txt": "3db27130f32f57eb798872410506eea4a638e0841dfd5474fdbeb216fb774b5b", + "experiments/candidates/alacritty/Cargo.lock": "f7b8906cc63b6a5d66943f15c50a78c51f773face60229fcddf24b2d02911736", + "experiments/candidates/alacritty/include/termforge_candidate.h": "e3accd6b57cce18784f9bd14bbb5042bec985464fe440bf54f652a0ebecfee92", + "experiments/candidates/alacritty/run.py": "2d83240506bf03b4da92f7b2dc85adf40446aff25cd0f0dbe08132b611c2627e", + "experiments/candidates/alacritty/src/ffi.rs": "09ea991999db3d1948cb5c4a46cd8d12e8e8f70d7648f07d09b1dd82eeab9c04", + "experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml": "2b5fc3bab139b7f141fbe39ae32364617cf526c4d6a5ec2ce12efc97379e42b7", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs": "eedd587edfd00917b3f8397cd5683263a33e63675ba332eba50e3648d008e157", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/row.rs": "05e489c6ac0c0bf6ea50182316985c4fa59e9a7c977e808edd0de4267d87c077", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs": "0479445db15d75f006398ba7fbbebba763cbedb1c993d69b3e5fdc94746a62e2", + "experiments/conpty/backend.hpp": "5ee3ab95dc4b9aefce23ff7900cda2ecdd9cd09d1b1044074de2f5e572c31489", + "experiments/conpty/resize_native_probe.cpp": "ea0f26b3cfc322929193a89ea72d3e987a2ec9f3fda87241989f508cd431336a", + "experiments/conpty/resize_native_run.py": "a7fdc198a9fde7cff00ce1f74031b680e83d130e5034a612e576340e037fa1f2", + "experiments/conpty/session.hpp": "6945e6ffba1d61e66266f867d3cd124eb63c39da37e27662a5eaf544f60ed96b", + "experiments/core/candidate_resize_replay.cpp": "c3e6bcd7dc41622c8e407507c4cd9da39398197d76aa7d3ea042ae0bd4164a7b", + "experiments/core/candidate_terminal.cpp": "16eb43f2488e9621fc965d50be99a25ebf49b6d51ad69d2446fcc13bedda7bf5", + "experiments/core/candidate_terminal.hpp": "f4e51039bc5a10fd120ef8a5fc2b12fcb9ddb1c32a4ae36fc74f226ae22898a1", + "experiments/core/candidate_terminal_test.cpp": "097faf9c92cad3751ba3d0292739a041943c4f1cd7f4e4bc5fac72bc578aa3fa", + "experiments/core/test_candidate_resize_replay.py": "14a78930060f8c66c78a6e601312cb6997efe77330d3c226e9560d8c48ce0944", + "experiments/shell/resize_trace.py": "fd75a4a1a6d3d0ad8ea213da04a875f4bda210c6501e41b4b4004161b0037501", + "experiments/shell/run.py": "45976b828eecb316881d698dedaef09ad304f72d991dfa360f1c552bbcbe9f1d", + "experiments/shell/shell.cpp": "ab5f39acd6174e3a5a256dea6f59037758e3b521af91c183a67f7d021700a089", + "experiments/shell/size_client.cpp": "cb11661bec9551053eec0dbb474bce2ac899a06ee2e50830a370af4816df39f4", + "experiments/shell/test_launcher.cpp": "19cab4a960f47f252c02f92a36316d43eed2481da1f72b5a7e97003c4fa1c2ed", + "experiments/shell/text_client.cpp": "aea8ed527e6e79fbe61446afadbb25d4fa0b7ddef4966930583ac8fa04cadd15", + "experiments/shell/workflow_checks.py": "f186ff0f5335c26fc5c4f0286961981dfd6af8fbaafc789e2bc50058486efa44", + "experiments/shell/workflow_sessions_checks.py": "b976d3504837eef57ff13319daed254870727741f36f265412a6b14b4cb3faae", + "experiments/shell/workspace_workflow_scenario.hpp": "467571556440f4e89305bc1261d389bb8f24ba84f607b8cd36b8078a58314369" + }, + "binaries_sha256": { + "termforge_shell.exe": "15a149a3d7d47efaf866226559d4b60bbf83e0ff6004a85df41edcb5415ad206", + "candidate_core_test.exe": "4c73b58d7da111ab52b6edbbb59898cf660860fdedaa64c4f49b46fae856a02d", + "candidate_resize_replay.exe": "c4cf0d9f68bb5bd688e601b87025f3237c8659f4d80e96bd4f67fa463463a13f", + "conpty_resize_native_probe.exe": "a570b55f7ef0f65be01b827112905a223fb32f37a0f645ce121ead1ed6555f59", + "shell_test_launcher.exe": "7bd78cffd78ac66d651933c9614d6be937c99baa105ce04c1a533df5785c11e2", + "shell_size_client.exe": "eeb0801f69cc8add0a8178a185dcedb78a9b33234abfe5c600fd9b20b0f5aab4", + "shell_text_client.exe": "9350e53de74c9a6b78495a3b59788e3a31e0067a9c5ec1b8685a729ff18244d3", + "shell_query_client.exe": "691b440e9c500c76c1c09c448aafbe2316793bf341ffe69bbc2bf70e41be64cc" + }, + "source_hash_policy": "Raw worktree bytes used for builds; Git checkout line endings can differ.", + "baseline_gui": { + "source": "e0b4d67e50bda6fe08cc02b43a475137133310b3", + "diagnostic_only": true, + "source_patch_applied": false, + "patched_resize_sha256": "4907427c9488435f1e1ee624629bc8445c4dde5399f0349955d1bbc2042080c4", + "binary_sha256": "e4be16054f1c43528cdaca0f54b664f856843669a43000778b751b722026dff4", + "cases": [ + { + "case": "resize-0", + "outcome": "failure", + "exit_code": 2, + "timed_out": false, + "job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "dbf63d052fae858dca4ec36d8884f2b919515defad23bec749a994eab1b9e393", + "stage": 43 + } + ], + "started_utc": "2026-10-26T19:54:02.639884+00:00" + }, + "formal_core_red": { + "exit_code": 1, + "error": "native viewport text differs", + "identity": { + "experiments/core/candidate_terminal.hpp": "f4e51039bc5a10fd120ef8a5fc2b12fcb9ddb1c32a4ae36fc74f226ae22898a1", + "experiments/core/candidate_terminal.cpp": "16eb43f2488e9621fc965d50be99a25ebf49b6d51ad69d2446fcc13bedda7bf5", + "experiments/core/candidate_terminal_test.cpp": "310ef633d13b909d5ad0d659cffc4cbe81b2f3d84bbd87400b13e54051bfc772", + "experiments/candidates/alacritty/src/ffi.rs": "0824acd1e13f74254ee38976485c21d79e6ae4feb38c4634ce3aef163315e6d4", + "experiments/candidates/alacritty/include/termforge_candidate.h": "e3accd6b57cce18784f9bd14bbb5042bec985464fe440bf54f652a0ebecfee92", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs": "4907427c9488435f1e1ee624629bc8445c4dde5399f0349955d1bbc2042080c4", + "test_exe_sha256": "0f5bd0454c288eb300368ab0090f2b917b618f74f32bde7522658b0db03f0ee8" + }, + "log_sha256": "2fb33922305c15a0912d40fe556589f7092375ffa05a903aa16b99a1f0a40dc2" + }, + "original_native_capture": { + "source": "188228ebe1a4e6cd3779fc7c6b150624514886fb", + "shell_sha256": "e81d92a8c7d380d4c33639861180b04935e021bc7c1a5179197c8a9e3c83454a", + "note": "Temporary size helper snapshots perturb timing; native row10 exact, candidate row10 had OW.", + "files_sha256": { + "case-0/resize-trace.json": "b3d57213f4da6a94e2a672ddd5d2e5690ad4604636c16a0835d506ea6c3d76f8", + "case-0/resize-trace.transport.bin": "2b4deb1d932af810cef0892064a13b481c582ad44a4faa02cd08fe502d410feb", + "case-0/r51-native-size-100x28-after.json": "67d2217d0c03212a87b4ccaac822cf9cbd93380a337f30ac91bdb988e0644ba0", + "case-0/sample.json": "87624dc16ace15e803bea774aa7af253099f9588975ddabfab0c4ccc7d04cb81", + "replay-0/plan.txt": "bab0875e0f31908a1c5f1a8194ee43b47113a78af1da28f618a17677bf29f57b" + }, + "native_row10": "TF_SIZE_100x28", + "candidate_row10": "TF_SIZE_100x28OW" + }, + "final_original_replay": { + "replay_exe_sha256": "c4cf0d9f68bb5bd688e601b87025f3237c8659f4d80e96bd4f67fa463463a13f", + "options": [ + "--native-viewport" + ], + "same_native_rows_0_through_10": true, + "exact_marker": true, + "history_rows": 9, + "cursor": [ + 12, + 3 + ], + "diagnostic_only": true + }, + "final_gui": { + "source": "92eac12d5cd2868b469e8b9a10e68f5e20ee2cb0", + "diagnostic_only": true, + "source_patch_applied": false, + "patched_resize_sha256": "eedd587edfd00917b3f8397cd5683263a33e63675ba332eba50e3648d008e157", + "binary_sha256": "15a149a3d7d47efaf866226559d4b60bbf83e0ff6004a85df41edcb5415ad206", + "cases": [ + { + "case": "resize-0", + "outcome": "green", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "b113686ea1536b96932401ec5caa6e678db2146d46ad8f43250729d5b0c037e0", + "result": { + "valid": true, + "exit_code": 37, + "active_children_after": 0, + "cleanup_complete": true, + "command_verified": true, + "unicode_verified": true, + "backspace_verified": true, + "arrows_verified": true, + "ctrl_c_verified": true, + "resize_verified": 3, + "rows": 18, + "columns": 72, + "input_rejected": 0, + "presented_done": null + } + }, + { + "case": "resize-1", + "outcome": "green", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "95eedda4e52bb2c78bf445fc068d8a7cf2acbd6952b63c8bf41ad68136862f1c", + "result": { + "valid": true, + "exit_code": 37, + "active_children_after": 0, + "cleanup_complete": true, + "command_verified": true, + "unicode_verified": true, + "backspace_verified": true, + "arrows_verified": true, + "ctrl_c_verified": true, + "resize_verified": 3, + "rows": 18, + "columns": 72, + "input_rejected": 0, + "presented_done": null + } + }, + { + "case": "resize-close-2", + "outcome": "green", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "4bfea5d8f916c27192b1d539d545e1df5ddc0fd04e338aae239fda902027a394", + "result": { + "valid": true, + "exit_code": 91, + "active_children_after": 0, + "cleanup_complete": true, + "command_verified": true, + "unicode_verified": true, + "backspace_verified": true, + "arrows_verified": true, + "ctrl_c_verified": false, + "resize_verified": 2, + "rows": 18, + "columns": 72, + "input_rejected": 0, + "presented_done": null + } + }, + { + "case": "candidate-unicode-3", + "outcome": "green", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "a3c614c702d92ac849cf287923da9593deeeb0a0506545c27876945904c119c9", + "result": { + "valid": true, + "exit_code": 37, + "active_children_after": 0, + "cleanup_complete": true, + "command_verified": true, + "unicode_verified": false, + "backspace_verified": false, + "arrows_verified": false, + "ctrl_c_verified": false, + "resize_verified": 0, + "rows": 30, + "columns": 100, + "input_rejected": 0, + "presented_done": null + }, + "unicode_output": { + "scope": "fixed-four-streaming-graphemes-width-selection-glyph", + "source_sha256": "a3b951ab67ca5d079f7e91ee8f1cd25dfde82ae4b08ed40c406c911e0ccdf8c2", + "transport_sha256": "c115bf2fd28e7a602993c30985853b895409884c0fba65fd697fbd1f89173c0e", + "transport_bytes": 3353, + "scoped_validation_passed": true, + "complex_grapheme_rendering_verified": true, + "whole_cluster_width_policy_verified": true, + "resized_native_console_verified": false, + "windterm_runtime_equivalence_verified": false, + "complete_unicode_ime_verified": false, + "presented_done": null, + "resize": { + "scope": "fixed-four-streaming-graphemes-width-selection-glyph", + "source_sha256": "a3b951ab67ca5d079f7e91ee8f1cd25dfde82ae4b08ed40c406c911e0ccdf8c2", + "transport_sha256": "d3b472c45f7ed73f9d1c4b0fb190e974cc1f39c0522d495d30402639957859c5", + "transport_bytes": 3819, + "scoped_validation_passed": true, + "complex_grapheme_rendering_verified": true, + "whole_cluster_width_policy_verified": true, + "resized_native_console_verified": true, + "windterm_runtime_equivalence_verified": false, + "complete_unicode_ime_verified": false, + "presented_done": null + } + }, + "result_field_note": "Generic result.unicode_verified belongs to the basic shell scenario; candidate-unicode acceptance is unicode_output.scoped_validation_passed and its resize record." + }, + { + "case": "candidate-unicode-4", + "outcome": "green", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "4fabd30042d7a6053663a2b3f084ea179926f211cb1acb634291877637349dd8", + "result": { + "valid": true, + "exit_code": 37, + "active_children_after": 0, + "cleanup_complete": true, + "command_verified": true, + "unicode_verified": false, + "backspace_verified": false, + "arrows_verified": false, + "ctrl_c_verified": false, + "resize_verified": 0, + "rows": 30, + "columns": 100, + "input_rejected": 0, + "presented_done": null + }, + "unicode_output": { + "scope": "fixed-four-streaming-graphemes-width-selection-glyph", + "source_sha256": "a3b951ab67ca5d079f7e91ee8f1cd25dfde82ae4b08ed40c406c911e0ccdf8c2", + "transport_sha256": "bff603002c1b35097b27bba74fd6fafa89eb44c3948b409c0c5feb9ffa40c32d", + "transport_bytes": 3353, + "scoped_validation_passed": true, + "complex_grapheme_rendering_verified": true, + "whole_cluster_width_policy_verified": true, + "resized_native_console_verified": false, + "windterm_runtime_equivalence_verified": false, + "complete_unicode_ime_verified": false, + "presented_done": null, + "resize": { + "scope": "fixed-four-streaming-graphemes-width-selection-glyph", + "source_sha256": "a3b951ab67ca5d079f7e91ee8f1cd25dfde82ae4b08ed40c406c911e0ccdf8c2", + "transport_sha256": "f50293197cfaeb077453e378f58c38c5c878e89d55f7833d2e0600e8ad2889cd", + "transport_bytes": 4001, + "scoped_validation_passed": true, + "complex_grapheme_rendering_verified": true, + "whole_cluster_width_policy_verified": true, + "resized_native_console_verified": true, + "windterm_runtime_equivalence_verified": false, + "complete_unicode_ime_verified": false, + "presented_done": null + } + }, + "result_field_note": "Generic result.unicode_verified belongs to the basic shell scenario; candidate-unicode acceptance is unicode_output.scoped_validation_passed and its resize record." + } + ], + "started_utc": "2026-10-26T20:46:03.844348+00:00" + }, + "native_cases_file": "native-cases.json", + "written_spaces_regression": { + "red": { + "source": "f40d347 plus written-space regression", + "files": { + "experiments/core/candidate_terminal_test.cpp": "097faf9c92cad3751ba3d0292739a041943c4f1cd7f4e4bc5fac72bc578aa3fa", + "experiments/candidates/alacritty/src/ffi.rs": "04f2b0a3cb66c3b588d083b861781eae6af83a2b03104d779f156a892bbd7517", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/row.rs": "2f963299e52525828d9d0bf1de758d793661c908eb7c126aa8556a1c9945a83d", + "experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs": "a1f0d8bcd133208f12b2eb0c96edf04be6dd7bae9cb3a95db6a2086cf8833bc3", + "build/candidate/bin/Release/candidate_core_test.exe": "f574f415c509ac3c78a8b048dfab3ae789d4a6d63591f0256c4492dcb9195233" + }, + "exit_code": 1, + "error": "resize changed written spaces" + }, + "red_log_sha256": "6c6ef8d9801b3020b3da305423cfd81ae0fad3197f980cdb5d21330dcdc5a39d", + "green_log_sha256": "2d790a6cc2d35b2379ea0385b384c326a361db1e5cb550d0fb209a80901b0c26", + "input": "ABC D", + "widths": [ + 4, + 5, + 8, + 4, + 5 + ], + "policies": [ + "reflow_history", + "native_viewport" + ], + "passed": true + }, + "auxiliary_review_checks": { + "source": "92eac12d5cd2868b469e8b9a10e68f5e20ee2cb0", + "native_copy_assertions": 630, + "files_sha256": { + "native-compile.log": "dad173ee0c7a5afd1408be074bfdfa13e828e1c1c71865b477a9a9ef90e058d9", + "native-probe.exe": "0a6d3f643dde129526b506d356970b311ff46a40a449ac1515da3783a25b52f9", + "native-run.log": "564666081a6201b624a62f3bd3daebf776bb83b024e29a8e93408e1bd1beb020", + "probe.cpp": "b8926e08ec1b369ebcc35bc74435863fb5cf999c902dc4832ba02a290b7f505f" + }, + "scope": "Additional reviewer diagnostic; not a native-oracle or performance comparison." + }, + "native_probe_negatives": { + "source": "92eac12d5cd2868b469e8b9a10e68f5e20ee2cb0", + "runner_sha256": "a7fdc198a9fde7cff00ce1f74031b680e83d130e5034a612e576340e037fa1f2", + "cases": [ + { + "name": "no-go", + "case": 0, + "exit_code": 2, + "timed_out": false, + "forced_outer_cleanup": false, + "job_after": { + "active": 0, + "total": 2, + "terminated": 0 + }, + "job_after_forced_cleanup": null, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [] + }, + { + "name": "watchdog", + "case": 0, + "exit_code": 99, + "timed_out": true, + "forced_outer_cleanup": true, + "job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "job_after_forced_cleanup": null, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [] + }, + { + "name": "missing-executable-and-unwritable-evidence", + "original_FileNotFoundError_preserved": true, + "winerror": 2, + "existing_file_unchanged": true + } + ] + }, + "rust_tests": { + "passed_per_target": [ + 157, + 2, + 2, + 2, + 1 + ], + "log_sha256": "798884c07803a574adecf796b5da6b8279639922a908b52d969abe44de62a42b" + }, + "initial_full_ctest": { + "log_sha256": "a04c06b541b15e23716ce4b542138d5a6844acb548944ff5c1a3d359c1317d9f", + "tests": [ + { + "name": "shell_launch", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "candidate_title", + "result": "passed", + "seconds": 0.72 + }, + { + "name": "candidate_reply_close", + "result": "passed", + "seconds": 0.36 + }, + { + "name": "runtime_observation", + "result": "passed", + "seconds": 1.61 + }, + { + "name": "ime_input_expectation", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "sent_message_wait", + "result": "passed", + "seconds": 0.3 + }, + { + "name": "find_bar", + "result": "passed", + "seconds": 0.39 + }, + { + "name": "renderer_metrics", + "result": "passed", + "seconds": 0.96 + }, + { + "name": "ime_context", + "result": "passed", + "seconds": 0.41 + }, + { + "name": "layout_state", + "result": "passed", + "seconds": 0.15 + }, + { + "name": "workspace_state", + "result": "passed", + "seconds": 0.38 + }, + { + "name": "transport_completion", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "scenario_tick_gate", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "candidate_core", + "result": "passed", + "seconds": 1.33 + }, + { + "name": "resize_core", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "history_core", + "result": "passed", + "seconds": 0.03 + }, + { + "name": "selection_core", + "result": "passed", + "seconds": 0.11 + }, + { + "name": "search_core", + "result": "passed", + "seconds": 0.15 + }, + { + "name": "paste", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "sender_fence", + "result": "passed", + "seconds": 1.03 + }, + { + "name": "sender_fence_pipe", + "result": "passed", + "seconds": 0.39 + }, + { + "name": "clipboard_policy", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "keyboard", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "text_action_policy", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "candidate_resize_replay", + "result": "passed", + "seconds": 0.7 + }, + { + "name": "candidate_shell_tui", + "result": "failed", + "seconds": 31.03 + }, + { + "name": "core", + "result": "passed", + "seconds": 6.59 + }, + { + "name": "recovery", + "result": "passed", + "seconds": 13.14 + }, + { + "name": "measure", + "result": "passed", + "seconds": 5.17 + }, + { + "name": "binary_paths", + "result": "passed", + "seconds": 0.21 + }, + { + "name": "conpty_margin_contract", + "result": "passed", + "seconds": 0.34 + }, + { + "name": "benchmark_tools", + "result": "passed", + "seconds": 5.67 + }, + { + "name": "differential", + "result": "passed", + "seconds": 3.55 + }, + { + "name": "ime_inventory", + "result": "passed", + "seconds": 54.43 + }, + { + "name": "text_actions", + "result": "passed", + "seconds": 5.58 + }, + { + "name": "workflow_fixture", + "result": "passed", + "seconds": 22.86 + }, + { + "name": "workflow_sessions", + "result": "failed", + "seconds": 39.01 + }, + { + "name": "workflow_resources", + "result": "passed", + "seconds": 0.5 + }, + { + "name": "workflow_resource_phases", + "result": "passed", + "seconds": 4.37 + }, + { + "name": "workflow_system_content", + "result": "passed", + "seconds": 0.18 + }, + { + "name": "workflow_system_runner", + "result": "passed", + "seconds": 0.66 + }, + { + "name": "copy_error_frame", + "result": "passed", + "seconds": 0.15 + }, + { + "name": "paste_fence_evidence", + "result": "passed", + "seconds": 0.11 + }, + { + "name": "resize_trace", + "result": "passed", + "seconds": 0.13 + }, + { + "name": "research_guards", + "result": "passed", + "seconds": 5.07 + }, + { + "name": "receive", + "result": "passed", + "seconds": 0.21 + }, + { + "name": "conpty", + "result": "passed", + "seconds": 23.1 + }, + { + "name": "conpty_ctrl_c", + "result": "passed", + "seconds": 4.34 + }, + { + "name": "conpty_margin", + "result": "passed", + "seconds": 0.38 + }, + { + "name": "conpty_backend", + "result": "passed", + "seconds": 1.19 + }, + { + "name": "presentation", + "result": "passed", + "seconds": 4.19 + }, + { + "name": "shell", + "result": "passed", + "seconds": 96.68 + }, + { + "name": "selection_bytes", + "result": "passed", + "seconds": 3.7 + }, + { + "name": "selection_context", + "result": "passed", + "seconds": 3.78 + }, + { + "name": "raw_console_receiver", + "result": "passed", + "seconds": 0.41 + }, + { + "name": "paste_system", + "result": "passed", + "seconds": 8.59 + } + ], + "total": 56, + "passed": 54, + "failed": [ + "candidate_shell_tui", + "workflow_sessions" + ], + "seconds": 354.65, + "failure_stages": [ + 50 + ], + "full_acceptance": false, + "product_source_commit": "57f24f5a2204a38bcc6429cc448247a7893ef349", + "shell_sha256": "4e23e277ea7984d85856b713b3af5b0545d68042c2bbd42a8422f8a6b422c28d" + }, + "fixed_path_intermediate_ctest": { + "log_sha256": "068ef8ad11e19c697e95df4e9d8bd8a2210c8e075e02550bf0ed706913949000", + "tests": [ + { + "name": "shell_launch", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "candidate_title", + "result": "passed", + "seconds": 0.88 + }, + { + "name": "candidate_reply_close", + "result": "passed", + "seconds": 0.33 + }, + { + "name": "runtime_observation", + "result": "passed", + "seconds": 1.43 + }, + { + "name": "ime_input_expectation", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "sent_message_wait", + "result": "passed", + "seconds": 0.29 + }, + { + "name": "find_bar", + "result": "passed", + "seconds": 0.35 + }, + { + "name": "renderer_metrics", + "result": "passed", + "seconds": 0.94 + }, + { + "name": "ime_context", + "result": "passed", + "seconds": 0.39 + }, + { + "name": "layout_state", + "result": "passed", + "seconds": 0.14 + }, + { + "name": "workspace_state", + "result": "passed", + "seconds": 0.35 + }, + { + "name": "transport_completion", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "scenario_tick_gate", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "candidate_core", + "result": "passed", + "seconds": 1.22 + }, + { + "name": "resize_core", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "history_core", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "selection_core", + "result": "passed", + "seconds": 0.1 + }, + { + "name": "search_core", + "result": "passed", + "seconds": 0.12 + }, + { + "name": "paste", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "sender_fence", + "result": "passed", + "seconds": 1.02 + }, + { + "name": "sender_fence_pipe", + "result": "passed", + "seconds": 0.37 + }, + { + "name": "clipboard_policy", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "keyboard", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "text_action_policy", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "candidate_resize_replay", + "result": "passed", + "seconds": 0.61 + }, + { + "name": "candidate_shell_tui", + "result": "failed", + "seconds": 30.57 + }, + { + "name": "core", + "result": "passed", + "seconds": 6.28 + }, + { + "name": "recovery", + "result": "passed", + "seconds": 10.45 + }, + { + "name": "measure", + "result": "passed", + "seconds": 4.02 + }, + { + "name": "binary_paths", + "result": "passed", + "seconds": 0.18 + }, + { + "name": "conpty_margin_contract", + "result": "passed", + "seconds": 0.36 + }, + { + "name": "benchmark_tools", + "result": "passed", + "seconds": 5.54 + }, + { + "name": "differential", + "result": "passed", + "seconds": 3.38 + }, + { + "name": "ime_inventory", + "result": "passed", + "seconds": 50.46 + }, + { + "name": "text_actions", + "result": "passed", + "seconds": 5.62 + }, + { + "name": "workflow_fixture", + "result": "passed", + "seconds": 22.72 + }, + { + "name": "workflow_sessions", + "result": "failed", + "seconds": 80.03 + }, + { + "name": "workflow_resources", + "result": "passed", + "seconds": 0.54 + }, + { + "name": "workflow_resource_phases", + "result": "passed", + "seconds": 4.43 + }, + { + "name": "workflow_system_content", + "result": "passed", + "seconds": 0.18 + }, + { + "name": "workflow_system_runner", + "result": "passed", + "seconds": 0.66 + }, + { + "name": "copy_error_frame", + "result": "passed", + "seconds": 0.14 + }, + { + "name": "paste_fence_evidence", + "result": "passed", + "seconds": 0.11 + }, + { + "name": "resize_trace", + "result": "passed", + "seconds": 0.13 + }, + { + "name": "research_guards", + "result": "passed", + "seconds": 5.0 + }, + { + "name": "receive", + "result": "passed", + "seconds": 0.21 + }, + { + "name": "conpty", + "result": "passed", + "seconds": 23.06 + }, + { + "name": "conpty_ctrl_c", + "result": "passed", + "seconds": 4.32 + }, + { + "name": "conpty_margin", + "result": "passed", + "seconds": 0.42 + }, + { + "name": "conpty_backend", + "result": "passed", + "seconds": 1.19 + }, + { + "name": "presentation", + "result": "passed", + "seconds": 4.15 + }, + { + "name": "shell", + "result": "passed", + "seconds": 97.02 + }, + { + "name": "selection_bytes", + "result": "passed", + "seconds": 3.56 + }, + { + "name": "selection_context", + "result": "passed", + "seconds": 3.8 + }, + { + "name": "raw_console_receiver", + "result": "passed", + "seconds": 0.39 + }, + { + "name": "paste_system", + "result": "passed", + "seconds": 8.43 + } + ], + "total": 56, + "passed": 54, + "failed": [ + "candidate_shell_tui", + "workflow_sessions" + ], + "seconds": 386.09, + "failure_stages": [ + 59 + ], + "full_acceptance": false, + "product_source_commit": "57f24f5a2204a38bcc6429cc448247a7893ef349", + "shell_sha256": "4e23e277ea7984d85856b713b3af5b0545d68042c2bbd42a8422f8a6b422c28d" + }, + "final_full_ctest": { + "log_sha256": "e9a8ea36af3b9b72665ee29c6e87a7cac15fe5531fe7c23e72bb27a60f95c17f", + "tests": [ + { + "name": "shell_launch", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "candidate_title", + "result": "passed", + "seconds": 0.9 + }, + { + "name": "candidate_reply_close", + "result": "passed", + "seconds": 0.36 + }, + { + "name": "runtime_observation", + "result": "passed", + "seconds": 1.56 + }, + { + "name": "ime_input_expectation", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "sent_message_wait", + "result": "passed", + "seconds": 0.3 + }, + { + "name": "find_bar", + "result": "passed", + "seconds": 0.36 + }, + { + "name": "renderer_metrics", + "result": "passed", + "seconds": 0.89 + }, + { + "name": "ime_context", + "result": "passed", + "seconds": 0.33 + }, + { + "name": "layout_state", + "result": "passed", + "seconds": 0.14 + }, + { + "name": "workspace_state", + "result": "passed", + "seconds": 0.37 + }, + { + "name": "transport_completion", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "scenario_tick_gate", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "candidate_core", + "result": "passed", + "seconds": 1.34 + }, + { + "name": "resize_core", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "history_core", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "selection_core", + "result": "passed", + "seconds": 0.09 + }, + { + "name": "search_core", + "result": "passed", + "seconds": 0.15 + }, + { + "name": "paste", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "sender_fence", + "result": "passed", + "seconds": 1.03 + }, + { + "name": "sender_fence_pipe", + "result": "passed", + "seconds": 0.38 + }, + { + "name": "clipboard_policy", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "keyboard", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "text_action_policy", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "candidate_resize_replay", + "result": "passed", + "seconds": 0.72 + }, + { + "name": "candidate_shell_tui", + "result": "failed", + "seconds": 30.51 + }, + { + "name": "core", + "result": "passed", + "seconds": 6.93 + }, + { + "name": "recovery", + "result": "passed", + "seconds": 10.97 + }, + { + "name": "measure", + "result": "passed", + "seconds": 4.03 + }, + { + "name": "binary_paths", + "result": "passed", + "seconds": 0.18 + }, + { + "name": "conpty_margin_contract", + "result": "passed", + "seconds": 0.29 + }, + { + "name": "benchmark_tools", + "result": "passed", + "seconds": 5.4 + }, + { + "name": "differential", + "result": "passed", + "seconds": 3.32 + }, + { + "name": "ime_inventory", + "result": "passed", + "seconds": 50.32 + }, + { + "name": "text_actions", + "result": "passed", + "seconds": 5.59 + }, + { + "name": "workflow_fixture", + "result": "passed", + "seconds": 22.88 + }, + { + "name": "workflow_sessions", + "result": "passed", + "seconds": 81.41 + }, + { + "name": "workflow_resources", + "result": "passed", + "seconds": 0.48 + }, + { + "name": "workflow_resource_phases", + "result": "passed", + "seconds": 4.31 + }, + { + "name": "workflow_system_content", + "result": "passed", + "seconds": 0.16 + }, + { + "name": "workflow_system_runner", + "result": "passed", + "seconds": 0.67 + }, + { + "name": "copy_error_frame", + "result": "passed", + "seconds": 0.14 + }, + { + "name": "paste_fence_evidence", + "result": "passed", + "seconds": 0.11 + }, + { + "name": "resize_trace", + "result": "passed", + "seconds": 0.12 + }, + { + "name": "research_guards", + "result": "passed", + "seconds": 5.0 + }, + { + "name": "receive", + "result": "passed", + "seconds": 0.23 + }, + { + "name": "conpty", + "result": "passed", + "seconds": 22.44 + }, + { + "name": "conpty_ctrl_c", + "result": "passed", + "seconds": 4.47 + }, + { + "name": "conpty_margin", + "result": "passed", + "seconds": 0.57 + }, + { + "name": "conpty_backend", + "result": "passed", + "seconds": 1.34 + }, + { + "name": "presentation", + "result": "passed", + "seconds": 4.25 + }, + { + "name": "shell", + "result": "failed", + "seconds": 121.65 + }, + { + "name": "selection_bytes", + "result": "passed", + "seconds": 3.76 + }, + { + "name": "selection_context", + "result": "passed", + "seconds": 3.81 + }, + { + "name": "raw_console_receiver", + "result": "passed", + "seconds": 0.39 + }, + { + "name": "paste_system", + "result": "passed", + "seconds": 6.72 + } + ], + "total": 56, + "passed": 54, + "failed": [ + "candidate_shell_tui", + "shell" + ], + "seconds": 411.59, + "failure_stages": [ + 59 + ], + "full_acceptance": false, + "last_test_log_sha256": "cc3f6814ba85db77091830cf20f54afcd66c5da826e6ccda539533dce7bbfc28", + "internal_skips": [ + { + "test": "setUpClass (test_candidate_compare.CandidateComparisonTests)", + "reason": "optional Alacritty binary is not built; core-candidate workflow runs these checks" + }, + { + "test": "setUpClass (test_capability_matrix.CapabilityMatrixTests)", + "reason": "optional Alacritty binary is not built; core-candidate workflow runs these checks" + }, + { + "test": "setUpClass (test_fuzz_matrix.FuzzMatrixTests)", + "reason": "optional Alacritty binary is not built; core-candidate workflow runs these checks" + }, + { + "test": "setUpClass (test_real_app_matrix.RealApplicationMatrixTests)", + "reason": "optional real application capture dependencies are not built" + }, + { + "test": "test_actual_system_copy_paste_and_shortcuts_twice (experiments.shell.test_actions.ActionWindowTest.test_actual_system_copy_paste_and_shortcuts_twice)", + "reason": "actual system Copy/Paste requires a disposable hosted VM" + }, + { + "test": "test_suspended_target_is_reclaimed_if_job_assignment_fails (test_ui_r1.EvidenceBoundaryTests.test_suspended_target_is_reclaimed_if_job_assignment_fails)", + "reason": "opt-in pinned reference failure injection" + }, + { + "test": "test_mouse_selection_to_real_system_clipboard (experiments.shell.test_shell.ShellTests.test_mouse_selection_to_real_system_clipboard)", + "reason": "system copy UI gate requires a disposable hosted VM" + }, + { + "test": "test_system_clipboard_product_read_errors_cancel_and_close (experiments.shell.test_shell.ShellTests.test_system_clipboard_product_read_errors_cancel_and_close)", + "reason": "product clipboard read gate requires a disposable hosted VM" + }, + { + "test": "test_system_clipboard_shortcut_to_window_confirmation_and_receiver (experiments.shell.test_shell.ShellTests.test_system_clipboard_shortcut_to_window_confirmation_and_receiver)", + "reason": "real system clipboard UI gate requires a disposable hosted VM" + }, + { + "test": "test_real_system_copy_shortcuts_for_styled_fixture_twice (experiments.shell.test_selection_bytes.SelectionBytesWindowTest.test_real_system_copy_shortcuts_for_styled_fixture_twice)", + "reason": "system CF requires a disposable hosted VM" + }, + { + "test": "test_real_system_copy_and_edit_context_twice (experiments.shell.test_selection_context.SelectionContextWindowTest.test_real_system_copy_and_edit_context_twice)", + "reason": "real CF/EDIT Copy requires a disposable hosted VM" + }, + { + "test": "test_real_system_clipboard (experiments.shell.test_paste.PasteTests.test_real_system_clipboard)", + "reason": "Private window station OpenClipboard denied (5); system API gate remains unverified." + } + ], + "log_encoding_note": "CTest localized date header is not UTF-8; only ASCII skip fields are extracted. Raw log hash is preserved.", + "shell_unittest": { + "run": 40, + "passed": 36, + "errors": 1, + "skipped": 3, + "seconds": 121.382 + }, + "product_source_commit": "92eac12d5cd2868b469e8b9a10e68f5e20ee2cb0", + "shell_sha256": "15a149a3d7d47efaf866226559d4b60bbf83e0ff6004a85df41edcb5415ad206" + }, + "vim_environment": { + "vim_sha256": "2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a", + "msys_sha256": "3096d2ba9be20e90be1399edd391bac65d84bc387f5a419b502c9b326081930f", + "initial_run": "Vim absent from process PATH; stage50 is not the original stage59.", + "isolated_intermediate": "Original two-round candidate_shell_tui passed once after process-only PATH fix, 6.95s.", + "full_intermediate": "Original stage59 reproduced in corrected full run; #52 remains open." + }, + "workflow_issue": 87, + "workflow_comparison": [ + { + "label": "baseline", + "shell_sha256": "e81d92a8c7d380d4c33639861180b04935e021bc7c1a5179197c8a9e3c83454a", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "error": null, + "sample_sha256": "213eccd11e89c1e4bbd0e2261ad43cf42772ac7c880ffe13d2ad4c17b52f8dd6" + }, + { + "label": "native_viewport", + "shell_sha256": "4e23e277ea7984d85856b713b3af5b0545d68042c2bbd42a8422f8a6b422c28d", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "error": null, + "sample_sha256": "9aa0079ca1b0ee43e186bd91d633490b80ee16ffc57bcf68f96b5c602994fc56" + } + ], + "workflow_mismatches": [ + { + "run": "run-89580b0cae404944b12e03e031dab1bd", + "bad": [ + { + "cycle": 1, + "name": "F5", + "source_bytes": 16384, + "actual": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301............", + "expected": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301...................................", + "exact_prefix": true, + "output": { + "queued_bytes": 8217, + "high_water_bytes": 8217, + "stopped": false + } + }, + { + "cycle": 1, + "name": "F5", + "source_bytes": 16384, + "actual": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301............", + "expected": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301...................................", + "exact_prefix": true, + "output": { + "queued_bytes": 8217, + "high_water_bytes": 8217, + "stopped": false + } + } + ] + }, + { + "run": "run-8abb195e8f824858adbc0149ce1bbca6", + "bad": [ + { + "cycle": 2, + "name": "F5", + "source_bytes": 16384, + "actual": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301............", + "expected": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301...................................", + "exact_prefix": true, + "output": { + "queued_bytes": 8217, + "high_water_bytes": 8217, + "stopped": false + } + }, + { + "cycle": 2, + "name": "F5", + "source_bytes": 16384, + "actual": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301............", + "expected": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301...................................", + "exact_prefix": true, + "output": { + "queued_bytes": 8217, + "high_water_bytes": 8217, + "stopped": false + } + } + ] + }, + { + "run": "run-9266bc270e244e05a0073d86f00ae8ae", + "bad": [] + } + ], + "workflow_runs": [ + { + "run": "run-3b821fd77c1c4e60870eeee8839d485d", + "validation_sha256": "b5bf6fe8e8dc71d8d9285378095ad9a542a74de68bf82ad4cbeafc1fb3ffae84", + "status": "passed", + "round": 1, + "sample_sha256": "915bfe48f290e082e1c461a167b1d5723217a0285de87d6597b0e2c830b99cf3", + "sessions_sha256": "0acec32f3af02cab9f89949382858ba19c121c1d1b6f8e0b1d06babc640bb39c", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "shell_sha256": "15a149a3d7d47efaf866226559d4b60bbf83e0ff6004a85df41edcb5415ad206" + }, + { + "run": "run-6515c0088c384c7c94542620ff12380b", + "validation_sha256": "450bd976cc67633db7b4a90aa28a3f818c9b188bc6281eb7fca0d3741cc553a1", + "status": "passed", + "round": 0, + "sample_sha256": "57c7f8bab95f8f98fa8b7dbae6110d18e68d4f73d1d689265c2b1fdc7e863e96", + "sessions_sha256": "e5d39eaf107c4dd112f52d1562ec6820033c356904975c4c72024100dac36b06", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "shell_sha256": "15a149a3d7d47efaf866226559d4b60bbf83e0ff6004a85df41edcb5415ad206" + }, + { + "run": "run-89580b0cae404944b12e03e031dab1bd", + "validation_sha256": "2de8f68846c3b76170acffe8bc3e717886d6f0f86b04af9405f9d58cf5df73f8", + "status": "failed", + "round": 0, + "sample_sha256": "3a5150e5e75ac3168080585af7865e1df631ec19d2b2a237f13be4a2e93e5e4f", + "sessions_sha256": "3d65a9c388ea43114b20bf3c6dc7da49825b54529e1eec25bdfc6107acfb2c97", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "shell_sha256": "4e23e277ea7984d85856b713b3af5b0545d68042c2bbd42a8422f8a6b422c28d" + }, + { + "run": "run-8abb195e8f824858adbc0149ce1bbca6", + "validation_sha256": "372adca843f92115d93a307240f58b30708e79c2700e29def8cceb179d508283", + "status": "failed", + "round": 1, + "sample_sha256": "74ceb06312732d0a38499149102f3f683f4a6f62a45961aeef8413ad6e0a20e9", + "sessions_sha256": "d4acc6bbc8f648732bf7e240d9f3613df82e1d4dbff4aa9a221363a122d6fcf2", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "shell_sha256": "4e23e277ea7984d85856b713b3af5b0545d68042c2bbd42a8422f8a6b422c28d" + }, + { + "run": "run-9266bc270e244e05a0073d86f00ae8ae", + "validation_sha256": "758bdc8ef0d865ad457bb898e14187751693d3c65b8240e15d2f3aa32a129083", + "status": "passed", + "round": 0, + "sample_sha256": "d669917b3065530eb1b65900b1ad86ef0b6a1f6bd186dc10cffa0bdddf8b1e0d", + "sessions_sha256": "8081ee0237b2d50a1450ff4c12c4a7824a955ccfde651d56eac659bd2935a2bc", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "shell_sha256": "4e23e277ea7984d85856b713b3af5b0545d68042c2bbd42a8422f8a6b422c28d" + } + ], + "restore_stage1": { + "issue": 88, + "mode": "ui-workspace-restore", + "stage": 1, + "repeat": 1, + "files_sha256": { + "persistence-failure.json": "7ccb0a90b526462a929cbebce42debccfd97b54676e54e0935fbf292826b5e5b", + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "5e546fb36ec65e2d2eb20e9d1047216be33dc8b17eaaa611d995686a1be7d620", + "workspace.layout": "660f3a4064006db91261100c6fb1e1d2fb82f73210575910a29880489dd0cdda" + }, + "result": { + "exit_code": 2, + "timed_out": false, + "job_after": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 11, + "terminated": 0 + } + }, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "ready": 1, + "observation_enabled": 1, + "observed_bytes": 230, + "observed_sha256": "68fadc3fc45b9fbaab78b7e1a55987f5fdc11e9a2abdbe39fc65135556703017" + }, + { + "group": "main", + "kind": "cmd", + "ready": 1, + "observation_enabled": 1, + "observed_bytes": 262, + "observed_sha256": "8db2595d8d8546bbb7bb7a6facf79ab72e8f88ee4ce6ec9cf255dbac8528013e" + }, + { + "group": "main", + "kind": "powershell", + "ready": 1, + "observation_enabled": 1, + "observed_bytes": 4373, + "observed_sha256": "06d376c5709d01185a5696726e5ed2a412184b5e1df886ac948c1ac64b349f4c" + } + ], + "cause_confirmed": false, + "note": "Original transports contain output and prompts; bare CR in cmd echo is an unproved oracle hypothesis." + }, + "excluded": [ + { + "affected": [ + "gui-restored-baseline", + "restored-replay" + ], + "validation_status": "invalid_source_binary_pair", + "reason": "Copy-Item retained the old source timestamp; incremental build reused the diagnostic static library and executables. Binary hashes matched the probe, not the restored source. Excluded from baseline comparisons.", + "recorded_utc": "2026-10-26T19:53:23.674551+00:00" + }, + { + "calls": 174, + "elapsed_seconds": 2.6090000001713634, + "reduction_complete": true, + "bytes": 30, + "events": 2, + "transport_sha256": "060ed625d586f873039fb5c6f517b99a6b1ddcab9f854e568a0edcbec91b7da0", + "diagnostic_only": true, + "expected_symptom": "exact TF_SIZE_100x28OW", + "usable_causal_reduction": false, + "reason": "The reduction removed resize and constructed a legitimate overwrite. Symptom equality alone is insufficient; not a regression fixture." + } + ], + "limits": [ + "Original GUI transport and private sample files remain local; only reviewed synthetic native cases are published.", + "Native microprobe does not reply to CPR; snapshots and bounded host query wait perturb timing.", + "Default/system, candidate/system and candidate/official evidence remain separate.", + "Final full run failed #52 Vim stage59 and #88 restore stage1; #87 earlier workflow failures remain unresolved.", + "This is a limited experimental resize fix; #29, #34 and full R2 acceptance remain open." + ], + "presented_done": null, + "windterm_runtime_equivalence": false, + "performance_acceptance": false, + "product_backend_selected": false, + "full_r2_verified": false +} 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/results/2026-10-27-upstream-margin-build/build-summary.json b/experiments/conpty/results/2026-10-27-upstream-margin-build/build-summary.json new file mode 100644 index 00000000..e4958591 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-upstream-margin-build/build-summary.json @@ -0,0 +1,266 @@ +{ + "schema": "termforge-upstream-conpty-build-v1", + "purpose": "isolated source-build and adapter experiment; not an official ConPTY package", + "status": "adapter-verified-runtime-unverified", + "termforge_source": "eb88c25fea343cff92bd7d5c03e74c55df6bafff", + "upstream_source": "dc8ae365847d41f62d1ebca93815bb00959bc12f", + "vcpkg_source": "927f62e4b8838bd7e441e9c45103a16ffd75007e", + "image_os": "win25-vs2026", + "image_version": "20261021.278.1", + "workflow_run": "40447628163", + "workflow_attempt": "1", + "configuration": "Release/x64/v145/WindowsSDK10.0.26100.0", + "stages": [ + { + "name": "vcpkg-tool-download", + "exit_code": 0, + "elapsed_seconds": 0.5864599, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "vcpkg-version", + "exit_code": 0, + "elapsed_seconds": 0.029197, + "log_sha256": "fea4ead8f40a8aeca48fc22406d6102a7f1e07373fc0d6a5decb129457a43161" + }, + { + "name": "nuget-restore", + "exit_code": 0, + "elapsed_seconds": 10.1804599, + "log_sha256": "c868754fca3b184523987c5c0ea4064a4c6e260955437bcb35a588298fc8d14e" + }, + { + "name": "baseline-patch-check", + "exit_code": 0, + "elapsed_seconds": 0.0519633, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "baseline-test-patch", + "exit_code": 0, + "elapsed_seconds": 0.0446913, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "baseline-proxy-build", + "exit_code": 0, + "elapsed_seconds": 88.2472551, + "log_sha256": "30c4807f5bb76565f5f2f7de380afcde17cd814f4165771b7057b41e0d69bf60" + }, + { + "name": "baseline-types-build", + "exit_code": 0, + "elapsed_seconds": 10.7958636, + "log_sha256": "2517edeadee0c2ece0eb57d76fc9b638eb4c24dcb36bff67d7aff6bce2c01fd4" + }, + { + "name": "baseline-buffer-build", + "exit_code": 0, + "elapsed_seconds": 16.3077754, + "log_sha256": "5e0cd5be3d1fa74b0db5bd0ca246b75cad536912d1264cb2a04ce5243fe483cc" + }, + { + "name": "baseline-render-base-build", + "exit_code": 0, + "elapsed_seconds": 8.5950454, + "log_sha256": "6a80b6b730498b86e451d25f08194d592438d4f02dca945aa6dd7ad4c67919c4" + }, + { + "name": "baseline-render-gdi-build", + "exit_code": 0, + "elapsed_seconds": 7.4118507, + "log_sha256": "5ffba2be38849e5bedbc3f852b4a416fbc44eb25b859c0ad59115626ece82867" + }, + { + "name": "baseline-adapter-build", + "exit_code": 0, + "elapsed_seconds": 114.0402119, + "log_sha256": "27de5599e32a701c602340f27ae0c9ca1cdf9c42ec2b9c9ab5f169a2c204f631" + }, + { + "name": "baseline-regression", + "exit_code": 1, + "elapsed_seconds": 0.304792, + "log_sha256": "d1907fb1f5e1dd008eef1b1e5611d6533d37538df0a2a6cba0871784cb8d4988" + }, + { + "name": "baseline-regression-contract", + "exit_code": 0, + "elapsed_seconds": 0.0679866, + "log_sha256": "b7aa5de14c43240cceac182a506bd51937189c7b22b4a3998c8ec37bba6f1896" + }, + { + "name": "production-patch-check", + "exit_code": 0, + "elapsed_seconds": 0.04526, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "production-patch", + "exit_code": 0, + "elapsed_seconds": 0.044372, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "patched-adapter-build", + "exit_code": 0, + "elapsed_seconds": 10.4338222, + "log_sha256": "29d597ee653b103114c0d7fdaad3fe694350d54f3914a0b6cc42ea251b84b928" + }, + { + "name": "patched-regression", + "exit_code": 0, + "elapsed_seconds": 0.2892821, + "log_sha256": "b0413a44a181bbd07e81dcf612d18a00423baa01da4a28d0759004dc2603873c" + }, + { + "name": "patched-regression-contract", + "exit_code": 0, + "elapsed_seconds": 0.0721329, + "log_sha256": "da281146aa232fafccb57afd0100781005fc28c4625a5a782e0513e12912573d" + }, + { + "name": "patched-adapter", + "exit_code": 0, + "elapsed_seconds": 3.0609593, + "log_sha256": "343a699ea0b2bf861cf9af70d8a93e5660f1086a73d59c3b9ab48971acd66704" + }, + { + "name": "patched-adapter-contract", + "exit_code": 0, + "elapsed_seconds": 0.0728841, + "log_sha256": "332b578d7b8603574df41dea702bfb7ef14c7e201ff24b194c57a0bf61c2ff4f" + }, + { + "name": "patched-propsheet-build", + "exit_code": 0, + "elapsed_seconds": 14.8334641, + "log_sha256": "b0ca024bae4050c4acbc7bea2ba6435710a8d7ea4b8f70fe351d79dba20f8eaf" + }, + { + "name": "patched-host-build", + "exit_code": 0, + "elapsed_seconds": 28.4543468, + "log_sha256": "3186d0fc9afea5dd466b68042787b259cf722a84d8a429850309d5a1d4e50bb8" + }, + { + "name": "patched-dll-build", + "exit_code": 0, + "elapsed_seconds": 12.3535776, + "log_sha256": "1dbb9fc9a551831d8d69ad67a7142e3247c28f92982a04bf42784be75ea8c650" + } + ], + "test_binaries": [ + { + "stage": "baseline-regression", + "test_dll_sha256": "3e161442de1785596a54f489d3c0b468a22b16ae8966a6dcb3402bbd70052856", + "taef_sha256": "b729c665a51a1e36dc24152ecde4e4a7efb618b75dc7387d8c6edb103ef952c1" + }, + { + "stage": "patched-regression", + "test_dll_sha256": "6a5c51aaeee2ef99eff4c0257c20e4bbaf9b4c2efb509a4d2b6a424cccb8a16b", + "taef_sha256": "b729c665a51a1e36dc24152ecde4e4a7efb618b75dc7387d8c6edb103ef952c1" + }, + { + "stage": "patched-adapter", + "test_dll_sha256": "6a5c51aaeee2ef99eff4c0257c20e4bbaf9b4c2efb509a4d2b6a424cccb8a16b", + "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", + "files": { + "OpenConsole.exe": { + "sha256": "6516b5c9b890d810ba9f178d082d69f664bce775acb2820e9d40f297ae037bdb", + "bytes": 1147392 + }, + "conpty.dll": { + "sha256": "22710cf4e1ddd94bd76888717e394be556644d137b08355884649a63ee5b7590", + "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-upstream-margin-build/validation.json b/experiments/conpty/results/2026-10-27-upstream-margin-build/validation.json new file mode 100644 index 00000000..4bb7411a --- /dev/null +++ b/experiments/conpty/results/2026-10-27-upstream-margin-build/validation.json @@ -0,0 +1,73 @@ +{ + "schema": "termforge-upstream-margin-build-evidence-v1", + "date_utc": "2026-10-27", + "issue": 56, + "pull_request": 96, + "head": "b56058e9c4becb30f6afda3100b73acf1e552ff7", + "tree": "e5031af701d8cd1d8c710624fb7e1a2ce3b61c74", + "run_url": "https://github.com/JavaZeroo/termforge/actions/runs/40447628163", + "job_id": 122002564243, + "summary_artifact": { + "id": 12708429315, + "zip_sha256": "ec391e524557e163aa40149dc9f5c3d801a0b7cede3b67ac52aaf15dc3fe5238" + }, + "pair_artifact": { + "id": 12708509216, + "zip_sha256": "60367bbde7a1ac5839ddb3718b19604869313e8f28df60bc33b22bdaa4d8eb31" + }, + "captured_summary_sha256": "c1f7d41e60417c50524c299ac8c9ca6617ebd90244ac9e756c51d29f39bd61c5", + "summary_representation": "build-summary.json preserves parsed fields; UTF-8/LF JSON normalization is not the captured byte identity", + "tests": { + "baseline-regression": { + "expected": "red", + "exit_code": 1, + "counts": { + "total": 1, + "passed": 0, + "failed": 1, + "blocked": 0, + "not_run": 0, + "skipped": 0 + }, + "accepted": true + }, + "patched-regression": { + "expected": "focused-green", + "exit_code": 0, + "counts": { + "total": 1, + "passed": 1, + "failed": 0, + "blocked": 0, + "not_run": 0, + "skipped": 0 + }, + "accepted": true + }, + "patched-adapter": { + "expected": "full-green", + "exit_code": 0, + "counts": { + "total": 412, + "passed": 412, + "failed": 0, + "blocked": 0, + "not_run": 0, + "skipped": 0 + }, + "accepted": true + } + }, + "baseline_failure": "expected default alternate margins; actual top=1,bottom=2 at adapterTest.cpp:3006", + "first_success_only_patched_pair": true, + "same_source_baseline_pair_built": false, + "native_runtime_verified": false, + "full_r2_contract_verified": false, + "product_backend_selected": false, + "limitations": [ + "Adapter state tests do not establish native console or downstream transport behavior.", + "This v1 build contains no unpatched native pair; same-source native red/green remains unmeasured.", + "Artifacts are temporary research outputs; no WindTerm files or upstream binary bytes are committed.", + "Earlier checkout, shallow-history and build-order failures remain in PR96 and issue56." + ] +} diff --git a/experiments/conpty/session.hpp b/experiments/conpty/session.hpp index 6f607f17..f52a4419 100644 --- a/experiments/conpty/session.hpp +++ b/experiments/conpty/session.hpp @@ -12,6 +12,7 @@ #include #include #include +#include "backend.hpp" namespace tf_conpty { inline void fail(const char* operation, DWORD error = GetLastError()) { @@ -38,6 +39,9 @@ class Handle { // The local shell supplies an external bounded streaming consumer and input // worker; the lifecycle probe uses bounded capture and its --child entry point. class Session { + // Destroy this last: callbacks, reader and HPCON must stop while their + // selected API module is still loaded. + Backend backend_; Handle input_, output_, process_, job_; HPCON console_ = nullptr; std::thread reader_; @@ -53,17 +57,22 @@ class Session { unsigned long long output_bytes = 0; bool capture_overflow = false, forced_reader_cancel = false, forced_process_cancel = false; bool child_signaled = false, cleanup_complete = false; - Session() = default; + explicit Session(Backend backend = Backend::configured()) : backend_(std::move(backend)) {} Session(const Session&) = delete; Session& operator=(const Session&) = delete; ~Session() { close(); } + const char* backend_identity() const { + return backend_.identity(); + } + DWORD backend_creation_flags() const { return backend_.creation_flags(); } void start(const std::wstring& executable, const std::wstring& arguments = L"--child", - std::function consume = {}, const std::wstring& working_directory = {}) { + std::function consume = {}, const std::wstring& working_directory = {}, + COORD initial_size = {80, 24}) { Handle child_input, child_output; if (!CreatePipe(child_input.put(), input_.put(), nullptr, 0)) fail("CreatePipe(input)"); if (!CreatePipe(output_.put(), child_output.put(), nullptr, 0)) fail("CreatePipe(output)"); - const HRESULT hr = CreatePseudoConsole({80, 24}, child_input.get(), child_output.get(), 0, &console_); + const HRESULT hr = backend_.create(initial_size, child_input.get(), child_output.get(), 0, &console_); if (FAILED(hr)) fail("CreatePseudoConsole", static_cast(hr)); child_input.reset(); child_output.reset(); reader_ = std::thread([this, consume = std::move(consume)] { @@ -155,7 +164,7 @@ class Session { } } void resize(short columns, short rows) { - const HRESULT hr = ResizePseudoConsole(console_, {columns, rows}); + const HRESULT hr = backend_.resize(console_, {columns, rows}); if (FAILED(hr)) fail("ResizePseudoConsole", static_cast(hr)); } bool wait_exit(unsigned timeout_ms, DWORD* observed_wait_result = nullptr, DWORD* observed_wait_error = nullptr) { @@ -173,7 +182,7 @@ class Session { void close() noexcept { if (cleanup_complete) return; input_.reset(); - if (console_) { ClosePseudoConsole(console_); console_ = nullptr; } + if (console_) { backend_.close(console_); console_ = nullptr; } if (process_.get()) { child_signaled = WaitForSingleObject(process_.get(), 5000) == WAIT_OBJECT_0; if (!child_signaled && job_.get()) { 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 new file mode 100644 index 00000000..47b64cdd --- /dev/null +++ b/experiments/conpty/test_backend.py @@ -0,0 +1,369 @@ +"""Owned, actual API-family/lifetime checks; no desktop or installation.""" +import json +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import time +import unittest +from unittest import mock + +from experiments.build_paths import ROOT, binary +from experiments.conpty.run import OwnedJob + + +@unittest.skipUnless(os.name == "nt", "Windows ConPTY backend") +class BackendTests(unittest.TestCase): + def setUp(self): + base = Path(os.environ.get("TERMFORGE_TEST_ARTIFACTS", ROOT / "build/test-artifacts")) / "backend" + base.mkdir(parents=True, exist_ok=True) + self.out = Path(tempfile.mkdtemp(prefix="run-", dir=base)) + self.serial = 0 + + def invoke(self, directory, mode="--host", *, executable=None): + job = process = None + stdout = stderr = b"" + result = {"status": "failed", "failure_code": None, "returncode": None, + "outer_job_after": None, "outer_job_after_forced_cleanup": None, + "forced_cleanup": False, "job_assigned": False, + "process_reaped": None, "streams_drained": False, "cleanup_errors": []} + case = self.out / f"case-{self.serial}" + self.serial += 1 + private = case / "_private" + # Prepare storage before acquiring any process/Job resource. + private.mkdir(parents=True) + stage = "job_create" + + def cleanup_fault(code): + result["cleanup_errors"].append(code) + result["status"] = "failed" + + try: + job = OwnedJob() + stage = "process_start" + 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" + job.assign(process.pid) + result["job_assigned"] = True + stage = "process_communicate" + stdout, stderr = process.communicate(b"GO\n", timeout=20) + result["streams_drained"] = True + stage = "stream_evidence" + (private / "host.stdout.bin").write_bytes(stdout) + (private / "host.stderr.bin").write_bytes(stderr) + result["returncode"] = process.returncode + stage = "native_report" + result["native"] = json.loads(stdout) if stdout else None + result["error"] = "identity_rejected" if stderr else None + result["status"] = "completed" + except BaseException: + result["failure_code"] = stage + raise + finally: + # One deadline covers all cleanup waits. Each operation may fail + # independently without replacing the original body exception. + deadline = time.monotonic() + 5 + + def job_observation(): + observed = job.drain(timeout=max(0, deadline - time.monotonic())) + if (type(observed) is not dict or set(observed) != {"active", "total", "terminated"} + or any(type(value) is not int or value < 0 for value in observed.values())): + raise ValueError("invalid_job_accounting") + return observed + + try: + if process is not None: + try: + running = process.poll() is None + except Exception: + running = True + cleanup_fault("process_poll") + if running: + result["forced_cleanup"] = True + result["status"] = "failed" + try: + if result["job_assigned"]: + job.terminate() + else: + process.kill() # This owned root is still waiting for GO. + except Exception: + cleanup_fault("process_terminate") + try: + stdout, stderr = process.communicate(timeout=max(0, deadline - time.monotonic())) + result["streams_drained"] = True + except Exception: + cleanup_fault("process_communicate") + if job is not None and result["job_assigned"]: + try: + result["outer_job_after"] = job_observation() + except Exception: + cleanup_fault("job_accounting") + if result["outer_job_after"] is not None and result["outer_job_after"]["active"]: + result["forced_cleanup"] = True + result["status"] = "failed" + try: + job.terminate() + except Exception: + cleanup_fault("job_terminate") + try: + result["outer_job_after_forced_cleanup"] = job_observation() + if result["outer_job_after_forced_cleanup"]["active"]: + cleanup_fault("job_not_empty") + except Exception: + cleanup_fault("job_accounting_after_forced_cleanup") + finally: + if job is not None: + try: + job.close() # Kill-on-close remains the final containment fallback. + except Exception: + cleanup_fault("job_close") + if process is not None: + try: + if not result["streams_drained"]: + stdout, stderr = process.communicate(timeout=max(0, deadline - time.monotonic())) + result["streams_drained"] = True + result["process_reaped"] = process.poll() is not None + if not result["process_reaped"]: + cleanup_fault("process_not_reaped") + except Exception: + cleanup_fault("process_reap_after_close") + for name, contents in (("host.stdout.bin", stdout), ("host.stderr.bin", stderr)): + try: + (private / name).write_bytes(contents) + except Exception: + cleanup_fault("stream_evidence_write") + if (result["cleanup_errors"] or result["forced_cleanup"] or result["outer_job_after"] is None + or result["process_reaped"] is not True or not result["streams_drained"]): + result["status"] = "failed" + if result["failure_code"] is None: + result["failure_code"] = "outer_cleanup_incomplete" + try: + (case / "summary.json").write_text(json.dumps(result, indent=2), encoding="utf-8") + except Exception: + cleanup_fault("summary_write") + if result["status"] != "completed": + raise ValueError("outer_cleanup_incomplete") + return result + + def check_lifecycle(self, sample, side_by_side): + self.assertEqual(sample["returncode"], 0) + self.assertEqual(sample["native"], { + "valid": True, "side_by_side": side_by_side, "creation_flags": 24 if side_by_side else 0, + "loaded_during_session": side_by_side, "loaded_after_close": side_by_side, + "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, + }) + + def selected_directory(self): + if os.environ.get("TERMFORGE_EXPECT_CONPTY_BACKEND") != "reviewed-side-by-side-1.25.260930003": + self.skipTest("explicit reviewed backend build not selected") + return binary("conpty_backend_probe").parent / "conpty-backend" + + def test_system_create_resize_and_close(self): + self.check_lifecycle(self.invoke("system"), False) + + def test_selected_module_survives_session_and_unloads_after_close(self): + self.check_lifecycle(self.invoke(self.selected_directory()), True) + + def test_relative_directory_is_rejected(self): + result = self.invoke("conpty-backend", "--identity") + self.assertEqual(result["returncode"], 2) + self.assertEqual(result["error"], "identity_rejected") + + def test_missing_pair_is_rejected(self): + result = self.invoke(self.out, "--identity") + self.assertEqual(result["returncode"], 2) + + def test_unreviewed_pair_is_rejected(self): + (self.out / "conpty.dll").write_bytes(b"unreviewed dll") + (self.out / "OpenConsole.exe").write_bytes(b"unreviewed host") + result = self.invoke(self.out, "--identity") + self.assertEqual(result["returncode"], 2) + + def test_either_selected_file_tamper_is_rejected(self): + selected = self.selected_directory() + for index, name in enumerate(("conpty.dll", "OpenConsole.exe")): + directory = self.out / f"tamper-{index}" + directory.mkdir() + for member in ("conpty.dll", "OpenConsole.exe"): + shutil.copyfile(selected / member, directory / member) + payload = bytearray((directory / name).read_bytes()) + payload[-1] ^= 1 + (directory / name).write_bytes(payload) + with self.subTest(file=name): + result = self.invoke(directory, "--identity") + self.assertEqual(result["returncode"], 2) + + +class BackendCleanupFailureTests(unittest.TestCase): + """Pure supervision failures also run on non-Windows CI.""" + + def mocks(self): + process, job = mock.Mock(), mock.Mock() + process.pid, process.returncode = 123, None + process.poll.return_value = None + job.drain.return_value = {"active": 0, "total": 1, "terminated": 0} + return process, job + + def invoke(self, root, process, job): + subject = BackendTests() + subject.out, subject.serial = Path(root), 0 + with mock.patch(__name__ + ".OwnedJob", return_value=job), \ + mock.patch(__name__ + ".binary", return_value=Path(root) / "never-executed"), \ + mock.patch.object(subprocess, "Popen", return_value=process), \ + mock.patch.object(subprocess, "CREATE_NO_WINDOW", 0, create=True): + return subject.invoke("system") + + def summary(self, root): + row = json.loads((Path(root) / "case-0/summary.json").read_text("utf-8")) + self.assertNotIn("PRIVATE", json.dumps(row)) + return row + + def normal_body(self, process): + process.returncode = 0 + process.poll.return_value = 0 + process.communicate.return_value = (b"{}", b"") + + def test_timeout_survives_all_cleanup_failures_and_saves_unknowns(self): + process, job = self.mocks() + original = subprocess.TimeoutExpired("PRIVATE original", 20) + process.communicate.side_effect = [original, OSError("PRIVATE pipe"), OSError("PRIVATE reap")] + job.terminate.side_effect = OSError("PRIVATE terminate") + job.drain.side_effect = OSError("PRIVATE accounting") + job.close.side_effect = OSError("PRIVATE close") + with tempfile.TemporaryDirectory() as root: + with self.assertRaises(subprocess.TimeoutExpired) as raised: + self.invoke(root, process, job) + self.assertIs(raised.exception, original) + row = self.summary(root) + self.assertEqual(row["status"], "failed") + self.assertEqual(row["failure_code"], "process_communicate") + self.assertIsNone(row["returncode"]) + self.assertIsNone(row["outer_job_after"]) + self.assertIsNone(row["process_reaped"]) + for code in ("process_terminate", "process_communicate", "job_accounting", "job_close", + "process_reap_after_close"): + self.assertIn(code, row["cleanup_errors"]) + job.close.assert_called_once() + + def test_assignment_failure_never_sends_go_and_kills_only_owned_root(self): + process, job = self.mocks() + original = OSError("PRIVATE assignment") + job.assign.side_effect = original + process.poll.side_effect = [None, 99] + process.communicate.return_value = (b"", b"") + with tempfile.TemporaryDirectory() as root: + with self.assertRaises(OSError) as raised: + self.invoke(root, process, job) + self.assertIs(raised.exception, original) + row = self.summary(root) + self.assertFalse(row["job_assigned"]) + self.assertTrue(row["process_reaped"]) + self.assertTrue(row["streams_drained"]) + self.assertIsNone(row["outer_job_after"]) + process.kill.assert_called_once() + process.communicate.assert_called_once() + self.assertEqual(process.communicate.call_args.args, ()) + job.terminate.assert_not_called() + job.drain.assert_not_called() + job.close.assert_called_once() + + def test_first_nonzero_accounting_is_preserved_and_cannot_pass(self): + process, job = self.mocks() + self.normal_body(process) + first = {"active": 2, "total": 3, "terminated": 0} + after = {"active": 0, "total": 3, "terminated": 2} + job.drain.side_effect = [first, after] + with tempfile.TemporaryDirectory() as root: + with self.assertRaisesRegex(ValueError, "outer_cleanup_incomplete"): + self.invoke(root, process, job) + row = self.summary(root) + self.assertEqual(row["outer_job_after"], first) + self.assertEqual(row["outer_job_after_forced_cleanup"], after) + self.assertTrue(row["forced_cleanup"]) + self.assertEqual(row["status"], "failed") + job.close.assert_called_once() + + def test_invalid_or_unavailable_accounting_stays_unknown_and_cannot_pass(self): + for observation in (OSError("PRIVATE query"), {"active": False, "total": 1, "terminated": 0}, + {"active": 0, "total": 1}): + with self.subTest(observation=type(observation).__name__), tempfile.TemporaryDirectory() as root: + process, job = self.mocks() + self.normal_body(process) + job.drain.side_effect = [observation] + with self.assertRaisesRegex(ValueError, "outer_cleanup_incomplete"): + self.invoke(root, process, job) + row = self.summary(root) + self.assertIsNone(row["outer_job_after"]) + self.assertEqual(row["status"], "failed") + self.assertIn("job_accounting", row["cleanup_errors"]) + job.close.assert_called_once() + + def test_directory_preparation_does_not_acquire_job(self): + _, job = self.mocks() + original = OSError("PRIVATE directory") + with tempfile.TemporaryDirectory() as root, mock.patch.object(Path, "mkdir", side_effect=original), \ + mock.patch(__name__ + ".OwnedJob", return_value=job) as acquire: + subject = BackendTests() + subject.out, subject.serial = Path(root), 0 + with self.assertRaises(OSError) as raised: + subject.invoke("system") + self.assertIs(raised.exception, original) + acquire.assert_not_called() + job.close.assert_not_called() + + def test_evidence_write_failure_still_closes_job_and_preserves_original(self): + process, job = self.mocks() + self.normal_body(process) + original = OSError("PRIVATE evidence disk") + with tempfile.TemporaryDirectory() as root, mock.patch.object(Path, "write_bytes", side_effect=original): + with self.assertRaises(OSError) as raised: + self.invoke(root, process, job) + self.assertIs(raised.exception, original) + row = self.summary(root) + self.assertEqual(row["failure_code"], "stream_evidence") + self.assertIn("stream_evidence_write", row["cleanup_errors"]) + self.assertTrue(row["process_reaped"]) + self.assertEqual(row["outer_job_after"]["active"], 0) + job.close.assert_called_once() + + def test_summary_write_failure_cannot_pass_or_replace_original_timeout(self): + for has_timeout in (False, True): + with self.subTest(has_timeout=has_timeout), tempfile.TemporaryDirectory() as root: + process, job = self.mocks() + self.normal_body(process) + original = subprocess.TimeoutExpired("PRIVATE original", 20) + if has_timeout: + process.poll.side_effect = [None, 99] + process.communicate.side_effect = [original, (b"", b"")] + with mock.patch.object(Path, "write_text", side_effect=OSError("PRIVATE summary disk")) as write: + with self.assertRaises(subprocess.TimeoutExpired if has_timeout else ValueError) as raised: + self.invoke(root, process, job) + if has_timeout: + self.assertIs(raised.exception, original) + write.assert_called_once() + self.assertNotIn("PRIVATE", write.call_args.args[0]) + job.close.assert_called_once() + + def test_cleanup_uses_one_five_second_deadline(self): + process, job = self.mocks() + self.normal_body(process) + process.poll.side_effect = [None, 37] + job.drain.side_effect = [{"active": 2, "total": 3, "terminated": 0}, + {"active": 0, "total": 3, "terminated": 2}] + with tempfile.TemporaryDirectory() as root, \ + mock.patch(__name__ + ".time.monotonic", side_effect=[100, 101, 102, 103]): + with self.assertRaisesRegex(ValueError, "outer_cleanup_incomplete"): + self.invoke(root, process, job) + self.assertEqual(process.communicate.call_args_list, + [mock.call(b"GO\n", timeout=20), mock.call(timeout=4)]) + self.assertEqual(job.drain.call_args_list, [mock.call(timeout=3), mock.call(timeout=2)]) + job.close.assert_called_once() + + +if __name__ == "__main__": + unittest.main() diff --git a/experiments/conpty/test_margin_run.py b/experiments/conpty/test_margin_run.py index 6f5dd293..887f0f2b 100644 --- a/experiments/conpty/test_margin_run.py +++ b/experiments/conpty/test_margin_run.py @@ -131,7 +131,7 @@ def test_exact_margin_result_and_wrapped_marker(self): def test_lifecycle_requires_exact_observed_types_and_values(self): sample = dict(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) + cleanup_complete=True, active_children_after=0, side_by_side=False, creation_flags=0) validate_lifecycle(sample) for key, value in sample.items(): for replacement in (None, not value if type(value) is bool else value + 1, diff --git a/experiments/conpty/upstream/README.md b/experiments/conpty/upstream/README.md index e8a6f716..990f1020 100644 --- a/experiments/conpty/upstream/README.md +++ b/experiments/conpty/upstream/README.md @@ -2,20 +2,24 @@ `dc8-native-margin-candidate.patch` 是针对 Microsoft Terminal `dc8ae365847d41f62d1ebca93815bb00959bc12f` 的独立候选改动,随附 -[Microsoft MIT 原许可](MICROSOFT-LICENSE.txt)。原作者版权保留。它目前只通过完整固定文件的 -`git apply --unidiff-zero --check`,**未编译、未运行上游测试、未生成后端、未证明修复**。 +[Microsoft MIT 原许可](MICROSOFT-LICENSE.txt)。原作者版权保留。 +[首次真实构建记录](../results/2026-10-27-upstream-margin-build/validation.json)包含原实现的断言失败、 +补丁后的 focused 1/1、完整 adapter 412/412 及同源 EXE/DLL 身份;**这些不证明原生运行时或完整合同修复**。 原问题、逐字段合同与有界运行见 [#56 调查](../../../docs/research/conpty-native-margin-path.md)。 候选在同一 `AdaptDispatch` 中保存隐藏主屏 margin,新建备用屏使用默认 margin,返回主屏恢复保存值; 重复 `1049h` 不覆盖隐藏主屏值,重复 `1049l` 不清空主屏当前值。它没有改源 fixture、运输、API 表、宽度 flag、 -TermForge 默认后端或现有官方双 SHA 校验。附加的上游 `AlternateScreenMarginsTest` 也尚未执行。 +TermForge 默认后端或现有官方双 SHA 校验。附加的上游 `AlternateScreenMarginsTest` 固定主/备用屏切换的 margin 回归。 -## 下一次隔离构建的具体入口 +## 隔离构建的具体入口 在单独的 Microsoft Terminal 源码 checkout 固定上述提交,校验 [validation.json](../results/2026-10-26-native-margin-path/validation.json) 的源文件 SHA 后,先用 `git apply --unidiff-zero --check` 检查、再以同一选项应用本 patch。完整保留上游许可证、依赖锁和构建日志。 按固定 [上游 README](https://github.com/microsoft/terminal/blob/dc8ae365847d41f62d1ebca93815bb00959bc12f/README.md#building-the-code) -准备工具链;不要执行会更改个人系统的 winget/Developer Mode 安装步骤。优先使用已有工具或隔离构建宿主。 +和实际构建文件准备工具链;固定提交的 +[`x64-windows-static.cmake`](https://github.com/microsoft/terminal/blob/dc8ae365847d41f62d1ebca93815bb00959bc12f/dep/vcpkg-overlay-triplets/x64-windows-static.cmake) +明确要求 **v145**,README 中的 VS2022 概述不能证明仅有 v143 的宿主满足依赖构建。 +不要执行会更改个人系统的 winget/Developer Mode 安装步骤。优先使用已有工具或隔离构建宿主。 从上游根目录加载 `tools/OpenConsole.psm1`、调用 `Set-MsBuildDevEnvironment` 后,逐一构建这些真实项目入口, 使用 `Configuration=Release`、`Platform=x64`、正确的根 `SolutionDir`、restore 和单 worker;每一步非零立即停止: @@ -25,9 +29,35 @@ TermForge 默认后端或现有官方双 SHA 校验。附加的上游 `Alternate - `src/host/exe/Host.EXE.vcxproj`:生成 `OpenConsole.exe`,包含本次 adapter 修订。 - `src/winconpty/dll/winconptydll.vcxproj`:从同一完整提交/依赖/配置生成配套 DLL。 -这些入口来自同一提交的 `OpenConsole.slnx`;不是已执行或已验证的构建命令结果。 +这些入口来自同一提交的 `OpenConsole.slnx`;实际执行身份和结果由对应 PR/#56 记录。 测试入口可用上游 `Invoke-OpenConsoleTests -Test adapter`,其映射在 `tools/tests.xml`,须限制为无交互 unit 路径。 +### 一次性 hosted 构建入口 + +[专用 workflow](../../../.github/workflows/conpty-upstream.yml)只在本目录/自身的 PR 改动或显式 dispatch 时运行, +独立于默认产品构建。它固定上述源码、上游 manifest 的 vcpkg baseline `927f62e4b8838bd7e441e9c45103a16ffd75007e`、 +vcpkg 工具 2026-04-08 的发布 SHA、现有 patch 与关键源码 SHA;在真正的 GitHub-hosted Windows 环境核对 v145/SDK。 +临时宿主的 checkout 同时固定 `core.autocrlf=false` 和 `core.eol=lf`,避免 `text=auto` 在 Windows 上改变补丁字节;原 SHA 校验保持严格。 +vcpkg 使用完整 Git 历史,HEAD 仍固定在原 baseline;[历史 port 的 tree 对象不能由 shallow checkout 保证](https://learn.microsoft.com/en-us/vcpkg/users/versioning-troubleshooting)。 +上游 NuGet 锁定版本、vcpkg overlay 和 terminal feature 均保留。宿主镜像与实际编译器版本/哈希另记,不能据 runner 名称假定工具不变。 +单项目入口显式补齐固定 `OpenConsole.slnx` 的顺序依赖:adapter 前构建 Proxy/Types/Buffer/Render Base/GDI,生成所需接口头;Host.EXE 前构建 Propsheet。 + +[脚本](build_hosted.ps1)先只应用已有测试,编译并要求一次 `AlternateScreenMarginsTest` 的明确 margin 断言失败; +缺包、编译错误、加载错误、零测试、跳过或超时均不算 red。随后构建并单独保存原生产代码的 baseline EXE/DLL, +再仅应用两个实现文件的 patch,重建并分别执行一次 focused 和完整 adapter 测试,通过后构建 patched EXE/DLL。 +两组都来自同一 source/工具/依赖,variant 清单分别记录是否应用生产补丁及实际双 SHA,不能互相混配。 +baseline 的 variant 标明 focused 预期失败、完整 adapter 未验;共享 summary 中的 adapter 通过仅指 patched 变体。 +TAEF focused/session 期限分别为60/600秒,整个构建 job 最长45分钟,没有自动重试。 +[判据](taef_result.py)和[拒绝用例](test_taef_result.py)防止把进程失败、缺失执行或局部测试当作完整结果。 +TAEF 参数语义来自 [Microsoft 文档](https://learn.microsoft.com/en-us/windows-hardware/drivers/taef/te-exe-command-line-parameters), +单失败 red 只接受退出码1;[harness/超时高位](https://learn.microsoft.com/en-us/windows-hardware/drivers/taef/exit-codes-for-taef)不得混作断言失败。 + +workflow 保存白名单 summary、实际包/工具/双文件 SHA;完整日志仅在 CI 日志中,额外文件留在不上传的 `_private`。 +全部门槛通过后才上传两组短期实验 artifact,均带上游 LICENSE/NOTICE 和候选补丁;baseline 清单明确该生产补丁未应用。 +它们不包含 TAEF、NuGet 工具或 WindTerm 文件。 +这些是可执行的验证步骤;其实际结果由对应 PR/#56 记录,入口存在不表示已经编译或通过。 +即使 adapter 通过,仍须新 fork 身份、原175B四阶段、原生/运输/候选末端及反例验证;不能加载为现有官方包或据此结项。 + 构建成功后,为**新的 fork 双文件身份**单独增加显式、默认关闭的测试配置与审查清单;保持当前官方包校验失败即拒绝。 不能把 fork 冒充官方 1.25,也不能用新的 EXE 混配另一个版本的 DLL。create/resize/close 必须来自同模块, 模块存活至 HPCON、读线程和子进程回收之后。记录来源、补丁、工具链、文件 SHA、许可和失败路径。 diff --git a/experiments/conpty/upstream/build_hosted.ps1 b/experiments/conpty/upstream/build_hosted.ps1 new file mode 100644 index 00000000..5c1b7158 --- /dev/null +++ b/experiments/conpty/upstream/build_hosted.ps1 @@ -0,0 +1,198 @@ +param( + [Parameter(Mandatory)][string]$Source, + [Parameter(Mandatory)][string]$Vcpkg, + [Parameter(Mandatory)][string]$Out +) +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $false +if (-not $IsWindows -or $env:GITHUB_ACTIONS -ne 'true' -or $env:RUNNER_ENVIRONMENT -ne 'github-hosted') { + throw 'This entry requires a disposable GitHub-hosted Windows runner.' +} +$repo = (Resolve-Path (Join-Path $PSScriptRoot '../../..')).Path +$Source = (Resolve-Path -LiteralPath $Source).Path +$Vcpkg = (Resolve-Path -LiteralPath $Vcpkg).Path +$Out = [IO.Path]::GetFullPath($Out, (Get-Location).Path) +$workspacePrefix = (Resolve-Path -LiteralPath $env:GITHUB_WORKSPACE).Path.TrimEnd('\') + '\' +foreach ($path in @($Source, $Vcpkg, $Out)) { + if (-not $path.StartsWith($workspacePrefix, [StringComparison]::OrdinalIgnoreCase)) { + throw 'Source, dependencies and output must be children of this disposable workspace.' + } +} +if (Test-Path -LiteralPath $Out) { throw 'A fresh output directory is required.' } +New-Item -ItemType Directory -Path "$Out/public", "$Out/_private", "$Out/cache" | Out-Null +$state = [ordered]@{ + schema = 'termforge-upstream-conpty-build-v2' + purpose = 'isolated source-build and adapter experiment; not an official ConPTY package' + status = 'running' + termforge_source = (& git -C $repo rev-parse HEAD) + upstream_source = 'dc8ae365847d41f62d1ebca93815bb00959bc12f' + vcpkg_source = '927f62e4b8838bd7e441e9c45103a16ffd75007e' + image_os = $env:ImageOS + image_version = $env:ImageVersion + workflow_run = $env:GITHUB_RUN_ID + workflow_attempt = $env:GITHUB_RUN_ATTEMPT + configuration = 'Release/x64/v145/WindowsSDK10.0.26100.0' + stages = @() + test_binaries = @() + adapter_tests_verified = $false + native_runtime_verified = $false + full_r2_contract_verified = $false + product_backend_selected = $false +} +$currentStage = 'source-identity' +function Save-State { + $state | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath "$Out/public/summary.json" -Encoding utf8 +} +function Digest([string]$Path) { + (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() +} +function Invoke-Native([string]$Name, [string]$Executable, [string[]]$Arguments, [switch]$AllowFailure) { + $script:currentStage = $Name + $clock = [Diagnostics.Stopwatch]::StartNew() + Set-Content -LiteralPath "$Out/_private/$Name.log" -Value '' -NoNewline -Encoding utf8 + & $Executable @Arguments 2>&1 | Tee-Object -FilePath "$Out/_private/$Name.log" | Out-Host + $code = $LASTEXITCODE + $state.stages += [ordered]@{name=$Name; exit_code=$code; elapsed_seconds=$clock.Elapsed.TotalSeconds; log_sha256=(Digest "$Out/_private/$Name.log")} + Save-State + if ($code -ne 0 -and -not $AllowFailure) { throw "Stage $Name failed with exit code $code." } + return $code +} +function Build-Project([string]$Name, [string]$Project) { + [void](Invoke-Native $Name 'msbuild.exe' @($Project, '/nologo', '/restore', '/m:1', '/nr:false', '/v:minimal', + '/p:Configuration=Release', '/p:Platform=x64', '/p:PlatformToolset=v145', + '/p:WindowsTargetPlatformVersion=10.0.26100.0', '/p:CL_MPCount=1', '/p:UseMultiToolTask=false', + "/p:VCToolsVersion=$env:VCToolsVersion", "/p:SolutionDir=$Source\", "/p:VcpkgRoot=$Vcpkg\")) +} +function Build-HostPair([string]$Prefix) { + Build-Project "$Prefix-propsheet-build" 'src/propsheet/propsheet.vcxproj' + Build-Project "$Prefix-host-build" 'src/host/exe/Host.EXE.vcxproj' + Build-Project "$Prefix-dll-build" 'src/winconpty/dll/winconptydll.vcxproj' +} +function Copy-Pair([string]$Directory) { + New-Item -ItemType Directory -Path "$Out/$Directory" | Out-Null + $files = [ordered]@{} + foreach ($name in @('OpenConsole.exe', 'conpty.dll')) { + $binary = "$Source/bin/x64/Release/$name" + $files[$name] = [ordered]@{sha256=(Digest $binary); bytes=(Get-Item -LiteralPath $binary).Length} + Copy-Item -LiteralPath $binary -Destination "$Out/$Directory/$name" + if ((Digest "$Out/$Directory/$name") -ne $files[$name].sha256) { throw 'Copied pair identity differs.' } + } + return $files +} +function Test-Adapter([string]$Name, [string]$Expected, [switch]$Full) { + $testBinary = "$Source/bin/x64/Release/ConAdapter.Unit.Tests.dll" + $binaryHash = Digest $testBinary + $state.test_binaries += [ordered]@{stage=$Name; test_dll_sha256=$binaryHash; + taef_sha256=(Digest "$Source/packages/Microsoft.Taef.10.100.251104001/build/Binaries/x64/te.exe")} + $testArgs = @("$Source/bin/x64/Release/ConAdapter.Unit.Tests.dll", '/labMode', + '/coloredConsoleOutput:false', '/unicodeOutput:false', '/console:flushWrites') + if ($Full) { $testArgs += '/sessionTimeout:0:10:0' } + else { $testArgs += @('/name:*AlternateScreenMarginsTest', '/sessionTimeout:0:1:0') } + $code = Invoke-Native $Name "$Source/packages/Microsoft.Taef.10.100.251104001/build/Binaries/x64/te.exe" $testArgs -AllowFailure + [void](Invoke-Native "$Name-contract" 'python' @("$PSScriptRoot/taef_result.py", "$Out/_private/$Name.log", + '--exit-code', "$code", '--expected', $Expected, '--out', "$Out/public/$Name.json")) + if ((Digest $testBinary) -ne $binaryHash) { throw 'Test binary identity changed during execution.' } +} +try { + Save-State + if ((& git -C $Source rev-parse HEAD) -ne $state.upstream_source) { throw 'Wrong Terminal source.' } + if ((& git -C $Vcpkg rev-parse HEAD) -ne $state.vcpkg_source) { throw 'Wrong vcpkg source.' } + $state.vcpkg_shallow = (& git -C $Vcpkg rev-parse --is-shallow-repository) + if ($LASTEXITCODE -ne 0 -or $state.vcpkg_shallow -ne 'false') { throw 'Pinned port versions require the full vcpkg Git history.' } + if (@(& git -C $Source status --porcelain).Count -ne 0) { throw 'Upstream checkout is dirty.' } + $prior = Get-Content -Raw -LiteralPath "$repo/experiments/conpty/results/2026-10-26-native-margin-path/validation.json" | ConvertFrom-Json -AsHashtable + foreach ($file in $prior.upstream_source_sha256.Keys) { + if ((Digest (Join-Path $Source $file)) -ne $prior.upstream_source_sha256[$file]) { throw "Source hash differs: $file" } + } + $patch = "$PSScriptRoot/dc8-native-margin-candidate.patch" + $state.patch_sha256 = Digest $patch + if ($state.patch_sha256 -ne 'dc27377e399bd0217a0b76e16cc89f087c1b781929e4c958ad30c3a475322b81') { throw 'Patch identity differs.' } + $state.source_hashes_verified = $true + $triplet = Get-Content -Raw -LiteralPath "$Source/dep/vcpkg-overlay-triplets/x64-windows-static.cmake" + if ($triplet -notmatch 'set\(VCPKG_PLATFORM_TOOLSET v145\)') { throw 'Unexpected upstream toolset requirement.' } + + $currentStage = 'toolchain' + $vswhere = "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" + $vs = & $vswhere -latest -products '*' -version '[18.0,19.0)' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + if ($LASTEXITCODE -ne 0 -or -not $vs) { throw 'The pinned triplet requires an installed v145 toolchain.' } + Import-Module "$vs/Common7/Tools/Microsoft.VisualStudio.DevShell.dll" + Enter-VsDevShell -VsInstallPath $vs -SkipAutomaticLocation -DevCmdArguments '-arch=x64 -host_arch=x64 -winsdk=10.0.26100.0' + if ($env:VisualStudioVersion -notlike '18.*') { throw 'Expected the v145 Visual Studio environment.' } + $state.tools = [ordered]@{visual_studio=$env:VisualStudioVersion; msvc=$env:VCToolsVersion; + sdk=$env:WindowsSDKVersion; compiler_sha256=(Digest (Get-Command cl.exe).Source); + msbuild_sha256=(Digest (Get-Command msbuild.exe).Source)} + $env:VCPKG_ROOT = $Vcpkg + $env:VCPKG_DISABLE_METRICS = '1' + $env:VCPKG_MAX_CONCURRENCY = '1' + $env:VCPKG_DOWNLOADS = "$Out/cache/vcpkg-downloads" + $env:VCPKG_DEFAULT_BINARY_CACHE = "$Out/cache/vcpkg-binary" + $env:VCPKG_BINARY_SOURCES = "clear;files,$env:VCPKG_DEFAULT_BINARY_CACHE,readwrite" + $env:NUGET_PACKAGES = "$Out/cache/nuget-global" + $env:NUGET_HTTP_CACHE_PATH = "$Out/cache/nuget-http" + $env:NUGET_PLUGINS_CACHE_PATH = "$Out/cache/nuget-plugins" + foreach ($folder in @($env:VCPKG_DOWNLOADS, $env:VCPKG_DEFAULT_BINARY_CACHE, $env:NUGET_PACKAGES, + $env:NUGET_HTTP_CACHE_PATH, $env:NUGET_PLUGINS_CACHE_PATH)) { + New-Item -ItemType Directory -Path $folder | Out-Null + } + Set-Location -LiteralPath $Source + [void](Invoke-Native 'vcpkg-tool-download' 'curl.exe' @('--fail', '--location', '--silent', '--show-error', + '--connect-timeout', '10', '--max-time', '180', '--retry', '0', '--output', "$Vcpkg/vcpkg.exe", + 'https://github.com/microsoft/vcpkg-tool/releases/download/2026-04-08/vcpkg.exe')) + $state.vcpkg_exe_sha256 = Digest "$Vcpkg/vcpkg.exe" + if ($state.vcpkg_exe_sha256 -ne 'f1edbf3a39de350e2bb065214fdc057111aa87a2e2ed9a7dcb8ddc86e17751b9') { throw 'vcpkg release digest differs.' } + [void](Invoke-Native 'vcpkg-version' "$Vcpkg/vcpkg.exe" @('version', '--disable-metrics')) + [void](Invoke-Native 'nuget-restore' "$Source/dep/nuget/nuget.exe" @('restore', "$Source/dep/nuget/packages.config", + '-ConfigFile', "$Source/NuGet.Config", '-PackagesDirectory', "$Source/packages", '-DisableParallelProcessing', '-NonInteractive')) + + $testFile = 'src/terminal/adapter/ut_adapter/adapterTest.cpp' + [void](Invoke-Native 'baseline-patch-check' 'git' @('apply', '--unidiff-zero', "--include=$testFile", '--check', $patch)) + [void](Invoke-Native 'baseline-test-patch' 'git' @('apply', '--unidiff-zero', "--include=$testFile", $patch)) + if ((@(& git diff --name-only) -join ',') -ne $testFile) { throw 'Baseline changed production source.' } + # Single-project builds do not inherit OpenConsole.slnx BuildDependency edges. + Build-Project 'baseline-proxy-build' 'src/host/proxy/Host.Proxy.vcxproj' + Build-Project 'baseline-types-build' 'src/types/lib/types.vcxproj' + Build-Project 'baseline-buffer-build' 'src/buffer/out/lib/bufferout.vcxproj' + Build-Project 'baseline-render-base-build' 'src/renderer/base/lib/base.vcxproj' + Build-Project 'baseline-render-gdi-build' 'src/renderer/gdi/lib/gdi.vcxproj' + Build-Project 'baseline-adapter-build' 'src/terminal/adapter/ut_adapter/Adapter.UnitTests.vcxproj' + Test-Adapter 'baseline-regression' 'red' + Build-HostPair 'baseline' + $currentStage = 'baseline-artifact-identity' + $state.baseline_files = Copy-Pair 'baseline-pair' + + [void](Invoke-Native 'production-patch-check' 'git' @('apply', '--unidiff-zero', "--exclude=$testFile", '--check', $patch)) + [void](Invoke-Native 'production-patch' 'git' @('apply', '--unidiff-zero', "--exclude=$testFile", $patch)) + $expectedFiles = @('src/terminal/adapter/adaptDispatch.cpp', 'src/terminal/adapter/adaptDispatch.hpp', $testFile) + if ((@(& git diff --name-only) -join ',') -ne ($expectedFiles -join ',')) { throw 'Unexpected patched source files.' } + Build-Project 'patched-adapter-build' 'src/terminal/adapter/ut_adapter/Adapter.UnitTests.vcxproj' + Test-Adapter 'patched-regression' 'focused-green' + Test-Adapter 'patched-adapter' 'full-green' -Full + $state.adapter_tests_verified = $true + Build-HostPair 'patched' + + $currentStage = 'artifact-identity' + $state.files = Copy-Pair 'pair' + $state.nuget_packages = @() + foreach ($package in Get-ChildItem -LiteralPath "$Source/packages" -Filter '*.nupkg' -Recurse -File) { + $state.nuget_packages += [ordered]@{name=$package.Name; sha256=(Digest $package.FullName)} + } + $state.vcpkg_status_sha256 = Digest "$Source/obj/x64/vcpkg/vcpkg/status" + $state.status = 'adapter-verified-runtime-unverified' + Save-State + foreach ($directory in @('baseline-pair', 'pair')) { + $patched = $directory -eq 'pair' + [ordered]@{schema='termforge-upstream-conpty-variant-v1'; upstream_source=$state.upstream_source; + workflow_run=$state.workflow_run; production_patch_applied=$patched; + focused_adapter_result=$(if ($patched) { 'passed' } else { 'expected-failure' }); + full_adapter_verified=$patched; + patch_sha256=$state.patch_sha256; files=$(if ($patched) { $state.files } else { $state.baseline_files }) + } | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath "$Out/$directory/variant.json" -Encoding utf8 + Copy-Item -LiteralPath "$Out/public/summary.json", $patch, "$Source/LICENSE", "$Source/NOTICE.md" -Destination "$Out/$directory" + } +} catch { + $state.status = 'failed' + $state.failed_stage = $currentStage + throw +} finally { + Save-State +} diff --git a/experiments/conpty/upstream/taef_result.py b/experiments/conpty/upstream/taef_result.py new file mode 100644 index 00000000..61176750 --- /dev/null +++ b/experiments/conpty/upstream/taef_result.py @@ -0,0 +1,58 @@ +"""Reject missing, skipped, loading-error or unexpected TAEF results.""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import re + +SUMMARY = re.compile( + r"Summary:\s*Total=(\d+),\s*Passed=(\d+),\s*Failed=(\d+),\s*" + r"Blocked=(\d+),\s*Not Run=(\d+),\s*Skipped=(\d+)") +METHOD = "AlternateScreenMarginsTest" + + +def validate(text: str, exit_code: int, expected: str) -> dict: + rows = SUMMARY.findall(text) + if len(rows) != 1: + raise ValueError("Expected exactly one completed TAEF summary") + counts = dict(zip(("total", "passed", "failed", "blocked", "not_run", "skipped"), + map(int, rows[0]))) + if counts["total"] != sum(counts[key] for key in counts if key != "total"): + raise ValueError("Inconsistent test counts") + if any(counts[key] for key in ("blocked", "not_run", "skipped")): + raise ValueError("Blocked, unexecuted or skipped tests are not acceptance") + if METHOD not in text: + raise ValueError("The margin regression was not observed") + if expected == "red": + # TAEF's high bits report harness/timeout faults; the low bits count failures. + if exit_code != 1 or counts != dict(total=1, passed=0, failed=1, blocked=0, not_run=0, skipped=0): + raise ValueError("Expected one failed regression") + if not re.search(r"(?:Error|Failed)[^\n]*Verify[^\n]*_scrollMargins", text, re.IGNORECASE): + raise ValueError("Failure did not identify the scrolling-margin assertion") + elif expected in ("focused-green", "full-green"): + if exit_code != 0 or counts["failed"] or counts["passed"] != counts["total"]: + raise ValueError("Expected a successful TAEF execution") + if expected == "focused-green" and counts["total"] != 1: + raise ValueError("Expected exactly one focused regression") + if expected == "full-green" and counts["total"] <= 1: + raise ValueError("A focused test cannot stand in for the full adapter suite") + else: + raise ValueError("Unknown result contract") + return {"expected": expected, "exit_code": exit_code, "counts": counts, "accepted": True} + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("log", type=Path) + parser.add_argument("--exit-code", type=int, required=True) + parser.add_argument("--expected", choices=("red", "focused-green", "full-green"), required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + result = validate(args.log.read_text(encoding="utf-8-sig"), args.exit_code, args.expected) + args.out.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8") + print(json.dumps(result)) + + +if __name__ == "__main__": + main() diff --git a/experiments/conpty/upstream/test_taef_result.py b/experiments/conpty/upstream/test_taef_result.py new file mode 100644 index 00000000..d59fab07 --- /dev/null +++ b/experiments/conpty/upstream/test_taef_result.py @@ -0,0 +1,51 @@ +import unittest + +from experiments.conpty.upstream.taef_result import METHOD, validate + + +def output(passed=0, failed=1, blocked=0, not_run=0, skipped=0): + total = passed + failed + blocked + not_run + skipped + assertion = "Error: Verify: AreEqual(til::inclusive_rect{}, _pDispatch->_scrollMargins)\n" if failed else "" + return (f"StartGroup: Adapter::{METHOD}\n" + f"{assertion}" + f"Summary: Total={total}, Passed={passed}, Failed={failed}, " + f"Blocked={blocked}, Not Run={not_run}, Skipped={skipped}\n") + + +class TaefResultTests(unittest.TestCase): + def test_expected_assertion_failure_is_red(self): + self.assertTrue(validate(output(), 1, "red")["accepted"]) + + def test_process_failure_is_not_a_red_regression(self): + for text in ("Error loading DLL", output().replace("Error: Verify", "Loader failure:"), + output().replace(METHOD, "UnrelatedTest"), output(blocked=1, failed=0)): + with self.subTest(text=text), self.assertRaises(ValueError): + validate(text, 1, "red") + + def test_no_tests_multiple_summaries_and_skips_are_rejected(self): + for text in (output(failed=0), output() + output(), output(passed=1, failed=0, skipped=1)): + with self.subTest(text=text), self.assertRaises(ValueError): + validate(text, 0, "focused-green") + + def test_focused_and_full_success_remain_separate(self): + self.assertTrue(validate(output(passed=1, failed=0), 0, "focused-green")["accepted"]) + self.assertTrue(validate(output(passed=5, failed=0), 0, "full-green")["accepted"]) + with self.assertRaises(ValueError): + validate(output(passed=1, failed=0), 0, "full-green") + with self.assertRaises(ValueError): + validate(output(passed=5, failed=0), 0, "focused-green") + + def test_exit_code_and_counts_must_agree(self): + for text, code, contract in ((output(), 0, "red"), (output(passed=1, failed=0), 1, "focused-green"), + (output().replace("Total=1", "Total=2"), 1, "red")): + with self.subTest(contract=contract), self.assertRaises(ValueError): + validate(text, code, contract) + + def test_harness_error_bits_cannot_be_accepted_as_causal_red(self): + for code in (2, 0x08000001, 0x02000001, 0x01000001, -1): + with self.subTest(code=code), self.assertRaises(ValueError): + validate(output(), code, "red") + + +if __name__ == "__main__": + unittest.main() diff --git a/experiments/core/CANDIDATE.md b/experiments/core/CANDIDATE.md index a7f81954..fdab1346 100644 --- a/experiments/core/CANDIDATE.md +++ b/experiments/core/CANDIDATE.md @@ -2,7 +2,7 @@ 按 [ADR-0006](../../docs/adr/0006-confirmed-project-direction.md),用户确认保留本候选代码继续评估,尚未确认最终采用。下述实现、来源与回归结果继续保留;与 WindTerm 2.7.0 的重要差异、性能容差及正式选型分别提交用户确认。 -`CandidateTerminal` 是 #27 的拥有值适配层,不是自研核心的替代品声明。它把固定 `0.26.1-dev+termforge.4` 的 `Term/Processor` 接到现有 `Screen`/`Line`;默认 CMake 保留自研核心且不需要 Rust,候选产品由 `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON` 显式启用。 +`CandidateTerminal` 是 #27 的拥有值适配层,不是自研核心的替代品声明。它把固定 `0.26.1-dev+termforge.5` 的 `Term/Processor` 接到现有 `Screen`/`Line`;默认 CMake 保留自研核心且不需要 Rust,候选产品由 `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON` 显式启用。 ## 构建与验证 @@ -26,6 +26,12 @@ Linux 使用相应 CMake 配置。候选 workflow 在 Windows/Ubuntu 构建 `can - 不走 JSON。Rust/C++ 文本由调用方拥有,未做增量 FFI 或性能验收;候选 damage 的产品化仍属后续。 - `screen.resize` 与候选 `Term::resize` 同步尺寸;默认自研回退继续采用 ADR-0005 的物理行裁剪,候选不把它称为完整 reflow。真实 ConPTY 重放可能先由上游把历史拉入可见区再覆盖,候选在完整重放后合并有界历史,按 ID 和完整行键去重,保留 Find/选区所需的稳定身份,且不让冲突产生重复 ID。连续同步缓存原始 FFI ID 与最终身份的映射,仅在需要重放的未知旧 ID 时回退到完整键匹配,不重复扫描全部历史。真实淘汰不恢复,显式清空不复活;普通空白行、未决 reflow、备用屏/滚动区全语义不在此保证内。合同与验证见 [resize/历史记录](results/2026-10-26-resize-replay/validation.json)。 +### 显式原生 viewport resize + +`CandidateResizePolicy::reflow_history` 是构造器默认值。显式官方 ConPTY 构建的候选 `ShellWindow` 选择 `native_viewport`:增高向底部加空行,增宽后可见行合并腾出的空间也保留在底部;历史继续有界保留,但不跨历史/可见区边界参与原生控制台的坐标重排。跨边界的软行关系继续用于文本复制;布局填充不变成正文空格。公开适配器检查六组原生行/光标、历史 ID、完整复制与重复 resize,并检查 0/2/100 行历史上限。 + +`candidate_resize_replay transport.bin plan.txt --native-viewport` 与 `resize_trace.py ... --native-viewport` 显式选择同一策略。缺省命令保留原历史重排行为,未知或额外参数拒绝;重放记录须标出策略,不能用默认策略解释官方候选窗口。原生观测、原因和完整场景边界见[resize 对照](../../docs/research/native-viewport-resize.md)。WindTerm 2.7.0 的对应行为仍需调查,本策略不替代 #29 或 #34 的确认。 + ## 真实产品场景 `candidate_shell_tui` 在同一 `ShellWindow` 和 ConPTY 路径上连续执行两轮真实 Vim:进入全屏编辑、插入文字、resize 到 60×12、退出写盘、确认主屏 shell 返回。本机 Vim 输出未发送 `1049/1047/47`,所以此场景不计作备用屏验收;1049 的固定核心用例与真实 Vim 证据分开。固定自动命令写入 `candidate-vim.vim` 并以短 `-S` 命令加载,避免交互式 cmd 拆分长启动命令。启动和插入由 Vim 写入的真实 `VimEnter`/`InsertEnter` 事件文件确认,避免 `echon` 消息被重绘覆盖;随后通过插入模式的 `Ctrl-O` 调用 Vim 命令,实际保存 `&columns/&lines` 并检查 60×12,再以 `Ctrl-O:wq` 写盘退出。不把 ConPTY 重绘或 `ResizePseudoConsole` 返回当作 Vim 已就绪。本路径不声称验证 Vim 的 Escape 行为,独立控制台 ESC 接收另测。`-W` 日志核对插入文本与 `:wq` 顺序,其中可能含 Vim 内部特殊键,不能冒充纯键盘字节。随后用独立 `shell_query_client.exe` 发出真实 `CSI 6n`,保存终端经 `InputQueue` 返回的光标报告,并用 `shell_size_client.exe` 核对子进程尺寸。另有 resize 后独立 ESC 接收检查。 diff --git a/experiments/core/candidate_resize_replay.cpp b/experiments/core/candidate_resize_replay.cpp new file mode 100644 index 00000000..a79218f3 --- /dev/null +++ b/experiments/core/candidate_resize_replay.cpp @@ -0,0 +1,67 @@ +// Reproduce a captured parser/feed ordering without a console or UI. +// Input and full screen snapshots can contain local paths; keep them private. +#include "candidate_terminal.hpp" +#include +#include +#include +#include +#include + +namespace { +std::string read_bounded(const std::filesystem::path& path) { + std::ifstream input(path, std::ios::binary | std::ios::ate); + if (!input || input.tellg() < 0 || input.tellg() > 128 * 1024) + throw std::runtime_error("invalid resize replay input size"); + input.seekg(0); + std::ostringstream out; out << input.rdbuf(); + if (!input) throw std::runtime_error("resize replay input read failed"); + return out.str(); +} + +size_t decimal(const std::string& token) { + size_t value = 0; + const auto parsed = std::from_chars(token.data(), token.data() + token.size(), value); + if (parsed.ec != std::errc{} || parsed.ptr != token.data() + token.size()) + throw std::runtime_error("resize replay integer invalid"); + return value; +} +} + +int main(int argc, char** argv) { + try { + if (argc != 3 && (argc != 4 || std::string(argv[3]) != "--native-viewport")) + throw std::runtime_error("usage: candidate_resize_replay transport.bin plan.txt [--native-viewport]"); + const auto bytes = read_bounded(argv[1]); + const auto commands = read_bounded(argv[2]); + std::istringstream plan(commands); + termforge::CandidateTerminal terminal(24,80,100, argc == 4 + ? termforge::CandidateResizePolicy::native_viewport : termforge::CandidateResizePolicy::reflow_history); + size_t offset = 0, step = 0, traced_cells = 0; + std::string line; + while (std::getline(plan, line)) { + if (++step > 1024) throw std::runtime_error("resize replay event limit exceeded"); + std::istringstream record(line); + std::string kind, first, second, extra; + if (!(record >> kind >> first >> second) || (record >> extra) || + (kind != "R" && kind != "F")) + throw std::runtime_error("resize replay record invalid"); + const auto a = decimal(first), b = decimal(second); + if (kind == "R") { + if (a < 1 || a > 200 || b < 1 || b > 500) + throw std::runtime_error("resize replay dimensions invalid"); + terminal.resize(static_cast(a), static_cast(b)); + } else { + if (a != offset || !b || b > bytes.size() - offset) + throw std::runtime_error("resize replay feed partition invalid"); + terminal.feed(std::string_view(bytes).substr(offset, b)); offset += b; + } + const auto cells = (terminal.screen.history.size() + terminal.screen.lines.size()) * terminal.screen.width(); + if (cells > 1000000 - traced_cells) throw std::runtime_error("resize replay snapshot limit exceeded"); + traced_cells += cells; + std::cout << "{\"step\":" << step << ",\"offset\":" << offset << ",\"kind\":\"" << kind << "\",\"state\":"; + terminal.screen.json(std::cout); std::cout << "}\n"; + } + if (!plan.eof() || !step || offset != bytes.size()) throw std::runtime_error("resize replay plan incomplete"); + return 0; + } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } +} diff --git a/experiments/core/candidate_terminal.cpp b/experiments/core/candidate_terminal.cpp index cc12246e..50096b07 100644 --- a/experiments/core/candidate_terminal.cpp +++ b/experiments/core/candidate_terminal.cpp @@ -33,8 +33,9 @@ std::string cell_text(void* impl, size_t row, size_t column, size_t size) { } } // namespace -CandidateTerminal::CandidateTerminal(int rows, int columns, size_t history) +CandidateTerminal::CandidateTerminal(int rows, int columns, size_t history, CandidateResizePolicy resize_policy) : impl_(tf_terminal_new(static_cast(rows), static_cast(columns), history)), + resize_policy_(resize_policy), screen(rows, columns, history) { if (!impl_) throw std::invalid_argument("invalid candidate terminal dimensions"); synchronize(); @@ -86,12 +87,11 @@ std::string replay_key(const Line& line) { template std::vector replay_keys(const History& history, const Lines& lines) { std::vector result; - const auto append = [&](const Line& line) { - auto key = replay_key(line); - if (!key.empty()) result.push_back(std::move(key)); - }; - for (const auto& line : history) append(line); - for (const auto& line : lines) append(line); + result.reserve(history.size() + lines.size()); + // Keep empty entries aligned with their rows so reconciliation can reuse + // each serialization while scanning the original and incoming order. + for (const auto& line : history) result.push_back(replay_key(line)); + for (const auto& line : lines) result.push_back(replay_key(line)); return result; } } // namespace @@ -150,17 +150,18 @@ std::unordered_set CandidateTerminal::reconcile_replay(std::vector{}, retained); std::unordered_map old_counts, new_counts; - for (const auto& key : expected) ++old_counts[key]; - for (const auto& key : actual) ++new_counts[key]; + for (const auto& key : expected) if (!key.empty()) ++old_counts[key]; + for (const auto& key : actual) if (!key.empty()) ++new_counts[key]; std::vector origin; origin.insert(origin.end(), source.history.begin(), source.history.end()); origin.insert(origin.end(), source.lines.begin(), source.lines.end()); std::unordered_set mapped; size_t origin_index = 0; - for (auto& line : retained) { - const auto key = replay_key(line); + for (size_t index = 0; index < retained.size(); ++index) { + auto& line = retained[index]; + const auto& key = actual[index]; if (key.empty() || old_counts[key] != 1 || new_counts[key] != 1) continue; - while (origin_index < origin.size() && replay_key(origin[origin_index]) != key) ++origin_index; + while (origin_index < origin.size() && expected[origin_index] != key) ++origin_index; if (origin_index == origin.size()) break; line.id = origin[origin_index].id; line.version = origin[origin_index].version; @@ -258,8 +259,10 @@ void CandidateTerminal::finish() { void CandidateTerminal::resize(int rows, int columns) { std::array error{}; - check(tf_terminal_resize(impl_, static_cast(rows), static_cast(columns), - error.data(), error.size()), "candidate resize failed"); + const auto resize = resize_policy_ == CandidateResizePolicy::native_viewport + ? tf_terminal_resize_native_viewport : tf_terminal_resize; + check(resize(impl_, static_cast(rows), static_cast(columns), + error.data(), error.size()), "candidate resize failed"); begin_replay(); screen.resize(rows, columns); synchronize(); diff --git a/experiments/core/candidate_terminal.hpp b/experiments/core/candidate_terminal.hpp index 55de3176..5c3fef27 100644 --- a/experiments/core/candidate_terminal.hpp +++ b/experiments/core/candidate_terminal.hpp @@ -14,8 +14,11 @@ struct CandidateEvent { std::string bytes; }; +enum class CandidateResizePolicy { reflow_history, native_viewport }; + class CandidateTerminal { void* impl_ = nullptr; + const CandidateResizePolicy resize_policy_; uint64_t mode_bits_ = 0, grapheme_overflows_ = 0; std::unordered_map versions_; std::vector events_; @@ -37,7 +40,8 @@ class CandidateTerminal { public: Screen screen; - CandidateTerminal(int rows = 24, int columns = 80, size_t history = 100); + CandidateTerminal(int rows = 24, int columns = 80, size_t history = 100, + CandidateResizePolicy resize_policy = CandidateResizePolicy::reflow_history); ~CandidateTerminal(); CandidateTerminal(const CandidateTerminal&) = delete; CandidateTerminal& operator=(const CandidateTerminal&) = delete; diff --git a/experiments/core/candidate_terminal_test.cpp b/experiments/core/candidate_terminal_test.cpp index 8f1082c0..5e6ac3a0 100644 --- a/experiments/core/candidate_terminal_test.cpp +++ b/experiments/core/candidate_terminal_test.cpp @@ -510,6 +510,98 @@ void resize_and_errors() { try { terminal.feed(std::string(1024 * 1024 + 1, 'x')); } catch (const std::runtime_error&) { rejected = true; } require(rejected, "oversized feed was accepted"); } + +void resize_preserves_written_spaces() { + for (const auto policy : {termforge::CandidateResizePolicy::reflow_history, + termforge::CandidateResizePolicy::native_viewport}) { + termforge::CandidateTerminal terminal(3,4,100,policy); + terminal.feed("ABC D"); + for (const int width : {4,5,8,4,5}) { + terminal.resize(3,width); + termforge::TextDocument document(terminal.screen); + size_t last = document.size() - 1; + while (last && document.line(last).projection.text.empty()) --last; + termforge::TextSelection selection; + require(selection.set(document, {*document.position(0,0), + *document.position(last,document.line(last).projection.text.size())}), + "written-space selection failed"); + const auto copied = selection.copy(document,4096); + require(copied.status == termforge::SelectionCopyStatus::ok && copied.text == "ABC D", + "resize changed written spaces"); + } + } +} + +void native_viewport_resize() { + struct Case { + int rows, columns, new_rows, new_columns; + const char* source; + std::vector expected; + int row, column; + }; + // Fixed synthetic native CHAR_INFO observations: the native viewport has + // no access to rows already retained in the terminal's scrollback. + const Case cases[] = { + {3,8,6,8,"H0\r\nH1\r\nTOP\r\nMID\r\nBOT",{"TOP","MID","BOT","","",""},2,3}, + {3,8,3,16,"H0\r\nH1\r\n123456789\r\nP>",{"123456789","P>",""},1,2}, + {3,8,3,16,"H0\r\nH1\r\nH2\r\n123456789",{"H2","123456789",""},1,9}, + {3,8,3,5,"H0\r\n123456789012345678901234567890",{"90123","45678","90"},2,2}, + {4,8,4,10,"H0\r\nH1\r\nABCDEFGHIJKLMNO\r\nBOTTOM\x1b[3;2H",{"H1","ABCDEFGHIJ","KLMNO","BOTTOM"},1,9}, + {3,8,3,16,"H0\r\n123456789012345678901234567890",{"9012345678901234","567890",""},1,6}, + }; + for (const auto& test : cases) { + termforge::CandidateTerminal terminal(test.rows,test.columns,100, + termforge::CandidateResizePolicy::native_viewport); + terminal.feed(std::string("\x1b[2J\x1b[H") + test.source); + require(!terminal.screen.history.empty(), "native resize fixture lacks history"); + const auto retained = terminal.screen.history.front().id; + const auto copy_text = [&] { + termforge::TextDocument document(terminal.screen); + size_t last = document.size() - 1; + while (last && document.line(last).projection.text.empty()) --last; + const auto length = document.line(last).projection.text.size(); + termforge::TextSelection selection; + require(selection.set(document, {*document.position(0, 0), *document.position(last, length)}), + "native viewport whole-text selection failed"); + const auto copied = selection.copy(document, 4096); + require(copied.status == termforge::SelectionCopyStatus::ok, "native viewport whole-text copy failed"); + return copied.text; + }; + std::string expected_text = test.source; + if (const auto cursor_move = expected_text.find('\x1b'); cursor_move != std::string::npos) + expected_text.resize(cursor_move); + require(copy_text() == expected_text, "native viewport source text differs before resize"); + terminal.resize(test.new_rows,test.new_columns); + require(terminal.screen.lines.size() == test.expected.size(), "native viewport row count differs"); + for (size_t row=0; row --exe ` 在 10 秒监督期限内,按实际 feed 分段与 UI parser resize 次序离线重放。校验器拒绝溢出、不完整字节分区和未知事件;回放成功只表示执行了所捕获的模型操作,不表示原场景通过,也不证明原生宿主状态、真实 glyph 或性能。原运输和逐步完整屏幕可含测试启动路径,保留在 `local/`,公开结果仅发布经审查的身份及脱敏结论。 + +Schema 1 必须包含 recorder 的全部字段与有效整数范围、非递减 QPC、初始 24×80 网格及每次 parser resize 后一致的 feed 网格。UI 的 desired/parser_begin/parser_end/request 和 worker 的 generation/begin/end 分别配对;worker 观察可以早于对应 request 观察,也允许请求被合并而没有 host 调用,不据观察次序补造同步保证。回放计划每行仅允许 `R rows columns` 或 `F offset bytes` 的完整十进制记录;残缺、未知、多余字段、空行和整数溢出都失败,末行可无换行。边界拒绝与三份原离线复验见[校验器验证](results/2026-10-26-resize-replay-guards/validation.json),不改变原 #51 失败。 + 每次运行依次验证普通命令、中文、退格、左右方向键编辑,再运行一百万轮的输出命令。至少持续一秒且消费 64 KiB 后发送 Ctrl-C,再执行 `echo TF_AFTER`;确认对应状态与已提交帧后 `exit 37`。[#36](https://github.com/JavaZeroo/termforge/issues/36) 保留历史中断超时与根因缺口;本次 normal/resize 各十轮未复现,不能写成产品修复。独立的[原始 ConPTY 回归](../conpty/README.md#持续-cmd-输出与-ctrlc-独立回归)另保留输入/输出与系统文件身份。另测输出中关闭,正常/关闭各重复三次,检查 Job 活跃进程归零与自有 desktop 消失。监督器用真实存活 shell 验证超时清理。独立队列探针验证容量、背压、顺序、停止唤醒与输入拒绝。短回归不代表长时无泄漏。 产品真实 IME 单独由 [`research.ime.product_runner`](../../research/ime/README.md) 显式托管运行,使用严格受控的真实 `SendInput`,不在这些私有 desktop 场景中合成 IME 消息。五个 `ui-ime-*` 场景有独立输入桌面/焦点/代次检查和内外 Job/GO 门控,开发机拒绝运行;历史/Find 与 rename 双 Escape 已有[固定源码系统审计](../../research/ime/results/2026-10-24-product-ime-contexts/validation.json)。 diff --git a/experiments/shell/candidate_reply_close_test.cpp b/experiments/shell/candidate_reply_close_test.cpp new file mode 100644 index 00000000..1a5e0149 --- /dev/null +++ b/experiments/shell/candidate_reply_close_test.cpp @@ -0,0 +1,110 @@ +// Exercise the real receive -> parser -> reply route while closing the input +// side. The existing owned desktop/Job supervises the window and console child. +#define WinMain included_shell_entry +#include "shell.cpp" +#undef WinMain +#include "ime_desktop_process.hpp" + +namespace { +template +void reply_wait(Predicate predicate, const char* detail) { + const auto deadline = GetTickCount64() + 3000; + while (!predicate()) { + require(GetTickCount64() < deadline, detail); + Sleep(1); + } +} + +int reply_client(const wchar_t* name) { + BOOL owned = FALSE; + require(IsProcessInJob(GetCurrentProcess(), nullptr, &owned) && owned, "reply client requires owned Job"); + const std::wstring prefix(name); + const auto open = [&](const wchar_t* suffix) { + return tf_conpty::Handle(OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, (prefix + suffix).c_str())); + }; + auto ready = open(L"-ready"), release = open(L"-release"), written = open(L"-written"); + require(ready.get() && release.get() && written.get(), "reply client gates unavailable"); + DWORD mode = 0; + const auto output = GetStdHandle(STD_OUTPUT_HANDLE); + require(GetConsoleMode(output, &mode) && SetConsoleMode(output, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING), + "reply client output mode failed"); + require(SetEvent(ready.get()), "reply client ready failed"); + require(WaitForSingleObject(release.get(), 3000) == WAIT_OBJECT_0, "reply client release deadline"); + constexpr char bytes[] = "TF_REPLY_CLOSE_READY\r\n\x1b[6n"; + DWORD count = 0; + require(WriteFile(output, bytes, sizeof(bytes) - 1, &count, nullptr) && count == sizeof(bytes) - 1, + "reply client query write failed"); + require(SetEvent(written.get()), "reply client written signal failed"); + // The parent owns cancellation; do not let a natural child exit precede it. + Sleep(10000); + return 2; +} + +int reply_window_case() { + require(tf_shell::is_ime_desktop_child(), "reply test requires owned non-input desktop"); + BOOL owned = FALSE; + require(IsProcessInJob(GetCurrentProcess(), nullptr, &owned) && owned, "reply test requires owned Job"); + require(SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2), "reply test DPI setup failed"); + const auto prefix = L"Local\\TermForgeReply-" + std::to_wstring(GetCurrentProcessId()) + L"-" + std::to_wstring(GetTickCount64()); + const auto create = [&](const wchar_t* suffix) { + const auto event = CreateEventW(nullptr, TRUE, FALSE, (prefix + suffix).c_str()); + const auto error = GetLastError(); + if (!event || error == ERROR_ALREADY_EXISTS) { + if (event) CloseHandle(event); + throw std::runtime_error("reply gates must be fresh"); + } + return event; + }; + tf_conpty::Handle ready(create(L"-ready")), release(create(L"-release")), written(create(L"-written")); + ShellWindow window("", ""); + window.enable_runtime_observation(); window.begin_output_observation(); + tf_shell::ShellLaunch launch; + wchar_t path[32768]{}; + const auto path_size = GetModuleFileNameW(nullptr, path, 32768); + require(path_size && path_size < 32768, "reply fixture executable path unavailable"); + launch.id = L"reply-close"; launch.executable.assign(path, path_size); + launch.arguments = L"--reply-client \"" + prefix + L"\""; + window.create(nullptr, launch); + require(WaitForSingleObject(ready.get(), 3000) == WAIT_OBJECT_0, "reply console ready deadline"); + const auto before = window.runtime_snapshot(); + require(SetEvent(release.get()), "reply release failed"); + require(WaitForSingleObject(written.get(), 3000) == WAIT_OBJECT_0, "reply console write deadline"); + reply_wait([&] { return window.runtime_snapshot().output.queued_bytes > before.output.queued_bytes; }, + "reply transport receive deadline"); + require(window.runtime_snapshot().consumed_bytes == 0, "query must remain unparsed before close"); + window.close(); + reply_wait([&] { return window.runtime_snapshot().input.stopping; }, "reply input stop deadline"); + const auto deadline = GetTickCount64() + 5000; + while (IsWindow(window.handle())) { + require(GetTickCount64() < deadline, "reply window cleanup deadline"); + MSG message{}; + for (int count = 0; count < 256 && PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE); ++count) { + TranslateMessage(&message); DispatchMessageW(&message); + } + MsgWaitForMultipleObjects(0, nullptr, FALSE, 1, QS_ALLINPUT); + } + const bool query = window.observed_output().find("\x1b[6n") != std::string::npos; + const auto final = window.runtime_snapshot(); + std::cout << "{\"query_consumed_after_close\":" << (query ? "true" : "false") + << ",\"consumed_bytes\":" << final.consumed_bytes + << ",\"input_stopping\":" << (final.input.stopping ? "true" : "false") + << ",\"input_rejected\":" << final.input.rejected << "}\n"; + const auto result = window.collect(); + require(query && window.live_contains_line("TF_REPLY_CLOSE_READY") && final.consumed_bytes > 4 && + final.input.stopping && final.input.rejected == 0 && result.complete && result.active_children == 0, + "closing input must drain the real query without attempting a reply"); + return 0; +} +} + +int main(int argc, char** argv) { + try { + if (argc == 3 && std::string(argv[1]) == "--reply-client") return reply_client(utf16(argv[2]).c_str()); + if (argc == 2 && std::string(argv[1]) == "--owned-desktop-child") return reply_window_case(); + require(argc == 1, "unexpected reply test arguments"); + for (int round = 0; round < 2; ++round) + require(tf_shell::run_ime_desktop_child(12000) == 0, "owned candidate closing reply failed"); + std::cout << "{\"closing_reply\":\"passed\",\"owned_job_zero\":true,\"desktop_deleted\":true}\n"; + return 0; + } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } +} diff --git a/experiments/shell/clipboard_scenario_state.hpp b/experiments/shell/clipboard_scenario_state.hpp index c1fc2e20..2d6728de 100644 --- a/experiments/shell/clipboard_scenario_state.hpp +++ b/experiments/shell/clipboard_scenario_state.hpp @@ -247,6 +247,14 @@ class ClipboardScenarioOwner { return value.release_event; } bool occupied() const { return !delayed_ && GetOpenClipboardWindow()==window_; } + // [DEBUG-r99] Read-only observation accessors for issue #99 stage logging. + // Zero-wait event queries and atomic reads only: no ownership change, no + // publication side effects, no waits. Inert unless the scenario debug + // logger is enabled and never consulted by product logic. + const char* debug_step() const { return step_.load(); } + bool debug_released_signaled() const { return clipboard_event(released_.get()); } + DWORD debug_error() const { return error_.load(); } + DWORD debug_native_error() const { return native_error_.load(); } void release() { policy_.request_ordinary(); paste_check(SetEvent(release_.get())!=FALSE,"clipboard owner release failed"); diff --git a/experiments/shell/margin_checks.py b/experiments/shell/margin_checks.py index 2518c302..76856d63 100644 --- a/experiments/shell/margin_checks.py +++ b/experiments/shell/margin_checks.py @@ -28,7 +28,10 @@ def expected_source(): ).encode("ascii") -def validate_candidate_margins(data, consoles, source, transport): +REVIEWED_BACKEND = "reviewed-side-by-side-1.25.260930003" + + +def _validate_source_native_gap(consoles, source, transport): _require(source == expected_source(), "margins source differs") _require(type(transport) is bytes and 0 < len(transport) <= 8192, "margins transport bound") _require(transport.count(b"\x1b[?1049h") == 1 and transport.count(b"\x1b[?1049l") == 1, @@ -55,6 +58,16 @@ def validate_candidate_margins(data, consoles, source, transport): _require(final["lines"][:6] != required, "ConPTY margin behavior changed; compare it before accepting") _require(final["top"] == 0 and final["cursor_row"] == 6 and final["cursor_column"] == 0, "main final margin cursor differs") + return final, observed, required + + +def validate_candidate_margins(data, consoles, source, transport, *, expected_backend="system"): + if expected_backend == REVIEWED_BACKEND: + return _validate_reviewed_terminal(data, consoles, source, transport) + _require(expected_backend == "system" and data.get("conpty_backend", "system") == "system" and + type(data.get("conpty_creation_flags", 0)) is int and data.get("conpty_creation_flags", 0) == 0, + "unexpected ConPTY backend; explicit contract required") + final, observed, required = _validate_source_native_gap(consoles, source, transport) _require(type(data) is dict and data.get("schema") == "termforge-candidate-margins-v1" and data.get("columns") == final["columns"] and data.get("rows") == final["rows"] and data.get("alternate_enabled") is False and data.get("default_fg") == -1 and data.get("default_bg") == -1 and @@ -74,7 +87,8 @@ def validate_candidate_margins(data, consoles, source, transport): _require(not any("ALT_" in line for line in document), "alternate text polluted main history") _require(all(type(cell) is dict and cell.get("fg") == -1 and cell.get("bg") == -1 and cell.get("bold") is False for cell in cells), "candidate default style differs") - return {"scoped_validation_passed": True, "observation_only": True, + return {"scoped_validation_passed": True, "observation_only": True, "conpty_backend": "system", + "terminal_main_margins_restored": False, "margins_per_buffer_verified": False, "native_main_margins_restored": False, "conpty_margin_difference_observed": True, "candidate_matches_native": True, "alternate_scroll_region_verified": True, "scroll_region_verified": False, @@ -87,6 +101,49 @@ def validate_candidate_margins(data, consoles, source, transport): "transport_bytes": len(transport)} +def _validate_reviewed_terminal(data, consoles, source, transport): + # Preserve the original source and native API failure assertions. The new + # contract independently requires the terminal to process forwarded VT. + final, observed, required = _validate_source_native_gap(consoles, source, transport) + _require(data.get("conpty_backend") == REVIEWED_BACKEND and + type(data.get("conpty_creation_flags")) is int and data["conpty_creation_flags"] == 0x18, + "reviewed terminal backend identity missing") + _require(transport.count(b"\x1b[2;3r") == 1 and transport.count(b"\x1b[4;5r") == 1, + "original per-buffer margins were not forwarded") + _require(data.get("schema") == "termforge-candidate-margins-v1" and + data.get("columns") == final["columns"] and data.get("rows") == final["rows"] and + data.get("alternate_enabled") is False and data.get("default_fg") == -1 and + data.get("default_bg") == -1 and data.get("frame_matches_full") is True and + data.get("presented_done") is None and + type(data.get("cursor_row")) is int and type(data.get("cursor_column")) is int and + data["cursor_row"] == 6 and data["cursor_column"] == 0, + "reviewed terminal mode, style, cursor or retained frame differs") + expected = required + [""] * (data["rows"] - len(required)) + _require(data.get("visible_lines") == expected and data["visible_lines"] != final["lines"], + "reviewed terminal did not independently restore main margins") + document, cells = data.get("document_lines"), data.get("styled_cells") + _require(type(document) is list and len(document) <= data["rows"] + 100 and + all(type(line) is str for line in document) and type(cells) is list, + "reviewed terminal retained document or styles missing") + for text in required: + if text: + _require(sum(line == text for line in document) == 1, + "reviewed terminal main text missing or duplicated") + _require(not any("ALT_" in line for line in document), "alternate text polluted main history") + _require(all(type(cell) is dict and cell.get("fg") == -1 and cell.get("bg") == -1 and + cell.get("bold") is False for cell in cells), "reviewed terminal default style differs") + return {"scoped_validation_passed": True, "observation_only": True, "conpty_backend": REVIEWED_BACKEND, + "terminal_main_margins_restored": True, "margins_per_buffer_verified": False, + "native_main_margins_restored": False, "conpty_margin_difference_observed": True, + "candidate_matches_native": False, "alternate_scroll_region_verified": True, + "scroll_region_verified": True, "outside_preserved": True, "native_main_restored": True, + "native_viewport_top": final["top"], "windterm_runtime_equivalence_verified": False, + "origin_and_wrap_modes_verified": False, "resize_cross_verified": False, + "full_r2_contract_verified": False, "presented_done": None, + "source_sha256": hashlib.sha256(source).hexdigest(), + "transport_sha256": hashlib.sha256(transport).hexdigest(), "transport_bytes": len(transport)} + + def _bounded(path, limit): _require(path.is_file() and not path.is_symlink(), "missing or linked margins evidence") with path.open("rb") as stream: @@ -95,11 +152,11 @@ def _bounded(path, limit): return value -def validate_margins_output(directory): +def validate_margins_output(directory, *, expected_backend="system"): directory = Path(directory) data = json.loads(_bounded(directory / "candidate-margins.json", 512000).decode("utf-8")) consoles = {stage: validate_console_window(directory, expected_role=role, expected_generation=1, prefix=prefix) for stage, (role, prefix) in STAGES.items()} source = _bounded(directory / "candidate-margins.source.bin", 4096) transport = _bounded(directory / "candidate-margins.transport.bin", 8192) - return validate_candidate_margins(data, consoles, source, transport) + return validate_candidate_margins(data, consoles, source, transport, expected_backend=expected_backend) diff --git a/experiments/shell/output_record_checkpoint.hpp b/experiments/shell/output_record_checkpoint.hpp new file mode 100644 index 00000000..d064f7ea --- /dev/null +++ b/experiments/shell/output_record_checkpoint.hpp @@ -0,0 +1,39 @@ +// Explicit synthetic-consumer coordination; disabled during ordinary use. +#pragma once +#include +#include +#include +#include + +namespace tf_shell_test { +class OutputRecordCheckpoint { +public: + using Clock=std::chrono::steady_clock; + static constexpr size_t byte_limit=256; +private: + bool armed_=false,held_=false; + size_t consumed_=0; + Clock::time_point deadline_{}; +public: + void arm(bool at_boundary,Clock::time_point deadline) { + if(armed_) return; + if(deadline<=Clock::now()) throw std::invalid_argument("output checkpoint deadline expired"); + deadline_=deadline;armed_=true;held_=at_boundary;consumed_=0; + } + size_t read_budget(size_t normal) const { + if(!armed_) return normal; + if(Clock::now()>=deadline_) throw std::runtime_error("output checkpoint deadline expired"); + if(!held_ && consumed_>=byte_limit) throw std::runtime_error("output checkpoint record bound exceeded"); + return held_?0:1; + } + void consumed(std::string_view bytes) { + if(!armed_) return; + if(held_ || bytes.size()!=1) throw std::runtime_error("output checkpoint read boundary violated"); + if(++consumed_>byte_limit) throw std::runtime_error("output checkpoint record bound exceeded"); + if(bytes.front()=='\n') held_=true; + } + bool held() const { return held_; } + bool active() const { return armed_; } + void release() noexcept { armed_=held_=false;consumed_=0; } +}; +} // namespace tf_shell_test diff --git a/experiments/shell/paste_checks.py b/experiments/shell/paste_checks.py index 8a2cbf90..3de07a7a 100644 --- a/experiments/shell/paste_checks.py +++ b/experiments/shell/paste_checks.py @@ -3,6 +3,19 @@ from pathlib import Path +def validate_paste_fence(row: dict, generation: int) -> None: + fence = row.get("fence") + if (not isinstance(fence, dict) or type(fence.get("generation")) is not int or + fence["generation"] != generation or fence.get("held") is not True or + fence.get("wait_finished") is not True or fence.get("other_responsive") is not True or + fence.get("wrong_generation_rejected") is not True or + fence.get("exit") not in (("cancelled",) if row.get("name") == "cancel" else ("cancelled", "stopped")) or + fence.get("final_state") != ("cancelled" if row.get("name") == "cancel" else "stopped") or + type(fence.get("owned_bytes")) is not int or not 0 < fence["owned_bytes"] <= 1024 or + row["before_accepted"] - row["before_sent"] != fence["owned_bytes"]): + raise ValueError("window paste sender fence identity, hold, wake or isolation evidence is incomplete") + + def validate_paste_workflow(directory: Path) -> dict: directory = Path(directory) data = json.loads((directory / "paste-workflow.json").read_text(encoding="utf-8")) @@ -18,7 +31,7 @@ def validate_paste_workflow(directory: Path) -> dict: raise ValueError("window paste cancellation and close evidence is incomplete") expected = b"\xe6\xb1\x89" * 1000000 summary = {} - for row in cases: + for generation, row in enumerate(cases, 1): integers = ("target", "total", "before_sent", "before_accepted", "accepted", "sent", "in_flight", "retained_capacity", "checkpoint_bytes", "received") if any(type(row.get(key)) is not int or row[key] < 0 for key in integers): @@ -30,6 +43,7 @@ def validate_paste_workflow(directory: Path) -> dict: row["in_flight"] or row["retained_capacity"] > 32 or row["checkpoint_bytes"] != 768 or row.get("flood_observed") is not True or row.get("state") not in ("cancelled", "failed")): raise ValueError("window paste did not remain partial, bounded and terminal under output flood") + validate_paste_fence(row, generation) received = (directory / f"{row['name']}.bin").read_bytes() if (not received or len(received) != row["received"] or len(received) > row["sent"] or received != expected[:len(received)]): diff --git a/experiments/shell/paste_scenario_state.hpp b/experiments/shell/paste_scenario_state.hpp index 34fa911e..77db7d43 100644 --- a/experiments/shell/paste_scenario_state.hpp +++ b/experiments/shell/paste_scenario_state.hpp @@ -2,10 +2,12 @@ #pragma once #include "../conpty/session.hpp" #include "paste.hpp" +#include "sender_fence_pipe.hpp" #include #include #include #include +#include namespace tf_shell_test { inline void paste_check(bool ok, const char* why) { if(!ok) throw std::runtime_error(why); } @@ -37,15 +39,20 @@ struct PasteWindowResult { std::string name; tf_shell::PasteProgress before, after; size_t checkpoint_bytes=0, received=0; - bool flood=false; + bool flood=false,other_responsive=false; + SenderFence::Snapshot fence_before,fence_after; + bool wrong_generation_rejected=false; }; struct PasteScenarioState { static constexpr size_t large_units=1000000; std::unique_ptr receiver; std::shared_ptr transfer; + std::shared_ptr fence; + std::unique_ptr control; std::vector results; PasteWindowResult pending; HWND target=nullptr; + size_t other_consumed=0; bool default_enter=false, escape_cancel=false, tab_enter=false; std::array original_keys{}; bool keys_saved=false; @@ -63,7 +70,7 @@ struct PasteScenarioState { } void start_receiver(const std::filesystem::path& capture,const std::string& name) { receiver=std::make_unique(static_cast(results.size())); - transfer.reset(); pending={}; pending.name=name; + control.reset(); transfer.reset(); fence.reset(); pending={}; pending.name=name; paste_check(!std::filesystem::exists(capture),"paste receiver capture must be new"); } std::wstring receiver_command(const std::filesystem::path& capture) const { @@ -76,16 +83,55 @@ struct PasteScenarioState { std::ifstream file(path,std::ios::binary); paste_check(file.good(),"paste evidence capture missing"); return {std::istreambuf_iterator(file),std::istreambuf_iterator()}; } + bool held() const { + paste_check(fence && transfer,"paste fence and transfer must be observed after admission"); + const auto state=fence->snapshot(fence->ticket()).state; + paste_check(state==SenderFence::State::armed || state==SenderFence::State::held,"paste fence exited before UI action"); + return state==SenderFence::State::held; + } void before_action(const std::filesystem::path& capture,bool flood) { + paste_check(held(),"paste action requires an actual held writer"); + paste_check(pending.other_responsive,"paste action requires another session response while held"); + control=std::make_unique(fence); + const auto generation=fence->ticket().generation; + control->send_go(generation+1);control->dispatch();const auto ack=control->acknowledgement(); + pending.wrong_generation_rejected=ack && ack->generation==generation+1 && !ack->accepted && held(); + paste_check(pending.wrong_generation_rejected,"wrong-generation pipe GO released the UI writer"); + pending.fence_before=fence->snapshot(fence->ticket()); paste_check(transfer!=nullptr && receiver->checkpoint(),"paste action requires a live transfer and receiver checkpoint"); pending.before=transfer->progress(); pending.flood=flood; pending.checkpoint_bytes=read(capture).size(); - paste_check(pending.checkpoint_bytes==768 && flood && pending.before.sent>=16384 && - pending.before.accepted=16384 && + pending.before.acceptedprogress(); const auto bytes=read(capture); pending.received=bytes.size(); + pending.fence_after=fence->snapshot(fence->ticket()); + const bool woke_cancelled=pending.fence_after.wait_exit_state==SenderFence::State::cancelled; + const bool woke_stopped=pending.fence_after.wait_exit_state==SenderFence::State::aborted && + pending.fence_after.wait_exit_reason==SenderFence::AbortReason::stop; + paste_check(pending.fence_after.wait_finished && + (cancelled?woke_cancelled:(woke_cancelled || woke_stopped)) && + (cancelled?pending.fence_after.state==SenderFence::State::cancelled: + pending.fence_after.state==SenderFence::State::aborted && pending.fence_after.abort_reason==SenderFence::AbortReason::stop), + "UI action did not wake its held writer through cancel/stop"); const auto& progress=pending.after; paste_check(progress.total==large_units*3 && progress.acceptedsnapshot(fence->ticket());const auto progress=transfer->progress(); + paste_check(state.wait_finished && progress.state==tf_shell::PasteState::failed && !progress.in_flight && + progress.retained_capacity<=32,"control failure did not release writer-owned payload"); + std::ofstream out(path,std::ios::binary); + out<<"{\"eof_observed\":"<<(control->eof_observed()?"true":"false") + <<",\"timed_out\":"<<(state.state==SenderFence::State::timed_out?"true":"false") + <<",\"wait_finished\":"<<(state.wait_finished?"true":"false") + <<",\"state\":\"failed\",\"in_flight\":"<(deadline-now)) == WAIT_OBJECT_0, + "escape input timeout"); wchar_t value = 0; DWORD count = 0; require(ReadConsoleW(input, &value, 1, &count, nullptr) && count == 1 && value == 27, diff --git a/experiments/shell/queue_probe.cpp b/experiments/shell/queue_probe.cpp index 64e05dd9..62a3bc0c 100644 --- a/experiments/shell/queue_probe.cpp +++ b/experiments/shell/queue_probe.cpp @@ -20,7 +20,7 @@ static std::wstring executable_path() { if (!size || size == path.size()) tf_conpty::fail("GetModuleFileName"); path.resize(size); return path; } -static int child(const wchar_t* ready_name) { +static int child(const wchar_t* ready_name, bool prime_input) { tf_conpty::Handle ready(OpenEventW(EVENT_MODIFY_STATE, FALSE, ready_name)); if (!ready.get()) tf_conpty::fail("OpenEvent(queue child)"); const std::wstring query_name = std::wstring(ready_name) + L"-query"; @@ -31,8 +31,27 @@ static int child(const wchar_t* ready_name) { !SetConsoleMode(output, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) tf_conpty::fail("SetConsoleMode(queue child)"); if (!SetEvent(ready.get())) tf_conpty::fail("SetEvent(queue child)"); - // Never reads input. Real console output fills while the parent reader is - // held at an explicit gate. No system clipboard or desktop input is used. + size_t prefix_read = 0; + if (prime_input) { + tf_conpty::Handle primed(OpenEventW(EVENT_MODIFY_STATE, FALSE, (std::wstring(ready_name) + L"-primed").c_str())); + tf_conpty::Handle flood(OpenEventW(SYNCHRONIZE, FALSE, (std::wstring(ready_name) + L"-flood").c_str())); + check(primed.get() && flood.get(), "open queue input phase gates"); + // A host's unread-input capacity is not a portable 16 KiB guarantee. + // Consume and verify exactly that prefix, then never read again. The + // sender still submits the original 4 MiB through the real InputQueue. + while (prefix_read < 16384) { + char bytes[256]; DWORD count = 0; + check(ReadFile(input, bytes, sizeof bytes, &count, nullptr) && count && count <= 16384 - prefix_read, + "read queue priming prefix"); + check(std::string_view(bytes, count).find_first_not_of('a') == std::string_view::npos, + "queue priming prefix differs from independent ASCII oracle"); + prefix_read += count; + } + check(SetEvent(primed.get()) != FALSE, "signal verified queue priming prefix"); + check(WaitForSingleObject(flood.get(), 3000) == WAIT_OBJECT_0, "queue flood phase release"); + } + // From this point input is never read. Output fills while the parent reader + // is held at its explicit gate; the old observation mode never primes input. const std::string bytes(65536, 'F'); for (;;) { if (WaitForSingleObject(query.get(), 0) == WAIT_OBJECT_0) { @@ -44,7 +63,8 @@ static int child(const wchar_t* ready_name) { std::this_thread::yield(); } const std::string reply = "\r\nTFQUEUE-SIZE:" + std::to_string(dimensions.dwSize.X) + "x" + - std::to_string(dimensions.dwSize.Y) + ":END\r\n"; + std::to_string(dimensions.dwSize.Y) + ":END\r\nTFQUEUE-PRIMED:" + + std::to_string(prefix_read) + ":END\r\n"; DWORD count = 0; if (!WriteFile(output, reply.data(), static_cast(reply.size()), &count, nullptr) || count != reply.size()) tf_conpty::fail("WriteFile(queue size)"); @@ -128,9 +148,11 @@ static void lifecycle_edges() { std::cout << "{\"case\":\"lifecycle-edges\",\"stopped_uncompleted_generation\":3,\"input_error_preserved\":true," "\"resize_error_preserved\":true}\n"; } -static void real_workers() { +enum class WriterAction { stop, cancel, cancel_stop_race }; +static void real_workers(bool fence_sender, WriterAction action) { std::mutex gate_mutex; std::condition_variable gate_changed; bool reader_entered = false, reader_released = false, resize_entered=false, resize_released=false; + bool reader_gate_enabled = !fence_sender; OutputQueue output; tf_conpty::Session session; InputQueue input(session); @@ -154,27 +176,96 @@ static void real_workers() { if (!ready.get()) tf_conpty::fail("CreateEvent(queue ready)"); tf_conpty::Handle query(CreateEventW(nullptr, FALSE, FALSE, (name + L"-query").c_str())); if (!query.get()) tf_conpty::fail("CreateEvent(queue query)"); + tf_conpty::Handle primed(CreateEventW(nullptr, TRUE, FALSE, (name + L"-primed").c_str())); + tf_conpty::Handle flood(CreateEventW(nullptr, TRUE, FALSE, (name + L"-flood").c_str())); + check(primed.get() && flood.get(), "create queue input phase gates"); try { - session.start(executable_path(), L"--queue-child \"" + name + L"\"", [&](std::string_view bytes) { - { std::unique_lock lock(gate_mutex); reader_entered = true; gate_changed.notify_all(); - gate_changed.wait(lock, [&] { return reader_released; }); } + session.start(executable_path(), (fence_sender ? L"--queue-child-primed \"" : L"--queue-child \"") + name + L"\"", [&](std::string_view bytes) { + { std::unique_lock lock(gate_mutex); + if (reader_gate_enabled) { + reader_entered = true; gate_changed.notify_all(); + gate_changed.wait(lock, [&] { return reader_released; }); + } } output.push(bytes); }); check(WaitForSingleObject(ready.get(), 3000) == WAIT_OBJECT_0, "real console child ready"); - { std::unique_lock lock(gate_mutex); + if (!fence_sender) { std::unique_lock lock(gate_mutex); check(gate_changed.wait_for(lock, std::chrono::seconds(3), [&] { return reader_entered; }), "real output reader reached gate"); } tf_shell::PreparedPaste prepared; prepared.bytes.assign(4 * 1024 * 1024, 'a'); auto transfer = std::make_shared(1, std::move(prepared)); - check(input.begin_paste(transfer), "real input transfer accepted"); input.start(); + const auto observation_deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3); + auto fence = fence_sender ? std::make_shared(transfer, 1, observation_deadline) : nullptr; + check(input.begin_paste(transfer, fence), "real input transfer accepted"); input.start(); InputQueue::Snapshot held{}; - await([&] { held = input.snapshot(); return held.in_flight_bytes && transfer->progress().sent >= 16384; }, - "real input writer holds a chunk"); + try { + while (true) { + held = input.snapshot(); + const bool held_fence = !fence || fence->snapshot(fence->ticket()).state == tf_shell_test::SenderFence::State::held; + if (held_fence && held.in_flight_bytes && transfer->progress().sent >= 16384) break; + check(std::chrono::steady_clock::now() < observation_deadline, "real input writer holds a chunk"); + std::this_thread::yield(); + } + } catch (...) { + const auto observed = transfer->progress(); + bool observed_reader_entered = false, observed_reader_released = false; + { std::lock_guard lock(gate_mutex); + observed_reader_entered = reader_entered; observed_reader_released = reader_released; } + std::cerr << "{\"case\":\"writer-observation-rejected\",\"total\":" << observed.total + << ",\"accepted\":" << observed.accepted << ",\"sent\":" << observed.sent + << ",\"paste_in_flight\":" << observed.in_flight << ",\"queue_in_flight\":" << held.in_flight_bytes + << ",\"state\":" << static_cast(observed.state) + << ",\"reader_entered\":" << observed_reader_entered << ",\"reader_released\":" << observed_reader_released << "}\n"; + throw; + } + if (fence_sender) { + while (WaitForSingleObject(primed.get(), 0) != WAIT_OBJECT_0) { + check(std::chrono::steady_clock::now() < observation_deadline, "real child verified the 16 KiB prefix"); + std::this_thread::yield(); + } + { std::lock_guard lock(gate_mutex); reader_gate_enabled = true; } + check(SetEvent(flood.get()) != FALSE, "release real queue child output flood"); + std::unique_lock lock(gate_mutex); + check(gate_changed.wait_until(lock, observation_deadline, [&] { return reader_entered; }), "real output reader reached gate"); + } check(!held.queued_bytes && held.in_flight_bytes <= 1024 && held.started && !held.joined && !held.stopping && !held.failed, "dequeued input remains in flight"); const auto before_stop = transfer->progress(); + if (action == WriterAction::cancel) { + input.cancel_paste(); + while (!tf_shell::paste_terminal(transfer->progress().state)) { + check(std::chrono::steady_clock::now() < observation_deadline, "real queue cancellation finishes its claimed chunk"); + std::this_thread::yield(); + } + const auto cancelled = transfer->progress(); + check(cancelled.state == tf_shell::PasteState::cancelled && cancelled.sent == before_stop.accepted && + cancelled.accepted == before_stop.accepted && !cancelled.in_flight && cancelled.retained_capacity <= 32, + "real queue cancellation uses send acknowledgement for its claimed chunk"); + } else if (action == WriterAction::cancel_stop_race) { + std::promise start; + const auto go = start.get_future().share(); + std::thread cancel([&] { go.wait(); input.cancel_paste(); }); + start.set_value(); input.stop(); cancel.join(); + } input.stop(); const auto joined_input = input.snapshot(); check(joined_input.started && joined_input.joined && joined_input.stopping && !joined_input.queued_bytes && !joined_input.in_flight_bytes && !joined_input.failed, "input stop joins and releases held payload"); + if (fence_sender) { + const auto stopped_fence = fence->snapshot(fence->ticket()); + const auto stopped_paste = transfer->progress(); + check(stopped_fence.ever_held && stopped_fence.wait_finished && + stopped_fence.state == tf_shell_test::SenderFence::State::aborted && + stopped_fence.abort_reason == tf_shell_test::SenderFence::AbortReason::stop, + "stop wakes the held writer without a control GO"); + check(tf_shell::paste_terminal(stopped_paste.state) && !stopped_paste.in_flight && + stopped_paste.state != tf_shell::PasteState::completed && + stopped_paste.sent >= before_stop.sent && stopped_paste.sent <= before_stop.accepted && + stopped_paste.accepted == before_stop.accepted && + stopped_paste.retained_capacity <= 32, + "stopped pre-send chunk is released without a fabricated write acknowledgement"); + if (action == WriterAction::stop) + check(stopped_paste.state == tf_shell::PasteState::failed && stopped_paste.sent == before_stop.sent, + "stop never submits its pre-send held chunk"); + } resize.request(40, 100); resize.start(); {std::unique_lock lock(gate_mutex); check(gate_changed.wait_for(lock,std::chrono::seconds(3),[&] {return resize_entered;}),"resize worker reaches explicit pre-API gate");} @@ -196,22 +287,33 @@ static void real_workers() { check(joined_resize.started && joined_resize.joined && joined_resize.stopping && !joined_resize.working && !joined_resize.pending, "resize stop joins worker"); if (!SetEvent(query.get())) tf_conpty::fail("SetEvent(queue size query)"); - std::string tail; + std::string tail; bool size_seen = false, prefix_seen = false; await([&] { tail += output.take(65536); - const bool found = tail.find("TFQUEUE-SIZE:102x42:END") != std::string::npos; + size_seen = size_seen || tail.find("TFQUEUE-SIZE:102x42:END") != std::string::npos; + prefix_seen = prefix_seen || tail.find(fence_sender ? "TFQUEUE-PRIMED:16384:END" : "TFQUEUE-PRIMED:0:END") != std::string::npos; if (tail.size() > 128) tail.erase(0, tail.size() - 128); - return found; + return size_seen && prefix_seen; }, "real child observes latest requested dimensions"); check(session.wait_exit(3000) && session.exit_code == 0, "real queue child exits normally after size report"); cleanup(); check(session.cleanup_complete && !session.active_after && session.child_signaled && session.exit_code == 0, "real queue fixture Job cleanup"); - std::cout << "{\"case\":\"real-workers\",\"input_queued\":" << held.queued_bytes + const char* action_name = action == WriterAction::stop ? "stop" : + action == WriterAction::cancel ? "cancel" : "cancel-stop-race"; + const auto after = transfer->progress(); + std::cout << "{\"case\":\"real-workers\",\"action\":\"" << action_name << "\",\"input_queued\":" << held.queued_bytes << ",\"input_in_flight\":" << held.in_flight_bytes << ",\"input_joined\":true,\"working_generation\":" << coalesced.working_generation << ",\"pending_generation\":" << coalesced.pending_generation << ",\"completed_generation\":" << complete.completed_generation << ",\"child_columns\":102,\"child_rows\":42,\"input_submitted_before_stop\":" << before_stop.sent + << ",\"input_accepted_before_stop\":" << before_stop.accepted + << ",\"paste_in_flight_before_stop\":" << before_stop.in_flight + << ",\"total\":" << before_stop.total << ",\"child_prefix_read\":" << (fence_sender ? 16384 : 0) + << ",\"sender_fence_held\":" << (fence_sender ? "true" : "false") + << ",\"sent_after\":" << after.sent << ",\"accepted_after\":" << after.accepted + << ",\"in_flight_after\":" << after.in_flight << ",\"retained_capacity_after\":" << after.retained_capacity + << ",\"state_after\":" << static_cast(after.state) << ",\"job_active_after\":0}\n"; } catch (...) { const auto original = std::current_exception(); @@ -221,14 +323,21 @@ static void real_workers() { } int wmain(int argc, wchar_t** argv) { try { - if (argc == 3 && std::wstring(argv[1]) == L"--queue-child") return child(argv[2]); + if (argc == 3 && std::wstring(argv[1]) == L"--queue-child") return child(argv[2], false); + if (argc == 3 && std::wstring(argv[1]) == L"--queue-child-primed") return child(argv[2], true); + bool fence_sender = true; + if (argc == 2 && std::wstring(argv[1]) == L"--ungated-writer-observation") { fence_sender = false; argc = 1; } if (argc == 2 && std::wstring(argv[1]) == L"--supervised") { std::string command; check(static_cast(std::getline(std::cin, command)) && command == "GO", "owned supervisor gate"); argc = 1; } check(argc == 1, "unknown queue probe arguments"); - bounded_queues(); lifecycle_edges(); real_workers(); + bounded_queues(); lifecycle_edges(); real_workers(fence_sender, WriterAction::stop); + if (fence_sender) { + real_workers(true, WriterAction::cancel); + real_workers(true, WriterAction::cancel_stop_race); + } std::cout << "bounded queues, backpressure, ordering, in-flight resize and stop verified\n"; return 0; } catch (const std::exception& e) { std::cerr << e.what() << '\n'; return 2; } diff --git a/experiments/shell/queues.hpp b/experiments/shell/queues.hpp index 7d8e4480..0cc8379a 100644 --- a/experiments/shell/queues.hpp +++ b/experiments/shell/queues.hpp @@ -2,6 +2,7 @@ #pragma once #include "../conpty/session.hpp" #include "paste.hpp" +#include "sender_fence.hpp" #include #include @@ -58,6 +59,7 @@ class InputQueue { std::exception_ptr error_; std::thread writer_; std::shared_ptr paste_; + std::shared_ptr sender_fence_; public: struct Snapshot { size_t queued_bytes = 0, in_flight_bytes = 0, high_water_bytes = 0, rejected = 0; @@ -87,17 +89,27 @@ class InputQueue { (paste_ && !tf_shell::paste_terminal(paste_->progress().state)); }); if (stopping_) break; std::string bytes; + std::shared_ptr fence; writing_paste.reset(); if (!chunks_.empty()) { bytes = std::move(chunks_.front()); chunks_.pop_front(); bytes_ -= bytes.size(); } else { writing_paste = paste_; bytes = writing_paste->take_chunk(); + fence = sender_fence_; high_water = (std::max)(high_water, bytes.size()); } in_flight_bytes_ = bytes.size(); lock.unlock(); if (bytes.empty()) continue; // Cancelled between the predicate and take. if (writing_paste) { + if (fence) { + fence->before_send(writing_paste, bytes.size()); + // A CV wait is not cancelled by CancelSynchronousIo. + // stop() first publishes stopping and wakes it; do + // not start a new write after that observation. + std::lock_guard stopped(mutex_); + if (stopping_) throw std::runtime_error("input stopped at sender fence"); + } session_.send(bytes, [&](size_t written) { writing_paste->acknowledge_written(written); }); writing_paste->finish_chunk(); } else session_.send(bytes); @@ -132,15 +144,30 @@ class InputQueue { // handle and target identity; later tab switches never retarget this queue. // Previously accepted keystrokes drain first. New keystrokes are rejected // while pasting, so they cannot interleave with clipboard bytes. - bool begin_paste(const std::shared_ptr& paste) { + bool begin_paste(const std::shared_ptr& paste, + const std::shared_ptr& fence = {}) { std::lock_guard lock(mutex_); if (error_) std::rethrow_exception(error_); - if (stopping_ || !paste || (paste_ && !tf_shell::paste_terminal(paste_->progress().state)) || !paste->claim_writer()) return false; - paste_ = paste; ready_.notify_one(); return true; + if (stopping_ || !paste || (paste_ && !tf_shell::paste_terminal(paste_->progress().state))) return false; + if (fence && !fence->attach(paste)) return false; + if (!paste->claim_writer()) { + if (fence) fence->abort(fence->ticket()); + return false; + } + paste_ = paste; sender_fence_ = fence; ready_.notify_one(); return true; + } + void cancel_paste() { + std::shared_ptr fence; + { std::lock_guard lock(mutex_); + if (paste_) paste_->cancel(); + fence = sender_fence_; ready_.notify_all(); } + if (fence) fence->cancelled(fence->ticket()); } void stop() noexcept { + std::shared_ptr fence; { std::lock_guard lock(mutex_); stopping_ = true; chunks_.clear(); bytes_ = 0; - if (paste_) paste_->cancel(); ready_.notify_all(); } + if (paste_) paste_->cancel(); fence = sender_fence_; ready_.notify_all(); } + if (fence) fence->abort(fence->ticket(), tf_shell_test::SenderFence::AbortReason::stop); if (writer_.joinable()) { // Retry covers the race where cancellation runs just before WriteFile. while (WaitForSingleObject(writer_.native_handle(), 10) == WAIT_TIMEOUT) diff --git a/experiments/shell/resize.hpp b/experiments/shell/resize.hpp index da9f739d..20843d15 100644 --- a/experiments/shell/resize.hpp +++ b/experiments/shell/resize.hpp @@ -9,6 +9,7 @@ class ResizeWorker { // performs Session::resize; a working generation means worker ownership, // not proof that the kernel call is blocked or a child applied the size. std::function before_resize_; + std::function observation_; mutable std::mutex mutex_; std::condition_variable ready_; std::thread thread_; @@ -24,8 +25,9 @@ class ResizeWorker { unsigned requested_generation = 0, completed_generation = 0, working_generation = 0, pending_generation = 0; bool working = false, pending = false, stopping = false, started = false, joined = false, failed = false; }; - explicit ResizeWorker(tf_conpty::Session& session,std::function before_resize={}) - : session_(session),before_resize_(std::move(before_resize)) {} + explicit ResizeWorker(tf_conpty::Session& session,std::function before_resize={}, + std::function observation={}) + : session_(session),before_resize_(std::move(before_resize)),observation_(std::move(observation)) {} ~ResizeWorker() { stop(); } Snapshot snapshot() const { std::lock_guard lock(mutex_); @@ -49,7 +51,9 @@ class ResizeWorker { working_ = true; working_generation_ = generation; lock.unlock(); if(before_resize_) before_resize_(); + if(observation_) observation_(true, generation, rows, columns); session_.resize(static_cast(columns), static_cast(rows)); + if(observation_) observation_(false, generation, rows, columns); lock.lock(); completed_ = generation; working_ = false; working_generation_ = 0; } } catch (...) { diff --git a/experiments/shell/resize_trace.hpp b/experiments/shell/resize_trace.hpp new file mode 100644 index 00000000..9feeef78 --- /dev/null +++ b/experiments/shell/resize_trace.hpp @@ -0,0 +1,86 @@ +// Opt-in observations for synthetic resize scenarios; payloads stay local. +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tf_shell_test { +class ResizeTrace { +public: + enum Kind { desired, parser_begin, parser_end, request, host_begin, host_end, feed, child_ack }; +private: + struct Event { + Kind kind; + uint64_t qpc, consumed; + unsigned generation; + int rows, columns, stage; + size_t bytes; + }; + std::atomic enabled_{false}; + mutable std::mutex mutex_; + std::vector events_; + std::string transport_; + uint64_t consumed_ = 0; + bool events_overflow_ = false, transport_overflow_ = false; + static constexpr size_t event_limit = 1024, transport_limit = 128 * 1024; +public: + void enable() { + events_.reserve(event_limit); + transport_.reserve(transport_limit); + enabled_.store(true); + } + bool enabled() const { return enabled_.load(); } + void observe(Kind kind, int rows, int columns, unsigned generation = 0, int stage = 0, + std::string_view bytes = {}) { + if (!enabled()) return; + std::lock_guard lock(mutex_); + LARGE_INTEGER now{}; + QueryPerformanceCounter(&now); + // Events give observer ordering only, not a host/child synchronization fence. + if (events_.size() < event_limit) + events_.push_back({kind, static_cast(now.QuadPart), consumed_, generation, + rows, columns, stage, bytes.size()}); + else events_overflow_ = true; + if (kind == feed) { + const auto count = (std::min)(bytes.size(), transport_limit - transport_.size()); + transport_.append(bytes.data(), count); + transport_overflow_ = transport_overflow_ || count != bytes.size(); + consumed_ += bytes.size(); + } + } + bool save(const std::filesystem::path& directory) const { + if (!enabled()) return true; + std::lock_guard lock(mutex_); + std::ofstream bytes(directory / "resize-trace.transport.bin", std::ios::binary | std::ios::trunc); + bytes.write(transport_.data(), static_cast(transport_.size())); + bytes.close(); + std::ofstream out(directory / "resize-trace.json", std::ios::binary | std::ios::trunc); + LARGE_INTEGER frequency{}; + QueryPerformanceFrequency(&frequency); + out << "{\"schema\":1,\"observer_order_only\":true,\"qpc_frequency\":" << frequency.QuadPart + << ",\"events_overflow\":" << (events_overflow_ ? "true" : "false") + << ",\"transport_overflow\":" << (transport_overflow_ ? "true" : "false") + << ",\"consumed_bytes\":" << consumed_ << ",\"events\":["; + static const char* names[] = {"desired", "parser_begin", "parser_end", "request", "host_begin", "host_end", "feed", "child_ack"}; + bool first = true; + for (const auto& event : events_) { + if (!first) out << ','; + first = false; + out << "{\"kind\":\"" << names[event.kind] << "\",\"qpc\":" << event.qpc + << ",\"offset\":" << event.consumed << ",\"generation\":" << event.generation + << ",\"rows\":" << event.rows << ",\"columns\":" << event.columns + << ",\"stage\":" << event.stage << ",\"bytes\":" << event.bytes << '}'; + } + out << "]}"; + out.close(); + return bool(bytes) && bool(out); + } +}; +} // namespace tf_shell_test diff --git a/experiments/shell/resize_trace.py b/experiments/shell/resize_trace.py new file mode 100644 index 00000000..cff3c499 --- /dev/null +++ b/experiments/shell/resize_trace.py @@ -0,0 +1,163 @@ +"""Bounded private replay of the observed feed/local-resize order; no timing claim.""" +import argparse +import hashlib +import json +from pathlib import Path +import subprocess + + +KINDS = {'desired', 'parser_begin', 'parser_end', 'request', 'host_begin', 'host_end', 'feed', 'child_ack'} +TRACE_FIELDS = {'schema', 'observer_order_only', 'qpc_frequency', 'events_overflow', + 'transport_overflow', 'consumed_bytes', 'events'} +EVENT_FIELDS = {'kind', 'qpc', 'offset', 'generation', 'rows', 'columns', 'stage', 'bytes'} +# Schema 1 records the ShellWindow parser from its fixed construction geometry. +INITIAL_GRID = (24, 80) + + +def integer(value, minimum, maximum): + return type(value) is int and minimum <= value <= maximum + + +def replay_plan(trace, transport): + if (not isinstance(trace, dict) or set(trace) != TRACE_FIELDS or + type(trace.get('schema')) is not int or trace['schema'] != 1 or + trace.get('observer_order_only') is not True or + trace.get('events_overflow') is not False or trace.get('transport_overflow') is not False or + not integer(trace.get('qpc_frequency'), 1, (1 << 63) - 1)): + raise ValueError('incomplete or unknown resize trace') + events = trace.get('events') + if (not isinstance(events, list) or not events or len(events) > 1024 or + not isinstance(transport, bytes) or len(transport) > 128 * 1024 or + type(trace.get('consumed_bytes')) is not int or + trace['consumed_bytes'] != len(transport)): + raise ValueError('invalid resize trace size') + offset, plan, grid, last_qpc = 0, [], INITIAL_GRID, -1 + ui_phase, desired = 'idle', None + requests, active_host, last_host_generation = {}, None, 0 + for event in events: + if (not isinstance(event, dict) or set(event) != EVENT_FIELDS or + type(event.get('kind')) is not str or event['kind'] not in KINDS): + raise ValueError('unknown resize trace event') + if (not integer(event['offset'], 0, len(transport)) or event['offset'] != offset or + not integer(event['bytes'], 0, len(transport))): + raise ValueError('resize trace byte ordering invalid') + if (not integer(event['qpc'], 0, (1 << 64) - 1) or event['qpc'] < last_qpc or + not integer(event['generation'], 0, (1 << 32) - 1) or + not integer(event['stage'], 0, (1 << 31) - 1)): + raise ValueError('resize trace clock or generation invalid') + last_qpc = event['qpc'] + if not integer(event['rows'], 1, 200) or not integer(event['columns'], 1, 500): + raise ValueError('resize trace dimensions invalid') + dimensions = event['rows'], event['columns'] + kind = event['kind'] + generation = event['generation'] + if kind != 'feed' and event['bytes']: + raise ValueError('non-feed trace event owns bytes') + if kind in ('host_begin', 'host_end'): + if not generation or event['stage'] != 0: + raise ValueError('resize trace host identity invalid') + if kind == 'host_begin': + if active_host is not None or generation <= last_host_generation: + raise ValueError('resize trace host calls overlap or regress') + # The worker can record begin/end before the UI records request. + # A coalesced request need not have a host call, and a worker + # already holding an older generation can run after newer UI + # requests. Neither observation is a completion fence. + if generation in requests: + expected = requests[generation] + elif generation == len(requests) + 1 and ui_phase == 'request': + expected = grid + else: + raise ValueError('resize trace host generation has no request') + if dimensions != expected: + raise ValueError('resize trace host request dimensions differ') + active_host = generation, dimensions + last_host_generation = generation + else: + if active_host != (generation, dimensions): + raise ValueError('resize trace host boundary invalid') + active_host = None + continue + if kind != 'request' and generation: + raise ValueError('resize trace UI generation invalid') + if kind == 'feed': + count = event['bytes'] + if not count or count > len(transport) - offset or ui_phase != 'idle' or dimensions != grid: + raise ValueError('resize trace feed partition invalid') + plan.append(f'F {offset} {count}\n') + offset += count + else: + if kind == 'desired': + if ui_phase != 'idle': + raise ValueError('resize trace desired boundary invalid') + desired, ui_phase = dimensions, 'parser_begin' + elif kind == 'parser_begin': + if ui_phase != 'parser_begin' or dimensions != desired: + raise ValueError('resize trace parser boundary invalid') + ui_phase = 'parser_end' + plan.append(f'R {dimensions[0]} {dimensions[1]}\n') + elif kind == 'parser_end': + if ui_phase != 'parser_end' or dimensions != desired: + raise ValueError('resize trace parser boundary invalid') + grid, ui_phase = dimensions, 'request' + elif kind == 'request': + if ui_phase != 'request' or dimensions != grid or generation != len(requests) + 1: + raise ValueError('resize trace request boundary invalid') + requests[generation] = dimensions + desired, ui_phase = None, 'idle' + elif kind == 'child_ack': + if ui_phase != 'idle' or dimensions != grid: + raise ValueError('resize trace child acknowledgement geometry invalid') + if offset != len(transport) or ui_phase != 'idle' or active_host is not None: + raise ValueError('resize trace does not cover transport') + return ''.join(plan) + + +def sha(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('capture', type=Path) + parser.add_argument('output', type=Path) + parser.add_argument('--exe', required=True, type=Path) + parser.add_argument('--native-viewport', action='store_true', + help='use the explicit native viewport resize policy') + args = parser.parse_args() + trace_path = args.capture / 'resize-trace.json' + transport_path = args.capture / 'resize-trace.transport.bin' + if trace_path.stat().st_size > 512 * 1024 or transport_path.stat().st_size > 128 * 1024: + raise ValueError('resize capture files exceed bound') + trace = json.loads(trace_path.read_text(encoding='utf-8')) + transport = transport_path.read_bytes() + plan = replay_plan(trace, transport) + args.output.mkdir(parents=True, exist_ok=False) + plan_path = args.output / 'plan.txt' + plan_path.write_text(plan, encoding='ascii') + # Full snapshots contain the captured terminal text. They are private artifacts. + timed_out = False + try: + command = [str(args.exe.resolve(strict=True)), str(transport_path.resolve()), str(plan_path.resolve())] + if args.native_viewport: + command.append('--native-viewport') + process = subprocess.run(command, capture_output=True, timeout=10) + stdout, stderr, exit_code = process.stdout, process.stderr, process.returncode + except subprocess.TimeoutExpired as error: + stdout, stderr, exit_code = error.stdout or b'', error.stderr or b'', None + timed_out = True + (args.output / 'states.jsonl').write_bytes(stdout) + (args.output / 'stderr.txt').write_bytes(stderr) + summary = dict(schema=1, replay_exit_code=exit_code, timed_out=timed_out, + resize_policy='native_viewport' if args.native_viewport else 'reflow_history', + trace_sha256=sha(trace_path), transport_sha256=sha(transport_path), + plan_sha256=sha(plan_path), replay_exe_sha256=sha(args.exe), + captured_bytes=len(transport), replay_steps=len(plan.splitlines()), + timing_or_native_acceptance=False) + (args.output / 'summary.json').write_text(json.dumps(summary, indent=2) + '\n', encoding='utf-8') + print(json.dumps(summary)) + return 0 if exit_code == 0 else 1 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/experiments/shell/results/2026-10-26-replay-cost-and-close/closing-reply.json b/experiments/shell/results/2026-10-26-replay-cost-and-close/closing-reply.json new file mode 100644 index 00000000..f3ca2ca5 --- /dev/null +++ b/experiments/shell/results/2026-10-26-replay-cost-and-close/closing-reply.json @@ -0,0 +1,65 @@ +{ + "red_test_note": "Red used the same ordering fixture before strengthening the green marker/consumed-byte assertions; source hashes differ explicitly.", + "native_backend": "candidate .4 plus explicit official ConPTY 1.25.260930003 flags24", + "runs": [ + { + "phase": "red", + "identity": { + "commit": "06bf6b5cf4c5f7457cbb02355aa4971276c2b581", + "files": { + "experiments/shell/candidate_reply_close_test.cpp": "79386c5fe342a959d8f14e0dd1a9d321aa9f6bac93d460532595a31a6b4898af", + "experiments/shell/shell.cpp": "b369968152bbff107c699181d4e23f271e62a5732f0c41793159b3efc3609321", + "experiments/core/candidate_terminal.cpp": "3092ee2ea44ca0f897a7d3b6244c079ba8073dc109993204e435f065388a2ed1", + "CMakeLists.txt": "f5bb85e12b7a815c4feb7417da9ebc5298de58664bcd2792b277cb282729da73", + "build/candidate/bin/Release/candidate_reply_close_test.exe": "7df5ef5ddc896b8fef626ed15cb9c3dbfd4955c93009bf83700e1b9c115e9664" + }, + "result": "query observed after close; stopping=true; rejected=1; terminal reply rejected by input queue" + }, + "log_sha256": "9c2fcbd3c53d9e831cae2d23396d8fe0c46578cf433f6ac3e14bf5f6f8f92baf", + "observations": [ + { + "query_consumed_after_close": true, + "consumed_bytes": 0, + "input_stopping": true, + "input_rejected": 1 + } + ], + "passed": false + }, + { + "phase": "green", + "identity": { + "files": { + "experiments/shell/candidate_reply_close_test.cpp": "453ab888fb57d4f918bcfa583da7167a21463ce185be2a3d67caec74505fc424", + "experiments/shell/shell.cpp": "ac4dd44861aa2928213fffe91d30cdaf3aedf19f08468aefeed22828352296cc", + "experiments/core/candidate_terminal.cpp": "3092ee2ea44ca0f897a7d3b6244c079ba8073dc109993204e435f065388a2ed1", + "CMakeLists.txt": "f5bb85e12b7a815c4feb7417da9ebc5298de58664bcd2792b277cb282729da73", + "build/candidate/bin/Release/candidate_reply_close_test.exe": "662a331701e49885ce56328ae19ddc2166347c5f802d7faa2787eef46e25b000", + "build/candidate/bin/Release/termforge_shell.exe": "9c01fa0577d8fc922d239d8a711ac13d1e4989104aad68795230bfcd998b4eb6" + }, + "result": "two actual owned processes; query observed after input stop; received65B; rejected0; Job0 and desktop deletion" + }, + "log_sha256": "d3e40250906444017e55fc7dc62fa00c7280131f83a521c300547349140a97af", + "observations": [ + { + "query_consumed_after_close": true, + "consumed_bytes": 65, + "input_stopping": true, + "input_rejected": 0 + }, + { + "query_consumed_after_close": true, + "consumed_bytes": 65, + "input_stopping": true, + "input_rejected": 0 + }, + { + "closing_reply": "passed", + "owned_job_zero": true, + "desktop_deleted": true + } + ], + "passed": true + } + ] +} diff --git a/experiments/shell/results/2026-10-26-replay-cost-and-close/headless-ordinary.json b/experiments/shell/results/2026-10-26-replay-cost-and-close/headless-ordinary.json new file mode 100644 index 00000000..e3db1b60 --- /dev/null +++ b/experiments/shell/results/2026-10-26-replay-cost-and-close/headless-ordinary.json @@ -0,0 +1,376 @@ +{ + "plan": { + "schema": "local-candidate-phase-probe-v1", + "source_commit": "8ff112e785073dde91a74142cf1a3000fa240007", + "files": { + "experiments\\core\\candidate_terminal.cpp": "0d0eaabe5ef896aa7b263c8151622f579f37c421bf9473fc3d69ada010a64e61", + "local\\r53-cost-probe\\instrumented.cpp": "753b5b1c5a9fb1585401dee36fba0722be78038ef6967c713ca61e4334997d12", + "local\\r53-cost-probe\\phase_probe.cpp": "3a39b8b01e97f6ae245a1cb0bd0fc05f8a6cab6e14a5584d6b8b98fdc942ba63", + "local\\r53-cost-probe\\probe_timing.hpp": "a114631fbb6777808a791a3e75c665a0f3b92e1103c2ed91a4e1fb46daed2579", + "local\\r53-cost-probe\\CMakeLists.txt": "a6d0346bdde98988d07f2b594b7ef79f6de9bf1570be0cba573913f9cbb83a6a", + "local\\r53-cost-probe\\f1.bin": "c0d35b68ed7d8147bfa42eb9ea7e8a64ddafef8a786b1b1f42b0b85f179057a4", + "experiments\\candidates\\alacritty\\target\\release\\termforge_alacritty_core.lib": "aa41a9ea5749f3933a0db8bfa41dda68812c68727d47d394fc3edd859973a260" + }, + "workload": { + "role": "F1 producer bytes; no ConPTY transformation", + "bytes": 2048000, + "rows": 17, + "columns": 74, + "history": 100, + "chunk": 8192 + }, + "order": [ + "plain", + "instrumented", + "instrumented", + "plain", + "plain", + "instrumented" + ], + "limitations": [ + "headless, no transport or GUI", + "shared host", + "timers perturb execution", + "diagnostic only, not R54 formal sample or performance budget" + ] + }, + "os": "Windows-11-10.0.26200-SP0", + "logical_cpus": 8, + "power_plan": "电源方案 GUID: 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c (高性能)", + "binaries": { + "plain": "be21af65b5017a9277092747843f894321a6b82621a25d0766377258cffb706a", + "instrumented": "0c27682bac6a51ed874d2d3b23f0b4d13de513c2ff0cf8e00f6bb3bd09698680" + }, + "run_script": "b19a723a976751a656a68ce24e1f428e67eb21a76a8fc24b1226bb46d246195a", + "utc_start": "2026-10-26T15:13:52Z", + "samples": [ + { + "mode": "plain", + "returncode": 0, + "outer_seconds": 0.5309999999590218, + "background_cpu_before_percent": 30.0, + "result": { + "bytes": 2048000, + "cpu_ns": 453125000, + "wall_ns": 486336100, + "valid": true, + "phases": [ + { + "id": 0, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 1, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 2, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 3, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 4, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 5, + "ns": 0, + "max_ns": 0, + "count": 0 + } + ] + }, + "state_sha256": "ecfa1c1b31203793cd40d0d2977910e6fef4df1c9f33ba8d7f65a8eee4364dc0" + }, + { + "mode": "instrumented", + "returncode": 0, + "outer_seconds": 0.4690000000409782, + "background_cpu_before_percent": 34.1, + "result": { + "bytes": 2048000, + "cpu_ns": 437500000, + "wall_ns": 430316800, + "valid": true, + "phases": [ + { + "id": 0, + "ns": 70976000, + "max_ns": 655500, + "count": 250 + }, + { + "id": 1, + "ns": 534800, + "max_ns": 26400, + "count": 250 + }, + { + "id": 2, + "ns": 358655700, + "max_ns": 2857500, + "count": 250 + }, + { + "id": 3, + "ns": 33800, + "max_ns": 1400, + "count": 250 + }, + { + "id": 4, + "ns": 295519000, + "max_ns": 2096700, + "count": 250 + }, + { + "id": 5, + "ns": 35803000, + "max_ns": 881800, + "count": 250 + } + ] + }, + "state_sha256": "ecfa1c1b31203793cd40d0d2977910e6fef4df1c9f33ba8d7f65a8eee4364dc0" + }, + { + "mode": "instrumented", + "returncode": 0, + "outer_seconds": 0.4839999999385327, + "background_cpu_before_percent": 15.6, + "result": { + "bytes": 2048000, + "cpu_ns": 437500000, + "wall_ns": 448960700, + "valid": true, + "phases": [ + { + "id": 0, + "ns": 72738000, + "max_ns": 724000, + "count": 250 + }, + { + "id": 1, + "ns": 460500, + "max_ns": 12300, + "count": 250 + }, + { + "id": 2, + "ns": 375608200, + "max_ns": 2725500, + "count": 250 + }, + { + "id": 3, + "ns": 27600, + "max_ns": 1300, + "count": 250 + }, + { + "id": 4, + "ns": 312636300, + "max_ns": 1923500, + "count": 250 + }, + { + "id": 5, + "ns": 35133200, + "max_ns": 586600, + "count": 250 + } + ] + }, + "state_sha256": "ecfa1c1b31203793cd40d0d2977910e6fef4df1c9f33ba8d7f65a8eee4364dc0" + }, + { + "mode": "plain", + "returncode": 0, + "outer_seconds": 0.4839999999385327, + "background_cpu_before_percent": 28.7, + "result": { + "bytes": 2048000, + "cpu_ns": 421875000, + "wall_ns": 446410900, + "valid": true, + "phases": [ + { + "id": 0, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 1, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 2, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 3, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 4, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 5, + "ns": 0, + "max_ns": 0, + "count": 0 + } + ] + }, + "state_sha256": "ecfa1c1b31203793cd40d0d2977910e6fef4df1c9f33ba8d7f65a8eee4364dc0" + }, + { + "mode": "plain", + "returncode": 0, + "outer_seconds": 0.5160000000614673, + "background_cpu_before_percent": 33.8, + "result": { + "bytes": 2048000, + "cpu_ns": 468750000, + "wall_ns": 478586600, + "valid": true, + "phases": [ + { + "id": 0, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 1, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 2, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 3, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 4, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 5, + "ns": 0, + "max_ns": 0, + "count": 0 + } + ] + }, + "state_sha256": "ecfa1c1b31203793cd40d0d2977910e6fef4df1c9f33ba8d7f65a8eee4364dc0" + }, + { + "mode": "instrumented", + "returncode": 0, + "outer_seconds": 0.546000000089407, + "background_cpu_before_percent": 25.6, + "result": { + "bytes": 2048000, + "cpu_ns": 500000000, + "wall_ns": 496894700, + "valid": true, + "phases": [ + { + "id": 0, + "ns": 80278700, + "max_ns": 1027000, + "count": 250 + }, + { + "id": 1, + "ns": 810600, + "max_ns": 103900, + "count": 250 + }, + { + "id": 2, + "ns": 414892000, + "max_ns": 6602700, + "count": 250 + }, + { + "id": 3, + "ns": 66400, + "max_ns": 6800, + "count": 250 + }, + { + "id": 4, + "ns": 325008500, + "max_ns": 5494600, + "count": 250 + }, + { + "id": 5, + "ns": 51881600, + "max_ns": 1497400, + "count": 250 + } + ] + }, + "state_sha256": "ecfa1c1b31203793cd40d0d2977910e6fef4df1c9f33ba8d7f65a8eee4364dc0" + } + ], + "local_raw_identity_sha256": "5fd91a263fb64c2487882aa1c98f35970fbff4eaa97c763c301595f02d81a52a", + "local_raw_files_sha256": { + "00-plain.state.json": "ecfa1c1b31203793cd40d0d2977910e6fef4df1c9f33ba8d7f65a8eee4364dc0", + "00-plain.stderr": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "00-plain.stdout": "00cc5fd93c82252f6968a348206e7c910ad7265160f1ae515857e9b47f94a3a9", + "01-instrumented.state.json": "ecfa1c1b31203793cd40d0d2977910e6fef4df1c9f33ba8d7f65a8eee4364dc0", + "01-instrumented.stderr": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "01-instrumented.stdout": "46b812a26f02455cfad72e5253fc1af4436909af5a8880b6e5483da6113aef2a", + "02-instrumented.state.json": "ecfa1c1b31203793cd40d0d2977910e6fef4df1c9f33ba8d7f65a8eee4364dc0", + "02-instrumented.stderr": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "02-instrumented.stdout": "f2d05c9fa772228c7900a288589d4a3a8ac9a2909595b972e3ca8cb0eec18cdb", + "03-plain.state.json": "ecfa1c1b31203793cd40d0d2977910e6fef4df1c9f33ba8d7f65a8eee4364dc0", + "03-plain.stderr": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "03-plain.stdout": "a3fe321b8bab4643b8530adf767adb6346a0e17af78ed892cfe0cd1b01d4e2ac", + "04-plain.state.json": "ecfa1c1b31203793cd40d0d2977910e6fef4df1c9f33ba8d7f65a8eee4364dc0", + "04-plain.stderr": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "04-plain.stdout": "28b8830d041869aa2b13826d5da2a0877a87b9216db31281960de97c712ea33d", + "05-instrumented.state.json": "ecfa1c1b31203793cd40d0d2977910e6fef4df1c9f33ba8d7f65a8eee4364dc0", + "05-instrumented.stderr": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "05-instrumented.stdout": "e20225b03b1c861180d2ce4620e1265b97eae88469bc9d39c5715ef0b12540ce", + "identity.json": "5fd91a263fb64c2487882aa1c98f35970fbff4eaa97c763c301595f02d81a52a" + } +} diff --git a/experiments/shell/results/2026-10-26-replay-cost-and-close/headless-resize.json b/experiments/shell/results/2026-10-26-replay-cost-and-close/headless-resize.json new file mode 100644 index 00000000..bfc3ec00 --- /dev/null +++ b/experiments/shell/results/2026-10-26-replay-cost-and-close/headless-resize.json @@ -0,0 +1,398 @@ +{ + "plan": { + "source_commit": "8ff112e785073dde91a74142cf1a3000fa240007", + "hypothesis": "same-size resize activates replay-origin reconciliation for subsequent ordinary output", + "files": { + "CMakeLists.txt": "fda1598de7e5f379dad96f66cd8a3f7eeaba3f67419a47604debaa523c2fb559", + "f1.bin": "e2639311e8fd54ea2632d4e35efa590d467284b0c3f25ba85875ef0312107580", + "instrumented.cpp": "753b5b1c5a9fb1585401dee36fba0722be78038ef6967c713ca61e4334997d12", + "original.cpp": "0d0eaabe5ef896aa7b263c8151622f579f37c421bf9473fc3d69ada010a64e61", + "phase_probe.cpp": "3e9b7260814772630bb2c32aae56fe3ff293cf46a0320c25146ed1ee59ef5980", + "probe_timing.hpp": "a114631fbb6777808a791a3e75c665a0f3b92e1103c2ed91a4e1fb46daed2579" + }, + "order": [ + [ + "plain", + "plain" + ], + [ + "plain", + "resize" + ], + [ + "instrumented", + "resize" + ], + [ + "instrumented", + "plain" + ], + [ + "plain", + "resize" + ], + [ + "plain", + "plain" + ] + ], + "workload": { + "bytes": 204800, + "records": 3200, + "rows": 17, + "columns": 74, + "history": 100, + "chunk": 8192 + }, + "limitations": [ + "shared host", + "headless diagnostic", + "not R54 formal samples or user budget" + ] + }, + "os": "Windows-11-10.0.26200-SP0", + "logical_cpus": 8, + "power_plan": "电源方案 GUID: 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c (高性能)", + "binaries": { + "plain": "e007dae8618944dde2c9c57e05796be9e6f4d68052badfa2690e248dd7d4e90e", + "instrumented": "9b0d14a30f0ae2fdfe649e0e89e9d5ebeb78b6b30291ea7eb7efda374e270a03" + }, + "run_script": "96d11bbf8c6a58859f10673db46bd091df18459f099e99adab48f9b550c48f73", + "utc_start": "2026-10-26T15:26:14Z", + "samples": [ + { + "mode": "plain", + "context": "plain", + "returncode": 0, + "outer_seconds": 0.06199999991804361, + "background_cpu_before_percent": 39.3, + "result": { + "bytes": 204800, + "cpu_ns": 46875000, + "wall_ns": 46402100, + "valid": true, + "phases": [ + { + "id": 0, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 1, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 2, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 3, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 4, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 5, + "ns": 0, + "max_ns": 0, + "count": 0 + } + ] + }, + "state_sha256": "aa05cafdeb1bf5737e553ef5c01669a8b30e9aef5b4e8eac3c73597ee005b19e" + }, + { + "mode": "plain", + "context": "resize", + "returncode": 0, + "outer_seconds": 0.9529999999795109, + "background_cpu_before_percent": 29.7, + "result": { + "bytes": 204800, + "cpu_ns": 921875000, + "wall_ns": 929186300, + "valid": true, + "phases": [ + { + "id": 0, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 1, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 2, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 3, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 4, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 5, + "ns": 0, + "max_ns": 0, + "count": 0 + } + ] + }, + "state_sha256": "aa05cafdeb1bf5737e553ef5c01669a8b30e9aef5b4e8eac3c73597ee005b19e" + }, + { + "mode": "instrumented", + "context": "resize", + "returncode": 0, + "outer_seconds": 0.9529999999795109, + "background_cpu_before_percent": 17.5, + "result": { + "bytes": 204800, + "cpu_ns": 921875000, + "wall_ns": 931817400, + "valid": true, + "phases": [ + { + "id": 0, + "ns": 7996700, + "max_ns": 824700, + "count": 25 + }, + { + "id": 1, + "ns": 104500, + "max_ns": 26100, + "count": 25 + }, + { + "id": 2, + "ns": 923683900, + "max_ns": 38156500, + "count": 25 + }, + { + "id": 3, + "ns": 9200, + "max_ns": 1100, + "count": 25 + }, + { + "id": 4, + "ns": 28528000, + "max_ns": 1404800, + "count": 25 + }, + { + "id": 5, + "ns": 891658400, + "max_ns": 36740200, + "count": 25 + } + ] + }, + "state_sha256": "aa05cafdeb1bf5737e553ef5c01669a8b30e9aef5b4e8eac3c73597ee005b19e" + }, + { + "mode": "instrumented", + "context": "plain", + "returncode": 0, + "outer_seconds": 0.0940000000409782, + "background_cpu_before_percent": 31.4, + "result": { + "bytes": 204800, + "cpu_ns": 62500000, + "wall_ns": 60924400, + "valid": true, + "phases": [ + { + "id": 0, + "ns": 10826000, + "max_ns": 1149300, + "count": 25 + }, + { + "id": 1, + "ns": 119100, + "max_ns": 20200, + "count": 25 + }, + { + "id": 2, + "ns": 49945500, + "max_ns": 3270500, + "count": 25 + }, + { + "id": 3, + "ns": 8900, + "max_ns": 2700, + "count": 25 + }, + { + "id": 4, + "ns": 37565700, + "max_ns": 2179600, + "count": 25 + }, + { + "id": 5, + "ns": 7396500, + "max_ns": 886500, + "count": 25 + } + ] + }, + "state_sha256": "aa05cafdeb1bf5737e553ef5c01669a8b30e9aef5b4e8eac3c73597ee005b19e" + }, + { + "mode": "plain", + "context": "resize", + "returncode": 0, + "outer_seconds": 1.0, + "background_cpu_before_percent": 39.5, + "result": { + "bytes": 204800, + "cpu_ns": 953125000, + "wall_ns": 968977700, + "valid": true, + "phases": [ + { + "id": 0, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 1, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 2, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 3, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 4, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 5, + "ns": 0, + "max_ns": 0, + "count": 0 + } + ] + }, + "state_sha256": "aa05cafdeb1bf5737e553ef5c01669a8b30e9aef5b4e8eac3c73597ee005b19e" + }, + { + "mode": "plain", + "context": "plain", + "returncode": 0, + "outer_seconds": 0.06199999991804361, + "background_cpu_before_percent": 30.6, + "result": { + "bytes": 204800, + "cpu_ns": 46875000, + "wall_ns": 44071200, + "valid": true, + "phases": [ + { + "id": 0, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 1, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 2, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 3, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 4, + "ns": 0, + "max_ns": 0, + "count": 0 + }, + { + "id": 5, + "ns": 0, + "max_ns": 0, + "count": 0 + } + ] + }, + "state_sha256": "aa05cafdeb1bf5737e553ef5c01669a8b30e9aef5b4e8eac3c73597ee005b19e" + } + ], + "local_raw_identity_sha256": "f4b084750d02bc8ca2c7b9a9c736c5c4b6fd727b2b514e63093b9bdfd3736b8d", + "local_raw_files_sha256": { + "00-plain-plain.state.json": "aa05cafdeb1bf5737e553ef5c01669a8b30e9aef5b4e8eac3c73597ee005b19e", + "00-plain-plain.stderr": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "00-plain-plain.stdout": "ac3258615fda6a3a7e5e900f9ebe62c83b85dfb3e3fe662fd62229b0cd1fd854", + "01-plain-resize.state.json": "aa05cafdeb1bf5737e553ef5c01669a8b30e9aef5b4e8eac3c73597ee005b19e", + "01-plain-resize.stderr": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "01-plain-resize.stdout": "4223388fbc400eb96eccbe2863f2f8f5e43c9326d81ecb8610f3a019dbe5ce65", + "02-instrumented-resize.state.json": "aa05cafdeb1bf5737e553ef5c01669a8b30e9aef5b4e8eac3c73597ee005b19e", + "02-instrumented-resize.stderr": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "02-instrumented-resize.stdout": "3a59d598755257174e624d9e265df19cfeaf791942029161a4a969f8dfaa4b50", + "03-instrumented-plain.state.json": "aa05cafdeb1bf5737e553ef5c01669a8b30e9aef5b4e8eac3c73597ee005b19e", + "03-instrumented-plain.stderr": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "03-instrumented-plain.stdout": "40f4e11781aae3fbfea6d9854df4816aa9beacb8a2b819b7842f454a5ed022e8", + "04-plain-resize.state.json": "aa05cafdeb1bf5737e553ef5c01669a8b30e9aef5b4e8eac3c73597ee005b19e", + "04-plain-resize.stderr": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "04-plain-resize.stdout": "845a7ddb7e0b9e8680c07b9f7f987d8b7af30457f992ceda73829dedfc90b4e9", + "05-plain-plain.state.json": "aa05cafdeb1bf5737e553ef5c01669a8b30e9aef5b4e8eac3c73597ee005b19e", + "05-plain-plain.stderr": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "05-plain-plain.stdout": "51761f7531d0d0e2a36b04c56a61fddd5cd9ddbb023544fd8e474ef31c5749f2", + "identity.json": "f4b084750d02bc8ca2c7b9a9c736c5c4b6fd727b2b514e63093b9bdfd3736b8d" + } +} diff --git a/experiments/shell/results/2026-10-26-replay-cost-and-close/intermediate-workflows.json b/experiments/shell/results/2026-10-26-replay-cost-and-close/intermediate-workflows.json new file mode 100644 index 00000000..4f2d8610 --- /dev/null +++ b/experiments/shell/results/2026-10-26-replay-cost-and-close/intermediate-workflows.json @@ -0,0 +1,716 @@ +{ + "runs": [ + { + "run": "run-868918e65b584884877b410fa6c48805", + "sample": { + "mode": "ui-workflow-sessions", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "resources": { + "summary": "resource-summary.json", + "scope": "launcher-tree", + "performance_gate": "not_evaluated" + }, + "error_kind": "ProbeFailure" + }, + "data_only": true, + "system_clipboard": false, + "ime": false, + "checkpoints": [ + { + "phase": "eight-ready", + "elapsed_ms": 572, + "cycle": 0 + }, + { + "phase": "reject-ninth", + "elapsed_ms": 576, + "cycle": 0 + }, + { + "phase": "load-started", + "elapsed_ms": 766, + "cycle": 0 + }, + { + "phase": "reading-before-append", + "elapsed_ms": 798, + "cycle": 0 + }, + { + "phase": "reading-after-append", + "elapsed_ms": 944, + "cycle": 0 + }, + { + "phase": "search-running", + "elapsed_ms": 1105, + "cycle": 0 + }, + { + "phase": "find-complete", + "elapsed_ms": 6944, + "cycle": 0 + }, + { + "phase": "foreground-input", + "elapsed_ms": 7093, + "cycle": 0 + }, + { + "phase": "zoom-75", + "elapsed_ms": 7222, + "cycle": 0 + }, + { + "phase": "switch-zoom", + "elapsed_ms": 7377, + "cycle": 0 + }, + { + "phase": "history-evicted", + "elapsed_ms": 7660, + "cycle": 0 + }, + { + "phase": "paste-cancel-pending", + "elapsed_ms": 8297, + "cycle": 0 + }, + { + "phase": "paste-cancelled", + "elapsed_ms": 8728, + "cycle": 0 + }, + { + "phase": "background-complete", + "elapsed_ms": 27467, + "cycle": 0 + }, + { + "phase": "cycle-six", + "elapsed_ms": 27779, + "cycle": 1 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 29048, + "cycle": 1 + }, + { + "phase": "cycle-six", + "elapsed_ms": 29328, + "cycle": 2 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 30633, + "cycle": 2 + }, + { + "phase": "cycle-six", + "elapsed_ms": 30925, + "cycle": 3 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 32255, + "cycle": 3 + }, + { + "phase": "cycle-six", + "elapsed_ms": 32580, + "cycle": 4 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 33843, + "cycle": 4 + }, + { + "phase": "cycle-six", + "elapsed_ms": 34162, + "cycle": 5 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 35424, + "cycle": 5 + }, + { + "phase": "paste-close-pending", + "elapsed_ms": 35756, + "cycle": 5 + }, + { + "phase": "after-paste-close", + "elapsed_ms": 35974, + "cycle": 5 + } + ], + "paste_cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "stopped", + "wrong_generation_rejected": true + } + } + ], + "raw_sha256": { + "cancel.bin": "bcc3593351da0137a27071da66416a7cbe02ca5605f49b14e5a6c85ebac894a6", + "cancel.bin.before.json": "a90746e62c8e815c76305806dfff25d55dd34d67f54308b0c865152693425663", + "close.bin": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c", + "close.bin.before.json": "42d48c9b60c19059b0fd753b871521787ece886f5976aef804fe05baa7db1584", + "frame.bmp": "4443a91feb73693650c2a6f8ac4feb91fa9fe3fee9e1340086892c2257db1990", + "resource-observations.jsonl": "dd709b8cd12e332610082a7ae44c1dcbbe486e9451a7a21b7b899639c83453a0", + "resource-phases.jsonl": "f6dc0847c07730eafd232b117c375628a3d746285700805245570f087c926b05", + "resource-summary.json": "75f03192836a8f1b0dd3f7789b618a6305025bb4f6b11ff98efc3fac12aef32a", + "sample.json": "170f4625886507719d22cbcc1d05e3e72df3c1d53f9c54b6dba7697cc9b09cfa", + "sample.public.json": "63735633ab5fcace05f2deb18b21a39d9df834cc9534f6f9ad16a1e3385dfcf9", + "sessions.json": "ba4522ff74c1942f45da7fd2729a8ba0e631c9783d3d01292a1c8bf3cae528cc", + "validation.json": "a01ca51810fd51c4fee9947b3a43422dede0cba5e6ae6a9a72a0fef8a10bc5b0", + "workspace.layout": "9a96d62943f54727e7c50ce5785a571efbfa6cbece6248f6a6d144257c1e1d58" + }, + "observed_failure": "terminal reply rejected by input queue", + "source": "06bf6b5cf4c5f7457cbb02355aa4971276c2b581" + }, + { + "run": "run-39a01aa2558747c3b71d531c66e234e8", + "sample": { + "mode": "ui-workflow-sessions", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workflow-sessions", + "sessions_closed": 18, + "active_children_after": 0, + "presented_done": null + }, + "frame_sha256": "68fb528174c7f7edf2916ca1b6f1f7d2a6b80deeb4cec787539fb051bb344ee5", + "sessions_workflow": { + "schema": "termforge-workflow-sessions-identity-validation-v1", + "live_sessions": 8, + "checkpoints": 26, + "rejected_ninth": true, + "load_foreground_verified": true, + "data_workflow_verified": true, + "complete_workflow_verified": false, + "system_clipboard_verified": false, + "ime_verified": false + }, + "resources": { + "summary": "resource-summary.json", + "scope": "launcher-tree", + "performance_gate": "not_evaluated" + }, + "error_kind": null + }, + "data_only": true, + "system_clipboard": false, + "ime": false, + "checkpoints": [ + { + "phase": "eight-ready", + "elapsed_ms": 570, + "cycle": 0 + }, + { + "phase": "reject-ninth", + "elapsed_ms": 573, + "cycle": 0 + }, + { + "phase": "load-started", + "elapsed_ms": 727, + "cycle": 0 + }, + { + "phase": "reading-before-append", + "elapsed_ms": 747, + "cycle": 0 + }, + { + "phase": "reading-after-append", + "elapsed_ms": 867, + "cycle": 0 + }, + { + "phase": "search-running", + "elapsed_ms": 1011, + "cycle": 0 + }, + { + "phase": "find-complete", + "elapsed_ms": 6314, + "cycle": 0 + }, + { + "phase": "foreground-input", + "elapsed_ms": 6454, + "cycle": 0 + }, + { + "phase": "zoom-75", + "elapsed_ms": 6591, + "cycle": 0 + }, + { + "phase": "switch-zoom", + "elapsed_ms": 6751, + "cycle": 0 + }, + { + "phase": "history-evicted", + "elapsed_ms": 7000, + "cycle": 0 + }, + { + "phase": "paste-cancel-pending", + "elapsed_ms": 7606, + "cycle": 0 + }, + { + "phase": "paste-cancelled", + "elapsed_ms": 7990, + "cycle": 0 + }, + { + "phase": "background-complete", + "elapsed_ms": 26131, + "cycle": 0 + }, + { + "phase": "cycle-six", + "elapsed_ms": 26437, + "cycle": 1 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 27720, + "cycle": 1 + }, + { + "phase": "cycle-six", + "elapsed_ms": 28020, + "cycle": 2 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 29254, + "cycle": 2 + }, + { + "phase": "cycle-six", + "elapsed_ms": 29556, + "cycle": 3 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 30810, + "cycle": 3 + }, + { + "phase": "cycle-six", + "elapsed_ms": 31122, + "cycle": 4 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 32383, + "cycle": 4 + }, + { + "phase": "cycle-six", + "elapsed_ms": 32669, + "cycle": 5 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 33961, + "cycle": 5 + }, + { + "phase": "paste-close-pending", + "elapsed_ms": 34293, + "cycle": 5 + }, + { + "phase": "after-paste-close", + "elapsed_ms": 34504, + "cycle": 5 + } + ], + "paste_cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "stopped", + "wrong_generation_rejected": true + } + } + ], + "raw_sha256": { + "cancel.bin": "bcc3593351da0137a27071da66416a7cbe02ca5605f49b14e5a6c85ebac894a6", + "cancel.bin.before.json": "a90746e62c8e815c76305806dfff25d55dd34d67f54308b0c865152693425663", + "close.bin": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c", + "close.bin.before.json": "42d48c9b60c19059b0fd753b871521787ece886f5976aef804fe05baa7db1584", + "frame.bmp": "68fb528174c7f7edf2916ca1b6f1f7d2a6b80deeb4cec787539fb051bb344ee5", + "resource-observations.jsonl": "86c5915c71b43c3724b5c083401c481d2fc644d7b92c7c7b71c6fac3b7eb8c1c", + "resource-phases.jsonl": "2c438eed39c5948baaebb03893ea1791c28c563f6f08555dff9da7c4007edb2e", + "resource-summary.json": "a1af28883ff9efa10a0ac32af7a649047a886161d7fbf9a24d8ee286e32b75ed", + "sample.json": "6e4100ef925345920031c7885393ffea19621c138ba4a7c990fd8f4d01592195", + "sample.public.json": "9507843aee6d9088fd93fb35fb925f64d9b854f0d0c307dc5bc6676422ff7604", + "sessions.json": "8daad6179ab2acbbc4800f02e8d1377bb925c477028ce999bbb4ef47efcda590", + "validation.json": "e2d23746c84fed9ac9dcddb248fabee42cd8f8bfe13bfe2cf9ff6cd87f15acd5", + "workspace.layout": "9a96d62943f54727e7c50ce5785a571efbfa6cbece6248f6a6d144257c1e1d58" + }, + "source": "06bf6b5 plus bounded reply-state diagnostics" + }, + { + "run": "run-942f5c4bfd9d4922a23481675ad17e7d", + "sample": { + "mode": "ui-workflow-sessions", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "resources": { + "summary": "resource-summary.json", + "scope": "launcher-tree", + "performance_gate": "not_evaluated" + }, + "error_kind": "ProbeFailure" + }, + "data_only": true, + "system_clipboard": false, + "ime": false, + "checkpoints": [ + { + "phase": "eight-ready", + "elapsed_ms": 533, + "cycle": 0 + }, + { + "phase": "reject-ninth", + "elapsed_ms": 536, + "cycle": 0 + }, + { + "phase": "load-started", + "elapsed_ms": 685, + "cycle": 0 + }, + { + "phase": "reading-before-append", + "elapsed_ms": 704, + "cycle": 0 + }, + { + "phase": "reading-after-append", + "elapsed_ms": 825, + "cycle": 0 + }, + { + "phase": "search-running", + "elapsed_ms": 966, + "cycle": 0 + }, + { + "phase": "find-complete", + "elapsed_ms": 6316, + "cycle": 0 + }, + { + "phase": "foreground-input", + "elapsed_ms": 6441, + "cycle": 0 + }, + { + "phase": "zoom-75", + "elapsed_ms": 6575, + "cycle": 0 + }, + { + "phase": "switch-zoom", + "elapsed_ms": 6738, + "cycle": 0 + }, + { + "phase": "history-evicted", + "elapsed_ms": 6984, + "cycle": 0 + }, + { + "phase": "paste-cancel-pending", + "elapsed_ms": 7647, + "cycle": 0 + }, + { + "phase": "paste-cancelled", + "elapsed_ms": 8011, + "cycle": 0 + }, + { + "phase": "background-complete", + "elapsed_ms": 26248, + "cycle": 0 + }, + { + "phase": "cycle-six", + "elapsed_ms": 26542, + "cycle": 1 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 27794, + "cycle": 1 + }, + { + "phase": "cycle-six", + "elapsed_ms": 28111, + "cycle": 2 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 29384, + "cycle": 2 + }, + { + "phase": "cycle-six", + "elapsed_ms": 29719, + "cycle": 3 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 30983, + "cycle": 3 + }, + { + "phase": "cycle-six", + "elapsed_ms": 31270, + "cycle": 4 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 32527, + "cycle": 4 + }, + { + "phase": "cycle-six", + "elapsed_ms": 32839, + "cycle": 5 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 34055, + "cycle": 5 + }, + { + "phase": "paste-close-pending", + "elapsed_ms": 34351, + "cycle": 5 + }, + { + "phase": "after-paste-close", + "elapsed_ms": 34568, + "cycle": 5 + } + ], + "paste_cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "stopped", + "wrong_generation_rejected": true + } + } + ], + "raw_sha256": { + "cancel.bin": "bcc3593351da0137a27071da66416a7cbe02ca5605f49b14e5a6c85ebac894a6", + "cancel.bin.before.json": "a90746e62c8e815c76305806dfff25d55dd34d67f54308b0c865152693425663", + "close.bin": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c", + "close.bin.before.json": "42d48c9b60c19059b0fd753b871521787ece886f5976aef804fe05baa7db1584", + "frame.bmp": "4443a91feb73693650c2a6f8ac4feb91fa9fe3fee9e1340086892c2257db1990", + "resource-observations.jsonl": "97e19f4c8fe8de708740e1d2cce01f8117b531ba7c231a4552a3091a5668adbf", + "resource-phases.jsonl": "100f32a411ed3930e70630d411c7c354019366470eb4d5bbce3e7ba5431b76ce", + "resource-summary.json": "2feb2d4c8ecfc99d7a82b9b0f4213df47396d9b6dab29d5604c15ea146df1d26", + "sample.json": "7edc081e4c8dc9e22ee08dc40dfdf90f99062c82ccd234f2c9ccfa8c2eb6177f", + "sample.public.json": "63735633ab5fcace05f2deb18b21a39d9df834cc9534f6f9ad16a1e3385dfcf9", + "sessions.json": "6afe299c8fbb4014d8c2875ed824bbcd8bb5f8076e42115dea3f204d4c74336e", + "validation.json": "6e9c0fe26639b5dae2ee347a9f2b972e89b4402568e9ef60c76881c9051dbc9d", + "workspace.layout": "9a96d62943f54727e7c50ce5785a571efbfa6cbece6248f6a6d144257c1e1d58" + }, + "observed_failure": "terminal reply rejected by input queue; session=3 closing=1 stopping=1 queued=0 failed=0 paste_busy=0 reply_hex=1b5b343b3452", + "source": "06bf6b5 plus bounded reply-state diagnostics" + } + ], + "reply_diagnostic_identity": { + "original_sha256": "b369968152bbff107c699181d4e23f271e62a5732f0c41793159b3efc3609321", + "diagnostic_sha256": "bcae8cd6c012fe3cf8b0e8ca5219c5136210dd6cd1e4940a9562d1eb73b59156" + } +} diff --git a/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/README.md b/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/README.md new file mode 100644 index 00000000..f9722477 --- /dev/null +++ b/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/README.md @@ -0,0 +1,12 @@ +# 成本诊断的固定计时边界 + +本目录归档 `8ff112e785073dde91a74142cf1a3000fa240007` 上实际使用的诊断源与插桩差异,不参与产品或 CI 构建。原始 EXE、屏幕、运输记录留在本地;脱敏后的全部样本、原始文件哈希及构建身份见上级 JSON。 + +- `ordinary_probe.cpp` 和 `resize_probe.cpp` 是实际 headless 驱动,分别消费 2,048,000B 和 204,800B;最终状态按独立的逐行字面值、历史长度、淘汰数及光标验证。 +- 合成输入每行由 `f'TF_R108_F1_{row:05d} 中文 e\u0301'.encode('utf-8').ljust(62, b'.') + b'\r\n'` 生成;分别连接 32,000 / 3,200 行。其字节哈希、17×74、history100、chunk8192 与完整样本次序已记录。 +- headless 构建将驱动与固定源码的 `experiments/core/candidate_terminal.cpp`(或应用 `headless-instrumentation.patch` 的副本)链接到同一固定 `.4` Rust Release 库;MSVC `/W4 /utf-8`、C++17,链接 `ws2_32 userenv ntdll`。构造和可选同尺寸 resize 在计时前,循环 feed 计时,finish 和正确性校验在计时后。每个样本是新的进程。 +- headless phases 0–5 依次是 Rust feed、scan_replay、synchronize、event drain、cell export、reconcile 及后续同步收尾。phase5 包含后续工作,不能直接称为纯 reconcile。UI 第二次插桩才把 reconcile、history merge 和 versions/copy 分开。 +- UI 第一批使用 `ui-instrumentation.patch` 的 phases0–9;第二批再使用 `ui-core-instrumentation.patch` 增加 phases10–16。`ui_timing.hpp` 为最终17槽版本;第一批仅10槽。UI 计时由主线程累计,析构写到 `TERMFORGE_DIAGNOSTIC_PHASE_OUT`;原场景仍由自有 Job/desktop 与原期限监督。 +- UI patch 的 include 位置已从本机绝对路径改成仓库相对标识,移植时需令其可由 include 搜索路径解析;各 patch 已规范化换行和行末空白。这些是归档差异,不是当前分支的可直接应用补丁。JSON 中源码与 EXE 哈希对应实际运行的本地版本。 + +嵌套 wall time 不相加。采样很短,宿主有其他负载,插桩有扰动;这些是定位原因的诊断,不是 #54 的正式 A/B、性能预算或实际呈现验收。修复后回归使用未插桩的产品代码。 diff --git a/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/headless-instrumentation.patch b/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/headless-instrumentation.patch new file mode 100644 index 00000000..ed03091a --- /dev/null +++ b/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/headless-instrumentation.patch @@ -0,0 +1,41 @@ +--- candidate_terminal.cpp ++++ instrumented.cpp +@@ -1,3 +1,4 @@ ++#include "probe_timing.hpp" + #include "candidate_terminal.hpp" + #include "../candidates/alacritty/include/termforge_candidate.h" + #include +@@ -224,10 +225,12 @@ + + void CandidateTerminal::feed(std::string_view bytes) { + std::array error{}; +- check(tf_terminal_feed(impl_, reinterpret_cast(bytes.data()), bytes.size(), +- error.data(), error.size()), "candidate feed failed"); +- scan_replay(bytes); +- synchronize(); ++ { ProbeTimer timer(0); ++ check(tf_terminal_feed(impl_, reinterpret_cast(bytes.data()), bytes.size(), ++ error.data(), error.size()), "candidate feed failed"); } ++ { ProbeTimer timer(1); scan_replay(bytes); } ++ { ProbeTimer timer(2); synchronize(); } ++ ProbeTimer event_timer(3); + uint32_t kind = 0; + for (;;) { + size_t needed = 0; +@@ -295,6 +298,7 @@ + std::vector retained(meta.history + meta.rows); + std::vector raw_ids; + raw_ids.reserve(retained.size()); ++ { ProbeTimer export_timer(4); + for (size_t r = 0; r < retained.size(); ++r) { + TfRowMeta row{}; + check(tf_terminal_row_meta(impl_, r, &row), "candidate row metadata failed"); +@@ -313,6 +317,8 @@ + } + retained[r] = std::move(line); + } ++ } ++ ProbeTimer reconcile_timer(5); + const auto preserved = reconcile_replay(retained); + if (replay_show_) preserve_history_ = true; + if (preserve_history_ && !replay_origin_ && meta.history == 0 && meta.discarded == screen.discarded) diff --git a/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/ordinary_probe.cpp b/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/ordinary_probe.cpp new file mode 100644 index 00000000..7980cfd9 --- /dev/null +++ b/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/ordinary_probe.cpp @@ -0,0 +1,53 @@ +#define NOMINMAX +#include +#include "candidate_terminal.hpp" +#include "text_projection.hpp" +#include "probe_timing.hpp" +#include +#include +#include +#include +#include + +uint64_t cpu_ns() { + FILETIME created{}, exited{}, kernel{}, user{}; + if (!GetProcessTimes(GetCurrentProcess(), &created, &exited, &kernel, &user)) + throw std::runtime_error("GetProcessTimes failed"); + const auto value = [](const FILETIME& ft) { return (uint64_t(ft.dwHighDateTime)<<32) | ft.dwLowDateTime; }; + return (value(kernel)+value(user))*100; +} +int main(int argc, char** argv) { + try { + if(argc!=3) throw std::runtime_error("usage: phase_probe input.bin state.json"); + std::ifstream input(argv[1], std::ios::binary); + if(!input) throw std::runtime_error("input missing"); + const std::string bytes((std::istreambuf_iterator(input)), {}); + if(bytes.size()!=2048000) throw std::runtime_error("unexpected workload"); + termforge::CandidateTerminal terminal(17,74,100); + probe_ns.fill(0); probe_count.fill(0); probe_max.fill(0); + const auto cpu_start=cpu_ns(); const auto start=ProbeTimer::Clock::now(); + for(size_t i=0;i(ProbeTimer::Clock::now()-start).count(); + const auto cpu_elapsed=cpu_ns()-cpu_start; + terminal.finish(); + const auto& screen=terminal.screen; + if(screen.row!=16 || screen.column!=0 || screen.history.size()!=100 || screen.retained_size()!=117 || screen.discarded!=31884) + throw std::runtime_error("final cursor/history mismatch"); + for(size_t r=0;r<116;++r) { + std::ostringstream expected; + expected<<"TF_R108_F1_"< +#include +#include +inline std::array probe_ns{}, probe_max{}, probe_count{}; +struct ProbeTimer { + using Clock = std::chrono::steady_clock; + size_t phase; + Clock::time_point start = Clock::now(); + explicit ProbeTimer(size_t value) : phase(value) {} + ~ProbeTimer() { + const auto elapsed = static_cast(std::chrono::duration_cast(Clock::now()-start).count()); + probe_ns[phase] += elapsed; ++probe_count[phase]; + if (elapsed > probe_max[phase]) probe_max[phase] = elapsed; + } +}; diff --git a/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/resize_probe.cpp b/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/resize_probe.cpp new file mode 100644 index 00000000..68fbf318 --- /dev/null +++ b/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/resize_probe.cpp @@ -0,0 +1,54 @@ +#define NOMINMAX +#include +#include "candidate_terminal.hpp" +#include "text_projection.hpp" +#include "probe_timing.hpp" +#include +#include +#include +#include +#include + +uint64_t cpu_ns() { + FILETIME created{}, exited{}, kernel{}, user{}; + if (!GetProcessTimes(GetCurrentProcess(), &created, &exited, &kernel, &user)) + throw std::runtime_error("GetProcessTimes failed"); + const auto value = [](const FILETIME& ft) { return (uint64_t(ft.dwHighDateTime)<<32) | ft.dwLowDateTime; }; + return (value(kernel)+value(user))*100; +} +int main(int argc, char** argv) { + try { + if(argc!=4) throw std::runtime_error("usage: phase_probe input.bin state.json resize|plain"); + std::ifstream input(argv[1], std::ios::binary); + if(!input) throw std::runtime_error("input missing"); + const std::string bytes((std::istreambuf_iterator(input)), {}); + if(bytes.size()!=204800) throw std::runtime_error("unexpected workload"); + termforge::CandidateTerminal terminal(17,74,100); + if(std::string(argv[3])=="resize") terminal.resize(17,74); + probe_ns.fill(0); probe_count.fill(0); probe_max.fill(0); + const auto cpu_start=cpu_ns(); const auto start=ProbeTimer::Clock::now(); + for(size_t i=0;i(ProbeTimer::Clock::now()-start).count(); + const auto cpu_elapsed=cpu_ns()-cpu_start; + terminal.finish(); + const auto& screen=terminal.screen; + if(screen.row!=16 || screen.column!=0 || screen.history.size()!=100 || screen.retained_size()!=117 || screen.discarded!=3084) + throw std::runtime_error("final cursor/history mismatch"); + for(size_t r=0;r<116;++r) { + std::ostringstream expected; + expected<<"TF_R108_F1_"< +@@ -224,10 +225,11 @@ + + void CandidateTerminal::feed(std::string_view bytes) { + std::array error{}; +- check(tf_terminal_feed(impl_, reinterpret_cast(bytes.data()), bytes.size(), +- error.data(), error.size()), "candidate feed failed"); +- scan_replay(bytes); +- synchronize(); ++ { UiPhase phase_core(10); ++ check(tf_terminal_feed(impl_, reinterpret_cast(bytes.data()), bytes.size(), ++ error.data(), error.size()), "candidate feed failed"); } ++ { UiPhase phase_scan(11); scan_replay(bytes); } ++ { UiPhase phase_sync(12); synchronize(); } + uint32_t kind = 0; + for (;;) { + size_t needed = 0; +@@ -295,6 +297,7 @@ + std::vector retained(meta.history + meta.rows); + std::vector raw_ids; + raw_ids.reserve(retained.size()); ++ { UiPhase phase_export(13); + for (size_t r = 0; r < retained.size(); ++r) { + TfRowMeta row{}; + check(tf_terminal_row_meta(impl_, r, &row), "candidate row metadata failed"); +@@ -313,11 +316,16 @@ + } + retained[r] = std::move(line); + } +- const auto preserved = reconcile_replay(retained); ++ } ++ std::unordered_set preserved; ++ { UiPhase phase_reconcile(14); preserved = reconcile_replay(retained); } + if (replay_show_) preserve_history_ = true; + if (preserve_history_ && !replay_origin_ && meta.history == 0 && meta.discarded == screen.discarded) + preserve_history_ = false; ++ { UiPhase phase_merge(15); + merge_history(retained, raw_ids, meta.history, meta.discarded); ++ } ++ UiPhase phase_versions(16); + if (replay_show_) replay_origin_.reset(); + screen.normalize_identities(retained); + for (size_t r = 0; r + 1 < retained.size(); ++r) diff --git a/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/ui-instrumentation.patch b/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/ui-instrumentation.patch new file mode 100644 index 00000000..fefd5eac --- /dev/null +++ b/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/ui-instrumentation.patch @@ -0,0 +1,103 @@ +--- experiments/shell/shell.cpp ++++ experiments/shell/shell.cpp +@@ -1,3 +1,4 @@ ++#include "local/r53-cost-probe/ui_timing.hpp" + // Local shell with physical-row resize. See README for the limited contract. + #include "../presentation/render.hpp" + #ifdef TERMFORGE_CANDIDATE_CORE +@@ -367,7 +368,7 @@ + closed_.store(true); + }); + } +- void publish(std::optional scroll_operation=std::nullopt,bool scroll_changed=false) { ++ void publish(std::optional scroll_operation=std::nullopt,bool scroll_changed=false) { UiPhase probe(3); + selection_.reconcile(termforge::TextDocument(parser_.screen)); + auto view = viewport_.snapshot(parser_.screen); + if(runtime_enabled_) runtime_published_viewport_=view.viewport; +@@ -617,7 +618,7 @@ + stage_ = 6; + #endif + } +- void scenario() { ++ void scenario() { UiPhase probe(5); + if (mode_.empty() || closing_) return; + require(ms(start_) < 25000, "shell scenario timeout"); + if (mode_ == "watchdog") { +@@ -778,7 +779,7 @@ + } + } + #endif +- void tick() { ++ void tick() { UiPhase probe(0); + const auto began = Clock::now(); ++result_.ticks; + poll_paste(); + selection_autoscroll(); +@@ -793,7 +794,7 @@ + if(observed_output_.size()>8192) observed_output_.erase(0,observed_output_.size()-8192); + } + resize_trace_.observe(tf_shell_test::ResizeTrace::feed, parser_.screen.height(), parser_.screen.width(), 0, stage_, bytes); +- parser_.feed(bytes); batch += bytes.size(); ++ { UiPhase probe(1); parser_.feed(bytes); } batch += bytes.size(); + #ifdef TERMFORGE_CANDIDATE_CORE + route_terminal_events(); + #endif +@@ -914,7 +915,7 @@ + } + if (message == WM_TIMER && (self->started_ || self->closing_)) { self->tick(); return 0; } + if (message == WM_ERASEBKGND) return 1; +- if (message == WM_PAINT) { ++ if (message == WM_PAINT) { UiPhase probe(6); + PAINTSTRUCT paint{}; HDC dc = BeginPaint(hwnd, &paint); + const int painted_revision=self->snapshot_->revision; + const bool observed=self->operation_observations_.paint_candidate(painted_revision); +--- experiments/shell/search_interactions.hpp ++++ experiments/shell/search_interactions.hpp +@@ -12,7 +12,7 @@ + find_->focus_query(); + } + +-inline void ShellWindow::search_changed() { ++inline void ShellWindow::search_changed() { UiPhase probe(2); + const termforge::TextDocument document(parser_.screen); + search_.notify_change(document); + if(find_resize_pending_ && !find_resize_anchor_.reconcile(document)) clear_find_resize_anchor(); +@@ -66,7 +66,7 @@ + publish(); + } + +-inline void ShellWindow::poll_find() { ++inline void ShellWindow::poll_find() { UiPhase probe(4); + if(closing_) return; + termforge::TextDocument document(parser_.screen); + if(find_) { +--- experiments/shell/workspace_workflow_scenario.hpp ++++ experiments/shell/workspace_workflow_scenario.hpp +@@ -44,7 +44,7 @@ + require(FlushFileBuffers(evidence.resource_phase_file.get())!=FALSE,"workflow resource phase flush failed"); + } + } +-inline void Workspace::workflow_checkpoint(const char* phase,unsigned cycle,const std::vector& names) { ++inline void Workspace::workflow_checkpoint(const char* phase,unsigned cycle,const std::vector& names) { UiPhase probe(8); + auto& evidence=*workflow_;evidence.reserve_observations(names.empty()?static_cast(session_count()):names.size()); + workflow_resource_mark(phase,cycle); + JOBOBJECT_BASIC_ACCOUNTING_INFORMATION job{}; +@@ -169,7 +169,7 @@ + if(workflow_system_ && stage_==100 && workflow_->closed.size()==18) workflow_system_finish();return; + } + } +-inline void Workspace::scenario_workflow() { ++inline void Workspace::scenario_workflow() { UiPhase probe(7); + if(image_.empty() || closing_) return; + require(workflow_!=nullptr,"workflow scenario state is missing"); + if(ms(started_)>=70000) throw std::runtime_error("workflow sessions scenario timeout at stage "+std::to_string(stage_)); +--- experiments/shell/workspace.hpp ++++ experiments/shell/workspace.hpp +@@ -381,7 +381,7 @@ + if(!settings_error_.empty()) result+=L": "+utf16(settings_error_.c_str()); + return result; + } +- void paint(HDC dc) { ++ void paint(HDC dc) { UiPhase probe(9); + auto old=SelectObject(dc,font_); SetBkMode(dc,TRANSPARENT); + fill(dc,{0,0,width_,height_},background); + label(dc,{0,0,82,33},L"Session"); label(dc,{82,0,150,33},L"View"); diff --git a/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/ui_timing.hpp b/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/ui_timing.hpp new file mode 100644 index 00000000..9b576009 --- /dev/null +++ b/experiments/shell/results/2026-10-26-replay-cost-and-close/probe/ui_timing.hpp @@ -0,0 +1,31 @@ +#pragma once +#include +#include +#include +#include +#include +struct UiProfile { + std::array ns{}, count{}, maximum{}; + ~UiProfile() { + const char* path=std::getenv("TERMFORGE_DIAGNOSTIC_PHASE_OUT"); + if(!path) return; + std::ofstream out(path); + out<<"{\"phases\":["; + for(size_t i=0;i(std::chrono::duration_cast(Clock::now()-start).count()); + ui_profile.ns[phase]+=elapsed; ++ui_profile.count[phase]; + if(elapsed>ui_profile.maximum[phase]) ui_profile.maximum[phase]=elapsed; + } +}; diff --git a/experiments/shell/results/2026-10-26-replay-cost-and-close/ui-profiles.json b/experiments/shell/results/2026-10-26-replay-cost-and-close/ui-profiles.json new file mode 100644 index 00000000..ebc26ccf --- /dev/null +++ b/experiments/shell/results/2026-10-26-replay-cost-and-close/ui-profiles.json @@ -0,0 +1,699 @@ +[ + { + "run": "ui-run-2b18652f1a8841f5a097e28148ff0d34", + "sample": { + "mode": "ui-workflow-sessions", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "error_kind": "ProbeFailure" + }, + "data_only": true, + "system_clipboard": false, + "ime": false, + "checkpoints": [ + { + "phase": "eight-ready", + "elapsed_ms": 579, + "cycle": 0 + }, + { + "phase": "reject-ninth", + "elapsed_ms": 583, + "cycle": 0 + }, + { + "phase": "load-started", + "elapsed_ms": 857, + "cycle": 0 + }, + { + "phase": "reading-before-append", + "elapsed_ms": 881, + "cycle": 0 + }, + { + "phase": "reading-after-append", + "elapsed_ms": 1130, + "cycle": 0 + }, + { + "phase": "search-running", + "elapsed_ms": 1392, + "cycle": 0 + }, + { + "phase": "find-complete", + "elapsed_ms": 11461, + "cycle": 0 + }, + { + "phase": "foreground-input", + "elapsed_ms": 11678, + "cycle": 0 + }, + { + "phase": "zoom-75", + "elapsed_ms": 11894, + "cycle": 0 + }, + { + "phase": "switch-zoom", + "elapsed_ms": 12152, + "cycle": 0 + }, + { + "phase": "history-evicted", + "elapsed_ms": 12611, + "cycle": 0 + }, + { + "phase": "paste-cancel-pending", + "elapsed_ms": 13775, + "cycle": 0 + }, + { + "phase": "paste-cancelled", + "elapsed_ms": 14486, + "cycle": 0 + }, + { + "phase": "background-complete", + "elapsed_ms": 50071, + "cycle": 0 + }, + { + "phase": "cycle-six", + "elapsed_ms": 50552, + "cycle": 1 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 51912, + "cycle": 1 + }, + { + "phase": "cycle-six", + "elapsed_ms": 52683, + "cycle": 2 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 54041, + "cycle": 2 + }, + { + "phase": "cycle-six", + "elapsed_ms": 54799, + "cycle": 3 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 56168, + "cycle": 3 + }, + { + "phase": "cycle-six", + "elapsed_ms": 56961, + "cycle": 4 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 58253, + "cycle": 4 + }, + { + "phase": "cycle-six", + "elapsed_ms": 58889, + "cycle": 5 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 60200, + "cycle": 5 + }, + { + "phase": "paste-close-pending", + "elapsed_ms": 60724, + "cycle": 5 + } + ], + "paste_cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "stopped", + "wrong_generation_rejected": true + } + } + ], + "raw_sha256": { + "cancel.bin": "bcc3593351da0137a27071da66416a7cbe02ca5605f49b14e5a6c85ebac894a6", + "cancel.bin.before.json": "a90746e62c8e815c76305806dfff25d55dd34d67f54308b0c865152693425663", + "close.bin": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c", + "close.bin.before.json": "42d48c9b60c19059b0fd753b871521787ece886f5976aef804fe05baa7db1584", + "identity.json": "bd2605c691401cbaba684f8b3dbf8d5ec4d87004dc5cc2a99043dd7137e0bd05", + "resource-phases.jsonl": "eb0a3de3035663d0086328cc52f98dac18a5e553f64197057a516169a4be56f3", + "sample.json": "7159224e988be562a77cbad22953c4bb2ef47b7ccc19ac9593c037630bde513e", + "sessions.json": "6ef9405ea3055273463e9d72fa467f7df9e9c4459dffb7a203a061c7dbaa3e94", + "ui-phases.json": "6644c6534aa391b9bd4bf2a6c344c4e8d9d1548c4c2fc3656e589b8a61809997", + "workspace.layout": "9a96d62943f54727e7c50ce5785a571efbfa6cbece6248f6a6d144257c1e1d58" + }, + "observed_failure": "workflow sessions scenario timeout at stage 45", + "instrumentation": { + "phases": [ + "whole_tick", + "parser_feed", + "search_changed", + "publish", + "poll_find", + "shell_scenario", + "shell_paint", + "workflow_scenario", + "workflow_checkpoint", + "workspace_paint" + ], + "source": { + "experiments/shell/shell.cpp": { + "original": "b369968152bbff107c699181d4e23f271e62a5732f0c41793159b3efc3609321", + "instrumented": "0e0cfbd952c37d05a0333bd79662ead2777dd19db220f08993a762437cb9bb77" + }, + "experiments/shell/search_interactions.hpp": { + "original": "75f1ac520dc3070efba2d01812bf56c29e8683540196956423b0984c42d7b181", + "instrumented": "b337e56639d273b45faca154ed20830157991b6e158834e3c377a1a74c6f4b55" + }, + "experiments/shell/workspace_workflow_scenario.hpp": { + "original": "467571556440f4e89305bc1261d389bb8f24ba84f607b8cd36b8078a58314369", + "instrumented": "2a95c33f6b6212f61c8217fd55298b16ca90209f8b9cd972e7dc135e94fc4c57" + }, + "experiments/shell/workspace.hpp": { + "original": "5669a13701b2fe60e8bd567a122edb970bdc4aadcb98ceb3927905f0c000275e", + "instrumented": "9cbe9e5db3529e578eec705307d9cab23035623b3f46ac9d14d6838f7ab7c61e" + } + }, + "original_exe": "0a1fee632cb640dff4599fdcca277c959038d31ec88bfed8cff893b3271d26a6", + "instrumented_exe": "babd7b24d89ae970f614c6d4b6090054db9df3a40dfbcbdcadaf53b5b2e79f24", + "resources": false + }, + "phase_wall_times": { + "phases": [ + { + "id": 0, + "ns": 64631951400, + "max_ns": 55502600, + "count": 4896 + }, + { + "id": 1, + "ns": 64079457000, + "max_ns": 55183600, + "count": 1771 + }, + { + "id": 2, + "ns": 335900, + "max_ns": 24500, + "count": 1806 + }, + { + "id": 3, + "ns": 500625100, + "max_ns": 8645600, + "count": 1958 + }, + { + "id": 4, + "ns": 73427600, + "max_ns": 9092300, + "count": 4896 + }, + { + "id": 5, + "ns": 745500, + "max_ns": 20500, + "count": 4754 + }, + { + "id": 6, + "ns": 1053698100, + "max_ns": 10532400, + "count": 233 + }, + { + "id": 7, + "ns": 1137670500, + "max_ns": 268286300, + "count": 611 + }, + { + "id": 8, + "ns": 157240300, + "max_ns": 17637500, + "count": 25 + }, + { + "id": 9, + "ns": 160054100, + "max_ns": 40076700, + "count": 48 + } + ] + }, + "limitations": [ + "nested inclusive wall timers; do not add phases", + "instrumentation overhead", + "resource sampler disabled for diagnosis", + "shared host; not formal R54 samples", + "no actual presented endpoint; no user-approved performance tolerance" + ] + }, + { + "run": "ui-run-82b1582d98de4cc1bd659c481ee99cfa", + "sample": { + "mode": "ui-workflow-sessions", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "error_kind": "ProbeFailure" + }, + "data_only": true, + "system_clipboard": false, + "ime": false, + "checkpoints": [ + { + "phase": "eight-ready", + "elapsed_ms": 603, + "cycle": 0 + }, + { + "phase": "reject-ninth", + "elapsed_ms": 606, + "cycle": 0 + }, + { + "phase": "load-started", + "elapsed_ms": 839, + "cycle": 0 + }, + { + "phase": "reading-before-append", + "elapsed_ms": 870, + "cycle": 0 + }, + { + "phase": "reading-after-append", + "elapsed_ms": 1104, + "cycle": 0 + }, + { + "phase": "search-running", + "elapsed_ms": 1350, + "cycle": 0 + }, + { + "phase": "find-complete", + "elapsed_ms": 11017, + "cycle": 0 + }, + { + "phase": "foreground-input", + "elapsed_ms": 11226, + "cycle": 0 + }, + { + "phase": "zoom-75", + "elapsed_ms": 11434, + "cycle": 0 + }, + { + "phase": "switch-zoom", + "elapsed_ms": 11664, + "cycle": 0 + }, + { + "phase": "history-evicted", + "elapsed_ms": 12079, + "cycle": 0 + }, + { + "phase": "paste-cancel-pending", + "elapsed_ms": 13120, + "cycle": 0 + }, + { + "phase": "paste-cancelled", + "elapsed_ms": 14002, + "cycle": 0 + }, + { + "phase": "background-complete", + "elapsed_ms": 47021, + "cycle": 0 + }, + { + "phase": "cycle-six", + "elapsed_ms": 47482, + "cycle": 1 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 48797, + "cycle": 1 + }, + { + "phase": "cycle-six", + "elapsed_ms": 49478, + "cycle": 2 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 50760, + "cycle": 2 + }, + { + "phase": "cycle-six", + "elapsed_ms": 51410, + "cycle": 3 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 52749, + "cycle": 3 + }, + { + "phase": "cycle-six", + "elapsed_ms": 53355, + "cycle": 4 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 54707, + "cycle": 4 + }, + { + "phase": "cycle-six", + "elapsed_ms": 55349, + "cycle": 5 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 56623, + "cycle": 5 + }, + { + "phase": "paste-close-pending", + "elapsed_ms": 57079, + "cycle": 5 + } + ], + "paste_cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "stopped", + "wrong_generation_rejected": true + } + } + ], + "raw_sha256": { + "cancel.bin": "bcc3593351da0137a27071da66416a7cbe02ca5605f49b14e5a6c85ebac894a6", + "cancel.bin.before.json": "a90746e62c8e815c76305806dfff25d55dd34d67f54308b0c865152693425663", + "close.bin": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c", + "close.bin.before.json": "42d48c9b60c19059b0fd753b871521787ece886f5976aef804fe05baa7db1584", + "identity.json": "7dc04a9a23392081f4b638214f91063c0a4b1704727470d1d14394d37aa97cde", + "resource-phases.jsonl": "0ed18c34974f8bf7e8d3e20949384e52b911672723293df5a3acf8c036cfb646", + "sample.json": "4d3fdb3f0561f468199900f2dfaaf4ed980cc1bdcdb9a1fa2db249676c170bbc", + "sessions.json": "1197f7ec3928c0dd08d8497610fe95b5171aa96559e9058e4ce1669f2fdc86cf", + "ui-phases.json": "cf363bd31befa7c37a6495591d630b1f0b9b164c07b1a9baa7871f6b1032aae4", + "workspace.layout": "9a96d62943f54727e7c50ce5785a571efbfa6cbece6248f6a6d144257c1e1d58" + }, + "observed_failure": "workflow sessions scenario timeout at stage 45", + "instrumentation": { + "phases": [ + "whole_tick", + "parser_feed", + "search_changed", + "publish", + "poll_find", + "shell_scenario", + "shell_paint", + "workflow_scenario", + "workflow_checkpoint", + "workspace_paint", + "core_feed", + "scan_replay", + "synchronize", + "cell_export", + "reconcile_replay", + "merge_history", + "versions_copy" + ], + "source": { + "experiments/shell/shell.cpp": { + "original": "b369968152bbff107c699181d4e23f271e62a5732f0c41793159b3efc3609321", + "instrumented": "0e0cfbd952c37d05a0333bd79662ead2777dd19db220f08993a762437cb9bb77" + }, + "experiments/shell/search_interactions.hpp": { + "original": "75f1ac520dc3070efba2d01812bf56c29e8683540196956423b0984c42d7b181", + "instrumented": "b337e56639d273b45faca154ed20830157991b6e158834e3c377a1a74c6f4b55" + }, + "experiments/shell/workspace_workflow_scenario.hpp": { + "original": "467571556440f4e89305bc1261d389bb8f24ba84f607b8cd36b8078a58314369", + "instrumented": "2a95c33f6b6212f61c8217fd55298b16ca90209f8b9cd972e7dc135e94fc4c57" + }, + "experiments/shell/workspace.hpp": { + "original": "5669a13701b2fe60e8bd567a122edb970bdc4aadcb98ceb3927905f0c000275e", + "instrumented": "9cbe9e5db3529e578eec705307d9cab23035623b3f46ac9d14d6838f7ab7c61e" + }, + "experiments/core/candidate_terminal.cpp": { + "original": "0d0eaabe5ef896aa7b263c8151622f579f37c421bf9473fc3d69ada010a64e61", + "instrumented": "5138c797086f84f584a254894004286257a1434bf2903ffcafd3751acb60ebba" + } + }, + "original_exe": "0a1fee632cb640dff4599fdcca277c959038d31ec88bfed8cff893b3271d26a6", + "instrumented_exe": "23b92b9087d836947bf848da3de7e04f624bada0c6ca6284690ea63719b6668f", + "resources": false + }, + "phase_wall_times": { + "phases": [ + { + "id": 0, + "ns": 64458192200, + "max_ns": 56417700, + "count": 5278 + }, + { + "id": 1, + "ns": 63986560900, + "max_ns": 56035900, + "count": 1892 + }, + { + "id": 2, + "ns": 449600, + "max_ns": 17600, + "count": 1922 + }, + { + "id": 3, + "ns": 432217300, + "max_ns": 3678600, + "count": 2074 + }, + { + "id": 4, + "ns": 52400300, + "max_ns": 4067000, + "count": 5278 + }, + { + "id": 5, + "ns": 787000, + "max_ns": 88700, + "count": 5137 + }, + { + "id": 6, + "ns": 958451800, + "max_ns": 11927600, + "count": 233 + }, + { + "id": 7, + "ns": 1086472600, + "max_ns": 239174200, + "count": 671 + }, + { + "id": 8, + "ns": 149742300, + "max_ns": 18294300, + "count": 25 + }, + { + "id": 9, + "ns": 141821100, + "max_ns": 21889600, + "count": 48 + }, + { + "id": 10, + "ns": 559755900, + "max_ns": 3343000, + "count": 1892 + }, + { + "id": 11, + "ns": 4690100, + "max_ns": 51400, + "count": 1892 + }, + { + "id": 12, + "ns": 63419742700, + "max_ns": 55759500, + "count": 1892 + }, + { + "id": 13, + "ns": 2031090200, + "max_ns": 7882100, + "count": 1930 + }, + { + "id": 14, + "ns": 60905226900, + "max_ns": 54182500, + "count": 1930 + }, + { + "id": 15, + "ns": 317300, + "max_ns": 3000, + "count": 1930 + }, + { + "id": 16, + "ns": 363684200, + "max_ns": 3660300, + "count": 1930 + } + ] + }, + "limitations": [ + "nested inclusive wall timers; do not add phases", + "instrumentation overhead", + "resource sampler disabled for diagnosis", + "shared host; not formal R54 samples", + "no actual presented endpoint; no user-approved performance tolerance" + ] + } +] diff --git a/experiments/shell/results/2026-10-26-replay-cost-and-close/validation.json b/experiments/shell/results/2026-10-26-replay-cost-and-close/validation.json new file mode 100644 index 00000000..a575046f --- /dev/null +++ b/experiments/shell/results/2026-10-26-replay-cost-and-close/validation.json @@ -0,0 +1,1513 @@ +{ + "schema": "termforge-replay-cost-closing-reply-v1", + "date": "2026-10-26", + "issue": 53, + "source_commit": "eae4b6a4ef3e044ebfebbbbfeccf9aec74cd7f0d", + "source_tree": "b07036829c5c8bd61ede6a8aa46f655e541ada1b", + "source_files": { + "CMakeLists.txt": { + "git_blob": "2454a7b8dc34fa2bbd74b25b0ad77d19a71a5f18", + "checkout_sha256": "f5bb85e12b7a815c4feb7417da9ebc5298de58664bcd2792b277cb282729da73" + }, + "experiments/core/candidate_terminal.cpp": { + "git_blob": "9bb1fab8f6deac294d0ba1a44712b67c919d03c7", + "checkout_sha256": "3092ee2ea44ca0f897a7d3b6244c079ba8073dc109993204e435f065388a2ed1" + }, + "experiments/shell/shell.cpp": { + "git_blob": "90b3f166e8a50ca2488f749e8e188c52cb5e4a91", + "checkout_sha256": "ac4dd44861aa2928213fffe91d30cdaf3aedf19f08468aefeed22828352296cc" + }, + "experiments/shell/candidate_reply_close_test.cpp": { + "git_blob": "1a5e0149c553469645dbfe3ddb217908b1958d42", + "checkout_sha256": "453ab888fb57d4f918bcfa583da7167a21463ce185be2a3d67caec74505fc424" + }, + "experiments/shell/test_workflow_sessions.py": { + "git_blob": "de6cef3be06725d606b91ae68eb1cc45eb744fb5", + "checkout_sha256": "6c968a443c46b30e75a929f880228e6a23bf2b92d9221bbbf18816933babd6e7" + }, + "experiments/shell/test_shell.py": { + "git_blob": "66512ac1099cf477cac8f26f225b18c301c1d3d8", + "checkout_sha256": "a6b63c1754b0e0dc1cc01863891f4c7c9d989f818d351838a8cfe295edaf811c" + }, + "experiments/shell/run.py": { + "git_blob": "bbd4dfc05012c55956973d7e2c69be717b48b1a9", + "checkout_sha256": "45976b828eecb316881d698dedaef09ad304f72d991dfa360f1c552bbcbe9f1d" + }, + "experiments/shell/workflow_sessions_checks.py": { + "git_blob": "8369b412abe893393bf316430ccedd22e9eb75ba", + "checkout_sha256": "b976d3504837eef57ff13319daed254870727741f36f265412a6b14b4cb3faae" + } + }, + "environment": { + "os": "Windows-11-10.0.26200-SP0", + "architecture": "AMD64", + "compiler": "MSVC 19.42.34433", + "configuration": "Release x64", + "candidate": "alacritty_terminal 0.26.1-dev+termforge.4", + "backend": "Microsoft.Windows.Console.ConPTY 1.25.260930003", + "glyph_width_flags": 24 + }, + "request": { + "utf8_bytes": 3000000, + "utf16_units": 1000000, + "literal": "U+6C49 repeated 1000000", + "sha256": "0ba67970de9941c1bc33e97869b116a0e129c8a859e3b4c0087e20c51f6a404b", + "minimum_sent": 16384, + "receiver_checkpoint_bytes": 768 + }, + "deadlines_ms": { + "standalone_ui": 35000, + "standalone_outer": 40000, + "receiver_gate": 15000, + "eight_session_ui": 70000, + "workflow_fixture": 55000, + "missing_go_negative_only": 2000 + }, + "binaries": { + "alpha_normal_smoke.exe": "36ac6dbe36c942b9850d6d9f5b3538fbfe2fc251f38ac026ea12c343b0e9abc8", + "candidate_core_test.exe": "6e9df1abbe2271690a1480a1be9042e219a5947779b6a4b03f529a742fbd10ce", + "candidate_reply_close_test.exe": "662a331701e49885ce56328ae19ddc2166347c5f802d7faa2787eef46e25b000", + "candidate_resize_replay.exe": "3d91227adb37bba4defcef8f803f0f3c2916001223300b99d18e0e2ab695de94", + "candidate_title_test.exe": "a9591c36d5a54a1f54cf30ec8f8b0e7ff047096f5adf2e2a74169252ab041852", + "clipboard_policy_test.exe": "809815e9838cad5b3443c98620d23a4e6c7186b5304b524d0990988eb47a6da9", + "conpty_backend_probe.exe": "3c54962c20f04e688a0413bb822713e908582af20b0be8bcfd188fb436907ef8", + "conpty_ctrl_c_probe.exe": "ac5ae5ccdb77c82512a8a6d769fafbe74693bf6fc535ce4d22bf60b0c8df2bfa", + "conpty_margin_probe.exe": "5404b009d0e005cd940d8a65afeb0fb9caf9c9ce2a18f651a88146343c41c252", + "core_measure.exe": "df9df797b16239d2fb71707f8a042a1ed956ad30c407d884527cdf18d600e785", + "find_bar_test.exe": "030733a2396ca9b31bbd029c610f1b838ab52394338c08410274501747dd9982", + "history_test.exe": "035507366facfd6de00e0c2762775c8d3816f605da3e55d498995a74a3f447d2", + "ime_context_test.exe": "be0d71ca72aee3dbd1565c9ad2314f81f0cd0c5511ec54cb623bbd39eede9974", + "ime_input_expectation_test.exe": "a5ea4532a06b893d955299793b4859cb9ccf221fab64e51949e1af2ee6ba5d0b", + "keyboard_test.exe": "18a35c2b2ab8a6316e0c938d8c36d0b82bcc131989d683205888b08bf4d33e19", + "layout_state_test.exe": "307519003536ce16d7d6fc552b3a25e9de25356d50a80c2de0c01168a10784a9", + "paste_test.exe": "47e9a5ce33335370b6fb199b0ff11adc19e2ab55bf64657061a13c1c020a24b0", + "presentation_probe.exe": "b7043cace46f65b22c7295df2fc06b214f060c2fd6fe18ac5ff27e13d943e096", + "raw_console_receiver_test.exe": "9c05cdb80d87ff264b3275387eb01dabc8f57640adbc7ee3910b0fc44f378224", + "real_app_capture.exe": "011efaad94da74ce182e30a3ff8abb0c1e1f58235bc77fa8c347ec3d9dcb822e", + "receive.exe": "ef316e11ddcc764ddac57426fc37b8b69f599ddb28a0ffedf5c2e85a3004a9d4", + "recovery_probe.exe": "c84e7b1b1792773b993f2758d5cafa59b80495451c01d194c3afd6cc653f0344", + "renderer_metrics_test.exe": "4cebad62798ff0f8eac42641a1119c7b9725728dd5fd31befcdf7e1ecb686cfd", + "replay.exe": "674f96ebbf00497576951a6780a5cf5195e3b80cc4acdb7f296fa8910a6203b7", + "resize_test.exe": "f566c56a1fa34e2a8ff480900ac5ae5059e2ac6872554208d5dbf0e24673dde0", + "runtime_observation_test.exe": "b721e08397dc0a1b80cff3cc9b3354d5ba81c59d9ef3e0934a7ec27fb2f5eebe", + "scenario_tick_gate_test.exe": "06cd4e813f1034d1a8c7fea592979f49c3ef89117c97fc6636cdae5012017ce8", + "search_test.exe": "0f29f3ea3147cc45eb2e500e683f3bcc1db04f820470b2ff473fffcb31cdb4f4", + "selection_test.exe": "104576787b62c2c4bb2428e0786c7742e63a23ceb766df293c88a5ccc6a1b836", + "sender_fence_pipe_test.exe": "6a94ac84631434a81ae1098932813d7ceeb81f2bb33a2e4b2456834bd8972e92", + "sender_fence_test.exe": "ab4af3b0f148f6e6377e70c98936c6c34d0be0d3cee4b17b3f8554c39b2d3e52", + "sent_message_wait_test.exe": "bc3cff14f60e3f1b17b3fd6255ff1c5b507f506d897d528802efa9666be74dcf", + "shell_clipboard_reader.exe": "bacc4bff465ca644fae61f9d26184a7adc22df9793470feca647af1d823fece8", + "shell_ime_client.exe": "d787ebf702cf5895e1ad0d71f895a51b3dead34d02953b5e7deff086c2bf8eb7", + "shell_input_client.exe": "850c82fb8bb58de8170bf096cc6dd4f8ba34d9ba11dbd2addc41508cddc31d8c", + "shell_launch_test.exe": "ace9b63148848bf7b405edb4b8818ca23d52699ac2ce8ca03a90c5bc1e32622d", + "shell_paste_probe.exe": "1370c6381b990270dfd816f53758e5aa57c63deba050fa7cf0dbd0d5cbf4f315", + "shell_query_client.exe": "71419f7918bc58f4ee47d14f8e6de35aa1828ac838a73ed607797fc120f56886", + "shell_queue_probe.exe": "7f34d6ff66b3e00d56798e22a480fb20a5afa083cff65a9d689dcbca44dbe12d", + "shell_selection_text_client.exe": "dbad81a22ecce4508eada90ae445f9a75abfe1effd2571776ef31b32144c8ac4", + "shell_size_client.exe": "5394d9863df9a589012f0d9a31f7e4167e7bc4ea23e93f9711c0a67c540651f6", + "shell_test_launcher.exe": "dbde4249dc08c7df80120398ffd252aa5e4958e4ef640cc161ea4c2d28b9f291", + "shell_text_client.exe": "9485b03424e421a4018596cc9487e7feeb33c26d30820985aa45843fdcf315d9", + "termforge_conpty_probe.exe": "7dd59bc8d0309ad5b131cde1c3ed98819139b6bb70f505347c98aabe8396f618", + "termforge_ime_probe.exe": "788a186b5fafb6d155eeab74f78d4f7cb992a599937b3aa48c5efcf49affe993", + "termforge_shell.exe": "9c01fa0577d8fc922d239d8a711ac13d1e4989104aad68795230bfcd998b4eb6", + "terminal_trace.exe": "40e869753f4c00515ee0990f3732eac04f965fe2c3070f1bf772717476269f3d", + "text_action_policy_test.exe": "535b469c9aa0da7e77670578f75d04956d98e9f912d5c9a6c310ab796ab58d4f", + "transport_completion_test.exe": "b83b797cacd8aaa05bfe8697b54f86da4d0ffe618b37b9b7080dbe05a0e99ad9", + "workflow_fixture_test.exe": "a6624a6b1536c6f445f92e05ff72a5318ece47d455500f6adc467d94f9f04222", + "workspace_state_test.exe": "ac3fbc8a56d33efda33cc9040c731c183e0a8666e23fce48d368c1868797714d", + "conpty-backend/conpty.dll": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "conpty-backend/OpenConsole.exe": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "vim": { + "version": "9.1 patches 1-532, MSYS2", + "sha256": "2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a", + "scope": "test-process PATH only" + }, + "final_ctest": { + "started_at": "2026-10-26T15:52:21.7023013Z", + "completed_at": "2026-10-26T16:00:07.0924704Z", + "exit_code": 8, + "tests": [ + { + "number": 1, + "name": "shell_launch", + "result": "Passed", + "elapsed_seconds": 0.01 + }, + { + "number": 2, + "name": "candidate_title", + "result": "Passed", + "elapsed_seconds": 0.78 + }, + { + "number": 3, + "name": "candidate_reply_close", + "result": "Passed", + "elapsed_seconds": 0.36 + }, + { + "number": 4, + "name": "runtime_observation", + "result": "Passed", + "elapsed_seconds": 1.66 + }, + { + "number": 5, + "name": "ime_input_expectation", + "result": "Passed", + "elapsed_seconds": 0.01 + }, + { + "number": 6, + "name": "sent_message_wait", + "result": "Passed", + "elapsed_seconds": 0.3 + }, + { + "number": 7, + "name": "find_bar", + "result": "Passed", + "elapsed_seconds": 0.37 + }, + { + "number": 8, + "name": "renderer_metrics", + "result": "Passed", + "elapsed_seconds": 0.92 + }, + { + "number": 9, + "name": "ime_context", + "result": "Passed", + "elapsed_seconds": 0.35 + }, + { + "number": 10, + "name": "layout_state", + "result": "Passed", + "elapsed_seconds": 0.13 + }, + { + "number": 11, + "name": "workspace_state", + "result": "Passed", + "elapsed_seconds": 0.34 + }, + { + "number": 12, + "name": "transport_completion", + "result": "Passed", + "elapsed_seconds": 0.01 + }, + { + "number": 13, + "name": "scenario_tick_gate", + "result": "Passed", + "elapsed_seconds": 0.01 + }, + { + "number": 14, + "name": "candidate_core", + "result": "Passed", + "elapsed_seconds": 1.17 + }, + { + "number": 15, + "name": "resize_core", + "result": "Passed", + "elapsed_seconds": 0.02 + }, + { + "number": 16, + "name": "history_core", + "result": "Passed", + "elapsed_seconds": 0.02 + }, + { + "number": 17, + "name": "selection_core", + "result": "Passed", + "elapsed_seconds": 0.09 + }, + { + "number": 18, + "name": "search_core", + "result": "Passed", + "elapsed_seconds": 0.12 + }, + { + "number": 19, + "name": "paste", + "result": "Passed", + "elapsed_seconds": 0.01 + }, + { + "number": 20, + "name": "sender_fence", + "result": "Passed", + "elapsed_seconds": 1.03 + }, + { + "number": 21, + "name": "sender_fence_pipe", + "result": "Passed", + "elapsed_seconds": 0.38 + }, + { + "number": 22, + "name": "clipboard_policy", + "result": "Passed", + "elapsed_seconds": 0.01 + }, + { + "number": 23, + "name": "keyboard", + "result": "Passed", + "elapsed_seconds": 0.01 + }, + { + "number": 24, + "name": "text_action_policy", + "result": "Passed", + "elapsed_seconds": 0.01 + }, + { + "number": 25, + "name": "candidate_resize_replay", + "result": "Passed", + "elapsed_seconds": 0.56 + }, + { + "number": 26, + "name": "candidate_shell_tui", + "result": "***Failed", + "elapsed_seconds": 30.68 + }, + { + "number": 27, + "name": "core", + "result": "Passed", + "elapsed_seconds": 6.15 + }, + { + "number": 28, + "name": "recovery", + "result": "Passed", + "elapsed_seconds": 9.89 + }, + { + "number": 29, + "name": "measure", + "result": "Passed", + "elapsed_seconds": 4.03 + }, + { + "number": 30, + "name": "binary_paths", + "result": "Passed", + "elapsed_seconds": 0.18 + }, + { + "number": 31, + "name": "conpty_margin_contract", + "result": "Passed", + "elapsed_seconds": 0.36 + }, + { + "number": 32, + "name": "benchmark_tools", + "result": "Passed", + "elapsed_seconds": 5.57 + }, + { + "number": 33, + "name": "differential", + "result": "Passed", + "elapsed_seconds": 3.67 + }, + { + "number": 34, + "name": "ime_inventory", + "result": "Passed", + "elapsed_seconds": 50.6 + }, + { + "number": 35, + "name": "text_actions", + "result": "Passed", + "elapsed_seconds": 5.6 + }, + { + "number": 36, + "name": "workflow_fixture", + "result": "Passed", + "elapsed_seconds": 22.74 + }, + { + "number": 37, + "name": "workflow_sessions", + "result": "Passed", + "elapsed_seconds": 77.08 + }, + { + "number": 38, + "name": "workflow_resources", + "result": "Passed", + "elapsed_seconds": 0.6 + }, + { + "number": 39, + "name": "workflow_resource_phases", + "result": "Passed", + "elapsed_seconds": 4.35 + }, + { + "number": 40, + "name": "workflow_system_content", + "result": "Passed", + "elapsed_seconds": 0.16 + }, + { + "number": 41, + "name": "workflow_system_runner", + "result": "Passed", + "elapsed_seconds": 0.67 + }, + { + "number": 42, + "name": "copy_error_frame", + "result": "Passed", + "elapsed_seconds": 0.17 + }, + { + "number": 43, + "name": "paste_fence_evidence", + "result": "Passed", + "elapsed_seconds": 0.12 + }, + { + "number": 44, + "name": "resize_trace", + "result": "Passed", + "elapsed_seconds": 0.13 + }, + { + "number": 45, + "name": "research_guards", + "result": "Passed", + "elapsed_seconds": 5.06 + }, + { + "number": 46, + "name": "receive", + "result": "Passed", + "elapsed_seconds": 0.25 + }, + { + "number": 47, + "name": "conpty", + "result": "Passed", + "elapsed_seconds": 23.48 + }, + { + "number": 48, + "name": "conpty_ctrl_c", + "result": "Passed", + "elapsed_seconds": 4.42 + }, + { + "number": 49, + "name": "conpty_margin", + "result": "Passed", + "elapsed_seconds": 0.36 + }, + { + "number": 50, + "name": "conpty_backend", + "result": "Passed", + "elapsed_seconds": 1.24 + }, + { + "number": 51, + "name": "presentation", + "result": "Passed", + "elapsed_seconds": 4.05 + }, + { + "number": 52, + "name": "shell", + "result": "***Timeout", + "elapsed_seconds": 180.05 + }, + { + "number": 53, + "name": "selection_bytes", + "result": "Passed", + "elapsed_seconds": 3.65 + }, + { + "number": 54, + "name": "selection_context", + "result": "Passed", + "elapsed_seconds": 3.91 + }, + { + "number": 55, + "name": "raw_console_receiver", + "result": "Passed", + "elapsed_seconds": 0.41 + }, + { + "number": 56, + "name": "paste_system", + "result": "Passed", + "elapsed_seconds": 6.7 + } + ], + "ctest_log_sha256": "8aafbb86aff94166f27131a0730ae9f89047eabfd573df3009c1b2be7d60bef2", + "last_test_log_sha256": "c7c335df7b947320a6fcd0fdd22287d8bdb13aed18bc9ed95377b8c8dc2e64a2" + }, + "diagnostic_artifacts": [ + "headless-ordinary.json", + "headless-resize.json", + "ui-profiles.json", + "intermediate-workflows.json", + "closing-reply.json" + ], + "limitations": [ + "Only candidate .4 with explicit official backend validated here", + "Data-only eight-session contract; no hosted real clipboard or IME acceptance", + "Shared-host diagnostics are not R54 formal samples or WindTerm performance equivalence", + "No user-approved numeric budget; presentation endpoint unknown", + "Unrelated Vim, resize and workspace failures remain open" + ], + "eight_session_runs": [ + { + "run": "run-40c6fff18ecd453684ad2ee06a44c669", + "sample": { + "mode": "ui-workflow-sessions", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workflow-sessions", + "sessions_closed": 18, + "active_children_after": 0, + "presented_done": null + }, + "frame_sha256": "68fb528174c7f7edf2916ca1b6f1f7d2a6b80deeb4cec787539fb051bb344ee5", + "sessions_workflow": { + "schema": "termforge-workflow-sessions-identity-validation-v1", + "live_sessions": 8, + "checkpoints": 26, + "rejected_ninth": true, + "load_foreground_verified": true, + "data_workflow_verified": true, + "complete_workflow_verified": false, + "system_clipboard_verified": false, + "ime_verified": false + }, + "resources": { + "summary": "resource-summary.json", + "scope": "launcher-tree", + "performance_gate": "not_evaluated" + }, + "error_kind": null + }, + "data_only": true, + "system_clipboard": false, + "ime": false, + "checkpoints": [ + { + "phase": "eight-ready", + "elapsed_ms": 571, + "cycle": 0 + }, + { + "phase": "reject-ninth", + "elapsed_ms": 574, + "cycle": 0 + }, + { + "phase": "load-started", + "elapsed_ms": 731, + "cycle": 0 + }, + { + "phase": "reading-before-append", + "elapsed_ms": 749, + "cycle": 0 + }, + { + "phase": "reading-after-append", + "elapsed_ms": 877, + "cycle": 0 + }, + { + "phase": "search-running", + "elapsed_ms": 1021, + "cycle": 0 + }, + { + "phase": "find-complete", + "elapsed_ms": 6355, + "cycle": 0 + }, + { + "phase": "foreground-input", + "elapsed_ms": 6493, + "cycle": 0 + }, + { + "phase": "zoom-75", + "elapsed_ms": 6626, + "cycle": 0 + }, + { + "phase": "switch-zoom", + "elapsed_ms": 6775, + "cycle": 0 + }, + { + "phase": "history-evicted", + "elapsed_ms": 7020, + "cycle": 0 + }, + { + "phase": "paste-cancel-pending", + "elapsed_ms": 7606, + "cycle": 0 + }, + { + "phase": "paste-cancelled", + "elapsed_ms": 7975, + "cycle": 0 + }, + { + "phase": "background-complete", + "elapsed_ms": 26268, + "cycle": 0 + }, + { + "phase": "cycle-six", + "elapsed_ms": 26584, + "cycle": 1 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 27859, + "cycle": 1 + }, + { + "phase": "cycle-six", + "elapsed_ms": 28160, + "cycle": 2 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 29429, + "cycle": 2 + }, + { + "phase": "cycle-six", + "elapsed_ms": 29734, + "cycle": 3 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 31003, + "cycle": 3 + }, + { + "phase": "cycle-six", + "elapsed_ms": 31326, + "cycle": 4 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 32562, + "cycle": 4 + }, + { + "phase": "cycle-six", + "elapsed_ms": 32875, + "cycle": 5 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 34169, + "cycle": 5 + }, + { + "phase": "paste-close-pending", + "elapsed_ms": 34461, + "cycle": 5 + }, + { + "phase": "after-paste-close", + "elapsed_ms": 34675, + "cycle": 5 + } + ], + "paste_cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "stopped", + "wrong_generation_rejected": true + } + } + ], + "raw_sha256": { + "cancel.bin": "bcc3593351da0137a27071da66416a7cbe02ca5605f49b14e5a6c85ebac894a6", + "cancel.bin.before.json": "a90746e62c8e815c76305806dfff25d55dd34d67f54308b0c865152693425663", + "close.bin": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c", + "close.bin.before.json": "42d48c9b60c19059b0fd753b871521787ece886f5976aef804fe05baa7db1584", + "frame.bmp": "68fb528174c7f7edf2916ca1b6f1f7d2a6b80deeb4cec787539fb051bb344ee5", + "resource-observations.jsonl": "39a33bf36d3da8512f99e18c617bb8ae9d2ce18bd45d8fb923ea89e3bc6fd15c", + "resource-phases.jsonl": "3d4bed0243cafdcb17e067cf7a80fc0a8deab7b297a4976cd4ca3fab6595b100", + "resource-summary.json": "8084b1234602e26ee63f26be0067a9fc8845eef227e9db7277c7092ea0528273", + "sample.json": "efb0f88de0d82b21f38592e2858c3937c9e4c56a0f57c9cbfc128b8cdb5ef818", + "sample.public.json": "9507843aee6d9088fd93fb35fb925f64d9b854f0d0c307dc5bc6676422ff7604", + "sessions.json": "953d27af5461179b5cfa672022f9ab7d7f1c50670b20f3acba9fb9bb046f7f28", + "validation.json": "0449c92f202590a5255928fb7df1f5ab615821d26656a3675c9b78a50c7692ef", + "workspace.layout": "9a96d62943f54727e7c50ce5785a571efbfa6cbece6248f6a6d144257c1e1d58" + } + }, + { + "run": "run-529ed519a8f145f191c850d0218abb04", + "sample": { + "mode": "ui-workflow-sessions", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workflow-sessions", + "sessions_closed": 18, + "active_children_after": 0, + "presented_done": null + }, + "frame_sha256": "4443a91feb73693650c2a6f8ac4feb91fa9fe3fee9e1340086892c2257db1990", + "sessions_workflow": { + "schema": "termforge-workflow-sessions-identity-validation-v1", + "live_sessions": 8, + "checkpoints": 26, + "rejected_ninth": true, + "load_foreground_verified": true, + "data_workflow_verified": true, + "complete_workflow_verified": false, + "system_clipboard_verified": false, + "ime_verified": false + }, + "resources": { + "summary": "resource-summary.json", + "scope": "launcher-tree", + "performance_gate": "not_evaluated" + }, + "error_kind": null + }, + "data_only": true, + "system_clipboard": false, + "ime": false, + "checkpoints": [ + { + "phase": "eight-ready", + "elapsed_ms": 570, + "cycle": 0 + }, + { + "phase": "reject-ninth", + "elapsed_ms": 574, + "cycle": 0 + }, + { + "phase": "load-started", + "elapsed_ms": 730, + "cycle": 0 + }, + { + "phase": "reading-before-append", + "elapsed_ms": 752, + "cycle": 0 + }, + { + "phase": "reading-after-append", + "elapsed_ms": 881, + "cycle": 0 + }, + { + "phase": "search-running", + "elapsed_ms": 1038, + "cycle": 0 + }, + { + "phase": "find-complete", + "elapsed_ms": 6482, + "cycle": 0 + }, + { + "phase": "foreground-input", + "elapsed_ms": 6642, + "cycle": 0 + }, + { + "phase": "zoom-75", + "elapsed_ms": 6791, + "cycle": 0 + }, + { + "phase": "switch-zoom", + "elapsed_ms": 6946, + "cycle": 0 + }, + { + "phase": "history-evicted", + "elapsed_ms": 7233, + "cycle": 0 + }, + { + "phase": "paste-cancel-pending", + "elapsed_ms": 7992, + "cycle": 0 + }, + { + "phase": "paste-cancelled", + "elapsed_ms": 8387, + "cycle": 0 + }, + { + "phase": "background-complete", + "elapsed_ms": 27312, + "cycle": 0 + }, + { + "phase": "cycle-six", + "elapsed_ms": 27632, + "cycle": 1 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 28930, + "cycle": 1 + }, + { + "phase": "cycle-six", + "elapsed_ms": 29255, + "cycle": 2 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 30560, + "cycle": 2 + }, + { + "phase": "cycle-six", + "elapsed_ms": 30907, + "cycle": 3 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 32144, + "cycle": 3 + }, + { + "phase": "cycle-six", + "elapsed_ms": 32466, + "cycle": 4 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 33763, + "cycle": 4 + }, + { + "phase": "cycle-six", + "elapsed_ms": 34105, + "cycle": 5 + }, + { + "phase": "cycle-eight", + "elapsed_ms": 35332, + "cycle": 5 + }, + { + "phase": "paste-close-pending", + "elapsed_ms": 35604, + "cycle": 5 + }, + { + "phase": "after-paste-close", + "elapsed_ms": 35808, + "cycle": 5 + } + ], + "paste_cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "stopped", + "wrong_generation_rejected": true + } + } + ], + "raw_sha256": { + "cancel.bin": "bcc3593351da0137a27071da66416a7cbe02ca5605f49b14e5a6c85ebac894a6", + "cancel.bin.before.json": "a90746e62c8e815c76305806dfff25d55dd34d67f54308b0c865152693425663", + "close.bin": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c", + "close.bin.before.json": "42d48c9b60c19059b0fd753b871521787ece886f5976aef804fe05baa7db1584", + "frame.bmp": "4443a91feb73693650c2a6f8ac4feb91fa9fe3fee9e1340086892c2257db1990", + "resource-observations.jsonl": "4a24473a1c2c48d3f96098f18dd8c99b16e1e33c30856900727c20af6cd4a832", + "resource-phases.jsonl": "4ddef626506c4410572d43f8daaa521487ce6121bc26fadca51c17a3ed73dc50", + "resource-summary.json": "a1c3b5a9b1c224c2ee9bd398ee72edecfc0046aacd464998ed68d20171dd5a59", + "sample.json": "d8abba7266e09bfbfab499957e620b1fd2e111c0b26aab6994baf0222108f3cf", + "sample.public.json": "2a0f6f9d77f7a858d372dcc0ad139b74b6e5e27550af87acb11f099df85986ed", + "sessions.json": "29bea82e4c709a55704b9dc6491e7a9c9a1cba08dfb42e4ef0564e765128aacf", + "validation.json": "1cc474b79845172d40b6544689ef261a331cdd7ebb4760d908c55c8d21fa9c41", + "workspace.layout": "9a96d62943f54727e7c50ce5785a571efbfa6cbece6248f6a6d144257c1e1d58" + } + } + ], + "internal_skips": [ + "setUpClass (test_candidate_compare.CandidateComparisonTests) ... skipped 'optional Alacritty binary is not built; core-candidate workflow runs these checks'", + "setUpClass (test_capability_matrix.CapabilityMatrixTests) ... skipped 'optional Alacritty binary is not built; core-candidate workflow runs these checks'", + "setUpClass (test_fuzz_matrix.FuzzMatrixTests) ... skipped 'optional Alacritty binary is not built; core-candidate workflow runs these checks'", + "setUpClass (test_real_app_matrix.RealApplicationMatrixTests) ... skipped 'optional real application capture dependencies are not built'", + "test_actual_system_copy_paste_and_shortcuts_twice (experiments.shell.test_actions.ActionWindowTest.test_actual_system_copy_paste_and_shortcuts_twice) ... skipped 'actual system Copy/Paste requires a disposable hosted VM'", + "test_suspended_target_is_reclaimed_if_job_assignment_fails (test_ui_r1.EvidenceBoundaryTests.test_suspended_target_is_reclaimed_if_job_assignment_fails) ... skipped 'opt-in pinned reference failure injection'", + "test_mouse_selection_to_real_system_clipboard (experiments.shell.test_shell.ShellTests.test_mouse_selection_to_real_system_clipboard) ... skipped 'system copy UI gate requires a disposable hosted VM'", + "test_system_clipboard_product_read_errors_cancel_and_close (experiments.shell.test_shell.ShellTests.test_system_clipboard_product_read_errors_cancel_and_close) ... skipped 'product clipboard read gate requires a disposable hosted VM'", + "test_system_clipboard_shortcut_to_window_confirmation_and_receiver (experiments.shell.test_shell.ShellTests.test_system_clipboard_shortcut_to_window_confirmation_and_receiver) ... skipped 'real system clipboard UI gate requires a disposable hosted VM'", + "test_real_system_copy_shortcuts_for_styled_fixture_twice (experiments.shell.test_selection_bytes.SelectionBytesWindowTest.test_real_system_copy_shortcuts_for_styled_fixture_twice) ... skipped 'system CF requires a disposable hosted VM'", + "test_real_system_copy_and_edit_context_twice (experiments.shell.test_selection_context.SelectionContextWindowTest.test_real_system_copy_and_edit_context_twice) ... skipped 'real CF/EDIT Copy requires a disposable hosted VM'", + "test_real_system_clipboard (experiments.shell.test_paste.PasteTests.test_real_system_clipboard) ... skipped 'Private window station OpenClipboard denied (5); system API gate remains unverified." + ], + "shell_unit_status": [ + { + "name": "test_clipboard_evidence_rejects_leaks_missing_phases_and_false_cleanup", + "status": "ok" + }, + { + "name": "test_hosted_guards_reject_missing_or_partial_identity_before_any_work", + "status": "ok" + }, + { + "name": "test_native_clipboard_mode_refuses_non_hosted_before_window_creation", + "status": "ok" + }, + { + "name": "test_actual_client_bounds_drive_geometry", + "status": "ok" + }, + { + "name": "test_native_copy_return_highlight_cf_and_target_are_independent_gates", + "status": "ok" + }, + { + "name": "test_occupied_product_copy_keeps_original_error_sentinel_target_and_strict_join", + "status": "ok" + }, + { + "name": "test_old_v3_is_readable_but_never_claims_the_new_copy_popup_gate", + "status": "ok" + }, + { + "name": "test_assignment_failure_reaps_gated_launcher_and_preserves_failure", + "status": "ok" + }, + { + "name": "test_close_during_continuous_output", + "status": "ok" + }, + { + "name": "test_close_while_resizing_continuous_output", + "status": "ERROR" + }, + { + "name": "test_commands_unicode_editing_interrupt_and_normal_exit", + "status": "ok" + }, + { + "name": "test_desktop_creation_failure_closes_job_and_keeps_evidence", + "status": "ok" + }, + { + "name": "test_find_continuous_output_partial_navigation_replace_cancel_and_target_close", + "status": "ok" + }, + { + "name": "test_find_real_edit_navigation_unicode_output_isolation_and_cancel", + "status": "ok" + }, + { + "name": "test_invalid_arguments_create_no_session", + "status": "ok" + }, + { + "name": "test_keyboard_message_path_to_real_console_bytes", + "status": "ok" + }, + { + "name": "test_launch_failure_is_visible_and_recoverable", + "status": "ok" + }, + { + "name": "test_local_shell_launch_working_directory_and_cleanup", + "status": "ok" + }, + { + "name": "test_main_and_bottom_sessions_focus_geometry_and_cleanup", + "status": "ERROR" + }, + { + "name": "test_main_split_background_restore_close_and_focus", + "status": "ok" + }, + { + "name": "test_mouse_selection_copy_model_zoom_sessions_and_eviction", + "status": "ok" + }, + { + "name": "test_mouse_selection_to_real_system_clipboard", + "status": "skipped" + }, + { + "name": "test_paste_confirmation_and_hidden_target_preserve_visible_focus", + "status": "ok" + }, + { + "name": "test_paste_control_eof_remains_failure_after_cleanup", + "status": "ok" + }, + { + "name": "test_paste_missing_go_remains_failure_after_cleanup", + "status": "ok" + }, + { + "name": "test_per_session_zoom_and_controlled_dpi_transitions", + "status": "ok" + }, + { + "name": "test_queues_apply_bounded_backpressure_and_reject_input", + "status": "ok" + }, + { + "name": "test_real_history_scroll_anchor_eviction_and_hidden_sessions", + "status": "ok" + }, + { + "name": "test_real_monitor_round_trip_preserves_workspace", + "status": "ok" + }, + { + "name": "test_resize_child_dimensions_and_interrupt", + "status": "ERROR" + }, + { + "name": "test_selection_autoscroll_with_short_and_tall_workspace", + "status": "ok" + }, + { + "name": "test_system_clipboard_product_read_errors_cancel_and_close", + "status": "skipped" + }, + { + "name": "test_system_clipboard_shortcut_to_window_confirmation_and_receiver", + "status": "skipped" + }, + { + "name": "test_watchdog_contains_real_shell_tree", + "status": "ok" + }, + { + "name": "test_workspace_rename_drag_and_fresh_process_restore", + "status": "ok" + }, + { + "name": "test_workspace_sessions_resize_and_cleanup", + "status": "ERROR" + }, + { + "name": "test_workspace_v1_empty_and_invalid_recovery", + "status": "ok" + }, + { + "name": "test_workspace_v2_empty_session_roundtrip", + "status": "ok" + } + ], + "shell_module_complete": false, + "shell_module_limitation": "CTest killed aggregate at original 180s; listed method statuses do not make the complete module pass", + "standalone_paste_runs": [ + { + "run": "paste-0", + "sample": { + "mode": "ui-paste", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 12, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 12, + "terminated": 0 + }, + "desktop_gone": true + }, + "raw_sha256": { + "cancel.bin": "bcc3593351da0137a27071da66416a7cbe02ca5605f49b14e5a6c85ebac894a6", + "cancel.bin.before.json": "a90746e62c8e815c76305806dfff25d55dd34d67f54308b0c865152693425663", + "close.bin": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c", + "close.bin.before.json": "42d48c9b60c19059b0fd753b871521787ece886f5976aef804fe05baa7db1584", + "confirmation.bmp": "2b63b1d079427584b5bb4be1739dc221535af2291ef0b062994c3e5903a7c6a8", + "frame.bmp": "6e947f9d4d723f67da816440e3182629b4c4321a9ccb5876c87a4fa2ea06b9ce", + "input.bin": "d4acd50607214fe6614644e1f5502d7f1043dc3e0d38ca87bbdaf66f12e527b2", + "paste-workflow.json": "6c971df3ae4711bcbf07ecf4f5336211e7da3256a0ffcf694c7a9b06574f464b", + "sample.json": "f14a5f6d1977734e06a599aeed4b01da1f9ff17e523953f8035cf4b1544726f1", + "workspace.layout": "4c271282879f651f87a060c325ff62287a9ddd86cda103c86855a3133b209b13" + }, + "workflow": { + "schema": "termforge-window-paste-v2", + "keyboard": { + "default_enter_accept": true, + "default_escape_cancel": true, + "tab_enter_accept": true + }, + "cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "stopped", + "wrong_generation_rejected": true + } + } + ] + } + }, + { + "run": "paste-1", + "sample": { + "mode": "ui-paste", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 12, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 12, + "terminated": 0 + }, + "desktop_gone": true + }, + "raw_sha256": { + "cancel.bin": "bcc3593351da0137a27071da66416a7cbe02ca5605f49b14e5a6c85ebac894a6", + "cancel.bin.before.json": "a90746e62c8e815c76305806dfff25d55dd34d67f54308b0c865152693425663", + "close.bin": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c", + "close.bin.before.json": "42d48c9b60c19059b0fd753b871521787ece886f5976aef804fe05baa7db1584", + "confirmation.bmp": "2b63b1d079427584b5bb4be1739dc221535af2291ef0b062994c3e5903a7c6a8", + "frame.bmp": "6e947f9d4d723f67da816440e3182629b4c4321a9ccb5876c87a4fa2ea06b9ce", + "input.bin": "d4acd50607214fe6614644e1f5502d7f1043dc3e0d38ca87bbdaf66f12e527b2", + "paste-workflow.json": "6c971df3ae4711bcbf07ecf4f5336211e7da3256a0ffcf694c7a9b06574f464b", + "sample.json": "d52d098a6e8be6cf6e415bee3659f3add3d6d9998e3ed1bc702ceeda70da81c1", + "workspace.layout": "4c271282879f651f87a060c325ff62287a9ddd86cda103c86855a3133b209b13" + }, + "workflow": { + "schema": "termforge-window-paste-v2", + "keyboard": { + "default_enter_accept": true, + "default_escape_cancel": true, + "tab_enter_accept": true + }, + "cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "stopped", + "wrong_generation_rejected": true + } + } + ] + } + }, + { + "run": "paste-control-eof", + "sample": { + "mode": "ui-paste-control-eof", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "desktop_gone": true + }, + "raw_sha256": { + "cancel.bin": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c", + "cancel.bin.before.json": "a90746e62c8e815c76305806dfff25d55dd34d67f54308b0c865152693425663", + "confirmation.bmp": "2b63b1d079427584b5bb4be1739dc221535af2291ef0b062994c3e5903a7c6a8", + "input.bin": "d4acd50607214fe6614644e1f5502d7f1043dc3e0d38ca87bbdaf66f12e527b2", + "paste-control-failure.json": "b0fd2d17e5d7fa2d635cae3416293e4a2c1d11e240a48f829b5da4552b51d68c", + "sample.json": "2f9a3caf3ad18d64dd7d6f542c465c52db071c462dfbac552096ed01d127a1f5", + "workspace.layout": "a26afdba5f7a4c7a7056bab7530fc98f0866419b5d87087cd85142d4243b163c" + }, + "observed_failure": "paste control EOF rejected", + "control_failure": { + "eof_observed": true, + "timed_out": false, + "wait_finished": true, + "state": "failed", + "in_flight": 0, + "retained_capacity": 15 + }, + "receiver_bytes": 768 + }, + { + "run": "paste-control-timeout", + "sample": { + "mode": "ui-paste-control-timeout", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "desktop_gone": true + }, + "raw_sha256": { + "cancel.bin": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c", + "cancel.bin.before.json": "a90746e62c8e815c76305806dfff25d55dd34d67f54308b0c865152693425663", + "confirmation.bmp": "2b63b1d079427584b5bb4be1739dc221535af2291ef0b062994c3e5903a7c6a8", + "input.bin": "d4acd50607214fe6614644e1f5502d7f1043dc3e0d38ca87bbdaf66f12e527b2", + "paste-control-failure.json": "e296e5fa38603e2d8818e9332d52248bb5a68c32c42c0593b84cfb311d473ec1", + "sample.json": "fbf107cba73ca6b72f1f78ae14cdbc94c3809b2cd5c919a3002c9a5c6828ee3c", + "workspace.layout": "a26afdba5f7a4c7a7056bab7530fc98f0866419b5d87087cd85142d4243b163c" + }, + "observed_failure": "paste control missing GO rejected", + "control_failure": { + "eof_observed": false, + "timed_out": true, + "wait_finished": true, + "state": "failed", + "in_flight": 0, + "retained_capacity": 15 + }, + "receiver_bytes": 768 + } + ], + "other_shell_failures": [ + { + "run": "resize-0", + "stage": 43, + "sample": { + "mode": "resize", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "desktop_gone": true + }, + "expected_exact_line": "TF_SIZE_100x28", + "observed_line": "TF_SIZE_100x28OW", + "local_raw_sample_sha256": "ac6f650ce3bd87b934755ca47072f65b689be5662ea3396cb7939c41e847d7da" + }, + { + "run": "resize-close-0", + "stage": 43, + "sample": { + "mode": "resize-close", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "desktop_gone": true + }, + "expected_exact_line": "TF_SIZE_100x28", + "observed_line": "TF_SIZE_100x28OW", + "local_raw_sample_sha256": "6489fa48ad6bb446e5c5395f3b133829d9f8852479f352f15926a126a8308c2f" + }, + { + "run": "main-2", + "stage": 7, + "sample": { + "mode": "ui-main", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 16, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 16, + "terminated": 0 + }, + "desktop_gone": true + }, + "expected_exact_line": null, + "observed_line": null, + "local_raw_sample_sha256": "8ca836c302b4bdf21a81dbfd1668381978d7c55c45fb05140bbcbad596712926" + }, + { + "run": "ui-2", + "stage": 52, + "sample": { + "mode": "ui", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 12, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 12, + "terminated": 0 + }, + "desktop_gone": true + }, + "expected_exact_line": null, + "observed_line": null, + "local_raw_sample_sha256": "de287a2da956ccb3a75985ec690146d8097ca61da63517c9fd61f33b02bc99d4" + } + ] +} diff --git a/experiments/shell/results/2026-10-26-resize-order/validation.json b/experiments/shell/results/2026-10-26-resize-order/validation.json new file mode 100644 index 00000000..4bb81fc2 --- /dev/null +++ b/experiments/shell/results/2026-10-26-resize-order/validation.json @@ -0,0 +1,276 @@ +{ + "schema": 1, + "date": "2026-10-26", + "scope": "R51 bounded resize-order diagnosis; no behavior fix or R2 acceptance", + "source_commit": "386ebe725aab691b13a7cf962a09b56f858b0804", + "parent_commit": "3e5b96d0e0854b691b6dd41821ab10b6124b83eb", + "platform": "Windows-11-10.0.26200-SP0", + "source_git_blob_sha1": { + "CMakeLists.txt": "2ce3ac63485b81a7140cdb87c3f32c7f24270c82", + "experiments/core/candidate_resize_replay.cpp": "7c8c58240e27f23cda1e1a24d10037f2e25a8cc6", + "experiments/core/candidate_terminal.cpp": "cc12246ee7e94382bf95835cd45e6ef6b8949a19", + "experiments/core/candidate_terminal.hpp": "55de3176ec220b2e404cc7e108f6d0063af3c004", + "experiments/shell/resize.hpp": "20843d154fffdd982e1a9221984245eb0d4cc654", + "experiments/shell/resize_trace.hpp": "9feeef78fa42eee3cbbc60d1ab94ccdef7fc539b", + "experiments/shell/resize_trace.py": "1f182627f341494b2ca07fe02f457596900abd06", + "experiments/shell/test_resize_trace.py": "29faf4557592932d9c4f2cbc543fa043d103fb4b", + "experiments/shell/shell.cpp": "e440a83a98f9e86093cb82bd30f5ffc28e9f8299", + "experiments/shell/size_client.cpp": "251220197098b1b654b7554b0a727e4a2e85d7a9", + "experiments/conpty/backend.hpp": "3195dceaefa166b1251839108d29c5fd034aa2fb", + "experiments/conpty/session.hpp": "d44f36661594a5b76b62d1ecb95cf166db22e90a", + "experiments/candidates/alacritty/Cargo.lock": "6501189a98bfe0610ebcb5e3c31d3abc2ae3f132" + }, + "source_raw_sha256": { + "CMakeLists.txt": "613d9a5a3285af26bc3cdce151ce87968f0795a5f13ab409e87a9f81f55efa06", + "experiments/core/candidate_resize_replay.cpp": "dd3f38062312592e39d6e63f63bebc5839c16efcf5b53b75127f2497b34714d8", + "experiments/core/candidate_terminal.cpp": "0d0eaabe5ef896aa7b263c8151622f579f37c421bf9473fc3d69ada010a64e61", + "experiments/core/candidate_terminal.hpp": "328411c1af6ddb509623ded8fe9c814cb6ac56cd502a2952e8cedd0a3bb2d997", + "experiments/shell/resize.hpp": "37d22ba31fc3fee8004a3dd555f53fa55cb398057b6576f5a2d9b5403e2f5cd0", + "experiments/shell/resize_trace.hpp": "a33f06302c9573b71d2432a4e962dc79d3e5916b69bfba210599f071a3cd6853", + "experiments/shell/resize_trace.py": "ffc6b0924ce5777ce3695e869179f479afa4d02e483c0547158e1f2dea2b34a0", + "experiments/shell/test_resize_trace.py": "31956a0aca0fdee2a8ea6bd26911bb1f3a4ded4233a485a4d04806aae31cfac7", + "experiments/shell/shell.cpp": "872f4d0c5f4ca4f38f074761d1ac5f3ac728fcc732ef5058c98ccfa8a4384b84", + "experiments/shell/size_client.cpp": "cb11661bec9551053eec0dbb474bce2ac899a06ee2e50830a370af4816df39f4", + "experiments/conpty/backend.hpp": "5ee3ab95dc4b9aefce23ff7900cda2ecdd9cd09d1b1044074de2f5e572c31489", + "experiments/conpty/session.hpp": "6945e6ffba1d61e66266f867d3cd124eb63c39da37e27662a5eaf544f60ed96b", + "experiments/candidates/alacritty/Cargo.lock": "896c6e60909d55d61fb26d277b1a98daf9245e8aee811caff37fc3e9d06ca627" + }, + "binaries_sha256": { + "termforge_shell.exe": "7c06cbf1b605e17aa2d70914565370088111dc6e48257a8e120e883f77c860a0", + "shell_test_launcher.exe": "05a18755d869de52608c32518291281161446ca70c794dde65f9a50f06ecaa97", + "shell_size_client.exe": "df1c8cbdc0ea13d583f4b616bba0e6b8df10c492afdcfba1308231912084905d", + "candidate_resize_replay.exe": "d17f350c4a673fa73cf806c2df734fe282ae005e0b671b03700d45f48a0c9a54" + }, + "rust_library_sha256": "e1897a65b7a06a174ac0dbc89c3c54a01672d1373037c27c8becdc329a6f27eb", + "candidate": "0.26.1-dev+termforge.4", + "backend": "reviewed-side-by-side-1.25.260930003", + "creation_flags": 24, + "native_deadline_seconds": 25, + "outer_deadline_seconds": 40, + "replay_deadline_seconds": 10, + "instrumentation": { + "opt_in": true, + "event_limit": 1024, + "transport_limit_bytes": 131072, + "observer_order_only": true + }, + "runs": [ + { + "index": 0, + "scenario": "resize", + "stage": 43, + "verdict": "known_red_reproduced", + "process_exit": 2, + "outer_timed_out": false, + "event_overflow": false, + "transport_overflow": false, + "captured_bytes": 8412, + "replay_steps": 29, + "replay_exit": 0, + "replay_full_screen_equals_live_failure": true, + "failing_line": { + "row_zero_based": 10, + "expected": "TF_SIZE_100x28", + "actual": "TF_SIZE_100x28OW" + }, + "actual_host_resize_calls": [ + { + "rows": 12, + "columns": 60, + "generation": 1 + }, + { + "rows": 30, + "columns": 90, + "generation": 2 + }, + { + "rows": 10, + "columns": 50, + "generation": 3 + }, + { + "rows": 28, + "columns": 100, + "generation": 4 + } + ], + "outer_job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "outer_job_after_cleanup": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "desktop_gone": true, + "private_artifact_sha256": { + "sample.json": "f8a340201bd2628ae48fcf3b4fece393ea9f5d210cc2bbef19c63a4343683d32", + "resize-trace.json": "204bdb643cdc1863b28a734ffa2b9ce5702d659021e262ed70ac030cfd4057c1", + "resize-trace.transport.bin": "38d09be70c1f18d512be5beb3966cea922423bf45d2aaee7c7bc56a61678aaf8" + }, + "private_replay_sha256": { + "plan.txt": "6f9d840e1d93847be1babf7cd36b65897f8feb246dbd731d26631fd300f86d1b", + "states.jsonl": "8bf1f05bf4304934f806e722c2e8516714669f11dd59c798519a8ceeccc8baac", + "summary.json": "76ad3cdaa39387f64a63d2dc77fbbde6bdcfefa85ff72b940c453b45666991c4" + } + }, + { + "index": 1, + "scenario": "resize", + "stage": 43, + "verdict": "known_red_reproduced", + "process_exit": 2, + "outer_timed_out": false, + "event_overflow": false, + "transport_overflow": false, + "captured_bytes": 8871, + "replay_steps": 22, + "replay_exit": 0, + "replay_full_screen_equals_live_failure": true, + "failing_line": { + "row_zero_based": 10, + "expected": "TF_SIZE_100x28", + "actual": "TF_SIZE_100x28OW" + }, + "actual_host_resize_calls": [ + { + "rows": 12, + "columns": 60, + "generation": 1 + }, + { + "rows": 30, + "columns": 90, + "generation": 2 + }, + { + "rows": 10, + "columns": 50, + "generation": 3 + }, + { + "rows": 28, + "columns": 100, + "generation": 4 + } + ], + "outer_job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "outer_job_after_cleanup": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "desktop_gone": true, + "private_artifact_sha256": { + "sample.json": "be125db57708bd7988aac013c90e6510f9fb2078ce195bd68bc696767f1d9f98", + "resize-trace.json": "02d915b8c3f560039ab367393f77223a8ad41baa5a54613eef9db5e4bc5144a0", + "resize-trace.transport.bin": "7808e2e2acfd47a79f8e1f5fe4c2f90aaf4687be164ad537f081f30ee0ca8dc5" + }, + "private_replay_sha256": { + "plan.txt": "f257a0af38e6a728e277d5f35d1857a26924e3fb2d224377a3098cae80287409", + "states.jsonl": "031084cab09d80bd9bee69bac7d2ee96583fa2fe4fc34f72ccb27fe94e7df9ea", + "summary.json": "71db0675197bc34fc7aef126237a196f259a968970739120173e1b1bea69a8a2" + } + }, + { + "index": 2, + "scenario": "resize", + "stage": 43, + "verdict": "known_red_reproduced", + "process_exit": 2, + "outer_timed_out": false, + "event_overflow": false, + "transport_overflow": false, + "captured_bytes": 7237, + "replay_steps": 29, + "replay_exit": 0, + "replay_full_screen_equals_live_failure": true, + "failing_line": { + "row_zero_based": 10, + "expected": "TF_SIZE_100x28", + "actual": "TF_SIZE_100x28OW" + }, + "actual_host_resize_calls": [ + { + "rows": 12, + "columns": 60, + "generation": 1 + }, + { + "rows": 30, + "columns": 90, + "generation": 2 + }, + { + "rows": 10, + "columns": 50, + "generation": 3 + }, + { + "rows": 28, + "columns": 100, + "generation": 4 + } + ], + "outer_job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "outer_job_after_cleanup": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "desktop_gone": true, + "private_artifact_sha256": { + "sample.json": "9418216aed5386bdace578e1031b533031bdb22b2f458af57e95f1d2516c500e", + "resize-trace.json": "776240939667bec7bd071d1a1f7d447a3b2474891a6e7fc725f48561bab9581d", + "resize-trace.transport.bin": "2a07279ca1df863f4ef9506d1e1ddf4cdd307d6a079a5800e26e1581146785b1" + }, + "private_replay_sha256": { + "plan.txt": "41bd8ed8c07abe1062f2b8a3f7628ed54819351b3657178b9c3d33551bf62a3b", + "states.jsonl": "78d7d10747d7019f9c50fee747de5990207863ffdcb7b3cfd09900c631438ec2", + "summary.json": "7ea79948a04acf18c4b05f8a671a8c91e1e090175bb7b079808d30824d47ae8b" + } + } + ], + "additional_validation": { + "pure_trace_tests_passed": 2, + "negative_capture_cases": 10, + "normal_mode_with_trace_environment": "passed_without_trace_files", + "normal_mode_job_after_cleanup": { + "active": 0, + "total": 6, + "terminated": 0 + } + }, + "observations": [ + "All three original resize runs retain the stage43 failure, and all four host API calls occurred; coalescing alone cannot explain these samples.", + "Replaying exactly the observed UI parser resizes and feed partitions reproduces the full live failed screen, not only the failing marker.", + "The candidate grows the visible buffer by pulling history into it. In these samples the later host stream does not repaint the whole resized buffer, and its cursor-addressed echo and size output overwrite different existing rows.", + "The native size helper output contains the independent TF_SIZE_100x28 result; no CHAR_INFO snapshot at this failure boundary was captured.", + "Pinned host source already documents divergent host/terminal reflow and lazy DSR CPR synchronization at console-info queries. This is relevant source evidence, not proof of an exhaustive root cause or a completed fix." + ], + "primary_sources": [ + "https://github.com/microsoft/terminal/blob/dc8ae365847d41f62d1ebca93815bb00959bc12f/src/host/screenInfo.cpp#L1412", + "https://github.com/microsoft/terminal/issues/18725", + "https://github.com/microsoft/terminal/pull/19535" + ], + "limitations": [ + "Opt-in observers alter scheduling. The uninstrumented joint baseline separately has both a red and a green; neither is overwritten by these results.", + "The scenario code and native deadline are unchanged, but the absolute synthetic helper launch path follows this isolated build directory; its transport is not byte-identical to the root joint-baseline capture.", + "This is a fixed diagnostic batch of three, not a pass/failure-rate estimate or performance measurement.", + "No native/transport/terminal synchronization fix has been applied. R51 remains open.", + "Raw transport, full screen states, frame and desktop identity remain local; this record publishes hashes and synthetic marker metadata only.", + "No input, oracle, load, native deadline or history/reflow feature was weakened. API return is not a host completion fence.", + "Default-core, system-backend, full R1/R2, native buffer contents, actual presentation and WindTerm parity are not validated by this batch." + ] +} diff --git a/experiments/shell/results/2026-10-26-resize-replay-guards/validation.json b/experiments/shell/results/2026-10-26-resize-replay-guards/validation.json new file mode 100644 index 00000000..bf6289f2 --- /dev/null +++ b/experiments/shell/results/2026-10-26-resize-replay-guards/validation.json @@ -0,0 +1,209 @@ +{ + "schema": "termforge-resize-replay-guard-validation-v1", + "source_base_commit": "3cdfda92c40666e214006df86f36de8c40cf1cda", + "source_identity": "source_sha256 identifies reviewed checkout bytes before this commit; Git blobs can differ by checkout newline normalization", + "source_sha256": { + "CMakeLists.txt": "aeb3802d438c96b160ef8185969b39416231b91c044fda4695a7f56f650d9f9e", + "experiments/core/candidate_resize_replay.cpp": "e5c2e29b29a63edc0b5d3671f0fd3e9cdc1df9567088c4242855206053439081", + "experiments/core/test_candidate_resize_replay.py": "eab1fefd6cbbc7eb9eda6271792af12704f8d03b46d99690312d83a46b88574e", + "experiments/shell/resize_trace.py": "e4b23211ae1b1f1a1435de744558ad04de053872db136f67a22dac476a8ec5cd", + "experiments/shell/test_resize_trace.py": "5438f9aeef805f90b789a1a881fa974b525e000f14365cab54b4507479dd6d6d", + "experiments/shell/README.md": "a218d43988f8f9e8cbcc4ce9d314e3a14d006e4eab91ac10c46b9ce3728c41b1" + }, + "binaries_sha256": { + "before": "d17f350c4a673fa73cf806c2df734fe282ae005e0b671b03700d45f48a0c9a54", + "after": "e859d743a54ae13177bdf0fba7834a12a1029b5de760cb194fb7929fe0809e05" + }, + "scope": "Diagnostic parser/schema validation only; no terminal, transport, worker or oracle behavior change", + "actual_exe_red_green": [ + { + "trailing_record": "R 12", + "before_exit": 0, + "after_exit": 1 + }, + { + "trailing_record": "F 3", + "before_exit": 0, + "after_exit": 1 + }, + { + "trailing_record": "X", + "before_exit": 0, + "after_exit": 1 + }, + { + "trailing_record": "R 12 999999999999999999999999999999999", + "before_exit": 0, + "after_exit": 1 + }, + { + "trailing_record": "F 3 999999999999999999999999999999999", + "before_exit": 0, + "after_exit": 1 + } + ], + "original_trace_mutation_red_green": [ + { + "capture": 0, + "case": "host-negative-generation", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 0, + "case": "host-end-wrong-generation", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 0, + "case": "host-zero-columns", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 0, + "case": "feed-wrong-current-grid", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 0, + "case": "qpc-regresses", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 1, + "case": "host-negative-generation", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 1, + "case": "host-end-wrong-generation", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 1, + "case": "host-zero-columns", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 1, + "case": "feed-wrong-current-grid", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 1, + "case": "qpc-regresses", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 2, + "case": "host-negative-generation", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 2, + "case": "host-end-wrong-generation", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 2, + "case": "host-zero-columns", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 2, + "case": "feed-wrong-current-grid", + "before_accepted": true, + "after_rejected": true + }, + { + "capture": 2, + "case": "qpc-regresses", + "before_accepted": true, + "after_rejected": true + } + ], + "original_offline_replays": [ + { + "index": 0, + "trace_sha256": "204bdb643cdc1863b28a734ffa2b9ce5702d659021e262ed70ac030cfd4057c1", + "transport_sha256": "38d09be70c1f18d512be5beb3966cea922423bf45d2aaee7c7bc56a61678aaf8", + "plan_sha256": "6f9d840e1d93847be1babf7cd36b65897f8feb246dbd731d26631fd300f86d1b", + "states_sha256": "8bf1f05bf4304934f806e722c2e8516714669f11dd59c798519a8ceeccc8baac", + "previous_states_sha256": "8bf1f05bf4304934f806e722c2e8516714669f11dd59c798519a8ceeccc8baac", + "replay_exit": 0, + "steps": 29, + "original_plan_unchanged": true, + "full_states_byte_identical": true, + "captured_bytes": 8412 + }, + { + "index": 1, + "trace_sha256": "02d915b8c3f560039ab367393f77223a8ad41baa5a54613eef9db5e4bc5144a0", + "transport_sha256": "7808e2e2acfd47a79f8e1f5fe4c2f90aaf4687be164ad537f081f30ee0ca8dc5", + "plan_sha256": "f257a0af38e6a728e277d5f35d1857a26924e3fb2d224377a3098cae80287409", + "states_sha256": "031084cab09d80bd9bee69bac7d2ee96583fa2fe4fc34f72ccb27fe94e7df9ea", + "previous_states_sha256": "031084cab09d80bd9bee69bac7d2ee96583fa2fe4fc34f72ccb27fe94e7df9ea", + "replay_exit": 0, + "steps": 22, + "original_plan_unchanged": true, + "full_states_byte_identical": true, + "captured_bytes": 8871 + }, + { + "index": 2, + "trace_sha256": "776240939667bec7bd071d1a1f7d447a3b2474891a6e7fc725f48561bab9581d", + "transport_sha256": "2a07279ca1df863f4ef9506d1e1ddf4cdd307d6a079a5800e26e1581146785b1", + "plan_sha256": "41bd8ed8c07abe1062f2b8a3f7628ed54819351b3657178b9c3d33551bf62a3b", + "states_sha256": "78d7d10747d7019f9c50fee747de5990207863ffdcb7b3cfd09900c631438ec2", + "previous_states_sha256": "78d7d10747d7019f9c50fee747de5990207863ffdcb7b3cfd09900c631438ec2", + "replay_exit": 0, + "steps": 29, + "original_plan_unchanged": true, + "full_states_byte_identical": true, + "captured_bytes": 7237 + } + ], + "focused_ctest": { + "tests": 2, + "passed": 2, + "failures": 0, + "internal_unit_tests": 8, + "log_sha256": "a90b9931d34986bcb980ae7da386b29c77d27d275883020cabddd5a155e90349" + }, + "before_test_logs": { + "exe_sha256": "d33fd8c72a470e740730424bf8526890c82715a7049320b788a228500098d7ce", + "trace_sha256": "7fa1e64b5d2a1ed4460219c44374884ec26fcb2a29b4847f00e686d16ae8d41a" + }, + "original_proof_sha256": "74769899db987da8842f2b37db5a78e5088eb413691426900c06fb5c0eae0c47", + "supported_observation_orders": [ + "host begin/end before the matching request observation", + "coalesced requests without host calls", + "in-flight older worker calls interleaved with later UI parser resizes", + "equal QPC values in mutex observation order" + ], + "deadlines": { + "exe_seconds": 10, + "original_live_native_seconds": 25, + "original_live_outer_seconds": 40 + }, + "limitations": [ + "No new live GUI or native run; original native failure and proof unchanged", + "Offline replay does not establish host completion, actual presentation or timing", + "Host request mapping validates observed metadata; no synchronization fence is inferred", + "Raw original trace, transport and complete states remain private" + ], + "r51_behavior_fixed": false, + "r2_accepted": false +} diff --git a/experiments/shell/results/2026-10-26-ui-paste-fence/validation.json b/experiments/shell/results/2026-10-26-ui-paste-fence/validation.json new file mode 100644 index 00000000..3e990195 --- /dev/null +++ b/experiments/shell/results/2026-10-26-ui-paste-fence/validation.json @@ -0,0 +1,689 @@ +{ + "schema": "termforge-ui-paste-fence-validation-v1", + "date": "2026-10-26", + "issue": 53, + "status": "partial_issue_remains_open", + "source_commit": "a15eb7f67315fb53c9d66b9c5a4424e13511a311", + "source_tree": "b2a2d7b5b5e1f74de1be4aa3ccb8b692cb4c9fec", + "source_files": { + "CMakeLists.txt": { + "git_blob": "4f29e60930e0ce5f5aed359a6ee94c2b178dbdd2", + "checkout_sha256": "fa0bc11bd7821cab83dc0076d2d1fd305410febca7ccba96dd14c7bfa4b39fcf" + }, + "experiments/shell/paste_checks.py": { + "git_blob": "3de07a7a49726e1365daba541054ae94ad609e7e", + "checkout_sha256": "3e565ed0c297c94573589230bcff57c5e6f4a043d5b63ac605a79cd46a0ae18e" + }, + "experiments/shell/paste_scenario_state.hpp": { + "git_blob": "77db7d43dc22777596e4597ec4081078acd994e6", + "checkout_sha256": "52fdef856fe365b3a39aa27360d31c9d88979366b39de7d618c49942b0fc5433" + }, + "experiments/shell/sender_fence.hpp": { + "git_blob": "56e092f0397d6052e278b464cfc08add2cbee8ec", + "checkout_sha256": "fefdd87fff5840b7a0e03f1c682a2ca626249b48536b483fac8a12e8cf882987" + }, + "experiments/shell/sender_fence_pipe.hpp": { + "git_blob": "aca1566dfd9224c9c4e798c4d7435aced680c3b1", + "checkout_sha256": "ee3b862a6496b060ad2312c234221570ac081ebbf2e24cfccbec3ced57bdeb03" + }, + "experiments/shell/sender_fence_pipe_test.cpp": { + "git_blob": "64580c0c831eab4e52ded57a36bfd7d2b07c5368", + "checkout_sha256": "424c5645246dec064bfc76129553a273f1ae8c3cb0104be91b19269c69b8a59a" + }, + "experiments/shell/shell.cpp": { + "git_blob": "cd112860aa377857cdbd749cc2ef44973c80360b", + "checkout_sha256": "b369968152bbff107c699181d4e23f271e62a5732f0c41793159b3efc3609321" + }, + "experiments/shell/test_paste_checks.py": { + "git_blob": "0c53e0532c74bb5e936389f24eb002c818489894", + "checkout_sha256": "fdb2a4ceff178d07e18facaa8e30226670d1b0c4a4c0b69b2fd21fd2f0327d1c" + }, + "experiments/shell/test_shell.py": { + "git_blob": "66512ac1099cf477cac8f26f225b18c301c1d3d8", + "checkout_sha256": "a6b63c1754b0e0dc1cc01863891f4c7c9d989f818d351838a8cfe295edaf811c" + }, + "experiments/shell/test_workflow_sessions.py": { + "git_blob": "de6cef3be06725d606b91ae68eb1cc45eb744fb5", + "checkout_sha256": "6c968a443c46b30e75a929f880228e6a23bf2b92d9221bbbf18816933babd6e7" + }, + "experiments/shell/workflow_sessions_checks.py": { + "git_blob": "8369b412abe893393bf316430ccedd22e9eb75ba", + "checkout_sha256": "b976d3504837eef57ff13319daed254870727741f36f265412a6b14b4cb3faae" + }, + "experiments/shell/workspace.hpp": { + "git_blob": "7d4fa607b4878add7e0b5c411c711cc3e60abe3d", + "checkout_sha256": "5669a13701b2fe60e8bd567a122edb970bdc4aadcb98ceb3927905f0c000275e" + }, + "experiments/shell/workspace_paste_scenario.hpp": { + "git_blob": "2a22a036696ecf3ce6d2ff2a0b7aed3decad49b4", + "checkout_sha256": "d55b7568f345373784c288f496e97e0b3bfc336c47f4b59378d4c51409e0b128" + }, + "experiments/shell/workspace_workflow_scenario.hpp": { + "git_blob": "029ef4bcf9393be4ab6194bbbe95d145e98ff70b", + "checkout_sha256": "467571556440f4e89305bc1261d389bb8f24ba84f607b8cd36b8078a58314369" + } + }, + "environment": { + "os": "Windows-11-10.0.26200-SP0", + "architecture": "AMD64", + "compiler": "MSVC 19.42.34433", + "configuration": "Release x64", + "candidate": "alacritty_terminal 0.26.1-dev+termforge.4", + "backend": "Microsoft.Windows.Console.ConPTY 1.25.260930003", + "glyph_width_flags": 24 + }, + "binaries": { + "termforge_shell.exe": "0a1fee632cb640dff4599fdcca277c959038d31ec88bfed8cff893b3271d26a6", + "shell_test_launcher.exe": "dbde4249dc08c7df80120398ffd252aa5e4958e4ef640cc161ea4c2d28b9f291", + "shell_input_client.exe": "850c82fb8bb58de8170bf096cc6dd4f8ba34d9ba11dbd2addc41508cddc31d8c", + "shell_text_client.exe": "9485b03424e421a4018596cc9487e7feeb33c26d30820985aa45843fdcf315d9", + "sender_fence_test.exe": "ab4af3b0f148f6e6377e70c98936c6c34d0be0d3cee4b17b3f8554c39b2d3e52", + "sender_fence_pipe_test.exe": "6a94ac84631434a81ae1098932813d7ceeb81f2bb33a2e4b2456834bd8972e92", + "conpty-backend/conpty.dll": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "conpty-backend/OpenConsole.exe": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "request": { + "utf8_bytes": 3000000, + "utf16_units": 1000000, + "literal": "U+6C49 repeated 1000000", + "sha256": "0ba67970de9941c1bc33e97869b116a0e129c8a859e3b4c0087e20c51f6a404b", + "minimum_sent": 16384, + "receiver_checkpoint_bytes": 768 + }, + "deadlines_ms": { + "standalone_ui": 35000, + "standalone_outer": 40000, + "receiver_gate": 15000, + "eight_session_ui": 70000, + "workflow_fixture": 55000, + "missing_go_negative_only": 2000 + }, + "controls": [ + { + "case": "paste-0", + "sample_sha256": "aa28ff969ef47debda3f78e9d5eeb2e414492e30ecea5d354cfbb2d7857997a9", + "status": "passed", + "exit_code": 0, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 12, + "terminated": 0 + }, + "desktop_gone": true, + "paste_cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "stopped", + "wrong_generation_rejected": true + } + } + ], + "captures": { + "cancel.bin": { + "bytes": 18414, + "sha256": "bcc3593351da0137a27071da66416a7cbe02ca5605f49b14e5a6c85ebac894a6" + }, + "close.bin": { + "bytes": 768, + "sha256": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c" + }, + "input.bin": { + "bytes": 25021, + "sha256": "d4acd50607214fe6614644e1f5502d7f1043dc3e0d38ca87bbdaf66f12e527b2" + } + } + }, + { + "case": "paste-1", + "sample_sha256": "d769fff8294f3b933520464be2a710863cbc1c2cbc4d48b09ce04fdb09fc9d27", + "status": "passed", + "exit_code": 0, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 12, + "terminated": 0 + }, + "desktop_gone": true, + "paste_cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "stopped", + "wrong_generation_rejected": true + } + } + ], + "captures": { + "cancel.bin": { + "bytes": 18414, + "sha256": "bcc3593351da0137a27071da66416a7cbe02ca5605f49b14e5a6c85ebac894a6" + }, + "close.bin": { + "bytes": 768, + "sha256": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c" + }, + "input.bin": { + "bytes": 25021, + "sha256": "d4acd50607214fe6614644e1f5502d7f1043dc3e0d38ca87bbdaf66f12e527b2" + } + } + }, + { + "case": "paste-control-eof", + "sample_sha256": "577e1a5125a10f42ba176cafcc8bd4b72d92271be33ae8ec6b4f0e1ba61a71dc", + "status": "failed", + "exit_code": 2, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "desktop_gone": true, + "expected_failure": { + "eof_observed": true, + "timed_out": false, + "wait_finished": true, + "state": "failed", + "in_flight": 0, + "retained_capacity": 15 + }, + "captures": { + "cancel.bin": { + "bytes": 768, + "sha256": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c" + }, + "input.bin": { + "bytes": 25021, + "sha256": "d4acd50607214fe6614644e1f5502d7f1043dc3e0d38ca87bbdaf66f12e527b2" + } + } + }, + { + "case": "paste-control-timeout", + "sample_sha256": "2a5b36750f15d093e34ad81a29363e33ea84c4ea21e2798af96dc5702960e19b", + "status": "failed", + "exit_code": 2, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "desktop_gone": true, + "expected_failure": { + "eof_observed": false, + "timed_out": true, + "wait_finished": true, + "state": "failed", + "in_flight": 0, + "retained_capacity": 15 + }, + "captures": { + "cancel.bin": { + "bytes": 768, + "sha256": "61da7e55d911305206ae68ac162a0a73d2fc1433efae5f0cfda2b9d0c9b5363c" + }, + "input.bin": { + "bytes": 25021, + "sha256": "d4acd50607214fe6614644e1f5502d7f1043dc3e0d38ca87bbdaf66f12e527b2" + } + } + } + ], + "eight_session_runs": [ + { + "source_commit": "62226af2d1f636cef1920ac0ef9998935a7dd243", + "resource_sampling": true, + "diagnostic_only": false, + "sample_sha256": "93be8809499aa33081e411df03e20c879d63e4c4bf4940f85fcab8e1d992059f", + "sessions_sha256": "063739d6e17ff15a2861fd13f247dffd40fe7d1f92a4835e040f9a04b0d61106", + "exit_code": 2, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "failure": "workflow sessions scenario timeout at stage 45", + "foreground_fixture_failure": "workflow control deadline expired", + "foreground_fixture_sha256": "48a40aec11a95182dd5a55b5f7c3e5eceb58b093f36ad02674aae6ba103325d5", + "phases": [ + { + "phase": "paste-cancel-pending", + "elapsed_ms": 14371 + }, + { + "phase": "paste-cancelled", + "elapsed_ms": 15111 + }, + { + "phase": "background-complete", + "elapsed_ms": 51158 + }, + { + "phase": "paste-close-pending", + "elapsed_ms": 61806 + } + ], + "paste_cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "stopped", + "wrong_generation_rejected": true + } + } + ], + "wake_evidence": "legacy_exit_is_final_state_not_actual_wake" + }, + { + "source_commit": "62226af2d1f636cef1920ac0ef9998935a7dd243", + "resource_sampling": true, + "diagnostic_only": false, + "sample_sha256": "6bcd382eca3ec3e09bdd3ab59f1aba530e1fe9670fb3423d9adda2a941920635", + "sessions_sha256": "dd918bc9fc472bfa6ff8bd851fc13467143185f740a9d5bca6348e5492c8a26a", + "exit_code": 2, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "failure": "workflow sessions scenario timeout at stage 45", + "foreground_fixture_failure": "workflow control deadline expired", + "foreground_fixture_sha256": "48a40aec11a95182dd5a55b5f7c3e5eceb58b093f36ad02674aae6ba103325d5", + "phases": [ + { + "phase": "paste-cancel-pending", + "elapsed_ms": 14766 + }, + { + "phase": "paste-cancelled", + "elapsed_ms": 15730 + }, + { + "phase": "background-complete", + "elapsed_ms": 52546 + }, + { + "phase": "paste-close-pending", + "elapsed_ms": 63096 + } + ], + "paste_cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "stopped", + "wrong_generation_rejected": true + } + } + ], + "wake_evidence": "legacy_exit_is_final_state_not_actual_wake" + }, + { + "source_commit": "a15eb7f67315fb53c9d66b9c5a4424e13511a311", + "resource_sampling": false, + "diagnostic_only": true, + "sample_sha256": "e762d5d0caf2d016959572ec2c60272ba791a7d994b0ff006627406776d3250d", + "sessions_sha256": "5762dc068ce78233fdc40d06ebfa28734d263f0b4557c258f0146147fd6b25ef", + "exit_code": 2, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "failure": "workflow sessions scenario timeout at stage 45", + "foreground_fixture_failure": "workflow control deadline expired", + "foreground_fixture_sha256": "48a40aec11a95182dd5a55b5f7c3e5eceb58b093f36ad02674aae6ba103325d5", + "phases": [ + { + "phase": "paste-cancel-pending", + "elapsed_ms": 14071 + }, + { + "phase": "paste-cancelled", + "elapsed_ms": 14785 + }, + { + "phase": "background-complete", + "elapsed_ms": 50262 + }, + { + "phase": "paste-close-pending", + "elapsed_ms": 60948 + } + ], + "paste_cases": [ + { + "name": "cancel", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 18414, + "flood_observed": true, + "fence": { + "generation": 1, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "cancelled", + "wrong_generation_rejected": true + } + }, + { + "name": "close", + "target": 1, + "total": 3000000, + "before_sent": 17391, + "before_accepted": 18414, + "accepted": 18414, + "sent": 18414, + "in_flight": 0, + "retained_capacity": 15, + "state": "cancelled", + "checkpoint_bytes": 768, + "received": 768, + "flood_observed": true, + "fence": { + "generation": 2, + "held": true, + "owned_bytes": 1023, + "wait_finished": true, + "other_responsive": true, + "exit": "cancelled", + "final_state": "stopped", + "wrong_generation_rejected": true + } + } + ], + "wake_evidence": "immutable_wait_exit_and_final_state" + } + ], + "original_red": { + "source_commit": "abe5a850343cdeb96606961a7d1bfc062fc52d2a", + "binaries": { + "build\\candidate\\bin\\Release\\termforge_shell.exe": { + "sha256": "62d57a551746f6c9379fba73af87230f1cb1edc9b74de9bc8136a38441fd33c6", + "bytes": 1741312 + }, + "build\\candidate\\bin\\Release\\conpty-backend\\manifest.json": { + "sha256": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439", + "bytes": 1467 + }, + "build\\candidate\\bin\\Release\\conpty-backend\\conpty.dll": { + "sha256": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "bytes": 119608 + }, + "build\\candidate\\bin\\Release\\conpty-backend\\OpenConsole.exe": { + "sha256": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586", + "bytes": 1089376 + } + }, + "samples": [ + { + "sample_sha256": "27341653ab0979ffc5a6feb9904a9abaa146bc37a956780c5a4b63b33d92ba01", + "exit_code": 2, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "desktop_gone": true, + "failure": "paste action did not occur during a partial transfer with consumed bytes and output flood; checkpoint=768 flood=1 sent=3000000 accepted=3000000 total=3000000 state=3" + }, + { + "sample_sha256": "16213745702fcc944287229eafc9c4029de21dd4c2a4651f244eb983e93848ff", + "exit_code": 2, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "desktop_gone": true, + "failure": "cancel transfer completed before receiver gate" + } + ] + }, + "fence_without_ui_notify": { + "base": "abe5a850343cdeb96606961a7d1bfc062fc52d2a", + "patch_sha256": "fef9501e555c5ba103da297e320df903cbd100f4864e9847e38844aa1f410cd0", + "exe_sha256": "6f5c95e32ff688768ac02383735a4864b5acf84f38b49faeaab04392a8f3ba03", + "status": "failed at original 35000ms UI deadline; original one-line UI cancel changed after this run" + }, + "full_suite": { + "source_commit": "62226af2d1f636cef1920ac0ef9998935a7dd243", + "ctest_passed": 52, + "ctest_total": 55, + "failed": [ + "candidate_shell_tui: Vim absent from process PATH", + "workflow_sessions: stage45, original B fixture deadline expired", + "shell: 180s CTest aggregate deadline; resize stage43 and workspace main7/ui52 failures retained" + ], + "log_sha256": "4295f3d895b289cb326b182fa3f1d30e09ea521d096ea95c768cb49aff746c07" + }, + "reviewed_targeted_checks": { + "ctest": [ + "sender_fence", + "sender_fence_pipe", + "paste_fence_evidence" + ], + "ctest_passed": 3, + "ui_positive_processes": 2, + "ui_expected_failure_processes": 2, + "go_ack": "actual anonymous Win32 pipes with same-process endpoints; positive GO uses unit-level payload simulation only", + "pipe_capacity": "actual inbound server buffer checked against whole128B request/192B reply budgets; saturation8+8 and ninthrejection tested" + }, + "limitations": [ + "Full eight-session acceptance still failed; issue53 and parent47 remain open.", + "Candidate/system and default/system have no success claimed from these official-backend samples.", + "No hosted system-clipboard or IME acceptance is claimed here.", + "Held payload is not measured OS backpressure; workload has synchronization and is not a performance benchmark.", + "UI observed producer, output, state and cleanup; actual presented endpoint is unknown.", + "Raw logs and captures remain local; only synthetic byte counts, hashes and whitelisted records published." + ], + "vim_path_followup": { + "source_commit": "a15eb7f67315fb53c9d66b9c5a4424e13511a311", + "vim_version": "9.1 patches1-532 MSYS2", + "vim_sha256": "2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a", + "sample_sha256": "5f35275f20697c8559b0b2e1b7a460b38032b9549fe0bbb60b3ccfb0ad18da01", + "status": "failed", + "failure": "candidate-tui stage59; resize command appeared in inserted text", + "exit_code": 2, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 7, + "terminated": 0 + }, + "desktop_gone": true, + "issue": 52 + } +} diff --git a/experiments/shell/results/2026-10-27-vim-msys-input/validation.json b/experiments/shell/results/2026-10-27-vim-msys-input/validation.json new file mode 100644 index 00000000..9d86b6b0 --- /dev/null +++ b/experiments/shell/results/2026-10-27-vim-msys-input/validation.json @@ -0,0 +1,1659 @@ +{ + "schema": "termforge.vim-msys-input-observation.v1", + "scope": "diagnosis only; no product fix or R2 acceptance", + "base_commit": "44ea45b988b6bb0b72f21cd24597fe54b9b4ec31", + "product_source": "c662011616a0498e0fbd6af8019f5f3cc03fdcaa", + "configuration": { + "candidate": "termforge.5", + "backend": "reviewed-side-by-side-1.25.260930003", + "creation_flags": 24, + "platform": "Windows-11-10.0.26200-SP0", + "vim_version": "9.1 patches 1-532", + "vim_file_sha256": "2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a", + "msys_file_sha256": "3096d2ba9be20e90be1399edd391bac65d84bc387f5a419b502c9b326081930f", + "msys_source_commit": "2e2ef940151485490aff0c7b00c00965ef7a71a4", + "frida_provider_version": null + }, + "diagnostic_identity": { + "shell_sha256": "73472864e5aa291704917812ff1848f7a59be6e5f30c41f6a613e3fc18c740d4", + "source_sha256": { + "experiments/conpty/session.hpp": "c41a4b401f412a9845fc6b2f365e241fdd039af3432f1e9f96f734e98118a521", + "experiments/shell/shell.cpp": "0f262abf4ef3ba62a2553bc2d80513b080e1bf06d4116c82d193c05722467495" + }, + "baseline_shell_sha256": "941ef34db129282a26625d8bad4fd663c4284fe3307802907366299ab04f31c4", + "original_scenario_sha256": "a300380b97ad424c5af06219a021a256de6178c3af7fb7e933e24bb8142201ee", + "original_runner_sha256": "45976b828eecb316881d698dedaef09ad304f72d991dfa360f1c552bbcbe9f1d", + "temporary_patch_sha256": "4e2b450c3d7c16bfc7c8d8530e381234bb1330b3e6b8840d6eace8c917ce7167" + }, + "protocol": { + "inner_timeout_seconds": 25, + "outer_timeout_seconds": 40, + "input": "\u000f:call writefile([string(&columns) . 'x' . string(&lines)], 'candidate-resize-log.txt', 'b')\r", + "attachment_gate": "only after original VimEnter READY and visible New; inside original deadline, before original i input", + "selection": "unique fixed-path Vim from session-owned Job; no global process scan", + "boundary_batch": { + "maximum": 4, + "executed": 3, + "stop": "at least one original stage59 and one valid green, or any different failure; otherwise maximum four cases", + "observed_outcomes": [ + "stage59", + "stage59", + "green" + ] + }, + "function_batch": { + "maximum": 2, + "executed": 1, + "stop": "first original stage59 or any different failure; otherwise maximum two cases", + "observed_outcomes": [ + "stage59" + ] + }, + "target_input_buffers_or_return_values_modified_by_script": false, + "records_limit": 512, + "native_records_per_call_limit": 256, + "read_bytes_per_call_limit": 512 + }, + "cases": [ + { + "id": "boundary-red-0", + "outcome": "stage59", + "outer_exit_code": 2, + "outer_timed_out": false, + "owned_job_after": { + "active": 0, + "total": 7, + "terminated": 0 + }, + "owned_desktop_gone": true, + "writer": { + "records": 91, + "truncated": false, + "command_written_bytes": 93, + "command_error": 0 + }, + "frida": { + "evidence_id": "ev_18adf14197eb077fa8d019c7f021251982860dcbcd9f22cc6e9117c8f41cd10c", + "event_count": 19, + "messages_truncated": false, + "provider": { + "id": "frida", + "name": "Frida", + "version": null + }, + "provider_limitations": [ + "Artifact identity is unavailable for this observation.", + "The target operating system and architecture were not reported by this observation." + ], + "script_limitations": [ + "calls already entered before attach are not observed", + "interception changes scheduling", + "timestamps are observer wall clock; no cross-process high resolution alignment" + ], + "script_sha256": "f01e1363d482ef404a7ef2031caf13434409f00dfe936b55fc3863c60628b9cb" + }, + "events": [ + { + "sequence": 2, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "69", + "resize_events": [] + }, + { + "sequence": 3, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 2048, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "69", + "resize_events": [] + }, + { + "sequence": 4, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "69", + "resize_events": [] + }, + { + "sequence": 5, + "type": "msys-read", + "thread": "T2", + "count": 1, + "requested": 4096, + "truncated": false, + "bytes_hex": "69" + }, + { + "sequence": 6, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "54", + "resize_events": [] + }, + { + "sequence": 7, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 16, + "requested": 2048, + "returned_count": 16, + "truncated": false, + "key_characters_hex": "54465f43414e4449444154455f545549", + "resize_events": [] + }, + { + "sequence": 8, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 16, + "requested": 16, + "returned_count": 16, + "truncated": false, + "key_characters_hex": "54465f43414e4449444154455f545549", + "resize_events": [] + }, + { + "sequence": 9, + "type": "msys-read", + "thread": "T2", + "count": 16, + "requested": 4096, + "truncated": false, + "bytes_hex": "54465f43414e4449444154455f545549" + }, + { + "sequence": 10, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 24 + } + ] + }, + { + "sequence": 11, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 2, + "requested": 2048, + "returned_count": 2, + "truncated": false, + "key_characters_hex": "", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 24 + }, + { + "position": 1, + "columns": 60, + "rows": 12 + } + ] + }, + { + "sequence": 12, + "type": "console", + "thread": "T3", + "name": "ReadConsoleInputW", + "success": true, + "count": 95, + "requested": 2176, + "returned_count": 95, + "truncated": false, + "key_characters_hex": "0f3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 24 + }, + { + "position": 1, + "columns": 60, + "rows": 12 + } + ] + }, + { + "sequence": 13, + "type": "console", + "thread": "T3", + "name": "WriteConsoleInputW", + "success": true, + "count": 93, + "requested": 93, + "returned_count": 93, + "truncated": false, + "key_characters_hex": "0f3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [] + }, + { + "sequence": 14, + "type": "console", + "thread": "T3", + "name": "PeekConsoleInputW", + "success": true, + "count": 93, + "requested": 2176, + "returned_count": 93, + "truncated": false, + "key_characters_hex": "0f3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [] + }, + { + "sequence": 15, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "0f", + "resize_events": [] + }, + { + "sequence": 16, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "3a", + "resize_events": [] + }, + { + "sequence": 17, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 92, + "requested": 2048, + "returned_count": 92, + "truncated": false, + "key_characters_hex": "3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [] + }, + { + "sequence": 18, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 92, + "requested": 92, + "returned_count": 92, + "truncated": false, + "key_characters_hex": "3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [] + }, + { + "sequence": 19, + "type": "msys-read", + "thread": "T2", + "count": 92, + "requested": 4096, + "truncated": false, + "bytes_hex": "3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d" + } + ], + "private_artifact_sha256": { + "sample.json": "a5b6287b358da1678d30efb47d541c10377ee24f3a606202f75a4053ef1eeae0", + "r52-writes.json": "d89fb7eadbcee2846a37c8b8b71784fa58d2f3a9a6a8d8da4a1453b654f6fd4d", + "candidate-input.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "candidate-insert-log.txt": "e6de4daeba1841783e7e22f81dbf87154f341c309dc5b54405e5fc2c2f3550a6", + "candidate-resize-log.txt": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "candidate-output.bin": "fc43e5c356005f9c32847476d4bdba5fabaf51439083d8ac4e530d3e8bb5c883", + "case-0-identity.json": "5632d1aac8f0e3df18b3fef69b532d6cd5e577a8496cf40ec03d5b21529d6081", + "case-0-status-final.json": "db0736829d3d23154f3dac4b8e4941d3350ba33e6e51f21f4d45de0c0396b76e", + "case-0-load.json": "cfa00b7a534855dea6708ea9bee6721f78df77f0beaa454549ea4a0af7b8176e", + "case-0-detach.json": "b53d8b7832810f75d86584dbc495295037f01103e868dcf359d1be54012afdc5" + }, + "observed_file_sha256": { + "alpha_normal_smoke.exe": "55f76e0ef0a68a2d3ee2d047852551b86e63074b84604e59fe6d0893361ba3a6", + "candidate_core_test.exe": "4c73b58d7da111ab52b6edbbb59898cf660860fdedaa64c4f49b46fae856a02d", + "candidate_reply_close_test.exe": "7431b2d5a414703d85fb9425b07b7df125eb7b5668851cb5383cf575cb620a69", + "candidate_resize_replay.exe": "c4cf0d9f68bb5bd688e601b87025f3237c8659f4d80e96bd4f67fa463463a13f", + "candidate_title_test.exe": "359141b75c6eeb9a0cc9ef59f9f21ad0d07bcaefb51beb19f64e3288a059bec2", + "clipboard_policy_test.exe": "ef7c7d860bad3b458b7ac8e659c46acd0f768f70281e7d6797e489520d1d559e", + "conpty_backend_probe.exe": "39b52ad4acc4e432718d3cdd6dc6ee2ab80b1584de16c0edc3d775ccc7b03c0d", + "conpty_ctrl_c_probe.exe": "30ad460b54cfcc752e20ccd5f656c6769e292528408a4a68e7182ab3206d39b6", + "conpty_margin_probe.exe": "7aaef1da461b3b6d833aea79332f28d8f924e26de2f9332feeb1f1303a51e30f", + "conpty_resize_native_probe.exe": "a570b55f7ef0f65be01b827112905a223fb32f37a0f645ce121ead1ed6555f59", + "core_measure.exe": "dfe74cf8e6ec0fdb976911569c14e52709be5e880d9b36d2a108187bca9c165b", + "find_bar_test.exe": "632e5ff486b7ad5d29292b4af1206c196d09ca1c1c8a38a830a004ee9c21f1a1", + "history_test.exe": "3b3f6199ad4d345ef5cc759ddcf80667fdde4c7778658e99764342cc17949e2e", + "ime_context_test.exe": "8941d52ed7642b15c46da5a50927ba09ceab7078c5f31c649e4e3baa859e8ae1", + "ime_input_expectation_test.exe": "a960fbd62a7c0d6eec564a5a2bb1cc271582e7bb55ec07ddb63bc46044b9c438", + "keyboard_test.exe": "fe027bbcb21a09fac9411dd5766a37e8edd711d3ef547f37a080ded097a4de3e", + "layout_state_test.exe": "7b174db85ff75e8a8f5613c949c264a4dedbb8cfeb8a18581f016191bd036ddb", + "paste_test.exe": "1e6acc382a75fe18b50212cf6950d3d714f89939b1cf63000795348e4db22d54", + "presentation_probe.exe": "91aa34c2ac7b4f6610da419a04091d4391cb78e224e6b5f567437a5565d4e33a", + "raw_console_receiver_test.exe": "0731af92b2536b189d80894b5ca4243d814e07ab2c513fff7b4d3fd3654578f2", + "real_app_capture.exe": "fddd86a02d7c8ba13b7cd458373c8acd19d999199baefce77e8f237efce47189", + "receive.exe": "b5ea1d10bba964ec80f183d761b1c0ea4334f6963597ccd1bb16b9c769fc76d5", + "recovery_probe.exe": "72213828da26b5657915667faccd1d0aca83dbe6d9537f38dbd9d5464750313b", + "renderer_metrics_test.exe": "f08e88f6ff8bb6be87f724ff6a3c398df359a639f83d75b5970e69659f8a4a5c", + "replay.exe": "54298c3fa5020d3ebcaa55c76564b9dcbfec5b25d772343c428e2e36e1858ca1", + "resize_test.exe": "fd97c87127d4763320832d1d538af5755eece8a1ec1485435cab6b41e9e549cd", + "runtime_observation_test.exe": "0049a0d863724bf8bd181c85868bd68a409eb5e12c755a5538add5f85bf8b0bf", + "scenario_tick_gate_test.exe": "f7368d2d4c13a9eee4a210e857fd5d13479641328fa71a984582c69b57735ecf", + "search_test.exe": "6bf2a5fa47e1f0665462e6601e01253674c6662e5db730161bd7eb09c81d621d", + "selection_test.exe": "19eaf7ef3e6f1a35f67e1b17a60858bfb0f268a20c9f2686d141c847a68fbb3a", + "sender_fence_pipe_test.exe": "19bba0df9e37159cc70728f68ae8a28364127fac21ab72cb21cdb036f9380686", + "sender_fence_test.exe": "b3c396a7d8a306d0987ef149fd9d5e014ab90ac6d54d0915a48b6d261a6e21c1", + "sent_message_wait_test.exe": "b9bb4960dc470d5aac08fecb2c0775c16e2a7e75fdabe283b6c028d284679abb", + "shell_clipboard_reader.exe": "01aad3988dfc520ec6419f9747cb2baa04efc9c1409f4b50a4a65b2f385bd7be", + "shell_ime_client.exe": "f02ec529d90db13bf4c904bbee8bc77930fdcf51ba868b4a43a43ed783f61830", + "shell_input_client.exe": "0075a332e524071809a1fdbd5161d1f360fa8e89faaf2b45b24c19f1d94829cc", + "shell_launch_test.exe": "6e96e163b4adc40b8d53c022a23a206176bf558c73eb76d9790b61f2abb13baf", + "shell_paste_probe.exe": "dd49d693dc30372d6f44cce2b7a1dcfc64adea1e6638399da4d400784a14bf6f", + "shell_query_client.exe": "691b440e9c500c76c1c09c448aafbe2316793bf341ffe69bbc2bf70e41be64cc", + "shell_queue_probe.exe": "e030070a6f287421bc3b74350fd45c8cd762837dc1512a76f688d20d39aa8715", + "shell_selection_text_client.exe": "18b60279aed48a9d39b6f5c57371c60b3a2b59bb21d6e30f92bac7adf50a3ed2", + "shell_size_client.exe": "eeb0801f69cc8add0a8178a185dcedb78a9b33234abfe5c600fd9b20b0f5aab4", + "shell_test_launcher.exe": "7bd78cffd78ac66d651933c9614d6be937c99baa105ce04c1a533df5785c11e2", + "shell_text_client.exe": "9350e53de74c9a6b78495a3b59788e3a31e0067a9c5ec1b8685a729ff18244d3", + "termforge_conpty_probe.exe": "1dcf69f65e9dd2abca1a58c10df2be3a296a4e22cdd895ba457222fe9e27f021", + "termforge_ime_probe.exe": "ba59ec28909430669c187a644651588b2f1984b1308b97056f222f332e7de9c9", + "termforge_shell.exe": "73472864e5aa291704917812ff1848f7a59be6e5f30c41f6a613e3fc18c740d4", + "terminal_trace.exe": "d09f34702cbd2d8be6ecfe98ab56a2326be1fde6b1fde4d10ef85d0f62955d2f", + "text_action_policy_test.exe": "9988026742d9ff45f300a6fa336ceca052a502b568f32114be7fd5057e57f5a4", + "transport_completion_test.exe": "6952438843e8a7605bb6cf0eb7add56dd8574f4e5edfbc3a534f795030a3fcc8", + "workflow_checkpoint_test.exe": "056047580e8ddd77224908667297a0b704f97ef57d1eadeb382b52c7c7875b86", + "workflow_fixture_test.exe": "ce3f09d8b3bb97d92dd6c61531e2cb9ba36070d2222b7b4ad7b86de82021f208", + "workspace_state_test.exe": "18080df77a0b7a370aa1e2dc79f2986dd3a4eb01661663dd95c43a11b802ee11" + } + }, + { + "id": "boundary-red-1", + "outcome": "stage59", + "outer_exit_code": 2, + "outer_timed_out": false, + "owned_job_after": { + "active": 0, + "total": 7, + "terminated": 0 + }, + "owned_desktop_gone": true, + "writer": { + "records": 91, + "truncated": false, + "command_written_bytes": 93, + "command_error": 0 + }, + "frida": { + "evidence_id": "ev_57b774550517dd49065104ebb12df86fbb31f791e78b51424637cd505a3a4d1e", + "event_count": 17, + "messages_truncated": false, + "provider": { + "id": "frida", + "name": "Frida", + "version": null + }, + "provider_limitations": [ + "Artifact identity is unavailable for this observation.", + "The target operating system and architecture were not reported by this observation." + ], + "script_limitations": [ + "calls already entered before attach are not observed", + "interception changes scheduling", + "timestamps are observer wall clock; no cross-process high resolution alignment" + ], + "script_sha256": "f01e1363d482ef404a7ef2031caf13434409f00dfe936b55fc3863c60628b9cb" + }, + "events": [ + { + "sequence": 2, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "69", + "resize_events": [] + }, + { + "sequence": 3, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 2048, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "69", + "resize_events": [] + }, + { + "sequence": 4, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "69", + "resize_events": [] + }, + { + "sequence": 5, + "type": "msys-read", + "thread": "T2", + "count": 1, + "requested": 4096, + "truncated": false, + "bytes_hex": "69" + }, + { + "sequence": 6, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "54", + "resize_events": [] + }, + { + "sequence": 7, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 16, + "requested": 2048, + "returned_count": 16, + "truncated": false, + "key_characters_hex": "54465f43414e4449444154455f545549", + "resize_events": [] + }, + { + "sequence": 8, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 16, + "requested": 16, + "returned_count": 16, + "truncated": false, + "key_characters_hex": "54465f43414e4449444154455f545549", + "resize_events": [] + }, + { + "sequence": 9, + "type": "msys-read", + "thread": "T2", + "count": 16, + "requested": 4096, + "truncated": false, + "bytes_hex": "54465f43414e4449444154455f545549" + }, + { + "sequence": 10, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 24 + } + ] + }, + { + "sequence": 11, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 2, + "requested": 2048, + "returned_count": 2, + "truncated": false, + "key_characters_hex": "", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 24 + }, + { + "position": 1, + "columns": 60, + "rows": 12 + } + ] + }, + { + "sequence": 12, + "type": "console", + "thread": "T3", + "name": "ReadConsoleInputW", + "success": true, + "count": 2, + "requested": 2176, + "returned_count": 2, + "truncated": false, + "key_characters_hex": "", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 24 + }, + { + "position": 1, + "columns": 60, + "rows": 12 + } + ] + }, + { + "sequence": 13, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "0f", + "resize_events": [] + }, + { + "sequence": 14, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "3a", + "resize_events": [] + }, + { + "sequence": 15, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 92, + "requested": 2048, + "returned_count": 92, + "truncated": false, + "key_characters_hex": "3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [] + }, + { + "sequence": 16, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 92, + "requested": 92, + "returned_count": 92, + "truncated": false, + "key_characters_hex": "3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [] + }, + { + "sequence": 17, + "type": "msys-read", + "thread": "T2", + "count": 92, + "requested": 4096, + "truncated": false, + "bytes_hex": "3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d" + } + ], + "private_artifact_sha256": { + "sample.json": "1fad8b791526612e05cc06053e43ef8277028ee5061874ab06940736b24a4b0f", + "r52-writes.json": "8b5f6d77dc5a2814e3f725e5ffa4a6c97820fb799c577debdaa2e6e06121ec87", + "candidate-input.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "candidate-insert-log.txt": "e6de4daeba1841783e7e22f81dbf87154f341c309dc5b54405e5fc2c2f3550a6", + "candidate-resize-log.txt": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "candidate-output.bin": "fc43e5c356005f9c32847476d4bdba5fabaf51439083d8ac4e530d3e8bb5c883", + "case-1-identity.json": "9feb950dd6183c656fa552975a985784774b6cd78022761cb9f7edede1160384", + "case-1-status-final.json": "bbaa93f260e171490538831f67c96aa21409c0cf0c94cec8fb316a8d23b16e11", + "case-1-load.json": "87f1209688e5efb1d81fa8f374640215070d15bc25f5dab8d5dcd4d5d965d103", + "case-1-detach.json": "b53d8b7832810f75d86584dbc495295037f01103e868dcf359d1be54012afdc5" + }, + "observed_file_sha256": { + "alpha_normal_smoke.exe": "55f76e0ef0a68a2d3ee2d047852551b86e63074b84604e59fe6d0893361ba3a6", + "candidate_core_test.exe": "4c73b58d7da111ab52b6edbbb59898cf660860fdedaa64c4f49b46fae856a02d", + "candidate_reply_close_test.exe": "7431b2d5a414703d85fb9425b07b7df125eb7b5668851cb5383cf575cb620a69", + "candidate_resize_replay.exe": "c4cf0d9f68bb5bd688e601b87025f3237c8659f4d80e96bd4f67fa463463a13f", + "candidate_title_test.exe": "359141b75c6eeb9a0cc9ef59f9f21ad0d07bcaefb51beb19f64e3288a059bec2", + "clipboard_policy_test.exe": "ef7c7d860bad3b458b7ac8e659c46acd0f768f70281e7d6797e489520d1d559e", + "conpty_backend_probe.exe": "39b52ad4acc4e432718d3cdd6dc6ee2ab80b1584de16c0edc3d775ccc7b03c0d", + "conpty_ctrl_c_probe.exe": "30ad460b54cfcc752e20ccd5f656c6769e292528408a4a68e7182ab3206d39b6", + "conpty_margin_probe.exe": "7aaef1da461b3b6d833aea79332f28d8f924e26de2f9332feeb1f1303a51e30f", + "conpty_resize_native_probe.exe": "a570b55f7ef0f65be01b827112905a223fb32f37a0f645ce121ead1ed6555f59", + "core_measure.exe": "dfe74cf8e6ec0fdb976911569c14e52709be5e880d9b36d2a108187bca9c165b", + "find_bar_test.exe": "632e5ff486b7ad5d29292b4af1206c196d09ca1c1c8a38a830a004ee9c21f1a1", + "history_test.exe": "3b3f6199ad4d345ef5cc759ddcf80667fdde4c7778658e99764342cc17949e2e", + "ime_context_test.exe": "8941d52ed7642b15c46da5a50927ba09ceab7078c5f31c649e4e3baa859e8ae1", + "ime_input_expectation_test.exe": "a960fbd62a7c0d6eec564a5a2bb1cc271582e7bb55ec07ddb63bc46044b9c438", + "keyboard_test.exe": "fe027bbcb21a09fac9411dd5766a37e8edd711d3ef547f37a080ded097a4de3e", + "layout_state_test.exe": "7b174db85ff75e8a8f5613c949c264a4dedbb8cfeb8a18581f016191bd036ddb", + "paste_test.exe": "1e6acc382a75fe18b50212cf6950d3d714f89939b1cf63000795348e4db22d54", + "presentation_probe.exe": "91aa34c2ac7b4f6610da419a04091d4391cb78e224e6b5f567437a5565d4e33a", + "raw_console_receiver_test.exe": "0731af92b2536b189d80894b5ca4243d814e07ab2c513fff7b4d3fd3654578f2", + "real_app_capture.exe": "fddd86a02d7c8ba13b7cd458373c8acd19d999199baefce77e8f237efce47189", + "receive.exe": "b5ea1d10bba964ec80f183d761b1c0ea4334f6963597ccd1bb16b9c769fc76d5", + "recovery_probe.exe": "72213828da26b5657915667faccd1d0aca83dbe6d9537f38dbd9d5464750313b", + "renderer_metrics_test.exe": "f08e88f6ff8bb6be87f724ff6a3c398df359a639f83d75b5970e69659f8a4a5c", + "replay.exe": "54298c3fa5020d3ebcaa55c76564b9dcbfec5b25d772343c428e2e36e1858ca1", + "resize_test.exe": "fd97c87127d4763320832d1d538af5755eece8a1ec1485435cab6b41e9e549cd", + "runtime_observation_test.exe": "0049a0d863724bf8bd181c85868bd68a409eb5e12c755a5538add5f85bf8b0bf", + "scenario_tick_gate_test.exe": "f7368d2d4c13a9eee4a210e857fd5d13479641328fa71a984582c69b57735ecf", + "search_test.exe": "6bf2a5fa47e1f0665462e6601e01253674c6662e5db730161bd7eb09c81d621d", + "selection_test.exe": "19eaf7ef3e6f1a35f67e1b17a60858bfb0f268a20c9f2686d141c847a68fbb3a", + "sender_fence_pipe_test.exe": "19bba0df9e37159cc70728f68ae8a28364127fac21ab72cb21cdb036f9380686", + "sender_fence_test.exe": "b3c396a7d8a306d0987ef149fd9d5e014ab90ac6d54d0915a48b6d261a6e21c1", + "sent_message_wait_test.exe": "b9bb4960dc470d5aac08fecb2c0775c16e2a7e75fdabe283b6c028d284679abb", + "shell_clipboard_reader.exe": "01aad3988dfc520ec6419f9747cb2baa04efc9c1409f4b50a4a65b2f385bd7be", + "shell_ime_client.exe": "f02ec529d90db13bf4c904bbee8bc77930fdcf51ba868b4a43a43ed783f61830", + "shell_input_client.exe": "0075a332e524071809a1fdbd5161d1f360fa8e89faaf2b45b24c19f1d94829cc", + "shell_launch_test.exe": "6e96e163b4adc40b8d53c022a23a206176bf558c73eb76d9790b61f2abb13baf", + "shell_paste_probe.exe": "dd49d693dc30372d6f44cce2b7a1dcfc64adea1e6638399da4d400784a14bf6f", + "shell_query_client.exe": "691b440e9c500c76c1c09c448aafbe2316793bf341ffe69bbc2bf70e41be64cc", + "shell_queue_probe.exe": "e030070a6f287421bc3b74350fd45c8cd762837dc1512a76f688d20d39aa8715", + "shell_selection_text_client.exe": "18b60279aed48a9d39b6f5c57371c60b3a2b59bb21d6e30f92bac7adf50a3ed2", + "shell_size_client.exe": "eeb0801f69cc8add0a8178a185dcedb78a9b33234abfe5c600fd9b20b0f5aab4", + "shell_test_launcher.exe": "7bd78cffd78ac66d651933c9614d6be937c99baa105ce04c1a533df5785c11e2", + "shell_text_client.exe": "9350e53de74c9a6b78495a3b59788e3a31e0067a9c5ec1b8685a729ff18244d3", + "termforge_conpty_probe.exe": "1dcf69f65e9dd2abca1a58c10df2be3a296a4e22cdd895ba457222fe9e27f021", + "termforge_ime_probe.exe": "ba59ec28909430669c187a644651588b2f1984b1308b97056f222f332e7de9c9", + "termforge_shell.exe": "73472864e5aa291704917812ff1848f7a59be6e5f30c41f6a613e3fc18c740d4", + "terminal_trace.exe": "d09f34702cbd2d8be6ecfe98ab56a2326be1fde6b1fde4d10ef85d0f62955d2f", + "text_action_policy_test.exe": "9988026742d9ff45f300a6fa336ceca052a502b568f32114be7fd5057e57f5a4", + "transport_completion_test.exe": "6952438843e8a7605bb6cf0eb7add56dd8574f4e5edfbc3a534f795030a3fcc8", + "workflow_checkpoint_test.exe": "056047580e8ddd77224908667297a0b704f97ef57d1eadeb382b52c7c7875b86", + "workflow_fixture_test.exe": "ce3f09d8b3bb97d92dd6c61531e2cb9ba36070d2222b7b4ad7b86de82021f208", + "workspace_state_test.exe": "18080df77a0b7a370aa1e2dc79f2986dd3a4eb01661663dd95c43a11b802ee11" + } + }, + { + "id": "boundary-green", + "outcome": "green", + "outer_exit_code": 0, + "outer_timed_out": false, + "owned_job_after": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "owned_desktop_gone": true, + "writer": { + "records": 288, + "truncated": false, + "command_written_bytes": 93, + "command_error": 0 + }, + "frida": { + "evidence_id": "ev_5d9f4e9910aa1a8acaae8b86d66a0b214a83e3d88f94b2dc5a8e4b141c2169a9", + "event_count": 23, + "messages_truncated": false, + "provider": { + "id": "frida", + "name": "Frida", + "version": null + }, + "provider_limitations": [ + "Artifact identity is unavailable for this observation.", + "The target operating system and architecture were not reported by this observation." + ], + "script_limitations": [ + "calls already entered before attach are not observed", + "interception changes scheduling", + "timestamps are observer wall clock; no cross-process high resolution alignment" + ], + "script_sha256": "f01e1363d482ef404a7ef2031caf13434409f00dfe936b55fc3863c60628b9cb" + }, + "events": [ + { + "sequence": 2, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "69", + "resize_events": [] + }, + { + "sequence": 3, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 2048, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "69", + "resize_events": [] + }, + { + "sequence": 4, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "69", + "resize_events": [] + }, + { + "sequence": 5, + "type": "msys-read", + "thread": "T2", + "count": 1, + "requested": 4096, + "truncated": false, + "bytes_hex": "69" + }, + { + "sequence": 6, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "54", + "resize_events": [] + }, + { + "sequence": 7, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 16, + "requested": 2048, + "returned_count": 16, + "truncated": false, + "key_characters_hex": "54465f43414e4449444154455f545549", + "resize_events": [] + }, + { + "sequence": 8, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 16, + "requested": 16, + "returned_count": 16, + "truncated": false, + "key_characters_hex": "54465f43414e4449444154455f545549", + "resize_events": [] + }, + { + "sequence": 9, + "type": "msys-read", + "thread": "T2", + "count": 16, + "requested": 4096, + "truncated": false, + "bytes_hex": "54465f43414e4449444154455f545549" + }, + { + "sequence": 10, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 24 + } + ] + }, + { + "sequence": 11, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 95, + "requested": 2048, + "returned_count": 95, + "truncated": false, + "key_characters_hex": "0f3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 24 + }, + { + "position": 1, + "columns": 60, + "rows": 12 + } + ] + }, + { + "sequence": 12, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 24 + } + ] + }, + { + "sequence": 13, + "type": "console", + "thread": "T3", + "name": "ReadConsoleInputW", + "success": true, + "count": 94, + "requested": 2176, + "returned_count": 94, + "truncated": false, + "key_characters_hex": "0f3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 12 + } + ] + }, + { + "sequence": 14, + "type": "console", + "thread": "T3", + "name": "WriteConsoleInputW", + "success": true, + "count": 93, + "requested": 93, + "returned_count": 93, + "truncated": false, + "key_characters_hex": "0f3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [] + }, + { + "sequence": 15, + "type": "console", + "thread": "T3", + "name": "PeekConsoleInputW", + "success": true, + "count": 93, + "requested": 2176, + "returned_count": 93, + "truncated": false, + "key_characters_hex": "0f3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [] + }, + { + "sequence": 16, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "0f", + "resize_events": [] + }, + { + "sequence": 17, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 93, + "requested": 2048, + "returned_count": 93, + "truncated": false, + "key_characters_hex": "0f3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [] + }, + { + "sequence": 18, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 93, + "requested": 93, + "returned_count": 93, + "truncated": false, + "key_characters_hex": "0f3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [] + }, + { + "sequence": 19, + "type": "msys-read", + "thread": "T2", + "count": 93, + "requested": 4096, + "truncated": false, + "bytes_hex": "0f3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d" + }, + { + "sequence": 20, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "truncated": false, + "key_characters_hex": "0f", + "resize_events": [] + }, + { + "sequence": 21, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 5, + "requested": 2048, + "returned_count": 5, + "truncated": false, + "key_characters_hex": "0f3a77710d", + "resize_events": [] + }, + { + "sequence": 22, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 5, + "requested": 5, + "returned_count": 5, + "truncated": false, + "key_characters_hex": "0f3a77710d", + "resize_events": [] + }, + { + "sequence": 23, + "type": "msys-read", + "thread": "T2", + "count": 5, + "requested": 4096, + "truncated": false, + "bytes_hex": "0f3a77710d" + } + ], + "private_artifact_sha256": { + "sample.json": "ff2fef363f302558772b9023862e77a5331b682768f214dfa8ab40655c6e1ab9", + "r52-writes.json": "4e4d6993b54463236bcba0e9beec30e58a65aa8718d7367f149127741ed5c532", + "candidate-input.bin": "4bd689a84f1eb7336afb3b607dec760d117caef3d83f3e8e1b6b1f83d70b4b68", + "candidate-insert-log.txt": "3d07108bcc44f0831b31530265fac711c1d1e69194aa9309897e4440c26de65a", + "candidate-resize-log.txt": "2d3db728e657a0e1ab99b23acb6b2d08c586c4eaba6e65a66ab65e7fb47693b7", + "candidate-output.bin": "fd4684c9af94212afbbc3d0274c03dfee4797125ec3c35a71f005794ff4d8235", + "case-2-identity.json": "625d8ec842d1024ea9b2bc35194fc0a2b8cf0667dcce5ae8a1c5a381ca351074", + "case-2-status-final.json": "96e4e0dec59e7b911b5a4cee13583fdf3ce49551261819a9b04616e99a69ff59", + "case-2-load.json": "2d19cccb2ff6833cf6dc1d2a903a2529ec89389594b2b6b3de478db3bd76ec55", + "case-2-detach.json": "b53d8b7832810f75d86584dbc495295037f01103e868dcf359d1be54012afdc5" + }, + "observed_file_sha256": { + "alpha_normal_smoke.exe": "55f76e0ef0a68a2d3ee2d047852551b86e63074b84604e59fe6d0893361ba3a6", + "candidate_core_test.exe": "4c73b58d7da111ab52b6edbbb59898cf660860fdedaa64c4f49b46fae856a02d", + "candidate_reply_close_test.exe": "7431b2d5a414703d85fb9425b07b7df125eb7b5668851cb5383cf575cb620a69", + "candidate_resize_replay.exe": "c4cf0d9f68bb5bd688e601b87025f3237c8659f4d80e96bd4f67fa463463a13f", + "candidate_title_test.exe": "359141b75c6eeb9a0cc9ef59f9f21ad0d07bcaefb51beb19f64e3288a059bec2", + "clipboard_policy_test.exe": "ef7c7d860bad3b458b7ac8e659c46acd0f768f70281e7d6797e489520d1d559e", + "conpty_backend_probe.exe": "39b52ad4acc4e432718d3cdd6dc6ee2ab80b1584de16c0edc3d775ccc7b03c0d", + "conpty_ctrl_c_probe.exe": "30ad460b54cfcc752e20ccd5f656c6769e292528408a4a68e7182ab3206d39b6", + "conpty_margin_probe.exe": "7aaef1da461b3b6d833aea79332f28d8f924e26de2f9332feeb1f1303a51e30f", + "conpty_resize_native_probe.exe": "a570b55f7ef0f65be01b827112905a223fb32f37a0f645ce121ead1ed6555f59", + "core_measure.exe": "dfe74cf8e6ec0fdb976911569c14e52709be5e880d9b36d2a108187bca9c165b", + "find_bar_test.exe": "632e5ff486b7ad5d29292b4af1206c196d09ca1c1c8a38a830a004ee9c21f1a1", + "history_test.exe": "3b3f6199ad4d345ef5cc759ddcf80667fdde4c7778658e99764342cc17949e2e", + "ime_context_test.exe": "8941d52ed7642b15c46da5a50927ba09ceab7078c5f31c649e4e3baa859e8ae1", + "ime_input_expectation_test.exe": "a960fbd62a7c0d6eec564a5a2bb1cc271582e7bb55ec07ddb63bc46044b9c438", + "keyboard_test.exe": "fe027bbcb21a09fac9411dd5766a37e8edd711d3ef547f37a080ded097a4de3e", + "layout_state_test.exe": "7b174db85ff75e8a8f5613c949c264a4dedbb8cfeb8a18581f016191bd036ddb", + "paste_test.exe": "1e6acc382a75fe18b50212cf6950d3d714f89939b1cf63000795348e4db22d54", + "presentation_probe.exe": "91aa34c2ac7b4f6610da419a04091d4391cb78e224e6b5f567437a5565d4e33a", + "raw_console_receiver_test.exe": "0731af92b2536b189d80894b5ca4243d814e07ab2c513fff7b4d3fd3654578f2", + "real_app_capture.exe": "fddd86a02d7c8ba13b7cd458373c8acd19d999199baefce77e8f237efce47189", + "receive.exe": "b5ea1d10bba964ec80f183d761b1c0ea4334f6963597ccd1bb16b9c769fc76d5", + "recovery_probe.exe": "72213828da26b5657915667faccd1d0aca83dbe6d9537f38dbd9d5464750313b", + "renderer_metrics_test.exe": "f08e88f6ff8bb6be87f724ff6a3c398df359a639f83d75b5970e69659f8a4a5c", + "replay.exe": "54298c3fa5020d3ebcaa55c76564b9dcbfec5b25d772343c428e2e36e1858ca1", + "resize_test.exe": "fd97c87127d4763320832d1d538af5755eece8a1ec1485435cab6b41e9e549cd", + "runtime_observation_test.exe": "0049a0d863724bf8bd181c85868bd68a409eb5e12c755a5538add5f85bf8b0bf", + "scenario_tick_gate_test.exe": "f7368d2d4c13a9eee4a210e857fd5d13479641328fa71a984582c69b57735ecf", + "search_test.exe": "6bf2a5fa47e1f0665462e6601e01253674c6662e5db730161bd7eb09c81d621d", + "selection_test.exe": "19eaf7ef3e6f1a35f67e1b17a60858bfb0f268a20c9f2686d141c847a68fbb3a", + "sender_fence_pipe_test.exe": "19bba0df9e37159cc70728f68ae8a28364127fac21ab72cb21cdb036f9380686", + "sender_fence_test.exe": "b3c396a7d8a306d0987ef149fd9d5e014ab90ac6d54d0915a48b6d261a6e21c1", + "sent_message_wait_test.exe": "b9bb4960dc470d5aac08fecb2c0775c16e2a7e75fdabe283b6c028d284679abb", + "shell_clipboard_reader.exe": "01aad3988dfc520ec6419f9747cb2baa04efc9c1409f4b50a4a65b2f385bd7be", + "shell_ime_client.exe": "f02ec529d90db13bf4c904bbee8bc77930fdcf51ba868b4a43a43ed783f61830", + "shell_input_client.exe": "0075a332e524071809a1fdbd5161d1f360fa8e89faaf2b45b24c19f1d94829cc", + "shell_launch_test.exe": "6e96e163b4adc40b8d53c022a23a206176bf558c73eb76d9790b61f2abb13baf", + "shell_paste_probe.exe": "dd49d693dc30372d6f44cce2b7a1dcfc64adea1e6638399da4d400784a14bf6f", + "shell_query_client.exe": "691b440e9c500c76c1c09c448aafbe2316793bf341ffe69bbc2bf70e41be64cc", + "shell_queue_probe.exe": "e030070a6f287421bc3b74350fd45c8cd762837dc1512a76f688d20d39aa8715", + "shell_selection_text_client.exe": "18b60279aed48a9d39b6f5c57371c60b3a2b59bb21d6e30f92bac7adf50a3ed2", + "shell_size_client.exe": "eeb0801f69cc8add0a8178a185dcedb78a9b33234abfe5c600fd9b20b0f5aab4", + "shell_test_launcher.exe": "7bd78cffd78ac66d651933c9614d6be937c99baa105ce04c1a533df5785c11e2", + "shell_text_client.exe": "9350e53de74c9a6b78495a3b59788e3a31e0067a9c5ec1b8685a729ff18244d3", + "termforge_conpty_probe.exe": "1dcf69f65e9dd2abca1a58c10df2be3a296a4e22cdd895ba457222fe9e27f021", + "termforge_ime_probe.exe": "ba59ec28909430669c187a644651588b2f1984b1308b97056f222f332e7de9c9", + "termforge_shell.exe": "73472864e5aa291704917812ff1848f7a59be6e5f30c41f6a613e3fc18c740d4", + "terminal_trace.exe": "d09f34702cbd2d8be6ecfe98ab56a2326be1fde6b1fde4d10ef85d0f62955d2f", + "text_action_policy_test.exe": "9988026742d9ff45f300a6fa336ceca052a502b568f32114be7fd5057e57f5a4", + "transport_completion_test.exe": "6952438843e8a7605bb6cf0eb7add56dd8574f4e5edfbc3a534f795030a3fcc8", + "workflow_checkpoint_test.exe": "056047580e8ddd77224908667297a0b704f97ef57d1eadeb382b52c7c7875b86", + "workflow_fixture_test.exe": "ce3f09d8b3bb97d92dd6c61531e2cb9ba36070d2222b7b4ad7b86de82021f208", + "workspace_state_test.exe": "18080df77a0b7a370aa1e2dc79f2986dd3a4eb01661663dd95c43a11b802ee11" + } + }, + { + "id": "symbol-red", + "outcome": "stage59", + "outer_exit_code": 2, + "outer_timed_out": false, + "owned_job_after": { + "active": 0, + "total": 7, + "terminated": 0 + }, + "owned_desktop_gone": true, + "writer": { + "records": 91, + "truncated": false, + "command_written_bytes": 93, + "command_error": 0 + }, + "frida": { + "evidence_id": "ev_d9ec8dd6c39ad99e42d4c2ada98b9546a47e9550b5c026eaf59ef80c35ccd485", + "event_count": 27, + "messages_truncated": false, + "provider": { + "id": "frida", + "name": "Frida", + "version": null + }, + "provider_limitations": [ + "Artifact identity is unavailable for this observation.", + "The target operating system and architecture were not reported by this observation." + ], + "script_limitations": [ + "calls already entered before attach are not observed", + "interception changes scheduling", + "timestamps are observer wall clock; no cross-process high resolution alignment" + ], + "script_sha256": "a33709df621e376ede656f88dd85d0c04dfb7c578c71a3ebee9dbd047dbd23bd" + }, + "events": [ + { + "sequence": 2, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "inside_process": false, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "69", + "resize_events": [], + "caller_symbol": "_ZL12peek_consoleP13select_recordb", + "caller_rva": "0x76ecb", + "caller_offset": 667 + }, + { + "sequence": 3, + "type": "process-enter", + "thread": "T1", + "inside_process": true, + "inside_winch": false + }, + { + "sequence": 4, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 2048, + "returned_count": 1, + "inside_process": true, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "69", + "resize_events": [], + "caller_symbol": "_ZN16fhandler_console21process_input_messageEv", + "caller_rva": "0xc7a50", + "caller_offset": 128 + }, + { + "sequence": 5, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "inside_process": true, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "69", + "resize_events": [], + "caller_symbol": "_ZN16fhandler_console21process_input_messageEv", + "caller_rva": "0xc7bfb", + "caller_offset": 555 + }, + { + "sequence": 6, + "type": "process-leave", + "thread": "T1", + "result": 1, + "inside_process": true, + "inside_winch": false + }, + { + "sequence": 7, + "type": "msys-read", + "thread": "T2", + "count": 1, + "requested": 4096, + "inside_process": false, + "inside_winch": false, + "truncated": false, + "bytes_hex": "69" + }, + { + "sequence": 8, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "inside_process": false, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "54", + "resize_events": [], + "caller_symbol": "_ZL12peek_consoleP13select_recordb", + "caller_rva": "0x76ecb", + "caller_offset": 667 + }, + { + "sequence": 9, + "type": "process-enter", + "thread": "T1", + "inside_process": true, + "inside_winch": false + }, + { + "sequence": 10, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 16, + "requested": 2048, + "returned_count": 16, + "inside_process": true, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "54465f43414e4449444154455f545549", + "resize_events": [], + "caller_symbol": "_ZN16fhandler_console21process_input_messageEv", + "caller_rva": "0xc7a50", + "caller_offset": 128 + }, + { + "sequence": 11, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 16, + "requested": 16, + "returned_count": 16, + "inside_process": true, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "54465f43414e4449444154455f545549", + "resize_events": [], + "caller_symbol": "_ZN16fhandler_console21process_input_messageEv", + "caller_rva": "0xc7bfb", + "caller_offset": 555 + }, + { + "sequence": 12, + "type": "process-leave", + "thread": "T1", + "result": 1, + "inside_process": true, + "inside_winch": false + }, + { + "sequence": 13, + "type": "msys-read", + "thread": "T2", + "count": 16, + "requested": 4096, + "inside_process": false, + "inside_winch": false, + "truncated": false, + "bytes_hex": "54465f43414e4449444154455f545549" + }, + { + "sequence": 14, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "inside_process": false, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 24 + } + ], + "caller_symbol": "_ZL12peek_consoleP13select_recordb", + "caller_rva": "0x76ecb", + "caller_offset": 667 + }, + { + "sequence": 15, + "type": "process-enter", + "thread": "T1", + "inside_process": true, + "inside_winch": false + }, + { + "sequence": 16, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 2, + "requested": 2048, + "returned_count": 2, + "inside_process": true, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 24 + }, + { + "position": 1, + "columns": 60, + "rows": 12 + } + ], + "caller_symbol": "_ZN16fhandler_console21process_input_messageEv", + "caller_rva": "0xc7a50", + "caller_offset": 128 + }, + { + "sequence": 17, + "type": "winch-enter", + "thread": "T1", + "inside_process": true, + "inside_winch": true + }, + { + "sequence": 18, + "type": "console", + "thread": "T3", + "name": "ReadConsoleInputW", + "success": true, + "count": 2, + "requested": 2176, + "returned_count": 2, + "inside_process": false, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "", + "resize_events": [ + { + "position": 0, + "columns": 60, + "rows": 24 + }, + { + "position": 1, + "columns": 60, + "rows": 12 + } + ], + "caller_symbol": "_ZN16fhandler_console18cons_master_threadEPN16fhandler_termios17cons_handle_set_tEP3tty", + "caller_rva": "0xc0520", + "caller_offset": 480 + }, + { + "sequence": 19, + "type": "winch-leave", + "thread": "T1", + "result": 1, + "inside_process": true, + "inside_winch": true + }, + { + "sequence": 20, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "inside_process": true, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "0f", + "resize_events": [], + "caller_symbol": "_ZN16fhandler_console21process_input_messageEv", + "caller_rva": "0xc7bfb", + "caller_offset": 555 + }, + { + "sequence": 21, + "type": "process-leave", + "thread": "T1", + "result": 3, + "inside_process": true, + "inside_winch": false + }, + { + "sequence": 22, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 1, + "requested": 1, + "returned_count": 1, + "inside_process": false, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "3a", + "resize_events": [], + "caller_symbol": "_ZL12peek_consoleP13select_recordb", + "caller_rva": "0x76ecb", + "caller_offset": 667 + }, + { + "sequence": 23, + "type": "process-enter", + "thread": "T1", + "inside_process": true, + "inside_winch": false + }, + { + "sequence": 24, + "type": "console", + "thread": "T1", + "name": "PeekConsoleInputW", + "success": true, + "count": 92, + "requested": 2048, + "returned_count": 92, + "inside_process": true, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [], + "caller_symbol": "_ZN16fhandler_console21process_input_messageEv", + "caller_rva": "0xc7a50", + "caller_offset": 128 + }, + { + "sequence": 25, + "type": "console", + "thread": "T1", + "name": "ReadConsoleInputW", + "success": true, + "count": 92, + "requested": 92, + "returned_count": 92, + "inside_process": true, + "inside_winch": false, + "truncated": false, + "key_characters_hex": "3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d", + "resize_events": [], + "caller_symbol": "_ZN16fhandler_console21process_input_messageEv", + "caller_rva": "0xc7bfb", + "caller_offset": 555 + }, + { + "sequence": 26, + "type": "process-leave", + "thread": "T1", + "result": 1, + "inside_process": true, + "inside_winch": false + }, + { + "sequence": 27, + "type": "msys-read", + "thread": "T2", + "count": 92, + "requested": 4096, + "inside_process": false, + "inside_winch": false, + "truncated": false, + "bytes_hex": "3a63616c6c20777269746566696c65285b737472696e672826636f6c756d6e7329202e20277827202e20737472696e6728266c696e6573295d2c202763616e6469646174652d726573697a652d6c6f672e747874272c20276227290d" + } + ], + "private_artifact_sha256": { + "sample.json": "323fd063cf25c12a79843b434d912070143b1b757fcdd4259c0c238764a56037", + "r52-writes.json": "da113f75b2f36563a7cf4ea3597afe800f15c6775707a7d3b9f7e4257fc29f90", + "candidate-input.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "candidate-insert-log.txt": "e6de4daeba1841783e7e22f81dbf87154f341c309dc5b54405e5fc2c2f3550a6", + "candidate-resize-log.txt": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "candidate-output.bin": "fc43e5c356005f9c32847476d4bdba5fabaf51439083d8ac4e530d3e8bb5c883", + "case-0-identity.json": "35ca331419b51f084db61cb550010eb64bfa90679beaac15d5247945184f07ab", + "case-0-status-final.json": "4238c82e9cd6c94747b5b3b5faa4134dc320e63534ba889526ae466467adabbd", + "case-0-load.json": "97d63987cc45685444eb3be50d7e21c22fbbbec1a97096073bba325b9eb4a77f", + "case-0-detach.json": "b53d8b7832810f75d86584dbc495295037f01103e868dcf359d1be54012afdc5" + }, + "observed_file_sha256": { + "alpha_normal_smoke.exe": "55f76e0ef0a68a2d3ee2d047852551b86e63074b84604e59fe6d0893361ba3a6", + "candidate_core_test.exe": "4c73b58d7da111ab52b6edbbb59898cf660860fdedaa64c4f49b46fae856a02d", + "candidate_reply_close_test.exe": "7431b2d5a414703d85fb9425b07b7df125eb7b5668851cb5383cf575cb620a69", + "candidate_resize_replay.exe": "c4cf0d9f68bb5bd688e601b87025f3237c8659f4d80e96bd4f67fa463463a13f", + "candidate_title_test.exe": "359141b75c6eeb9a0cc9ef59f9f21ad0d07bcaefb51beb19f64e3288a059bec2", + "clipboard_policy_test.exe": "ef7c7d860bad3b458b7ac8e659c46acd0f768f70281e7d6797e489520d1d559e", + "conpty_backend_probe.exe": "39b52ad4acc4e432718d3cdd6dc6ee2ab80b1584de16c0edc3d775ccc7b03c0d", + "conpty_ctrl_c_probe.exe": "30ad460b54cfcc752e20ccd5f656c6769e292528408a4a68e7182ab3206d39b6", + "conpty_margin_probe.exe": "7aaef1da461b3b6d833aea79332f28d8f924e26de2f9332feeb1f1303a51e30f", + "conpty_resize_native_probe.exe": "a570b55f7ef0f65be01b827112905a223fb32f37a0f645ce121ead1ed6555f59", + "core_measure.exe": "dfe74cf8e6ec0fdb976911569c14e52709be5e880d9b36d2a108187bca9c165b", + "find_bar_test.exe": "632e5ff486b7ad5d29292b4af1206c196d09ca1c1c8a38a830a004ee9c21f1a1", + "history_test.exe": "3b3f6199ad4d345ef5cc759ddcf80667fdde4c7778658e99764342cc17949e2e", + "ime_context_test.exe": "8941d52ed7642b15c46da5a50927ba09ceab7078c5f31c649e4e3baa859e8ae1", + "ime_input_expectation_test.exe": "a960fbd62a7c0d6eec564a5a2bb1cc271582e7bb55ec07ddb63bc46044b9c438", + "keyboard_test.exe": "fe027bbcb21a09fac9411dd5766a37e8edd711d3ef547f37a080ded097a4de3e", + "layout_state_test.exe": "7b174db85ff75e8a8f5613c949c264a4dedbb8cfeb8a18581f016191bd036ddb", + "paste_test.exe": "1e6acc382a75fe18b50212cf6950d3d714f89939b1cf63000795348e4db22d54", + "presentation_probe.exe": "91aa34c2ac7b4f6610da419a04091d4391cb78e224e6b5f567437a5565d4e33a", + "raw_console_receiver_test.exe": "0731af92b2536b189d80894b5ca4243d814e07ab2c513fff7b4d3fd3654578f2", + "real_app_capture.exe": "fddd86a02d7c8ba13b7cd458373c8acd19d999199baefce77e8f237efce47189", + "receive.exe": "b5ea1d10bba964ec80f183d761b1c0ea4334f6963597ccd1bb16b9c769fc76d5", + "recovery_probe.exe": "72213828da26b5657915667faccd1d0aca83dbe6d9537f38dbd9d5464750313b", + "renderer_metrics_test.exe": "f08e88f6ff8bb6be87f724ff6a3c398df359a639f83d75b5970e69659f8a4a5c", + "replay.exe": "54298c3fa5020d3ebcaa55c76564b9dcbfec5b25d772343c428e2e36e1858ca1", + "resize_test.exe": "fd97c87127d4763320832d1d538af5755eece8a1ec1485435cab6b41e9e549cd", + "runtime_observation_test.exe": "0049a0d863724bf8bd181c85868bd68a409eb5e12c755a5538add5f85bf8b0bf", + "scenario_tick_gate_test.exe": "f7368d2d4c13a9eee4a210e857fd5d13479641328fa71a984582c69b57735ecf", + "search_test.exe": "6bf2a5fa47e1f0665462e6601e01253674c6662e5db730161bd7eb09c81d621d", + "selection_test.exe": "19eaf7ef3e6f1a35f67e1b17a60858bfb0f268a20c9f2686d141c847a68fbb3a", + "sender_fence_pipe_test.exe": "19bba0df9e37159cc70728f68ae8a28364127fac21ab72cb21cdb036f9380686", + "sender_fence_test.exe": "b3c396a7d8a306d0987ef149fd9d5e014ab90ac6d54d0915a48b6d261a6e21c1", + "sent_message_wait_test.exe": "b9bb4960dc470d5aac08fecb2c0775c16e2a7e75fdabe283b6c028d284679abb", + "shell_clipboard_reader.exe": "771da921ef4d30a7e710989ea0d4446254fc4e85c59199730704edf2256bb641", + "shell_ime_client.exe": "f02ec529d90db13bf4c904bbee8bc77930fdcf51ba868b4a43a43ed783f61830", + "shell_input_client.exe": "0075a332e524071809a1fdbd5161d1f360fa8e89faaf2b45b24c19f1d94829cc", + "shell_launch_test.exe": "6e96e163b4adc40b8d53c022a23a206176bf558c73eb76d9790b61f2abb13baf", + "shell_paste_probe.exe": "dd49d693dc30372d6f44cce2b7a1dcfc64adea1e6638399da4d400784a14bf6f", + "shell_query_client.exe": "691b440e9c500c76c1c09c448aafbe2316793bf341ffe69bbc2bf70e41be64cc", + "shell_queue_probe.exe": "e030070a6f287421bc3b74350fd45c8cd762837dc1512a76f688d20d39aa8715", + "shell_selection_text_client.exe": "18b60279aed48a9d39b6f5c57371c60b3a2b59bb21d6e30f92bac7adf50a3ed2", + "shell_size_client.exe": "eeb0801f69cc8add0a8178a185dcedb78a9b33234abfe5c600fd9b20b0f5aab4", + "shell_test_launcher.exe": "7bd78cffd78ac66d651933c9614d6be937c99baa105ce04c1a533df5785c11e2", + "shell_text_client.exe": "9350e53de74c9a6b78495a3b59788e3a31e0067a9c5ec1b8685a729ff18244d3", + "termforge_conpty_probe.exe": "1dcf69f65e9dd2abca1a58c10df2be3a296a4e22cdd895ba457222fe9e27f021", + "termforge_ime_probe.exe": "ba59ec28909430669c187a644651588b2f1984b1308b97056f222f332e7de9c9", + "termforge_shell.exe": "73472864e5aa291704917812ff1848f7a59be6e5f30c41f6a613e3fc18c740d4", + "terminal_trace.exe": "d09f34702cbd2d8be6ecfe98ab56a2326be1fde6b1fde4d10ef85d0f62955d2f", + "text_action_policy_test.exe": "9988026742d9ff45f300a6fa336ceca052a502b568f32114be7fd5057e57f5a4", + "transport_completion_test.exe": "6952438843e8a7605bb6cf0eb7add56dd8574f4e5edfbc3a534f795030a3fcc8", + "workflow_checkpoint_test.exe": "056047580e8ddd77224908667297a0b704f97ef57d1eadeb382b52c7c7875b86", + "workflow_fixture_test.exe": "ce3f09d8b3bb97d92dd6c61531e2cb9ba36070d2222b7b4ad7b86de82021f208", + "workspace_state_test.exe": "18080df77a0b7a370aa1e2dc79f2986dd3a4eb01661663dd95c43a11b802ee11" + } + } + ], + "restoration": { + "source_bytes_equal_baseline": true, + "rebuild_completed": true, + "rebuild_log_sha256": "1e9970789882be9fec43ca72b65f8b5329b0aa7e5a93dbe87627a2c2df7e415d", + "rebuilt_shell_sha256": "b061934e08561a7bc2e0dc2773412ef3d900e2728267ce1d5c54be5ca6ad8515", + "same_binary_as_pre_probe": false, + "new_regression_run_claimed": false + }, + "conclusions": { + "observed": "Original command reaches native input. On red runs process_input_message consumes Ctrl-O after another thread removes the earlier resize events; read(0) returns only the following 92 bytes. Diagnostic green returns all 93 bytes.", + "source_inference": "send_winch_maybe temporarily releases input_mutex; the saved PeekConsoleInput snapshot can become stale before discard. Function-boundary observation confirms the corresponding cross-thread order, not a tested patch.", + "hypotheses": { + "lost_before_native": "contradicted in these captured runs", + "lost_in_msys_console_to_read": "observed in three captured red runs", + "vim_ignores_received_control": "not the boundary of these captured failures" + }, + "product_fixed": false, + "fixed_target_replacement_or_runtime_upgrade_tested": false, + "unobserved_historical_runs_attributed": false + }, + "limitations": [ + "Frida installs code hooks; instrumentation and initial attachment gate alter scheduling; no performance conclusion.", + "No fixed application update, no production workaround, no controlled patch red-to-green; issue 52 remains open.", + "Provider did not supply artifact identity, OS/architecture or version; local file hashes and script-reported identity/function-prefix checks are separate evidence.", + "Calls already entered before attach are outside coverage; the original i and marker are captured before the failing command.", + "Threads are aliases within each run; observer wall-clock intervals are not cross-process timing measurements.", + "A fresh source rebuild has a different executable hash from the pre-probe binary; no new full regression is claimed.", + "No private raw captures, local absolute paths, PIDs, native addresses or binary prefixes are published." + ] +} diff --git a/experiments/shell/results/2026-10-27-vim-msys-runtime-prototype/validation.json b/experiments/shell/results/2026-10-27-vim-msys-runtime-prototype/validation.json new file mode 100644 index 00000000..697e6029 --- /dev/null +++ b/experiments/shell/results/2026-10-27-vim-msys-runtime-prototype/validation.json @@ -0,0 +1,655 @@ +{ + "schema_version": 1, + "purpose": "isolated source-rebuilt MSYS runtime prototype for issue 52", + "status": "rebuilt baseline reproduced stage59; patched runtime passed four original GUI cases; original fixed-runtime acceptance remains open", + "termforge": { + "source_commit": "ed55a8758423b3273fbd98f07e0bfd77d88e46ac", + "product_code_same_as": "c662011", + "product_and_test_changes": false, + "binary_sha256": "7f26395942f107297565f50402568650e1e17c5eb23924bee1bfb02c50c60d16", + "candidate": "0.26.1-dev+termforge.5", + "backend": "reviewed-side-by-side-1.25.260930003", + "conpty_flags": 24, + "source_files_sha256": { + "experiments/shell/shell.cpp": "b4f6fe98812d1d23fa5f5bbcc7376437404af6cdb67cd6cdcdf448b57cb31fe9", + "experiments/shell/test_candidate_tui.py": "a300380b97ad424c5af06219a021a256de6178c3af7fb7e933e24bb8142201ee", + "experiments/shell/run.py": "45976b828eecb316881d698dedaef09ad304f72d991dfa360f1c552bbcbe9f1d" + } + }, + "environment": { + "os_version_observed_after_batch": [ + "11", + "10.0.26200", + "SP0", + "Multiprocessor Free" + ], + "architecture": "x64", + "personal_installation_unchanged_hash_check": true, + "fixture_files_original_sha256": { + "vim.exe": "2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a", + "msys-ncursesw6.dll": "73ff8bc8b1f515e57b185e57d25d9ad29067057ff0819a8e588677c65ca61629", + "msys-2.0.dll": "3096d2ba9be20e90be1399edd391bac65d84bc387f5a419b502c9b326081930f", + "msys-intl-8.dll": "23d30f4bcbc0a258cf5ecfe29e707f9b05d51c32d5b16a910f1012b889136c12", + "msys-iconv-2.dll": "9cbf5613b946be7bad403ac0d7277efbd514a35faa644c6a0d6e7f6cc0b40780" + }, + "same_active_fixture_path_across_variants": true, + "fixture_runtime_files_not_copied": true, + "loaded_module_paths_observed": false, + "diagnostic_instrumentation": false + }, + "runtime": { + "source_commit": "2e2ef940151485490aff0c7b00c00965ef7a71a4", + "original_dll_sha256": "3096d2ba9be20e90be1399edd391bac65d84bc387f5a419b502c9b326081930f", + "sdk_commit": "36b102843d99c4993084b29d839dc6f5ef4db2f9", + "headers_commit": "eb069c08fe8ef4d2f19dc6b99c4816a4c51fd2ae", + "win32_headers": "msys2-w32api-headers 12.0.0.r0.g819a6ec2e-1; both w32api-prefixed and bare paths pinned", + "c_compiler": "gcc 16.2.0, GNU17, -Wno-error=old-style-definition", + "cxx_compiler": "g++ 16.2.0, GNU++17, -O2 -g -Wno-error -Wno-narrowing -Wno-use-after-free", + "build_script_sha256": "13d47fb56cec438c591861e996b2954214869c541d3b2540e623912c82844fa9", + "packages_sha256": "0c10cee128f4d02add7fd28c9e5d4df6035c3cddca2a80cdc8c15600eade5f67", + "package_inventory_path": "research/msys-runtime-window-prefix/build-packages.txt", + "selected_build_packages": { + "gcc": "16.2.0-1", + "binutils": "2.47-1", + "make": "4.4.1-3", + "cocom": "0.996-6", + "zlib-devel": "1.3.2-1" + }, + "build_object_count": 310, + "changed_build_object_count": 6, + "changed_build_objects": { + "fhandler/console.o": { + "base": "308e4d37447fd48dbb4b06b511abf5ffe16ccd0899bea8e08506fd765ef9867b", + "window-prefix": "06a2a4519d4f7fd74f240aae0fc91b262bbafceff36f2b4414928e8a59e47b27" + }, + "libdll.a": { + "base": "6c12fadc029325b2f683ccaa1a391a2ba50c385a2bd36fc0cba1e3065285d57c", + "window-prefix": "03c56b71231c3f8b224c10009531a94baad6c5ebd8a2bcdfb92efa03b61c7b80" + }, + "msysdll.a": { + "base": "e8e3b40bbcc8de7f7b266c57e7d64e9f147f71d1fcfcb9bff00f0126f859f14a", + "window-prefix": "f3768c0c931ba35c3050a23cb062c2a39a1ed93410e8e06ee6722076b3d142fc" + }, + "new-msys-2.0.dll": { + "base": "d9e490fdbe31c233eab6578168fb4f2bda84f53252be3898d0cdd0e8610db721", + "window-prefix": "dd792766fa976b031cbff0540744b6f3d2b3fc0d695ed764a01457b65eab6258" + }, + "version.o": { + "base": "3a669b9dac2aed0a7619052d974d5819e48846d1af3fa8de41bc1cda9da7efd5", + "window-prefix": "51bc7e867867b7eec6694f40618f13ebf66eb8317a8e7ff22b71342b18ef836a" + }, + "winver.o": { + "base": "b5cd6766287393c78274b5c16a85ac94009cbec3603096d94cf6533642ca584b", + "window-prefix": "c52d7f4fe56736b0a55f2b4f32676efd0a0f7786933731185f5bc0b60e45e24f" + } + }, + "variants": { + "base": { + "dll_sha256": "d9e490fdbe31c233eab6578168fb4f2bda84f53252be3898d0cdd0e8610db721", + "console_source_sha256": "00d73637398314b74b81f6fabf4bd32176056dd088ce825f4546b2c4b38726b8", + "source_status": "", + "private_identity_sha256": "c8f08c4544fa76a1355900000dbe34cdc5676c7f183f853a6c8aa55fcf6c15d3" + }, + "window-prefix": { + "dll_sha256": "dd792766fa976b031cbff0540744b6f3d2b3fc0d695ed764a01457b65eab6258", + "console_source_sha256": "5d2b1b4c1fecefa15518d8e3ff6a375a5a00f8c91fee0267a96a1c4e08eb3fc1", + "source_status": " M winsup/cygwin/fhandler/console.cc\n", + "private_identity_sha256": "be5d87b794b709ad4dd63bc9ea1a6cb594e99ffd74b92a2b5f531556072755d0" + } + } + }, + "patch": { + "path": "research/msys-runtime-window-prefix/window-prefix.patch", + "sha256": "e942c2d08b7cc5a294203bf0a52f7414b9b19f3cd675624644feadf907418c77", + "changed_source_file": "winsup/cygwin/fhandler/console.cc", + "insertions": 12, + "deletions": 6, + "apply_check_and_resulting_source_hash_verified": true, + "license": "LGPL-3.0-or-later with upstream CYGWIN_LICENSE linking exception", + "license_files_sha256": { + "CYGWIN_LICENSE": "794433752103cf4bbb4a84a1bdb8fbc150abb1762704bb35fecc9f7f820be984", + "COPYING.LIB": "da7eabb7bafdf7d3ae5e9f223aa5bdc1eece45ac569dc21b3b037520b4464768", + "COPYING": "8ceb4b9ee5adedde47b31e975c1d90c73ad27b6b165a1dcd80c7c545eb65b903" + } + }, + "method": { + "name": "CandidateTuiTests.test_real_fullscreen_editor_and_cursor_reply", + "original_repetitions": 2, + "inner_timeout_seconds": 25, + "outer_timeout_seconds_per_case": 40, + "max_methods": { + "original": 1, + "base": 2, + "window-prefix": 2 + }, + "stops": [ + "Original relocated DLL: one method only.", + "Rebuilt base: stop on first stage59 or other failure.", + "Patched: run only after rebuilt stage59; stop on any failure or after two methods.", + "Do not switch runtime until all preceding owned Jobs are inactive and desktops gone." + ], + "actual_methods": 4, + "actual_cases": 7, + "passed_cases": 5, + "stage59_cases": 2, + "patched_passed_cases": 4, + "no_extra_gui_runs_during_export": true + }, + "methods": [ + { + "id": "original-method-0", + "variant": "original", + "started_utc": "2026-10-27T00:43:09.383669+00:00", + "runtime_sha256": "3096d2ba9be20e90be1399edd391bac65d84bc387f5a419b502c9b326081930f", + "outcome": "stage59", + "test_success": false, + "tests_run": 1, + "skips": 0, + "original_assertions_offline_rechecked_for_successful_cases": true, + "samples": [ + { + "index": 0, + "status": "passed", + "failure_stage": null, + "exit_code": 0, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "candidate_tui_start": true, + "candidate_tui": true, + "candidate_resize": true, + "candidate_replies": true, + "command_verified": true, + "exit_code": 37, + "active_children_after": 0, + "cleanup_complete": true, + "input_rejected": 0, + "columns": 60, + "rows": 12, + "presented_done": null + }, + "files": { + "candidate-input.bin": { + "bytes": 115, + "sha256": "4bd689a84f1eb7336afb3b607dec760d117caef3d83f3e8e1b6b1f83d70b4b68" + }, + "candidate-insert-log.txt": { + "bytes": 58, + "sha256": "3d07108bcc44f0831b31530265fac711c1d1e69194aa9309897e4440c26de65a" + }, + "candidate-resize-log.txt": { + "bytes": 5, + "sha256": "2d3db728e657a0e1ab99b23acb6b2d08c586c4eaba6e65a66ab65e7fb47693b7" + }, + "candidate-startup-log.txt": { + "bytes": 6, + "sha256": "3dfa3fb239f56c778a1e9b33eb31328349edd155a95806dac4378904e88527e5" + }, + "candidate-vim.vim": { + "bytes": 334, + "sha256": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797" + }, + "candidate-tui.txt": { + "bytes": 17, + "sha256": "d9d3b8d10b660a808ece2b2c46b3767713ba4e74631c56b376b492eadf511dec" + }, + "candidate-replies.bin": { + "bytes": 7, + "sha256": "39cb2fc06ace6ef92a7015a38ab2e7fe1f03a846af0313e27ec7cdceca6643ff" + }, + "sample.json": { + "bytes": 6004, + "sha256": "1da51ee58c0342cc6c35ece841fe5ceb2c53d34886cc58f142eb2ea3665b584d" + } + }, + "empty_input_file_is_not_a_complete_input_trace": false + }, + { + "index": 1, + "status": "failed", + "failure_stage": 59, + "exit_code": 2, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 7, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 7, + "terminated": 0 + }, + "desktop_gone": true, + "result": null, + "files": { + "candidate-input.bin": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "candidate-insert-log.txt": { + "bytes": 221, + "sha256": "e6de4daeba1841783e7e22f81dbf87154f341c309dc5b54405e5fc2c2f3550a6" + }, + "candidate-resize-log.txt": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "candidate-startup-log.txt": { + "bytes": 6, + "sha256": "3dfa3fb239f56c778a1e9b33eb31328349edd155a95806dac4378904e88527e5" + }, + "candidate-vim.vim": { + "bytes": 334, + "sha256": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797" + }, + "sample.json": { + "bytes": 2315, + "sha256": "e756a2193be5abaf109f8f1560088a9a107581ec3fb18ca2201a4877c004169f" + } + }, + "empty_input_file_is_not_a_complete_input_trace": true + } + ], + "private_artifact_hashes": { + "identity.json": "fe45dbd34bb82fea0a9a401cdc8424bf7fab2ec04c112f8f59612953a621a4fd", + "outcome.json": "e4906807e67e3bd88cc49f9cd92e4304d17f8c839e91e76a6a48a6c677f0d569", + "unittest.log": "20433d14432af18e521d5419b02c90f33d485c2d279c6c0f61f79bb7dcfa6ca1" + } + }, + { + "id": "base-method-0", + "variant": "base", + "started_utc": "2026-10-27T00:44:15.818364+00:00", + "runtime_sha256": "d9e490fdbe31c233eab6578168fb4f2bda84f53252be3898d0cdd0e8610db721", + "outcome": "stage59", + "test_success": false, + "tests_run": 1, + "skips": 0, + "original_assertions_offline_rechecked_for_successful_cases": true, + "samples": [ + { + "index": 0, + "status": "failed", + "failure_stage": 59, + "exit_code": 2, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 7, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 7, + "terminated": 0 + }, + "desktop_gone": true, + "result": null, + "files": { + "candidate-input.bin": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "candidate-insert-log.txt": { + "bytes": 221, + "sha256": "e6de4daeba1841783e7e22f81dbf87154f341c309dc5b54405e5fc2c2f3550a6" + }, + "candidate-resize-log.txt": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "candidate-startup-log.txt": { + "bytes": 6, + "sha256": "3dfa3fb239f56c778a1e9b33eb31328349edd155a95806dac4378904e88527e5" + }, + "candidate-vim.vim": { + "bytes": 334, + "sha256": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797" + }, + "sample.json": { + "bytes": 2315, + "sha256": "9bbb1c8b4112cb0bd4018fc1b84ae38ee6922f1eced471759e9504a0df3b0c62" + } + }, + "empty_input_file_is_not_a_complete_input_trace": true + } + ], + "private_artifact_hashes": { + "identity.json": "9a99e3bd48c3170adaf9c8740bc997c0411409311e4baf5677ebe131343e951f", + "outcome.json": "b2daaab684843847a1fbc02fb4c951684aefb65a5367a6d66336ff3af02ae326", + "unittest.log": "1f06d393a95d0ce3a57280bfea3196b1e3fd3c3d341da71f1d25a016dee8a34d" + } + }, + { + "id": "window-prefix-method-0", + "variant": "window-prefix", + "started_utc": "2026-10-27T00:48:45.063902+00:00", + "runtime_sha256": "dd792766fa976b031cbff0540744b6f3d2b3fc0d695ed764a01457b65eab6258", + "outcome": "green", + "test_success": true, + "tests_run": 1, + "skips": 0, + "original_assertions_offline_rechecked_for_successful_cases": true, + "samples": [ + { + "index": 0, + "status": "passed", + "failure_stage": null, + "exit_code": 0, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "candidate_tui_start": true, + "candidate_tui": true, + "candidate_resize": true, + "candidate_replies": true, + "command_verified": true, + "exit_code": 37, + "active_children_after": 0, + "cleanup_complete": true, + "input_rejected": 0, + "columns": 60, + "rows": 12, + "presented_done": null + }, + "files": { + "candidate-input.bin": { + "bytes": 115, + "sha256": "4bd689a84f1eb7336afb3b607dec760d117caef3d83f3e8e1b6b1f83d70b4b68" + }, + "candidate-insert-log.txt": { + "bytes": 58, + "sha256": "3d07108bcc44f0831b31530265fac711c1d1e69194aa9309897e4440c26de65a" + }, + "candidate-resize-log.txt": { + "bytes": 5, + "sha256": "2d3db728e657a0e1ab99b23acb6b2d08c586c4eaba6e65a66ab65e7fb47693b7" + }, + "candidate-startup-log.txt": { + "bytes": 6, + "sha256": "3dfa3fb239f56c778a1e9b33eb31328349edd155a95806dac4378904e88527e5" + }, + "candidate-vim.vim": { + "bytes": 334, + "sha256": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797" + }, + "candidate-tui.txt": { + "bytes": 17, + "sha256": "d9d3b8d10b660a808ece2b2c46b3767713ba4e74631c56b376b492eadf511dec" + }, + "candidate-replies.bin": { + "bytes": 7, + "sha256": "39cb2fc06ace6ef92a7015a38ab2e7fe1f03a846af0313e27ec7cdceca6643ff" + }, + "sample.json": { + "bytes": 6004, + "sha256": "34e6041be10d8d7fceb26fc8c20b2f5f4429787425d032a3ff01c31cda557c0d" + } + }, + "empty_input_file_is_not_a_complete_input_trace": false + }, + { + "index": 1, + "status": "passed", + "failure_stage": null, + "exit_code": 0, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "candidate_tui_start": true, + "candidate_tui": true, + "candidate_resize": true, + "candidate_replies": true, + "command_verified": true, + "exit_code": 37, + "active_children_after": 0, + "cleanup_complete": true, + "input_rejected": 0, + "columns": 60, + "rows": 12, + "presented_done": null + }, + "files": { + "candidate-input.bin": { + "bytes": 115, + "sha256": "4bd689a84f1eb7336afb3b607dec760d117caef3d83f3e8e1b6b1f83d70b4b68" + }, + "candidate-insert-log.txt": { + "bytes": 58, + "sha256": "3d07108bcc44f0831b31530265fac711c1d1e69194aa9309897e4440c26de65a" + }, + "candidate-resize-log.txt": { + "bytes": 5, + "sha256": "2d3db728e657a0e1ab99b23acb6b2d08c586c4eaba6e65a66ab65e7fb47693b7" + }, + "candidate-startup-log.txt": { + "bytes": 6, + "sha256": "3dfa3fb239f56c778a1e9b33eb31328349edd155a95806dac4378904e88527e5" + }, + "candidate-vim.vim": { + "bytes": 334, + "sha256": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797" + }, + "candidate-tui.txt": { + "bytes": 17, + "sha256": "d9d3b8d10b660a808ece2b2c46b3767713ba4e74631c56b376b492eadf511dec" + }, + "candidate-replies.bin": { + "bytes": 7, + "sha256": "39cb2fc06ace6ef92a7015a38ab2e7fe1f03a846af0313e27ec7cdceca6643ff" + }, + "sample.json": { + "bytes": 6004, + "sha256": "f85f7d92aa9f83a148d5d6fc21073deaf5e8dad217c2e3dcaeba15c19bff1810" + } + }, + "empty_input_file_is_not_a_complete_input_trace": false + } + ], + "private_artifact_hashes": { + "identity.json": "5fa7f66913bc8be1ce699e5bfa8eae126f2bc9d1cacea6e9c446b146fbd39550", + "outcome.json": "f444c8c30a9d9732aea35e120cbf6813205b65f90e7641ec9fcdc660b214ef37", + "unittest.log": "e40d466f8594611cc5a2653d807819bad8c30c9aba126cce1bd2a1b6a9818e12" + } + }, + { + "id": "window-prefix-method-1", + "variant": "window-prefix", + "started_utc": "2026-10-27T00:49:03.977836+00:00", + "runtime_sha256": "dd792766fa976b031cbff0540744b6f3d2b3fc0d695ed764a01457b65eab6258", + "outcome": "green", + "test_success": true, + "tests_run": 1, + "skips": 0, + "original_assertions_offline_rechecked_for_successful_cases": true, + "samples": [ + { + "index": 0, + "status": "passed", + "failure_stage": null, + "exit_code": 0, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "candidate_tui_start": true, + "candidate_tui": true, + "candidate_resize": true, + "candidate_replies": true, + "command_verified": true, + "exit_code": 37, + "active_children_after": 0, + "cleanup_complete": true, + "input_rejected": 0, + "columns": 60, + "rows": 12, + "presented_done": null + }, + "files": { + "candidate-input.bin": { + "bytes": 115, + "sha256": "4bd689a84f1eb7336afb3b607dec760d117caef3d83f3e8e1b6b1f83d70b4b68" + }, + "candidate-insert-log.txt": { + "bytes": 58, + "sha256": "3d07108bcc44f0831b31530265fac711c1d1e69194aa9309897e4440c26de65a" + }, + "candidate-resize-log.txt": { + "bytes": 5, + "sha256": "2d3db728e657a0e1ab99b23acb6b2d08c586c4eaba6e65a66ab65e7fb47693b7" + }, + "candidate-startup-log.txt": { + "bytes": 6, + "sha256": "3dfa3fb239f56c778a1e9b33eb31328349edd155a95806dac4378904e88527e5" + }, + "candidate-vim.vim": { + "bytes": 334, + "sha256": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797" + }, + "candidate-tui.txt": { + "bytes": 17, + "sha256": "d9d3b8d10b660a808ece2b2c46b3767713ba4e74631c56b376b492eadf511dec" + }, + "candidate-replies.bin": { + "bytes": 7, + "sha256": "39cb2fc06ace6ef92a7015a38ab2e7fe1f03a846af0313e27ec7cdceca6643ff" + }, + "sample.json": { + "bytes": 6004, + "sha256": "9ccfd04084e5417ca36cc66c03748580f58e5a9cd355ee6c96cfbc4f1bdf5b6f" + } + }, + "empty_input_file_is_not_a_complete_input_trace": false + }, + { + "index": 1, + "status": "passed", + "failure_stage": null, + "exit_code": 0, + "outer_timed_out": false, + "job_after": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 9, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "candidate_tui_start": true, + "candidate_tui": true, + "candidate_resize": true, + "candidate_replies": true, + "command_verified": true, + "exit_code": 37, + "active_children_after": 0, + "cleanup_complete": true, + "input_rejected": 0, + "columns": 60, + "rows": 12, + "presented_done": null + }, + "files": { + "candidate-input.bin": { + "bytes": 115, + "sha256": "4bd689a84f1eb7336afb3b607dec760d117caef3d83f3e8e1b6b1f83d70b4b68" + }, + "candidate-insert-log.txt": { + "bytes": 58, + "sha256": "3d07108bcc44f0831b31530265fac711c1d1e69194aa9309897e4440c26de65a" + }, + "candidate-resize-log.txt": { + "bytes": 5, + "sha256": "2d3db728e657a0e1ab99b23acb6b2d08c586c4eaba6e65a66ab65e7fb47693b7" + }, + "candidate-startup-log.txt": { + "bytes": 6, + "sha256": "3dfa3fb239f56c778a1e9b33eb31328349edd155a95806dac4378904e88527e5" + }, + "candidate-vim.vim": { + "bytes": 334, + "sha256": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797" + }, + "candidate-tui.txt": { + "bytes": 17, + "sha256": "d9d3b8d10b660a808ece2b2c46b3767713ba4e74631c56b376b492eadf511dec" + }, + "candidate-replies.bin": { + "bytes": 7, + "sha256": "39cb2fc06ace6ef92a7015a38ab2e7fe1f03a846af0313e27ec7cdceca6643ff" + }, + "sample.json": { + "bytes": 6006, + "sha256": "9a83660ad5eb603a378cf3db037dc92e78b98276c4159901b935e8d05803b76b" + } + }, + "empty_input_file_is_not_a_complete_input_trace": false + } + ], + "private_artifact_hashes": { + "identity.json": "bfda2110a737353912cafed36e6b3c401e1eca36b840e39fcc9ad3dfb2db7e36", + "outcome.json": "a23be95c07a043b02f3a869d0ca403b0cf17f2168652e3783258978c07490c97", + "unittest.log": "143f43a9096edec23651fa9c4f968d76e7ad6509ce7d9976da9e84b9cfd6bc49" + } + } + ], + "limitations": [ + "The toolchain differs from the distributed 2024 DLL; this is an independent source rebuild, not a byte-identical rebuild.", + "The sole intentional source change is the WINDOW_BUFFER_SIZE_EVENT branch; six build artifacts differ, including regenerated version metadata.", + "No API probe or loaded-module path observation was performed in this batch; input-boundary call evidence belongs to the earlier separate diagnostic.", + "Failed Vim input files are empty and unflushed; they do not prove absent input or a complete equal input trace.", + "Only the original Vim GUI method was run; MOUSE_EVENT, actual signal-handler longjmp, other applications and full CTest were not validated.", + "Signal delivery still unlocks/relocks structurally; that is not a longjmp regression test.", + "The existing ReadConsoleInputW error handling was not strengthened or fault-injected.", + "Four patched cases support the queue-prefix diagnosis; they do not establish universal reliability, performance equivalence or WindTerm parity.", + "No product runtime adoption, compatibility strategy or fixed-target acceptance change is made; issue 52 remains open." + ] +} diff --git a/experiments/shell/results/2026-10-27-workflow-record-checkpoint/validation.json b/experiments/shell/results/2026-10-27-workflow-record-checkpoint/validation.json new file mode 100644 index 00000000..02ba0d93 --- /dev/null +++ b/experiments/shell/results/2026-10-27-workflow-record-checkpoint/validation.json @@ -0,0 +1,1032 @@ +{ + "schema": 1, + "issue": 87, + "source_commit": "c662011616a0498e0fbd6af8019f5f3cc03fdcaa", + "base_commit": "6b365b3cae9fbbfbb25f19a716d4fe85bcb0c04b", + "configuration": "candidate termforge.5 / explicit official ConPTY 1.25.260930003 / flags24", + "windows": "10.0.26200", + "identity": { + "source_commit": "c662011616a0498e0fbd6af8019f5f3cc03fdcaa", + "after_successful_build_utc": "2026-10-26T22:29:38.710131+00:00", + "diagnostic_instrumentation": false, + "files_sha256": { + "CMakeLists.txt": "8a0e3f86981e91fd6482b590669ccc4adb571a359bd265b3dc98aee59a7b92f2", + "experiments/shell/shell.cpp": "b4f6fe98812d1d23fa5f5bbcc7376437404af6cdb67cd6cdcdf448b57cb31fe9", + "experiments/shell/output_record_checkpoint.hpp": "b4925aadf2e29d8bf44c1d0101143ead51dadf58fd1bb681ad4af8f3c008fa69", + "experiments/shell/workflow_checkpoint.hpp": "34ddd734ff6ab81ae39194d9231a162dbe2069eb2c7bad81a9f15d028be92369", + "experiments/shell/workflow_checkpoint_test.cpp": "f4d670707a251285be37546c7be1c8e6668bf5087414ae7e415cbb5b510bc8af", + "experiments/shell/runtime_observation_test.cpp": "3a262672b6e28d326d1765ba7488940aadd8edaffaee47314ec27368ba56f98b", + "experiments/shell/workspace_workflow_scenario.hpp": "d70ec033c24fc8763996181bb30da7acc7a069cba4f527440fb9356c21fb2d7b", + "experiments/shell/workflow_scenario_state.hpp": "2627160b91fb468616dd3ec1cee289bf021771b63c8708e1989b6a2964995e3f", + "experiments/shell/workflow_fixture.hpp": "332d73699320a6f503fc212417a4d3820b82cc2d3feb3d1fffe3930660d57453", + "experiments/shell/workflow_sessions_checks.py": "b976d3504837eef57ff13319daed254870727741f36f265412a6b14b4cb3faae", + "experiments/shell/test_workflow_sessions.py": "6c968a443c46b30e75a929f880228e6a23bf2b92d9221bbbf18816933babd6e7", + "experiments/shell/run.py": "45976b828eecb316881d698dedaef09ad304f72d991dfa360f1c552bbcbe9f1d", + "build/candidate/bin/Release/termforge_shell.exe": "941ef34db129282a26625d8bad4fd663c4284fe3307802907366299ab04f31c4", + "build/candidate/bin/Release/workflow_checkpoint_test.exe": "056047580e8ddd77224908667297a0b704f97ef57d1eadeb382b52c7c7875b86", + "build/candidate/bin/Release/runtime_observation_test.exe": "0049a0d863724bf8bd181c85868bd68a409eb5e12c755a5538add5f85bf8b0bf", + "build/candidate/bin/Release/shell_text_client.exe": "9350e53de74c9a6b78495a3b59788e3a31e0067a9c5ec1b8685a729ff18244d3", + "build/candidate/bin/Release/shell_test_launcher.exe": "7bd78cffd78ac66d651933c9614d6be937c99baa105ce04c1a533df5785c11e2", + "vim.exe": "2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a" + } + }, + "source_hash_policy": "SHA256 of on-disk compilation inputs; Windows checkout line endings retained.", + "candidate_dependency_sha256": { + "experiments/candidates/alacritty/Cargo.lock": "f7b8906cc63b6a5d66943f15c50a78c51f773face60229fcddf24b2d02911736", + "experiments/candidates/alacritty/target/release/termforge_alacritty_core.lib": "f2461f024d8b3002ca6bb872b409e1dadb34eff6fc92892108cfd18b3aece807" + }, + "backend": { + "version": "1.25.260930003", + "source_commit": "dc8ae365847d41f62d1ebca93815bb00959bc12f", + "package_sha256": "6d6f8b008c655d814c498d8205d4012dcd4d0b6dfc1096461d0a185d1e3caf03", + "files": { + "conpty.dll": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "OpenConsole.exe": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "configured_creation_flags": 24 + }, + "workload": { + "fixture": "r108-console-v1", + "records": 32000, + "source_bytes": 2048000, + "block_bytes": 4096, + "block_delay_ms": 20, + "replacement_cycles": 5, + "scenario_deadline_ms": 70000, + "runner_timeout_seconds": 90, + "ctest_timeout_seconds": 300, + "python_oracle_changed": false + }, + "original_failures": { + "prior_artifact": "experiments/conpty/results/2026-10-27-native-viewport-resize/validation.json", + "product_source": "57f24f5", + "shell_sha256": "4e23e277ea7984d85856b713b3af5b0545d68042c2bbd42a8422f8a6b422c28d", + "mismatches": [ + { + "run": "run-89580b0cae404944b12e03e031dab1bd", + "bad": [ + { + "cycle": 1, + "name": "F5", + "source_bytes": 16384, + "actual": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301............", + "expected": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301...................................", + "exact_prefix": true, + "output": { + "queued_bytes": 8217, + "high_water_bytes": 8217, + "stopped": false + } + }, + { + "cycle": 1, + "name": "F5", + "source_bytes": 16384, + "actual": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301............", + "expected": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301...................................", + "exact_prefix": true, + "output": { + "queued_bytes": 8217, + "high_water_bytes": 8217, + "stopped": false + } + } + ] + }, + { + "run": "run-8abb195e8f824858adbc0149ce1bbca6", + "bad": [ + { + "cycle": 2, + "name": "F5", + "source_bytes": 16384, + "actual": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301............", + "expected": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301...................................", + "exact_prefix": true, + "output": { + "queued_bytes": 8217, + "high_water_bytes": 8217, + "stopped": false + } + }, + { + "cycle": 2, + "name": "F5", + "source_bytes": 16384, + "actual": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301............", + "expected": "TF_R108_F5_00127 \u4e2d\u6587 e\u0301...................................", + "exact_prefix": true, + "output": { + "queued_bytes": 8217, + "high_water_bytes": 8217, + "stopped": false + } + } + ] + }, + { + "run": "run-9266bc270e244e05a0073d86f00ae8ae", + "bad": [] + } + ], + "offline_validator": [ + { + "run": "run-89580b0cae404944b12e03e031dab1bd", + "error": "replacement core payload/role mismatch", + "seconds": 0.6168144999537617 + }, + { + "run": "run-8abb195e8f824858adbc0149ce1bbca6", + "error": "replacement core payload/role mismatch", + "seconds": 0.5770988999865949 + } + ], + "original_native_continuation_available": false + }, + "initial_live_observation": { + "identity": { + "base_commit": "f2ea8d2f5851724bedeb13344ee4681c7b092691", + "diagnostic_only": true, + "build_completed_before_test_utc": "2026-10-26T21:36:40.942995+00:00", + "sha256": { + "experiments/shell/shell.cpp": "1154c7eeaa2ae32c36958ef3f04446778bb59ec3802c3fcfb9508ac9731fe0b4", + "experiments/shell/workspace_workflow_scenario.hpp": "7ec62dda236dbb189a6922d2c7d4ec1ba9eca379b2e76b816ac3d58731c252b5", + "build/candidate/bin/Release/termforge_shell.exe": "74efba3d16080fc74ae2a8793083d3612e445ebb91eab4006ee28d28b07cec0a" + } + }, + "runs": [ + { + "sample_sha256": "37dc6911b059f25c708e2c7beba6d37d9eb2a508e2dbd53e8fa69295f9c56884", + "sessions_sha256": "3846b6d9a1570dafd7cc13f302d6ce6a684765aa62a0685439c32b761b20da12", + "lifecycle": { + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + } + } + }, + { + "sample_sha256": "f0af6eef429ef1f4f00d606916ce3995cc1dd42004c57fcc79fe4c22b3edaaff", + "sessions_sha256": "6b631bad7a48dd82fa5e50004d5b066db1f6d5af0bb3a193ec286e27093cf245", + "lifecycle": { + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + } + } + }, + { + "sample_sha256": "a6d5d75585a618cfcd4e53cf034c744533f701689530f2f413844afa146a4640", + "sessions_sha256": "babe9f73ce179f11564225e9b06d9f564d14115f625e474628afc197ce8b7b9b", + "lifecycle": { + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + } + } + }, + { + "sample_sha256": "ff1b5ecd829ca68959c881806681af160513a17cb6ffeb2d7b09e518d82be1d0", + "sessions_sha256": "1c41e39aa93e60a8c75f7aa88cc5579daea96f8d6f479ca7cbafffa2e0ca8156", + "lifecycle": { + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + } + } + } + ], + "half_row_captured": false + }, + "repartition": { + "transport_sha256": "f7c5134e5779bd946460d01c0db43b4ee53819261da701b031bb9a17a901b725", + "same_real_bytes": true, + "partition_changed": true, + "gui_timing_reproduced": false, + "remaining_25_bytes_hex": "2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e0d0a", + "partial_dots": 12, + "final_dots": 35, + "prefix_state_offset": 11032, + "final_state_offset": 11057, + "raw_cell_lines": [ + "TF_R108_F5_00127 \u4e2d \u6587 e\u0301............", + "TF_R108_F5_00127 \u4e2d \u6587 e\u0301..................................." + ], + "note": "Screen JSON includes explicit width-0 spacer cells; these are not user text. GUI raw snapshots used canonical project_line." + }, + "core_regression": { + "red_test_source_sha256": "50b4c17d494695b50d65f3f52f0c2dfb458feb1509989579627f52225c2e0aeb", + "red_helper_sha256": "c5efe3d91748921292090f212e32f62a252053bb2ca7d5abe76be4c530e0e8f3", + "red_executable_sha256": "76a92dda8f6bfa025f3d50e1e43d65889c569c83688fc8be0d6b5124e47c546f", + "red_log_sha256": "df03de6cb3deec3b3df505c58e1f89347bddfcb9dda3b960e2fbf50af18438c3", + "red_error": "numbered prefix must not admit an unfinished consumer row", + "both_cores_failed_before": true, + "final_test_passed": true, + "note": "Original 8192/8217 trigger retained. Final test additionally covers active hold/release, readiness negatives and bounds; entire red/final test sources differ." + }, + "rejected_passive_wait": { + "identity": { + "source_commit": "51ad19dc5b4f829046b41f360b1428aa4ee0d37e", + "diagnostic_only": true, + "build_completed_before_test_utc": "2026-10-26T21:57:55.185445+00:00", + "files_sha256": { + "experiments/shell/shell.cpp": "b0c769769614fb56d91528a5cfdf96938f2557d4c1c591e43d971a151666e836", + "experiments/shell/workspace_workflow_scenario.hpp": "ecfa477b8b47e68f3c14782a885cb9f3ec217cae91bf33b0cca6a1d405674503", + "build/candidate/bin/Release/termforge_shell.exe": "6792f6eacb3100f2da98f8a6c29e9e2125becf29d0760a9e32a815ea02f89a8e" + } + }, + "results": [ + { + "policy": "old", + "error": "replacement core payload/role mismatch", + "checked": null, + "exit_code": 0, + "timed_out": false, + "desktop_gone": true, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + } + }, + { + "policy": "fixed", + "error": "replacement output not active", + "checked": null, + "exit_code": 0, + "timed_out": false, + "desktop_gone": true, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + } + } + ], + "log_sha256": "5a0f3c61bcdf00977bd26332e17758955282a96b34d6f4dd31145e14ba1d0102", + "error": "replacement output not active", + "usable_for_acceptance": false + }, + "controlled_gui": { + "identity": { + "source_commit": "51ad19dc5b4f829046b41f360b1428aa4ee0d37e", + "diagnostic_only": true, + "uncommitted_record_checkpoint": true, + "build_completed_before_test_utc": "2026-10-26T22:14:07.691106+00:00", + "files_sha256": { + "experiments/shell/shell.cpp": "eb8cd8a7f7548756fd529b8f6443b69151f605837760ca23e3d99198fe467438", + "experiments/shell/workspace_workflow_scenario.hpp": "757693dae0eff8eb093a16360136893bdfdce3b533d672eedc1de8f770ca2e7d", + "experiments/shell/output_record_checkpoint.hpp": "b4925aadf2e29d8bf44c1d0101143ead51dadf58fd1bb681ad4af8f3c008fa69", + "experiments/shell/workflow_checkpoint.hpp": "34ddd734ff6ab81ae39194d9231a162dbe2069eb2c7bad81a9f15d028be92369", + "experiments/shell/workflow_checkpoint_test.cpp": "f4d670707a251285be37546c7be1c8e6668bf5087414ae7e415cbb5b510bc8af", + "build/candidate/bin/Release/termforge_shell.exe": "8ca1957989d43941a67e38593f240a7bf651b145b78ca0918813d23de6c825bd" + } + }, + "same_diagnostic_executable": true, + "diagnostic_patch_sha256": "809c19056e30bc26dd810af81a65fe5220e5fc95ad8af96ddc156b20ec96d0de", + "cases": [ + { + "mode": "old", + "record": 117, + "partial": "TF_R108_F5_00117 \u4e2d\u6587 e", + "generation": 10, + "source_bytes_at_checkpoint": 32768, + "output_done": false, + "fixture_running": true, + "raw_core_cell_rows": [ + "TF_R108_F5_00117 \u4e2d \u6587 e", + "TF_R108_F5_00117 \u4e2d \u6587 e\u0301..................................." + ], + "same_session_continuation": true, + "native_prefix_preserved": true, + "actual_suffix_bytes": 39, + "held_cursor": [ + 6, + 23 + ], + "checkpoint_cursor": [ + 6, + 23 + ], + "held_transport_sha256": "49f4051e5f5f0c47fc955f2d0d94ac19a2224a2f5005dac34bed217a90374f7e", + "continued_transport_sha256": "d13c8002fabf72bccc837a892f546af898e924e7560f95c3d32ccc7e6f06425b", + "trace_overflow": { + "held": { + "events_overflow": false, + "transport_overflow": false + }, + "checkpoint": { + "events_overflow": false, + "transport_overflow": false + }, + "collected": { + "events_overflow": false, + "transport_overflow": false + } + }, + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "sample_error": "replacement core payload/role mismatch", + "source_sha256": "693dacfd941aefbb0b2379d44ec1d5492536611af9fd5b83c49642c65bcc56bb", + "sample_sha256": "faa3bea07e0092773dc66d925d85e644f36aefc1fc50eb1e9175885c3cb1568a", + "sessions_sha256": "b32e1e3d3ee3cb8164c124c9f42bbc635a25c8071ffd0b3fbb31068156a6e690", + "consumer_snapshots_sha256": { + "held": "7ddb00077095a97ae3950834fd4f432cd0fcd33d706b2b353a96a19e512dc04e", + "checkpoint": "1e979b605d3ef65ec7816409da7eb9fb2b80c2abda87047bcd77c45871d3a608", + "collected": "051f2a6058c23a03fe057a76f5078a87bd91ba74da2f918476f9621cfc321b31" + }, + "replay_sha256": "1b96eb77b7e7ec0c220349c7b4dc2496bd3bf2485d4610fa0cda04bccd929906" + }, + { + "mode": "new", + "record": 117, + "partial": "TF_R108_F5_00117 \u4e2d\u6587", + "generation": 10, + "source_bytes_at_checkpoint": 241664, + "output_done": false, + "fixture_running": true, + "raw_core_cell_rows": [ + "TF_R108_F5_00117 \u4e2d \u6587", + "TF_R108_F5_00117 \u4e2d \u6587 e\u0301..................................." + ], + "same_session_continuation": true, + "native_prefix_preserved": true, + "actual_suffix_bytes": 41, + "held_cursor": [ + 6, + 21 + ], + "checkpoint_cursor": [ + 6, + 0 + ], + "held_transport_sha256": "fe9c9fbb43fda939d2ea77e0c189dcc8ef6924aa0bf24b9ab8d733ccfb7bed99", + "continued_transport_sha256": "f31909f03efe242ab80365c59630cdcaf48612493a1e19ff0c64e600a43582ff", + "trace_overflow": { + "held": { + "events_overflow": false, + "transport_overflow": false + }, + "checkpoint": { + "events_overflow": false, + "transport_overflow": false + }, + "collected": { + "events_overflow": false, + "transport_overflow": true + } + }, + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "sample_error": null, + "source_sha256": "0c9ad5475733cff134f0372a4e9ef9348dec984b07d9e639741165750149cb90", + "sample_sha256": "7e0b9a8f32f40b64ac7d23d65f77757e759be53c053b65b29b6b860692adc9a7", + "sessions_sha256": "953c44c025631475b6cf8856fd632ec2d60f9409c4c9baf56bdc37e5c0b5ef9d", + "consumer_snapshots_sha256": { + "held": "f5806a2dd48560e4886b3dd7c8918259532781dd3ce19316c9a2d687aa273243", + "checkpoint": "e11551cb7501f13591f38a989f661e76b8feb69db9a8a9363797ba7b1d794d90", + "collected": "bf229d4ab212b4de45cc1082862705b3ce05fadb08889ea0a99b3f03540dbf63" + }, + "replay_sha256": "f97529bca6dbd1cbfd393ec92fa668e6bc06b7fdd88a4e73954c94a131cbbb72" + } + ], + "default_product_scheduling": false, + "schedule": "Pause F5 consumer before replacement output, wait source >=16384, consume one actual 8192B block, retain snapshot and resume. Producer input/rate/bytes and original oracle unchanged.", + "trace_limit_bytes": 131072, + "trace_limit_events": 1024, + "trace_limit_note": "New collected trace overflowed; needed suffix is present in its preserved prefix. No claim of complete subsequent transport." + }, + "close_guard": { + "red_log_sha256": "60504f0ee912974ade65cf8e573d774bcfe8370343f4da7ac96668f229fea49c", + "red_test_source_sha256": "3a262672b6e28d326d1765ba7488940aadd8edaffaee47314ec27368ba56f98b", + "red_product_source": "f89b0ac3a95f28e2b4ffa5bdfd368164848e8bde", + "red_executable_sha256": null, + "red_build_log_sha256": "e89d6ac2ad05e5807ee29cdcdf8b3a3d437b773407e560e70340f09426652d68", + "error": "closing window must reject a new output record checkpoint", + "green_log_sha256": "bbdc9e41c57357182dde0a239b4beaf2638e4bed38a656974ce7316b4fb442e4" + }, + "final_workflow_rounds": [ + { + "sample_sha256": "0e004559ca3b8bed704a3bf9cd4e6d54f92fc31db6576d2f2fc5cfc8090bfae2", + "sessions_sha256": "f437c524c63fa144d7389e7d38982d8f3b9568ff894dee08dd5e4609928fbe94", + "checked": { + "schema": "termforge-workflow-sessions-identity-validation-v1", + "live_sessions": 8, + "checkpoints": 26, + "rejected_ninth": true, + "load_foreground_verified": true, + "data_workflow_verified": true, + "complete_workflow_verified": false, + "system_clipboard_verified": false, + "ime_verified": false + }, + "lifecycle": { + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + } + }, + "replacement_cycles": [ + { + "cycle": 1, + "source_bytes": [ + 20480, + 20480 + ], + "generations": [ + 9, + 10 + ], + "all_output_active": true + }, + { + "cycle": 2, + "source_bytes": [ + 16384, + 16384 + ], + "generations": [ + 11, + 12 + ], + "all_output_active": true + }, + { + "cycle": 3, + "source_bytes": [ + 12288, + 12288 + ], + "generations": [ + 13, + 14 + ], + "all_output_active": true + }, + { + "cycle": 4, + "source_bytes": [ + 12288, + 12288 + ], + "generations": [ + 15, + 16 + ], + "all_output_active": true + }, + { + "cycle": 5, + "source_bytes": [ + 16384, + 16384 + ], + "generations": [ + 17, + 18 + ], + "all_output_active": true + } + ] + }, + { + "sample_sha256": "18ab9afea089669bb30b6e69a401e5a8e7d8082aa9d49e5c0d28d2b1544cf12a", + "sessions_sha256": "ae6443ec74669dabed594afffdb99c94af6a022bb64582468e6bb80abe35c7da", + "checked": { + "schema": "termforge-workflow-sessions-identity-validation-v1", + "live_sessions": 8, + "checkpoints": 26, + "rejected_ninth": true, + "load_foreground_verified": true, + "data_workflow_verified": true, + "complete_workflow_verified": false, + "system_clipboard_verified": false, + "ime_verified": false + }, + "lifecycle": { + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 77, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 77, + "terminated": 0 + } + }, + "replacement_cycles": [ + { + "cycle": 1, + "source_bytes": [ + 16384, + 16384 + ], + "generations": [ + 9, + 10 + ], + "all_output_active": true + }, + { + "cycle": 2, + "source_bytes": [ + 12288, + 12288 + ], + "generations": [ + 11, + 12 + ], + "all_output_active": true + }, + { + "cycle": 3, + "source_bytes": [ + 12288, + 12288 + ], + "generations": [ + 13, + 14 + ], + "all_output_active": true + }, + { + "cycle": 4, + "source_bytes": [ + 8192, + 12288 + ], + "generations": [ + 15, + 16 + ], + "all_output_active": true + }, + { + "cycle": 5, + "source_bytes": [ + 12288, + 12288 + ], + "generations": [ + 17, + 18 + ], + "all_output_active": true + } + ] + } + ], + "final_full_ctest": { + "log_sha256": "ea5283e3c04791877299e5e410d98fdbf695fd0c97ecc888e1e604dedc87f387", + "tests": [ + { + "name": "shell_launch", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "candidate_title", + "result": "passed", + "seconds": 0.75 + }, + { + "name": "candidate_reply_close", + "result": "passed", + "seconds": 0.37 + }, + { + "name": "runtime_observation", + "result": "passed", + "seconds": 1.45 + }, + { + "name": "ime_input_expectation", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "sent_message_wait", + "result": "passed", + "seconds": 0.28 + }, + { + "name": "find_bar", + "result": "passed", + "seconds": 0.36 + }, + { + "name": "renderer_metrics", + "result": "passed", + "seconds": 0.87 + }, + { + "name": "ime_context", + "result": "passed", + "seconds": 0.38 + }, + { + "name": "layout_state", + "result": "passed", + "seconds": 0.15 + }, + { + "name": "workspace_state", + "result": "passed", + "seconds": 0.37 + }, + { + "name": "transport_completion", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "workflow_checkpoint", + "result": "passed", + "seconds": 0.07 + }, + { + "name": "scenario_tick_gate", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "candidate_core", + "result": "passed", + "seconds": 1.23 + }, + { + "name": "resize_core", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "history_core", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "selection_core", + "result": "passed", + "seconds": 0.08 + }, + { + "name": "search_core", + "result": "passed", + "seconds": 0.12 + }, + { + "name": "paste", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "sender_fence", + "result": "passed", + "seconds": 1.02 + }, + { + "name": "sender_fence_pipe", + "result": "passed", + "seconds": 0.37 + }, + { + "name": "clipboard_policy", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "keyboard", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "text_action_policy", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "candidate_resize_replay", + "result": "passed", + "seconds": 0.66 + }, + { + "name": "candidate_shell_tui", + "result": "failed", + "seconds": 31.29 + }, + { + "name": "core", + "result": "passed", + "seconds": 6.43 + }, + { + "name": "recovery", + "result": "passed", + "seconds": 10.07 + }, + { + "name": "measure", + "result": "passed", + "seconds": 4.24 + }, + { + "name": "binary_paths", + "result": "passed", + "seconds": 0.18 + }, + { + "name": "conpty_margin_contract", + "result": "passed", + "seconds": 0.28 + }, + { + "name": "benchmark_tools", + "result": "passed", + "seconds": 5.37 + }, + { + "name": "differential", + "result": "passed", + "seconds": 20.33 + }, + { + "name": "ime_inventory", + "result": "passed", + "seconds": 49.88 + }, + { + "name": "text_actions", + "result": "passed", + "seconds": 5.43 + }, + { + "name": "workflow_fixture", + "result": "passed", + "seconds": 22.84 + }, + { + "name": "workflow_sessions", + "result": "passed", + "seconds": 81.61 + }, + { + "name": "workflow_resources", + "result": "passed", + "seconds": 0.47 + }, + { + "name": "workflow_resource_phases", + "result": "passed", + "seconds": 4.37 + }, + { + "name": "workflow_system_content", + "result": "passed", + "seconds": 0.18 + }, + { + "name": "workflow_system_runner", + "result": "passed", + "seconds": 0.68 + }, + { + "name": "copy_error_frame", + "result": "passed", + "seconds": 0.14 + }, + { + "name": "paste_fence_evidence", + "result": "passed", + "seconds": 0.11 + }, + { + "name": "resize_trace", + "result": "passed", + "seconds": 0.13 + }, + { + "name": "research_guards", + "result": "passed", + "seconds": 4.93 + }, + { + "name": "receive", + "result": "passed", + "seconds": 0.2 + }, + { + "name": "conpty", + "result": "passed", + "seconds": 22.94 + }, + { + "name": "conpty_ctrl_c", + "result": "passed", + "seconds": 4.33 + }, + { + "name": "conpty_margin", + "result": "passed", + "seconds": 0.39 + }, + { + "name": "conpty_backend", + "result": "passed", + "seconds": 1.16 + }, + { + "name": "presentation", + "result": "passed", + "seconds": 4.11 + }, + { + "name": "shell", + "result": "passed", + "seconds": 94.93 + }, + { + "name": "selection_bytes", + "result": "passed", + "seconds": 3.64 + }, + { + "name": "selection_context", + "result": "passed", + "seconds": 3.94 + }, + { + "name": "raw_console_receiver", + "result": "passed", + "seconds": 0.39 + }, + { + "name": "paste_system", + "result": "passed", + "seconds": 8.93 + } + ], + "total": 57, + "passed": 56, + "failed": [ + "candidate_shell_tui" + ], + "seconds": 402.66, + "failure_stages": [ + 59 + ], + "internal_skips": [ + { + "test": "test_actual_system_copy_paste_and_shortcuts_twice (experiments.shell.test_actions.ActionWindowTest.test_actual_system_copy_paste_and_shortcuts_twice)", + "reason": "actual system Copy/Paste requires a disposable hosted VM" + }, + { + "test": "test_suspended_target_is_reclaimed_if_job_assignment_fails (test_ui_r1.EvidenceBoundaryTests.test_suspended_target_is_reclaimed_if_job_assignment_fails)", + "reason": "opt-in pinned reference failure injection" + }, + { + "test": "test_mouse_selection_to_real_system_clipboard (experiments.shell.test_shell.ShellTests.test_mouse_selection_to_real_system_clipboard)", + "reason": "system copy UI gate requires a disposable hosted VM" + }, + { + "test": "test_system_clipboard_product_read_errors_cancel_and_close (experiments.shell.test_shell.ShellTests.test_system_clipboard_product_read_errors_cancel_and_close)", + "reason": "product clipboard read gate requires a disposable hosted VM" + }, + { + "test": "test_system_clipboard_shortcut_to_window_confirmation_and_receiver (experiments.shell.test_shell.ShellTests.test_system_clipboard_shortcut_to_window_confirmation_and_receiver)", + "reason": "real system clipboard UI gate requires a disposable hosted VM" + }, + { + "test": "test_real_system_copy_shortcuts_for_styled_fixture_twice (experiments.shell.test_selection_bytes.SelectionBytesWindowTest.test_real_system_copy_shortcuts_for_styled_fixture_twice)", + "reason": "system CF requires a disposable hosted VM" + }, + { + "test": "test_real_system_copy_and_edit_context_twice (experiments.shell.test_selection_context.SelectionContextWindowTest.test_real_system_copy_and_edit_context_twice)", + "reason": "real CF/EDIT Copy requires a disposable hosted VM" + }, + { + "test": "test_real_system_clipboard (experiments.shell.test_paste.PasteTests.test_real_system_clipboard)", + "reason": "Private window station OpenClipboard denied (5); system API gate remains unverified." + } + ], + "full_acceptance": false, + "last_test_log_sha256": "c7728e3c25e030fe12e7e39652b7e01409661a34f3f5aa1c23f16e78b332afa1" + }, + "other_failures": [ + { + "mode": "candidate-tui", + "stage": 59, + "lifecycle": { + "exit_code": 2, + "timed_out": false, + "job_after": { + "active": 0, + "total": 7, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 7, + "terminated": 0 + } + }, + "files_sha256": { + "candidate-input.bin": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "candidate-insert-log.txt": "e6de4daeba1841783e7e22f81dbf87154f341c309dc5b54405e5fc2c2f3550a6", + "candidate-output.bin": "fc43e5c356005f9c32847476d4bdba5fabaf51439083d8ac4e530d3e8bb5c883", + "candidate-resize-log.txt": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "candidate-startup-log.txt": "3dfa3fb239f56c778a1e9b33eb31328349edd155a95806dac4378904e88527e5", + "candidate-vim.vim": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797", + "sample.json": "115d07631f728fb3ed2cd0a0e0a6a183c39627c627b7c849a029bbeb6eb4b695" + } + } + ], + "earlier_pre_close_guard_ctest": { + "log_sha256": "c47eca6a71598128c07a98c65d9b975163177fe197d80a01a2720b120ca0f3ed", + "total": 57, + "passed": 57, + "failed": [], + "seconds": 382.56, + "failure_stages": [], + "full_acceptance": false, + "source_commit": "f89b0ac3a95f28e2b4ffa5bdfd368164848e8bde", + "usable_for_acceptance": false, + "internal_skip_count": 8 + }, + "limits": [ + "Original two historical failures had no native continuation capture; later controlled causal reproduction does not fabricate it.", + "Only synthetic sampling coordination changes; no demonstrated product text-loss fix, oracle weakening, performance or WindTerm-equivalence claim.", + "Four passive live probes missed the half-row; a passive readiness implementation later starved in a controlled run and is rejected.", + "An excluded generic cmd echo test assumed a literal LF that the transport need not emit; final live close test only claims close while armed.", + "Raw native bytes, personal paths and desktop identities remain local. Controlled diagnostic scheduling and final original uninstrumented runs are separate.", + "Real CF/IME, actual presentation, full official-backend acceptance, #52/#63 and R2 remain unverified." + ], + "presented_done": null, + "performance_acceptance": false, + "windterm_runtime_equivalence": false, + "full_r2_verified": false, + "excluded_cmd_lf_test_sha256": "7d2cafa5d70d57e69527f626f533995e8d8c71a77a6b4f55f7aae646af91be09" +} diff --git a/experiments/shell/results/2026-10-27-workspace-echo-cr/validation.json b/experiments/shell/results/2026-10-27-workspace-echo-cr/validation.json new file mode 100644 index 00000000..0fbc810e --- /dev/null +++ b/experiments/shell/results/2026-10-27-workspace-echo-cr/validation.json @@ -0,0 +1,1009 @@ +{ + "schema": 1, + "issue": 88, + "source_commit": "c93db58ede50ad1288ceeeb937403fa8db43b459", + "implementation_commit": "341b4c22e36ffb5a47c7c1d0ccd70b46402e93b6", + "base_commit": "259e03667f361fd00c6e678f3e86f6ee2dfdca1b", + "configuration": "candidate termforge.5 / explicit official ConPTY 1.25.260930003 / flags24", + "identity": { + "source_commit": "c93db58ede50ad1288ceeeb937403fa8db43b459", + "after_successful_build_utc": "2026-10-26T21:18:00.803994+00:00", + "files_sha256": { + "experiments/shell/transport_completion.hpp": "39dfeb0845e572ec842b7da09dc2b822ba221caa1414a6a386f066214eb4da15", + "experiments/shell/transport_completion_test.cpp": "3761c858c97e5975d51a5c552104ba8457a5183070b12c0f635a7da42647e80d", + "experiments/shell/workspace_persistence_scenario.hpp": "7f70d755549718d0fddb94b43cce12f3955727fcc51599a8bb124bf3e59ec263", + "experiments/shell/test_shell.py": "a6b63c1754b0e0dc1cc01863891f4c7c9d989f818d351838a8cfe295edaf811c", + "experiments/shell/run.py": "45976b828eecb316881d698dedaef09ad304f72d991dfa360f1c552bbcbe9f1d", + "experiments/candidates/alacritty/test_candidate.py": "80b19e6fcd9416d4a34c43a5e50525857e6504f1a485827045f209d4dae4f106", + "build/candidate/bin/Release/termforge_shell.exe": "6adcb6091cbe937f398eace55499042eebcd5c900bd0f36c6eef480a6eac0eb4", + "build/candidate/bin/Release/transport_completion_test.exe": "6952438843e8a7605bb6cf0eb7add56dd8574f4e5edfbc3a534f795030a3fcc8", + "experiments/candidates/alacritty/target/release/termforge-alacritty-trace.exe": "5941d4569d0ae146eb005f12ecfb15014bc06c18ee48d1c9460824ab65df93d3" + } + }, + "baseline": { + "source_commit": "ab360c7aebeb8e9afb5e61d386081abd8e3e8278", + "product_commit": "92eac12d5cd2868b469e8b9a10e68f5e20ee2cb0", + "files_sha256": { + "termforge_shell.exe": "15a149a3d7d47efaf866226559d4b60bbf83e0ff6004a85df41edcb5415ad206", + "transport_completion.hpp": "535ff47bfbdd1038dca22820b467d1ee0d1839a29be38cd750e4de49439b602f", + "transport_completion_test.cpp": "c9bb02bd739f916e78530590ee0f3b29bc952c58e445d31bc047ee69d301dcee", + "transport_completion_test.exe": "ecb414aa56e45cb6026154d2c5bf4c3e2c173c038d4f2641debfa452749b0233" + } + }, + "original_failure": { + "source_commit": "92eac12d5cd2868b469e8b9a10e68f5e20ee2cb0", + "mode": "ui-workspace-restore", + "stage": 1, + "repeat": 1, + "files_sha256": { + "persistence-failure.json": "7ccb0a90b526462a929cbebce42debccfd97b54676e54e0935fbf292826b5e5b", + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "5e546fb36ec65e2d2eb20e9d1047216be33dc8b17eaaa611d995686a1be7d620", + "workspace.layout": "660f3a4064006db91261100c6fb1e1d2fb82f73210575910a29880489dd0cdda" + }, + "result": { + "exit_code": 2, + "timed_out": false, + "job_after": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 11, + "terminated": 0 + } + }, + "native_exit_code": null, + "reader_eof": null, + "native_forced_cleanup": null + }, + "original_replay": { + "product_source": "92eac12d5cd2868b469e8b9a10e68f5e20ee2cb0", + "failure_sha256": "7ccb0a90b526462a929cbebce42debccfd97b54676e54e0935fbf292826b5e5b", + "helper_sha256": "535ff47bfbdd1038dca22820b467d1ee0d1839a29be38cd750e4de49439b602f", + "probe_sha256": "eca81870a7f8228bab14f5d35900d27fc7a207a07c2a1e5b2e49c9faf0189698", + "cases": [ + { + "tab": 0, + "kind": "cmd", + "variant": "original", + "bytes": 230, + "sha256": "68fadc3fc45b9fbaab78b7e1a55987f5fdc11e9a2abdbe39fc65135556703017", + "exit_code": 0, + "stdout": "complete", + "stderr": "" + }, + { + "tab": 1, + "kind": "cmd", + "variant": "original", + "bytes": 262, + "sha256": "8db2595d8d8546bbb7bb7a6facf79ab72e8f88ee4ce6ec9cf255dbac8528013e", + "exit_code": 1, + "stdout": "incomplete", + "stderr": "" + }, + { + "tab": 1, + "kind": "cmd", + "variant": "remove-one-bare-cr", + "bytes": 261, + "sha256": "e2e797ab3c6434bb462b109f08f16e4966cf71df74558cc5ecf6c3906e4cf8ff", + "exit_code": 0, + "stdout": "complete", + "stderr": "" + }, + { + "tab": 1, + "kind": "cmd", + "variant": "independent-output-removed", + "bytes": 230, + "sha256": "e7e16d6f14705c58c00f64d0f4183cfd6efa3a0c1a0e104c4242a4de75ce0b6a", + "exit_code": 1, + "stdout": "incomplete", + "stderr": "" + }, + { + "tab": 2, + "kind": "powershell", + "variant": "original", + "bytes": 4373, + "sha256": "06d376c5709d01185a5696726e5ed2a412184b5e1df886ac948c1ac64b349f4c", + "exit_code": 0, + "stdout": "complete", + "stderr": "" + }, + { + "tab": 3, + "kind": "powershell", + "variant": "original", + "bytes": 4373, + "sha256": "06d376c5709d01185a5696726e5ed2a412184b5e1df886ac948c1ac64b349f4c", + "exit_code": 0, + "stdout": "complete", + "stderr": "" + } + ] + }, + "fixed_replay": { + "product_source": "92eac12d5cd2868b469e8b9a10e68f5e20ee2cb0", + "failure_sha256": "7ccb0a90b526462a929cbebce42debccfd97b54676e54e0935fbf292826b5e5b", + "helper_sha256": "39dfeb0845e572ec842b7da09dc2b822ba221caa1414a6a386f066214eb4da15", + "probe_sha256": "e8f1f3d93d9af701244792e4dfda6ac2813ac9a65299a3a56cb230d6fed503e3", + "cases": [ + { + "tab": 0, + "kind": "cmd", + "variant": "original", + "bytes": 230, + "sha256": "68fadc3fc45b9fbaab78b7e1a55987f5fdc11e9a2abdbe39fc65135556703017", + "exit_code": 0, + "stdout": "complete", + "stderr": "" + }, + { + "tab": 1, + "kind": "cmd", + "variant": "original", + "bytes": 262, + "sha256": "8db2595d8d8546bbb7bb7a6facf79ab72e8f88ee4ce6ec9cf255dbac8528013e", + "exit_code": 0, + "stdout": "complete", + "stderr": "" + }, + { + "tab": 1, + "kind": "cmd", + "variant": "remove-one-bare-cr", + "bytes": 261, + "sha256": "e2e797ab3c6434bb462b109f08f16e4966cf71df74558cc5ecf6c3906e4cf8ff", + "exit_code": 0, + "stdout": "complete", + "stderr": "" + }, + { + "tab": 1, + "kind": "cmd", + "variant": "independent-output-removed", + "bytes": 230, + "sha256": "e7e16d6f14705c58c00f64d0f4183cfd6efa3a0c1a0e104c4242a4de75ce0b6a", + "exit_code": 1, + "stdout": "incomplete", + "stderr": "" + }, + { + "tab": 2, + "kind": "powershell", + "variant": "original", + "bytes": 4373, + "sha256": "06d376c5709d01185a5696726e5ed2a412184b5e1df886ac948c1ac64b349f4c", + "exit_code": 0, + "stdout": "complete", + "stderr": "" + }, + { + "tab": 3, + "kind": "powershell", + "variant": "original", + "bytes": 4373, + "sha256": "06d376c5709d01185a5696726e5ed2a412184b5e1df886ac948c1ac64b349f4c", + "exit_code": 0, + "stdout": "complete", + "stderr": "" + } + ] + }, + "regression": { + "red": { + "base_commit": "ab360c7aebeb8e9afb5e61d386081abd8e3e8278", + "helper_changed": false, + "tests_changed": true, + "files_sha256": { + "experiments/shell/transport_completion.hpp": "535ff47bfbdd1038dca22820b467d1ee0d1839a29be38cd750e4de49439b602f", + "experiments/shell/transport_completion_test.cpp": "1df5bbaddc0bdf1d063bd94708939688d7c2f32a9ca23cb7979b5709002dde3c", + "build/candidate/bin/Release/transport_completion_test.exe": "4ebcad595f55474a3602f33a0720550aba4de10aed512875a437324ce2ae9352", + "local/r88-restore-oracle/red-test.log": "724e968e4f09d21069d2fa3b5ec9bfc79eed48ed561ff947f9f9d67e9bcb2482" + } + }, + "red_exit_code": 1, + "red_error": "wrapped command echo must not hide later independent output and prompt", + "final_test_source_sha256": "3761c858c97e5975d51a5c552104ba8457a5183070b12c0f635a7da42647e80d", + "green_log_sha256": "2cbd854c42e58401a37020b1dd40d4a3f06cd63668c3d942a61da4498c38fce9", + "green_passed": true, + "note": "Red and green retain the exact wrapped_cmd trigger and single-CR control. Final tests additionally include one fresh-LF positive and three negative cases; complete test sources are not identical." + }, + "targeted": { + "method": "ShellTests.test_workspace_v2_multiple_shells_split_and_geometry_restore", + "passed": true, + "seconds": 10.901, + "log_sha256": "c9a30d2427e825ca926fd9cf2f3ac30732f375c89c707f96acf30a776a07ce27", + "cases": [ + { + "repeat": 0, + "mode": "save", + "sample_sha256": "704a24d77efe914364b1942fd416e839a4f5cdd6788585d272ca1ec72584e293", + "geometry_sha256": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "layout_sha256": "7dca24d3d78fbbdf53784c68f808add4ca4c976db31cf3d5b85f4b95a1564440", + "result": { + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + } + } + }, + { + "repeat": 0, + "mode": "restore", + "sample_sha256": "c7edac02b5de9f0945766ae59cc20688b07f500556e697e11a4fbcbc163f7c55", + "geometry_sha256": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "layout_sha256": "7dca24d3d78fbbdf53784c68f808add4ca4c976db31cf3d5b85f4b95a1564440", + "result": { + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + } + } + }, + { + "repeat": 0, + "mode": "clamped", + "sample_sha256": "19744ab851d4905387d37d639fa6406d50d2d4929f805a5e63a8df896d889ff5", + "geometry_sha256": "e1b9fdfa4c0be1cf1901dfc4678c2952f5be23b95ca1d2a434c92b17f5069ce0", + "layout_sha256": "e3ac37c9350b6b0158f9b95c199fb2e1683663d5c89e1b2c70e9a11e70e6efd3", + "result": { + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + } + } + }, + { + "repeat": 1, + "mode": "save", + "sample_sha256": "231e67a9e06720c61da7f82a56794192d6a5124cc80d645d59b27e2bd80c007e", + "geometry_sha256": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "layout_sha256": "5364bdd865f1b515f64b040d88d1e576a54116929472718fa0a43a6c31db5021", + "result": { + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + } + } + }, + { + "repeat": 1, + "mode": "restore", + "sample_sha256": "c834a7430334cce1714c6df8a916e745bba8031762cd7711de3b9e99e08be04d", + "geometry_sha256": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "layout_sha256": "5364bdd865f1b515f64b040d88d1e576a54116929472718fa0a43a6c31db5021", + "result": { + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + } + } + }, + { + "repeat": 1, + "mode": "clamped", + "sample_sha256": "a3c5d95d9167cf40185cd6214b6f16a6e5a5d384a69d42d36d706d61df85b230", + "geometry_sha256": "e1b9fdfa4c0be1cf1901dfc4678c2952f5be23b95ca1d2a434c92b17f5069ce0", + "layout_sha256": "815480efcb4523f66552fc0afcc8724f075ff1f3db1214d2cb9a2e2d380593ae", + "result": { + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + } + } + } + ], + "contract": "Original two rounds of save, ordinary restore, clamped restore; exact directories/version/membership/title/zoom/split/geometry/child dimensions and Job/desktop checks; original 30/40 second deadlines." + }, + "final_full_ctest": { + "log_sha256": "8fe637e73b7dbbc0aa5025ad8620933626ac80c42b09e715cd840bdff6a691fc", + "tests": [ + { + "name": "shell_launch", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "candidate_title", + "result": "passed", + "seconds": 0.7 + }, + { + "name": "candidate_reply_close", + "result": "passed", + "seconds": 0.35 + }, + { + "name": "runtime_observation", + "result": "passed", + "seconds": 1.52 + }, + { + "name": "ime_input_expectation", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "sent_message_wait", + "result": "passed", + "seconds": 0.27 + }, + { + "name": "find_bar", + "result": "passed", + "seconds": 0.36 + }, + { + "name": "renderer_metrics", + "result": "passed", + "seconds": 0.86 + }, + { + "name": "ime_context", + "result": "passed", + "seconds": 0.37 + }, + { + "name": "layout_state", + "result": "passed", + "seconds": 0.16 + }, + { + "name": "workspace_state", + "result": "passed", + "seconds": 0.36 + }, + { + "name": "transport_completion", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "scenario_tick_gate", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "candidate_core", + "result": "passed", + "seconds": 1.22 + }, + { + "name": "resize_core", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "history_core", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "selection_core", + "result": "passed", + "seconds": 0.09 + }, + { + "name": "search_core", + "result": "passed", + "seconds": 0.13 + }, + { + "name": "paste", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "sender_fence", + "result": "passed", + "seconds": 1.03 + }, + { + "name": "sender_fence_pipe", + "result": "passed", + "seconds": 0.38 + }, + { + "name": "clipboard_policy", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "keyboard", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "text_action_policy", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "candidate_resize_replay", + "result": "passed", + "seconds": 0.67 + }, + { + "name": "candidate_shell_tui", + "result": "failed", + "seconds": 31.3 + }, + { + "name": "core", + "result": "passed", + "seconds": 6.1 + }, + { + "name": "recovery", + "result": "passed", + "seconds": 9.92 + }, + { + "name": "measure", + "result": "passed", + "seconds": 3.93 + }, + { + "name": "binary_paths", + "result": "passed", + "seconds": 0.18 + }, + { + "name": "conpty_margin_contract", + "result": "passed", + "seconds": 0.28 + }, + { + "name": "benchmark_tools", + "result": "passed", + "seconds": 5.35 + }, + { + "name": "differential", + "result": "passed", + "seconds": 19.61 + }, + { + "name": "ime_inventory", + "result": "passed", + "seconds": 49.71 + }, + { + "name": "text_actions", + "result": "passed", + "seconds": 5.51 + }, + { + "name": "workflow_fixture", + "result": "passed", + "seconds": 22.78 + }, + { + "name": "workflow_sessions", + "result": "passed", + "seconds": 80.64 + }, + { + "name": "workflow_resources", + "result": "passed", + "seconds": 0.58 + }, + { + "name": "workflow_resource_phases", + "result": "passed", + "seconds": 4.34 + }, + { + "name": "workflow_system_content", + "result": "passed", + "seconds": 0.16 + }, + { + "name": "workflow_system_runner", + "result": "passed", + "seconds": 0.64 + }, + { + "name": "copy_error_frame", + "result": "passed", + "seconds": 0.14 + }, + { + "name": "paste_fence_evidence", + "result": "passed", + "seconds": 0.1 + }, + { + "name": "resize_trace", + "result": "passed", + "seconds": 0.11 + }, + { + "name": "research_guards", + "result": "passed", + "seconds": 5.01 + }, + { + "name": "receive", + "result": "passed", + "seconds": 0.22 + }, + { + "name": "conpty", + "result": "passed", + "seconds": 22.88 + }, + { + "name": "conpty_ctrl_c", + "result": "passed", + "seconds": 4.33 + }, + { + "name": "conpty_margin", + "result": "passed", + "seconds": 0.4 + }, + { + "name": "conpty_backend", + "result": "passed", + "seconds": 1.29 + }, + { + "name": "presentation", + "result": "passed", + "seconds": 4.11 + }, + { + "name": "shell", + "result": "passed", + "seconds": 93.71 + }, + { + "name": "selection_bytes", + "result": "passed", + "seconds": 3.66 + }, + { + "name": "selection_context", + "result": "passed", + "seconds": 3.76 + }, + { + "name": "raw_console_receiver", + "result": "passed", + "seconds": 0.38 + }, + { + "name": "paste_system", + "result": "passed", + "seconds": 8.31 + } + ], + "total": 56, + "passed": 55, + "failed": [ + "candidate_shell_tui" + ], + "seconds": 398.08, + "failure_stages": [ + 59 + ], + "full_acceptance": false, + "last_test_log_sha256": "35d684a3b3ec9d593cccc3243ea8a8d2e39a76fc0746c137e942f2d65ff2d75b", + "internal_skips": [ + { + "test": "test_actual_system_copy_paste_and_shortcuts_twice (experiments.shell.test_actions.ActionWindowTest.test_actual_system_copy_paste_and_shortcuts_twice)", + "reason": "actual system Copy/Paste requires a disposable hosted VM" + }, + { + "test": "test_suspended_target_is_reclaimed_if_job_assignment_fails (test_ui_r1.EvidenceBoundaryTests.test_suspended_target_is_reclaimed_if_job_assignment_fails)", + "reason": "opt-in pinned reference failure injection" + }, + { + "test": "test_mouse_selection_to_real_system_clipboard (experiments.shell.test_shell.ShellTests.test_mouse_selection_to_real_system_clipboard)", + "reason": "system copy UI gate requires a disposable hosted VM" + }, + { + "test": "test_system_clipboard_product_read_errors_cancel_and_close (experiments.shell.test_shell.ShellTests.test_system_clipboard_product_read_errors_cancel_and_close)", + "reason": "product clipboard read gate requires a disposable hosted VM" + }, + { + "test": "test_system_clipboard_shortcut_to_window_confirmation_and_receiver (experiments.shell.test_shell.ShellTests.test_system_clipboard_shortcut_to_window_confirmation_and_receiver)", + "reason": "real system clipboard UI gate requires a disposable hosted VM" + }, + { + "test": "test_real_system_copy_shortcuts_for_styled_fixture_twice (experiments.shell.test_selection_bytes.SelectionBytesWindowTest.test_real_system_copy_shortcuts_for_styled_fixture_twice)", + "reason": "system CF requires a disposable hosted VM" + }, + { + "test": "test_real_system_copy_and_edit_context_twice (experiments.shell.test_selection_context.SelectionContextWindowTest.test_real_system_copy_and_edit_context_twice)", + "reason": "real CF/EDIT Copy requires a disposable hosted VM" + }, + { + "test": "test_real_system_clipboard (experiments.shell.test_paste.PasteTests.test_real_system_clipboard)", + "reason": "Private window station OpenClipboard denied (5); system API gate remains unverified." + } + ], + "log_encoding_note": "Localized CTest date header is not UTF-8; only ASCII skip fields are extracted.", + "shell_unittest": { + "run": 40, + "passed": 37, + "errors": 0, + "skipped": 3, + "seconds": 93.494 + }, + "optional_trace_note": "The .5 trace executable is now built; four previously skipped differential classes executed. Eight remaining internal skips are listed separately." + }, + "excluded_overlapping_build": { + "reason": "Targeted method started before the changed shell finished linking. Full CTest also started before overall build completion; retain both only as diagnostics and repeat after completed build.", + "targeted_log_sha256": "410952fb9bc506d798b53102a9c96c97e50f79fe10a8baaebf4bf524537bb8fb", + "diagnostic_full_ctest": { + "log_sha256": "7f5b1cd6692cd76112c71f7bb906684d25a495d24b3252c1481c07b9eba454c4", + "tests": [ + { + "name": "shell_launch", + "result": "passed", + "seconds": 0.06 + }, + { + "name": "candidate_title", + "result": "passed", + "seconds": 1.03 + }, + { + "name": "candidate_reply_close", + "result": "passed", + "seconds": 0.37 + }, + { + "name": "runtime_observation", + "result": "passed", + "seconds": 1.54 + }, + { + "name": "ime_input_expectation", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "sent_message_wait", + "result": "passed", + "seconds": 0.29 + }, + { + "name": "find_bar", + "result": "passed", + "seconds": 0.41 + }, + { + "name": "renderer_metrics", + "result": "passed", + "seconds": 1.24 + }, + { + "name": "ime_context", + "result": "passed", + "seconds": 0.33 + }, + { + "name": "layout_state", + "result": "passed", + "seconds": 0.15 + }, + { + "name": "workspace_state", + "result": "passed", + "seconds": 0.43 + }, + { + "name": "transport_completion", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "scenario_tick_gate", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "candidate_core", + "result": "passed", + "seconds": 1.21 + }, + { + "name": "resize_core", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "history_core", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "selection_core", + "result": "passed", + "seconds": 0.09 + }, + { + "name": "search_core", + "result": "passed", + "seconds": 0.13 + }, + { + "name": "paste", + "result": "passed", + "seconds": 0.02 + }, + { + "name": "sender_fence", + "result": "passed", + "seconds": 1.03 + }, + { + "name": "sender_fence_pipe", + "result": "passed", + "seconds": 0.36 + }, + { + "name": "clipboard_policy", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "keyboard", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "text_action_policy", + "result": "passed", + "seconds": 0.01 + }, + { + "name": "candidate_resize_replay", + "result": "passed", + "seconds": 0.79 + }, + { + "name": "candidate_shell_tui", + "result": "failed", + "seconds": 30.49 + }, + { + "name": "core", + "result": "passed", + "seconds": 6.25 + }, + { + "name": "recovery", + "result": "passed", + "seconds": 9.96 + }, + { + "name": "measure", + "result": "passed", + "seconds": 4.3 + }, + { + "name": "binary_paths", + "result": "passed", + "seconds": 0.2 + }, + { + "name": "conpty_margin_contract", + "result": "passed", + "seconds": 0.28 + }, + { + "name": "benchmark_tools", + "result": "passed", + "seconds": 5.33 + }, + { + "name": "differential", + "result": "passed", + "seconds": 3.35 + }, + { + "name": "ime_inventory", + "result": "passed", + "seconds": 51.23 + }, + { + "name": "text_actions", + "result": "passed", + "seconds": 5.57 + }, + { + "name": "workflow_fixture", + "result": "passed", + "seconds": 22.66 + }, + { + "name": "workflow_sessions", + "result": "passed", + "seconds": 82.77 + }, + { + "name": "workflow_resources", + "result": "passed", + "seconds": 0.53 + }, + { + "name": "workflow_resource_phases", + "result": "passed", + "seconds": 4.31 + }, + { + "name": "workflow_system_content", + "result": "passed", + "seconds": 0.16 + }, + { + "name": "workflow_system_runner", + "result": "passed", + "seconds": 0.64 + }, + { + "name": "copy_error_frame", + "result": "passed", + "seconds": 0.14 + }, + { + "name": "paste_fence_evidence", + "result": "passed", + "seconds": 0.11 + }, + { + "name": "resize_trace", + "result": "passed", + "seconds": 0.12 + }, + { + "name": "research_guards", + "result": "passed", + "seconds": 4.97 + }, + { + "name": "receive", + "result": "passed", + "seconds": 0.21 + }, + { + "name": "conpty", + "result": "passed", + "seconds": 23.52 + }, + { + "name": "conpty_ctrl_c", + "result": "passed", + "seconds": 4.36 + }, + { + "name": "conpty_margin", + "result": "passed", + "seconds": 0.4 + }, + { + "name": "conpty_backend", + "result": "passed", + "seconds": 1.23 + }, + { + "name": "presentation", + "result": "passed", + "seconds": 4.08 + }, + { + "name": "shell", + "result": "passed", + "seconds": 94.42 + }, + { + "name": "selection_bytes", + "result": "passed", + "seconds": 3.59 + }, + { + "name": "selection_context", + "result": "passed", + "seconds": 3.86 + }, + { + "name": "raw_console_receiver", + "result": "passed", + "seconds": 0.42 + }, + { + "name": "paste_system", + "result": "passed", + "seconds": 10.02 + } + ], + "total": 56, + "passed": 55, + "failed": [ + "candidate_shell_tui" + ], + "seconds": 389.17, + "failure_stages": [ + 59 + ], + "full_acceptance": false, + "usable_for_acceptance": false + }, + "usable_for_acceptance": false + }, + "limits": [ + "Raw original transports and workspace files remain local; public regression source contains only fixed synthetic commands and no machine path.", + "This corrects a test completion oracle, not a demonstrated product output-loss defect.", + "Separate configuration results and unresolved #52/#63/#87 remain independent; later green runs do not explain earlier failures.", + "Real CF/IME, actual presentation, performance, WindTerm equivalence and full R2 acceptance are not supplied by this fix." + ], + "presented_done": null, + "performance_acceptance": false, + "windterm_runtime_equivalence": false, + "full_r2_verified": false +} diff --git a/experiments/shell/results/2026-10-27-workspace-empty-cr/validation.json b/experiments/shell/results/2026-10-27-workspace-empty-cr/validation.json new file mode 100644 index 00000000..1f029aff --- /dev/null +++ b/experiments/shell/results/2026-10-27-workspace-empty-cr/validation.json @@ -0,0 +1,3195 @@ +{ + "source_commit": "4daf99250a2740e1943635b6d46293d633427163", + "platform": "Windows-11-10.0.26200-SP0", + "python": "3.12.10", + "started_utc": "2026-10-26T16:35:57.098152+00:00", + "source_sha256": { + "CMakeLists.txt": "f5bb85e12b7a815c4feb7417da9ebc5298de58664bcd2792b277cb282729da73", + "experiments/shell/transport_completion.hpp": "535ff47bfbdd1038dca22820b467d1ee0d1839a29be38cd750e4de49439b602f", + "experiments/shell/transport_completion_test.cpp": "c9bb02bd739f916e78530590ee0f3b29bc952c58e445d31bc047ee69d301dcee", + "experiments/shell/workspace_persistence_scenario.hpp": "7f70d755549718d0fddb94b43cce12f3955727fcc51599a8bb124bf3e59ec263", + "experiments/shell/test_shell.py": "a6b63c1754b0e0dc1cc01863891f4c7c9d989f818d351838a8cfe295edaf811c", + "experiments/shell/run.py": "45976b828eecb316881d698dedaef09ad304f72d991dfa360f1c552bbcbe9f1d", + "experiments/shell/shell.cpp": "ac4dd44861aa2928213fffe91d30cdaf3aedf19f08468aefeed22828352296cc", + "experiments/shell/test_launcher.cpp": "19cab4a960f47f252c02f92a36316d43eed2481da1f72b5a7e97003c4fa1c2ed", + "experiments/core/candidate_terminal.cpp": "3092ee2ea44ca0f897a7d3b6244c079ba8073dc109993204e435f065388a2ed1", + "experiments/candidates/alacritty/Cargo.lock": "896c6e60909d55d61fb26d277b1a98daf9245e8aee811caff37fc3e9d06ca627" + }, + "source_git_blobs": { + "CMakeLists.txt": "2454a7b8dc34fa2bbd74b25b0ad77d19a71a5f18", + "experiments/shell/transport_completion.hpp": "e038584c5d719d2890ce58b292d5c2ac27c94ca2", + "experiments/shell/transport_completion_test.cpp": "70a412afaa644413460ada14f8c28c91b206d509", + "experiments/shell/workspace_persistence_scenario.hpp": "f7f73dbd771f559384d9b4e569d815d2ef0792da", + "experiments/shell/test_shell.py": "66512ac1099cf477cac8f26f225b18c301c1d3d8", + "experiments/shell/run.py": "bbd4dfc05012c55956973d7e2c69be717b48b1a9", + "experiments/shell/shell.cpp": "90b3f166e8a50ca2488f749e8e188c52cb5e4a91", + "experiments/shell/test_launcher.cpp": "6da0882255cd9587111aa4536b1f0b5a93b193bf", + "experiments/core/candidate_terminal.cpp": "9bb1fab8f6deac294d0ba1a44712b67c919d03c7", + "experiments/candidates/alacritty/Cargo.lock": "6501189a98bfe0610ebcb5e3c31d3abc2ae3f132" + }, + "configurations": [ + { + "build_config": "system-r72", + "candidate": false, + "backend": "system", + "binaries_sha256": { + "shell_clipboard_reader.exe": "6afbaba725490cc3856df97b56a8a16190251b27fa5c34024c63132a6c6bde41", + "shell_ime_client.exe": "2f032970045e33b80b615e0ffebe06e29ebe7b18698b7609bffc129af9b4d7df", + "shell_input_client.exe": "0471578cce2959b200c2e174514cbd8663e2b51db1bb973f8932ebe5a866f1b7", + "shell_query_client.exe": "d92be37207a6a7a4c03d4b08dd1f704ccddc2e86b7ef00085e028168d1a915e1", + "shell_selection_text_client.exe": "0ec9d66d66e672e98487bf38ee4db4313ac7bdad8b6852ebdc8fcd6309fd4358", + "shell_size_client.exe": "323583d4127d4aca27026cbe3ef9328a80390a269e99be763580f706c1cb4115", + "shell_test_launcher.exe": "3e72c4d5bd418eaa22062606f39891f0412acaf46a2841a3f9d219f24bc33014", + "shell_text_client.exe": "f151602090a8700fd48115cd4db1169a9f2cfe8e856f454bc21bf1509ddff85b", + "termforge_shell.exe": "433905e75f4f12699e59adc8c8775756f9098125aa68595d2dec6290c334dfeb", + "transport_completion_test.exe": "ba1c84b60d68d366c9d9e15e0b11e96b84e7066376c805b6ee0122c7defedc67" + }, + "cmake_cache_sha256": "27b22b5286dfc2aba8325201d143f7102400812bf27667300025232fc6394500", + "tests": [ + { + "name": "transport-completion", + "returncode": 0, + "started_utc": "2026-10-26T16:35:57.572260+00:00", + "elapsed_seconds": 0.141528, + "log_sha256": "c804b0583bcb4501edf84ab54dfe03ddcf8aeb00f7e6ffddf65d25cf6e031cb0" + }, + { + "name": "workspace", + "returncode": 0, + "started_utc": "2026-10-26T16:35:57.713788+00:00", + "elapsed_seconds": 10.657289, + "log_sha256": "eae576610ed3c3de80350f33f0d094c51b612339639b0fcf26108f3ba3eb80c2" + } + ], + "cases": [ + { + "case": "workspace-v2-clamped-0", + "sample": { + "mode": "ui-workspace-restore", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-restore", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 0, + 60, + 2560, + 799 + ], + "client": [ + 2544, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 2544, + 238 + ], + "visible": 1, + "grid": [ + 210, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 1272, + 321 + ], + "visible": 1, + "grid": [ + 69, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 1272, + 93, + 1272, + 321 + ], + "visible": 1, + "grid": [ + 139, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "e1b9fdfa4c0be1cf1901dfc4678c2952f5be23b95ca1d2a434c92b17f5069ce0", + "sample.json": "78f0976c47b06a5007bd588febd69f08bf2e3c48ac4476d548896ff6a4af319f", + "workspace.layout": "11f3b16527405dd1119da84184d416509f384d791371fca82644f257d306e87e" + } + }, + { + "case": "workspace-v2-clamped-1", + "sample": { + "mode": "ui-workspace-restore", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-restore", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 0, + 60, + 2560, + 799 + ], + "client": [ + 2544, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 2544, + 238 + ], + "visible": 1, + "grid": [ + 210, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 1272, + 321 + ], + "visible": 1, + "grid": [ + 69, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 1272, + 93, + 1272, + 321 + ], + "visible": 1, + "grid": [ + 139, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "e1b9fdfa4c0be1cf1901dfc4678c2952f5be23b95ca1d2a434c92b17f5069ce0", + "sample.json": "2d802b2f19ac76b98bae2a8455fc094be1abb857543062072d52bdac7d27b74e", + "workspace.layout": "657ae2809533be0c5138485fed93babf98a75061aea17e0f18a4dc74f5913cae" + } + }, + { + "case": "workspace-v2-restore-0", + "sample": { + "mode": "ui-workspace-restore", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-restore", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 90, + 60, + 1216, + 799 + ], + "client": [ + 1200, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 1200, + 238 + ], + "visible": 1, + "grid": [ + 98, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 32, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 600, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 64, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "fa0f1df0488dc2f6c7f650ade82dd1ebe12d40a14ae7b12419dc9f569928daab", + "workspace.layout": "59e62adef0b758158c65b56919350064f7d2c344906d2c848bab80e6f32739de" + } + }, + { + "case": "workspace-v2-restore-1", + "sample": { + "mode": "ui-workspace-restore", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-restore", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 90, + 60, + 1216, + 799 + ], + "client": [ + 1200, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 1200, + 238 + ], + "visible": 1, + "grid": [ + 98, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 32, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 600, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 64, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "95b7d480f900492b5ef1cb2d75ee939eb9bb2c4aa047be9dd213fb49371525fd", + "workspace.layout": "37b9ad8d5a982460f484042018baed17340c713c7e5d361376a1ebd3c31fb21d" + } + }, + { + "case": "workspace-v2-save-0", + "sample": { + "mode": "ui-workspace-save", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-save", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 90, + 60, + 1216, + 799 + ], + "client": [ + 1200, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 1200, + 238 + ], + "visible": 1, + "grid": [ + 98, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 32, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 600, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 64, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "cc79cd9828da737c892cf94466baf7fceda5844c42fee17c140dec83a6ef5275", + "workspace.layout": "59e62adef0b758158c65b56919350064f7d2c344906d2c848bab80e6f32739de" + } + }, + { + "case": "workspace-v2-save-1", + "sample": { + "mode": "ui-workspace-save", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-save", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 90, + 60, + 1216, + 799 + ], + "client": [ + 1200, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 1200, + 238 + ], + "visible": 1, + "grid": [ + 98, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 32, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 600, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 64, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "1316f55f86c6af9c425b9b5d996d0d9fad6b16f3745fda847ca9b798bbe0bb1d", + "workspace.layout": "37b9ad8d5a982460f484042018baed17340c713c7e5d361376a1ebd3c31fb21d" + } + } + ] + }, + { + "build_config": "candidate-system-r72", + "candidate": true, + "backend": "system", + "binaries_sha256": { + "shell_clipboard_reader.exe": "71faaa8009762d7bfe0186cc499422762f532581288eb57fde949d7774872e98", + "shell_ime_client.exe": "c671b3bcc051f2f4397467fa52029a964a721613841913e7c2e334355d4e5236", + "shell_input_client.exe": "a550513b18161e05bae681d4c9ddf79cb1a86d42bafb3a7518057a44105b99c2", + "shell_query_client.exe": "58e704e2090c4b9b0550622c84cb4ffafc6f6f42df1cdfe363a0e99e6e7db486", + "shell_selection_text_client.exe": "37862fcc2f1db49118d10c3e03dac1be851bf255cbebe2f0556f89062342bc18", + "shell_size_client.exe": "427afbd778881f5e921a1782d473ad190a1340fb691b95adce9e138a516317b5", + "shell_test_launcher.exe": "cb96d03fed43d7e63e820e0ced6cf0f241762b0cc4dbec094c458041f38f1e55", + "shell_text_client.exe": "b81cba00bdaed52c50783d6fa0a1528301e3df40d2c6c4e060aa01a9c2f81ce3", + "termforge_shell.exe": "6c756b03e445dfec9a1fd4fd8b5d628f031076d546defd4e3f3eb0c546e1cb4d", + "transport_completion_test.exe": "1945189627c71d0bedd1f8875f6bb80da6065246c687fa8651efa205346d336b" + }, + "cmake_cache_sha256": "65d497ede16ae771b8f5bad312df0d7dc98e70c095622bfbec009f455c6decdb", + "tests": [ + { + "name": "transport-completion", + "returncode": 0, + "started_utc": "2026-10-26T16:36:08.380170+00:00", + "elapsed_seconds": 0.144565, + "log_sha256": "173c950ca1f323012ddd687b96628b2bd747bbf63e28a971fef46fa28b0e2e66" + }, + { + "name": "workspace", + "returncode": 0, + "started_utc": "2026-10-26T16:36:08.524735+00:00", + "elapsed_seconds": 10.226751, + "log_sha256": "276fc4b4f011a8e5eae6e3b748d01a83b0f907771d7463fb7677c23470b846f4" + } + ], + "cases": [ + { + "case": "workspace-v2-clamped-0", + "sample": { + "mode": "ui-workspace-restore", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-restore", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 0, + 60, + 2560, + 799 + ], + "client": [ + 2544, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 2544, + 238 + ], + "visible": 1, + "grid": [ + 210, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 1272, + 321 + ], + "visible": 1, + "grid": [ + 69, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 1272, + 93, + 1272, + 321 + ], + "visible": 1, + "grid": [ + 139, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "e1b9fdfa4c0be1cf1901dfc4678c2952f5be23b95ca1d2a434c92b17f5069ce0", + "sample.json": "4120d7f440c8effc70b25ba522cbc3b44a64999a4e1875f467478fe96b03245a", + "workspace.layout": "2b84a08b2af758e35a196ad71dc3356a5c15836c9ff301845e5a0fcc80fc767a" + } + }, + { + "case": "workspace-v2-clamped-1", + "sample": { + "mode": "ui-workspace-restore", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-restore", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 0, + 60, + 2560, + 799 + ], + "client": [ + 2544, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 2544, + 238 + ], + "visible": 1, + "grid": [ + 210, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 1272, + 321 + ], + "visible": 1, + "grid": [ + 69, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 1272, + 93, + 1272, + 321 + ], + "visible": 1, + "grid": [ + 139, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "e1b9fdfa4c0be1cf1901dfc4678c2952f5be23b95ca1d2a434c92b17f5069ce0", + "sample.json": "0b54da0e331d564807006ede5bc5141d6ebb3dcbc78fc703ec9afb84e9e835f8", + "workspace.layout": "b396797597866e3036655c455b67ea7f3525fb73bc83dcd4a6124740f6a6a00d" + } + }, + { + "case": "workspace-v2-restore-0", + "sample": { + "mode": "ui-workspace-restore", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-restore", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 90, + 60, + 1216, + 799 + ], + "client": [ + 1200, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 1200, + 238 + ], + "visible": 1, + "grid": [ + 98, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 32, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 600, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 64, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "bb7dcffd003f6836c81ebe2c1a76add10137e75c17de550d4c79f02cf09ac770", + "workspace.layout": "fad7156f6463e92356c52c64121b793c2989eada6377228f502b314dcc1cd822" + } + }, + { + "case": "workspace-v2-restore-1", + "sample": { + "mode": "ui-workspace-restore", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-restore", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 90, + 60, + 1216, + 799 + ], + "client": [ + 1200, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 1200, + 238 + ], + "visible": 1, + "grid": [ + 98, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 32, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 600, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 64, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "bb37ee8b05088b72c466f87a716434b2ad1eee5e83f5597a8c438de7c729415b", + "workspace.layout": "dff8885eeadffc72e58dc84b031fe4d6a3179edeffce82853c5f480e12c6d055" + } + }, + { + "case": "workspace-v2-save-0", + "sample": { + "mode": "ui-workspace-save", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-save", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 90, + 60, + 1216, + 799 + ], + "client": [ + 1200, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 1200, + 238 + ], + "visible": 1, + "grid": [ + 98, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 32, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 600, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 64, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "ed0cfb5a2ddcbffb67515f70aceca1817e78da50eee4979166aeae7e07bed292", + "workspace.layout": "fad7156f6463e92356c52c64121b793c2989eada6377228f502b314dcc1cd822" + } + }, + { + "case": "workspace-v2-save-1", + "sample": { + "mode": "ui-workspace-save", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-save", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 90, + 60, + 1216, + 799 + ], + "client": [ + 1200, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 1200, + 238 + ], + "visible": 1, + "grid": [ + 98, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 32, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 600, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 64, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "ab509cefe2e4a9dde03dc8d9e12b0d3060026a8b8392db9a847a004321544d1b", + "workspace.layout": "dff8885eeadffc72e58dc84b031fe4d6a3179edeffce82853c5f480e12c6d055" + } + } + ] + }, + { + "build_config": "candidate", + "candidate": true, + "backend": "reviewed-side-by-side-1.25.260930003", + "binaries_sha256": { + "alpha_normal_smoke.exe": "36ac6dbe36c942b9850d6d9f5b3538fbfe2fc251f38ac026ea12c343b0e9abc8", + "candidate_core_test.exe": "6e9df1abbe2271690a1480a1be9042e219a5947779b6a4b03f529a742fbd10ce", + "candidate_reply_close_test.exe": "40603aa54fbad42d55a5a7ea24b7aecebefdacd8f3c802e3ac89c52ef3a4b74d", + "candidate_resize_replay.exe": "3d91227adb37bba4defcef8f803f0f3c2916001223300b99d18e0e2ab695de94", + "candidate_title_test.exe": "90db63148f6aafaf79d65d5b40e61bbdc9f54486a6be36e26523699e0b46035d", + "clipboard_policy_test.exe": "809815e9838cad5b3443c98620d23a4e6c7186b5304b524d0990988eb47a6da9", + "conpty_backend_probe.exe": "3c54962c20f04e688a0413bb822713e908582af20b0be8bcfd188fb436907ef8", + "conpty_ctrl_c_probe.exe": "ac5ae5ccdb77c82512a8a6d769fafbe74693bf6fc535ce4d22bf60b0c8df2bfa", + "conpty_margin_probe.exe": "5404b009d0e005cd940d8a65afeb0fb9caf9c9ce2a18f651a88146343c41c252", + "core_measure.exe": "df9df797b16239d2fb71707f8a042a1ed956ad30c407d884527cdf18d600e785", + "find_bar_test.exe": "030733a2396ca9b31bbd029c610f1b838ab52394338c08410274501747dd9982", + "history_test.exe": "035507366facfd6de00e0c2762775c8d3816f605da3e55d498995a74a3f447d2", + "ime_context_test.exe": "be0d71ca72aee3dbd1565c9ad2314f81f0cd0c5511ec54cb623bbd39eede9974", + "ime_input_expectation_test.exe": "a5ea4532a06b893d955299793b4859cb9ccf221fab64e51949e1af2ee6ba5d0b", + "keyboard_test.exe": "18a35c2b2ab8a6316e0c938d8c36d0b82bcc131989d683205888b08bf4d33e19", + "layout_state_test.exe": "307519003536ce16d7d6fc552b3a25e9de25356d50a80c2de0c01168a10784a9", + "paste_test.exe": "47e9a5ce33335370b6fb199b0ff11adc19e2ab55bf64657061a13c1c020a24b0", + "presentation_probe.exe": "b7043cace46f65b22c7295df2fc06b214f060c2fd6fe18ac5ff27e13d943e096", + "raw_console_receiver_test.exe": "9c05cdb80d87ff264b3275387eb01dabc8f57640adbc7ee3910b0fc44f378224", + "real_app_capture.exe": "011efaad94da74ce182e30a3ff8abb0c1e1f58235bc77fa8c347ec3d9dcb822e", + "receive.exe": "ef316e11ddcc764ddac57426fc37b8b69f599ddb28a0ffedf5c2e85a3004a9d4", + "recovery_probe.exe": "c84e7b1b1792773b993f2758d5cafa59b80495451c01d194c3afd6cc653f0344", + "renderer_metrics_test.exe": "4cebad62798ff0f8eac42641a1119c7b9725728dd5fd31befcdf7e1ecb686cfd", + "replay.exe": "674f96ebbf00497576951a6780a5cf5195e3b80cc4acdb7f296fa8910a6203b7", + "resize_test.exe": "f566c56a1fa34e2a8ff480900ac5ae5059e2ac6872554208d5dbf0e24673dde0", + "runtime_observation_test.exe": "dfd04533d35d78401da75ad7d0965d923d7b7e95ad304775367b6088acc341ed", + "scenario_tick_gate_test.exe": "06cd4e813f1034d1a8c7fea592979f49c3ef89117c97fc6636cdae5012017ce8", + "search_test.exe": "0f29f3ea3147cc45eb2e500e683f3bcc1db04f820470b2ff473fffcb31cdb4f4", + "selection_test.exe": "104576787b62c2c4bb2428e0786c7742e63a23ceb766df293c88a5ccc6a1b836", + "sender_fence_pipe_test.exe": "6a94ac84631434a81ae1098932813d7ceeb81f2bb33a2e4b2456834bd8972e92", + "sender_fence_test.exe": "ab4af3b0f148f6e6377e70c98936c6c34d0be0d3cee4b17b3f8554c39b2d3e52", + "sent_message_wait_test.exe": "bc3cff14f60e3f1b17b3fd6255ff1c5b507f506d897d528802efa9666be74dcf", + "shell_clipboard_reader.exe": "bacc4bff465ca644fae61f9d26184a7adc22df9793470feca647af1d823fece8", + "shell_ime_client.exe": "d787ebf702cf5895e1ad0d71f895a51b3dead34d02953b5e7deff086c2bf8eb7", + "shell_input_client.exe": "850c82fb8bb58de8170bf096cc6dd4f8ba34d9ba11dbd2addc41508cddc31d8c", + "shell_launch_test.exe": "ace9b63148848bf7b405edb4b8818ca23d52699ac2ce8ca03a90c5bc1e32622d", + "shell_paste_probe.exe": "1370c6381b990270dfd816f53758e5aa57c63deba050fa7cf0dbd0d5cbf4f315", + "shell_query_client.exe": "71419f7918bc58f4ee47d14f8e6de35aa1828ac838a73ed607797fc120f56886", + "shell_queue_probe.exe": "7f34d6ff66b3e00d56798e22a480fb20a5afa083cff65a9d689dcbca44dbe12d", + "shell_selection_text_client.exe": "dbad81a22ecce4508eada90ae445f9a75abfe1effd2571776ef31b32144c8ac4", + "shell_size_client.exe": "5394d9863df9a589012f0d9a31f7e4167e7bc4ea23e93f9711c0a67c540651f6", + "shell_test_launcher.exe": "dbde4249dc08c7df80120398ffd252aa5e4958e4ef640cc161ea4c2d28b9f291", + "shell_text_client.exe": "9485b03424e421a4018596cc9487e7feeb33c26d30820985aa45843fdcf315d9", + "termforge_conpty_probe.exe": "7dd59bc8d0309ad5b131cde1c3ed98819139b6bb70f505347c98aabe8396f618", + "termforge_ime_probe.exe": "788a186b5fafb6d155eeab74f78d4f7cb992a599937b3aa48c5efcf49affe993", + "termforge_shell.exe": "650ea74ba3ad39b91ab4773c72af3ee70e59bc9d63e76249ea8fa6745a367081", + "terminal_trace.exe": "40e869753f4c00515ee0990f3732eac04f965fe2c3070f1bf772717476269f3d", + "text_action_policy_test.exe": "535b469c9aa0da7e77670578f75d04956d98e9f912d5c9a6c310ab796ab58d4f", + "transport_completion_test.exe": "9f63a2f8a08ddcd459d422f69bfbeae9ceaee9527edc0198c7e4fbd07bff7dc4", + "workflow_fixture_test.exe": "a6624a6b1536c6f445f92e05ff72a5318ece47d455500f6adc467d94f9f04222", + "workspace_state_test.exe": "ac3fbc8a56d33efda33cc9040c731c183e0a8666e23fce48d368c1868797714d" + }, + "cmake_cache_sha256": "d652393042e1195908894a592dda1ebf0c8e1e8ccd3e05ecacec2760b41bc2ff", + "tests": [ + { + "name": "transport-completion", + "returncode": 0, + "started_utc": "2026-10-26T16:36:18.796351+00:00", + "elapsed_seconds": 0.134451, + "log_sha256": "2bcde71179fda986d1dab2d5b84737f9d5a4c965da407e6f2f07b4c60faa49cd" + }, + { + "name": "workspace", + "returncode": 0, + "started_utc": "2026-10-26T16:36:18.932314+00:00", + "elapsed_seconds": 10.383378, + "log_sha256": "9004ff6178e1bec317c7c8de5c70c0a7318ef5a298aae3e48a161e85b05a2b34" + } + ], + "cases": [ + { + "case": "workspace-v2-clamped-0", + "sample": { + "mode": "ui-workspace-restore", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-restore", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 0, + 60, + 2560, + 799 + ], + "client": [ + 2544, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 2544, + 238 + ], + "visible": 1, + "grid": [ + 210, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 1272, + 321 + ], + "visible": 1, + "grid": [ + 69, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 1272, + 93, + 1272, + 321 + ], + "visible": 1, + "grid": [ + 139, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "e1b9fdfa4c0be1cf1901dfc4678c2952f5be23b95ca1d2a434c92b17f5069ce0", + "sample.json": "4f5443ddb183618cbe787be35499c53a9c5dc71383e1242f44b98b565ecb2794", + "workspace.layout": "85ad2c772bbe4ae1a16ac48dcb4d6d52dfaed5a50f60cbf818a6538d94fa32af" + } + }, + { + "case": "workspace-v2-clamped-1", + "sample": { + "mode": "ui-workspace-restore", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-restore", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 0, + 60, + 2560, + 799 + ], + "client": [ + 2544, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 2544, + 238 + ], + "visible": 1, + "grid": [ + 210, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 1272, + 321 + ], + "visible": 1, + "grid": [ + 69, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 1272, + 93, + 1272, + 321 + ], + "visible": 1, + "grid": [ + 139, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "e1b9fdfa4c0be1cf1901dfc4678c2952f5be23b95ca1d2a434c92b17f5069ce0", + "sample.json": "88bd123a353389461c7e3f69a05baac16695a3b1a43c70dfbf4d8818f996561d", + "workspace.layout": "eaa185a9288a25bab6e5d3518fb87182b9959b3b591a1840a0699cb999c62fff" + } + }, + { + "case": "workspace-v2-restore-0", + "sample": { + "mode": "ui-workspace-restore", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-restore", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 90, + 60, + 1216, + 799 + ], + "client": [ + 1200, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 1200, + 238 + ], + "visible": 1, + "grid": [ + 98, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 32, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 600, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 64, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "61e5af44331e37e52fc4f46e10f56e8ba11f2c1316d3a123ce79d3dbda073d2e", + "workspace.layout": "c5cc46a5395e488830e636e2cae90b038fe5eaac0cb3244395754c24830757fe" + } + }, + { + "case": "workspace-v2-restore-1", + "sample": { + "mode": "ui-workspace-restore", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-restore", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 90, + 60, + 1216, + 799 + ], + "client": [ + 1200, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 1200, + 238 + ], + "visible": 1, + "grid": [ + 98, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 32, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 600, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 64, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "23088bdaea3a39e571c3407357847fc1559b62839b5a5fd13bad69bc8f0bf9d5", + "workspace.layout": "ce7ccde5387ee25fc6ef9e2485f794d737406942d0cdbd72970c33938bf0cd8d" + } + }, + { + "case": "workspace-v2-save-0", + "sample": { + "mode": "ui-workspace-save", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-save", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 90, + 60, + 1216, + 799 + ], + "client": [ + 1200, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 1200, + 238 + ], + "visible": 1, + "grid": [ + 98, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 32, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 600, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 64, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "118e7fca8cedbd507e67a915eb98425603dc48d12edd50cfeee8119221788d46", + "workspace.layout": "c5cc46a5395e488830e636e2cae90b038fe5eaac0cb3244395754c24830757fe" + } + }, + { + "case": "workspace-v2-save-1", + "sample": { + "mode": "ui-workspace-save", + "status": "passed", + "timed_out": false, + "exit_code": 0, + "job_after": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "desktop_gone": true, + "result": { + "valid": true, + "mode": "ui-workspace-save", + "sessions_closed": 3, + "active_children_after": 0, + "settings_save_failed": false, + "presented_done": null + }, + "data_only": true + }, + "geometry": { + "schema": "termforge-workspace-restore-v1", + "dpi": 96, + "window": [ + 90, + 60, + 1216, + 799 + ], + "client": [ + 1200, + 760 + ], + "ratios": [ + 0.24, + 0.21, + 0.57, + 0.38, + 0.69 + ], + "sides": 0, + "focus_mode": 0, + "focused": "bottom", + "bottom_active": 0, + "main_active": 0, + "split_index": 1, + "split_active": 1, + "split_right_focus": 1, + "tabs": [ + { + "group": "bottom", + "kind": "cmd", + "title": "底部 cmd", + "zoom": 100, + "rect": [ + 0, + 465, + 1200, + 238 + ], + "visible": 1, + "grid": [ + 98, + 9 + ], + "cell": [ + 12, + 26 + ] + }, + { + "group": "main", + "kind": "cmd", + "title": "主 cmd", + "zoom": 150, + "rect": [ + 0, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 32, + 8 + ], + "cell": [ + 18, + 39 + ] + }, + { + "group": "main", + "kind": "powershell", + "title": "研究 PowerShell", + "zoom": 75, + "rect": [ + 600, + 93, + 600, + 321 + ], + "visible": 1, + "grid": [ + 64, + 16 + ], + "cell": [ + 9, + 20 + ] + } + ] + }, + "raw_sha256": { + "restore-geometry.json": "5cc777f65d580599f8bda2d3e7e3d246b63aba19dba55a27e65d358709402638", + "sample.json": "33bb1634252634013da65fd77333a253ad2277350fdbac4ccbad07cc9e1596fa", + "workspace.layout": "ce7ccde5387ee25fc6ef9e2485f794d737406942d0cdbd72970c33938bf0cd8d" + } + } + ] + } + ], + "completed_utc": "2026-10-26T16:36:29.317251+00:00", + "local_identity_sha256": "4584b1e3190d354115907cf46ad9344d9d45a6048da4fc6babe6f61f9407aeaa", + "schema": "termforge-workspace-empty-cr-v1", + "scope": "Test completion oracle correction; no product persistence or timeout change", + "compiler": "MSVC 19.42.34433 x64 Release; Windows SDK 10.0.26100", + "alacritty": "0.26.1-dev+termforge.4", + "candidate_library_sha256": "aa41a9ea5749f3933a0db8bfa41dda68812c68727d47d394fc3edd859973a260", + "preserved_contract": { + "observation_cap_bytes": 8192, + "native_deadline_seconds": 30, + "outer_deadline_seconds": 40, + "rounds_per_configuration": 2, + "processes_per_round": 3, + "independent_exact_output_and_fresh_prompt": true, + "unicode_directories_and_power_shell_version": true, + "layout_titles_zoom_split_window_and_child_grid": true, + "owned_job_and_noninput_desktop": true, + "presented_done": null + }, + "causal_observation": { + "original_and_repeated_failure_bytes_identical": true, + "failure_json_sha256": "71b9a860f70cbee2e8b52501bb0877ee8e218c8962f580321df40e215f4d6f4a", + "powershell_observed_bytes": 122, + "powershell_observed_sha256": "4c1d91f70cb82f745b1fa3eab3bca03048ac419aab0fe8c1e510cc2bc63041b5", + "original_run": 40313671258, + "original_job": 121540798389, + "repeated_run": 40355938302, + "repeated_job": 121688289054, + "repeated_source_head": "848cb0e621a291f732cc0e046d0b2fab6a77d962", + "repeated_checkout": "f72c239bb1bfa6d978c72646d88cbccf434f3fd5", + "repeated_artifact": 12665678304, + "repeated_archive_sha256": "c0b2aad326ee14b3e3c89aba06c12366bdf7f45af3a2233709729427919c4ab2", + "original_status": "failed", + "original_exit_code": 2, + "original_stage": 2, + "native_child_exit": null, + "reader_eof": null, + "native_forced_cleanup": null, + "no_cr_diagnostic_control_passed_old_helper": true, + "unchanged_captured_bytes_passed_old_helper": false, + "unchanged_captured_bytes_passed_corrected_helper": true, + "cr_removal": "diagnostic control only; accepted input and transport bytes remain unchanged" + }, + "causal_runs": [ + { + "phase": "red", + "passed": false, + "runtime_seconds": 0.02, + "files_sha256": { + "ctest.log": "d9d8617d9bbbebd631e88c800872fee19634c2673523b2f7348d7df4b3525a09", + "transport_completion.hpp": "47f85232785ccade107123342019a81f013991e33cacffad7616ce8334fe69ab", + "transport_completion_test.cpp": "c9bb02bd739f916e78530590ee0f3b29bc952c58e445d31bc047ee69d301dcee", + "transport_completion_test.exe": "62b08594caa62170468316ad65e6038954e9d2d40bddb0ce42db1342732cea9d" + } + }, + { + "phase": "green", + "passed": true, + "runtime_seconds": 0.01, + "files_sha256": { + "ctest.log": "8d61cc19d56f28141ec46fdb9d22845036f434cbe5f49301bdc14d426c36c46f", + "transport_completion.hpp": "535ff47bfbdd1038dca22820b467d1ee0d1839a29be38cd750e4de49439b602f", + "transport_completion_test.cpp": "c9bb02bd739f916e78530590ee0f3b29bc952c58e445d31bc047ee69d301dcee", + "transport_completion_test.exe": "9f63a2f8a08ddcd459d422f69bfbeae9ceaee9527edc0198c7e4fbd07bff7dc4" + } + } + ], + "full_candidate_official": { + "source_commit": "4daf99250a2740e1943635b6d46293d633427163", + "platform": "Windows-11-10.0.26200-SP0", + "started_utc": "2026-10-26T16:37:05.024869+00:00", + "backend": "reviewed-side-by-side-1.25.260930003", + "candidate": true, + "binaries_sha256": { + "alpha_normal_smoke.exe": "36ac6dbe36c942b9850d6d9f5b3538fbfe2fc251f38ac026ea12c343b0e9abc8", + "candidate_core_test.exe": "6e9df1abbe2271690a1480a1be9042e219a5947779b6a4b03f529a742fbd10ce", + "candidate_reply_close_test.exe": "40603aa54fbad42d55a5a7ea24b7aecebefdacd8f3c802e3ac89c52ef3a4b74d", + "candidate_resize_replay.exe": "3d91227adb37bba4defcef8f803f0f3c2916001223300b99d18e0e2ab695de94", + "candidate_title_test.exe": "90db63148f6aafaf79d65d5b40e61bbdc9f54486a6be36e26523699e0b46035d", + "clipboard_policy_test.exe": "809815e9838cad5b3443c98620d23a4e6c7186b5304b524d0990988eb47a6da9", + "conpty_backend_probe.exe": "3c54962c20f04e688a0413bb822713e908582af20b0be8bcfd188fb436907ef8", + "conpty_ctrl_c_probe.exe": "ac5ae5ccdb77c82512a8a6d769fafbe74693bf6fc535ce4d22bf60b0c8df2bfa", + "conpty_margin_probe.exe": "5404b009d0e005cd940d8a65afeb0fb9caf9c9ce2a18f651a88146343c41c252", + "core_measure.exe": "df9df797b16239d2fb71707f8a042a1ed956ad30c407d884527cdf18d600e785", + "find_bar_test.exe": "030733a2396ca9b31bbd029c610f1b838ab52394338c08410274501747dd9982", + "history_test.exe": "035507366facfd6de00e0c2762775c8d3816f605da3e55d498995a74a3f447d2", + "ime_context_test.exe": "be0d71ca72aee3dbd1565c9ad2314f81f0cd0c5511ec54cb623bbd39eede9974", + "ime_input_expectation_test.exe": "a5ea4532a06b893d955299793b4859cb9ccf221fab64e51949e1af2ee6ba5d0b", + "keyboard_test.exe": "18a35c2b2ab8a6316e0c938d8c36d0b82bcc131989d683205888b08bf4d33e19", + "layout_state_test.exe": "307519003536ce16d7d6fc552b3a25e9de25356d50a80c2de0c01168a10784a9", + "paste_test.exe": "47e9a5ce33335370b6fb199b0ff11adc19e2ab55bf64657061a13c1c020a24b0", + "presentation_probe.exe": "b7043cace46f65b22c7295df2fc06b214f060c2fd6fe18ac5ff27e13d943e096", + "raw_console_receiver_test.exe": "9c05cdb80d87ff264b3275387eb01dabc8f57640adbc7ee3910b0fc44f378224", + "real_app_capture.exe": "011efaad94da74ce182e30a3ff8abb0c1e1f58235bc77fa8c347ec3d9dcb822e", + "receive.exe": "ef316e11ddcc764ddac57426fc37b8b69f599ddb28a0ffedf5c2e85a3004a9d4", + "recovery_probe.exe": "c84e7b1b1792773b993f2758d5cafa59b80495451c01d194c3afd6cc653f0344", + "renderer_metrics_test.exe": "4cebad62798ff0f8eac42641a1119c7b9725728dd5fd31befcdf7e1ecb686cfd", + "replay.exe": "674f96ebbf00497576951a6780a5cf5195e3b80cc4acdb7f296fa8910a6203b7", + "resize_test.exe": "f566c56a1fa34e2a8ff480900ac5ae5059e2ac6872554208d5dbf0e24673dde0", + "runtime_observation_test.exe": "dfd04533d35d78401da75ad7d0965d923d7b7e95ad304775367b6088acc341ed", + "scenario_tick_gate_test.exe": "06cd4e813f1034d1a8c7fea592979f49c3ef89117c97fc6636cdae5012017ce8", + "search_test.exe": "0f29f3ea3147cc45eb2e500e683f3bcc1db04f820470b2ff473fffcb31cdb4f4", + "selection_test.exe": "104576787b62c2c4bb2428e0786c7742e63a23ceb766df293c88a5ccc6a1b836", + "sender_fence_pipe_test.exe": "6a94ac84631434a81ae1098932813d7ceeb81f2bb33a2e4b2456834bd8972e92", + "sender_fence_test.exe": "ab4af3b0f148f6e6377e70c98936c6c34d0be0d3cee4b17b3f8554c39b2d3e52", + "sent_message_wait_test.exe": "bc3cff14f60e3f1b17b3fd6255ff1c5b507f506d897d528802efa9666be74dcf", + "shell_clipboard_reader.exe": "bacc4bff465ca644fae61f9d26184a7adc22df9793470feca647af1d823fece8", + "shell_ime_client.exe": "d787ebf702cf5895e1ad0d71f895a51b3dead34d02953b5e7deff086c2bf8eb7", + "shell_input_client.exe": "850c82fb8bb58de8170bf096cc6dd4f8ba34d9ba11dbd2addc41508cddc31d8c", + "shell_launch_test.exe": "ace9b63148848bf7b405edb4b8818ca23d52699ac2ce8ca03a90c5bc1e32622d", + "shell_paste_probe.exe": "1370c6381b990270dfd816f53758e5aa57c63deba050fa7cf0dbd0d5cbf4f315", + "shell_query_client.exe": "71419f7918bc58f4ee47d14f8e6de35aa1828ac838a73ed607797fc120f56886", + "shell_queue_probe.exe": "7f34d6ff66b3e00d56798e22a480fb20a5afa083cff65a9d689dcbca44dbe12d", + "shell_selection_text_client.exe": "dbad81a22ecce4508eada90ae445f9a75abfe1effd2571776ef31b32144c8ac4", + "shell_size_client.exe": "5394d9863df9a589012f0d9a31f7e4167e7bc4ea23e93f9711c0a67c540651f6", + "shell_test_launcher.exe": "dbde4249dc08c7df80120398ffd252aa5e4958e4ef640cc161ea4c2d28b9f291", + "shell_text_client.exe": "9485b03424e421a4018596cc9487e7feeb33c26d30820985aa45843fdcf315d9", + "termforge_conpty_probe.exe": "7dd59bc8d0309ad5b131cde1c3ed98819139b6bb70f505347c98aabe8396f618", + "termforge_ime_probe.exe": "788a186b5fafb6d155eeab74f78d4f7cb992a599937b3aa48c5efcf49affe993", + "termforge_shell.exe": "650ea74ba3ad39b91ab4773c72af3ee70e59bc9d63e76249ea8fa6745a367081", + "terminal_trace.exe": "40e869753f4c00515ee0990f3732eac04f965fe2c3070f1bf772717476269f3d", + "text_action_policy_test.exe": "535b469c9aa0da7e77670578f75d04956d98e9f912d5c9a6c310ab796ab58d4f", + "transport_completion_test.exe": "9f63a2f8a08ddcd459d422f69bfbeae9ceaee9527edc0198c7e4fbd07bff7dc4", + "workflow_fixture_test.exe": "a6624a6b1536c6f445f92e05ff72a5318ece47d455500f6adc467d94f9f04222", + "workspace_state_test.exe": "ac3fbc8a56d33efda33cc9040c731c183e0a8666e23fce48d368c1868797714d" + }, + "vim_sha256": "2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a", + "vim_version": [ + "VIM - Vi IMproved 9.1 (2024 Jan 02, compiled Jul 09 2024 07:07:42)", + "Included patches: 1-532", + "Compiled by " + ], + "cmake_cache_sha256": "d652393042e1195908894a592dda1ebf0c8e1e8ccd3e05ecacec2760b41bc2ff", + "returncode": 8, + "completed_utc": "2026-10-26T16:44:34.597552+00:00", + "log_sha256": "2e0a4fd1afa9234461cb2598a6d52063115edcce3510017644d026125c7d37a7", + "last_test_sha256": "4da7c00a9422b9911c25cf4b6aba315e7e723e758132f61e285e5ca61bc0cf11", + "tests": [ + { + "name": "shell_launch", + "status": "run", + "seconds": 0.0119766, + "failed": false, + "skipped": false + }, + { + "name": "candidate_title", + "status": "run", + "seconds": 0.679326, + "failed": false, + "skipped": false + }, + { + "name": "candidate_reply_close", + "status": "run", + "seconds": 0.331894, + "failed": false, + "skipped": false + }, + { + "name": "runtime_observation", + "status": "run", + "seconds": 1.42167, + "failed": false, + "skipped": false + }, + { + "name": "ime_input_expectation", + "status": "run", + "seconds": 0.0101662, + "failed": false, + "skipped": false + }, + { + "name": "sent_message_wait", + "status": "run", + "seconds": 0.272669, + "failed": false, + "skipped": false + }, + { + "name": "find_bar", + "status": "run", + "seconds": 0.372583, + "failed": false, + "skipped": false + }, + { + "name": "renderer_metrics", + "status": "run", + "seconds": 0.89513, + "failed": false, + "skipped": false + }, + { + "name": "ime_context", + "status": "run", + "seconds": 0.333653, + "failed": false, + "skipped": false + }, + { + "name": "layout_state", + "status": "run", + "seconds": 0.123253, + "failed": false, + "skipped": false + }, + { + "name": "workspace_state", + "status": "run", + "seconds": 0.31827, + "failed": false, + "skipped": false + }, + { + "name": "transport_completion", + "status": "run", + "seconds": 0.0096818, + "failed": false, + "skipped": false + }, + { + "name": "scenario_tick_gate", + "status": "run", + "seconds": 0.0094621, + "failed": false, + "skipped": false + }, + { + "name": "candidate_core", + "status": "run", + "seconds": 1.12604, + "failed": false, + "skipped": false + }, + { + "name": "resize_core", + "status": "run", + "seconds": 0.0141228, + "failed": false, + "skipped": false + }, + { + "name": "history_core", + "status": "run", + "seconds": 0.0160041, + "failed": false, + "skipped": false + }, + { + "name": "selection_core", + "status": "run", + "seconds": 0.0736148, + "failed": false, + "skipped": false + }, + { + "name": "search_core", + "status": "run", + "seconds": 0.104065, + "failed": false, + "skipped": false + }, + { + "name": "paste", + "status": "run", + "seconds": 0.0100795, + "failed": false, + "skipped": false + }, + { + "name": "sender_fence", + "status": "run", + "seconds": 1.01974, + "failed": false, + "skipped": false + }, + { + "name": "sender_fence_pipe", + "status": "run", + "seconds": 0.377036, + "failed": false, + "skipped": false + }, + { + "name": "clipboard_policy", + "status": "run", + "seconds": 0.0080975, + "failed": false, + "skipped": false + }, + { + "name": "keyboard", + "status": "run", + "seconds": 0.0083753, + "failed": false, + "skipped": false + }, + { + "name": "text_action_policy", + "status": "run", + "seconds": 0.0075863, + "failed": false, + "skipped": false + }, + { + "name": "candidate_resize_replay", + "status": "run", + "seconds": 0.456575, + "failed": false, + "skipped": false + }, + { + "name": "candidate_shell_tui", + "status": "fail", + "seconds": 30.2581, + "failed": true, + "skipped": false + }, + { + "name": "core", + "status": "run", + "seconds": 5.70024, + "failed": false, + "skipped": false + }, + { + "name": "recovery", + "status": "run", + "seconds": 8.93909, + "failed": false, + "skipped": false + }, + { + "name": "measure", + "status": "run", + "seconds": 3.92379, + "failed": false, + "skipped": false + }, + { + "name": "binary_paths", + "status": "run", + "seconds": 0.190192, + "failed": false, + "skipped": false + }, + { + "name": "conpty_margin_contract", + "status": "run", + "seconds": 0.292098, + "failed": false, + "skipped": false + }, + { + "name": "benchmark_tools", + "status": "run", + "seconds": 5.29884, + "failed": false, + "skipped": false + }, + { + "name": "differential", + "status": "run", + "seconds": 2.9914, + "failed": false, + "skipped": false + }, + { + "name": "ime_inventory", + "status": "run", + "seconds": 48.3912, + "failed": false, + "skipped": false + }, + { + "name": "text_actions", + "status": "run", + "seconds": 5.26478, + "failed": false, + "skipped": false + }, + { + "name": "workflow_fixture", + "status": "run", + "seconds": 22.4382, + "failed": false, + "skipped": false + }, + { + "name": "workflow_sessions", + "status": "run", + "seconds": 73.5678, + "failed": false, + "skipped": false + }, + { + "name": "workflow_resources", + "status": "run", + "seconds": 0.473082, + "failed": false, + "skipped": false + }, + { + "name": "workflow_resource_phases", + "status": "run", + "seconds": 4.20935, + "failed": false, + "skipped": false + }, + { + "name": "workflow_system_content", + "status": "run", + "seconds": 0.139848, + "failed": false, + "skipped": false + }, + { + "name": "workflow_system_runner", + "status": "run", + "seconds": 0.59471, + "failed": false, + "skipped": false + }, + { + "name": "copy_error_frame", + "status": "run", + "seconds": 0.122081, + "failed": false, + "skipped": false + }, + { + "name": "paste_fence_evidence", + "status": "run", + "seconds": 0.101017, + "failed": false, + "skipped": false + }, + { + "name": "resize_trace", + "status": "run", + "seconds": 0.115742, + "failed": false, + "skipped": false + }, + { + "name": "research_guards", + "status": "run", + "seconds": 4.75846, + "failed": false, + "skipped": false + }, + { + "name": "receive", + "status": "run", + "seconds": 0.195176, + "failed": false, + "skipped": false + }, + { + "name": "conpty", + "status": "run", + "seconds": 23.2193, + "failed": false, + "skipped": false + }, + { + "name": "conpty_ctrl_c", + "status": "run", + "seconds": 4.26167, + "failed": false, + "skipped": false + }, + { + "name": "conpty_margin", + "status": "run", + "seconds": 0.348728, + "failed": false, + "skipped": false + }, + { + "name": "conpty_backend", + "status": "run", + "seconds": 1.13411, + "failed": false, + "skipped": false + }, + { + "name": "presentation", + "status": "run", + "seconds": 4.02108, + "failed": false, + "skipped": false + }, + { + "name": "shell", + "status": "fail", + "seconds": 174.653, + "failed": true, + "skipped": false + }, + { + "name": "selection_bytes", + "status": "run", + "seconds": 3.80834, + "failed": false, + "skipped": false + }, + { + "name": "selection_context", + "status": "run", + "seconds": 3.77349, + "failed": false, + "skipped": false + }, + { + "name": "raw_console_receiver", + "status": "run", + "seconds": 0.364619, + "failed": false, + "skipped": false + }, + { + "name": "paste_system", + "status": "run", + "seconds": 7.74685, + "failed": false, + "skipped": false + } + ], + "internal_skips": [ + { + "test": "setUpClass (test_candidate_compare.CandidateComparisonTests)", + "reason": "optional Alacritty binary is not built; core-candidate workflow runs these checks" + }, + { + "test": "setUpClass (test_capability_matrix.CapabilityMatrixTests)", + "reason": "optional Alacritty binary is not built; core-candidate workflow runs these checks" + }, + { + "test": "setUpClass (test_fuzz_matrix.FuzzMatrixTests)", + "reason": "optional Alacritty binary is not built; core-candidate workflow runs these checks" + }, + { + "test": "setUpClass (test_real_app_matrix.RealApplicationMatrixTests)", + "reason": "optional real application capture dependencies are not built" + }, + { + "test": "test_actual_system_copy_paste_and_shortcuts_twice (experiments.shell.test_actions.ActionWindowTest.test_actual_system_copy_paste_and_shortcuts_twice)", + "reason": "actual system Copy/Paste requires a disposable hosted VM" + }, + { + "test": "test_suspended_target_is_reclaimed_if_job_assignment_fails (test_ui_r1.EvidenceBoundaryTests.test_suspended_target_is_reclaimed_if_job_assignment_fails)", + "reason": "opt-in pinned reference failure injection" + }, + { + "test": "test_mouse_selection_to_real_system_clipboard (experiments.shell.test_shell.ShellTests.test_mouse_selection_to_real_system_clipboard)", + "reason": "system copy UI gate requires a disposable hosted VM" + }, + { + "test": "test_system_clipboard_product_read_errors_cancel_and_close (experiments.shell.test_shell.ShellTests.test_system_clipboard_product_read_errors_cancel_and_close)", + "reason": "product clipboard read gate requires a disposable hosted VM" + }, + { + "test": "test_system_clipboard_shortcut_to_window_confirmation_and_receiver (experiments.shell.test_shell.ShellTests.test_system_clipboard_shortcut_to_window_confirmation_and_receiver)", + "reason": "real system clipboard UI gate requires a disposable hosted VM" + }, + { + "test": "test_real_system_copy_shortcuts_for_styled_fixture_twice (experiments.shell.test_selection_bytes.SelectionBytesWindowTest.test_real_system_copy_shortcuts_for_styled_fixture_twice)", + "reason": "system CF requires a disposable hosted VM" + }, + { + "test": "test_real_system_copy_and_edit_context_twice (experiments.shell.test_selection_context.SelectionContextWindowTest.test_real_system_copy_and_edit_context_twice)", + "reason": "real CF/EDIT Copy requires a disposable hosted VM" + }, + { + "test": "test_real_system_clipboard (experiments.shell.test_paste.PasteTests.test_real_system_clipboard)", + "reason": "Private window station OpenClipboard denied (5); system API gate remains unverified." + } + ], + "failed_methods": [ + "test_real_fullscreen_editor_and_cursor_reply", + "test_close_while_resizing_continuous_output", + "test_main_and_bottom_sessions_focus_geometry_and_cleanup", + "test_resize_child_dimensions_and_interrupt", + "test_workspace_sessions_resize_and_cleanup" + ], + "observed_failures": [ + { + "case": "candidate-tui-0", + "sample": { + "mode": "candidate-tui", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 7, + "terminated": 0 + }, + "desktop_gone": true + }, + "stage": 59, + "raw_sample_sha256": "556fb95b611df35afd43c36b79c5ecd471b4fc524addcff9568b1178972bb265" + }, + { + "case": "main-2", + "sample": { + "mode": "ui-main", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 16, + "terminated": 0 + }, + "desktop_gone": true + }, + "stage": 7, + "raw_sample_sha256": "3958d53e4a69819bd3aa9db04c2fe09d26b78aba22d76db904102b257ea59340" + }, + { + "case": "resize-0", + "sample": { + "mode": "resize", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "desktop_gone": true + }, + "stage": 43, + "raw_sample_sha256": "eab4ad23688907b4cfe66756203e088ccfcfbc251d271e2497f64ee9375ac043" + }, + { + "case": "resize-close-0", + "sample": { + "mode": "resize-close", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 8, + "terminated": 0 + }, + "desktop_gone": true + }, + "stage": 43, + "raw_sample_sha256": "24ce02899ca5110cc20fabc847725d28ea73da6866f9b2a387e7620760829700" + }, + { + "case": "ui-2", + "sample": { + "mode": "ui", + "status": "failed", + "timed_out": false, + "exit_code": 2, + "job_after": { + "active": 0, + "total": 12, + "terminated": 0 + }, + "desktop_gone": true + }, + "stage": 52, + "raw_sample_sha256": "fe0985a95d8c70063e214bebec6476afdb651413f554ade5a199d46d79428d18" + } + ], + "persistence_method_passed": true, + "all_tests_completed": true, + "total_seconds": 449.38, + "limitations": [ + "Candidate/official full regression retains actual failures and internal skips.", + "Standalone persistence passes do not replace the failing aggregate shell suite.", + "No actual presented endpoint, long-run stability or WindTerm performance equivalence." + ] + }, + "ci": { + "source_head": "4daf99250a2740e1943635b6d46293d633427163", + "checkout": "c5d397999fbc00a83dd4055d975d8c12f855af5e", + "workflows": [ + { + "run": 40360427005, + "name": "Build and synthetic regression", + "conclusion": "success", + "windows_job": 121704057921, + "windows_tests": 51, + "windows_failed": 0, + "windows_seconds": 273.64 + }, + { + "run": 40360426973, + "name": "External core candidate", + "conclusion": "success", + "windows_job": 121704056881 + } + ], + "normalized_local_job_logs_sha256": { + "121704057921": "939a15bf59996dd31ce5a90cdefb240af0450060087d5b8e2b712b651bfb9310", + "121704056881": "26b187a507ac46184e0dec9da964e08fb82ffdab0a725c58a5979dd7e37acd7e" + }, + "limitation": "CI source is the experimental stack; a separate main-line patch requires its own merge-checkout validation." + } +} diff --git a/experiments/shell/results/2026-10-27-workspace-native-viewport/validation.json b/experiments/shell/results/2026-10-27-workspace-native-viewport/validation.json new file mode 100644 index 00000000..70f3c24a --- /dev/null +++ b/experiments/shell/results/2026-10-27-workspace-native-viewport/validation.json @@ -0,0 +1,1100 @@ +{ + "schema": 1, + "issue": 63, + "source_commit": "dd533878538490c2464673218ea0000c6467a495", + "product_source_equivalent_to": "c662011616a0498e0fbd6af8019f5f3cc03fdcaa", + "configuration": "candidate termforge.5 / explicit official ConPTY 1.25.260930003 / flags24", + "windows_version": "10.0.26200", + "presented_done": null, + "performance_claim": false, + "finding": "Existing #51 native_viewport policy also resolves captured ui stage52 and ui-main stage7 stale-tail failures; no new product change.", + "hypotheses_ranked_before_instrumentation": [ + "candidate/native viewport layout divergence", + "child size query before real resize applied", + "incomplete command or output delivery" + ], + "original_reproduction": [ + { + "mode": "ui", + "source_commit": "188228ebe1a4e6cd3779fc7c6b150624514886fb", + "candidate_version": "termforge.4", + "shell_sha256": "e81d92a8c7d380d4c33639861180b04935e021bc7c1a5179197c8a9e3c83454a", + "unchanged_binary_verified_after": true, + "cases": [ + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stderr": "", + "job_after": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "5749ce08975d166621625e1fc756108ce2ff05b17d348fe3795c56ced1f189ec" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stderr": "", + "job_after": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "5f47ee907b86e2bc1c6df13d4bd9bfe83b9e79a22081f084acd1947acf037ca8" + }, + { + "status": "failed", + "exit_code": 2, + "timed_out": false, + "stderr": "workspace scenario timeout: stage=52 client=2048x1120", + "job_after": { + "active": 0, + "total": 12, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "7ff419167b1039def1ff3eddabd9d1d9694d91cb9eebe9c5b24ccd90ecbbc229" + } + ], + "test_log_sha256": "d6ac3fa071ba5f551a4ad7951ddd0a620044ed05c13f78e6584dc90036b2e888", + "original_repetitions": 3, + "inner_seconds": 20, + "outer_seconds": 40 + }, + { + "mode": "main", + "source_commit": "188228ebe1a4e6cd3779fc7c6b150624514886fb", + "candidate_version": "termforge.4", + "shell_sha256": "e81d92a8c7d380d4c33639861180b04935e021bc7c1a5179197c8a9e3c83454a", + "unchanged_binary_verified_after": true, + "cases": [ + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stderr": "", + "job_after": { + "active": 0, + "total": 22, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "b7c48f3af87b79247157f445d6ba05173dbbb241e0c5e9c901107177d04a1081" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stderr": "", + "job_after": { + "active": 0, + "total": 22, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "a79b445bb25b00cd9e42b38b1c3202ae20c8e71df3c9d382b530fb3e5fd2973d" + }, + { + "status": "failed", + "exit_code": 2, + "timed_out": false, + "stderr": "workspace main scenario timeout: stage=7", + "job_after": { + "active": 0, + "total": 16, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "e86c1d45ad8d26597883877bfac564278eff3ecfb0e4d5a9fb5fa46da13c3f0d" + } + ], + "test_log_sha256": "b458b5b63498b19b70ae8edc85413a9bfe25a3a9005b61dfde6922f5fd5cd9c3", + "original_repetitions": 3, + "inner_seconds": 30, + "outer_seconds": 40 + } + ], + "diagnostics": [ + { + "mode": "ui", + "protocol": { + "source": "d6b3f798c82fd07cd0c88d989bfce3876c9692fa", + "baseline": { + "experiments/shell/shell.cpp": "b4f6fe98812d1d23fa5f5bbcc7376437404af6cdb67cd6cdcdf448b57cb31fe9", + "experiments/shell/workspace_scenarios.hpp": "c9a40dc370bf538c24b62b6177795975b4fac84ff78b0055117f7203b58a7b7d", + "experiments/shell/size_client.cpp": "cb11661bec9551053eec0dbb474bce2ac899a06ee2e50830a370af4816df39f4" + }, + "instrumented": { + "experiments/shell/shell.cpp": "d89c38f28c5f1a1a44aa72b73d825e01a4040ab0d194154b7a575f05408c8a3c", + "experiments/shell/workspace_scenarios.hpp": "a8fb5b8386c745520b7c1debc10ef3a00cb16751058966ba211aa7ddb4ab5145", + "experiments/shell/size_client.cpp": "2b15ce2e75f6e02e48d5d87efca1f50df67e9f39c1229fa0af8978fe3bf8f79e" + }, + "policy": "same binary; only TERMFORGE_R63_LEGACY_REFLOW switches native_viewport/reflow_history", + "max_runs": 2, + "order": [ + "legacy-reflow", + "native-viewport" + ], + "stop": "no retries; do not continue if first result differs from original stage52; stop after both cases", + "limits": { + "resize_events_per_snapshot": 1024, + "transport_bytes_per_snapshot": 131072, + "native_cells_per_helper": 100000 + }, + "original_deadlines": { + "inner_seconds": 20, + "outer_seconds": 40 + } + }, + "cases": [ + { + "policy": "reflow", + "shell_sha256": "f207f8a0fe1c8093e478e406d4ab84890dd7e6f1fff16b5be92399808d5b5da9", + "size_helper_sha256": "27285fc361b911a2e7892681846933d729d77e1b4e46d30310a64ed9c1624ce9", + "backend_files": { + "conpty-backend/conpty.dll": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "conpty-backend/LICENSE": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada", + "conpty-backend/manifest.json": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439", + "conpty-backend/OpenConsole.exe": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "outcome": { + "status": "failed", + "exit_code": 2, + "timed_out": false, + "stderr": "workspace scenario timeout: stage=52 client=2048x1120", + "job_after": { + "active": 0, + "total": 12, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "5795c43f744207c9deaa8e30917fd70fff0848be8ca7e50a457cc9cbec65b691" + }, + "bottom": { + "grid": [ + 169, + 11 + ], + "marker": "TF_SIZE_169x11", + "marker_row": 6, + "core_exact_marker": false, + "visible_matches_core": true, + "unexpected_suffix_bytes": 69, + "core_marker_line_sha256": "e5dc9dab268f0d43dde76e1b40639dbf5cf3a05e79b2ecb2d6f48e3a76f3f7c8", + "native": { + "query_size": [ + 169, + 11 + ], + "after_size": [ + 169, + 11 + ], + "exact_marker": "TF_SIZE_169x11", + "marker_row": 6, + "written_bytes": 18, + "write_ok": true, + "read_ok": true, + "metadata_sha256": "9c34c4b6bdb02956a3a4f6d8d189bf74493a7fd0cc8351071cd1fd72d9b70bd6", + "native_cells_sha256": "40d85ffb0dd8b154c96792e1c0aab7ef0ba8dd24247daf70d1d239f9a25632ea" + }, + "resize": { + "requested": 6, + "completed": 6, + "working": 0 + }, + "input_rejected": 0, + "transport_bytes": 2814, + "event_count": 50, + "truncated": false, + "native_viewport": false, + "parser_resize_grids": [ + [ + 82, + 5 + ], + [ + 82, + 15 + ], + [ + 82, + 5 + ], + [ + 82, + 20 + ], + [ + 82, + 5 + ], + [ + 169, + 11 + ] + ], + "state_sha256": "a715b12b40b79d420ce18ce7b8efb9a55277c6d8f343e3399e32041b3f62578c", + "trace_sha256": "9189923f9d741911aa0c3f466ec17db370abaa91afd2278061232c75a9df81d1", + "transport_sha256": "fd5df6523d1cc87c996e8d83e7b4d7c16a42da31487b60846750e39df30bd07a" + }, + "input_layout_sha256": "ba784527a981f5b90525d971197f57d9f25485e9856feb377ad45ea34eb370e4", + "runner_sha256": "4deb18d076a2aaca4571dc2f1ac2e6a3d67c28c95de6fd8f559e120265c60c04", + "case_identity_sha256": "696dd1c1e371c753b187a55e98c4080a2e9404fa40e94386bad737be1489d407" + }, + { + "policy": "native", + "shell_sha256": "f207f8a0fe1c8093e478e406d4ab84890dd7e6f1fff16b5be92399808d5b5da9", + "size_helper_sha256": "27285fc361b911a2e7892681846933d729d77e1b4e46d30310a64ed9c1624ce9", + "backend_files": { + "conpty-backend/conpty.dll": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "conpty-backend/LICENSE": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada", + "conpty-backend/manifest.json": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439", + "conpty-backend/OpenConsole.exe": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "outcome": { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stderr": "", + "job_after": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "1ed1047ba01a534a612d0b38d8e4389268494ec04230f6b59949600c1aedd0cb" + }, + "bottom": { + "grid": [ + 169, + 11 + ], + "marker": "TF_SIZE_169x11", + "marker_row": 6, + "core_exact_marker": true, + "visible_matches_core": true, + "unexpected_suffix_bytes": 0, + "core_marker_line_sha256": "f68f745acd26a08bd159d31b4517873821bb08a81589ea33419412f3ed8553b4", + "native": { + "query_size": [ + 169, + 11 + ], + "after_size": [ + 169, + 11 + ], + "exact_marker": "TF_SIZE_169x11", + "marker_row": 6, + "written_bytes": 18, + "write_ok": true, + "read_ok": true, + "metadata_sha256": "9c34c4b6bdb02956a3a4f6d8d189bf74493a7fd0cc8351071cd1fd72d9b70bd6", + "native_cells_sha256": "40d85ffb0dd8b154c96792e1c0aab7ef0ba8dd24247daf70d1d239f9a25632ea" + }, + "resize": { + "requested": 6, + "completed": 6, + "working": 0 + }, + "input_rejected": 0, + "transport_bytes": 2744, + "event_count": 48, + "truncated": false, + "native_viewport": true, + "parser_resize_grids": [ + [ + 82, + 5 + ], + [ + 82, + 15 + ], + [ + 82, + 5 + ], + [ + 82, + 20 + ], + [ + 82, + 5 + ], + [ + 169, + 11 + ] + ], + "state_sha256": "7bb36d8515d70e97c644470c49226a572ff9fa8bd9ec8de93f9e0d53c7b1e757", + "trace_sha256": "0012049e554648b8dd4f94074563a4d0e6c9d9700fcd48ce51bf9d679f5a0585", + "transport_sha256": "f449a77d40db369d69a8fb03bfcf632a53325fa10fa12b1e05ed43d077b7c763" + }, + "input_layout_sha256": "ba784527a981f5b90525d971197f57d9f25485e9856feb377ad45ea34eb370e4", + "runner_sha256": "4deb18d076a2aaca4571dc2f1ac2e6a3d67c28c95de6fd8f559e120265c60c04", + "case_identity_sha256": "99c9c1f3244081bca5fb8f8de8d62056dcd745700ab1e4861c91209cb87b99b2" + } + ], + "captured_replay": { + "replay_sha256": "c4cf0d9f68bb5bd688e601b87025f3237c8659f4d80e96bd4f67fa463463a13f", + "cases": [ + { + "policy": "reflow", + "returncode": 0, + "exact_marker": false, + "matches_live_core": true, + "marker_line_sha256": [ + "e5dc9dab268f0d43dde76e1b40639dbf5cf3a05e79b2ecb2d6f48e3a76f3f7c8" + ] + }, + { + "policy": "native", + "returncode": 0, + "exact_marker": true, + "matches_live_core": false, + "marker_line_sha256": [ + "f68f745acd26a08bd159d31b4517873821bb08a81589ea33419412f3ed8553b4" + ] + } + ] + }, + "replay_report_sha256": "2577bc1c45aeaa99774881a311676f8ee102c1344f69d47bed4a84f6a30ea823", + "instrumentation_patch_sha256": "c836d8b8c0c8350990e63e42960287e5a186c5679da24e0e94b394de73f4daa7" + }, + { + "mode": "ui-main", + "protocol": { + "source": "d6b3f798c82fd07cd0c88d989bfce3876c9692fa", + "instrumented_sha256": { + "experiments/shell/shell.cpp": "d89c38f28c5f1a1a44aa72b73d825e01a4040ab0d194154b7a575f05408c8a3c", + "experiments/shell/workspace_scenarios.hpp": "a8fb5b8386c745520b7c1debc10ef3a00cb16751058966ba211aa7ddb4ab5145", + "experiments/shell/workspace_main_scenario.hpp": "3a747624e063f094d7c3a60f87029637a704bc2a41081e9f1dbb84bad6390802", + "experiments/shell/size_client.cpp": "2b15ce2e75f6e02e48d5d87efca1f50df67e9f39c1229fa0af8978fe3bf8f79e" + }, + "policy": "same binary, only TERMFORGE_R63_LEGACY_REFLOW switch", + "max_runs": 2, + "order": [ + "legacy-reflow", + "native-viewport" + ], + "stop": "no retries; stop if first differs from original stage7; stop after both cases", + "inner_seconds": 30, + "outer_seconds": 40, + "scope": "original ui-main with third-run narrow settings" + }, + "cases": [ + { + "policy": "reflow", + "shell_sha256": "97c9f0234101a9414b8355e2f167695802458f7c43b33d06cf667571014fdc73", + "size_helper_sha256": "27285fc361b911a2e7892681846933d729d77e1b4e46d30310a64ed9c1624ce9", + "backend_files": { + "conpty-backend/conpty.dll": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "conpty-backend/LICENSE": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada", + "conpty-backend/manifest.json": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439", + "conpty-backend/OpenConsole.exe": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "outcome": { + "status": "failed", + "exit_code": 2, + "timed_out": false, + "stderr": "workspace main scenario timeout: stage=7", + "job_after": { + "active": 0, + "total": 16, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "c38a8e52005f17ed0324e40eb25d6d818366ffac73d11458c3d5a007eb320a1d" + }, + "bottom": { + "grid": [ + 169, + 11 + ], + "marker": "TF_SIZE_169x11", + "marker_row": 6, + "core_exact_marker": false, + "visible_matches_core": true, + "unexpected_suffix_bytes": 69, + "core_marker_line_sha256": "e5dc9dab268f0d43dde76e1b40639dbf5cf3a05e79b2ecb2d6f48e3a76f3f7c8", + "native": { + "query_size": [ + 169, + 11 + ], + "after_size": [ + 169, + 11 + ], + "exact_marker": "TF_SIZE_169x11", + "marker_row": 6, + "written_bytes": 18, + "write_ok": true, + "read_ok": true, + "metadata_sha256": "9c34c4b6bdb02956a3a4f6d8d189bf74493a7fd0cc8351071cd1fd72d9b70bd6", + "native_cells_sha256": "40d85ffb0dd8b154c96792e1c0aab7ef0ba8dd24247daf70d1d239f9a25632ea" + }, + "resize": { + "requested": 4, + "completed": 4, + "working": 0 + }, + "input_rejected": 0, + "transport_bytes": 3091, + "event_count": 36, + "truncated": false, + "native_viewport": false, + "parser_resize_grids": [ + [ + 82, + 5 + ], + [ + 82, + 20 + ], + [ + 82, + 5 + ], + [ + 169, + 11 + ] + ], + "state_sha256": "4eeac0ebae0fc836fc1c0a717189d8c0b500552ba6351d2f905141b157b6d994", + "trace_sha256": "2737573c91fa603900513e1dc4de8c4d9ec1daf9fc68494213d1f4ffd4180cdb", + "transport_sha256": "594ed5e2996878a07a29a74d33ebe1191e389934dfb0dcaa717630974c1531b6" + }, + "input_layout_sha256": "ba784527a981f5b90525d971197f57d9f25485e9856feb377ad45ea34eb370e4", + "runner_sha256": "ff9079dc9957d2d12a141d96637e98ac19a639fed94a848012db478043efce78", + "case_identity_sha256": "bceaf6fba5f700ba8def685296e543e41e3ea71396aadd9e890f4cf8ad04416c", + "main": { + "grid": [ + 112, + 24 + ], + "marker": "TF_SIZE_112x24", + "marker_row": 10, + "core_exact_marker": true, + "visible_matches_core": true, + "unexpected_suffix_bytes": 0, + "core_marker_line_sha256": "808e8570040f670407f7cc3842ca9dfc6bd0acd2e46ac8885f79bea32a559d4c", + "native": { + "query_size": [ + 112, + 24 + ], + "after_size": [ + 112, + 24 + ], + "exact_marker": "TF_SIZE_112x24", + "marker_row": 10, + "written_bytes": 18, + "write_ok": true, + "read_ok": true, + "metadata_sha256": "cf821e7760d9e30d50d3c1486e05910cbc218711275dc9ea28ca4ae3bc84003f", + "native_cells_sha256": "baed700dba2d4d4fdf239e01d0de08aa15ce95739c0037445f9a08603c416ee0" + }, + "resize": { + "requested": 2, + "completed": 2, + "working": 0 + }, + "input_rejected": 0, + "transport_bytes": 2857, + "event_count": 23, + "truncated": false, + "native_viewport": false, + "parser_resize_grids": [ + [ + 54, + 12 + ], + [ + 112, + 24 + ] + ], + "state_sha256": "1b0c3589f967d5277982588d3eefb6e1e9c32d946da66f5c5203f2b964a584d2", + "trace_sha256": "436c2eebe465ddbb65c13e67f6b5ecf21e38372b85efa0a6b074e8ef8996a459", + "transport_sha256": "d3290f1e27b493fe8d4cfafb989de7e21d6b0be24caffc1173a247015788bd83" + } + }, + { + "policy": "native", + "shell_sha256": "97c9f0234101a9414b8355e2f167695802458f7c43b33d06cf667571014fdc73", + "size_helper_sha256": "27285fc361b911a2e7892681846933d729d77e1b4e46d30310a64ed9c1624ce9", + "backend_files": { + "conpty-backend/conpty.dll": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "conpty-backend/LICENSE": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada", + "conpty-backend/manifest.json": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439", + "conpty-backend/OpenConsole.exe": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "outcome": { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stderr": "", + "job_after": { + "active": 0, + "total": 22, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "b05be1041e2cdadc96ddfeabf3dba8b2594688f4147ea2e8994f51f586112f72" + }, + "bottom": { + "grid": [ + 169, + 11 + ], + "marker": "TF_SIZE_169x11", + "marker_row": 6, + "core_exact_marker": true, + "visible_matches_core": true, + "unexpected_suffix_bytes": 0, + "core_marker_line_sha256": "f68f745acd26a08bd159d31b4517873821bb08a81589ea33419412f3ed8553b4", + "native": { + "query_size": [ + 169, + 11 + ], + "after_size": [ + 169, + 11 + ], + "exact_marker": "TF_SIZE_169x11", + "marker_row": 6, + "written_bytes": 18, + "write_ok": true, + "read_ok": true, + "metadata_sha256": "9c34c4b6bdb02956a3a4f6d8d189bf74493a7fd0cc8351071cd1fd72d9b70bd6", + "native_cells_sha256": "40d85ffb0dd8b154c96792e1c0aab7ef0ba8dd24247daf70d1d239f9a25632ea" + }, + "resize": { + "requested": 4, + "completed": 4, + "working": 0 + }, + "input_rejected": 0, + "transport_bytes": 2971, + "event_count": 35, + "truncated": false, + "native_viewport": true, + "parser_resize_grids": [ + [ + 82, + 5 + ], + [ + 82, + 20 + ], + [ + 82, + 5 + ], + [ + 169, + 11 + ] + ], + "state_sha256": "62579ab4a1f75bcd7e469f39b27a87e3ab0b76571479a4b1be9f19f357449006", + "trace_sha256": "5c4faccbff43baf50474c8a2a74533ea75157cec1b437a37400e511c0efd88c4", + "transport_sha256": "7a6c36a62d5506eb13523b203016f196f7b0745e1247f73b95aa256402e861c6" + }, + "input_layout_sha256": "ba784527a981f5b90525d971197f57d9f25485e9856feb377ad45ea34eb370e4", + "runner_sha256": "ff9079dc9957d2d12a141d96637e98ac19a639fed94a848012db478043efce78", + "case_identity_sha256": "1f7a258244363c026d991e4fba6d40cf51a016bd5dc1c8bdcd2478c99953b253", + "main": { + "grid": [ + 112, + 24 + ], + "marker": "TF_SIZE_112x24", + "marker_row": 10, + "core_exact_marker": true, + "visible_matches_core": true, + "unexpected_suffix_bytes": 0, + "core_marker_line_sha256": "808e8570040f670407f7cc3842ca9dfc6bd0acd2e46ac8885f79bea32a559d4c", + "native": { + "query_size": [ + 112, + 24 + ], + "after_size": [ + 112, + 24 + ], + "exact_marker": "TF_SIZE_112x24", + "marker_row": 10, + "written_bytes": 18, + "write_ok": true, + "read_ok": true, + "metadata_sha256": "cf821e7760d9e30d50d3c1486e05910cbc218711275dc9ea28ca4ae3bc84003f", + "native_cells_sha256": "baed700dba2d4d4fdf239e01d0de08aa15ce95739c0037445f9a08603c416ee0" + }, + "resize": { + "requested": 2, + "completed": 2, + "working": 0 + }, + "input_rejected": 0, + "transport_bytes": 3027, + "event_count": 21, + "truncated": false, + "native_viewport": true, + "parser_resize_grids": [ + [ + 54, + 12 + ], + [ + 112, + 24 + ] + ], + "state_sha256": "70b84496873334a34c9cc220478f24795162ad6a39c05ad549cc8104c82f89a0", + "trace_sha256": "89a8bb0999e90db6b499da940908aa3f30470f27fe3a00411cbb6d3d0bd4ca2a", + "transport_sha256": "524967350ac9ca7898c4ca434895b814b43970a9567dd12f35d2d97ccca1a3ba" + } + } + ], + "captured_replay": { + "replay_sha256": "c4cf0d9f68bb5bd688e601b87025f3237c8659f4d80e96bd4f67fa463463a13f", + "cases": [ + { + "session": 1, + "policy": "reflow", + "returncode": 0, + "exact_marker": false, + "matches_live_core": true, + "marker_line_sha256": [ + "e5dc9dab268f0d43dde76e1b40639dbf5cf3a05e79b2ecb2d6f48e3a76f3f7c8" + ] + }, + { + "session": 1, + "policy": "native", + "returncode": 0, + "exact_marker": true, + "matches_live_core": false, + "marker_line_sha256": [ + "f68f745acd26a08bd159d31b4517873821bb08a81589ea33419412f3ed8553b4" + ] + }, + { + "session": 3, + "policy": "reflow", + "returncode": 0, + "exact_marker": true, + "matches_live_core": true, + "marker_line_sha256": [ + "808e8570040f670407f7cc3842ca9dfc6bd0acd2e46ac8885f79bea32a559d4c" + ] + }, + { + "session": 3, + "policy": "native", + "returncode": 0, + "exact_marker": true, + "matches_live_core": true, + "marker_line_sha256": [ + "808e8570040f670407f7cc3842ca9dfc6bd0acd2e46ac8885f79bea32a559d4c" + ] + } + ] + }, + "replay_report_sha256": "6bf3f6d315eebe1fe575ca5b4e1c1fcc4564816effac9cc9b6952a8b604d95f7", + "instrumentation_patch_sha256": "4f73cb8d27da05741cbc880fda306938eaadd77b709169637e12cf47abdbda49" + } + ], + "minimal_ui_original": { + "status": "failed", + "exit_code": 2, + "timed_out": false, + "stderr": "workspace scenario timeout: stage=52 client=2048x1120", + "job_after": { + "active": 0, + "total": 12, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "e8580998bb507198486a37766027f6d45495b5712d6d038ec69f498c2750c679" + }, + "restored_source_sha256": { + "shell.cpp": "b4f6fe98812d1d23fa5f5bbcc7376437404af6cdb67cd6cdcdf448b57cb31fe9", + "size_client.cpp": "cb11661bec9551053eec0dbb474bce2ac899a06ee2e50830a370af4816df39f4", + "workspace_scenarios.hpp": "c9a40dc370bf538c24b62b6177795975b4fac84ff78b0055117f7203b58a7b7d", + "workspace_main_scenario.hpp": "c3994cc60041cd1e7a8599e5c9037473634412bcb6cfd349850ff8cd2808a47e" + }, + "acceptance": { + "identity": { + "source_commit": "dd533878538490c2464673218ea0000c6467a495", + "started_utc": "2026-10-26T23:30:34.047090+00:00", + "source_clean": true, + "diagnostic_instrumentation": false, + "configuration": "candidate termforge.5 / official ConPTY 1.25.260930003 / flags24", + "files_sha256": { + "alpha_normal_smoke.exe": "55f76e0ef0a68a2d3ee2d047852551b86e63074b84604e59fe6d0893361ba3a6", + "candidate_core_test.exe": "4c73b58d7da111ab52b6edbbb59898cf660860fdedaa64c4f49b46fae856a02d", + "candidate_reply_close_test.exe": "7431b2d5a414703d85fb9425b07b7df125eb7b5668851cb5383cf575cb620a69", + "candidate_resize_replay.exe": "c4cf0d9f68bb5bd688e601b87025f3237c8659f4d80e96bd4f67fa463463a13f", + "candidate_title_test.exe": "359141b75c6eeb9a0cc9ef59f9f21ad0d07bcaefb51beb19f64e3288a059bec2", + "clipboard_policy_test.exe": "ef7c7d860bad3b458b7ac8e659c46acd0f768f70281e7d6797e489520d1d559e", + "conpty_backend_probe.exe": "39b52ad4acc4e432718d3cdd6dc6ee2ab80b1584de16c0edc3d775ccc7b03c0d", + "conpty_ctrl_c_probe.exe": "30ad460b54cfcc752e20ccd5f656c6769e292528408a4a68e7182ab3206d39b6", + "conpty_margin_probe.exe": "7aaef1da461b3b6d833aea79332f28d8f924e26de2f9332feeb1f1303a51e30f", + "conpty_resize_native_probe.exe": "a570b55f7ef0f65be01b827112905a223fb32f37a0f645ce121ead1ed6555f59", + "core_measure.exe": "dfe74cf8e6ec0fdb976911569c14e52709be5e880d9b36d2a108187bca9c165b", + "find_bar_test.exe": "632e5ff486b7ad5d29292b4af1206c196d09ca1c1c8a38a830a004ee9c21f1a1", + "history_test.exe": "3b3f6199ad4d345ef5cc759ddcf80667fdde4c7778658e99764342cc17949e2e", + "ime_context_test.exe": "8941d52ed7642b15c46da5a50927ba09ceab7078c5f31c649e4e3baa859e8ae1", + "ime_input_expectation_test.exe": "a960fbd62a7c0d6eec564a5a2bb1cc271582e7bb55ec07ddb63bc46044b9c438", + "keyboard_test.exe": "fe027bbcb21a09fac9411dd5766a37e8edd711d3ef547f37a080ded097a4de3e", + "layout_state_test.exe": "7b174db85ff75e8a8f5613c949c264a4dedbb8cfeb8a18581f016191bd036ddb", + "paste_test.exe": "1e6acc382a75fe18b50212cf6950d3d714f89939b1cf63000795348e4db22d54", + "presentation_probe.exe": "91aa34c2ac7b4f6610da419a04091d4391cb78e224e6b5f567437a5565d4e33a", + "raw_console_receiver_test.exe": "0731af92b2536b189d80894b5ca4243d814e07ab2c513fff7b4d3fd3654578f2", + "real_app_capture.exe": "fddd86a02d7c8ba13b7cd458373c8acd19d999199baefce77e8f237efce47189", + "receive.exe": "b5ea1d10bba964ec80f183d761b1c0ea4334f6963597ccd1bb16b9c769fc76d5", + "recovery_probe.exe": "72213828da26b5657915667faccd1d0aca83dbe6d9537f38dbd9d5464750313b", + "renderer_metrics_test.exe": "f08e88f6ff8bb6be87f724ff6a3c398df359a639f83d75b5970e69659f8a4a5c", + "replay.exe": "54298c3fa5020d3ebcaa55c76564b9dcbfec5b25d772343c428e2e36e1858ca1", + "resize_test.exe": "fd97c87127d4763320832d1d538af5755eece8a1ec1485435cab6b41e9e549cd", + "runtime_observation_test.exe": "0049a0d863724bf8bd181c85868bd68a409eb5e12c755a5538add5f85bf8b0bf", + "scenario_tick_gate_test.exe": "f7368d2d4c13a9eee4a210e857fd5d13479641328fa71a984582c69b57735ecf", + "search_test.exe": "6bf2a5fa47e1f0665462e6601e01253674c6662e5db730161bd7eb09c81d621d", + "selection_test.exe": "19eaf7ef3e6f1a35f67e1b17a60858bfb0f268a20c9f2686d141c847a68fbb3a", + "sender_fence_pipe_test.exe": "19bba0df9e37159cc70728f68ae8a28364127fac21ab72cb21cdb036f9380686", + "sender_fence_test.exe": "b3c396a7d8a306d0987ef149fd9d5e014ab90ac6d54d0915a48b6d261a6e21c1", + "sent_message_wait_test.exe": "b9bb4960dc470d5aac08fecb2c0775c16e2a7e75fdabe283b6c028d284679abb", + "shell_clipboard_reader.exe": "771da921ef4d30a7e710989ea0d4446254fc4e85c59199730704edf2256bb641", + "shell_ime_client.exe": "f02ec529d90db13bf4c904bbee8bc77930fdcf51ba868b4a43a43ed783f61830", + "shell_input_client.exe": "0075a332e524071809a1fdbd5161d1f360fa8e89faaf2b45b24c19f1d94829cc", + "shell_launch_test.exe": "6e96e163b4adc40b8d53c022a23a206176bf558c73eb76d9790b61f2abb13baf", + "shell_paste_probe.exe": "dd49d693dc30372d6f44cce2b7a1dcfc64adea1e6638399da4d400784a14bf6f", + "shell_query_client.exe": "691b440e9c500c76c1c09c448aafbe2316793bf341ffe69bbc2bf70e41be64cc", + "shell_queue_probe.exe": "e030070a6f287421bc3b74350fd45c8cd762837dc1512a76f688d20d39aa8715", + "shell_selection_text_client.exe": "18b60279aed48a9d39b6f5c57371c60b3a2b59bb21d6e30f92bac7adf50a3ed2", + "shell_size_client.exe": "a7a45db0976dd1e157871b6a9b50b1362ceb8cb8ea604470e58791920805d408", + "shell_test_launcher.exe": "7bd78cffd78ac66d651933c9614d6be937c99baa105ce04c1a533df5785c11e2", + "shell_text_client.exe": "9350e53de74c9a6b78495a3b59788e3a31e0067a9c5ec1b8685a729ff18244d3", + "termforge_conpty_probe.exe": "1dcf69f65e9dd2abca1a58c10df2be3a296a4e22cdd895ba457222fe9e27f021", + "termforge_ime_probe.exe": "ba59ec28909430669c187a644651588b2f1984b1308b97056f222f332e7de9c9", + "termforge_shell.exe": "7f26395942f107297565f50402568650e1e17c5eb23924bee1bfb02c50c60d16", + "terminal_trace.exe": "d09f34702cbd2d8be6ecfe98ab56a2326be1fde6b1fde4d10ef85d0f62955d2f", + "text_action_policy_test.exe": "9988026742d9ff45f300a6fa336ceca052a502b568f32114be7fd5057e57f5a4", + "transport_completion_test.exe": "6952438843e8a7605bb6cf0eb7add56dd8574f4e5edfbc3a534f795030a3fcc8", + "workflow_checkpoint_test.exe": "056047580e8ddd77224908667297a0b704f97ef57d1eadeb382b52c7c7875b86", + "workflow_fixture_test.exe": "ce3f09d8b3bb97d92dd6c61531e2cb9ba36070d2222b7b4ad7b86de82021f208", + "workspace_state_test.exe": "18080df77a0b7a370aa1e2dc79f2986dd3a4eb01661663dd95c43a11b802ee11", + "conpty-backend/conpty.dll": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "conpty-backend/LICENSE": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada", + "conpty-backend/manifest.json": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439", + "conpty-backend/OpenConsole.exe": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586", + "assets/fonts/OFL.txt": "50ab8dd54680d3473f649c9db86fece88434d097c7834475c1c72d2f8c429215", + "assets/fonts/README.md": "a278deeec4b6240acc5398fe4271ebe8cc0ef26502ac3a1cfb950d06ddaf43e6", + "assets/fonts/RobotoMono-Regular.ttf": "af0bff7599c3df3831755c16e39b3c496df74b8c8d8a1161b14dc8461be17cb4" + }, + "source_sha256": { + "CMakeLists.txt": "8a0e3f86981e91fd6482b590669ccc4adb571a359bd265b3dc98aee59a7b92f2", + "experiments/shell/shell.cpp": "b4f6fe98812d1d23fa5f5bbcc7376437404af6cdb67cd6cdcdf448b57cb31fe9", + "experiments/shell/size_client.cpp": "cb11661bec9551053eec0dbb474bce2ac899a06ee2e50830a370af4816df39f4", + "experiments/shell/workspace_scenarios.hpp": "c9a40dc370bf538c24b62b6177795975b4fac84ff78b0055117f7203b58a7b7d", + "experiments/shell/workspace_main_scenario.hpp": "c3994cc60041cd1e7a8599e5c9037473634412bcb6cfd349850ff8cd2808a47e", + "experiments/shell/test_shell.py": "a6b63c1754b0e0dc1cc01863891f4c7c9d989f818d351838a8cfe295edaf811c", + "experiments/shell/run.py": "45976b828eecb316881d698dedaef09ad304f72d991dfa360f1c552bbcbe9f1d" + }, + "max_focused_batches": 2, + "original_repetitions_per_method": 3, + "full_shell_batches": 1, + "full_shell_ctest_timeout": 180 + }, + "runs": [ + { + "kind": "focused-0", + "returncode": 0, + "supervisor_seconds_rounded": 8.375, + "log_sha256": "9eff816b554e80f0214b9fd5f798c0c600e22b488889bfaef5948ef7d34c36d8" + }, + { + "kind": "focused-1", + "returncode": 0, + "supervisor_seconds_rounded": 7.797, + "log_sha256": "f7421a6c60cb5edfc6736469357a83113332949c007eb013f8c5cdf5d382ceec" + }, + { + "kind": "full-shell", + "returncode": 0, + "supervisor_seconds_rounded": 93.734, + "log_sha256": "0d7cfae3fb2e9d2eaa426cade9f0301e388e880d9f749a97b1361f62b9f33f92", + "ctest_timeout_seconds": 180, + "summary_lines": [ + "53: test_mouse_selection_to_real_system_clipboard (experiments.shell.test_shell.ShellTests.test_mouse_selection_to_real_system_clipboard) ... skipped 'system copy UI gate requires a disposable hosted VM'", + "53: test_system_clipboard_product_read_errors_cancel_and_close (experiments.shell.test_shell.ShellTests.test_system_clipboard_product_read_errors_cancel_and_close) ... skipped 'product clipboard read gate requires a disposable hosted VM'", + "53: test_system_clipboard_shortcut_to_window_confirmation_and_receiver (experiments.shell.test_shell.ShellTests.test_system_clipboard_shortcut_to_window_confirmation_and_receiver) ... skipped 'real system clipboard UI gate requires a disposable hosted VM'", + "53: Ran 40 tests in 93.403s", + "53: OK (skipped=3)", + "Total Test time (real) = 93.62 sec" + ] + } + ], + "samples": [ + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 22, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "ac8d45f5390e69786383cebd14f6c89f5e7ee464d14287ee18d140fcfd33d85d", + "case": "focused-0/main-0" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 22, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "8ba795e82717e0ecff5bad5f255048b9e028e6277490c2c31f3ff2a2c6aa6b8d", + "case": "focused-0/main-1" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 22, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "04f90e5763d5e3f9d066785efa5c6c85adfc2e94a6a38d9b1f8b47fb579c1ce2", + "case": "focused-0/main-2" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "7feccb4f0160565751c36e7f2c9d65f8d3f593ee3bfa9d414585442130061835", + "case": "focused-0/ui-0" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "1a15dfc7d7f848c466838e25b6bdb50d69cd0389bfb5d59ec70e69414b57695e", + "case": "focused-0/ui-1" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "33dcb261b49a07847dcf17f0accfdd1d51ff08cd2bb8f93e8c8602eb2745fb0c", + "case": "focused-0/ui-2" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 22, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "14450dc6b7587d56213d6ebac638dcfc489248babb1d35e759f4eee8402187cb", + "case": "focused-1/main-0" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 22, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "f0faea737dc6cc7518723446cf0f901b0aedcbc16e942490e31c87e25e41d8a7", + "case": "focused-1/main-1" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 22, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "0b706f2012bd51398f62c642870ea1fb2caae6584352ce7f0a053f43f5bc4448", + "case": "focused-1/main-2" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "df654941e0290caf0ce97ccd58fe05e721acee4337757d777466348fa31a0cf4", + "case": "focused-1/ui-0" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "f7eb090e4c2a6131557dc68f4f795176f12bc765e54cb9ca52c6bda2fd2cb26a", + "case": "focused-1/ui-1" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "34d40e087c3e8d0baa071e740d75d5c043dfdb6cdd14d228758cfd340052a1f6", + "case": "focused-1/ui-2" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 22, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "e07d8d1561af320604d4f6f415dce42f07203905011d80f2470708674084229e", + "case": "full-shell/main-0" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 22, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "73990fe243ef403da72cb66265a007fa150dfc8ef38da982e82fafeefdc0da81", + "case": "full-shell/main-1" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 22, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "bab0fdbbbc12ab7dd73c8b0cee2c6292dea941349b11010b9514d00b03fe4135", + "case": "full-shell/main-2" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "fb6832554596c8ba0d6cecb33a41df390f811d98b8320fa229638219efb86d4b", + "case": "full-shell/ui-0" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "e096621b2e713b45a6ead7774b39fc0ad22977e444442a31fc94cefddcf13157", + "case": "full-shell/ui-1" + }, + { + "status": "passed", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "desktop_gone": true, + "sample_sha256": "236dd3b4c21679097ac5e930073961f466a1c152660ec9667bf1f1f96fa03d61", + "case": "full-shell/ui-2" + } + ] + }, + "limitations": [ + "Temporary state/native capture and policy switch perturb scheduling; no timing/performance claim.", + "Native snapshots occur immediately after the size helper writes; they are separate from later terminal rendering and actual presentation.", + "Offline replay uses captured feed/resize ordering; exact old live core match checked, not a live host or timing equivalence claim.", + "The historical 3e5b96d failure without matching transport is not retroactively assigned a cause; 188228e reproduces the original symptoms; detailed native/transport attribution comes from controlled current-source failures.", + "Full shell suite retains original hosted clipboard skips; no personal clipboard or display settings changed.", + "Per-run public acceptance samples cover the ui/main scenarios; other full-suite negative fixtures retain their original assertions in the complete test log.", + "Vim #52 and complete official configuration/R2 remain open; system configurations and WindTerm are not inferred.", + "Original raw traces, native cells and helper paths stay local; public rows retain marker, shape, state and identity hashes only." + ] +} diff --git a/experiments/shell/run.py b/experiments/shell/run.py index 271d8ac7..bbd4dfc0 100644 --- a/experiments/shell/run.py +++ b/experiments/shell/run.py @@ -483,7 +483,8 @@ def execute(exe, mode, out, timeout=40, settings_source=None, *, resources=False validate_history(json.loads((out / "history.json").read_text(encoding="utf-8"))) validate_native_scroll([json.loads(line) for line in (out / "native-scroll.jsonl").read_text(encoding="utf-8").splitlines()]) if mode == "candidate-margins": - record["margins_output"] = validate_margins_output(out) + record["margins_output"] = validate_margins_output(out, + expected_backend=os.environ.get("TERMFORGE_EXPECT_CONPTY_BACKEND", "system")) if mode == "candidate-scroll": record["scroll_output"] = validate_scroll_output(out) if mode == "candidate-alt": diff --git a/experiments/shell/runtime_observation_test.cpp b/experiments/shell/runtime_observation_test.cpp index c5ddaa00..a247acc2 100644 --- a/experiments/shell/runtime_observation_test.cpp +++ b/experiments/shell/runtime_observation_test.cpp @@ -124,11 +124,21 @@ static void runtime_default_off_case() { require(!window.runtime_observation_enabled(), "normal live window keeps observation disabled"); require(!window.operation_observation_enabled() && window.operation_observations().empty(), "normal live window keeps operation observation disabled"); + require(!window.output_record_checkpoint_active(),"ordinary live output has no record checkpoint"); + window.arm_output_record_checkpoint(std::chrono::steady_clock::now()+std::chrono::seconds(5)); + require(window.output_record_checkpoint_active(),"explicit output checkpoint must arm"); window.close(); + require(!window.output_record_checkpoint_active(),"close must release an armed output record checkpoint"); + bool closed_arm_rejected=false; + try { window.arm_output_record_checkpoint(std::chrono::steady_clock::now()+std::chrono::seconds(5)); } + catch(const std::exception&) { closed_arm_rejected=true; } + const bool rearmed=window.output_record_checkpoint_active(); + window.release_output_record_checkpoint(); runtime_wait(window, [&] { return IsWindow(window.handle()) == FALSE; }, "unobserved real shell closes deadline"); const auto result = window.collect(); require(result.complete && !result.runtime_before_close && !result.runtime_final && !window.runtime_before_close() && !window.runtime_final(), "normal lifecycle creates no runtime samples"); + require(closed_arm_rejected && !rearmed,"closing window must reject a new output record checkpoint"); } static void operation_state_case() { using R=tf_shell::OperationReason; diff --git a/experiments/shell/sender_fence.hpp b/experiments/shell/sender_fence.hpp new file mode 100644 index 00000000..56e092f0 --- /dev/null +++ b/experiments/shell/sender_fence.hpp @@ -0,0 +1,151 @@ +// Opt-in, one-transfer test coordination. A held payload is not OS backpressure. +#pragma once +#include "paste.hpp" +#include +#include +#include +#include + +namespace tf_shell_test { +class SenderFence { +public: + using Clock = std::chrono::steady_clock; + enum class State { armed, held, released, cancelled, aborted, timed_out, failed }; + enum class AbortReason { none, stop, control_eof }; + struct Ticket { + const SenderFence* owner = nullptr; + const tf_shell::PasteTransfer* transfer = nullptr; + uint64_t generation = 0; + }; + struct Snapshot { + uint64_t generation = 0; + State state = State::armed; + AbortReason abort_reason = AbortReason::none; + State wait_exit_state = State::armed; + AbortReason wait_exit_reason = AbortReason::none; + bool attached = false, ever_held = false, wait_finished = false; + size_t owned_bytes = 0; + tf_shell::PasteProgress held_progress; + }; + static constexpr size_t minimum_sent = 16384; +private: + const std::shared_ptr transfer_; + const uint64_t generation_; + const Clock::time_point deadline_; + mutable std::mutex mutex_; + std::condition_variable changed_; + Snapshot snapshot_; + + bool matches(Ticket ticket) const noexcept { + return ticket.owner == this && ticket.transfer == transfer_.get() && ticket.generation == generation_; + } + [[noreturn]] static void rejected_wait(State state) { + if (state == State::timed_out) throw std::runtime_error("sender fence deadline expired"); + throw std::runtime_error("sender fence aborted or invalid"); + } +public: + SenderFence(std::shared_ptr transfer, uint64_t generation, + Clock::time_point deadline) + : transfer_(std::move(transfer)), generation_(generation), deadline_(deadline) { + if (!transfer_ || !generation_ || deadline_ <= Clock::now()) + throw std::invalid_argument("sender fence identity or deadline invalid"); + const auto progress = transfer_->progress(); + if (progress.state != tf_shell::PasteState::ready || progress.accepted || progress.sent || + progress.in_flight || progress.total <= minimum_sent) + throw std::invalid_argument("sender fence must precede a large paste admission"); + snapshot_.generation = generation_; + } + Ticket ticket() const noexcept { return {this, transfer_.get(), generation_}; } + Snapshot snapshot(Ticket ticket) const { + std::lock_guard lock(mutex_); + if (!matches(ticket)) throw std::invalid_argument("sender fence ticket differs"); + return snapshot_; + } + // Queue admission only. Failed admission leaves no worker waiting and must + // not replace an existing queue binding or claim a mismatched transfer. + bool attach(const std::shared_ptr& transfer) { + std::lock_guard lock(mutex_); + if (transfer.get() != transfer_.get() || snapshot_.attached || snapshot_.state != State::armed) + return false; + const auto progress = transfer_->progress(); + if (progress.state != tf_shell::PasteState::ready || progress.accepted || progress.sent || progress.in_flight) + return false; + if (Clock::now() >= deadline_) { snapshot_.state = State::timed_out; return false; } + snapshot_.attached = true; + return true; + } + bool release(Ticket ticket) { + std::lock_guard lock(mutex_); + if (!matches(ticket) || snapshot_.state != State::held) return false; + if (Clock::now() >= deadline_) { + snapshot_.state = State::timed_out; + changed_.notify_all(); + return false; + } + snapshot_.state = State::released; + changed_.notify_all(); + return true; + } + bool abort(Ticket ticket, AbortReason reason = AbortReason::control_eof) { + std::lock_guard lock(mutex_); + if (!matches(ticket) || reason == AbortReason::none || snapshot_.state == State::aborted || + snapshot_.state == State::timed_out || snapshot_.state == State::failed) return false; + snapshot_.state = State::aborted; + snapshot_.abort_reason = reason; + changed_.notify_all(); + return true; + } + // A real PasteTransfer::cancel must occur first. Wake permits only its + // already claimed chunk to use the unchanged send/ack/finish path. + bool cancelled(Ticket ticket) { + std::lock_guard lock(mutex_); + if (!matches(ticket) || (snapshot_.state != State::armed && snapshot_.state != State::held)) return false; + if (Clock::now() >= deadline_) { + snapshot_.state = State::timed_out; + changed_.notify_all(); + return false; + } + const auto state = transfer_->progress().state; + if (state != tf_shell::PasteState::cancelling && state != tf_shell::PasteState::cancelled) return false; + snapshot_.state = State::cancelled; + changed_.notify_all(); + return true; + } + // Exactly one real writer calls this after take_chunk/in-flight publication + // and after releasing the queue lock, immediately before Session::send. + void before_send(const std::shared_ptr& transfer, size_t owned_bytes) { + std::unique_lock lock(mutex_); + if (transfer.get() != transfer_.get() || !snapshot_.attached) + throw std::logic_error("sender fence writer identity differs"); + if (snapshot_.state == State::released || snapshot_.state == State::cancelled) return; + if (snapshot_.state != State::armed) rejected_wait(snapshot_.state); + if (Clock::now() >= deadline_) { + snapshot_.state = State::timed_out; + rejected_wait(snapshot_.state); + } + const auto progress = transfer_->progress(); + if (progress.state == tf_shell::PasteState::cancelling) return; + if (tf_shell::paste_terminal(progress.state)) { + snapshot_.state = State::failed; + rejected_wait(snapshot_.state); + } + if (progress.sent < minimum_sent) return; + if (!owned_bytes || owned_bytes > tf_shell::PasteTransfer::chunk_limit || + progress.in_flight != owned_bytes || progress.accepted != progress.sent + owned_bytes || + progress.accepted >= progress.total || progress.state != tf_shell::PasteState::sending) { + snapshot_.state = State::failed; + rejected_wait(snapshot_.state); + } + snapshot_.state = State::held; + snapshot_.ever_held = true; + snapshot_.held_progress = progress; + snapshot_.owned_bytes = owned_bytes; + const bool woken = changed_.wait_until(lock, deadline_, [&] { return snapshot_.state != State::held; }); + if (!woken) snapshot_.state = State::timed_out; + snapshot_.wait_finished = true; + snapshot_.wait_exit_state = snapshot_.state; + snapshot_.wait_exit_reason = snapshot_.abort_reason; + if (snapshot_.state != State::released && snapshot_.state != State::cancelled) rejected_wait(snapshot_.state); + } +}; +} // namespace tf_shell_test diff --git a/experiments/shell/sender_fence_pipe.hpp b/experiments/shell/sender_fence_pipe.hpp new file mode 100644 index 00000000..aca1566d --- /dev/null +++ b/experiments/shell/sender_fence_pipe.hpp @@ -0,0 +1,83 @@ +// Bounded test-only control pipe. Neither commands nor replies carry paste bytes. +#pragma once +#include "../conpty/session.hpp" +#include "sender_fence.hpp" +#include +#include + +namespace tf_shell_test { +class SenderFencePipe { + std::shared_ptr fence_; + tf_conpty::Handle request_read_,request_write_,reply_read_,reply_write_; + uint64_t sent_=0,dispatched_=0,received_=0; + bool eof_=false; + static constexpr uint64_t command_limit=8; + static void check(bool value,const char* why) { if(!value) throw std::runtime_error(why); } + static void create(tf_conpty::Handle& reader,tf_conpty::Handle& writer,DWORD required) { + HANDLE read=nullptr,write=nullptr; + // On failure CreatePipe's output handles are unspecified. Adopt only + // successful handles, then verify the actual inbound server buffer. + check(CreatePipe(&read,&write,nullptr,4096)!=FALSE,"fence control pipe creation failed"); + reader.reset(read);writer.reset(write); + DWORD flags=0,capacity=0; + check(GetNamedPipeInfo(reader.get(),&flags,nullptr,&capacity,nullptr) && + (flags&PIPE_SERVER_END) && capacity>=required,"fence control pipe capacity cannot hold its command budget"); + } + template static void write(HANDLE pipe,const std::array& message) { + DWORD size=0; + check(WriteFile(pipe,message.data(),sizeof(message),&size,nullptr) && size==sizeof(message),"fence control write failed"); + } + template static std::array read(HANDLE pipe) { + std::array message{};DWORD size=0; + check(ReadFile(pipe,message.data(),sizeof(message),&size,nullptr) && size==sizeof(message),"fence control frame truncated"); + return message; + } +public: + struct Ack { uint64_t generation;bool accepted; }; + explicit SenderFencePipe(std::shared_ptr fence):fence_(std::move(fence)) { + check(fence_!=nullptr,"fence control requires a transfer binding"); + create(request_read_,request_write_,static_cast(command_limit*2*sizeof(uint64_t))); + create(reply_read_,reply_write_,static_cast(command_limit*3*sizeof(uint64_t))); + } + ~SenderFencePipe() { + const auto state=fence_->snapshot(fence_->ticket()).state; + if(state==SenderFence::State::armed || state==SenderFence::State::held) + fence_->abort(fence_->ticket(),SenderFence::AbortReason::stop); + } + void send_go(uint64_t generation) { + check(request_write_.get() && sent_{++sent_,generation}); + } + void disconnect() { request_write_.reset(); } + bool eof_observed() const { return eof_; } + // The owned UI/test message pump dispatches only complete, already buffered + // frames. Eight commands and replies fit in each pipe without blocking. + void dispatch() { + if(eof_) return; + DWORD available=0; + if(!PeekNamedPipe(request_read_.get(),nullptr,0,nullptr,&available,nullptr)) { + check(GetLastError()==ERROR_BROKEN_PIPE,"fence control peek failed"); + char byte=0;DWORD count=0; + check(!ReadFile(request_read_.get(),&byte,1,&count,nullptr) && GetLastError()==ERROR_BROKEN_PIPE && !count, + "fence control EOF was not confirmed by ReadFile"); + eof_=true;fence_->abort(fence_->ticket(),SenderFence::AbortReason::control_eof);return; + } + if(!available) return; + check(available>=2*sizeof(uint64_t),"fence control partial request"); + const auto request=read<2>(request_read_.get()); + check(request[0]==++dispatched_ && dispatched_<=command_limit,"fence control request sequence invalid"); + auto ticket=fence_->ticket();ticket.generation=request[1]; + const bool accepted=fence_->release(ticket); + write(reply_write_.get(),std::array{request[0],request[1],accepted?1ull:0ull}); + } + std::optional acknowledgement() { + DWORD available=0; + check(PeekNamedPipe(reply_read_.get(),nullptr,0,nullptr,&available,nullptr)!=FALSE,"fence control reply peek failed"); + if(!available) return std::nullopt; + check(available>=3*sizeof(uint64_t),"fence control partial reply"); + const auto reply=read<3>(reply_read_.get()); + check(reply[0]==++received_ && received_<=sent_ && reply[2]<=1,"fence control reply sequence invalid"); + return Ack{reply[1],reply[2]!=0}; + } +}; +} // namespace tf_shell_test diff --git a/experiments/shell/sender_fence_pipe_test.cpp b/experiments/shell/sender_fence_pipe_test.cpp new file mode 100644 index 00000000..64580c0c --- /dev/null +++ b/experiments/shell/sender_fence_pipe_test.cpp @@ -0,0 +1,77 @@ +// The control protocol uses real pipe reads/EOF; payload writes are unit-level here. +#include "sender_fence_pipe.hpp" +#include +#include + +using tf_shell_test::SenderFence; +using tf_shell_test::SenderFencePipe; +static void check(bool value,const char* why) { if(!value) throw std::runtime_error(why); } +struct HeldTransfer { + std::shared_ptr transfer=std::make_shared( + 1,tf_shell::prepare_paste(std::u16string(1000000,u'汉')),true); + std::shared_ptr fence; + SenderFencePipe pipe; + std::future writer; + explicit HeldTransfer(std::chrono::milliseconds lifetime=std::chrono::seconds(2)) + :fence(std::make_shared(transfer,7,SenderFence::Clock::now()+lifetime)),pipe(fence) { + check(fence->attach(transfer) && transfer->claim_writer(),"admit test writer"); + } + void hold() { + while(transfer->progress().sent<16384) { + auto chunk=transfer->take_chunk();transfer->acknowledge_written(chunk.size());transfer->finish_chunk(); + } + const auto chunk=transfer->take_chunk(); + writer=std::async(std::launch::async,[this,count=chunk.size()] { + try {fence->before_send(transfer,count);return true;} catch(const std::runtime_error&) {return false;} + }); + const auto limit=SenderFence::Clock::now()+std::chrono::seconds(1); + while(fence->snapshot(fence->ticket()).state==SenderFence::State::armed && SenderFence::Clock::now()snapshot(fence->ticket()).state==SenderFence::State::held,"writer reached real CV hold"); + } + bool go(uint64_t generation) { + pipe.send_go(generation);pipe.dispatch();const auto ack=pipe.acknowledgement(); + check(ack.has_value() && ack->generation==generation,"pipe GO reply identity");return ack->accepted; + } +}; +int main() { + try { + { + HeldTransfer value; + check(!value.go(7),"GO before held must fail");value.hold(); + check(!value.go(8),"wrong generation must fail"); + check(value.go(7),"matching held GO must succeed"); + check(!value.go(7),"duplicate GO must fail");check(value.writer.get(),"GO wakes writer"); + value.fence->abort(value.fence->ticket(),SenderFence::AbortReason::stop); + const auto state=value.fence->snapshot(value.fence->ticket()); + check(state.state==SenderFence::State::aborted && state.wait_exit_state==SenderFence::State::released, + "later stop cannot rewrite the actual wait exit"); + } + { + HeldTransfer value; + for(unsigned i=0;i<8;++i) value.pipe.send_go(7); + bool rejected=false;try {value.pipe.send_go(7);} catch(const std::runtime_error&) {rejected=true;} + check(rejected,"ninth queued command exceeds bounded budget"); + for(unsigned i=0;i<8;++i) value.pipe.dispatch(); + for(unsigned i=0;i<8;++i) { + const auto ack=value.pipe.acknowledgement(); + check(ack && ack->generation==7 && !ack->accepted,"saturated pipe replies preserve order and rejection"); + } + check(!value.pipe.acknowledgement(),"no extra reply"); + } + { + HeldTransfer value;value.hold();value.pipe.disconnect();value.pipe.dispatch(); + check(value.pipe.eof_observed(),"actual pipe EOF observed"); + check(!value.writer.get(),"EOF fails held writer"); + const auto state=value.fence->snapshot(value.fence->ticket()); + check(state.state==SenderFence::State::aborted && state.abort_reason==SenderFence::AbortReason::control_eof, + "EOF reason retained"); + } + { + HeldTransfer value(std::chrono::milliseconds(300));value.hold(); + check(!value.writer.get(),"missing GO fails held writer"); + check(value.fence->snapshot(value.fence->ticket()).state==SenderFence::State::timed_out,"absolute deadline retained"); + } + std::cout<<"real pipe GO/ack, generation, early/repeated GO, EOF and missing GO passed\n";return 0; + } catch(const std::exception& error) {std::cerr< +#include +#include + +namespace { +using Fence = tf_shell_test::SenderFence; +using Transfer = tf_shell::PasteTransfer; +using namespace std::chrono_literals; + +void check(bool condition, const char* detail) { + if (!condition) throw std::runtime_error(detail); +} +template void rejects(F function, const char* detail) { + bool rejected = false; + try { function(); } catch (const std::exception&) { rejected = true; } + check(rejected, detail); +} +std::shared_ptr transfer(size_t bytes = 4 * 1024 * 1024) { + tf_shell::PreparedPaste prepared; + prepared.bytes.assign(bytes, 'a'); + return std::make_shared(7, std::move(prepared)); +} +std::shared_ptr fence(const std::shared_ptr& paste, + Fence::Clock::duration duration = 2s) { + return std::make_shared(paste, 19, Fence::Clock::now() + duration); +} +// Unit setup only: prepare the exact real-PasteTransfer state reached after +// sixteen complete 1024B writes, then claim the seventeenth chunk. +std::string claimed_chunk(const std::shared_ptr& paste) { + for (size_t i = 0; i < 16; ++i) { + const auto bytes = paste->take_chunk(); + check(bytes.size() == 1024, "unit prefix chunk differs"); + paste->acknowledge_written(bytes.size()); + paste->finish_chunk(); + } + return paste->take_chunk(); +} +std::future wait(const std::shared_ptr& gate, const std::shared_ptr& paste, + size_t owned_bytes) { + return std::async(std::launch::async, [gate, paste, owned_bytes] { + try { gate->before_send(paste, owned_bytes); return true; } + catch (const std::exception&) { return false; } + }); +} +void held(const std::shared_ptr& gate) { + const auto deadline = Fence::Clock::now() + 1s; + while (gate->snapshot(gate->ticket()).state != Fence::State::held) { + check(Fence::Clock::now() < deadline, "unit writer did not reach held state"); + std::this_thread::yield(); + } +} +void identities_and_admission() { + auto paste = transfer(); + rejects([&] { Fence invalid(paste, 0, Fence::Clock::now() + 1s); }, "zero generation accepted"); + rejects([&] { Fence invalid(paste, 1, Fence::Clock::now()); }, "expired deadline accepted"); + rejects([&] { Fence invalid({}, 1, Fence::Clock::now() + 1s); }, "missing transfer accepted"); + auto gate = fence(paste); + auto ticket = gate->ticket(); + check(!gate->release(ticket), "release before held accepted"); + check(!gate->cancelled(ticket), "fake cancellation accepted"); + auto other = transfer(); + check(!gate->attach(other), "different transfer admitted"); + check(gate->attach(paste) && !gate->attach(paste), "attachment is not single use"); + ++ticket.generation; + check(!gate->release(ticket) && !gate->abort(ticket), "wrong generation changed fence"); + rejects([&] { gate->snapshot(ticket); }, "wrong generation observed fence"); + ticket = gate->ticket(); ticket.transfer = other.get(); + rejects([&] { gate->snapshot(ticket); }, "wrong transfer observed fence"); +} +void threshold_and_release() { + auto paste = transfer(); auto gate = fence(paste); + check(gate->attach(paste), "attach failed"); + const auto first = paste->take_chunk(); + gate->before_send(paste, first.size()); + check(!gate->snapshot(gate->ticket()).ever_held, "fence lowered sent floor"); + paste->acknowledge_written(first.size()); paste->finish_chunk(); + for (size_t i = 1; i < 16; ++i) { + const auto chunk = paste->take_chunk(); + paste->acknowledge_written(chunk.size()); paste->finish_chunk(); + } + const auto current = paste->take_chunk(); + auto worker = wait(gate, paste, current.size()); held(gate); + const auto before = gate->snapshot(gate->ticket()); + check(before.ever_held && before.owned_bytes == 1024 && before.held_progress.sent == 16384 && + before.held_progress.accepted == 17408 && before.held_progress.in_flight == 1024, + "held snapshot does not describe real transfer state"); + auto wrong = gate->ticket(); ++wrong.generation; + check(!gate->release(wrong) && worker.wait_for(0ms) == std::future_status::timeout, + "wrong generation released held writer"); + check(gate->release(gate->ticket()) && !gate->release(gate->ticket()), "release duplicate accepted"); + check(worker.get(), "valid release failed"); + check(paste->progress().sent == 16384, "fence fabricated write acknowledgement"); + paste->acknowledge_written(current.size()); paste->finish_chunk(); + check(gate->snapshot(gate->ticket()).wait_finished, "release did not finish wait"); +} +void cancellation_preserves_claimed_chunk(bool before_wait) { + auto paste = transfer(); auto gate = fence(paste); + check(gate->attach(paste), "cancel attach failed"); + const auto current = claimed_chunk(paste); + std::future worker; + if (!before_wait) { worker = wait(gate, paste, current.size()); held(gate); } + paste->cancel(); + check(gate->cancelled(gate->ticket()), "real cancellation did not wake fence"); + if (before_wait) gate->before_send(paste, current.size()); + else check(worker.get(), "cancellation aborted claimed chunk instead of allowing it"); + check(paste->progress().state == tf_shell::PasteState::cancelling && paste->progress().sent == 16384, + "cancellation prematurely completed an unsubmitted chunk"); + paste->acknowledge_written(current.size()); paste->finish_chunk(); + const auto after = paste->progress(); + check(after.state == tf_shell::PasteState::cancelled && after.sent == after.accepted && + after.sent == 17408 && !after.in_flight && after.retained_capacity <= 32, + "cancel accounting or capacity differs"); +} +void abort_wakes(Fence::AbortReason reason) { + auto paste = transfer(); auto gate = fence(paste); + check(gate->attach(paste), "abort attach failed"); + const auto current = claimed_chunk(paste); + auto worker = wait(gate, paste, current.size()); held(gate); + check(gate->abort(gate->ticket(), reason) && !gate->abort(gate->ticket(), reason), "abort duplicate accepted"); + check(worker.wait_for(1s) == std::future_status::ready && !worker.get(), "abort did not finish wait as failure"); + check(gate->snapshot(gate->ticket()).abort_reason == reason && paste->progress().sent == 16384, + "abort lost reason or fabricated acknowledgement"); + paste->fail("unit writer aborted before submission"); + check(!paste->progress().in_flight && paste->progress().retained_capacity <= 32, "writer failure did not release payload"); +} +void no_go_times_out() { + auto paste = transfer(); auto gate = fence(paste, 1s); + check(gate->attach(paste), "timeout attach failed"); + const auto current = claimed_chunk(paste); + auto worker = wait(gate, paste, current.size()); + check(worker.wait_for(2s) == std::future_status::ready && !worker.get(), "missing GO waited without bound"); + const auto after = gate->snapshot(gate->ticket()); + check(after.state == Fence::State::timed_out && after.ever_held && after.wait_finished && + !gate->release(gate->ticket()), "timeout was converted to release"); +} +void premature_completion_is_rejected() { + auto paste = transfer(17 * 1024); auto gate = fence(paste); + check(gate->attach(paste), "small large-paste attach failed"); + const auto current = claimed_chunk(paste); + rejects([&] { gate->before_send(paste, current.size()); }, "last accepted chunk was treated as a partial transfer"); + check(!gate->snapshot(gate->ticket()).ever_held, "completed admission manufactured held state"); +} +} // namespace + +int main() { + try { + identities_and_admission(); threshold_and_release(); + cancellation_preserves_claimed_chunk(false); cancellation_preserves_claimed_chunk(true); + abort_wakes(Fence::AbortReason::stop); abort_wakes(Fence::AbortReason::control_eof); + no_go_times_out(); premature_completion_is_rejected(); + std::cout << "sender fence pure protocol checks passed; no transport or OS backpressure claim\n"; + return 0; + } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } +} diff --git a/experiments/shell/shell.cpp b/experiments/shell/shell.cpp index b3681dd0..cec8f84f 100644 --- a/experiments/shell/shell.cpp +++ b/experiments/shell/shell.cpp @@ -5,6 +5,8 @@ #endif #include "queues.hpp" #include "resize.hpp" +#include "resize_trace.hpp" +#include "output_record_checkpoint.hpp" #include "keyboard.hpp" #include "ime.hpp" #include "hosted_ime_input.hpp" @@ -38,10 +40,18 @@ class ShellWindow { const DWORD ui_thread_id_ = GetCurrentThreadId(); OutputQueue output_; tf_conpty::Session session_; + tf_shell_test::ResizeTrace resize_trace_; InputQueue input_{session_}; - ResizeWorker resize_{session_}; + ResizeWorker resize_{session_, {}, [this](bool begin, unsigned generation, int rows, int columns) { + resize_trace_.observe(begin ? tf_shell_test::ResizeTrace::host_begin : tf_shell_test::ResizeTrace::host_end, + rows, columns, generation); + }}; #ifdef TERMFORGE_CANDIDATE_CORE +#ifdef TERMFORGE_CONPTY_SIDEBYSIDE + termforge::CandidateTerminal parser_{24, 80, 100, termforge::CandidateResizePolicy::native_viewport}; +#else termforge::CandidateTerminal parser_{24, 80, 100}; +#endif #else termforge::Parser parser_{24, 80, 100, termforge::ParseMode::recovery}; #endif @@ -72,6 +82,8 @@ class ShellWindow { std::optional pending_paste_; std::unique_ptr paste_prompt_; std::shared_ptr paste_transfer_; + std::optional> next_paste_fence_; + std::shared_ptr observed_paste_fence_; std::wstring input_status_, command_prefix_, terminal_title_; int wheel_remainder_ = 0; termforge::TextSelection selection_; @@ -91,6 +103,7 @@ class ShellWindow { bool find_initial_ = false, find_terminal_visible_ = true; bool observe_output_ = false; std::string observed_output_; // Opt-in synthetic probe, capped independently of queue size. + tf_shell_test::OutputRecordCheckpoint output_record_checkpoint_; Clock::time_point start_ = Clock::now(), input_time_, flood_start_; bool frame_after_ = false; Result result_; @@ -150,6 +163,7 @@ class ShellWindow { const int columns = std::clamp(width/renderer_->cell_width(), 1, 500); const int rows = std::clamp(height/renderer_->cell_height(), 1, 200); if (!force && !metrics_dirty_ && columns == parser_.screen.width() && rows == parser_.screen.height()) return; + resize_trace_.observe(tf_shell_test::ResizeTrace::desired, rows, columns, 0, stage_); auto surface = std::make_unique(renderer_->x(columns), renderer_->y(rows)); selection_end(); // Preserve a current hit only across exact unchanged row projections. @@ -158,15 +172,19 @@ class ShellWindow { find_resize_pending_=find_resize_anchor_.set(termforge::TextDocument(parser_.screen),*current); find_resize_wrapped_=find_current_->wrapped; } + resize_trace_.observe(tf_shell_test::ResizeTrace::parser_begin, rows, columns, 0, stage_); #ifdef TERMFORGE_CANDIDATE_CORE parser_.resize(rows, columns); #else parser_.screen.resize(rows, columns); #endif + resize_trace_.observe(tf_shell_test::ResizeTrace::parser_end, rows, columns, 0, stage_); search_changed(); dirty_first_ = 0; dirty_last_ = rows; surface_ = std::move(surface); metrics_dirty_ = false; publish(); resize_.request(rows, columns); + if (resize_trace_.enabled()) resize_trace_.observe(tf_shell_test::ResizeTrace::request, rows, columns, + resize_.snapshot().requested_generation, stage_); require(InvalidateRect(hwnd_, nullptr, FALSE) != FALSE, "resize invalidate failed"); } void change_metrics(int dpi, int zoom, bool defer_resize = false) { @@ -263,7 +281,13 @@ class ShellWindow { void start_prepared_paste(tf_shell::PreparedPaste value) { const auto observed_source=runtime_enabled_?std::optional(value.source_units):std::nullopt; auto transfer=std::make_shared(identity_,std::move(value),true); - if(!input_.begin_paste(transfer)) { input_status(L"Input busy; paste not started"); return; } + std::shared_ptr fence; + if(next_paste_fence_) { + fence=std::make_shared(transfer,next_paste_fence_->first,next_paste_fence_->second); + next_paste_fence_.reset(); + } + if(!input_.begin_paste(transfer,fence)) { input_status(L"Input busy; paste not started"); return; } + observed_paste_fence_=std::move(fence); if(runtime_enabled_) runtime_paste_source_units_=observed_source; viewport_.bottom(parser_.screen); publish(); paste_transfer_=std::move(transfer); input_status(L"Pasting — Escape cancels"); @@ -332,6 +356,7 @@ class ShellWindow { if (closing_) return; if(runtime_enabled_) runtime_before_close_=runtime_snapshot(); closing_ = true; + output_record_checkpoint_.release(); // Closing must drain even a held synthetic checkpoint. operation_observations_.close(); selection_end(); find_.reset(); search_.close(); find_current_.reset(); clear_find_resize_anchor(); @@ -451,7 +476,9 @@ class ShellWindow { require(frame_matches_full(), "alternate retained frame mismatch"); std::ostringstream out; out << "{\"schema\":\"" << (scroll ? "termforge-candidate-scroll-v1" : margins ? "termforge-candidate-margins-v1" : "termforge-candidate-alt-v1") - << "\",\"columns\":" << parser_.screen.width() + << "\",\"conpty_backend\":\"" << session_.backend_identity() + << "\",\"conpty_creation_flags\":" << session_.backend_creation_flags() + << ",\"columns\":" << parser_.screen.width() << ",\"rows\":" << parser_.screen.height() << ",\"cursor_row\":" << parser_.screen.row << ",\"cursor_column\":" << parser_.screen.column << ",\"alternate_enabled\":" << ((parser_.mode_bits() & (1 << 12)) ? "true" : "false") @@ -619,7 +646,7 @@ class ShellWindow { begin_output_observation(); resize_window(60, 12); stage_ = 61; } else if (stage_ == 61 && resize_.settled() && parser_.screen.width() == 60 && parser_.screen.height() == 12 && - observed_output_.find("\x1b[?25h") != std::string::npos) { + live_contains_line("TF_QUERY_RESIZED_60x12")) { fixture_command(std::wstring(1, 27)); stage_ = 62; } else if (stage_ == 62 && live_contains_line("TF_ESCAPE_DONE")) { std::ifstream stream("candidate-escape.bin", std::ios::binary); @@ -715,6 +742,7 @@ class ShellWindow { } else if (stage_ == 40 && resize_.settled()) { query_size(); stage_ = 41; } else if (stage_ == 41 && line_is("TF_SIZE_60x12")) { + resize_trace_.observe(tf_shell_test::ResizeTrace::child_ack, 12, 60, 0, stage_); require(parser_.screen.width() == 60 && parser_.screen.height() == 12, "core size mismatch"); ++result_.resize_verified; // Coalesce rapid requests without a queue of allocated surfaces. @@ -722,6 +750,7 @@ class ShellWindow { } else if (stage_ == 42 && resize_.settled()) { query_size(); stage_ = 43; } else if (stage_ == 43 && line_is("TF_SIZE_100x28")) { + resize_trace_.observe(tf_shell_test::ResizeTrace::child_ack, 28, 100, 0, stage_); require(parser_.screen.width() == 100 && parser_.screen.height() == 28, "grown core size mismatch"); ++result_.resize_verified; type(L"for /L %i in (1,1,1000000) do @echo TF_BUSY_%i\r"); stage_ = 4; flood_start_ = Clock::now(); @@ -744,7 +773,10 @@ class ShellWindow { for (const auto& event : parser_.take_events()) { switch (event.kind) { case 1: - if (!event.bytes.empty()) require(input_.send(event.bytes), "terminal reply rejected by input queue"); + // Closing stops input before the remaining output is drained. + // Queries in that output no longer have a live input peer. + if (!closing_ && !event.bytes.empty()) + require(input_.send(event.bytes), "terminal reply rejected by input queue"); break; case 2: terminal_title_ = utf16(event.bytes); break; case 3: terminal_title_.clear(); break; @@ -764,16 +796,21 @@ class ShellWindow { bool drained = false; // Check time between small feeds; also retain a hard byte budget. do { - const auto bytes = output_.take(8192); + const auto budget=output_record_checkpoint_.read_budget(8192); + if(!budget) break; + const auto bytes = output_.take(budget); if (bytes.empty()) { drained = true; break; } if(observe_output_) { observed_output_.append(bytes); if(observed_output_.size()>8192) observed_output_.erase(0,observed_output_.size()-8192); } + resize_trace_.observe(tf_shell_test::ResizeTrace::feed, parser_.screen.height(), parser_.screen.width(), 0, stage_, bytes); parser_.feed(bytes); batch += bytes.size(); + output_record_checkpoint_.consumed(bytes); #ifdef TERMFORGE_CANDIDATE_CORE route_terminal_events(); #endif + if(output_record_checkpoint_.held()) break; } while (batch < 65536 && ms(began) < 4); result_.max_consumed_per_tick = (std::max)(result_.max_consumed_per_tick, batch); if (batch) { result_.consumed += batch; search_changed(); publish(); } @@ -953,6 +990,10 @@ class ShellWindow { DispatchMessageW(&message); } ShellWindow(std::string mode, std::string image) : mode_(std::move(mode)), image_path_(std::move(image)) { + wchar_t trace[2]{}; + if ((mode_ == "resize" || mode_ == "resize-close") && + GetEnvironmentVariableW(L"TERMFORGE_RESIZE_TRACE", trace, 2) == 1 && trace[0] == L'1') + resize_trace_.enable(); snapshot_ = std::make_shared(viewport_.snapshot(parser_.screen), 0); } ~ShellWindow() { @@ -1100,6 +1141,13 @@ class ShellWindow { return clipboard_read_?std::optional(clipboard_read_->progress()):std::nullopt; } std::shared_ptr observed_paste() const { return paste_transfer_; } + // Owned test scenarios bind before admission, including asynchronous clipboard reads. + void arm_next_paste_fence(uint64_t generation,Clock::time_point deadline) { + require(!closing_ && !paste_busy() && !next_paste_fence_ && generation && deadline>Clock::now(), + "paste fence must be armed before a new request"); + next_paste_fence_=std::make_pair(generation,deadline); observed_paste_fence_.reset(); + } + std::shared_ptr observed_paste_fence() const { return observed_paste_fence_; } bool composing() const { return ime_.composing(); } void begin_ime_observation(unsigned generation) { ime_observation_.begin(generation); update_ime_caret(); @@ -1150,6 +1198,17 @@ class ShellWindow { return lines; } int observed_tick_count() const { return result_.ticks; } + void arm_output_record_checkpoint(Clock::time_point deadline) { + require_ui_thread(); + require(!closing_ && hwnd_ && IsWindow(hwnd_),"output checkpoint target must be live"); + const auto& view=*snapshot_; + const bool empty=view.cursor_row>=0 && static_cast(view.cursor_row)(view.cursor_row)]).text.empty(); + output_record_checkpoint_.arm(empty,deadline); + } + bool output_record_checkpoint_held() const { require_ui_thread();return output_record_checkpoint_.held(); } + bool output_record_checkpoint_active() const { require_ui_thread();return output_record_checkpoint_.active(); } + void release_output_record_checkpoint() { require_ui_thread();output_record_checkpoint_.release(); } void set_search_work_budget(size_t units) { require(units>=1 && units<=4096,"search work budget outside testable bound"); search_work_budget_=units; } @@ -1176,8 +1235,9 @@ class ShellWindow { if(had_prompt) input_status(L"Paste cancelled before sending"); if(clipboard_read_) clipboard_read_->cancel(); paste_prompt_.reset(); pending_paste_.reset(); + next_paste_fence_.reset(); if(had_prompt) restore_input_focus(); - if(paste_transfer_) paste_transfer_->cancel(); + input_.cancel_paste(); } void synthetic_paste(std::u16string text) { require(!closing_ && !paste_busy(),"synthetic paste cannot replace a running operation"); @@ -1270,6 +1330,9 @@ class ShellWindow { Result collect() { require_ui_thread(); if (closer_.joinable()) { closer_.join(); closer_joined_=true; } + if (resize_trace_.enabled() && !image_path_.empty() && + !resize_trace_.save(std::filesystem::path(image_path_).parent_path())) + std::cerr << "resize diagnostic write failed\n"; if(runtime_enabled_ && closer_joined_) { if(session_closed_on_closer_) { tf_shell::RuntimeSnapshot::FinalSession final; @@ -1320,7 +1383,7 @@ static int application(int argc, char** argv) { require(argc == 1 || (argc == 4 && (std::string(argv[1]) == "--test" || std::string(argv[1]) == "--test-owned")), "termforge_shell [--test mode frame.bmp]"); if (argc == 4) { mode = argv[2]; image = argv[3]; - require(mode == "candidate-tui" || mode == "candidate-alt" || mode == "candidate-scroll" || mode == "candidate-margins" || mode == "candidate-unicode" || mode == "candidate-input-escape" || mode == "normal" || mode == "close" || mode == "watchdog" || mode == "resize" || mode == "resize-close" || mode == "ui" || mode == "ui-edit" || mode == "ui-restore" || mode == "ui-workspace-save" || mode == "ui-workspace-restore" || mode == "ui-workspace-v1" || mode == "ui-workspace-empty-create" || mode == "ui-workspace-empty" || mode == "ui-workspace-corrupt" || mode == "ui-workspace-future" || mode == "ui-workspace-write-failure" || mode == "ui-workspace-close-failure" || mode == "ui-main" || mode == "ui-dpi" || mode == "ui-monitors" || mode == "ui-shells" || mode == "ui-launch-failure" || mode == "ui-split" || mode == "ui-history" || mode == "ui-keyboard" || mode == "ui-paste" || mode == "ui-paste-system" || mode == "ui-clipboard-system" || mode == "ui-selection" || mode == "ui-copy-system" || mode == "ui-find" || mode == "ui-find-stream" || mode == "ui-selection-bytes" || mode == "ui-copy-bytes-system" || mode == "ui-selection-context" || mode == "ui-copy-context-system" || mode == "ui-actions" || mode == "ui-actions-system" || mode == "ui-workflow-sessions", "invalid shell test mode"); + require(mode == "candidate-tui" || mode == "candidate-alt" || mode == "candidate-scroll" || mode == "candidate-margins" || mode == "candidate-unicode" || mode == "candidate-input-escape" || mode == "normal" || mode == "close" || mode == "watchdog" || mode == "resize" || mode == "resize-close" || mode == "ui" || mode == "ui-edit" || mode == "ui-restore" || mode == "ui-workspace-save" || mode == "ui-workspace-restore" || mode == "ui-workspace-v1" || mode == "ui-workspace-empty-create" || mode == "ui-workspace-empty" || mode == "ui-workspace-corrupt" || mode == "ui-workspace-future" || mode == "ui-workspace-write-failure" || mode == "ui-workspace-close-failure" || mode == "ui-main" || mode == "ui-dpi" || mode == "ui-monitors" || mode == "ui-shells" || mode == "ui-launch-failure" || mode == "ui-split" || mode == "ui-history" || mode == "ui-keyboard" || mode == "ui-paste" || mode == "ui-paste-system" || mode == "ui-paste-control-eof" || mode == "ui-paste-control-timeout" || mode == "ui-clipboard-system" || mode == "ui-selection" || mode == "ui-copy-system" || mode == "ui-find" || mode == "ui-find-stream" || mode == "ui-selection-bytes" || mode == "ui-copy-bytes-system" || mode == "ui-selection-context" || mode == "ui-copy-context-system" || mode == "ui-actions" || mode == "ui-actions-system" || mode == "ui-workflow-sessions", "invalid shell test mode"); if(mode=="ui-clipboard-system" || mode=="ui-copy-system" || mode=="ui-copy-bytes-system" || mode=="ui-copy-context-system" || mode=="ui-actions-system") tf_shell_test::require_hosted_clipboard(); if(std::string(argv[1])=="--test") { std::string gate; std::getline(std::cin, gate); require(gate == "GO", "missing owned-job gate"); @@ -1333,7 +1396,7 @@ static int application(int argc, char** argv) { wchar_t desktop_name[256]{}; DWORD desktop_size=0; require(GetUserObjectInformationW(GetThreadDesktop(GetCurrentThreadId()),UOI_NAME,desktop_name,sizeof(desktop_name),&desktop_size)!=FALSE && std::wstring_view(desktop_name).find(L"TermForgeShell-")==0,"shell test requires the runner-owned desktop"); - if (mode == "ui" || mode == "ui-edit" || mode == "ui-restore" || mode == "ui-workspace-save" || mode == "ui-workspace-restore" || mode == "ui-workspace-v1" || mode == "ui-workspace-empty-create" || mode == "ui-workspace-empty" || mode == "ui-workspace-corrupt" || mode == "ui-workspace-future" || mode == "ui-workspace-write-failure" || mode == "ui-workspace-close-failure" || mode == "ui-main" || mode == "ui-dpi" || mode == "ui-monitors" || mode == "ui-shells" || mode == "ui-launch-failure" || mode == "ui-split" || mode == "ui-history" || mode == "ui-keyboard" || mode == "ui-paste" || mode == "ui-paste-system" || mode == "ui-clipboard-system" || mode == "ui-selection" || mode == "ui-copy-system" || mode == "ui-find" || mode == "ui-find-stream" || mode == "ui-selection-bytes" || mode == "ui-copy-bytes-system" || mode == "ui-selection-context" || mode == "ui-copy-context-system" || mode == "ui-actions" || mode == "ui-actions-system" || mode == "ui-workflow-sessions") { + if (mode == "ui" || mode == "ui-edit" || mode == "ui-restore" || mode == "ui-workspace-save" || mode == "ui-workspace-restore" || mode == "ui-workspace-v1" || mode == "ui-workspace-empty-create" || mode == "ui-workspace-empty" || mode == "ui-workspace-corrupt" || mode == "ui-workspace-future" || mode == "ui-workspace-write-failure" || mode == "ui-workspace-close-failure" || mode == "ui-main" || mode == "ui-dpi" || mode == "ui-monitors" || mode == "ui-shells" || mode == "ui-launch-failure" || mode == "ui-split" || mode == "ui-history" || mode == "ui-keyboard" || mode == "ui-paste" || mode == "ui-paste-system" || mode == "ui-paste-control-eof" || mode == "ui-paste-control-timeout" || mode == "ui-clipboard-system" || mode == "ui-selection" || mode == "ui-copy-system" || mode == "ui-find" || mode == "ui-find-stream" || mode == "ui-selection-bytes" || mode == "ui-copy-bytes-system" || mode == "ui-selection-context" || mode == "ui-copy-context-system" || mode == "ui-actions" || mode == "ui-actions-system" || mode == "ui-workflow-sessions") { Workspace window(image,mode); return window.run(); } Result r; diff --git a/experiments/shell/test_candidate_tui.py b/experiments/shell/test_candidate_tui.py index 7e452db7..f7cdd397 100644 --- a/experiments/shell/test_candidate_tui.py +++ b/experiments/shell/test_candidate_tui.py @@ -10,7 +10,7 @@ from experiments.shell.unicode_checks import validate_candidate_unicode from experiments.shell.alternate_checks import STAGES, validate_candidate_alternate from experiments.shell.scroll_checks import STAGES as SCROLL_STAGES, validate_candidate_scroll -from experiments.shell.margin_checks import STAGES as MARGIN_STAGES, validate_candidate_margins +from experiments.shell.margin_checks import STAGES as MARGIN_STAGES, REVIEWED_BACKEND, validate_candidate_margins from experiments.shell.workflow_checks import validate_console_window @@ -130,6 +130,9 @@ def test_real_scroll_region(self): transport.replace(b"REGION_C", b"REGION_X") if corruption == "transport" else transport) def test_real_conpty_margin_difference(self): + expected_backend = os.environ.get("TERMFORGE_EXPECT_CONPTY_BACKEND", "system") + self.assertIn(expected_backend, ("system", REVIEWED_BACKEND)) + terminal_restored = expected_backend == REVIEWED_BACKEND for repeat in range(2): out = self.out / f"candidate-margins-{repeat}" record = execute(binary("termforge_shell"), "candidate-margins", out, timeout=20) @@ -138,7 +141,9 @@ def test_real_conpty_margin_difference(self): self.assertFalse(summary["margins_per_buffer_verified"]) self.assertFalse(summary["native_main_margins_restored"]) self.assertTrue(summary["conpty_margin_difference_observed"]) - self.assertTrue(summary["candidate_matches_native"]) + self.assertEqual(summary["conpty_backend"], expected_backend) + self.assertEqual(summary["terminal_main_margins_restored"], terminal_restored) + self.assertEqual(summary["candidate_matches_native"], not terminal_restored) self.assertTrue(summary["native_main_restored"]) self.assertFalse(summary["windterm_runtime_equivalence_verified"]) self.assertFalse(summary["full_r2_contract_verified"]) @@ -173,7 +178,13 @@ def test_real_conpty_margin_difference(self): with self.assertRaises(ValueError): validate_candidate_margins(changed, changed_consoles, b"" if corruption == "source" else source, - transport.replace(b"\x1b[?1049h", b"\x1b[?1048h") if corruption == "transport" else transport) + transport.replace(b"\x1b[?1049h", b"\x1b[?1048h") if corruption == "transport" else transport, + expected_backend=expected_backend) + if terminal_restored: + for margin in (b"\x1b[2;3r", b"\x1b[4;5r"): + with self.subTest(repeat=repeat, missing_margin=margin), self.assertRaises(ValueError): + validate_candidate_margins(data, consoles, source, transport.replace(margin, b""), + expected_backend=expected_backend) def test_real_unicode_scalar_preservation_and_glyphs(self): for repeat in range(2): diff --git a/experiments/shell/test_paste_checks.py b/experiments/shell/test_paste_checks.py new file mode 100644 index 00000000..0c53e053 --- /dev/null +++ b/experiments/shell/test_paste_checks.py @@ -0,0 +1,27 @@ +"""Reject falsely accepted UI paste evidence without launching a desktop.""" +import copy +import unittest +from experiments.shell.paste_checks import validate_paste_fence + + +class PasteFenceEvidenceTests(unittest.TestCase): + def test_requires_held_partial_progress_and_real_cancel_or_stop(self): + row = dict(name="cancel", before_sent=17391, before_accepted=18414, + fence=dict(generation=1, held=True, owned_bytes=1023, wait_finished=True, + other_responsive=True, exit="cancelled", final_state="cancelled", wrong_generation_rejected=True)) + validate_paste_fence(row, 1) + for key, value in (("generation", 2), ("held", False), ("owned_bytes", 1024), + ("wait_finished", False), ("exit", "released"), ("final_state", "stopped"), + ("other_responsive", False), ("wrong_generation_rejected", False)): + broken = copy.deepcopy(row) + broken["fence"][key] = value + with self.subTest(key=key), self.assertRaises(ValueError): + validate_paste_fence(broken, 1) + with self.assertRaises(ValueError): + validate_paste_fence({k: v for k, v in row.items() if k != "fence"}, 1) + close = copy.deepcopy(row) + close["name"] = "close" + close["fence"]["final_state"] = "stopped" + for actual_wake in ("cancelled", "stopped"): + close["fence"]["exit"] = actual_wake + validate_paste_fence(close, 1) diff --git a/experiments/shell/test_resize_trace.py b/experiments/shell/test_resize_trace.py new file mode 100644 index 00000000..4a1680f0 --- /dev/null +++ b/experiments/shell/test_resize_trace.py @@ -0,0 +1,128 @@ +"""A replay must preserve geometry/feed ordering and reject incomplete capture.""" +import copy +import unittest + +from experiments.shell.resize_trace import replay_plan + + +def fixture(): + events = [dict(kind='feed', offset=0, bytes=1, rows=24, columns=80), + dict(kind='desired', offset=1, bytes=0, rows=12, columns=60), + dict(kind='parser_begin', offset=1, bytes=0, rows=12, columns=60), + dict(kind='parser_end', offset=1, bytes=0, rows=12, columns=60), + dict(kind='host_begin', offset=1, bytes=0, rows=12, columns=60, generation=1), + dict(kind='host_end', offset=1, bytes=0, rows=12, columns=60, generation=1), + dict(kind='request', offset=1, bytes=0, rows=12, columns=60, generation=1), + dict(kind='feed', offset=1, bytes=2, rows=12, columns=60)] + return trace(events) + + +def trace(events): + for index, event in enumerate(events): + event.setdefault('qpc', index + 1) + event.setdefault('generation', 0) + event.setdefault('stage', 0) + return dict(schema=1, observer_order_only=True, qpc_frequency=10000000, events_overflow=False, + transport_overflow=False, consumed_bytes=3, events=events) + + +def find(trace, kind): + return next(event for event in trace['events'] if event['kind'] == kind) + + +class ResizeTraceTests(unittest.TestCase): + def test_preserves_feed_split_and_ui_geometry_without_turning_host_observation_into_resize(self): + self.assertEqual(replay_plan(fixture(), b'abc'), 'F 0 1\nR 12 60\nF 1 2\n') + + def test_rejects_missing_overflowing_and_reordered_capture(self): + for corruption in ('event-overflow', 'transport-overflow', 'count', 'gap', 'duplicate', + 'missing-feed', 'unknown', 'missing-end', 'feed-during-resize', 'bad-size'): + changed = copy.deepcopy(fixture()) + if corruption == 'event-overflow': + changed['events_overflow'] = True + elif corruption == 'transport-overflow': + changed['transport_overflow'] = True + elif corruption == 'count': + changed['consumed_bytes'] = 4 + elif corruption == 'gap': + changed['events'][-1]['offset'] = 2 + elif corruption == 'duplicate': + changed['events'].append(copy.deepcopy(changed['events'][-1])) + elif corruption == 'missing-feed': + changed['events'].pop() + elif corruption == 'unknown': + changed['events'][2]['kind'] = 'synthetic-ack' + elif corruption == 'missing-end': + changed['events'].remove(find(changed, 'parser_end')) + elif corruption == 'feed-during-resize': + changed['events'][3], changed['events'][-1] = changed['events'][-1], changed['events'][3] + else: + find(changed, 'parser_begin')['columns'] = 0 + with self.subTest(corruption=corruption), self.assertRaises(ValueError): + replay_plan(changed, b'abc') + + def test_schema_generation_host_pair_geometry_and_qpc_corruptions_are_rejected(self): + cases = [('host_begin', 'generation', -1), ('host_end', 'generation', 999), + ('host_begin', 'columns', 0), ('feed', 'rows', 200), ('host_end', 'rows', 13), + ('request', 'generation', 2), ('request', 'rows', 13), + ('parser_end', 'generation', 1), ('feed', 'generation', 1), + ('feed', 'qpc', -1), ('host_begin', 'qpc', 1), + ('host_end', 'qpc', True), ('feed', 'qpc', 1 << 64), + ('host_begin', 'generation', 1 << 32), ('feed', 'rows', True), + ('host_begin', 'stage', -1), ('feed', 'extra', 0)] + for kind, field, value in cases: + changed = fixture() + find(changed, kind)[field] = value + with self.subTest(kind=kind, field=field, value=value), self.assertRaises(ValueError): + replay_plan(changed, b'abc') + for field, value in [('schema', True), ('qpc_frequency', 0), ('qpc_frequency', False), + ('qpc_frequency', -1), ('qpc_frequency', 1.5), ('extra', 0)]: + changed = fixture() + changed[field] = value + with self.subTest(field=field, value=value), self.assertRaises(ValueError): + replay_plan(changed, b'abc') + for kind in ('desired', 'parser_begin', 'parser_end', 'request', 'host_begin', 'host_end'): + changed = fixture() + changed['events'].remove(find(changed, kind)) + with self.subTest(missing=kind), self.assertRaises(ValueError): + replay_plan(changed, b'abc') + for field in ('qpc', 'generation', 'rows', 'columns', 'stage'): + changed = fixture() + del find(changed, 'feed')[field] + with self.subTest(missing=field), self.assertRaises(ValueError): + replay_plan(changed, b'abc') + + def test_coalescing_and_worker_observation_before_request_are_valid(self): + events = [] + for generation, (rows, columns) in enumerate(((12, 60), (30, 90), (10, 50), (28, 100)), 1): + for kind in ('desired', 'parser_begin', 'parser_end'): + events.append(dict(kind=kind, rows=rows, columns=columns, offset=0, bytes=0)) + if generation == 3 and kind == 'parser_begin': + events.append(dict(kind='host_end', generation=2, rows=30, columns=90, offset=0, bytes=0)) + if generation == 2: + events.append(dict(kind='host_begin', generation=2, rows=rows, columns=columns, offset=0, bytes=0)) + events.append(dict(kind='request', generation=generation, rows=rows, columns=columns, offset=0, bytes=0)) + if generation == 4: + for kind in ('host_begin', 'host_end'): + events.append(dict(kind=kind, generation=4, rows=rows, columns=columns, offset=0, bytes=0)) + events.append(dict(kind='feed', rows=28, columns=100, offset=0, bytes=3)) + self.assertEqual(replay_plan(trace(events), b'abc'), 'R 12 60\nR 30 90\nR 10 50\nR 28 100\nF 0 3\n') + + def test_equal_qpc_is_valid_but_future_host_generation_and_overlapping_calls_are_not(self): + changed = fixture() + find(changed, 'host_end')['qpc'] = find(changed, 'host_begin')['qpc'] + self.assertEqual(replay_plan(changed, b'abc'), 'F 0 1\nR 12 60\nF 1 2\n') + changed = fixture() + begin = find(changed, 'host_begin') + begin['generation'] = 2 + find(changed, 'host_end')['generation'] = 2 + with self.assertRaises(ValueError): + replay_plan(changed, b'abc') + changed = fixture() + changed['events'][5] = copy.deepcopy(changed['events'][4]) + with self.assertRaises(ValueError): + replay_plan(changed, b'abc') + + +if __name__ == '__main__': + unittest.main() diff --git a/experiments/shell/test_shell.py b/experiments/shell/test_shell.py index 09782b1c..66512ac1 100644 --- a/experiments/shell/test_shell.py +++ b/experiments/shell/test_shell.py @@ -373,6 +373,33 @@ def test_paste_confirmation_and_hidden_target_preserve_visible_focus(self): "tab_enter_accept": True}) self.assertEqual((out / "input.bin").read_bytes(), b"DEFAULT_ENTER_ACCEPT\r" + b"P\xe6\xb1\x89\r" * 5000) + def test_paste_control_eof_remains_failure_after_cleanup(self): + failure = self._rejected_paste_control("eof", "EOF") + self.assertTrue(failure["eof_observed"]) + self.assertFalse(failure["timed_out"]) + + def test_paste_missing_go_remains_failure_after_cleanup(self): + failure = self._rejected_paste_control("timeout", "missing GO") + self.assertFalse(failure["eof_observed"]) + self.assertTrue(failure["timed_out"]) + + def _rejected_paste_control(self, mode, reason): + out = self.out / ("paste-control-" + mode) + with self.assertRaisesRegex(ValueError, "paste control " + reason + " rejected"): + execute(self.exe, "ui-paste-control-" + mode, out) + sample = json.loads((out / "sample.json").read_text()) + self.assertEqual(sample["exit_code"], 2) + self.assertFalse(sample["timed_out"]) + self.assertEqual(sample["job_after"]["active"], 0) + self.assertTrue(sample["desktop_gone"]) + failure = json.loads((out / "paste-control-failure.json").read_text()) + self.assertTrue(failure["wait_finished"]) + self.assertEqual(failure["state"], "failed") + self.assertEqual(failure["in_flight"], 0) + self.assertLessEqual(failure["retained_capacity"], 32) + self.assertEqual((out / "cancel.bin").read_bytes(), b"\xe6\xb1\x89" * 256) + return failure + @unittest.skipUnless(os.environ.get("GITHUB_ACTIONS") == "true" and os.environ.get("RUNNER_ENVIRONMENT") == "github-hosted", "real system clipboard UI gate requires a disposable hosted VM") @@ -463,6 +490,29 @@ def create_job(): def test_queues_apply_bounded_backpressure_and_reject_input(self): result = subprocess.run([str(binary("shell_queue_probe"))], capture_output=True, timeout=10) self.assertEqual(result.returncode, 0, result.stderr) + rows = [json.loads(line) for line in result.stdout.decode("utf-8").splitlines() if line.startswith("{")] + actual = [row for row in rows if row.get("case") == "real-workers"] + self.assertEqual([row.get("action") for row in actual], ["stop", "cancel", "cancel-stop-race"], result.stdout) + for row in actual: + for field, value in dict(total=4 * 1024 * 1024, input_submitted_before_stop=16384, + input_accepted_before_stop=17408, input_in_flight=1024, + paste_in_flight_before_stop=1024, child_prefix_read=16384, + sender_fence_held=True, input_joined=True, input_queued=0, + working_generation=1, pending_generation=3, completed_generation=3, + child_columns=102, child_rows=42, job_active_after=0).items(): + with self.subTest(action=row["action"], field=field): + self.assertEqual(row.get(field), value) + self.assertEqual(row["accepted_after"], 17408) + self.assertEqual(row["in_flight_after"], 0) + self.assertLessEqual(row["retained_capacity_after"], 32) + if row["action"] == "cancel": + self.assertEqual((row["sent_after"], row["state_after"]), (17408, 4)) + elif row["action"] == "stop": + self.assertEqual((row["sent_after"], row["state_after"]), (16384, 5)) + else: + self.assertIn(row["state_after"], (4, 5)) + self.assertGreaterEqual(row["sent_after"], 16384) + self.assertLessEqual(row["sent_after"], 17408) def test_invalid_arguments_create_no_session(self): result = subprocess.run([str(self.exe), "--test", "invalid", "unused.bmp"], diff --git a/experiments/shell/test_workflow_sessions.py b/experiments/shell/test_workflow_sessions.py index b1122c1a..de6cef3b 100644 --- a/experiments/shell/test_workflow_sessions.py +++ b/experiments/shell/test_workflow_sessions.py @@ -21,6 +21,7 @@ 'experiments/shell/workflow_controller.hpp', 'experiments/shell/workflow_fixture.hpp', 'experiments/shell/runtime_observation.hpp', 'experiments/shell/operation_observation.hpp', 'experiments/shell/queues.hpp', 'experiments/shell/resize.hpp', + 'experiments/shell/sender_fence.hpp', 'experiments/shell/sender_fence_pipe.hpp', 'experiments/shell/paste_checks.py', 'experiments/shell/queue_probe.cpp', 'experiments/shell/raw_console_receiver.hpp', 'experiments/shell/text_client.cpp', 'experiments/shell/input_client.cpp', 'experiments/shell/test_launcher.cpp', 'experiments/shell/paste.hpp', 'experiments/shell/paste_scenario_state.hpp', @@ -171,9 +172,12 @@ def test_paste_exact_prefix_rejects_late_eot_wrong_target_and_unreleased_capacit out=Path(temporary) (out/'cancel.bin').write_bytes('汉'.encode()*6000) (out/'close.bin').write_bytes('汉'.encode()*256) - cases=[dict(name=name,target=3,total=3000000,before_sent=16384,before_accepted=18000, + cases=[dict(name=name,target=3,total=3000000,before_sent=16977,before_accepted=18000, accepted=18000,sent=18000,in_flight=0,retained_capacity=0,checkpoint_bytes=768, - received=18000 if name=='cancel' else 768,flood_observed=True,state='cancelled') for name in ('cancel','close')] + received=18000 if name=='cancel' else 768,flood_observed=True,state='cancelled', + fence=dict(generation=i,held=True,owned_bytes=1023,wait_finished=True,other_responsive=True, + exit='cancelled',final_state='cancelled' if name=='cancel' else 'stopped',wrong_generation_rejected=True)) + for i,name in enumerate(('cancel','close'),1)] data={'paste_cases':cases};_validate_paste_cases(out,data,3) for key,value in [('target',4),('in_flight',1),('retained_capacity',3000000),('checkpoint_bytes',0),('sent',17999)]: broken=copy.deepcopy(data);broken['paste_cases'][0][key]=value diff --git a/experiments/shell/transport_completion.hpp b/experiments/shell/transport_completion.hpp index cc56ce3a..170d2f9e 100644 --- a/experiments/shell/transport_completion.hpp +++ b/experiments/shell/transport_completion.hpp @@ -10,20 +10,23 @@ namespace tf_shell_test { // column-one CUP. This is a record boundary, without assuming the initial cursor // row or claiming that the marker still occupies a separate visible screen row. // Complete CSI envelopes are omitted without interpreting their semantics. -// A command-editor BS before output invalidates that record until a real LF; +// A command-editor BS or nonempty-record CR before output invalidates that +// record until a real LF; // it cannot erase an echo prefix or splice a marker. Unknown ESC forms, // malformed/incomplete envelopes and other controls remain unproven. +// A bare CR leaves an empty, unedited record unchanged after an observed +// boundary and before marker output. It cannot discard text or end a record. // ShellWindow retains its independent 8192-byte observation cap. inline bool transport_output_completed(std::string_view raw, std::string_view marker) { if(marker.empty()) return false; for(const unsigned char byte:marker) if(byte<0x20 || byte==0x7f) return false; const auto prompt=[](std::string_view line) { return line=="TF>" || line=="TF> "; }; std::string line; - bool output_seen=false,prompt_seen=false,literal_line=true; + bool output_seen=false,prompt_seen=false,literal_line=true,record_boundary=false; const auto end_line=[&] { if(literal_line && line==marker) output_seen=true; else if(literal_line && output_seen && prompt(line)) prompt_seen=true; - line.clear(); literal_line=true; + line.clear(); literal_line=true; record_boundary=true; }; for(std::size_t i=0;i(raw[i++]); @@ -73,9 +76,14 @@ inline bool transport_output_completed(std::string_view raw, std::string_view ma if(!terminated) return false; } else return false; } else if(byte=='\r') { - // CR alone may overwrite an echo; only a following LF proves the - // literal output line has completed. - if(i==raw.size() || raw[i]!='\n') return false; + // Only CRLF completes literal text. A wrapped command echo may use + // bare CR/CUP, but that edited record cannot supply marker output. + // Only a later real LF permits a fresh independent output record. + if(i==raw.size() || raw[i]!='\n') { + if(output_seen) return false; + if(!line.empty() || !literal_line) literal_line=false; + else if(!record_boundary) return false; + } } else if(byte=='\n') { end_line(); } else if(byte=='\b') { diff --git a/experiments/shell/transport_completion_test.cpp b/experiments/shell/transport_completion_test.cpp index 1707f4d4..3179ccb6 100644 --- a/experiments/shell/transport_completion_test.cpp +++ b/experiments/shell/transport_completion_test.cpp @@ -31,6 +31,36 @@ int main() { line+"TF>",marker),"command redraw BS before a fresh literal output line does not poison later records"); check(transport_output_completed("echo old\b\r\n"+line+"TF_PS_MAJOR=5\r\nTF> ",marker), "fresh output remains independent of a prior edited command record"); + const std::string wrapped_cmd="\x1b[2;4Hf\x1b[2;5H\x1b[2;5Hor %I in (\"%CD%\") do @echo T \r" + "\x1b[3;1H\x1b[3;1HF_CMD_DIR=%~nxI\x1b[3;16H\x1b[3;16H\r\n"+ + title+"\x1b\\"+line+"\x1b]0;cmd.exe\x1b\\\r\nTF>"; + auto no_echo_cr=wrapped_cmd; + no_echo_cr.erase(no_echo_cr.find("\r\x1b[3;1H"),1); + check(transport_output_completed(no_echo_cr,marker), + "removing only the wrapped command echo CR isolates its rejection"); + check(transport_output_completed(wrapped_cmd,marker), + "wrapped command echo must not hide later independent output and prompt"); + check(transport_output_completed("echo old\rredraw\r\n"+line+"TF> ",marker), + "real LF separates an edited echo from new literal output"); + check(!transport_output_completed("echo \r\x1b[3;1H"+marker+"\x1b[4;1HTF>\r\nTF>",marker), + "CUP cannot recover a CR-edited record to invent output"); + check(!transport_output_completed("echo \r\x1b[3;1H"+marker+"\r\nTF>",marker), + "LF cannot retroactively turn a CR-edited echo into output"); + check(!transport_output_completed("echo \r\x1b[3;1Htail\r\n"+line,marker), + "a discarded wrapped echo still requires a fresh prompt"); + const std::string save="\x1b[?25l\x1b[1;3H> \x1b[93mWrite-Output \x1b[36m" + "'TF_SAVE_PS' \x1b[m\r\n" + "\x1b[63X\x1b[63C\r\x1b[?25hTF_SAVE_PS\r\nTF> "; + auto no_empty_cr=save; + no_empty_cr.erase(no_empty_cr.find("\r\x1b[?25h"),1); + check(transport_output_completed(no_empty_cr,"TF_SAVE_PS"), + "removing only the empty-record CR isolates the captured rejection"); + check(transport_output_completed(save,"TF_SAVE_PS"), + "captured PowerShell empty-record CR must not hide independent output and prompt"); + check(transport_output_completed("echo old\r\n\r"+line+"TF>",marker), + "an empty record CR preserves a later exact output and prompt"); + check(transport_output_completed("echo old\b\r\n\x1b[63X\x1b[63C\r\r"+line+"TF> ",marker), + "LF restores an independent record before repeated empty redraw CRs"); check(!transport_output_completed("TF>echo "+line+"TF>",marker),"command echo is not independent output"); check(!transport_output_completed(title+"\a\r\nTF>",marker),"OSC BEL payload is not output"); @@ -69,6 +99,22 @@ int main() { check(!transport_output_completed(line+"\bTF>",marker),"BS after a marker cannot manufacture prompt"); check(!transport_output_completed(line+"TF>\b",marker),"trailing prompt BS remains unknown"); check(!transport_output_completed("\t\r\n"+line+"TF>",marker),"other bare controls remain rejected"); + check(!transport_output_completed("\r"+line+"TF>",marker), + "initial bare CR lacks an observed record boundary"); + check(!transport_output_completed("echo\r\nTF>echo \r"+line+"TF>",marker), + "CR cannot erase a nonempty echo prefix"); + check(!transport_output_completed("echo\r\n\b\r"+line+"TF>",marker), + "CR cannot reset an empty BS-tainted record"); + check(!transport_output_completed("echo\r\nTF_CMD_DIR=\rrestore-cmd \xe7\x9b\xae\xe5\xbd\x95\r\nTF>",marker), + "CR-separated marker fragments do not form literal output"); + check(!transport_output_completed("echo\r\n\rTF>echo "+line+"TF>",marker), + "empty-record CR does not admit a later command echo"); + check(!transport_output_completed("echo\r\n\rTF_CMD_DIR=restore-cmd \xe7\x9b\xfe\xe5\xbd\x95\r\nTF>",marker), + "empty-record CR does not repair corrupted UTF-8 marker bytes"); + check(!transport_output_completed("echo\r\n\r"+line+"\x1b[31",marker), + "empty-record CR does not admit an incomplete trailing envelope"); + check(!transport_output_completed(line+"\rTF>",marker), + "bare CR after matched output does not manufacture a prompt"); std::cout<<"transport completion checks passed\n"; } catch(const std::exception& error) { std::cerr< +#include + +namespace tf_shell_test { +inline bool workflow_numbered_output_started(const std::string& role, + const std::vector& document) { + const auto prefix="TF_R108_"+role+'_'; + for(const auto& row:document) { + const auto& text=row.projection.text; + if(text.rfind(prefix,0)==0 && text.size()>prefix.size() && + text[prefix.size()]>='0' && text[prefix.size()]<='9') return true; + } + return false; +} +template +inline bool workflow_checkpoint_ready(const std::string& role, + const std::vector& document, + const View& view) { + // Prefixes can be visible halfway through an output-queue read. The + // fixed CR/LF fixture is sampled only on its next empty live row; the + // independent oracle still validates every captured numbered payload. + return view.cursor_row>=0 && static_cast(view.cursor_row)(view.cursor_row)]).text.empty() && + workflow_numbered_output_started(role,document); +} +} // namespace tf_shell_test diff --git a/experiments/shell/workflow_checkpoint_test.cpp b/experiments/shell/workflow_checkpoint_test.cpp new file mode 100644 index 00000000..56e73c20 --- /dev/null +++ b/experiments/shell/workflow_checkpoint_test.cpp @@ -0,0 +1,116 @@ +#include "workflow_checkpoint.hpp" +#include "output_record_checkpoint.hpp" +#include "../core/terminal.hpp" +#ifdef TERMFORGE_CANDIDATE_CORE +#include "../core/candidate_terminal.hpp" +#endif +#include +#include +#include +#include + +namespace { +void check(bool value,const char* message) { + if(!value) throw std::runtime_error(message); +} +template bool ready(Terminal& terminal) { + termforge::TextDocument document(terminal.screen); + std::vector lines; + for(size_t i=0;i void chunk_boundary(Terminal& terminal) { + // The fixture emits READY before signaling its side-channel readiness. + // Its 25 bytes can share an output-queue read with numbered source data. + std::ostringstream source; + source<<"\x1b[2J\x1b[HTF_R108_F5_READY\r\n"; + for(unsigned i=0;i<128;++i) + source<<"TF_R108_F5_"< void boundary_states(Terminal& terminal) { + terminal.feed("\x1b[2J\x1b[H"); + check(!ready(terminal),"empty startup must not qualify as numbered output"); + terminal.feed("TF_R108_F5_READY\r\n"); + check(!ready(terminal),"READY is not a numbered payload"); + terminal.feed("TF_R108_F5_00000 中文 é...................................\r"); + check(!ready(terminal),"CR alone leaves the completed row under the cursor"); + terminal.feed("\n"); + check(ready(terminal),"CR/LF completes the record boundary"); + terminal.feed("TF_R108_F5_"); + check(!ready(terminal),"an earlier complete row must not hide a new partial prefix"); + terminal.feed("00001 中文 é...................................\r\n"); + check(ready(terminal),"later fragmented record must reach its boundary"); + terminal.feed("\x1b[2J\x1b[HTF_R108_F4_00000 中文 é...................................\r\n"); + check(!ready(terminal),"another role alone must not make this role ready"); + terminal.feed("TF_R108_F5_00000 BROKEN\r\n"); + check(ready(terminal),"readiness must expose completed corrupt rows to the independent oracle"); +} +} +int main() { + int failures=0; + try { checkpoint_bounds(); } catch(const std::exception& error) { std::cerr<= row['before_accepted'] and row['in_flight'] == 0 and row['retained_capacity'] <= 32 and row['checkpoint_bytes'] == 768 and row.get('flood_observed') is True and row.get('state') in ('cancelled', 'failed'), 'paste did not remain bounded/partial/terminal') + validate_paste_fence(row, generation) raw = _bounded(out / (row['name']+'.bin'), 3000000) _require(0 < len(raw) == row['received'] <= row['sent'] and raw == expected[:len(raw)] and len(raw)%3 == 0, 'gated receiver differs from exact complete Unicode prefix') diff --git a/experiments/shell/workspace.hpp b/experiments/shell/workspace.hpp index f2411d64..7d4fa607 100644 --- a/experiments/shell/workspace.hpp +++ b/experiments/shell/workspace.hpp @@ -618,7 +618,7 @@ class Workspace { else if(self->test_mode_=="ui-split") self->scenario_split(); else if(self->test_mode_=="ui-history") self->scenario_history(); else if(self->test_mode_=="ui-keyboard") self->scenario_keyboard(); - else if(self->test_mode_=="ui-paste" || self->test_mode_=="ui-paste-system") self->scenario_paste(); + else if(self->test_mode_.rfind("ui-paste",0)==0) self->scenario_paste(); else if(self->test_mode_=="ui-clipboard-system") self->scenario_clipboard(); else if(self->test_mode_=="ui-selection" || self->test_mode_=="ui-copy-system") self->scenario_selection(); else if(self->test_mode_=="ui-selection-bytes" || self->test_mode_=="ui-copy-bytes-system") self->scenario_selection_bytes(); @@ -779,7 +779,7 @@ class Workspace { require(stage_==100 && collected_==1,"workspace keyboard scenario incomplete"); std::cout<<"{\"valid\":true,\"mode\":\"ui-keyboard\",\"sessions_closed\":1,\"active_children_after\":0,\"presented_done\":null}\n"; } - if(!image_.empty() && (test_mode_=="ui-paste" || test_mode_=="ui-paste-system" || test_mode_=="ui-clipboard-system")) { + if(!image_.empty() && (test_mode_.rfind("ui-paste",0)==0 || test_mode_=="ui-clipboard-system")) { require(stage_==100 && collected_==2,"workspace paste scenario incomplete"); std::cout<<"{\"valid\":true,\"mode\":\""<=256) return; + const long long now=static_cast(ms(started_)); + const bool helper0=evidence.helper_exited(); + const bool busy=first&&first->paste_busy(); + const char* owner_step=evidence.owner?evidence.owner->debug_step():"none"; + const bool owner_rel=evidence.owner&&evidence.owner->debug_released_signaled(); + const DWORD owner_err=evidence.owner?evidence.owner->debug_error():0; + const DWORD owner_native=evidence.owner?evidence.owner->debug_native_error():0; + const std::string sig=std::to_string(evidence.index)+":"+std::to_string(stage_)+":"+event+":"+ + (busy?"1":"0")+(helper0?"1":"0")+":"+owner_step+":"+(owner_rel?"1":"0")+":"+ + std::to_string(owner_err)+":"+std::to_string(owner_native); + if(sig==r99_last_sig && now-r99_last_ms<1000) return; + r99_last_sig=sig; r99_last_ms=now; ++r99_lines; + std::ofstream out(directory/"r99-debug.jsonl",std::ios::app); + out<<"{\"tag\":\"DEBUG-r99\",\"event\":\""<=4 && stage_<8) { evidence.pending.prompt_seen|=first->paste_prompt_handle()!=nullptr; evidence.pending.transfer_seen|=first->observed_paste()!=nullptr; } + debug_log("pump"); if(stage_==0 && first->ready()) { tf_shell_test::require_hosted_clipboard(); add(); stage_=1; } else if(stage_==1 && other && other->ready()) { @@ -64,13 +105,15 @@ inline void Workspace::scenario_clipboard() { evidence.pending.helper_pid=progress->process_id; evidence.helper.reset(OpenProcess(SYNCHRONIZE,FALSE,progress->process_id)); require(evidence.helper.get()!=nullptr,"product clipboard helper observation failed"); + debug_log("product-read-started"); evidence.target=first->handle(); - select(1); ordinary(other,"echo "+evidence.marker(false)+"\r"); stage_=4; + select(1); ordinary(other,"echo "+evidence.marker(false)+"\r"); debug_log("marker-during"); stage_=4; } else if(stage_==4 && other->live_contains_line(evidence.marker(false))) { evidence.pending.other_during=true; if(evidence.index<2) { if(first->paste_busy() || !evidence.helper_exited()) return; if(evidence.index==0) require(evidence.owner->occupied(),"clipboard owner stopped holding before failure"); + debug_log("error-feedback"); select(0); no_payload(); require(first->status_text()==L"system clipboard read failed" && IsWindowVisible(first->handle()) && active()==first,"clipboard read error is not the selected session's visible status"); @@ -102,14 +145,16 @@ inline void Workspace::scenario_clipboard() { tf_shell_test::ClipboardReleaseReason::target_closed:tf_shell_test::ClipboardReleaseReason::requester_cancelled,proof); } } - stage_=6; + debug_log("owner-release"); stage_=6; } else if(stage_==6 && (!evidence.owner || evidence.owner->released())) { if(evidence.owner) evidence.pending.owner_release=evidence.owner->finish(); + debug_log("owner-finish"); evidence.owner.reset(); - select(target_gone?0:1); ordinary(other,"echo "+evidence.marker(true)+"\r"); stage_=7; + select(target_gone?0:1); ordinary(other,"echo "+evidence.marker(true)+"\r"); debug_log("marker-after"); stage_=7; } else if(stage_==7 && other->live_contains_line(evidence.marker(true))) { evidence.pending.other_after=true; no_payload(); if(target_gone) { + debug_log("case-complete"); finish_case(); evidence.save(directory/"clipboard-workflow.json"); Bitmap frame(physical_width_,physical_height_); paint(frame.dc); const RECT rect=physical(terminal_); active()->render_to(frame.dc,rect.left,rect.top); frame.save(image_); @@ -118,13 +163,14 @@ inline void Workspace::scenario_clipboard() { require(!first->paste_busy(),"late clipboard result restarted input after cancellation/error"); select(0); const auto retry=evidence.retry(); tf_shell::write_unicode_clipboard(window_,std::u16string(retry.begin(),retry.end())); - SendMessageW(window_,WM_COMMAND,12,0); stage_=8; + SendMessageW(window_,WM_COMMAND,12,0); debug_log("retry-call"); stage_=8; } } else if(stage_==8 && !first->paste_busy() && captured()==evidence.baseline()+evidence.retry()) { require(!first->paste_prompt_handle(),"single-line retry unexpectedly confirmed"); evidence.pending.retry_verified=true; first->command(std::wstring(1,4)); stage_=9; } else if(stage_==9 && first->observed_output().find("TF_KEY_DONE")!=std::string::npos) { require(captured()==evidence.baseline()+evidence.retry(),"retry contains late data, Escape or missing bytes"); + debug_log("case-complete"); finish_case(); ++evidence.index; start_receiver(); stage_=2; } } diff --git a/experiments/shell/workspace_paste_scenario.hpp b/experiments/shell/workspace_paste_scenario.hpp index f9109f02..2a22a036 100644 --- a/experiments/shell/workspace_paste_scenario.hpp +++ b/experiments/shell/workspace_paste_scenario.hpp @@ -8,7 +8,7 @@ inline void Workspace::scenario_paste() { const auto directory=std::filesystem::path(image_).parent_path(); // The close case destroys the first tab. Never retain its C++ address // across a message-pump turn; the saved HWND is only checked for liveness. - auto* first=stage_<30?tabs_.front().shell.get():nullptr; + auto* first=(stage_<30 || stage_==114 || stage_==120)?tabs_.front().shell.get():nullptr; const bool system=test_mode_=="ui-paste-system"; auto request=[&](std::u16string text) { if(system) { @@ -76,15 +76,26 @@ inline void Workspace::scenario_paste() { evidence.start_receiver(directory/"cancel.bin","cancel"); first->begin_output_observation(); first->command(evidence.receiver_command(directory/"cancel.bin")); stage_=13; } else if(stage_==13 && evidence.receiver->ready()) { + const auto deadline=started_+std::chrono::milliseconds(35000); + first->arm_next_paste_fence(1,test_mode_=="ui-paste-control-timeout"? + (std::min)(deadline,Clock::now()+std::chrono::milliseconds(2000)):deadline); first->synthetic_paste(std::u16string(evidence.large_units,u'\u6c49')); evidence.transfer=first->observed_paste(); + evidence.fence=first->observed_paste_fence(); require(evidence.transfer!=nullptr,"cancel test could not observe its window transfer"); - active()->command(L"\recho TF_CANCEL_RESPONSIVE\r"); stage_=14; - } else if(stage_==14 && evidence.transfer->progress().sent>=16384 && active()->live_contains_line("TF_CANCEL_RESPONSIVE")) { + stage_=14; + } else if(stage_==14 && evidence.held()) { + active()->command(L"\recho TF_CANCEL_RESPONSIVE\r"); stage_=114; + } else if(stage_==114 && active()->live_contains_line("TF_CANCEL_RESPONSIVE")) { + evidence.pending.other_responsive=true; require(!tf_shell::paste_terminal(evidence.transfer->progress().state),"cancel transfer completed before receiver gate"); evidence.receiver->allow_read(); stage_=15; - } else if(stage_==15 && evidence.receiver->checkpoint()) { + } else if(stage_==15 && evidence.receiver->checkpoint() && first->observed_output().find("TF_PASTE_FLOOD")!=std::string::npos) { evidence.before_action(directory/"cancel.bin",first->observed_output().find("TF_PASTE_FLOOD")!=std::string::npos); + if(test_mode_=="ui-paste-control-eof") { + evidence.control->disconnect();evidence.control->dispatch();stage_=40;return; + } + if(test_mode_=="ui-paste-control-timeout") {stage_=41;return;} select(0); evidence.key(first->handle(),VK_ESCAPE); stage_=16; } else if(stage_==16 && (evidence.transfer->progress().state==tf_shell::PasteState::cancelling || evidence.transfer->progress().state==tf_shell::PasteState::cancelled)) { @@ -97,14 +108,19 @@ inline void Workspace::scenario_paste() { evidence.start_receiver(directory/"close.bin","close"); evidence.target=first->handle(); first->begin_output_observation(); first->command(evidence.receiver_command(directory/"close.bin")); stage_=19; } else if(stage_==19 && evidence.receiver->ready()) { + first->arm_next_paste_fence(2,started_+std::chrono::milliseconds(35000)); first->synthetic_paste(std::u16string(evidence.large_units,u'\u6c49')); evidence.transfer=first->observed_paste(); + evidence.fence=first->observed_paste_fence(); require(evidence.transfer!=nullptr,"close test could not observe its window transfer"); - select(1); active()->command(L"echo TF_CLOSE_RESPONSIVE\r"); stage_=20; - } else if(stage_==20 && evidence.transfer->progress().sent>=16384 && active()->live_contains_line("TF_CLOSE_RESPONSIVE")) { + select(1);stage_=20; + } else if(stage_==20 && evidence.held()) { + active()->command(L"echo TF_CLOSE_RESPONSIVE\r");stage_=120; + } else if(stage_==120 && active()->live_contains_line("TF_CLOSE_RESPONSIVE")) { + evidence.pending.other_responsive=true; require(!tf_shell::paste_terminal(evidence.transfer->progress().state),"close transfer completed before receiver gate"); evidence.receiver->allow_read(); stage_=21; - } else if(stage_==21 && evidence.receiver->checkpoint()) { + } else if(stage_==21 && evidence.receiver->checkpoint() && first->observed_output().find("TF_PASTE_FLOOD")!=std::string::npos) { evidence.before_action(directory/"close.bin",first->observed_output().find("TF_PASTE_FLOOD")!=std::string::npos); select(0); SendMessageW(window_,WM_COMMAND,2,0); stage_=30; } else if(stage_==30 && collected_==1 && !IsWindow(evidence.target)) { @@ -113,5 +129,15 @@ inline void Workspace::scenario_paste() { Bitmap frame(physical_width_,physical_height_); paint(frame.dc); const RECT rect=physical(terminal_); active()->render_to(frame.dc,rect.left,rect.top); frame.save(image_); stage_=100; close_all(); + } else if(stage_==40 && evidence.transfer->progress().state==tf_shell::PasteState::failed) { + require(evidence.control->eof_observed() && evidence.fence->snapshot(evidence.fence->ticket()).abort_reason== + tf_shell_test::SenderFence::AbortReason::control_eof,"control failure requires actual pipe EOF"); + evidence.save_control_failure(directory/"paste-control-failure.json"); + throw std::runtime_error("paste control EOF rejected"); + } else if(stage_==41 && evidence.transfer->progress().state==tf_shell::PasteState::failed) { + require(evidence.fence->snapshot(evidence.fence->ticket()).state==tf_shell_test::SenderFence::State::timed_out, + "missing GO requires actual fence deadline expiry"); + evidence.save_control_failure(directory/"paste-control-failure.json"); + throw std::runtime_error("paste control missing GO rejected"); } } diff --git a/experiments/shell/workspace_workflow_scenario.hpp b/experiments/shell/workspace_workflow_scenario.hpp index 36470e9a..9943d5d4 100644 --- a/experiments/shell/workspace_workflow_scenario.hpp +++ b/experiments/shell/workspace_workflow_scenario.hpp @@ -148,7 +148,8 @@ inline void Workspace::workflow_write() { <<",\"before_sent\":"<begin_output_observation();target->fixture_command(paste_scenario_.receiver_command(path));stage_=21; } else if(stage_==21 && paste_scenario_.receiver->ready()) { auto* target=workflow_shell("C"); + target->arm_next_paste_fence(1,started_+std::chrono::milliseconds(70000)); if(workflow_system_) { tf_shell_test::require_hosted_ime_input();workflow_select("C");tf_shell::write_unicode_clipboard(window_,std::u16string(paste_scenario_.large_units,u'汉')); workflow_system_shortcut("C",false);workflow_system_point("large-cancel-requested");stage_=221; } else { target->synthetic_paste(std::u16string(paste_scenario_.large_units,u'汉')); paste_scenario_.transfer=target->observed_paste();require(paste_scenario_.transfer!=nullptr,"workflow cancel transfer missing");stage_=22; + paste_scenario_.fence=target->observed_paste_fence(); } } else if(stage_==221) { paste_scenario_.transfer=workflow_shell("C")->observed_paste();if(!paste_scenario_.transfer) return; + paste_scenario_.fence=workflow_shell("C")->observed_paste_fence(); workflow_system_point("large-cancel-ready");stage_=22; - } else if(stage_==22 && paste_scenario_.transfer->progress().sent>=16384) { + } else if(stage_==22 && paste_scenario_.held()) { + paste_scenario_.other_consumed=workflow_shell("F1")->runtime_snapshot().consumed_bytes;stage_=222; + } else if(stage_==222 && workflow_shell("F1")->runtime_snapshot().consumed_bytes>paste_scenario_.other_consumed) { + paste_scenario_.pending.other_responsive=true; require(!tf_shell::paste_terminal(paste_scenario_.transfer->progress().state),"workflow cancel transfer completed before gate"); paste_scenario_.receiver->allow_read();stage_=23; - } else if(stage_==23 && paste_scenario_.receiver->checkpoint()) { + } else if(stage_==23 && paste_scenario_.receiver->checkpoint() && workflow_shell("C")->observed_output().find("TF_PASTE_FLOOD")!=std::string::npos) { paste_scenario_.before_action(std::filesystem::path(image_).parent_path()/"cancel.bin",workflow_shell("C")->observed_output().find("TF_PASTE_FLOOD")!=std::string::npos); backgrounds_running();workflow_checkpoint("paste-cancel-pending",0,{"B","C"}); workflow_select("C");paste_scenario_.key(workflow_shell("C")->handle(),VK_ESCAPE);stage_=24; @@ -337,35 +344,44 @@ inline void Workspace::scenario_workflow() { if(!gate("F4").ready() || !gate("F5").ready()) return; gate("F4").start(tf_shell_test::WorkflowPhase::output);gate("F5").start(tf_shell_test::WorkflowPhase::output);stage_=35; } else if(stage_==35) { + bool held=true; for(const std::string name:{"F4","F5"}) { - if(std::filesystem::file_size(gate(name).path.wstring()+L".source.bin")<4096) return; - const auto prefix="TF_R108_"+name+'_';bool consumed=false; - for(const auto& row:workflow_shell(name)->observed_document_lines()) { - const auto& text=row.projection.text; - if(text.rfind(prefix,0)==0 && text.size()>prefix.size() && text[prefix.size()]>='0' && text[prefix.size()]<='9') consumed=true; - } - if(!consumed) return; + if(std::filesystem::file_size(gate(name).path.wstring()+L".source.bin")<4096) {held=false;continue;} + auto* shell=workflow_shell(name); + const auto document=shell->observed_document_lines(); + if(!tf_shell_test::workflow_numbered_output_started(name,document)) {held=false;continue;} + shell->arm_output_record_checkpoint(started_+std::chrono::milliseconds(70000)); + if(!shell->output_record_checkpoint_held()) {held=false;continue;} + require(tf_shell_test::workflow_checkpoint_ready(name,document,shell->visible_snapshot()),"workflow checkpoint did not reach an empty record boundary"); } + if(!held) return; workflow_checkpoint("cycle-eight",evidence.cycle); + for(const std::string name:{"F4","F5"}) workflow_shell(name)->release_output_record_checkpoint(); if(evidence.cycle<5) {++evidence.cycle;stage_=30;} else stage_=40; } else if(stage_==40) { paste_scenario_.start_receiver(std::filesystem::path(image_).parent_path()/"close.bin","close"); auto* target=workflow_shell("C");paste_scenario_.target=target->handle();target->begin_output_observation();target->fixture_command(paste_scenario_.receiver_command(std::filesystem::path(image_).parent_path()/"close.bin"));stage_=41; } else if(stage_==41 && paste_scenario_.receiver->ready()) { auto* target=workflow_shell("C"); + target->arm_next_paste_fence(2,started_+std::chrono::milliseconds(70000)); if(workflow_system_) { tf_shell_test::require_hosted_ime_input();workflow_select("C");tf_shell::write_unicode_clipboard(window_,std::u16string(paste_scenario_.large_units,u'汉')); workflow_system_shortcut("C",false);workflow_system_point("large-close-requested");stage_=241; } else { target->synthetic_paste(std::u16string(paste_scenario_.large_units,u'汉')); paste_scenario_.transfer=target->observed_paste();require(paste_scenario_.transfer!=nullptr,"workflow close transfer missing");stage_=42; + paste_scenario_.fence=target->observed_paste_fence(); } } else if(stage_==241) { paste_scenario_.transfer=workflow_shell("C")->observed_paste();if(!paste_scenario_.transfer) return; + paste_scenario_.fence=workflow_shell("C")->observed_paste_fence(); workflow_system_point("large-close-ready");stage_=42; - } else if(stage_==42 && paste_scenario_.transfer->progress().sent>=16384) { + } else if(stage_==42 && paste_scenario_.held()) { + paste_scenario_.other_consumed=workflow_shell("F4")->runtime_snapshot().consumed_bytes;stage_=242; + } else if(stage_==242 && workflow_shell("F4")->runtime_snapshot().consumed_bytes>paste_scenario_.other_consumed) { + paste_scenario_.pending.other_responsive=true; require(!tf_shell::paste_terminal(paste_scenario_.transfer->progress().state),"workflow close transfer completed before gate");paste_scenario_.receiver->allow_read();stage_=43; - } else if(stage_==43 && paste_scenario_.receiver->checkpoint()) { + } else if(stage_==43 && paste_scenario_.receiver->checkpoint() && workflow_shell("C")->observed_output().find("TF_PASTE_FLOOD")!=std::string::npos) { paste_scenario_.before_action(std::filesystem::path(image_).parent_path()/"close.bin",workflow_shell("C")->observed_output().find("TF_PASTE_FLOOD")!=std::string::npos); workflow_checkpoint("paste-close-pending",5,{"B","C"});workflow_select("C");workflow_shell("C")->close();stage_=44; } else if(stage_==44) { diff --git a/research/msys-runtime-window-prefix/COPYING b/research/msys-runtime-window-prefix/COPYING new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/research/msys-runtime-window-prefix/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/research/msys-runtime-window-prefix/COPYING.LIB b/research/msys-runtime-window-prefix/COPYING.LIB new file mode 100644 index 00000000..65c5ca88 --- /dev/null +++ b/research/msys-runtime-window-prefix/COPYING.LIB @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/research/msys-runtime-window-prefix/CYGWIN_LICENSE b/research/msys-runtime-window-prefix/CYGWIN_LICENSE new file mode 100644 index 00000000..dba58e8d --- /dev/null +++ b/research/msys-runtime-window-prefix/CYGWIN_LICENSE @@ -0,0 +1,30 @@ +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESSED OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +Unless stated otherwise, the sources under the cygwin subdirectory, +as well as the sources under the cygserver subdirectory linked into +the Cygwin DLL, are licensed under the Lesser Gnu Public License, +version 3 or (at your option) any later version (LGPLv3+). See the +COPYING.LIB file for the exact wording of that license. + +Unless stated otherwise, the sources under the cygserver subdir not +linked into the Cygwin DLL, as well as the sources under the lsaauth +and the utils subdirectories are licensed under the Gnu Public License, +version 3 or (at your option) any later version (GPLv3+). See the +COPYING file for the exact wording of that license. + +Parts of the sources in any subdirectory are licensed using a BSD-like +license. The affected source files contain explicit copyright notices +to that effect. + +Linking Exception: + + As a special exception, the copyright holders of the Cygwin library + grant you additional permission to link libcygwin.a, crt0.o, and + gcrt0.o with independent modules to produce an executable, and to + convey the resulting executable under terms of your choice, without + any need to comply with the conditions of LGPLv3 section 4. An + independent module is a module which is not itself based on the + Cygwin library. + diff --git a/research/msys-runtime-window-prefix/README.md b/research/msys-runtime-window-prefix/README.md new file mode 100644 index 00000000..01c8be97 --- /dev/null +++ b/research/msys-runtime-window-prefix/README.md @@ -0,0 +1,16 @@ +# MSYS resize 输入前缀研究补丁 + +本目录保存 [#52](https://github.com/JavaZeroo/termforge/issues/52) 的外部运行库实验,结果与限制见[实验记录](../../docs/research/vim-msys-runtime-prototype.md)。这是公开 MSYS/Cygwin 源码的研究补丁,不是 WindTerm 源码;不进入 TermForge 的默认构建或发布包,也不表示选定新的产品依赖或兼容策略。 + +## 来源与许可 + +- 上游:[git-for-windows/msys2-runtime](https://github.com/git-for-windows/msys2-runtime/tree/2e2ef940151485490aff0c7b00c00965ef7a71a4),固定提交 `2e2ef940151485490aff0c7b00c00965ef7a71a4`。 +- 原文件:[winsup/cygwin/fhandler/console.cc](https://github.com/git-for-windows/msys2-runtime/blob/2e2ef940151485490aff0c7b00c00965ef7a71a4/winsup/cygwin/fhandler/console.cc),属于 Cygwin,文件头要求遵循 `CYGWIN_LICENSE`。 +- 本补丁的上下文和修改遵循该文件的上游许可,**不由 TermForge 根目录的 MIT 许可证覆盖**。固定版本的 [CYGWIN_LICENSE](CYGWIN_LICENSE) 将该子目录默认许可列为 LGPLv3 或更新版本,并保留其中的 Linking Exception;完整文本 [COPYING.LIB](COPYING.LIB) 和 [COPYING](COPYING) 随附,三份许可文件均原样来自同一上游提交的 `winsup/`。 +- 修改方:TermForge contributors;日期:2026-10-27。仅调整 `WINDOW_BUFFER_SIZE_EVENT` 分支的事件消费位置;保留 `send_winch_maybe` 的解锁、SIGWINCH 与再加锁。鼠标分支、信号处理器 longjmp 的实际回归及正式兼容方案均未由本补丁验收。 + +## 核对补丁 + +在上述固定上游 checkout 中运行 `git apply --check /path/to/window-prefix.patch`,然后应用补丁。原源与补丁源分别构建,不能拿新的二进制直接覆盖原验收目标并宣称通过。补丁 SHA-256 为 `e942c2d08b7cc5a294203bf0a52f7414b9b19f3cd675624644feadf907418c77`。 + +本轮使用独立 SDK 和原 Vim 的隔离副本,TermForge 自身的产品代码、测试方法、输入、断言与期限未改;构建条件、实际样本及不能外推的边界见实验记录。DLL、原始终端输出、工作目录与工具日志仅保存在本地,没有随本目录发布。 diff --git a/research/msys-runtime-window-prefix/build-packages.txt b/research/msys-runtime-window-prefix/build-packages.txt new file mode 100644 index 00000000..9e594974 --- /dev/null +++ b/research/msys-runtime-window-prefix/build-packages.txt @@ -0,0 +1,336 @@ +apr 1.7.6-2 +apr-util 1.6.5-1 +asciidoc 10.2.1-2 +autoconf-wrapper 20260320-3 +autoconf2.13 2.13-6 +autoconf2.69 2.69-4 +autoconf2.71 2.71-4 +autoconf2.72 2.72-4 +autoconf2.73 2.73-1 +autogen 5.18.16-6 +automake-wrapper 20260320-3 +automake1.11 1.11.6-6 +automake1.12 1.12.6-6 +automake1.13 1.13.4-7 +automake1.14 1.14.1-6 +automake1.15 1.15.1-4 +automake1.16 1.16.5-1 +automake1.17 1.17-2 +automake1.18 1.18.1-2 +automake1.19 1.19-1 +autotools 2026.08.04-1 +bash 5.3.015-2 +bash-completion 2.18.0-1 +binutils 2.47-1 +bison 3.8.2-5 +bsdcpio 3.8.10-2 +bsdtar 3.8.10-2 +bzip2 1.0.8-4 +ca-certificates 20260816-2 +cocom 0.996-6 +coreutils 8.32-5 +curl 8.23.0-1 +dash 0.5.13.5-1 +db 6.2.32-6 +diffstat 1.69-1 +diffutils 3.12-1 +docbook-xml 4.5-6 +docbook-xsl 1.79.2-3 +docbook-xsl-ns 1.79.2-2 +docx2txt 1.4-2 +dos2unix 7.5.7-1 +expat 2.9.0-1 +file 5.48-1 +filesystem 2026.10.17-1 +findutils 4.11.0-2 +flex 2.6.4-4 +gawk 5.4.1-1 +gcc 16.2.0-1 +gcc-libs 16.2.0-1 +gdb 18.1-2 +gdbm 1.26-1 +gettext 0.22.5-1 +gettext-devel 0.22.5-1 +git 2.56.0-1 +git-for-windows-keyring 1.1.4.a0be1ed4-1 +gmp 6.3.0-2 +gnupg 2.4.9-3 +gnutls 3.8.13-2 +grep 1~3.0-7 +groff 1.24.2-1 +gzip 1.15-1 +heimdal 7.8.0-5 +heimdal-libs 7.8.0-5 +help2man 1.49.3-2 +iconv 1.19-1 +info 7.2-3 +isl 0.28-1 +less 710-1 +libarchive 3.8.10-2 +libasprintf 0.22.5-1 +libassuan 3.0.2-1 +libbz2 1.0.8-4 +libcbor 0.11.0-1 +libcurl 8.23.0-1 +libdb 6.2.32-6 +libedit 20240808_3.1-1 +libexpat 2.9.0-1 +libffi 3.8.0-1 +libfido2 1.15.0-1 +libgc 8.2.12-1 +libgcrypt 1.12.4-1 +libgdbm 1.26-1 +libgettextpo 0.22.5-1 +libgnutls 3.8.13-2 +libgpg-error 1.61-1 +libguile 3.0.11-1 +libhogweed 4.0-1 +libiconv 1.19-1 +libiconv-devel 1.19-1 +libidn2 2.3.8-1 +libintl 0.22.5-1 +libksba 1.8.1-1 +libltdl 2.6.2-2 +liblz4 1.10.0-1 +liblzma 5.8.4-1 +libnettle 4.0-1 +libnghttp2 1.70.0-1 +libnpth 1.8-1 +libopenssl 3.5.9-1 +libp11-kit 0.26.5-1 +libpcre 8.45-5 +libpcre16 8.45-5 +libpcre2_16 10.49-1 +libpcre2_32 10.49-1 +libpcre2_8 10.49-1 +libpcre2posix 10.49-1 +libpcre32 8.45-5 +libpcrecpp 8.45-5 +libpcreposix 8.45-5 +libpipeline 1.5.8-1 +libpsl 0.23.2-1 +libreadline 8.3.006-1 +libsasl 2.1.28-5 +libserf 1.3.10-2 +libsqlite 3.54.0-1 +libssh2 1.11.1-1 +libtasn1 4.21.0-1 +libtool 2.6.2-2 +libunistring 1.4.2-1 +libunrar 7.3.1-1 +libutil-linux 2.40.2-2 +libxcrypt 4.5.2-1 +libxml2 2.15.4-1 +libxslt 1.1.45-1 +libxxhash 0.8.4-1 +libzstd 1.5.7-1 +lndir 1.0.6-1 +m4 1.4.21-1 +make 4.4.1-3 +man-db 2.13.1-2 +markdown 1.0.1-2 +mingw-w64-cross-common-binutils 2.47-1 +mingw-w64-cross-crt 14.0.0.r501.gb13721ccf-1 +mingw-w64-cross-gcc 16.2.0-3 +mingw-w64-cross-mingw32-binutils 2.47-1 +mingw-w64-cross-mingw32-crt 14.0.0.r501.gb13721ccf-1 +mingw-w64-cross-mingw32-gcc 16.2.0-3 +mingw-w64-cross-mingw32-headers 14.0.0.r501.gb13721ccf-1 +mingw-w64-cross-mingw32-windows-default-manifest 6.4-5 +mingw-w64-cross-mingw32-winpthreads 14.0.0.r501.gb13721ccf-1 +mingw-w64-cross-mingw32-zlib 1.3.2-2 +mingw-w64-cross-mingw64-binutils 2.47-1 +mingw-w64-cross-mingw64-crt 14.0.0.r501.gb13721ccf-1 +mingw-w64-cross-mingw64-gcc 16.2.0-3 +mingw-w64-cross-mingw64-headers 14.0.0.r501.gb13721ccf-1 +mingw-w64-cross-mingw64-windows-default-manifest 6.4-5 +mingw-w64-cross-mingw64-winpthreads 14.0.0.r501.gb13721ccf-1 +mingw-w64-cross-mingw64-zlib 1.3.2-2 +mingw-w64-cross-mingwarm64-zlib 1.3.2-2 +mingw-w64-cross-ucrt64-binutils 2.47-1 +mingw-w64-cross-ucrt64-crt 14.0.0.r501.gb13721ccf-1 +mingw-w64-cross-ucrt64-gcc 16.2.0-3 +mingw-w64-cross-ucrt64-headers 14.0.0.r501.gb13721ccf-1 +mingw-w64-cross-ucrt64-windows-default-manifest 6.4-5 +mingw-w64-cross-ucrt64-winpthreads 14.0.0.r501.gb13721ccf-1 +mingw-w64-cross-ucrt64-zlib 1.3.2-2 +mingw-w64-cross-zlib 1.3.2-2 +mingw-w64-ucrt-x86_64-7zip 26.05-1 +mingw-w64-ucrt-x86_64-antiword 0.37-3 +mingw-w64-ucrt-x86_64-asciidoctor 2.0.26-2 +mingw-w64-ucrt-x86_64-binutils 2.47-4 +mingw-w64-ucrt-x86_64-brotli 1.2.1-1 +mingw-w64-ucrt-x86_64-bzip2 1.0.8-4 +mingw-w64-ucrt-x86_64-c-ares 1.34.9-1 +mingw-w64-ucrt-x86_64-ca-certificates 20260816-3 +mingw-w64-ucrt-x86_64-cc-libs 16.2.0-6 +mingw-w64-ucrt-x86_64-connect 1.105-5 +mingw-w64-ucrt-x86_64-crt 14.0.0.r536.g7ce22f663-1 +mingw-w64-ucrt-x86_64-curl-openssl-alternate 8.23.0-1 +mingw-w64-ucrt-x86_64-curl-winssl 8.23.0-1 +mingw-w64-ucrt-x86_64-cv2pdb 0.53-1 +mingw-w64-ucrt-x86_64-docx2txt-rs 0.2.0-1 +mingw-w64-ucrt-x86_64-expat 2.9.0-1 +mingw-w64-ucrt-x86_64-gcc 16.2.0-6 +mingw-w64-ucrt-x86_64-gcc-libs 16.2.0-6 +mingw-w64-ucrt-x86_64-gdb 18.1-3 +mingw-w64-ucrt-x86_64-gdbm 1.26-1 +mingw-w64-ucrt-x86_64-gettext-libtextstyle 1.0-1 +mingw-w64-ucrt-x86_64-gettext-runtime 1.0-1 +mingw-w64-ucrt-x86_64-gettext-tools 1.0-1 +mingw-w64-ucrt-x86_64-git 2.56.0.4-1 +mingw-w64-ucrt-x86_64-git-credential-manager 2.9.1-1 +mingw-w64-ucrt-x86_64-git-credential-wincred 2.56.0.4-1 +mingw-w64-ucrt-x86_64-git-doc-html 2.56.0.4-1 +mingw-w64-ucrt-x86_64-git-extra 1.1.715.85f886165-1 +mingw-w64-ucrt-x86_64-git-for-windows-addons 2.56.0.4-1 +mingw-w64-ucrt-x86_64-git-gui 2.56.0.4-1 +mingw-w64-ucrt-x86_64-git-lfs 3.8.1-1 +mingw-w64-ucrt-x86_64-git-perl 2.56.0.4-1 +mingw-w64-ucrt-x86_64-git-send-email 2.56.0.4-1 +mingw-w64-ucrt-x86_64-git-subtree 2.56.0.4-1 +mingw-w64-ucrt-x86_64-git-svn 2.56.0.4-1 +mingw-w64-ucrt-x86_64-gitk 2.56.0.4-1 +mingw-w64-ucrt-x86_64-gmp 6.3.0-2 +mingw-w64-ucrt-x86_64-gnutls 3.8.13-3 +mingw-w64-ucrt-x86_64-headers 14.0.0.r536.g7ce22f663-1 +mingw-w64-ucrt-x86_64-isl 0.28-1 +mingw-w64-ucrt-x86_64-libatomic 16.2.0-6 +mingw-w64-ucrt-x86_64-libb2 0.98.1-3 +mingw-w64-ucrt-x86_64-libffi 3.8.0-1 +mingw-w64-ucrt-x86_64-libgcc 16.2.0-6 +mingw-w64-ucrt-x86_64-libgomp 16.2.0-6 +mingw-w64-ucrt-x86_64-libiconv 1.19-1 +mingw-w64-ucrt-x86_64-libidn2 2.3.8-4 +mingw-w64-ucrt-x86_64-libmangle 14.0.0.r536.g7ce22f663-1 +mingw-w64-ucrt-x86_64-libpsl 0.21.5-3 +mingw-w64-ucrt-x86_64-libquadmath 16.2.0-6 +mingw-w64-ucrt-x86_64-libssh2-wincng 1.11.1-2 +mingw-w64-ucrt-x86_64-libstdc++ 16.2.0-6 +mingw-w64-ucrt-x86_64-libsystre 1.0.2-3 +mingw-w64-ucrt-x86_64-libtasn1 4.21.0-1 +mingw-w64-ucrt-x86_64-libtre 0.9.0-2 +mingw-w64-ucrt-x86_64-libunistring 1.4.2-1 +mingw-w64-ucrt-x86_64-libwinpthread 14.0.0.r536.g7ce22f663-1 +mingw-w64-ucrt-x86_64-libyaml 0.2.5-2 +mingw-w64-ucrt-x86_64-libzip 1.12.1-1 +mingw-w64-ucrt-x86_64-mpc 1.4.1-1 +mingw-w64-ucrt-x86_64-mpdecimal 4.0.1-3 +mingw-w64-ucrt-x86_64-mpfr 4.2.2-3 +mingw-w64-ucrt-x86_64-ncurses 6.6-4 +mingw-w64-ucrt-x86_64-nettle 4.0-1 +mingw-w64-ucrt-x86_64-nghttp2 1.70.0-1 +mingw-w64-ucrt-x86_64-odt2txt 0.5-4 +mingw-w64-ucrt-x86_64-openssl 3.5.9-1 +mingw-w64-ucrt-x86_64-osslsigncode 2.14-1 +mingw-w64-ucrt-x86_64-p11-kit 0.26.5-1 +mingw-w64-ucrt-x86_64-pcre 8.45-2 +mingw-w64-ucrt-x86_64-pcre2 10.49-1 +mingw-w64-ucrt-x86_64-pkgconf 1~3.0.10-1 +mingw-w64-ucrt-x86_64-python 3.14.9-1 +mingw-w64-ucrt-x86_64-readline 8.3.003-1 +mingw-w64-ucrt-x86_64-ruby 4.0.9-1 +mingw-w64-ucrt-x86_64-sqlite3 3.54.0-1 +mingw-w64-ucrt-x86_64-tcl 8.6.18-1 +mingw-w64-ucrt-x86_64-termcap 1.3.1-8 +mingw-w64-ucrt-x86_64-tk 8.6.18-1 +mingw-w64-ucrt-x86_64-tools 14.0.0.r536.g7ce22f663-1 +mingw-w64-ucrt-x86_64-tzdata 2026e-1 +mingw-w64-ucrt-x86_64-windows-default-manifest 20260815-1 +mingw-w64-ucrt-x86_64-wineditline 2.208-1 +mingw-w64-ucrt-x86_64-winpthreads 14.0.0.r536.g7ce22f663-1 +mingw-w64-ucrt-x86_64-wintoast 1.0.0.275.a44fa02-1 +mingw-w64-ucrt-x86_64-xpdf-tools 4.06-1 +mingw-w64-ucrt-x86_64-xxhash 0.8.4-1 +mingw-w64-ucrt-x86_64-xz 5.8.4-1 +mingw-w64-ucrt-x86_64-zlib 1.3.2-3 +mingw-w64-ucrt-x86_64-zstd 1.5.7-2 +mintty 1~3.8.3-1 +mpc 1.4.1-1 +mpdecimal 4.0.1-1 +mpfr 4.2.2-1 +msys2-keyring 1~20260814-1 +msys2-launcher 1.5-3 +msys2-runtime 3.6.10-4 +msys2-runtime-devel 3.6.10-4 +msys2-w32api-headers 14.0.0.r0.g9b3dd0125-1 +msys2-w32api-runtime 14.0.0.r0.g9b3dd0125-1 +nano 9.2-1 +ncurses 6.6-2 +nettle 4.0-1 +openssh 10.6p1-1 +openssl 3.5.9-1 +p11-kit 0.26.5-1 +pacman 6.1.0-26 +pacman-contrib 1.10.6-2 +pacman-mirrors 20260129-1 +patch 2.8.1-1 +patchutils 0.4.3-1 +pcre 8.45-5 +pcre2 10.49-1 +perl 5.42.3-3 +perl-authen-sasl 2.2100-2 +perl-class-inspector 1.36-2 +perl-clone 0.51-1 +perl-convert-binhex 1.125-3 +perl-encode-locale 1.05-3 +perl-error 0.17030-3 +perl-file-listing 6.16-2 +perl-file-sharedir 1.118-2 +perl-html-parser 3.86-2 +perl-html-tagset 3.24-2 +perl-http-cookiejar 0.014-2 +perl-http-cookies 6.12-2 +perl-http-daemon 6.17-2 +perl-http-date 6.08-2 +perl-http-message 7.04-2 +perl-http-negotiate 6.01-4 +perl-inc-latest 0.500-3 +perl-io-html 1.004-3 +perl-io-socket-ssl 2.099-2 +perl-io-stringy 2.113-4 +perl-json 4.11-2 +perl-libwww 6.83-3 +perl-locale-gettext 1.07-14 +perl-lwp-mediatypes 6.05-2 +perl-mailtools 2.22-2 +perl-mime-tools 5.518-2 +perl-module-build 0.4234-3 +perl-net-http 6.24-2 +perl-net-smtp-ssl 1.04-3 +perl-net-ssleay 1.96-2 +perl-termreadkey 2.38-9 +perl-test-pod 1.52-3 +perl-timedate 2.35-2 +perl-try-tiny 0.32-2 +perl-uri 5.40-1 +perl-www-robotrules 6.03-2 +perl-xml-parser 2.59-2 +perl-yaml-syck 1.47-3 +pinentry 1.3.3-1 +pkgconf 1.7.4-1 +pkgfile 26-1 +popt 1.19-1 +python 3.12.15-1 +rebase 4.5.0-5 +rsync 3.5.1-2 +sed 4.9-1 +ssh-pageant 1.4.r15.gc804ba4-1 +subversion 1.14.5-3 +swig 4.5.1-1 +tar 1.35-3 +tig 2.6.1-1 +time 1.10-1 +tzcode 2026e-1 +unzip 6.0-3 +util-linux 2.40.2-2 +vim 9.2.1178-1 +which 2.25-1 +windows-default-manifest 20260815-1 +winpty 0.4.3-3 +xmlto 0.0.29-2 +xxhash 0.8.4-1 +xz 5.8.4-1 +zlib 1.3.2-1 +zlib-devel 1.3.2-1 +zstd 1.5.7-1 diff --git a/research/msys-runtime-window-prefix/window-prefix.patch b/research/msys-runtime-window-prefix/window-prefix.patch new file mode 100644 index 00000000..92f2b91b --- /dev/null +++ b/research/msys-runtime-window-prefix/window-prefix.patch @@ -0,0 +1,27 @@ +--- a/winsup/cygwin/fhandler/console.cc ++++ b/winsup/cygwin/fhandler/console.cc +@@ -1486,12 +1486,18 @@ + break; + + case WINDOW_BUFFER_SIZE_EVENT: +- if (send_winch_maybe ()) +- { +- stat = input_winch; +- goto out; +- } +- /* fall through */ ++ { ++ /* Retire the peeked prefix while input_mutex still protects it. ++ send_winch_maybe releases that mutex for signal delivery, so ++ neither the queue prefix nor the remaining peek is valid after ++ it returns. A non-resizing event starts a fresh input pass. */ ++ DWORD discarded = 0; ++ acquire_attach_mutex (mutex_timeout); ++ ReadConsoleInputW (get_handle (), input_rec, i + 1, &discarded); ++ release_attach_mutex (); ++ con.num_processed -= min (con.num_processed, discarded); ++ return send_winch_maybe () ? input_winch : stat; ++ } + default: + continue; + } diff --git a/research/ui/results/2026-10-27-vim-native-reference/validation.json b/research/ui/results/2026-10-27-vim-native-reference/validation.json new file mode 100644 index 00000000..e02b6d9c --- /dev/null +++ b/research/ui/results/2026-10-27-vim-native-reference/validation.json @@ -0,0 +1,808 @@ +{ + "schema_version": 1, + "purpose": "bounded original WindTerm behavior observation, not product acceptance", + "source_commit": "b60cd65b9fa6af724279780faa8d78847eb5cef0", + "reference_version": "WindTerm 2.7.0 Windows x64", + "environment": { + "observed_cmd_banner_windows_version": "10.0.26200.9457", + "rea_cli_version": "6.7.0", + "window_station": "per-run-owned-non-input", + "profile": "one-synthetic-local-shell" + }, + "observations": [ + { + "id": "entry", + "declared_maximum_runs": 1, + "actual_runs": 1, + "source_commit_recorded_in_plan": null, + "rea_duration_seconds": 30, + "scenario_timeout_ms": 28000, + "status": "observed", + "stage": 5, + "scenario_elapsed_ms_diagnostic_only": 2010, + "scenario_error": null, + "script_sha256": "e8774e12a08eedf872ae87aa045057b76fb2c91b125a289e43fbf6c555834b3f", + "template_sha256": "30c93bba80eff6881dca333fdbcda21838475ea0eb44aed7664dff434967e1a7", + "fixture_sha256": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797", + "probe_files_sha256": { + "launch.py": "8b5ffe020fd3c92f23b188e5673b32396c882d9e6d1cdfc8b57fd14cc2ffee2d", + "qt_r1_text.js": "30c93bba80eff6881dca333fdbcda21838475ea0eb44aed7664dff434967e1a7", + "driver.js": "cf445845d21860f3102f8f546dfe3f452e2908badc0b62ad8d6dde634fa034fa", + "candidate-vim.vim": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797", + "launcher.patch": "a3c3163fde217b5198fae8b1c80f45e9fb4f843df83ad6cc8de347eac1e0a411" + }, + "source_files_sha256": { + "research/ui/r1_launch.py": "54fb41872c6abcc48242a0938044184854b016785cc13659c66b1bf404da0d8a", + "research/ui/launch.py": "6970e0d4f2fe5e7ec4827df7aa1026cd0e74aeb828cdc9b7a0b42b3c887eb4e6", + "research/ui/qt_r1_text.js": "f5820d413fdd72c13dffcefbf4d0ade615abfe15754c6b0b52e1e098814eea6f" + }, + "helper_exe_sha256": {}, + "reference_identity": { + "exe_sha256": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "files_manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203" + }, + "provider": { + "evidence_id": "ev_8e744c8ba99a794f4c59caeade8fc5fb62bdea0169cfc89c21cd2981660f06f1", + "name": "Frida", + "version": null, + "confidence": "observed", + "subject": null, + "environment": null, + "captured_state": "running", + "messages_truncated": false, + "cleanup_error": null, + "limitations": [ + "Artifact identity is unavailable for this observation.", + "The target operating system and architecture were not reported by this observation." + ] + }, + "backend_calls": [ + { + "function": "winpty_open", + "success": true, + "size": null + }, + { + "function": "winpty_spawn", + "success": true, + "size": null + }, + { + "function": "winpty_set_size", + "success": true, + "size": [ + 91, + 23 + ] + } + ], + "probe_error": { + "count": 0, + "kind": null + }, + "profile_check": { + "only_synthetic_session": true, + "session_count": 1 + }, + "outer_cleanup": { + "job_accounting": { + "active": 0, + "total": 13, + "terminated": 0 + }, + "deliberate_termination_requested": true, + "termination_counter_is_not_natural_exit_proof": true + }, + "raw_files": { + "run.json": { + "bytes": 615, + "sha256": "df015ba174dc04e60906175a9205f996d629d8ae29c7d07b696ca407dfe8eb6f" + }, + "probe.js": { + "bytes": 17645, + "sha256": "e8774e12a08eedf872ae87aa045057b76fb2c91b125a289e43fbf6c555834b3f" + }, + "probe-result.json": { + "bytes": 137, + "sha256": "0f9b75e9023dfa3b034e8b69d3fa75e732274233dd87f0307cfc9ae29f50c267" + }, + "profile-check.json": { + "bytes": 52, + "sha256": "5e522a64e98197722ccd084639b1ce50258b238e03be2f016e81c8af023b5ee1" + }, + "cleanup.json": { + "bytes": 43, + "sha256": "43174a2e069f646c7d5370c56c5b4aedae68af7b4735434b0aba110f7442a969" + }, + "evidence.json": { + "bytes": 35278, + "sha256": "78edce088914731eb60d070c4695463cb2d9c1fd491fa4d35ad86d4fe5f210f1" + }, + "stderr.txt": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "files.json": { + "bytes": 637666, + "sha256": "a25c87cba935294d5bfd3bbc76c650d31a8473809564d798ad5a889e4aea00e8" + } + }, + "synthetic_artifacts": { + "candidate-input.bin": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "candidate-insert-log.txt": { + "bytes": 39, + "sha256": "dd1833a26b6aa08e7d20b2e6731bb15db71a01c24705592ef26670f4d3639665" + }, + "candidate-startup-log.txt": { + "bytes": 6, + "sha256": "3dfa3fb239f56c778a1e9b33eb31328349edd155a95806dac4378904e88527e5" + }, + "candidate-vim.vim": { + "bytes": 334, + "sha256": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797" + } + }, + "limitations": [ + "Vim is started by cmd /k instead of a typed launch command in this entry feasibility probe.", + "No resize/Ctrl-O/write/exit/CPR or full original method is claimed.", + "Qt/Frida observation perturbs scheduling; not physical input or performance.", + "The Vim input log may remain unflushed because this pilot deliberately ends by owned cleanup." + ], + "full_vim_contract_verified": false, + "presented_done": null, + "coverage": { + "direct_cmd_start": true, + "ready": true, + "insert_marker": true, + "resize": false, + "save": false, + "normal_shell_exit": false + }, + "runtime_files_sha256": { + "msys-2.0.dll": "3096d2ba9be20e90be1399edd391bac65d84bc387f5a419b502c9b326081930f", + "msys-iconv-2.dll": "9cbf5613b946be7bad403ac0d7277efbd514a35faa644c6a0d6e7f6cc0b40780", + "msys-intl-8.dll": "23d30f4bcbc0a258cf5ecfe29e707f9b05d51c32d5b16a910f1012b889136c12", + "msys-ncursesw6.dll": "73ff8bc8b1f515e57b185e57d25d9ad29067057ff0819a8e588677c65ca61629", + "vim.exe": "2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a" + } + }, + { + "id": "vim", + "declared_maximum_runs": 1, + "actual_runs": 1, + "source_commit_recorded_in_plan": "b60cd65b9fa6af724279780faa8d78847eb5cef0", + "rea_duration_seconds": 30, + "scenario_timeout_ms": 25000, + "status": "failed", + "stage": 56, + "scenario_elapsed_ms_diagnostic_only": 25088, + "scenario_error": "Error: scenario timeout", + "script_sha256": "9ec842a01202da3e4a25207a3aa368074b2662443052612d120fd6a8add3f4df", + "template_sha256": "144caee2c053240eca713a5d4b42f12bb6324400f3c5c6eafe5ead618bad8a4c", + "fixture_sha256": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797", + "probe_files_sha256": { + "launch.py": "c24a82e3f0e546ae176b2ba3f463c5a1f576bf6e226110567208703b4738203c", + "launcher.patch": "1e440626b2bc20a9b2a6a5ea5263c4437ab06fe5285ce931cfb8b153bc475f9a", + "candidate-vim.vim": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797", + "driver.js": "95177a635e6c5bfca11eb17c394957d8aa5e0dcb6dcec96d77809b2331bc490f", + "qt_r1_text.js": "144caee2c053240eca713a5d4b42f12bb6324400f3c5c6eafe5ead618bad8a4c" + }, + "source_files_sha256": { + "research/ui/r1_launch.py": "54fb41872c6abcc48242a0938044184854b016785cc13659c66b1bf404da0d8a", + "research/ui/launch.py": "6970e0d4f2fe5e7ec4827df7aa1026cd0e74aeb828cdc9b7a0b42b3c887eb4e6", + "research/ui/qt_r1_text.js": "f5820d413fdd72c13dffcefbf4d0ade615abfe15754c6b0b52e1e098814eea6f", + "experiments/shell/shell.cpp": "b4f6fe98812d1d23fa5f5bbcc7376437404af6cdb67cd6cdcdf448b57cb31fe9", + "experiments/shell/test_candidate_tui.py": "a300380b97ad424c5af06219a021a256de6178c3af7fb7e933e24bb8142201ee", + "experiments/shell/query_client.cpp": "10d982efc87fa6a832fb674a43b515e2d993dcf8b40bcfa845aae5a2e7ae0545", + "experiments/shell/size_client.cpp": "cb11661bec9551053eec0dbb474bce2ac899a06ee2e50830a370af4816df39f4" + }, + "helper_exe_sha256": { + "shell_query_client.exe": "691b440e9c500c76c1c09c448aafbe2316793bf341ffe69bbc2bf70e41be64cc", + "shell_size_client.exe": "a7a45db0976dd1e157871b6a9b50b1362ceb8cb8ea604470e58791920805d408" + }, + "reference_identity": { + "exe_sha256": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "files_manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203" + }, + "provider": { + "evidence_id": "ev_1958f096bb4e6fe83d95cadf88cd600433cd9f39ec5172e577cbde3a1e8bc21b", + "name": "Frida", + "version": null, + "confidence": "observed", + "subject": null, + "environment": null, + "captured_state": "running", + "messages_truncated": false, + "cleanup_error": null, + "limitations": [ + "Artifact identity is unavailable for this observation.", + "The target operating system and architecture were not reported by this observation." + ] + }, + "backend_calls": [ + { + "function": "winpty_open", + "success": true, + "size": null + }, + { + "function": "winpty_conin_name", + "success": true, + "size": null + }, + { + "function": "winpty_set_size", + "success": true, + "size": [ + 91, + 23 + ] + }, + { + "function": "winpty_set_size", + "success": true, + "size": [ + 52, + 12 + ] + }, + { + "function": "winpty_set_size", + "success": true, + "size": [ + 60, + 12 + ] + }, + { + "function": "winpty_set_size", + "success": true, + "size": [ + 91, + 23 + ] + }, + { + "function": "winpty_set_size", + "success": true, + "size": [ + 60, + 12 + ] + } + ], + "probe_error": { + "count": 1, + "kind": "shell-handle-argument-type" + }, + "profile_check": { + "only_synthetic_session": true, + "session_count": 1 + }, + "outer_cleanup": { + "job_accounting": { + "active": 0, + "total": 14, + "terminated": 0 + }, + "deliberate_termination_requested": true, + "termination_counter_is_not_natural_exit_proof": true + }, + "raw_files": { + "run.json": { + "bytes": 614, + "sha256": "4008e44a44e3d601afae3fb6d08e77f5e88715281ba4d01eb49bac54bc4ecc23" + }, + "probe.js": { + "bytes": 23964, + "sha256": "9ec842a01202da3e4a25207a3aa368074b2662443052612d120fd6a8add3f4df" + }, + "probe-result.json": { + "bytes": 378, + "sha256": "d33a9b0c3348921efeeee60187254405066026aa1af20ef60931fd67fdc1c4f1" + }, + "profile-check.json": { + "bytes": 52, + "sha256": "5e522a64e98197722ccd084639b1ce50258b238e03be2f016e81c8af023b5ee1" + }, + "cleanup.json": { + "bytes": 43, + "sha256": "b77999aca40acf28442897d9e5eea89029418b73111973255ec0d654bba760c8" + }, + "evidence.json": { + "bytes": 101735, + "sha256": "99e68d373210e3dc735d2b434e0db3f5dd88317531ba191c82ecd7cee847d978" + }, + "stderr.txt": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "files.json": { + "bytes": 637666, + "sha256": "a25c87cba935294d5bfd3bbc76c650d31a8473809564d798ad5a889e4aea00e8" + } + }, + "synthetic_artifacts": { + "candidate-input.bin": { + "bytes": 115, + "sha256": "4bd689a84f1eb7336afb3b607dec760d117caef3d83f3e8e1b6b1f83d70b4b68" + }, + "candidate-insert-log.txt": { + "bytes": 58, + "sha256": "3d07108bcc44f0831b31530265fac711c1d1e69194aa9309897e4440c26de65a" + }, + "candidate-resize-log.txt": { + "bytes": 5, + "sha256": "2d3db728e657a0e1ab99b23acb6b2d08c586c4eaba6e65a66ab65e7fb47693b7" + }, + "candidate-startup-log.txt": { + "bytes": 6, + "sha256": "3dfa3fb239f56c778a1e9b33eb31328349edd155a95806dac4378904e88527e5" + }, + "candidate-tui.txt": { + "bytes": 17, + "sha256": "d9d3b8d10b660a808ece2b2c46b3767713ba4e74631c56b376b492eadf511dec" + }, + "candidate-vim.vim": { + "bytes": 334, + "sha256": "386d475e5297169cf7fcc425dcc0f6b0174c237ff86be03564b2d505c5d79797" + } + }, + "limitations": [ + "Qt/Frida hooks perturb scheduling; reference geometry and event-loop timing differ from TermForge.", + "The reference uses its observed WinPTY backend; there is no ConPTY flags24 equivalence.", + "Input is Qt synthetic key delivery, not physical input.", + "Original runtime files are pinned but actual Vim loaded-module paths are not observed.", + "WinPTY input-pipe WriteFile calls are observed only after matching its named pipe; asynchronous completions may be incomplete.", + "Whole WindTerm process is deliberately reclaimed after observation; only the shell exit handle may prove shell natural exit.", + "No full57 regression, repeatability or presentation/performance acceptance." + ], + "full_vim_contract_verified": false, + "presented_done": null, + "coverage": { + "typed_original_launch": true, + "ready_and_new": true, + "insert_marker": true, + "vim_resize_60x12": true, + "exact_input_115_bytes": true, + "exact_event_file": true, + "exact_saved_file": true, + "restored_main_prompt": true, + "original_native_size_gate_passed": false, + "cpr_ran": false, + "normal_shell_exit_observed": false + }, + "prelaunch_geometry_calibration": [ + { + "pixels": [ + 1360, + 860 + ], + "size": [ + 91, + 23 + ] + }, + { + "pixels": [ + 895, + 563 + ], + "size": [ + 52, + 12 + ] + }, + { + "pixels": [ + 990, + 563 + ], + "size": [ + 60, + 12 + ] + } + ], + "target_pixels": [ + 990, + 563 + ], + "input_capture": { + "named_pipe_matched": true, + "write_records": 0, + "zero_records_is_not_zero_input": true, + "actual_vim_input_log_exact": true, + "expected_input_sha256": "4bd689a84f1eb7336afb3b607dec760d117caef3d83f3e8e1b6b1f83d70b4b68" + }, + "runtime_files_sha256": { + "msys-2.0.dll": "3096d2ba9be20e90be1399edd391bac65d84bc387f5a419b502c9b326081930f", + "msys-iconv-2.dll": "9cbf5613b946be7bad403ac0d7277efbd514a35faa644c6a0d6e7f6cc0b40780", + "msys-intl-8.dll": "23d30f4bcbc0a258cf5ecfe29e707f9b05d51c32d5b16a910f1012b889136c12", + "msys-ncursesw6.dll": "73ff8bc8b1f515e57b185e57d25d9ad29067057ff0819a8e588677c65ca61629", + "vim.exe": "2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a" + } + }, + { + "id": "native", + "declared_maximum_runs": 1, + "actual_runs": 1, + "source_commit_recorded_in_plan": "b60cd65b9fa6af724279780faa8d78847eb5cef0", + "rea_duration_seconds": 30, + "scenario_timeout_ms": 25000, + "status": "failed", + "stage": 52, + "scenario_elapsed_ms_diagnostic_only": 25049, + "scenario_error": "Error: scenario timeout", + "script_sha256": "6fdf5b858f28053425b91db6120650b6fecfb04501865c9bd347c585451d36a0", + "template_sha256": "50372e7df493445fec62ed9a93b37725f2bc5e1805173c8126e9b15d4d2513eb", + "fixture_sha256": "e3712da5d12d088868de039835041d634dbefe6714077292ad9d97b484913939", + "probe_files_sha256": { + "native_size.py": "e3712da5d12d088868de039835041d634dbefe6714077292ad9d97b484913939", + "launch.py": "1fe09af15a7f21eae90e2750e9ddd8fb6660ac6a50dc3029f3f96b4336b6f390", + "launcher.patch": "b8248a3db99e6bf9e0c57f2e2bfaf955f0d79eb13c66113b75c2df6341b5ee47", + "driver.js": "4ba8d4a737e1b07785028189bf1e7c375e08e049bef9e358d920a3e7277a609c", + "qt_r1_text.js": "50372e7df493445fec62ed9a93b37725f2bc5e1805173c8126e9b15d4d2513eb" + }, + "source_files_sha256": { + "research/ui/r1_launch.py": "54fb41872c6abcc48242a0938044184854b016785cc13659c66b1bf404da0d8a", + "research/ui/launch.py": "6970e0d4f2fe5e7ec4827df7aa1026cd0e74aeb828cdc9b7a0b42b3c887eb4e6", + "research/ui/qt_r1_text.js": "f5820d413fdd72c13dffcefbf4d0ade615abfe15754c6b0b52e1e098814eea6f", + "experiments/shell/size_client.cpp": "cb11661bec9551053eec0dbb474bce2ac899a06ee2e50830a370af4816df39f4", + "experiments/shell/query_client.cpp": "10d982efc87fa6a832fb674a43b515e2d993dcf8b40bcfa845aae5a2e7ae0545" + }, + "helper_exe_sha256": { + "shell_size_client.exe": "a7a45db0976dd1e157871b6a9b50b1362ceb8cb8ea604470e58791920805d408", + "shell_query_client.exe": "691b440e9c500c76c1c09c448aafbe2316793bf341ffe69bbc2bf70e41be64cc" + }, + "reference_identity": { + "exe_sha256": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "files_manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203" + }, + "provider": { + "evidence_id": "ev_a30c2cddfec137471e5583ee699123e5472cfca1e56d70c4191e92355c7a175a", + "name": "Frida", + "version": null, + "confidence": "observed", + "subject": null, + "environment": null, + "captured_state": "running", + "messages_truncated": false, + "cleanup_error": null, + "limitations": [ + "Artifact identity is unavailable for this observation.", + "The target operating system and architecture were not reported by this observation." + ] + }, + "backend_calls": [ + { + "function": "winpty_open", + "success": true, + "size": null + }, + { + "function": "winpty_spawn", + "success": true, + "size": null + }, + { + "function": "winpty_set_size", + "success": true, + "size": [ + 91, + 23 + ] + }, + { + "function": "winpty_set_size", + "success": true, + "size": [ + 60, + 12 + ] + } + ], + "probe_error": { + "count": 0, + "kind": null + }, + "profile_check": { + "only_synthetic_session": true, + "session_count": 1 + }, + "outer_cleanup": { + "job_accounting": { + "active": 0, + "total": 15, + "terminated": 0 + }, + "deliberate_termination_requested": true, + "termination_counter_is_not_natural_exit_proof": true + }, + "raw_files": { + "run.json": { + "bytes": 615, + "sha256": "11ffb4a721570976ddfee849b6bfa144035cc0b07bbdae772d33c922ddeac4c3" + }, + "probe.js": { + "bytes": 20813, + "sha256": "6fdf5b858f28053425b91db6120650b6fecfb04501865c9bd347c585451d36a0" + }, + "probe-result.json": { + "bytes": 413, + "sha256": "ad10920cb07300b7ee2d1eaac0722a44718f3c1573803a063b09596e3f627c14" + }, + "profile-check.json": { + "bytes": 52, + "sha256": "5e522a64e98197722ccd084639b1ce50258b238e03be2f016e81c8af023b5ee1" + }, + "cleanup.json": { + "bytes": 43, + "sha256": "61cfd944477da214d7cdeb0ea35df03f50b15c61527b13979bbc78ee05c1b3a6" + }, + "evidence.json": { + "bytes": 36505, + "sha256": "55001f84f47c90b633300697747a7b5ac16dc51a8ea9e943d538f9bd9ca44f33" + }, + "stderr.txt": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "files.json": { + "bytes": 637666, + "sha256": "a25c87cba935294d5bfd3bbc76c650d31a8473809564d798ad5a889e4aea00e8" + } + }, + "synthetic_artifacts": { + "candidate-replies.bin": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "native-size.json": { + "bytes": 127, + "sha256": "665c0ee3dd9ecb263e8773262c403cf64bacfd909ace63c8c70216519a7e06db" + }, + "native_size.py": { + "bytes": 1386, + "sha256": "e3712da5d12d088868de039835041d634dbefe6714077292ad9d97b484913939" + } + }, + "limitations": [ + "No Vim in this independent sample; never combine it with the prior sample as one full scenario.", + "Qt/Frida scheduling and synthetic input; not physical input or performance.", + "Original dwSize60x12 assertion remains unchanged even if viewport is60x12.", + "The earlier shell-handle hook passed a Boolean where Frida required an integer; this sample corrects only that probe argument.", + "No input-pipe WriteFile tracking; prior unmatched writes are not a zero-input claim.", + "CPR origin may be the native console rather than WindTerm itself; this sample does not trace the producer.", + "Outer WindTerm cleanup deliberately terminates its owned process tree; only recorded shell exit can be natural." + ], + "full_vim_contract_verified": false, + "presented_done": null, + "native": { + "buffer": [ + 60, + 3000 + ], + "window": [ + 0, + 0, + 59, + 11 + ], + "viewport": [ + 60, + 12 + ], + "cursor": [ + 0, + 9 + ], + "attributes": 7, + "maximum": [ + 60, + 172 + ] + }, + "size_helper": "TF_SIZE_60x3000", + "original_buffer_60x12_matches": false, + "cpr": { + "response_bytes": 0, + "observed": false, + "producer": null, + "query_exit_code": null, + "shell_exit_command_sent": false + } + }, + { + "id": "mode", + "declared_maximum_runs": 1, + "actual_runs": 1, + "source_commit_recorded_in_plan": "b60cd65b9fa6af724279780faa8d78847eb5cef0", + "rea_duration_seconds": 30, + "scenario_timeout_ms": 25000, + "status": "observed-mode-variant", + "stage": 6, + "scenario_elapsed_ms_diagnostic_only": 2072, + "scenario_error": null, + "script_sha256": "d4d7eb7e8cddc0f247ab5fdd6699c172d10828d38bf0de7c0e349335c33c008a", + "template_sha256": "3eaf2aaa122bc95424e84316e8b2c0f010122a750d6ddcdd0d1fb2a1d9167d50", + "fixture_sha256": "4c2022640afe72e8811c80beadb580fa7237e0710373666b7399735078931e80", + "probe_files_sha256": { + "launch.py": "f173292c0407d2032173dd40ef8e250cd1fee268ab8adc9f3c89ca17b342d92d", + "launcher.patch": "f94ebb59f80b43d75b60a11457da110685be325c394fc20e92d189d0932672a3", + "cpr_mode.py": "4c2022640afe72e8811c80beadb580fa7237e0710373666b7399735078931e80", + "driver.js": "34bb3f4ded92d2bc254cf232e92a17c0f2c89c539037939759706915ddcca240", + "stages.js": "599d0fa733e680551752a359f0b1721d9b0251b9cca9e3171f6d7e99555a0bb1", + "qt_r1_text.js": "3eaf2aaa122bc95424e84316e8b2c0f010122a750d6ddcdd0d1fb2a1d9167d50" + }, + "source_files_sha256": {}, + "helper_exe_sha256": { + "shell_query_client.exe": "691b440e9c500c76c1c09c448aafbe2316793bf341ffe69bbc2bf70e41be64cc", + "shell_size_client.exe": "a7a45db0976dd1e157871b6a9b50b1362ceb8cb8ea604470e58791920805d408" + }, + "reference_identity": { + "exe_sha256": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "files_manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203" + }, + "provider": { + "evidence_id": "ev_bd0edb379354ad528be42fae8d14ff68867cd44937736efc85d25ce6d9814369", + "name": "Frida", + "version": null, + "confidence": "observed", + "subject": null, + "environment": null, + "captured_state": "running", + "messages_truncated": false, + "cleanup_error": null, + "limitations": [ + "Artifact identity is unavailable for this observation.", + "The target operating system and architecture were not reported by this observation." + ] + }, + "backend_calls": [ + { + "function": "winpty_open", + "success": true, + "size": null + }, + { + "function": "winpty_spawn", + "success": true, + "size": null + }, + { + "function": "winpty_set_size", + "success": true, + "size": [ + 91, + 23 + ] + }, + { + "function": "winpty_set_size", + "success": true, + "size": [ + 60, + 12 + ] + } + ], + "probe_error": { + "count": 0, + "kind": null + }, + "profile_check": { + "only_synthetic_session": true, + "session_count": 1 + }, + "outer_cleanup": { + "job_accounting": { + "active": 0, + "total": 10, + "terminated": 0 + }, + "deliberate_termination_requested": true, + "termination_counter_is_not_natural_exit_proof": true + }, + "raw_files": { + "run.json": { + "bytes": 615, + "sha256": "237437dbba6364c627abe4070ee6ace480899e30ffa1e4bd4af70ad7e2846703" + }, + "probe.js": { + "bytes": 20385, + "sha256": "d4d7eb7e8cddc0f247ab5fdd6699c172d10828d38bf0de7c0e349335c33c008a" + }, + "probe-result.json": { + "bytes": 599, + "sha256": "0403786252a7645516ce01828290c2da87fe24c783aaa364e7fd5442eda5c55c" + }, + "profile-check.json": { + "bytes": 52, + "sha256": "5e522a64e98197722ccd084639b1ce50258b238e03be2f016e81c8af023b5ee1" + }, + "cleanup.json": { + "bytes": 43, + "sha256": "4eb92b3fbbccaaafadac2890d56ca14e7f057cc9be16207dd90f81ec823c5ef6" + }, + "evidence.json": { + "bytes": 51939, + "sha256": "01686e0703da3a1d0fb4183a702e76b03bd15f352e44ede465c3d4c81489d4af" + }, + "stderr.txt": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "files.json": { + "bytes": 637666, + "sha256": "a25c87cba935294d5bfd3bbc76c650d31a8473809564d798ad5a889e4aea00e8" + } + }, + "synthetic_artifacts": { + "candidate-replies.bin": { + "bytes": 6, + "sha256": "492cbb9f988cb4c7cd28d78fa972aff059026fcca9efb775699dda04999ceea4" + }, + "cpr-mode.json": { + "bytes": 383, + "sha256": "0d5c4d3c47895a73217abac0330a895fb479bfb29dc4130880c6e204c460e6ca" + }, + "cpr_mode.py": { + "bytes": 1975, + "sha256": "4c2022640afe72e8811c80beadb580fa7237e0710373666b7399735078931e80" + } + }, + "limitations": [ + "The added mode-setting wrapper is a different fixture configuration, not a pass of the original unmodified entry.", + "No input flush, no external application/runtime changes, no product/test source change.", + "This is one new sample, not a controlled same-process replay of the earlier failure.", + "CPR producer is not traced; native console may answer rather than WindTerm.", + "Parent supervision bounds a possible blocking ReadConsoleW beyond the original client WaitForSingleObject; it is not a product deadline change.", + "No Vim or native-size acceptance in this sample; do not combine runs as one complete workflow.", + "Qt/Frida synthetic input and deliberate outer cleanup; no performance or presentation claim." + ], + "full_vim_contract_verified": false, + "presented_done": null, + "mode_observation": { + "output_mode_before": 3, + "input_mode_before": 503, + "requested_output_mode": 7, + "query_client_unchanged": true, + "query_supervision_seconds": 7, + "timed_out": false, + "exit_code": 0, + "output_mode_active": 7, + "input_mode_after_query": 512, + "output_restore_ok": true, + "input_restore_ok": true, + "output_mode_restored": 3, + "input_mode_restored": 503 + }, + "cpr": { + "response_bytes": 6, + "response_hex": "1b5b373b3152", + "producer": null, + "query_exit_code": 0 + }, + "natural_shell_exit_code": 37, + "original_unmodified_entry_passed": false + } + ], + "all_owned_jobs_empty_after_cleanup": true, + "same_run_complete_vim_size_cpr_exit": false, + "termforge_tests_changed": false, + "runtime_patch_adopted": false, + "product_backend_selected": false, + "original_fixed_runtime_official_ctest_latest": { + "passed": 56, + "total": 57, + "seconds": 402.66, + "internal_skips": 8, + "rerun_in_this_batch": false + }, + "performance_acceptance": false, + "presented_done": null +} diff --git a/third_party/conpty/LICENSE b/third_party/conpty/LICENSE new file mode 100644 index 00000000..8cb179cd --- /dev/null +++ b/third_party/conpty/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/conpty/README.md b/third_party/conpty/README.md new file mode 100644 index 00000000..e5bdfc92 --- /dev/null +++ b/third_party/conpty/README.md @@ -0,0 +1,18 @@ +# 固定 ConPTY 实验包 + +本目录只保存来源、身份和 MIT 许可;不提交 DLL/EXE,不下载或安装系统组件。[manifest.json](manifest.json) 固定 Microsoft 官方 `Microsoft.Windows.Console.ConPTY 1.25.260930003`、对应 release/source commit、完整包及同包 Windows x64 两个文件的 SHA-256。`LICENSE` 来自该固定源码提交。 + +从 manifest 的固定官方 URL 下载包,先核对包大小及 SHA-256,再仅将列出的两个 `package_members` 提取到一个实验目录,保持名称 `conpty.dll` / `OpenConsole.exe`。不要从其他包混合复制文件。Microsoft 包声明的最低系统版本不等于本项目的验证范围;当前实验结果仅来自本机 Windows 11 build 26200。 + +```powershell +cmake -S . -B build/candidate-conpty -A x64 ` + -DTERMFORGE_BUILD_CANDIDATE_ADAPTER=ON ` + -DTERMFORGE_CONPTY_BACKEND_DIR= +cmake --build build/candidate-conpty --config Release +``` + +构建阶段核对固定双哈希,并把匹配的 DLL/EXE、manifest 和许可复制到可执行文件旁的 `conpty-backend/`。运行阶段从自身可执行文件位置取得绝对目录,再次核对两文件;使用限制 DLL 搜索目录的 `LoadLibraryExW`,三项 ConPTY API 和模块生命期由同一个 `Backend` 拥有。缺失、身份不符或导出不存在即失败,不回退到系统 API。 + +这是显式实验构建。默认未设置目录时继续使用系统 API,不查找机器上的 Windows Terminal 安装,也不锁定产品后端或更改 Alpha 包路线。实验 `configured()` 使用固定官方头文件的 `PSEUDOCONSOLE_GLYPH_WIDTH_CONSOLE=0x18`,维持现有 Win32 readback 合同;系统始终 flags 0,通用 `reviewed_side_by_side()` / 独立 margin A/B 也默认 flags 0。这是宽度策略,**不是已删除的 passthrough mode**。末端核心的复杂字素合同仍由 #39 跟踪。 + +当前明确缺口:实际多次 resize 的文字/位置仍有差异;最终 Console-width 策略下 Vim 的 GUI resize 和 native new-buffer 场景失败;原粘贴并发夹具在新宿主已接收全部 3MB 后才看到 receiver checkpoint,不能当成“未完成传输时取消”通过;源控制台的每屏 margin 仍未恢复。详细结果见 [实验整合记录](../../docs/research/experimental-conpty-backend.md)。#47、#41 和 R2 保持开放。 diff --git a/third_party/conpty/manifest.json b/third_party/conpty/manifest.json new file mode 100644 index 00000000..a3f7fb23 --- /dev/null +++ b/third_party/conpty/manifest.json @@ -0,0 +1,29 @@ +{ + "schema": "termforge-conpty-package-v1", + "name": "Microsoft.Windows.Console.ConPTY", + "version": "1.25.260930003", + "architecture": "win-x64", + "license": "MIT", + "source_commit": "dc8ae365847d41f62d1ebca93815bb00959bc12f", + "source_tag": "v1.25.2733.0", + "license_source_url": "https://raw.githubusercontent.com/microsoft/terminal/dc8ae365847d41f62d1ebca93815bb00959bc12f/LICENSE", + "license_source_sha256": "5d177f23ecfeb0ea8e050b6a5a16355e1ae9a0b286436ca8f83ed08b3795be6b", + "bundled_license_sha256": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada", + "license_normalization": "CRLF to LF only; complete text verified equal", + "package_url": "https://github.com/microsoft/terminal/releases/download/v1.25.2733.0/Microsoft.Windows.Console.ConPTY.1.25.260930003.nupkg", + "package_bytes": 1750533, + "package_sha256": "6d6f8b008c655d814c498d8205d4012dcd4d0b6dfc1096461d0a185d1e3caf03", + "files": { + "conpty.dll": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a", + "OpenConsole.exe": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "package_members": { + "conpty.dll": "runtimes/win-x64/native/conpty.dll", + "OpenConsole.exe": "build/native/runtimes/x64/OpenConsole.exe" + }, + "experimental": true, + "configured_creation_flags": 24, + "configured_glyph_width": "PSEUDOCONSOLE_GLYPH_WIDTH_CONSOLE", + "default_backend": false, + "supported_client_platforms_verified": false +} diff --git a/tools/prepare_hosted_shells.py b/tools/prepare_hosted_shells.py index 65a29e16..c2f497d2 100644 --- a/tools/prepare_hosted_shells.py +++ b/tools/prepare_hosted_shells.py @@ -29,6 +29,11 @@ ROOTFS_SHA256 = "8251e27ffff381a4af5f41dcb94d867de3e0d9774a9241908ab34555d99315ea" MAX_DOWNLOAD = 400 * 1024 * 1024 STEPS = {"preflight": 45, "download": 120, "import": 90, "check-before": 25, "check-after": 25} +COMMANDS_BY_STEP = {"preflight": ("cmd", "ps51", "ps7", "list"), "download": (), + "import": ("list-before-import", "import"), + "check-before": ("list", "startup"), "check-after": ("list", "startup")} +COMMAND_DEADLINES = {"cmd": 10, "ps51": 10, "ps7": 10, "list": 10, + "list-before-import": 10, "import": 80, "startup": 20} class PreparationError(Exception): @@ -165,16 +170,95 @@ def copy_verified_rootfs(response, destination, *, expected_hash=ROOTFS_SHA256, raise PreparationError("download_hash") +def validate_command_events(value, step): + """Whitelist an ordered prefix; an invoking tail is not a completed command.""" + if (step not in COMMANDS_BY_STEP or not isinstance(value, dict) or set(value) != {"events"} or + not isinstance(value["events"], list) or len(value["events"]) > 8): + raise ValueError("command event schema") + expected = COMMANDS_BY_STEP[step] + index, waiting, timed_out = 0, False, False + for event in value["events"]: + if (not isinstance(event, dict) or + set(event) != {"command", "phase", "deadline_ms", "elapsed_ms", "returncode"} or + index >= len(expected) or timed_out or event["command"] != expected[index]): + raise ValueError("command event identity") + phase = event["phase"] + if (phase not in (("returned", "timed_out") if waiting else ("invoking",)) or + type(event["deadline_ms"]) is not int or + event["deadline_ms"] != 1000 * COMMAND_DEADLINES[expected[index]] or + type(event["elapsed_ms"]) is not int or not 0 <= event["elapsed_ms"] <= STEPS[step] * 1000 + 10000): + raise ValueError("command event boundary") + if phase == "returned": + if type(event["returncode"]) is not int or not -(2**31) <= event["returncode"] < 2**32: + raise ValueError("command exit code") + waiting = False + index += 1 + else: + if event["returncode"] is not None or (phase == "invoking" and event["elapsed_ms"] != 0): + raise ValueError("unobserved command exit") + waiting = phase == "invoking" + timed_out = phase == "timed_out" + return value["events"] + + +def read_command_events(directory, step): + with (directory / "command-events.json").open("rb") as stream: + raw = stream.read(8193) + if len(raw) > 8192: + raise ValueError("command event size") + return validate_command_events(json.loads(raw), step) + + +def write_command_event(directory, name, phase, elapsed_ms, returncode=None): + try: + path = directory / "command-events.json" + events = read_command_events(directory, directory.name) if path.exists() else [] + event = {"command": name, "phase": phase, "deadline_ms": 1000 * COMMAND_DEADLINES[name], + "elapsed_ms": elapsed_ms, "returncode": returncode} + value = {"events": events + [event]} + validate_command_events(value, directory.name) + write_json(path, value) + except (OSError, ValueError, KeyError, RecursionError): + raise PreparationError("command_observation") from None + + +def public_command_observation(directory, step): + # Read only after the worker is reaped. Partial traces survive its deadline; + # raw arguments/output and malformed data never enter the public report. + if step not in COMMANDS_BY_STEP: + return {"status": "invalid", "events": []} + if not COMMANDS_BY_STEP[step]: + return {"status": "not_applicable", "events": []} + try: + return {"status": "observed", "events": read_command_events(directory, step)} + except FileNotFoundError: + return {"status": "not_observed", "events": []} + except (OSError, ValueError, RecursionError): + return {"status": "invalid", "events": []} + + def run_command(command, directory, name, *, cwd, timeout=10, data=None): # This function only runs inside a GO-gated worker already in the parent's # exact Job. No shell string construction or global-process cleanup. + if name not in COMMANDS_BY_STEP.get(directory.name, ()) or timeout != COMMAND_DEADLINES[name]: + raise PreparationError("command_observation") + def observe(phase, elapsed_ms, returncode=None): + try: + write_command_event(directory, name, phase, elapsed_ms, returncode) + except PreparationError: + # A missing/partial trace cannot replace the actual command result. + pass stdout_path, stderr_path = directory / (name + ".stdout"), directory / (name + ".stderr") with stdout_path.open("xb") as stdout, stderr_path.open("xb") as stderr: + observe("invoking", 0) + started = time.monotonic_ns() try: result = subprocess.run(command, input=data, stdout=stdout, stderr=stderr, cwd=cwd, timeout=timeout, creationflags=subprocess.CREATE_NO_WINDOW) except subprocess.TimeoutExpired: + observe("timed_out", (time.monotonic_ns() - started) // 1000000) raise PreparationError("command_deadline") from None + observe("returned", (time.monotonic_ns() - started) // 1000000, result.returncode) if stdout_path.stat().st_size > 65536: raise PreparationError("command_report_size") return result.returncode, stdout_path.read_bytes() @@ -268,7 +352,8 @@ def worker(step, out): FAILURES = {"hosted_guard", "workspace_guard", "windows_admin_guard", "worker_job_guard", "cwd_identity", "wsl_report_size", "wsl_list_encoding", "wsl_list_identity", "marker_encoding", "shell_startup", "download_deadline", "download_size", "download_hash", "command_deadline", "command_report_size", "worker_directory_guard", - "wsl_list", "download_response", "ubuntu_already_present", "wsl_import", "ubuntu_missing", "worker_step"} + "wsl_list", "download_response", "ubuntu_already_present", "wsl_import", "ubuntu_missing", "worker_step", + "command_observation"} def run_step(step, out): @@ -375,6 +460,8 @@ def terminate_job(): job.close() (private / "worker.stdout").write_bytes(stdout) (private / "worker.stderr").write_bytes(stderr) + record["command_observation"] = (public_command_observation(private, step) if cleanup["root_reaped"] else + {"status": "worker_not_reaped", "events": []}) return record diff --git a/tools/test_prepare_hosted_shells.py b/tools/test_prepare_hosted_shells.py index b7110612..9036d951 100644 --- a/tools/test_prepare_hosted_shells.py +++ b/tools/test_prepare_hosted_shells.py @@ -166,5 +166,155 @@ def test_local_runtime_is_rejected_before_any_subprocess_or_download(self): urlopen.assert_not_called() +class CommandBoundaryTests(unittest.TestCase): + @staticmethod + def event(command="cmd", phase="invoking", elapsed=0, code=None): + return {"command": command, "phase": phase, "deadline_ms": 10000, + "elapsed_ms": elapsed, "returncode": code} + + def test_timeout_keeps_actual_command_boundary_without_private_arguments(self): + secret = "synthetic-private-command-path" + with tempfile.TemporaryDirectory() as temporary: + directory = Path(temporary) / "preflight" + directory.mkdir() + failure = prep.subprocess.TimeoutExpired([secret], 10, output=b"synthetic-private-output") + with mock.patch.object(prep.subprocess, "run", side_effect=failure) as command, \ + mock.patch.object(prep.time, "monotonic_ns", side_effect=[0, 10000000000]), \ + mock.patch.object(prep.subprocess, "CREATE_NO_WINDOW", 0, create=True): + with self.assertRaisesRegex(prep.PreparationError, "^command_deadline$"): + prep.run_command([secret], directory, "cmd", cwd=directory) + command.assert_called_once() + self.assertEqual(command.call_args.kwargs["timeout"], 10) + data = json.loads((directory / "command-events.json").read_text(encoding="utf-8")) + self.assertEqual(data, {"events": [ + {"command": "cmd", "phase": "invoking", "deadline_ms": 10000, "elapsed_ms": 0, "returncode": None}, + {"command": "cmd", "phase": "timed_out", "deadline_ms": 10000, "elapsed_ms": 10000, "returncode": None}]}) + self.assertNotIn(secret, json.dumps(data)) + self.assertNotIn("synthetic-private-output", json.dumps(data)) + + def test_returned_command_and_partial_invocation_are_reported_independently(self): + with tempfile.TemporaryDirectory() as temporary: + directory = Path(temporary) / "preflight" + directory.mkdir() + def returned(command, **kwargs): + kwargs["stdout"].write(b"private-native-output") + return prep.subprocess.CompletedProcess(command, 7) + with mock.patch.object(prep.subprocess, "run", side_effect=returned), \ + mock.patch.object(prep.time, "monotonic_ns", side_effect=[0, 123000000]), \ + mock.patch.object(prep.subprocess, "CREATE_NO_WINDOW", 0, create=True): + self.assertEqual(prep.run_command(["private-command"], directory, "cmd", cwd=directory), + (7, b"private-native-output")) + self.assertEqual(prep.public_command_observation(directory, "preflight"), + {"status": "observed", "events": [self.event(), self.event(phase="returned", elapsed=123, code=7)]}) + prep.write_json(directory / "command-events.json", {"events": [self.event()]}) + self.assertEqual(prep.public_command_observation(directory, "preflight"), + {"status": "observed", "events": [self.event()]}) + + def test_unknown_or_unordered_data_is_rejected_without_echoing_it(self): + good = self.event() + values = [ + {"events": [{**good, "command": "private-path"}]}, + {"events": [{**good, "argv": ["private-token"]}]}, + {"events": [self.event(phase="returned", code=0)]}, + {"events": [good, self.event(phase="returned", code=True)]}, + {"events": [good, self.event(phase="timed_out", code=91)]}, + {"events": [good, self.event(phase="returned", code=0), self.event(command="ps7")]}, + {"events": [{**good, "deadline_ms": 11000}]}, + {"events": [good, self.event(phase="returned", elapsed=-1, code=0)]}, + {"events": [good] * 9}, + ] + with tempfile.TemporaryDirectory() as temporary: + directory = Path(temporary) / "preflight" + directory.mkdir() + for value in values: + with self.subTest(value=value): + prep.write_json(directory / "command-events.json", value) + self.assertEqual(prep.public_command_observation(directory, "preflight"), + {"status": "invalid", "events": []}) + (directory / "command-events.json").write_bytes(b"private-token" * 1000) + self.assertEqual(prep.public_command_observation(directory, "preflight"), + {"status": "invalid", "events": []}) + (directory / "command-events.json").write_bytes(b"[" * 2000 + b"]" * 2000) + self.assertEqual(prep.public_command_observation(directory, "preflight"), + {"status": "invalid", "events": []}) + + def test_trace_write_failure_cannot_replace_observed_timeout(self): + with tempfile.TemporaryDirectory() as temporary: + directory = Path(temporary) / "preflight" + directory.mkdir() + with mock.patch.object(prep.subprocess, "run", side_effect=prep.subprocess.TimeoutExpired(["synthetic"], 10)), \ + mock.patch.object(prep, "write_command_event", side_effect=[None, prep.PreparationError("command_observation")]), \ + mock.patch.object(prep.subprocess, "CREATE_NO_WINDOW", 0, create=True): + with self.assertRaisesRegex(prep.PreparationError, "^command_deadline$"): + prep.run_command(["synthetic"], directory, "cmd", cwd=directory) + + def test_trace_write_failure_preserves_command_and_marker_result(self): + marker = "TERMFORGE_HOSTED_CMD_READY" + output = (marker + "\r\n").encode("ascii") + write_json = prep.write_json + for phase in ("invoking", "returned"): + for code in (0, 7): + with self.subTest(phase=phase, code=code), tempfile.TemporaryDirectory() as temporary: + directory = Path(temporary) / "preflight" + directory.mkdir() + def failed_write(path, value): + if value["events"][-1]["phase"] == phase: + raise OSError("synthetic-private-storage-error") + write_json(path, value) + def returned(command, **kwargs): + kwargs["stdout"].write(output) + return prep.subprocess.CompletedProcess(command, code) + with mock.patch.object(prep, "write_json", side_effect=failed_write), \ + mock.patch.object(prep.subprocess, "run", side_effect=returned) as command, \ + mock.patch.object(prep.subprocess, "CREATE_NO_WINDOW", 0, create=True): + result_code, result_output = prep.run_command(["synthetic"], directory, "cmd", cwd=directory) + command.assert_called_once() + self.assertEqual((result_code, result_output), (code, output)) + if code: + with self.assertRaisesRegex(prep.PreparationError, "^shell_startup$"): + prep.validate_marker(result_output, result_code, marker) + else: + prep.validate_marker(result_output, result_code, marker) + observation = prep.public_command_observation(directory, "preflight") + self.assertEqual(observation, {"status": "not_observed", "events": []} if phase == "invoking" else + {"status": "observed", "events": [self.event()]}) + + def test_parent_retains_command_or_worker_deadline_after_cleanup(self): + for worker_timeout in (False, True): + with self.subTest(worker_timeout=worker_timeout), tempfile.TemporaryDirectory() as temporary: + out = Path(temporary) + events = [self.event(), self.event(phase="returned", elapsed=2, code=0), self.event(command="ps51")] + if not worker_timeout: + events.append(self.event(command="ps51", phase="timed_out", elapsed=10000)) + process = mock.Mock(pid=123, returncode=None) + process.poll.side_effect = lambda: process.returncode + calls = [] + def communicate(*args, **kwargs): + calls.append(kwargs["timeout"]) + prep.write_json(out / "_private/preflight/command-events.json", {"events": events}) + if worker_timeout and len(calls) == 1: + raise prep.subprocess.TimeoutExpired(["synthetic-worker"], 45) + process.returncode = 99 if worker_timeout else 1 + return (b"" if worker_timeout else b'{"failure_code":"command_deadline"}', b"private-worker-error") + process.communicate.side_effect = communicate + job = mock.Mock() + job.drain.return_value = {"active": 0, "total": 2, "terminated": 0} + audit = mock.Mock(stable=True, members=[], private=[]) + audit.process_ids.return_value = [] + with mock.patch.object(prep, "OwnedJob", return_value=job), \ + mock.patch.object(prep, "JobMembers", return_value=audit), \ + mock.patch.object(prep.subprocess, "Popen", return_value=process), \ + mock.patch.object(prep.subprocess, "CREATE_NO_WINDOW", 0, create=True): + result = prep.run_step("preflight", out) + self.assertEqual(result["status"], "failed") + self.assertEqual(result["failure_code"], "worker_deadline" if worker_timeout else "command_deadline") + self.assertEqual(result["command_observation"], {"status": "observed", "events": events}) + self.assertTrue(result["cleanup"]["root_reaped"]) + self.assertEqual(result["cleanup"]["root_natural_exit"], not worker_timeout) + self.assertEqual(result["cleanup"]["job_termination_requests"], int(worker_timeout)) + self.assertEqual(calls, [45, 5] if worker_timeout else [45]) + self.assertNotIn("private-worker-error", json.dumps(result)) + + if __name__ == "__main__": unittest.main()