From dcb7e6ed0ebc2cb358b68823c348c2fd9a78dac9 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:43:12 +0800 Subject: [PATCH 1/2] Wait for both session timers before selection byte observations --- CMakeLists.txt | 3 ++ experiments/shell/scenario_tick_gate.hpp | 20 +++++++++++++ experiments/shell/scenario_tick_gate_test.cpp | 29 +++++++++++++++++++ .../workspace_selection_bytes_scenario.hpp | 22 +++++++++----- 4 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 experiments/shell/scenario_tick_gate.hpp create mode 100644 experiments/shell/scenario_tick_gate_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a765f3bb..e5b3e38a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -263,6 +263,9 @@ 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(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) if(TERMFORGE_BUILD_CANDIDATE_ADAPTER) add_test(NAME candidate_core COMMAND candidate_core_test) endif() diff --git a/experiments/shell/scenario_tick_gate.hpp b/experiments/shell/scenario_tick_gate.hpp new file mode 100644 index 00000000..938059af --- /dev/null +++ b/experiments/shell/scenario_tick_gate.hpp @@ -0,0 +1,20 @@ +// A scenario phase waits for each independently scheduled session timer. +#pragma once +#include +#include + +namespace tf_shell_test { +class TwoSessionTickGate { + std::array baseline_{}; + bool armed_ = false; +public: + void begin(std::array ticks) { baseline_=ticks; armed_=true; } + bool ready(std::array ticks) const { + if(!armed_) return false; + for(std::size_t index=0;index(ticks[index])-baseline_[index]<3) return false; + return true; + } +}; +} diff --git a/experiments/shell/scenario_tick_gate_test.cpp b/experiments/shell/scenario_tick_gate_test.cpp new file mode 100644 index 00000000..01ea6f28 --- /dev/null +++ b/experiments/shell/scenario_tick_gate_test.cpp @@ -0,0 +1,29 @@ +#include "scenario_tick_gate.hpp" +#include +#include +#include + +static void check(bool value,const char* detail) { if(!value) throw std::runtime_error(detail); } + +int main() { + try { + tf_shell_test::TwoSessionTickGate gate; + check(!gate.ready({3,3}),"an unstarted phase has no timer evidence"); + // The real CI forward snapshot was bottom=14/main=11. At reverse, + // main had three ticks but bottom only two; recording was premature. + gate.begin({14,11}); + check(!gate.ready({16,14}),"the captured skew must not pass on main ticks alone"); + check(gate.ready({17,14}),"each session meeting its own three ticks is sufficient"); + check(!gate.ready({17,13}),"main must also reach its independent threshold"); + check(!gate.ready({13,14}),"a regressed session counter is not progress"); + gate.begin({100,7}); + check(!gate.ready({102,100}),"a faster main cannot cover a slow bottom"); + check(!gate.ready({103,9}),"a new phase replaces both prior baselines"); + check(gate.ready({103,10}),"different absolute counters use independent baselines"); + gate.begin({INT_MAX-3,INT_MAX-3}); + check(gate.ready({INT_MAX,INT_MAX}),"threshold arithmetic must not overflow"); + gate.begin({-1,0}); + check(!gate.ready({3,3}),"negative baseline is invalid evidence"); + std::cout<<"scenario two-session timer checks passed\n"; + } catch(const std::exception& error) { std::cerr< #include #include +#include "scenario_tick_gate.hpp" inline void Workspace::scenario_selection_bytes() { if (image_.empty() || closing_) return; @@ -16,7 +17,10 @@ inline void Workspace::scenario_selection_bytes() { static std::array,2> initial_grids{}; static std::array saved_keys{}; static bool restore_keys = false; - static int phase_tick = 0; + static tf_shell_test::TwoSessionTickGate phase_ticks; + const auto begin_phase = [&]() { + phase_ticks.begin({bottom->observed_tick_count(),main ? main->observed_tick_count() : 0}); + }; if (restore_keys) { require(SetKeyboardState(saved_keys.data()) != FALSE, "selection bytes keyboard state restoration failed"); restore_keys = false; @@ -96,7 +100,7 @@ inline void Workspace::scenario_selection_bytes() { }; const auto copy_if_system = [&]() { if (system) { sentinel(); queued_key('C',true,true); } - phase_tick = main->observed_tick_count(); + begin_phase(); }; const auto transition = [&](int dpi) { // A controlled message never changes the OS monitor or the display. @@ -106,7 +110,7 @@ inline void Workspace::scenario_selection_bytes() { "selection bytes DPI frame calculation failed"); SendMessageW(window_,WM_DPICHANGED,MAKEWPARAM(dpi,dpi),reinterpret_cast(&next)); require(dpi_ == dpi && width_ == 1360 && height_ == 860, "selection bytes controlled DPI changed logical client size"); - phase_tick = main ? main->observed_tick_count() : 0; + begin_phase(); }; const auto record = [&](const char* phase, bool copy, bool empty_copy = false) { std::ostringstream out; @@ -157,7 +161,9 @@ inline void Workspace::scenario_selection_bytes() { shell->begin_output_observation(); shell->command(L"\"" + client.wstring() + L"\" \"" + prefix.wstring() + L".bin\" \"" + prefix.wstring() + L"\" styled-tail-v1\r"); }; - const auto three_ticks = [&]() { return main && main->observed_tick_count() - phase_tick >= 3; }; + const auto three_ticks = [&]() { + return main && phase_ticks.ready({bottom->observed_tick_count(),main->observed_tick_count()}); + }; const auto settled = [&]() { return main && main->settled() && bottom->settled(); }; if (stage_ == 0 && bottom->ready()) { samples.clear(); @@ -170,7 +176,7 @@ inline void Workspace::scenario_selection_bytes() { launch(bottom,L"bottom"); launch(main,L"main"); stage_ = 3; } else if (stage_ == 3 && main && main->live_contains_line("TF_SELECT_BYTES_READY") && bottom->live_contains_line("TF_SELECT_BYTES_READY") && settled()) { - phase_tick = main->observed_tick_count(); stage_ = 4; + begin_phase(); stage_ = 4; } else if (stage_ == 4 && three_ticks() && settled()) { select(0,Group::main); const int soft = row_of(main,"SOFT_",true), continuation = row_of(main,"CONT 中文 e\xcc\x81"), hard = row_of(main,"HARD 中文 e\xcc\x81"); @@ -198,14 +204,14 @@ inline void Workspace::scenario_selection_bytes() { copy_if_system(); stage_ = 13; } else if (stage_ == 13 && three_ticks()) { record("dpi-96-restored",system); queued_key(VK_ESCAPE); - phase_tick = main->observed_tick_count(); stage_ = 14; + begin_phase(); stage_ = 14; } else if (stage_ == 14 && three_ticks()) { record("cleared",false); if (system) sentinel(); - queued_key('C',true,true); phase_tick = main->observed_tick_count(); stage_ = 15; + queued_key('C',true,true); begin_phase(); stage_ = 15; } else if (stage_ == 15 && three_ticks()) { record("empty-copy",false,true); queued_key('X'); - phase_tick = main->observed_tick_count(); stage_ = 16; + begin_phase(); stage_ = 16; } else if (stage_ == 16 && three_ticks() && !capture(L"main").empty()) { record("input-after",false); Bitmap frame(physical_width_,physical_height_); paint(frame.dc); From c63413430e9f9c8a18a728bcaf6b90cdbdb65abd Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:46:14 +0800 Subject: [PATCH 2/2] Record R66 timer skew and preserve hosted clipboard acceptance boundary --- docs/EVIDENCE.md | 1 + docs/FEATURES.md | 2 +- docs/research/selection-session-tick-gate.md | 20 + .../validation.json | 705 ++++++++++++++++++ 4 files changed, 727 insertions(+), 1 deletion(-) create mode 100644 docs/research/selection-session-tick-gate.md create mode 100644 experiments/shell/results/2026-10-26-selection-tick-gate/validation.json diff --git a/docs/EVIDENCE.md b/docs/EVIDENCE.md index fda49292..a0ccc930 100644 --- a/docs/EVIDENCE.md +++ b/docs/EVIDENCE.md @@ -85,6 +85,7 @@ R1 固定总出口的源码为 `ce9b24d`,早期 `2dc8d6d` / `9b16acc` 仍分 | 增量查找与原生控件 | [搜索模块](../experiments/core/text_search.hpp)、[独立内容测试](../experiments/core/search_test.cpp)、[查找控件](../experiments/shell/find_bar.hpp)、[真实控件测试](../experiments/shell/find_bar_test.cpp) | 独立搜索模块与原生 Find 控件的 Unicode/预算/版本/焦点和回收;完整会话行为引用查找验收。 | | 选择/查找窗口整合(历史) | [最新边界与六轮窗口验证](../experiments/shell/results/2026-10-24-r1-window-boundaries/validation.json)、[前轮整合](../experiments/shell/results/2026-10-24-r1-selection-find/validation.json)、[自动回归](../experiments/shell/test_shell.py) | 历史窗口整合与边界记录;原先漏证据的负例保留。后续系统入口、DPI 与 IME 的结论引用各自独立验收。 | | 选择字节与受控 DPI | [源码身份、真实窗口与保留失败](../experiments/shell/results/2026-10-24-r1-selection-bytes/validation.json)、[系统 CF 两轮审计](../experiments/shell/results/2026-10-24-r1-styled-system-copy/validation.json) | 样式尾空格、软硬行和受控 DPI 的精确 CF 字节已核对;普通尾空格被 ConPTY 规范化的红样本保留,不恢复已丢信息,也不代表真实异 DPI 跨屏。 | +| 选择字节的双会话阶段等待(#66) | [独立阶段合同](research/selection-session-tick-gate.md)、[原 hosted 计数及本地复验](../experiments/shell/results/2026-10-26-selection-tick-gate/validation.json) | 原 reverse 只满足 main 的三 tick,bottom 实际仅两 tick;修正场景等待双方独立进度,保留 Python oracle、九阶段、CF 保护及原期限。本地数据两轮不代替真实 hosted CF。 | | R1 组合夹具基础 | [夹具协议](../experiments/shell/WORKFLOW.md)、[固定源码数据审计](../experiments/shell/results/2026-10-24-r1-workflow-foundation/validation.json)、[动作/Paste复验](../experiments/shell/results/2026-10-24-r1-workflow-foundation/regression.json)、[真实IME复验](../experiments/shell/results/2026-10-24-r1-workflow-foundation/ime-regression.json)、[主CI 40034110925](https://github.com/JavaZeroo/termforge/actions/runs/40034110925)、[verify 40034133987](https://github.com/JavaZeroo/termforge/actions/runs/40034133987) | 组合夹具的数据、快照与既有回归基础;不单独验收真实 Paste/IME、八会话或 R1 总出口。 | | R1 八会话数据工作流 | [协议/自动入口](../experiments/shell/WORKFLOW.md)、[固定源码审计](../experiments/shell/results/2026-10-24-r1-eight-sessions/validation.json)、[既有能力复验](../experiments/shell/results/2026-10-24-r1-eight-sessions/regression.json)、[主CI](https://github.com/JavaZeroo/termforge/actions/runs/40040980384)、[同源码真实IME复验](https://github.com/JavaZeroo/termforge/actions/runs/40040994121) | 固定源码 72fe231 的八会话数据工作流、容量/后台/阅读/查找/淘汰/回收已独立复核;这是 data-only 记录,同次真实 CF/IME 以 R1 总出口为准。 | | 历史 launcher-tree 资源观察 | [复用采样器的adapter](../experiments/shell/workflow_resources.py)、[同一固定源码审计](../experiments/shell/results/2026-10-24-r1-eight-sessions/validation.json) | 历史 launcher-tree 资源记录可离线重算;不是 UI-root 归因。观测成本/调度缺口显著,phase_windows=null,不追溯补造校准或低扰动结论。 | diff --git a/docs/FEATURES.md b/docs/FEATURES.md index e80c9546..b79c693c 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -24,7 +24,7 @@ |---|---|---|---| | 本地命令输入/输出 | **实现中,未验收**:Windows shell → ConPTY → 有界队列 → 终端核心 → HWND;普通字符、中文字符消息、基础编辑和回收有固定场景。[持续 cmd 输出/Ctrl-C 回归](../experiments/conpty/README.md#持续-cmd-输出与-ctrlc-独立回归)保留完整负载、单次 ETX、原始字节和清理 | [#36](https://github.com/JavaZeroo/termforge/issues/36) 的历史 normal/resize 超时原因未知;旧、新探针的后续成功不是因果修复。原生控制处理函数、完整键盘模式及四 shell 交叉仍需验收;中文字符消息不等于 IME | R1 / R2 | | 历史浏览 | **限定验收**:100 行有界物理行历史、只读投影、滚轮/翻页/原生滚动条与每会话阅读锚点;编号内容、隐藏输出、淘汰、缩放与回收有[固定回归](../experiments/shell/results/2026-10-23-r1-foundations/validation.json),组合出口见八会话工作流 | 候选历史/resize 合同另列;完整 reflow 与大历史规模的内存、搜索和滚动性能未验收 | R1;扩展 R2 / R4 | -| 选择与复制 | **限定验收**:屏幕/100行有界历史的拖选、词/行、全选、自动滚动及每会话高亮;菜单/快捷键和实际 CF_UNICODETEXT 精确往返。[最终系统审计](../experiments/shell/results/2026-10-24-r1-selection-accepted/validation.json)覆盖占用反馈/保持/重试、live覆盖失效、同组标签、原生rename EDIT复制与关闭后目标隔离;[带样式尾空格/软硬行/受控DPI](../experiments/shell/results/2026-10-24-r1-styled-system-copy/validation.json)另有两轮系统证据 | hosted reverse 场景的复合断言失败由 [#66](https://github.com/JavaZeroo/termforge/issues/66) 独立定位;普通尾空格经 ConPTY 规范化后不保留;显式空白端点策略未对照,词规则与容量为本项目策略。限定动作与参考差异见统一文字动作,完整组合见 #18 的独立验收;矩形选择归 R4,受控 DPI 不等于真实跨显示器 | R1;扩展 R2 / R4 | +| 选择与复制 | **限定验收**:屏幕/100行有界历史的拖选、词/行、全选、自动滚动及每会话高亮;菜单/快捷键和实际 CF_UNICODETEXT 精确往返。[最终系统审计](../experiments/shell/results/2026-10-24-r1-selection-accepted/validation.json)覆盖占用反馈/保持/重试、live覆盖失效、同组标签、原生rename EDIT复制与关闭后目标隔离;[带样式尾空格/软硬行/受控DPI](../experiments/shell/results/2026-10-24-r1-styled-system-copy/validation.json)另有两轮系统证据 | [#66](https://github.com/JavaZeroo/termforge/issues/66) 已定位为场景只等待单会话,改为[双方各三 tick 的阶段门槛](research/selection-session-tick-gate.md);本地数据两轮已复验,真实 hosted CF 验收仍独立进行;普通尾空格经 ConPTY 规范化后不保留;显式空白端点策略未对照,词规则与容量为本项目策略。限定动作与参考差异见统一文字动作,完整组合见 #18 的独立验收;矩形选择归 R4,受控 DPI 不等于真实跨显示器 | R1;扩展 R2 / R4 | | 粘贴 | **限定验收**:有界传输、异步读取、确认与目标绑定;正常系统粘贴、占用/超限错误与重试、读取和发送途中取消/关闭、隐藏目标、洪流及精确接收前缀均有重复验证。[最终系统证据](../experiments/shell/results/2026-10-24-r1-clipboard-accepted/validation.json)保留晚发布原始 1418 和完整回收 | 默认 Return 等限定参考动作已在统一文字动作验收,全流程交叉见 #18 的独立验收;bracketed paste/应用模式归 R2,未宣称全部系统或任意负载验收 | R1;应用模式 R2 | | 查找与定位 | **限定验收**:有界增量字面量搜索、原生查找框、方向/循环/高亮、中文、追加与淘汰;持续输出时可显式使用有效局部命中,运行中查询替换/取消/关闭均有[托管两轮独立证据](../experiments/shell/results/2026-10-24-r1-find-accepted/validation.json) | 局部结果不表示全局末端/循环;动作与受控 DPI 交叉见统一文字动作,组合与资源见 #18 的独立验收。正则、跨会话、完整 Unicode caseless 和大历史规模归 R4 | R1 / R4 | | 统一文字动作 | **限定验收**:共用目标/可用状态,实际终端右键/快捷键、原生 Find/rename 编辑、busy/closing/失效选区反馈及受控 DPI/zoom 保持;[最终系统审计](../experiments/shell/results/2026-10-24-r1-actions-accepted/validation.json)与[逐项对照](research/r1-action-comparison.md)覆盖 Find 实际 Ctrl+C/V、中文重选、默认 Return 和会话隔离 | 复杂 busy/closing 参考行为仍未知,采用明确项目策略;完整流程与八会话有限资源已由 #18 另验收,真实跨显示器及完整 WindTerm 等价未验收 | R1 | diff --git a/docs/research/selection-session-tick-gate.md b/docs/research/selection-session-tick-gate.md new file mode 100644 index 00000000..c34f5bb9 --- /dev/null +++ b/docs/research/selection-session-tick-gate.md @@ -0,0 +1,20 @@ +# 选择字节场景的双会话阶段门槛 + +`ui-selection-bytes` 与仅限一次性 hosted VM 的 `ui-copy-bytes-system` 共用九阶段场景。独立 Python oracle 要求每次相邻观察之间,bottom 和 main 各自至少推进三个 timer tick;这验证两个真实会话都获得执行机会,不是实际上屏端点。 + +[#66](https://github.com/JavaZeroo/termforge/issues/66) 的[原 Windows CI](https://github.com/JavaZeroo/termforge/actions/runs/40306319630/job/121516200920)在第二轮系统复制的 reverse 阶段失败。原件拆解为 bottom `14→16`,main `11→14`:只有 bottom 的两 tick 不满足三 tick 门槛。grid、DPI、capture 和完整重绘字段符合期望;native 进程正常退出,两会话已关闭,自有 Job 为零且 desktop 已删除。不能把复合错误字符串解释成绘制或剪贴板损坏,也不能把正常退出当作该轮验收成功。 + +旧场景只根据 main 计数推进。现在每个原有 `three_ticks` 等待点都使用 `TwoSessionTickGate`,在动作后的同一位置保存两会话独立基点,再分别要求至少三个 tick。没有增加 sleep、修改时限或降低 Python oracle。相同 gate 同时用于正逆拖选、受控 DPI、清除、空 Copy 和实际输入后的观察,因为这些阶段使用同一双会话合同。 + +共享 helper 的确定性反例直接使用原 CI 数值:`{14,11}→{16,14}` 必须等待;只有 bottom 再推进到 17 后才满足门槛。另验证主会话过慢、不同绝对计数、基点重置及计数倒退。真实数据模式继续由独立文本、运输、物理行、完整重绘及回收 oracle 验证;系统 CF 必须在真实 hosted VM 运行,不能用本地数据模式或伪造环境变量代替。 + +[固定源码与脱敏复核](../../experiments/shell/results/2026-10-26-selection-tick-gate/validation.json)保留原 artifact 身份、原失败和新数据模式结果。原始运输、完整屏幕、frame 与 desktop 信息留本地;最终 `frame.bmp` 不是 reverse 阶段截图。每阶段 `frame_matches_full` 也不证明实际呈现完成。 + +```powershell +ctest --test-dir build/r66 -C Release -R '^scenario_tick_gate$' --output-on-failure +$env:TERMFORGE_BIN_DIR = "$PWD/build/r66/bin/Release" +$env:PYTHONUTF8 = '1' +python -m unittest experiments.shell.test_selection_bytes -v +``` + +普通本地环境会明确跳过实际系统 CF,用例仍检查 native/Python 的 hosted 保护。完整系统门槛由 PR 的 Windows hosted CI 执行;本页和固定记录不借用其他核心/后端或历史成功。 diff --git a/experiments/shell/results/2026-10-26-selection-tick-gate/validation.json b/experiments/shell/results/2026-10-26-selection-tick-gate/validation.json new file mode 100644 index 00000000..1dbb495f --- /dev/null +++ b/experiments/shell/results/2026-10-26-selection-tick-gate/validation.json @@ -0,0 +1,705 @@ +{ + "schema": "termforge-selection-session-tick-gate-v1", + "date": "2026-10-26", + "scope": "R66 scenario synchronization correction; Python acceptance oracle and product behavior unchanged", + "implementation_source_commit": "dcb7e6ed0ebc2cb358b68823c348c2fd9a78dac9", + "base_commit": "3819093b5b622f340bec02a3057a74241507d559", + "source_git_blob_sha1": { + "CMakeLists.txt": "e5b3e38afdb2edca99bba8b3fafc91e90cb19e28", + "experiments/shell/scenario_tick_gate.hpp": "938059af4fa61fe49e2c39b39ace298f91068c33", + "experiments/shell/scenario_tick_gate_test.cpp": "01ea6f28fd0f5042567814c90a9d2ba077de4cab", + "experiments/shell/workspace_selection_bytes_scenario.hpp": "d4365a261c96d6203b380c627682628b31777acf", + "experiments/shell/selection_bytes_checks.py": "6026dc5a0aee332e6d609951729edc17c1be3593", + "experiments/shell/test_selection_bytes.py": "cf3eb4a38d33d5c0f051a16e5706732d125f425a", + "experiments/shell/shell.cpp": "b3681dd0a69dc013783c3d5872b4b48847e7d32e", + "experiments/shell/shell_selection_text_client.cpp": "e01b0e38ff421b5cd9d3b89e6025f529f6fe8bfa" + }, + "source_raw_sha256": { + "CMakeLists.txt": "0763ec046daa5e932583012021a0058351844b28aecf314657ede1f4a2c64603", + "experiments/shell/scenario_tick_gate.hpp": "29a045b5559d7d00c9e0ec48793d889afcd1aea0d30e70d49f95a8394ea0c404", + "experiments/shell/scenario_tick_gate_test.cpp": "c105a6f56c079ecd6efe2154976b7b867a74b8238c20975adaeefe10209acf1a", + "experiments/shell/workspace_selection_bytes_scenario.hpp": "3d9f27fc32d59c7262183f3c86a0f9eced6d13b598c2ac41b824ba5f2b8ea4ca", + "experiments/shell/selection_bytes_checks.py": "7e008035ee827ac1e16416c98536c9b1e7ee523bbf1c3acaf68e5c62ea9a9874", + "experiments/shell/test_selection_bytes.py": "cf639057c87c9bbca9d81710c29f8c0c7a1fb4c09680fd3304414e059179146a", + "experiments/shell/shell.cpp": "49703b00131d88b491f58287f43daa0a6f12fbf5b36a19a097d901af7d62ce44", + "experiments/shell/shell_selection_text_client.cpp": "e4efe9d634839c491fca51219b6d249193d0dc770082d63149ebbe472a642b9a" + }, + "original_ci": { + "run": 40306319630, + "job": 121516200920, + "artifact_id": 12642991922, + "artifact_bytes": 4736664, + "artifact_sha256": "c5fb6a790e6914e2b5195e9f78ee162611bff69d052d5024c47547c865740cb3", + "download_count": 1, + "reported_pr_head": "4a315ee40d9e0deb133780f1ad54278030575cc2", + "actual_checkout": "a160e924b05718141852c894e635a64a0aec7b04", + "checkout_tree": "86eaba6b7428f7ce27dd609eb4730d8d5fbaf10e", + "head_tree_equals_checkout_tree": true, + "environment": { + "os": "Windows Server 2025 Datacenter 10.0.26100", + "runner": "2.338.0", + "image": "windows-2025-vs2026", + "image_version": "20261021.278.1" + }, + "related_same_job_binary_sha256": { + "termforge_shell.exe": "5e83784174f64dc0bbc62555ba443e8d1f1da7dc0731738ee0683bce886dc2a5", + "shell_test_launcher.exe": "2b5d2d176eeb157708362c50366a6c931f0c15e9a41a094f5c8716893f1f449d", + "shell_text_client.exe": "51cfb49c877fd1ba1e48b9e65c3858517f9529f478d62bd6b6df2eb848d2d80f", + "shell_clipboard_reader.exe": "147529df3123fb7e2d0ee2760e2dddc787f84eb667e483f06e75c0653d02c012" + }, + "binary_identity_note": "The selection run does not record a per-run EXE hash. Same-job text-actions and both workflow manifests agree on these hashes; no source-to-binary proof is inferred.", + "per_phase_font_face": null, + "font_limitation": "Stage records measure DPI/cell pixels but do not identify the actual face; no font failure is inferred.", + "original_collector_status": "failed", + "native_cleanup": { + "exit_code": 0, + "timed_out": false, + "native_result": { + "valid": true, + "mode": "ui-copy-bytes-system", + "sessions_closed": 2, + "active_children_after": 0, + "presented_done": null + }, + "job_after": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "desktop_gone": true + }, + "original_private_files_sha256": { + "sample.json": "44c20a786f3069acf07e83405ea77be55b8feff0fae8d34b57cca282383372c7", + "selection-bytes.json": "2e8db8e8df82eb7e01a67f271b41c7d07404881dcb90879ce18c20ff083916c5", + "selection-bytes-progress.jsonl": "fcef5355749908663e3990cc4a05a6db422f460be68b6d48995e9c5c9c9f3c41", + "frame.bmp": "f24abb53b0fcf3ce76e4a4bb1b2810638fbfc3b5a1cc27ddfd8d319116a0c0a0" + }, + "final_frame": { + "width": 1360, + "height": -860, + "sha256": "f24abb53b0fcf3ce76e4a4bb1b2810638fbfc3b5a1cc27ddfd8d319116a0c0a0", + "reverse_stage_frame": false + }, + "phases": [ + { + "phase": "forward", + "ticks_bottom_main": [ + 14, + 11 + ], + "delta_bottom_main": [ + 14, + 11 + ], + "grid_and_cell_valid_both": true, + "dpi_valid_both": true, + "full_redraw_matches_both": true, + "capture_released_both": true, + "semantic_fields_match_both": true, + "clipboard_matches": true, + "raw_input_matches": true + }, + { + "phase": "reverse", + "ticks_bottom_main": [ + 16, + 14 + ], + "delta_bottom_main": [ + 2, + 3 + ], + "grid_and_cell_valid_both": true, + "dpi_valid_both": true, + "full_redraw_matches_both": true, + "capture_released_both": true, + "semantic_fields_match_both": true, + "clipboard_matches": true, + "raw_input_matches": true + }, + { + "phase": "dpi-96", + "ticks_bottom_main": [ + 19, + 17 + ], + "delta_bottom_main": [ + 3, + 3 + ], + "grid_and_cell_valid_both": true, + "dpi_valid_both": true, + "full_redraw_matches_both": true, + "capture_released_both": true, + "semantic_fields_match_both": true, + "clipboard_matches": true, + "raw_input_matches": true + }, + { + "phase": "dpi-144", + "ticks_bottom_main": [ + 25, + 23 + ], + "delta_bottom_main": [ + 6, + 6 + ], + "grid_and_cell_valid_both": true, + "dpi_valid_both": true, + "full_redraw_matches_both": true, + "capture_released_both": true, + "semantic_fields_match_both": true, + "clipboard_matches": true, + "raw_input_matches": true + }, + { + "phase": "dpi-192", + "ticks_bottom_main": [ + 31, + 29 + ], + "delta_bottom_main": [ + 6, + 6 + ], + "grid_and_cell_valid_both": true, + "dpi_valid_both": true, + "full_redraw_matches_both": true, + "capture_released_both": true, + "semantic_fields_match_both": true, + "clipboard_matches": true, + "raw_input_matches": true + }, + { + "phase": "dpi-96-restored", + "ticks_bottom_main": [ + 37, + 35 + ], + "delta_bottom_main": [ + 6, + 6 + ], + "grid_and_cell_valid_both": true, + "dpi_valid_both": true, + "full_redraw_matches_both": true, + "capture_released_both": true, + "semantic_fields_match_both": true, + "clipboard_matches": true, + "raw_input_matches": true + }, + { + "phase": "cleared", + "ticks_bottom_main": [ + 41, + 39 + ], + "delta_bottom_main": [ + 4, + 4 + ], + "grid_and_cell_valid_both": true, + "dpi_valid_both": true, + "full_redraw_matches_both": true, + "capture_released_both": true, + "semantic_fields_match_both": true, + "clipboard_matches": true, + "raw_input_matches": true + }, + { + "phase": "empty-copy", + "ticks_bottom_main": [ + 44, + 42 + ], + "delta_bottom_main": [ + 3, + 3 + ], + "grid_and_cell_valid_both": true, + "dpi_valid_both": true, + "full_redraw_matches_both": true, + "capture_released_both": true, + "semantic_fields_match_both": true, + "clipboard_matches": true, + "raw_input_matches": true + }, + { + "phase": "input-after", + "ticks_bottom_main": [ + 47, + 45 + ], + "delta_bottom_main": [ + 3, + 3 + ], + "grid_and_cell_valid_both": true, + "dpi_valid_both": true, + "full_redraw_matches_both": true, + "capture_released_both": true, + "semantic_fields_match_both": true, + "clipboard_matches": true, + "raw_input_matches": true + } + ] + }, + "cause": { + "phase": "reverse", + "session": "bottom", + "previous_ticks": 14, + "observed_ticks": 16, + "delta": 2, + "required_delta": 3, + "main_previous_ticks": 11, + "main_observed_ticks": 14, + "old_main_only_gate": true, + "new_two_session_gate": false, + "gate_after_only_bottom_advances_to_17": true, + "explanation": "Two independently scheduled session timers cannot use main-only progress as evidence that both have advanced. All existing three_ticks phase gates now use per-session baselines." + }, + "preserved_contract": { + "native_deadline_seconds": 30, + "outer_deadline_seconds": 40, + "phase_count": 9, + "observation_cap_bytes": 8192, + "minimum_ticks_each_session": 3, + "python_oracle_unchanged": true, + "text_transport_colors_reverse_drag_geometry_dpi_capture_full_redraw_unchanged": true, + "real_clipboard_vm_guard_unchanged": true, + "added_sleep": false + }, + "local_validation": { + "platform": "Windows-11-10.0.26200-SP0", + "configuration": "default-core/system-ConPTY", + "binary_sha256": { + "termforge_shell.exe": "170aeb171fca34e05672b09448daf68db112d8a42a0d2d9ab05011ee042db7ba", + "shell_test_launcher.exe": "8909722a360d5ea599252ba784aa6199a78ed2665f89e7ded33b796419dd3db7", + "shell_selection_text_client.exe": "7aede0e7788fc9b45317216bb898cfb1a2dfbb28932a0e6fcd223d2b2ef01cfc", + "scenario_tick_gate_test.exe": "c0ea2011b0d4d46daf1cf6a72353a592a86724384713e337149280d089016850" + }, + "helper_ctest": "passed", + "helper_assertions": 10, + "selection_bytes_module": { + "tests": 9, + "passed": 8, + "skipped": 1, + "skip": "real system CF requires a disposable GitHub-hosted VM", + "log_sha256": "dc9eb6a6ea341a6889a6a86a31660265d40a04c86d890373404b692b0aef4efc" + }, + "fresh_data_runs": [ + { + "case": "local-0", + "mode": "ui-selection-bytes", + "exit_code": 0, + "timed_out": false, + "native_result": { + "valid": true, + "mode": "ui-selection-bytes", + "sessions_closed": 2, + "active_children_after": 0, + "presented_done": null + }, + "job_after": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "desktop_gone": true, + "independent_validation": { + "system_clipboard": false, + "phases": 9, + "fixture_style": "styled-tail-v1", + "soft_wrap_join": true, + "hard_crlf": true, + "explicit_trailing_spaces": 2, + "controlled_dpi": [ + 96, + 144, + 192, + 96 + ], + "dpi_selection_preserved": true, + "raw_input_isolated": true, + "cleanup_verified": true, + "evidence": { + "bottom": { + "producer_sha256": "e8f12ba1dfede1f963c49264142db4c8790c29d27876a9dfaad57fb3e4a86b8b", + "transport_sha256": "a960cfb1d8ae2b225613f643cbde5c2ad46160164e663473bfd7f6eab6044d20", + "transport_bytes": 1765, + "transport_equals_producer": false, + "literal_styled_tail_occurrences": 8, + "raw_bytes": 0 + }, + "main": { + "producer_sha256": "4ae7ba438881c4049c6cd1540175acf68bdb3b8e64146c4541eea70fbb6b9e31", + "transport_sha256": "6c76fabac86e0ec7eb39cc90590bc58621abf489b7de61ab3a8c4a68e6c48eac", + "transport_bytes": 1809, + "transport_equals_producer": false, + "literal_styled_tail_occurrences": 8, + "raw_bytes": 1 + } + } + }, + "phases": [ + { + "phase": "forward", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 14, + 12 + ], + "delta_bottom_main": [ + 14, + 12 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "reverse", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 17, + 15 + ], + "delta_bottom_main": [ + 3, + 3 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "dpi-96", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 20, + 18 + ], + "delta_bottom_main": [ + 3, + 3 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "dpi-144", + "controlled_dpi": 144, + "ticks_bottom_main": [ + 26, + 24 + ], + "delta_bottom_main": [ + 6, + 6 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "dpi-192", + "controlled_dpi": 192, + "ticks_bottom_main": [ + 32, + 31 + ], + "delta_bottom_main": [ + 6, + 7 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "dpi-96-restored", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 38, + 37 + ], + "delta_bottom_main": [ + 6, + 6 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "cleared", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 41, + 40 + ], + "delta_bottom_main": [ + 3, + 3 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "empty-copy", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 44, + 43 + ], + "delta_bottom_main": [ + 3, + 3 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "input-after", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 48, + 47 + ], + "delta_bottom_main": [ + 4, + 4 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + } + ], + "private_sha256": { + "sample.json": "38bb682c893b0532a147e7d37589442f0e02de98e0f5aac5d8e5fcae60cda7e6", + "selection-bytes.json": "6fad6e0d1fb7ef1de74e9dd310bfd4aad10ec1de2ba4391f788cd66bfd92656c", + "selection-bytes-progress.jsonl": "1ede6dde2d49d0e3364414d831ce749e288c98b6cec3e7ff0293c9b4a1a667e3", + "frame.bmp": "83cce5102441d1590b5ad469a1cb9d2a444a83a0660c129c301cbfdc4c6452b0", + "selection-bytes-main.producer.json": "67bbbd89891bbe141d604f3f196fb6e3a41376142ef7db91b63163b856ecb60a", + "selection-bytes-main.producer.bin": "4ae7ba438881c4049c6cd1540175acf68bdb3b8e64146c4541eea70fbb6b9e31", + "selection-bytes-main.transport.bin": "6c76fabac86e0ec7eb39cc90590bc58621abf489b7de61ab3a8c4a68e6c48eac", + "selection-bytes-bottom.producer.json": "df7fe9106a98a3785c4c623ff3306957ac9b7af3843bf8a6072c8826459668c7", + "selection-bytes-bottom.producer.bin": "e8f12ba1dfede1f963c49264142db4c8790c29d27876a9dfaad57fb3e4a86b8b", + "selection-bytes-bottom.transport.bin": "a960cfb1d8ae2b225613f643cbde5c2ad46160164e663473bfd7f6eab6044d20" + } + }, + { + "case": "local-1", + "mode": "ui-selection-bytes", + "exit_code": 0, + "timed_out": false, + "native_result": { + "valid": true, + "mode": "ui-selection-bytes", + "sessions_closed": 2, + "active_children_after": 0, + "presented_done": null + }, + "job_after": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "job_after_cleanup": { + "active": 0, + "total": 11, + "terminated": 0 + }, + "desktop_gone": true, + "independent_validation": { + "system_clipboard": false, + "phases": 9, + "fixture_style": "styled-tail-v1", + "soft_wrap_join": true, + "hard_crlf": true, + "explicit_trailing_spaces": 2, + "controlled_dpi": [ + 96, + 144, + 192, + 96 + ], + "dpi_selection_preserved": true, + "raw_input_isolated": true, + "cleanup_verified": true, + "evidence": { + "bottom": { + "producer_sha256": "e8f12ba1dfede1f963c49264142db4c8790c29d27876a9dfaad57fb3e4a86b8b", + "transport_sha256": "38d7c6a5589594edfbbcfc683ff5f1666926586ffe493faaf131c35bfb076795", + "transport_bytes": 1765, + "transport_equals_producer": false, + "literal_styled_tail_occurrences": 8, + "raw_bytes": 0 + }, + "main": { + "producer_sha256": "4ae7ba438881c4049c6cd1540175acf68bdb3b8e64146c4541eea70fbb6b9e31", + "transport_sha256": "26b39893b5c01a51473791e789cf6a5349a11efe16883e3f5d801df863825ff3", + "transport_bytes": 1809, + "transport_equals_producer": false, + "literal_styled_tail_occurrences": 8, + "raw_bytes": 1 + } + } + }, + "phases": [ + { + "phase": "forward", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 14, + 11 + ], + "delta_bottom_main": [ + 14, + 11 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "reverse", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 17, + 14 + ], + "delta_bottom_main": [ + 3, + 3 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "dpi-96", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 20, + 17 + ], + "delta_bottom_main": [ + 3, + 3 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "dpi-144", + "controlled_dpi": 144, + "ticks_bottom_main": [ + 26, + 23 + ], + "delta_bottom_main": [ + 6, + 6 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "dpi-192", + "controlled_dpi": 192, + "ticks_bottom_main": [ + 32, + 29 + ], + "delta_bottom_main": [ + 6, + 6 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "dpi-96-restored", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 38, + 35 + ], + "delta_bottom_main": [ + 6, + 6 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "cleared", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 41, + 38 + ], + "delta_bottom_main": [ + 3, + 3 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "empty-copy", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 44, + 41 + ], + "delta_bottom_main": [ + 3, + 3 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + }, + { + "phase": "input-after", + "controlled_dpi": 96, + "ticks_bottom_main": [ + 47, + 44 + ], + "delta_bottom_main": [ + 3, + 3 + ], + "full_redraw_matches_both": true, + "capture_released_both": true + } + ], + "private_sha256": { + "sample.json": "78a3569fa6183d0658c9ca26d6640ce139d11bd5384f9e1c07de0c81f925c859", + "selection-bytes.json": "16b81feb01139e69bf328f74da761e77543fef0e0c72c16473d6022bd74a8a87", + "selection-bytes-progress.jsonl": "9cd55bba47a1c785bb9f2c231b06f9a2cac1421f09c18f595e34cabac407d63f", + "frame.bmp": "83cce5102441d1590b5ad469a1cb9d2a444a83a0660c129c301cbfdc4c6452b0", + "selection-bytes-main.producer.json": "67bbbd89891bbe141d604f3f196fb6e3a41376142ef7db91b63163b856ecb60a", + "selection-bytes-main.producer.bin": "4ae7ba438881c4049c6cd1540175acf68bdb3b8e64146c4541eea70fbb6b9e31", + "selection-bytes-main.transport.bin": "26b39893b5c01a51473791e789cf6a5349a11efe16883e3f5d801df863825ff3", + "selection-bytes-bottom.producer.json": "df7fe9106a98a3785c4c623ff3306957ac9b7af3843bf8a6072c8826459668c7", + "selection-bytes-bottom.producer.bin": "e8f12ba1dfede1f963c49264142db4c8790c29d27876a9dfaad57fb3e4a86b8b", + "selection-bytes-bottom.transport.bin": "38d7c6a5589594edfbbcfc683ff5f1666926586ffe493faaf131c35bfb076795" + } + } + ] + }, + "limitations": [ + "The original hosted system-clipboard process exited zero but its collector remains failed because the required per-session timer evidence was missing.", + "No original stage result is rewritten as passed. This artifact batch is inspected once; no old hosted rerun is used to choose a green.", + "Local fresh data-mode runs do not accept the system-clipboard path; final hosted same-source CF validation is still required.", + "The final frame.bmp is not a captured reverse-stage frame. Per-stage full-redraw boolean observations are separate from actual presentation, which remains unknown.", + "No specific cause for independent workspace, InputPump or transport failures is inferred.", + "The change fixes test phase synchronization, not renderer or clipboard behavior, and does not establish full R1/R2, different-DPI monitors, candidate/official backend, or performance acceptance." + ] +}