diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b1e2cac..73207ac2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,6 +297,7 @@ if(BUILD_TESTING) termforge_add_python_test(binary_paths -m unittest experiments.test_build_paths -v) termforge_add_python_test(conpty_margin_contract -m unittest experiments.conpty.test_margin_contract -v) termforge_add_python_test(benchmark_tools -m unittest discover -s benchmarks/tests -v) + termforge_add_python_test(windterm_alternate_export -m unittest research.ui.test_export_alternate_reference -v) termforge_add_python_test(differential -m unittest discover -s experiments/differential -p test_*.py -v) termforge_add_python_test(ime_inventory -m unittest research.ime.test_hosted_inventory research.ime.test_prepare_hosted research.ime.test_product_ime -v) termforge_add_python_test(text_actions -m unittest experiments.shell.test_actions -v) diff --git a/docs/research/windterm-alternate-reference-2026-10-28.md b/docs/research/windterm-alternate-reference-2026-10-28.md new file mode 100644 index 00000000..7dc94364 --- /dev/null +++ b/docs/research/windterm-alternate-reference-2026-10-28.md @@ -0,0 +1,55 @@ +# WindTerm 2.7.0:主/备用屏模式的直达终端观察 + +本次 [#118](https://github.com/JavaZeroo/termforge/issues/118) 在固定原版上完成一次 26 阶段的合成输入。它提供原版终端的文字投影和光标协议回复,不能代替本地 Shell 运输、逐格样式、滚动区域或完整 R2 验收。自主 C++ 后续实现应保留下面的原版事实,不能直接把通用规范或旧候选结果当作原版合同。 + +## 身份与方法 + +固定官方 2.7.0 Windows x64 EXE SHA-256 为 `7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`;复制的 4,047 个发行文件按既有 `e459adf2…203` 清单守卫核对。完整来源、探针哈希、精确输入、每阶段输出及限制见[白名单摘要](../../research/ui/results/2026-10-28-alternate-reference/validation.json)。REA Frida Evidence ID 为 `ev_3bdab796b335d64f0ffc8dc26bbcfd3c1441a2805993157451f8602311609a6f`。 + +复用 [r1_launch.py](../../research/ui/r1_launch.py) 的独立 WindowStation/Desktop、挂起创建后分配 OwnedJob、独立 HOME 和单个合成 profile;没有切换用户桌面、访问剪贴板或修改显示设置。合成 Telnet peer 只监听 `127.0.0.1` 的随机端口,直接发送 VT,避开 WinPTY/ConPTY 的本地控制台改写。最终 NAWS 为 **91×25**,所有已存 UI 快照 capacity 均为 25;没有校准或重试。源生成器在首次 NAWS 时冻结压力输入,实际源唯一对应其 rows=24 分支,初始 NAWS 值及转换序列未单独保留,不能声称启动全过程网格固定。原版最多运行一次。 + +`rea_capture_seconds=45` 是传给 REA `--duration-ms 45000` 的捕获时长;复用启动器以 `seconds+15` 设置 CLI 等待超时,因此 `cli_wait_timeout_seconds=60`。原版在 CLI 启动前已恢复运行,期间还有单独的 2 秒等待,CLI 返回后再执行资源回收。**45 秒不是整个原版进程生命周期或整个启动器的墙钟上限**,60 秒也仅约束 CLI 等待。本记录不提供整程生命周期上限保证。 + +[公开探针](../../research/ui/alternate_reference.py) 在每阶段发送独立输入及 `CSI 6n`,等原版 UI 投影连续稳定后保存快照,再经文件确认允许下一阶段发送。收到 **26 条回复、26 份快照和 26 次阶段确认**;应用回复恰好是 26 条 `CSI row;column R`,没有 `?` 前缀或其他应用字节。完整源十六进制及回复逐项核对,不能把这次 TCP 传输解释成逐字节/任意分块回归。探针没有在首次或重复 `1049h` 后补发 ED/H。 + +## 已观察事实 + +以下光标均为 **DSR 的一基行、列**。这是原版终端主动发出的协议回复;UI 的 `caretColumn` 在全程一直为 5,不能把该 UI 字段当作终端光标。 + +| 输入或阶段 | 原版观察 | +|---|---| +| 主屏写 `MAIN_A`,定位 `3;6H` | DSR `(3,6)`;文档第 0 行为 `MAIN_A` | +| 第一次 `?1049h` | DSR `(1,1)`;文档仍返回原主屏文字,未取得完整 active grid | +| 写 `ALT_ONE`,定位 `5;9H`,再次 `?1049h` | DSR 从 `(5,9)` 回 `(1,1)`;`ALT_ONE` 从返回的文字投影消失,文档行数 7→3,scrollTop 2→0 | +| 备用屏写 `DUP` 后 `?1049l` | DSR 恢复 `(3,6)`;主屏 `MAIN_A` 保持,`DUP` 不在返回投影中 | +| 主屏写 `M`,再次 `?1049l` | `M` 位于文档第 2 行、零基第 5 列;重复退出保持 DSR `(3,7)` | +| 重新 `?1049h` | DSR `(1,1)`,旧 `DUP` 不在返回投影中 | +| 备用屏写超过一屏的 `ALT_LINE_00…28` | DSR `(25,12)`;投影保留 `ALT_LINE_04…28`,更早四行及 `ALT_TWO` 不在返回投影中 | +| 压力后 `?1049l` | DSR 恢复 `(3,7)`,投影恰好恢复主屏 `MAIN_A` 和 `M`,未带入 ALT 标记 | +| 随后 `?47h` | 再次取得前次备用文字和 DSR `(25,12)`;`47l` 后主屏仍 `(3,7)` | +| `47` 备用屏定位 `(6,11)`、退出再进入 | 备用文字保持、DSR 仍 `(6,11)` | +| 随后 `?1047h` | 保留此前 `47` 备用文字和 DSR `(6,11)` | +| `1047` 备用屏定位 `(7,12)`、退出再进入 | 备用文字保持、DSR 仍 `(7,12)` | +| `8;13H` 后 `?1048h`,再定位 `12;20H`、`?1048l` | 仍回复 `(12,20)`;随后 `Q` 确实写在文档第 11 行、零基第 19 列,DSR `(12,21)` | + +不能将 `1049h` 的重复进入按一般的“当前已在备用屏则不做事”处理并声称原版一致。`1048` 本次没有恢复光标;这只约束这个具体输入,不证明所有 `1048` 或其他保存恢复路径均不支持。`47` / `1047` 是在已有 `1049` 内容后观察,未覆盖全新会话的首次进入及全部交叉转换。 + +## 文档投影与未知项 + +`activeText.lineText` / `visibleLineCount` 返回的是保留文档。备用屏期间仍能读到原主屏 `MAIN_A`,`scrollTop` 随备用输出变化,因此不能把整个数组当成 active screen。压力后共 28 行,其中主屏仍占前三行;备用投影为 25 行。本次只枚举 `[0, visibleLineCount)`,不证明该区间就是完整 active grid,也没有调用该区间以外的 `lineText`。原始投影保持不变,不用候选结果补造 viewport 映射。 + +重复和重新 `1049h` 的归位、旧 ALT 标记消失,是已观察事实;它们与备用区清空一致,但**完整 active grid 是否清空仍未知**。首次进入的默认空白格、当前样式是否继承或恢复、逐格颜色、pending-wrap、主历史在备用期间的可浏览方式及真实选择行为均未取到。暴露的 `isAltScreen` / `isMainScreen` 名称本次仅被枚举,没有调用,字段保持未知。 + +原版 UI 光标 API、DSR 光标与文档行号分别保存,未混作一套坐标。`cell_styles` / `current_style` 为 `null`,接口名不构成样式证据。此观察不能批准标准行为差异或原版缺陷修复,更不解除 #41/#56 的本地 Shell 原生 margin 差异。 + +## 回收、复现与实现边界 + +REA 正常结束,无消息截断、脚本错误、清理错误或 stderr;自有 Job 最终 `active=0,total=3,terminated=0`,profile 仍只有一个合成 Telnet 会话。启动器主动结束原版窗口,计数不是自然 GUI 退出证明。provider 的 subject/environment 为 null,包身份由独立准备守卫保证。 + +原始插桩、绝对路径、PID、复制的原版资源和合成 profile 保留在 `local/alternate-reference-0`;公开内容仅包含自有探针、合成字节和脱敏结果。现有本地证据可用 [export_alternate_reference.py](../../research/ui/export_alternate_reference.py) 重新审计导出,不需要再启动原版。重新执行调查须另建输出目录并声明次数及边界,不把本次一次观察改写成重复可靠性或性能结论。 + +导出门槛使用显式 `require` / `ValueError`,在 `python -O` 下仍核对身份、来源、消息一致性、确认文件、回收与合成文本白名单。文本仅允许本夹具的标记及已有覆盖片段,不能因为字符均为大写就放行未知文字;所有门槛通过后才创建导出目录和文件。[公共 CLI 回归](../../research/ui/test_export_alternate_reference.py) 使用自造记录,在普通和优化解释器下分别验证有效记录及私有文本、错误身份、变更脚本/输入、快照不一致、错误阶段确认和未回收 Job 的拒绝路径,不依赖原版资源。 + +离线修复前,该回归复现了 9 个拒绝用例失败及 2 个新时长字段缺失;修复后,`python -m unittest research.ui.test_export_alternate_reference -v` 和 `python -O -m unittest research.ui.test_export_alternate_reference -v` 各 8 项通过,每次覆盖 20 次公共导出 CLI 调用。有效原始记录也经普通及 `-O` 导出得到相同 JSON;与首次摘要相比,26 阶段输入、文字/光标观测、provider、原始文件哈希和原版身份均不变。修复只更新离线导出校验、时长含义及导出来源标识,没有新增原版运行。 + +首个 C++ 模式切片可以据此锁定已观察光标转换及主屏文字恢复,并为未知的清空、样式、历史投影与更复杂交叉继续取证。实现仍需独立验证全局行身份、版本、选区失效和有界主/备用历史;原版文档布局不要求内部容器结构相同。完整 #27 以及 R2 均未完成。 diff --git a/research/ui/alternate_reference.js b/research/ui/alternate_reference.js new file mode 100644 index 00000000..da7ed36a --- /dev/null +++ b/research/ui/alternate_reference.js @@ -0,0 +1,40 @@ +// Original UI-state observation only. No clipboard, user settings or physical input. +let stage=0,busy=false,finished=false,due=0,lastPhase=-1,stable=null,stableSince=0; +const started=Date.now(),observations=[]; +function currentTerminal(){ + const raw=evalText(`JSON.stringify((function(){var t=window.activeText;if(!t)return null;function get(k){try{var v=typeof t[k]==='function'?t[k]():t[k];return v===undefined?null:v;}catch(e){return null;}}var n=get('visibleLineCount');var lines=[];if(typeof n==='number'&&n>=0&&n<=200){for(var i=0;i41000)throw Error('bounded scenario deadline'); + if(stage===0){if(!ready('ui'))return;resize(main,1360,860);stage=1;} + else if(stage===1){scan();expandAll(tree);focus(tree,7);key(tree,0x1000011);stage=2;} + else if(stage===2){key(tree,0x1000004);stage=3;} + else{ + const peer=peerState();if(!peer||!peer.connected||peer.phase===null)return; + if(peer.complete){finish('observed-all-phases',null);return;} + if(peer.error)throw Error('peer '+peer.error); + if(peer.phase!==lastPhase){lastPhase=peer.phase;stable=null;stableSince=Date.now();} + const terminal=currentTerminal();if(!terminal||!terminal.connected||terminal.protocol!=='Telnet')return; + const state=JSON.stringify(terminal); + if(state!==stable){stable=state;stableSince=Date.now();return;} + const record=peer.records[peer.phase]; + if(Date.now()-stableSince<450||(!record.cursor_reply&&Date.now()-stableSince<900))return; + const observation={phase:peer.phase,label:peer.phase_label,terminal,cursor_reply:record.cursor_reply}; + observations.push(observation);send({kind:'alternate_phase',...observation}); + const ack=new File(__RUN_DIRECTORY_JSON__+'/ack-'+peer.phase,'w');ack.write('observed');ack.close(); + stableSince=Date.now()+100000; + } + due=Date.now()+75; + }catch(e){finish('failed',String(e));}finally{busy=false;} +}}); +setTimeout(()=>finish('failed','bounded observation timer'),42000); diff --git a/research/ui/alternate_reference.py b/research/ui/alternate_reference.py new file mode 100644 index 00000000..2f4a1a21 --- /dev/null +++ b/research/ui/alternate_reference.py @@ -0,0 +1,207 @@ +"""Bounded original WindTerm terminal-mode observation over an owned loopback peer. + +Uses the existing pinned-package, synthetic-profile and non-input station guards. +This observes the original terminal directly, independently of local-shell transport. +""" +from __future__ import annotations +import argparse +import hashlib +import json +from pathlib import Path +import re +import socket +import sys +import threading +import time + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) + + +def phases(rows: int) -> list[tuple[str, bytes]]: + esc = b"\x1b[" + return [ + ("main-before", esc+b"0m"+esc+b"2J"+esc+b"HMAIN_A"+esc+b"3;6H"+esc+b"31;44m"), + ("1049-first-enter", esc+b"?1049h"), + ("1049-alt-written", b"ALT_ONE"+esc+b"5;9H"+esc+b"32;45m"), + ("1049-duplicate-enter", esc+b"?1049h"), + ("1049-duplicate-write", b"DUP"), + ("1049-first-exit", esc+b"?1049l"), + ("1049-main-write", b"M"), + ("1049-duplicate-exit", esc+b"?1049l"), + ("1049-second-enter", esc+b"?1049h"), + ("1049-second-write", b"ALT_TWO"), + ("1049-alt-pressure", b"\r\n"+b"\r\n".join(f"ALT_LINE_{n:02}".encode() for n in range(rows+5))), + ("1049-second-exit", esc+b"?1049l"), + ("47-first-enter", esc+b"?47h"), + ("47-alt-written", b"ALT47"+esc+b"6;11H"+esc+b"33;46m"), + ("47-first-exit", esc+b"?47l"), + ("47-second-enter", esc+b"?47h"), + ("47-second-exit", esc+b"?47l"), + ("1047-first-enter", esc+b"?1047h"), + ("1047-alt-written", b"ALT1047"+esc+b"7;12H"), + ("1047-first-exit", esc+b"?1047l"), + ("1047-second-enter", esc+b"?1047h"), + ("1047-second-exit", esc+b"?1047l"), + ("1048-save", esc+b"8;13H"+esc+b"31;44m"+esc+b"?1048h"), + ("1048-moved", esc+b"12;20H"+esc+b"32;45m"), + ("1048-restore", esc+b"?1048l"), + ("1048-restored-write", b"Q"), + ] + + +class Peer: + def __init__(self, directory): + self.directory = directory + self.listener = socket.socket() + self.listener.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) + self.listener.bind(("127.0.0.1", 0)) + self.listener.listen(1) + self.listener.settimeout(.1) + self.port = self.listener.getsockname()[1] + self.stop = threading.Event() + self.state = {"connected": False, "columns": None, "rows": None, "phase": None, "records": []} + self.thread = threading.Thread(target=self._serve, name="TermForge-owned-mode-peer", daemon=False) + self.thread.start() + + def _record(self): + temporary = self.directory / "peer.tmp" + temporary.write_text(json.dumps(self.state), encoding="utf-8") + try: + temporary.replace(self.directory / "peer.json") + except PermissionError: + pass + + def _serve(self): + connection = None + try: + while not self.stop.is_set(): + try: + connection, address = self.listener.accept() + break + except socket.timeout: + pass + if connection is None: + return + if address[0] != "127.0.0.1": + raise RuntimeError("Non-loopback client") + self.state["connected"] = True + connection.settimeout(.05) + connection.sendall(bytes([255,251,1,255,251,3,255,253,31])) + parse, verb, sub = "text", 0, bytearray() + received = bytearray() + index = -1 + reply_start = 0 + selected = None + deadline = time.monotonic() + 43 + while not self.stop.is_set() and time.monotonic() < deadline: + try: + block = connection.recv(4096) + if not block: + break + except socket.timeout: + block = b"" + for byte in block: + if parse == "text": + if byte == 255: + parse = "iac" + else: + received.append(byte) + elif parse == "iac": + if byte in (251,252,253,254): + verb, parse = byte, "option" + elif byte == 250: + sub, parse = bytearray(), "sub" + else: + if byte == 255: + received.append(byte) + parse = "text" + elif parse == "option": + if verb == 253 and byte not in (1,3): + connection.sendall(bytes([255,252,byte])) + elif verb == 251 and byte != 31: + connection.sendall(bytes([255,254,byte])) + parse = "text" + elif parse == "sub": + if byte == 255: + parse = "sub-iac" + elif len(sub) < 256: + sub.append(byte) + else: + raise RuntimeError("Suboption bound") + elif parse == "sub-iac": + if byte == 240: + if len(sub) == 5 and sub[0] == 31: + self.state["columns"] = int.from_bytes(sub[1:3], "big") + self.state["rows"] = int.from_bytes(sub[3:5], "big") + parse = "text" + else: + if byte == 255: + sub.append(byte) + parse = "sub" + if len(received) > 65536: + raise RuntimeError("Input bound") + if self.state["rows"] and selected is None: + if not 10 <= self.state["rows"] <= 60 or not 40 <= self.state["columns"] <= 160: + raise RuntimeError("Unexpected controlled grid") + selected = phases(self.state["rows"]) + self.state["phase_count"] = len(selected) + if index >= 0: + matches = list(re.finditer(rb"\x1b\[(\??)(\d+);(\d+)R", bytes(received[reply_start:]))) + self.state["records"][index]["cursor_reply"] = [int(matches[-1][2]),int(matches[-1][3])] if matches else None + if selected and (index == -1 or (self.directory / f"ack-{index}").exists()): + index += 1 + if index == len(selected): + self.state["complete"] = True + self._record() + break + label, content = selected[index] + wire = content+b"\x1b[6n" + reply_start = len(received) + connection.sendall(wire) + self.state["phase"] = index + self.state["phase_label"] = label + self.state["records"].append({"phase": index, "label": label, "source_hex": content.hex(), "source_bytes": len(content), "source_sha256": hashlib.sha256(content).hexdigest(), "dsr_suffix_hex": "1b5b366e", "cursor_reply": None}) + self.state["received_hex"] = received.hex() + self._record() + except (OSError, RuntimeError, ValueError) as error: + if not self.stop.is_set(): + self.state["error"] = str(error) + finally: + if connection: + connection.close() + self.state["closed"] = True + self._record() + + def close(self): + self.stop.set() + self.thread.join(3) + self.listener.close() + if self.thread.is_alive(): + raise RuntimeError("Owned loopback peer failed to stop") + + +def run(source: Path, out: Path, rea: Path): + from research.ui import r1_launch + launcher = Path(r1_launch.__file__) + text = launcher.read_text(encoding="utf-8") + replacements = { + "from research.ui.r1_vt_fixture import Peer": "from research.ui.alternate_reference import Peer", + 'Path(__file__).with_name("r1_menu_wrap.js").read_text(encoding="utf-8")': 'Path(__file__).with_name("alternate_reference.js").read_text(encoding="utf-8")', + } + for old, new in replacements.items(): + if text.count(old) != 1: + raise RuntimeError("Existing protected launcher source changed") + text = text.replace(old, new) + namespace = {"__file__": str(launcher), "__name__": "termforge_alternate_launcher"} + exec(compile(text, str(launcher), "exec"), namespace) + namespace["run"](source, out, rea, 45, False, "menu-wrap") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--source", type=Path, required=True) + parser.add_argument("--out", type=Path, required=True) + parser.add_argument("--rea", type=Path, required=True) + args = parser.parse_args() + run(args.source, args.out, args.rea) diff --git a/research/ui/export_alternate_reference.py b/research/ui/export_alternate_reference.py new file mode 100644 index 00000000..46d8a943 --- /dev/null +++ b/research/ui/export_alternate_reference.py @@ -0,0 +1,119 @@ +"""Export only synthetic bytes, original text projections and bounded run metadata.""" +from __future__ import annotations +import argparse +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) +from research.ui.alternate_reference import phases + +# This exporter audits the fixed observation, not a newly executed source tree. +OBSERVATION_BASE_COMMIT = "01673e89a7db718a1a6faf63991a50b2531710fa" +SYNTHETIC_LINE = re.compile(r" *(?:MAIN_A|ALT_ONE|DUP|M|ALT_TWO|ALT_LINE_[0-9]{2}(?:ALT47)?|ALT_LINE_[0-9]ALT1047|Q)? *") + + +def require(condition, message): + """Publication gates must remain active under Python optimization.""" + if not condition: + raise ValueError(message) + + +def export(directory: Path, destination: Path): + sha = lambda path: hashlib.sha256(path.read_bytes()).hexdigest() + read = lambda name: json.loads((directory / name).read_text(encoding="utf-8")) + run, peer, result = read("run.json"), read("peer.json"), read("probe-result.json") + evidence_record = read("evidence.json") + evidence = evidence_record["data"]["evidence"] + normalized = evidence["normalized_result"] + cleanup, profile = read("cleanup.json"), read("profile-check.json") + require(evidence_record["ok"] and result["status"] == "observed-all-phases", "Observation is not complete") + require(not result["error"] and peer["complete"] and not peer.get("error"), "Observation contains an error") + require(cleanup["active"] == 0 and profile == {"only_synthetic_session": True, "session_count": 1}, "Owned resources or profile scope failed validation") + require(run["sha256"] == "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", "Original executable identity changed") + require(run["files_sha256"] == "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203", "Original package manifest changed") + require(normalized["source_sha256"] == run["script_sha256"] == sha(directory/"probe.js"), "Instrumented probe identity changed") + message_phases = [message["payload"] for message in normalized["messages"] if message["type"] == "send" and message.get("payload",{}).get("kind") == "alternate_phase"] + require([{key:value for key,value in message.items() if key != "kind"} for message in message_phases] == result["observations"], "Provider phase messages differ from saved observations") + message_results = [message["payload"] for message in normalized["messages"] if message["type"] == "send" and message.get("payload",{}).get("kind") == "alternate_reference_result"] + require(len(message_results) == 1 and {key:value for key,value in message_results[0].items() if key != "kind"} == result, "Provider final message differs from saved result") + received = bytes.fromhex(peer["received_hex"]) + matches = list(re.finditer(rb"\x1b\[(\??)(\d+);(\d+)R", received)) + require(not re.sub(rb"\x1b\[(\??)(\d+);(\d+)R", b"", received), "Unexpected application reply bytes") + # The original resizes during session startup. The peer freezes its payload + # at first NAWS, but v1 did not retain that initial negotiation separately. + # Recover only the generator's row argument from the exact retained source; + # do not substitute the final grid or rewrite any source bytes. + matching_rows = [rows for rows in range(10,61) if [source.hex() for _,source in phases(rows)] == [record["source_hex"] for record in peer["records"]]] + require(len(matching_rows) == 1, "Retained source does not uniquely match the public generator") + source_generation_rows = matching_rows[0] + expected = phases(source_generation_rows) + require(len(matches) == len(expected) == len(result["observations"]) == len(peer["records"]) == 26, "Phase, observation or reply count differs") + exported = [] + for index, ((label, source), record, observation, reply) in enumerate(zip(expected, peer["records"], result["observations"], matches)): + require(record["phase"] == observation["phase"] == index, "Phase order differs") + require(record["label"] == observation["label"] == label, "Phase label differs") + require(record["source_hex"] == source.hex() and record["source_sha256"] == hashlib.sha256(source).hexdigest(), "Phase source identity differs") + cursor = [int(reply[2]), int(reply[3])] + require(observation["cursor_reply"] == record["cursor_reply"] == cursor, "Cursor reply differs across retained records") + require((directory / f"ack-{index}").read_text() == "observed", "Stage acknowledgment is missing or changed") + terminal = observation["terminal"] + require(terminal["protocol"] == "Telnet" and terminal["connected"] is True, "Terminal protocol or connection differs") + require(terminal["capacity"] == peer["rows"], "Captured UI capacity differs from final NAWS") + require(terminal["line_count"] == len(terminal["lines"]) <= 200, "Document projection exceeds its declared bound") + for line in terminal["lines"]: + require(SYNTHETIC_LINE.fullmatch(line), "Only synthetic fixture text is publishable") + exported.append({**record, "cursor_reply_hex": reply[0].hex(), "cursor_reply_private_prefix": reply[1].decode("ascii"), "terminal_document_projection": terminal}) + require(not normalized["messages_truncated"] and not normalized["cleanup_error"] and not evidence_record["data"]["cleanup_error"], "Provider messages are truncated or cleanup failed") + require(not any(message["type"] == "error" for message in normalized["messages"]), "Provider contains a script error") + result = { + "schema": "termforge-original-alternate-reference-v1", "issue": 118, + "scope": "One instrumented original WindTerm terminal run over owned loopback Telnet; no local-shell transport or full-grid/style parity claim.", + "source_base_commit": OBSERVATION_BASE_COMMIT, + "export_source_base_commit": subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip(), + "reference_identity": {"exe_sha256": run["sha256"], "files_manifest_sha256": run["files_sha256"], "files_count": 4047}, + "probe_files_sha256": {name: sha(ROOT / name) for name in ("research/ui/alternate_reference.py", "research/ui/alternate_reference.js", "research/ui/export_alternate_reference.py", "research/ui/r1_launch.py", "research/ui/launch.py", "research/ui/qt_r1_text.js")}, + "status": "observed-all-phases", "actual_original_runs": 1, "maximum_original_runs": 1, + "rea_capture_seconds": 45, "cli_wait_timeout_seconds": 60, + "final_observed_naws": [peer["columns"], peer["rows"]], "geometry_retries": 0, + "source_generation_rows": source_generation_rows, "source_generation_rows_evidence": "Unique exact match of all retained source_hex records against the public deterministic generator; not a retained first-NAWS event.", + "initial_naws": None, "naws_transition_history": None, + "phase_transport": "Each phase is sent once followed by CSI 6n. The peer waits for a UI snapshot acknowledgment before sending the next phase; TCP fragmentation is not prescribed.", + "dsr_request_hex": "1b5b366e", "dsr_reply_count": len(matches), "unmatched_application_reply_hex": "", + "phases": exported, + "provider": {"evidence_id": evidence["evidence_id"], "provider": evidence["provider"], "confidence": evidence["confidence"], "subject": evidence["subject"], "environment": evidence["environment"], "messages_truncated": normalized["messages_truncated"], "cleanup_error": normalized["cleanup_error"], "limitations": evidence["limitations"]}, + "lifecycle": {"outer_job": cleanup, "deliberate_original_process_termination": True, "natural_gui_exit_verified": False, "profile": profile, "stderr_bytes": (directory/"stderr.txt").stat().st_size}, + "raw_files": {name: {"bytes": (directory/name).stat().st_size, "sha256": sha(directory/name)} for name in ("evidence.json", "run.json", "probe.js", "probe-result.json", "peer.json", "cleanup.json", "profile-check.json", "stderr.txt")}, + "local_shell_transport_verified": False, "full_active_grid_verified": False, "terminal_styles_verified": False, + "scroll_region_verified": False, "resize_verified": False, "windterm_parity": False, "r2_complete": False, "presented_done": None, + "limitations": [ + "The original direct-terminal Telnet path does not prove WinPTY/ConPTY local-shell semantics or resolve #41/#56.", + "activeText lineText and visibleLineCount expose a retained document projection including main text during alternate mode, not a complete active grid.", + "scrollTop, caretLine and caretColumn are raw exposed UI values; caretColumn remains 5 even when DSR changes and is not treated as terminal cursor state.", + "Cell styles/current style, active-buffer flags, full viewport mapping and pending-wrap are unobserved/null.", + "Duplicate 1049h homes the protocol cursor and removes prior alternate markers from the returned document projection; full-grid clearing remains unproven.", + "47 and 1047 are observed after earlier 1049 use; fresh-session or all cross-mode transitions are not covered.", + "1048l does not restore the cursor in this specific input; this is not a claim that every 1048 sequence is unsupported.", + "One run is not repeated reliability, bytewise parser coverage, presented-frame verification or a performance measurement.", + "45 seconds is the REA capture duration. The CLI wait timeout is 60 seconds; the original process resumes before a separate 2-second delay and cleanup follows the CLI wait. No 45-second whole-lifecycle guarantee is established.", + "The fixture chose source_generation_rows=24 at first NAWS, while final NAWS and all captured UI capacities are 25 rows. Initial negotiation and transition history were not retained; the run does not prove a fixed grid throughout startup.", + "The original GUI is deliberately terminated by its owned launcher; Job active=0 is cleanup proof, not natural exit proof.", + "Provider subject/environment are null. Pinned executable/package identity is independently enforced by the existing preparation guard.", + "Raw instrumentation, absolute paths, process identifiers, copied original resources and synthetic profile remain local." + ], + } + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_text(json.dumps(result, ensure_ascii=False, indent=2)+"\n", encoding="utf-8", newline="\n") + print(json.dumps({"exported": destination.as_posix(), "phases": len(exported), "dsr_replies": len(matches)})) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--directory", type=Path, required=True) + parser.add_argument("--destination", type=Path, required=True) + args = parser.parse_args() + export(args.directory, args.destination) diff --git a/research/ui/results/2026-10-28-alternate-reference/validation.json b/research/ui/results/2026-10-28-alternate-reference/validation.json new file mode 100644 index 00000000..b75a8660 --- /dev/null +++ b/research/ui/results/2026-10-28-alternate-reference/validation.json @@ -0,0 +1,1901 @@ +{ + "schema": "termforge-original-alternate-reference-v1", + "issue": 118, + "scope": "One instrumented original WindTerm terminal run over owned loopback Telnet; no local-shell transport or full-grid/style parity claim.", + "source_base_commit": "01673e89a7db718a1a6faf63991a50b2531710fa", + "export_source_base_commit": "bd1badfb03d5be609e02ad83ba528c4a05f87be8", + "reference_identity": { + "exe_sha256": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "files_manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203", + "files_count": 4047 + }, + "probe_files_sha256": { + "research/ui/alternate_reference.py": "94b85d49347e0e9813f7acddf0244f58520a7d1fce14084677c91cf4ddafffea", + "research/ui/alternate_reference.js": "1fb2eec99336902ddeeab5fffd76ac85381ba203aae9df048f51f11936542eb6", + "research/ui/export_alternate_reference.py": "9930c1cec3003dc54d0479c1b787379aae0c7bd08903e6c9645e3a85c5faba3e", + "research/ui/r1_launch.py": "54fb41872c6abcc48242a0938044184854b016785cc13659c66b1bf404da0d8a", + "research/ui/launch.py": "6970e0d4f2fe5e7ec4827df7aa1026cd0e74aeb828cdc9b7a0b42b3c887eb4e6", + "research/ui/qt_r1_text.js": "f5820d413fdd72c13dffcefbf4d0ade615abfe15754c6b0b52e1e098814eea6f" + }, + "status": "observed-all-phases", + "actual_original_runs": 1, + "maximum_original_runs": 1, + "rea_capture_seconds": 45, + "cli_wait_timeout_seconds": 60, + "final_observed_naws": [ + 91, + 25 + ], + "geometry_retries": 0, + "source_generation_rows": 24, + "source_generation_rows_evidence": "Unique exact match of all retained source_hex records against the public deterministic generator; not a retained first-NAWS event.", + "initial_naws": null, + "naws_transition_history": null, + "phase_transport": "Each phase is sent once followed by CSI 6n. The peer waits for a UI snapshot acknowledgment before sending the next phase; TCP fragmentation is not prescribed.", + "dsr_request_hex": "1b5b366e", + "dsr_reply_count": 26, + "unmatched_application_reply_hex": "", + "phases": [ + { + "phase": 0, + "label": "main-before", + "source_hex": "1b5b306d1b5b324a1b5b484d41494e5f411b5b333b36481b5b33313b34346d", + "source_bytes": 31, + "source_sha256": "15b13c5b019fdc9d60f5503c20de1a8797b454eaccd4c20bc09cb19bc879bc96", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 3, + 6 + ], + "cursor_reply_hex": "1b5b333b3652", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 3, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " " + ], + "caret_api": { + "caretLine": 2, + "caretColumn": 5, + "caretPosition": 13, + "selectionCaretP": 13 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 1, + "label": "1049-first-enter", + "source_hex": "1b5b3f3130343968", + "source_bytes": 8, + "source_sha256": "b7c9b6f9082bb2a92f63566d8800008f96b5dee9d380c2d460b2466ad1496440", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 1 + ], + "cursor_reply_hex": "1b5b313b3152", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 3, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + "" + ], + "caret_api": { + "caretLine": 0, + "caretColumn": 5, + "caretPosition": 0, + "selectionCaretP": 8 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 2, + "label": "1049-alt-written", + "source_hex": "414c545f4f4e451b5b353b39481b5b33323b34356d", + "source_bytes": 21, + "source_sha256": "d10d1b2936300ae99996ff798c12820411efcfe2fdff4a6f8e33ad0d30ed8a8c", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 5, + 9 + ], + "cursor_reply_hex": "1b5b353b3952", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 2, + "line_count": 7, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + "ALT_ONE", + "", + "", + "", + " " + ], + "caret_api": { + "caretLine": 6, + "caretColumn": 5, + "caretPosition": 27, + "selectionCaretP": 27 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 3, + "label": "1049-duplicate-enter", + "source_hex": "1b5b3f3130343968", + "source_bytes": 8, + "source_sha256": "b7c9b6f9082bb2a92f63566d8800008f96b5dee9d380c2d460b2466ad1496440", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 1 + ], + "cursor_reply_hex": "1b5b313b3152", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 3, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + "" + ], + "caret_api": { + "caretLine": 0, + "caretColumn": 5, + "caretPosition": 0, + "selectionCaretP": 8 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 4, + "label": "1049-duplicate-write", + "source_hex": "445550", + "source_bytes": 3, + "source_sha256": "bee4be1e75ef5ec13f41ecbffd640e197d96307b577e2f8c0ffed1bad5c41246", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 4 + ], + "cursor_reply_hex": "1b5b313b3452", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 2, + "line_count": 3, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + "DUP" + ], + "caret_api": { + "caretLine": 2, + "caretColumn": 5, + "caretPosition": 11, + "selectionCaretP": 11 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 5, + "label": "1049-first-exit", + "source_hex": "1b5b3f313034396c", + "source_bytes": 8, + "source_sha256": "36a3d2013e70720f8d7c0105d9b9c23acfa208b175099330c7c820ad3e1e1df5", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 3, + 6 + ], + "cursor_reply_hex": "1b5b333b3652", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 3, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " " + ], + "caret_api": { + "caretLine": 2, + "caretColumn": 5, + "caretPosition": 13, + "selectionCaretP": 13 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 6, + "label": "1049-main-write", + "source_hex": "4d", + "source_bytes": 1, + "source_sha256": "08f271887ce94707da822d5263bae19d5519cb3614e0daedc4c7ce5dab7473f1", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 3, + 7 + ], + "cursor_reply_hex": "1b5b333b3752", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 3, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M" + ], + "caret_api": { + "caretLine": 2, + "caretColumn": 5, + "caretPosition": 14, + "selectionCaretP": 14 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 7, + "label": "1049-duplicate-exit", + "source_hex": "1b5b3f313034396c", + "source_bytes": 8, + "source_sha256": "36a3d2013e70720f8d7c0105d9b9c23acfa208b175099330c7c820ad3e1e1df5", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 3, + 7 + ], + "cursor_reply_hex": "1b5b333b3752", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 3, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M" + ], + "caret_api": { + "caretLine": 2, + "caretColumn": 5, + "caretPosition": 14, + "selectionCaretP": 14 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 8, + "label": "1049-second-enter", + "source_hex": "1b5b3f3130343968", + "source_bytes": 8, + "source_sha256": "b7c9b6f9082bb2a92f63566d8800008f96b5dee9d380c2d460b2466ad1496440", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 1 + ], + "cursor_reply_hex": "1b5b313b3152", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 4, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "" + ], + "caret_api": { + "caretLine": 0, + "caretColumn": 5, + "caretPosition": 0, + "selectionCaretP": 15 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 9, + "label": "1049-second-write", + "source_hex": "414c545f54574f", + "source_bytes": 7, + "source_sha256": "b1138ed877e903cc22568fd009dddeda589a13a00b2a1d1e2ca064e738f80b23", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 8 + ], + "cursor_reply_hex": "1b5b313b3852", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 3, + "line_count": 4, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "ALT_TWO" + ], + "caret_api": { + "caretLine": 3, + "caretColumn": 5, + "caretPosition": 22, + "selectionCaretP": 22 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 10, + "label": "1049-alt-pressure", + "source_hex": "0d0a414c545f4c494e455f30300d0a414c545f4c494e455f30310d0a414c545f4c494e455f30320d0a414c545f4c494e455f30330d0a414c545f4c494e455f30340d0a414c545f4c494e455f30350d0a414c545f4c494e455f30360d0a414c545f4c494e455f30370d0a414c545f4c494e455f30380d0a414c545f4c494e455f30390d0a414c545f4c494e455f31300d0a414c545f4c494e455f31310d0a414c545f4c494e455f31320d0a414c545f4c494e455f31330d0a414c545f4c494e455f31340d0a414c545f4c494e455f31350d0a414c545f4c494e455f31360d0a414c545f4c494e455f31370d0a414c545f4c494e455f31380d0a414c545f4c494e455f31390d0a414c545f4c494e455f32300d0a414c545f4c494e455f32310d0a414c545f4c494e455f32320d0a414c545f4c494e455f32330d0a414c545f4c494e455f32340d0a414c545f4c494e455f32350d0a414c545f4c494e455f32360d0a414c545f4c494e455f32370d0a414c545f4c494e455f3238", + "source_bytes": 377, + "source_sha256": "9681041830e467fb9240bc689ae5f03666a4cf164c6ed6b17c82a75b2ce1f345", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 25, + 12 + ], + "cursor_reply_hex": "1b5b32353b313252", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 3, + "line_count": 28, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "ALT_LINE_04", + "ALT_LINE_05", + "ALT_LINE_06", + "ALT_LINE_07", + "ALT_LINE_08", + "ALT_LINE_09", + "ALT_LINE_10", + "ALT_LINE_11", + "ALT_LINE_12", + "ALT_LINE_13", + "ALT_LINE_14", + "ALT_LINE_15", + "ALT_LINE_16", + "ALT_LINE_17", + "ALT_LINE_18", + "ALT_LINE_19", + "ALT_LINE_20", + "ALT_LINE_21", + "ALT_LINE_22", + "ALT_LINE_23", + "ALT_LINE_24", + "ALT_LINE_25", + "ALT_LINE_26", + "ALT_LINE_27", + "ALT_LINE_28" + ], + "caret_api": { + "caretLine": 27, + "caretColumn": 5, + "caretPosition": 314, + "selectionCaretP": 314 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 11, + "label": "1049-second-exit", + "source_hex": "1b5b3f313034396c", + "source_bytes": 8, + "source_sha256": "36a3d2013e70720f8d7c0105d9b9c23acfa208b175099330c7c820ad3e1e1df5", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 3, + 7 + ], + "cursor_reply_hex": "1b5b333b3752", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 3, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M" + ], + "caret_api": { + "caretLine": 2, + "caretColumn": 5, + "caretPosition": 14, + "selectionCaretP": 14 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 12, + "label": "47-first-enter", + "source_hex": "1b5b3f343768", + "source_bytes": 6, + "source_sha256": "fbe3d90cc187a5702b7a513a2ec869442045d839f2e9ff330b2e70806fe7d9e8", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 25, + 12 + ], + "cursor_reply_hex": "1b5b32353b313252", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 3, + "line_count": 28, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "ALT_LINE_04", + "ALT_LINE_05", + "ALT_LINE_06", + "ALT_LINE_07", + "ALT_LINE_08", + "ALT_LINE_09", + "ALT_LINE_10", + "ALT_LINE_11", + "ALT_LINE_12", + "ALT_LINE_13", + "ALT_LINE_14", + "ALT_LINE_15", + "ALT_LINE_16", + "ALT_LINE_17", + "ALT_LINE_18", + "ALT_LINE_19", + "ALT_LINE_20", + "ALT_LINE_21", + "ALT_LINE_22", + "ALT_LINE_23", + "ALT_LINE_24", + "ALT_LINE_25", + "ALT_LINE_26", + "ALT_LINE_27", + "ALT_LINE_28" + ], + "caret_api": { + "caretLine": 27, + "caretColumn": 5, + "caretPosition": 314, + "selectionCaretP": 314 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 13, + "label": "47-alt-written", + "source_hex": "414c5434371b5b363b3131481b5b33333b34366d", + "source_bytes": 20, + "source_sha256": "ed5fe553abe2395d40807389fff72831435826e01fa4c60db46d57abe8d96471", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 6, + 11 + ], + "cursor_reply_hex": "1b5b363b313152", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 3, + "line_count": 28, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "ALT_LINE_04", + "ALT_LINE_05", + "ALT_LINE_06", + "ALT_LINE_07", + "ALT_LINE_08", + "ALT_LINE_09", + "ALT_LINE_10", + "ALT_LINE_11", + "ALT_LINE_12", + "ALT_LINE_13", + "ALT_LINE_14", + "ALT_LINE_15", + "ALT_LINE_16", + "ALT_LINE_17", + "ALT_LINE_18", + "ALT_LINE_19", + "ALT_LINE_20", + "ALT_LINE_21", + "ALT_LINE_22", + "ALT_LINE_23", + "ALT_LINE_24", + "ALT_LINE_25", + "ALT_LINE_26", + "ALT_LINE_27", + "ALT_LINE_28ALT47" + ], + "caret_api": { + "caretLine": 8, + "caretColumn": 5, + "caretPosition": 85, + "selectionCaretP": 319 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 14, + "label": "47-first-exit", + "source_hex": "1b5b3f34376c", + "source_bytes": 6, + "source_sha256": "5adb756b34344c3429da13b200514e8397843cd843bdce4742d1318aefb4420f", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 3, + 7 + ], + "cursor_reply_hex": "1b5b333b3752", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 3, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M" + ], + "caret_api": { + "caretLine": 2, + "caretColumn": 5, + "caretPosition": 14, + "selectionCaretP": 14 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 15, + "label": "47-second-enter", + "source_hex": "1b5b3f343768", + "source_bytes": 6, + "source_sha256": "fbe3d90cc187a5702b7a513a2ec869442045d839f2e9ff330b2e70806fe7d9e8", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 6, + 11 + ], + "cursor_reply_hex": "1b5b363b313152", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 3, + "line_count": 28, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "ALT_LINE_04", + "ALT_LINE_05", + "ALT_LINE_06", + "ALT_LINE_07", + "ALT_LINE_08", + "ALT_LINE_09", + "ALT_LINE_10", + "ALT_LINE_11", + "ALT_LINE_12", + "ALT_LINE_13", + "ALT_LINE_14", + "ALT_LINE_15", + "ALT_LINE_16", + "ALT_LINE_17", + "ALT_LINE_18", + "ALT_LINE_19", + "ALT_LINE_20", + "ALT_LINE_21", + "ALT_LINE_22", + "ALT_LINE_23", + "ALT_LINE_24", + "ALT_LINE_25", + "ALT_LINE_26", + "ALT_LINE_27", + "ALT_LINE_28ALT47" + ], + "caret_api": { + "caretLine": 8, + "caretColumn": 5, + "caretPosition": 85, + "selectionCaretP": 319 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 16, + "label": "47-second-exit", + "source_hex": "1b5b3f34376c", + "source_bytes": 6, + "source_sha256": "5adb756b34344c3429da13b200514e8397843cd843bdce4742d1318aefb4420f", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 3, + 7 + ], + "cursor_reply_hex": "1b5b333b3752", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 3, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M" + ], + "caret_api": { + "caretLine": 2, + "caretColumn": 5, + "caretPosition": 14, + "selectionCaretP": 14 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 17, + "label": "1047-first-enter", + "source_hex": "1b5b3f3130343768", + "source_bytes": 8, + "source_sha256": "6ab7a90cda4716245572ca816c96ecd86e25a1bc3942bda7af545c3869083673", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 6, + 11 + ], + "cursor_reply_hex": "1b5b363b313152", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 3, + "line_count": 28, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "ALT_LINE_04", + "ALT_LINE_05", + "ALT_LINE_06", + "ALT_LINE_07", + "ALT_LINE_08", + "ALT_LINE_09", + "ALT_LINE_10", + "ALT_LINE_11", + "ALT_LINE_12", + "ALT_LINE_13", + "ALT_LINE_14", + "ALT_LINE_15", + "ALT_LINE_16", + "ALT_LINE_17", + "ALT_LINE_18", + "ALT_LINE_19", + "ALT_LINE_20", + "ALT_LINE_21", + "ALT_LINE_22", + "ALT_LINE_23", + "ALT_LINE_24", + "ALT_LINE_25", + "ALT_LINE_26", + "ALT_LINE_27", + "ALT_LINE_28ALT47" + ], + "caret_api": { + "caretLine": 8, + "caretColumn": 5, + "caretPosition": 85, + "selectionCaretP": 319 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 18, + "label": "1047-alt-written", + "source_hex": "414c54313034371b5b373b313248", + "source_bytes": 14, + "source_sha256": "2b55ff77f9f3a7a2b66b8d0371b1832d16cf6f20cbe131bfb1a9989acbbc183d", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 7, + 12 + ], + "cursor_reply_hex": "1b5b373b313252", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 3, + "line_count": 28, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "ALT_LINE_04", + "ALT_LINE_05", + "ALT_LINE_06", + "ALT_LINE_07", + "ALT_LINE_08", + "ALT_LINE_0ALT1047", + "ALT_LINE_10", + "ALT_LINE_11", + "ALT_LINE_12", + "ALT_LINE_13", + "ALT_LINE_14", + "ALT_LINE_15", + "ALT_LINE_16", + "ALT_LINE_17", + "ALT_LINE_18", + "ALT_LINE_19", + "ALT_LINE_20", + "ALT_LINE_21", + "ALT_LINE_22", + "ALT_LINE_23", + "ALT_LINE_24", + "ALT_LINE_25", + "ALT_LINE_26", + "ALT_LINE_27", + "ALT_LINE_28ALT47" + ], + "caret_api": { + "caretLine": 9, + "caretColumn": 5, + "caretPosition": 104, + "selectionCaretP": 325 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 19, + "label": "1047-first-exit", + "source_hex": "1b5b3f313034376c", + "source_bytes": 8, + "source_sha256": "28fdacbcc209f390a374ef78fe277415633ec70eb280e44d4eb2c5d827cb791c", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 3, + 7 + ], + "cursor_reply_hex": "1b5b333b3752", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 3, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M" + ], + "caret_api": { + "caretLine": 2, + "caretColumn": 5, + "caretPosition": 14, + "selectionCaretP": 14 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 20, + "label": "1047-second-enter", + "source_hex": "1b5b3f3130343768", + "source_bytes": 8, + "source_sha256": "6ab7a90cda4716245572ca816c96ecd86e25a1bc3942bda7af545c3869083673", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 7, + 12 + ], + "cursor_reply_hex": "1b5b373b313252", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 3, + "line_count": 28, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "ALT_LINE_04", + "ALT_LINE_05", + "ALT_LINE_06", + "ALT_LINE_07", + "ALT_LINE_08", + "ALT_LINE_0ALT1047", + "ALT_LINE_10", + "ALT_LINE_11", + "ALT_LINE_12", + "ALT_LINE_13", + "ALT_LINE_14", + "ALT_LINE_15", + "ALT_LINE_16", + "ALT_LINE_17", + "ALT_LINE_18", + "ALT_LINE_19", + "ALT_LINE_20", + "ALT_LINE_21", + "ALT_LINE_22", + "ALT_LINE_23", + "ALT_LINE_24", + "ALT_LINE_25", + "ALT_LINE_26", + "ALT_LINE_27", + "ALT_LINE_28ALT47" + ], + "caret_api": { + "caretLine": 9, + "caretColumn": 5, + "caretPosition": 104, + "selectionCaretP": 325 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 21, + "label": "1047-second-exit", + "source_hex": "1b5b3f313034376c", + "source_bytes": 8, + "source_sha256": "28fdacbcc209f390a374ef78fe277415633ec70eb280e44d4eb2c5d827cb791c", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 3, + 7 + ], + "cursor_reply_hex": "1b5b333b3752", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 3, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M" + ], + "caret_api": { + "caretLine": 2, + "caretColumn": 5, + "caretPosition": 14, + "selectionCaretP": 14 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 22, + "label": "1048-save", + "source_hex": "1b5b383b3133481b5b33313b34346d1b5b3f3130343868", + "source_bytes": 23, + "source_sha256": "5fd1a954fa749c4f121f64679e24a09e4a440bff74ce42bc7e3857ebd3189300", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 8, + 13 + ], + "cursor_reply_hex": "1b5b383b313352", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 8, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "", + "", + "", + "", + " " + ], + "caret_api": { + "caretLine": 7, + "caretColumn": 5, + "caretPosition": 31, + "selectionCaretP": 31 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 23, + "label": "1048-moved", + "source_hex": "1b5b31323b3230481b5b33323b34356d", + "source_bytes": 16, + "source_sha256": "b4fb44b59dc3dab31875f199ab1f8c3dffeaaefbf9337eb101566122f8a46de2", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 12, + 20 + ], + "cursor_reply_hex": "1b5b31323b323052", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 12, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "", + "", + "", + "", + "", + "", + "", + "", + " " + ], + "caret_api": { + "caretLine": 11, + "caretColumn": 5, + "caretPosition": 42, + "selectionCaretP": 42 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 24, + "label": "1048-restore", + "source_hex": "1b5b3f313034386c", + "source_bytes": 8, + "source_sha256": "c6ab4ddd80bdf7edddff782a00f84ec2b80ff0922dba8b334b2e9affcd70a206", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 12, + 20 + ], + "cursor_reply_hex": "1b5b31323b323052", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 12, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "", + "", + "", + "", + "", + "", + "", + "", + " " + ], + "caret_api": { + "caretLine": 11, + "caretColumn": 5, + "caretPosition": 42, + "selectionCaretP": 42 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + }, + { + "phase": 25, + "label": "1048-restored-write", + "source_hex": "51", + "source_bytes": 1, + "source_sha256": "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 12, + 21 + ], + "cursor_reply_hex": "1b5b31323b323152", + "cursor_reply_private_prefix": "", + "terminal_document_projection": { + "protocol": "Telnet", + "connected": true, + "top": null, + "scroll": 0, + "line_count": 12, + "capacity": 25, + "lines": [ + "MAIN_A", + "", + " M", + "", + "", + "", + "", + "", + "", + "", + "", + " Q" + ], + "caret_api": { + "caretLine": 11, + "caretColumn": 5, + "caretPosition": 43, + "selectionCaretP": 43 + }, + "cell_styles": null, + "current_style": null, + "api_names": [ + "cursor", + "fullScreen", + "styleSheet", + "caretColumn", + "caretLine", + "caretLineOffset", + "caretPosition", + "visibleLineCount", + "lineCount", + "screenLineCapacity", + "setStyleSheet", + "showFullScreen", + "caretPositionChanged", + "marginStyleChanged", + "schemeAnsiStyleChanged", + "exchangeCaretAnchor", + "selectionCaretP", + "screenSizeChanged", + "caretVirtualSpace", + "clearScreen", + "clearScreenAndScrollback", + "isAltScreen", + "isMainScreen" + ] + } + } + ], + "provider": { + "evidence_id": "ev_3bdab796b335d64f0ffc8dc26bbcfd3c1441a2805993157451f8602311609a6f", + "provider": { + "id": "frida", + "name": "Frida", + "version": null + }, + "confidence": "observed", + "subject": null, + "environment": null, + "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." + ] + }, + "lifecycle": { + "outer_job": { + "active": 0, + "total": 3, + "terminated": 0 + }, + "deliberate_original_process_termination": true, + "natural_gui_exit_verified": false, + "profile": { + "only_synthetic_session": true, + "session_count": 1 + }, + "stderr_bytes": 0 + }, + "raw_files": { + "evidence.json": { + "bytes": 48375, + "sha256": "860f5cf1f0225eaa74c31e1c317393f78f3cfbc3fa5028c00d1d23f3af001c22" + }, + "run.json": { + "bytes": 619, + "sha256": "1ece9aaeaacbb103db1ac35039d33417b6f9f7834f69d163c30442488aa7a1ac" + }, + "probe.js": { + "bytes": 18444, + "sha256": "284b89d9460394c6b8f160e420af0d7fb9ca1dc0df3d9647643c949f191f815d" + }, + "probe-result.json": { + "bytes": 21822, + "sha256": "6332afb80888e65aba700521ef9cbb55716765c5ddea6e3b02e79eb4211704f3" + }, + "peer.json": { + "bytes": 7496, + "sha256": "87640bafe17d60dd2d7b89f960f58bd655099f74ad927ccfcfad499ad14dfcb1" + }, + "cleanup.json": { + "bytes": 42, + "sha256": "8400f93b6a4a4a50cde35b5d474660590ed215732f55772a9d8afac138da4f1d" + }, + "profile-check.json": { + "bytes": 52, + "sha256": "5e522a64e98197722ccd084639b1ce50258b238e03be2f016e81c8af023b5ee1" + }, + "stderr.txt": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + "local_shell_transport_verified": false, + "full_active_grid_verified": false, + "terminal_styles_verified": false, + "scroll_region_verified": false, + "resize_verified": false, + "windterm_parity": false, + "r2_complete": false, + "presented_done": null, + "limitations": [ + "The original direct-terminal Telnet path does not prove WinPTY/ConPTY local-shell semantics or resolve #41/#56.", + "activeText lineText and visibleLineCount expose a retained document projection including main text during alternate mode, not a complete active grid.", + "scrollTop, caretLine and caretColumn are raw exposed UI values; caretColumn remains 5 even when DSR changes and is not treated as terminal cursor state.", + "Cell styles/current style, active-buffer flags, full viewport mapping and pending-wrap are unobserved/null.", + "Duplicate 1049h homes the protocol cursor and removes prior alternate markers from the returned document projection; full-grid clearing remains unproven.", + "47 and 1047 are observed after earlier 1049 use; fresh-session or all cross-mode transitions are not covered.", + "1048l does not restore the cursor in this specific input; this is not a claim that every 1048 sequence is unsupported.", + "One run is not repeated reliability, bytewise parser coverage, presented-frame verification or a performance measurement.", + "45 seconds is the REA capture duration. The CLI wait timeout is 60 seconds; the original process resumes before a separate 2-second delay and cleanup follows the CLI wait. No 45-second whole-lifecycle guarantee is established.", + "The fixture chose source_generation_rows=24 at first NAWS, while final NAWS and all captured UI capacities are 25 rows. Initial negotiation and transition history were not retained; the run does not prove a fixed grid throughout startup.", + "The original GUI is deliberately terminated by its owned launcher; Job active=0 is cleanup proof, not natural exit proof.", + "Provider subject/environment are null. Pinned executable/package identity is independently enforced by the existing preparation guard.", + "Raw instrumentation, absolute paths, process identifiers, copied original resources and synthetic profile remain local." + ] +} diff --git a/research/ui/test_export_alternate_reference.py b/research/ui/test_export_alternate_reference.py new file mode 100644 index 00000000..5710bf88 --- /dev/null +++ b/research/ui/test_export_alternate_reference.py @@ -0,0 +1,132 @@ +"""Public exporter CLI regressions using only a generated synthetic fixture.""" +from __future__ import annotations +import copy +import hashlib +import json +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) +from research.ui.alternate_reference import phases + +EXPORTER = ROOT / "research/ui/export_alternate_reference.py" + + +def fixture(directory: Path): + """Create value-consistent test records, without launching any application.""" + directory.mkdir() + script = b"// Synthetic exporter test fixture; not original runtime evidence.\n" + (directory / "probe.js").write_bytes(script) + (directory / "stderr.txt").write_bytes(b"") + records, observations = [], [] + for index, (label, source) in enumerate(phases(25)): + records.append({"phase": index, "label": label, "source_hex": source.hex(), + "source_sha256": hashlib.sha256(source).hexdigest(), "cursor_reply": [1, 1]}) + observations.append({"phase": index, "label": label, "cursor_reply": [1, 1], + "terminal": {"protocol": "Telnet", "connected": True, "capacity": 25, + "line_count": 1, "lines": ["MAIN_A"]}}) + (directory / f"ack-{index}").write_text("observed", encoding="utf-8") + result = {"status": "observed-all-phases", "error": None, "observations": observations} + normalized = {"source_sha256": hashlib.sha256(script).hexdigest(), + "messages_truncated": False, "cleanup_error": None} + data = { + "run.json": {"sha256": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "files_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203", + "script_sha256": hashlib.sha256(script).hexdigest()}, + "peer.json": {"complete": True, "columns": 91, "rows": 25, "records": records, + "received_hex": (b"\x1b[1;1R" * 26).hex()}, + "probe-result.json": result, + "evidence.json": {"ok": True, "data": {"cleanup_error": None, "evidence": { + "normalized_result": normalized, "evidence_id": "synthetic-export-test-only", + "provider": {"name": "synthetic-test"}, "confidence": "test-fixture", + "subject": None, "environment": None, "limitations": ["Not runtime evidence."]}}}, + "cleanup.json": {"active": 0, "total": 0, "terminated": 0}, + "profile-check.json": {"only_synthetic_session": True, "session_count": 1}, + } + synchronize_messages(data) + write_records(directory, data) + return data + + +def synchronize_messages(data): + result = data["probe-result.json"] + messages = [{"type": "send", "payload": {"kind": "alternate_phase", **copy.deepcopy(observation)}} + for observation in result["observations"]] + messages.append({"type": "send", "payload": {"kind": "alternate_reference_result", **copy.deepcopy(result)}}) + data["evidence.json"]["data"]["evidence"]["normalized_result"]["messages"] = messages + + +def write_records(directory, data): + for name, value in data.items(): + (directory / name).write_text(json.dumps(value), encoding="utf-8") + + +class ExportCliTests(unittest.TestCase): + def run_cli(self, directory, destination, optimized): + return subprocess.run([sys.executable, *(["-O"] if optimized else []), str(EXPORTER), + "--directory", str(directory), "--destination", str(destination)], + cwd=ROOT, capture_output=True, text=True, timeout=10) + + def check_rejected(self, change, reason=None): + for optimized in (False, True): + with self.subTest(optimized=optimized), tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + directory, destination = root / "fixture", root / "public/validation.json" + data = fixture(directory) + change(data, directory) + write_records(directory, data) + completed = self.run_cli(directory, destination, optimized) + self.assertNotEqual(completed.returncode, 0, completed.stdout) + self.assertFalse(destination.exists(), "A rejected record produced public output") + self.assertFalse(destination.parent.exists(), "A failed gate created the public directory") + if reason: + self.assertIn(reason, completed.stderr) + + def test_valid_fixture_exports_in_normal_and_optimized_python(self): + for optimized in (False, True): + with self.subTest(optimized=optimized), tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + fixture(root / "fixture") + destination = root / "public/validation.json" + completed = self.run_cli(root / "fixture", destination, optimized) + self.assertEqual(completed.returncode, 0, completed.stderr) + exported = json.loads(destination.read_text(encoding="utf-8")) + self.assertEqual(exported["dsr_reply_count"], 26) + self.assertEqual(exported["phases"][0]["terminal_document_projection"]["lines"], ["MAIN_A"]) + self.assertEqual(exported["rea_capture_seconds"], 45) + self.assertEqual(exported["cli_wait_timeout_seconds"], 60) + self.assertNotIn("maximum_cli_seconds", exported) + + def test_private_or_nonfixture_text_is_rejected_even_when_records_agree(self): + for marker in ("PRIVATE_REVIEW_MARKER", "PRIVATE_REVIEW_MARKER/host/path", "C:\\host\\private"): + with self.subTest(marker=marker): + def change(data, directory): + data["probe-result.json"]["observations"][0]["terminal"]["lines"][0] = marker + synchronize_messages(data) + self.check_rejected(change, "Only synthetic fixture text is publishable") + + def test_wrong_original_identity_is_rejected(self): + self.check_rejected(lambda data, directory: data["run.json"].update(sha256="0" * 64)) + + def test_changed_probe_script_is_rejected(self): + self.check_rejected(lambda data, directory: (directory / "probe.js").write_text("changed script")) + + def test_changed_source_is_rejected(self): + self.check_rejected(lambda data, directory: data["peer.json"]["records"][0].update(source_hex="00")) + + def test_missing_stage_acknowledgment_is_rejected(self): + self.check_rejected(lambda data, directory: (directory / "ack-0").write_text("not observed")) + + def test_inconsistent_observation_is_rejected(self): + self.check_rejected(lambda data, directory: data["probe-result.json"]["observations"][0].update(cursor_reply=[2, 2])) + + def test_unreclaimed_job_is_rejected(self): + self.check_rejected(lambda data, directory: data["cleanup.json"].update(active=1)) + + +if __name__ == "__main__": + unittest.main()